update vendor/

This commit is contained in:
Ettore Di Giacinto
2019-06-05 19:14:29 +02:00
parent 0bb72e67fd
commit e2442f8ed8
601 changed files with 439146 additions and 0 deletions

166
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,166 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:4ca90d338f8308896cc66929935175fa18a34ecda213d7b836a484961da885de"
name = "github.com/crillab/gophersat"
packages = [
"bf",
"solver",
]
pruneopts = "UT"
revision = "29177c297796f794c4a8c1b7dadfeaabc19de141"
version = "v1.1.7"
[[projects]]
branch = "master"
digest = "1:59392ed8afb901aab4287d4894df8191722e34f3957716f4350c8c133ce99046"
name = "github.com/hpcloud/tail"
packages = [
".",
"ratelimiter",
"util",
"watch",
"winfile",
]
pruneopts = "UT"
revision = "a1dbeea552b7c8df4b542c66073e393de198a800"
[[projects]]
branch = "master"
digest = "1:5c3444689562053b027ef3b96372e306adbe0d7d109b6cdd48d01eb80f8bab14"
name = "github.com/jinzhu/copier"
packages = ["."]
pruneopts = "UT"
revision = "7e38e58719c33e0d44d585c4ab477a30f8cb82dd"
[[projects]]
digest = "1:e9c3bb68a6c9470302b8046d4647e0612a2ea6037b9c6a47de60c0a90db504f8"
name = "github.com/onsi/ginkgo"
packages = [
".",
"config",
"internal/codelocation",
"internal/containernode",
"internal/failer",
"internal/leafnodes",
"internal/remote",
"internal/spec",
"internal/spec_iterator",
"internal/specrunner",
"internal/suite",
"internal/testingtproxy",
"internal/writer",
"reporters",
"reporters/stenographer",
"reporters/stenographer/support/go-colorable",
"reporters/stenographer/support/go-isatty",
"types",
]
pruneopts = "UT"
revision = "eea6ad008b96acdaa524f5b409513bf062b500ad"
version = "v1.8.0"
[[projects]]
digest = "1:fe167e8f858cbfc10c721f2000f2446140800bb53fa83c088e03d191275611a4"
name = "github.com/onsi/gomega"
packages = [
".",
"format",
"internal/assertion",
"internal/asyncassertion",
"internal/oraclematcher",
"internal/testingtsupport",
"matchers",
"matchers/support/goraph/bipartitegraph",
"matchers/support/goraph/edge",
"matchers/support/goraph/node",
"matchers/support/goraph/util",
"types",
]
pruneopts = "UT"
revision = "90e289841c1ed79b7a598a7cd9959750cb5e89e2"
version = "v1.5.0"
[[projects]]
branch = "master"
digest = "1:a167b5c532f3245f5a147ade26185f16d6ee8f8d3f6c9846f447e9d8b9705505"
name = "golang.org/x/net"
packages = [
"html",
"html/atom",
"html/charset",
]
pruneopts = "UT"
revision = "60506f45cf65977eb3a9c6e30f995f54a721c271"
[[projects]]
branch = "master"
digest = "1:8f5108406bc43c7669b0d67d282e40d05c9f268615fcaf8c1f0f76965aa3f09f"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "4c4f7f33c9ed00de01c4c741d2177abfcfe19307"
[[projects]]
digest = "1:8a0baffd5559acaa560f854d7d525c02f4fec2d4f8a214398556fb661a10f6e0"
name = "golang.org/x/text"
packages = [
"encoding",
"encoding/charmap",
"encoding/htmlindex",
"encoding/internal",
"encoding/internal/identifier",
"encoding/japanese",
"encoding/korean",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"encoding/unicode",
"internal/gen",
"internal/language",
"internal/language/compact",
"internal/tag",
"internal/utf8internal",
"language",
"runes",
"transform",
"unicode/cldr",
]
pruneopts = "UT"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "gopkg.in/fsnotify/fsnotify.v1"
packages = ["."]
pruneopts = "UT"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:3c839a777de0e6da035c9de900b60cbec463b0a89351192c1ea083eaf9e0fce0"
name = "gopkg.in/tomb.v1"
packages = ["."]
pruneopts = "UT"
revision = "c131134a1947e9afd9cecfe11f4c6dff0732ae58"
[[projects]]
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/crillab/gophersat/bf",
"github.com/jinzhu/copier",
"github.com/onsi/ginkgo",
"github.com/onsi/gomega",
]
solver-name = "gps-cdcl"
solver-version = 1

46
Gopkg.toml Normal file
View File

@@ -0,0 +1,46 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/crillab/gophersat"
version = "1.1.7"
[[constraint]]
branch = "master"
name = "github.com/jinzhu/copier"
[[constraint]]
name = "github.com/onsi/ginkgo"
version = "1.8.0"
[[constraint]]
name = "github.com/onsi/gomega"
version = "1.5.0"
[prune]
go-tests = true
unused-packages = true

7
vendor/github.com/crillab/gophersat/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2017 Centre de Recherche en Informatique de Lens and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

480
vendor/github.com/crillab/gophersat/bf/bf.go generated vendored Normal file
View File

@@ -0,0 +1,480 @@
package bf
import (
"fmt"
"io"
"math"
"sort"
"strconv"
"strings"
"github.com/crillab/gophersat/solver"
)
// A Formula is any kind of boolean formula, not necessarily in CNF.
type Formula interface {
nnf() Formula
String() string
Eval(model map[string]bool) bool
}
// Solve solves the given formula.
// f is first converted as a CNF formula. It is then given to gophersat.
// The function returns a model associating each variable name with its binding, or nil if the formula was not satisfiable.
func Solve(f Formula) map[string]bool {
return asCnf(f).solve()
}
// Dimacs writes the DIMACS CNF version of the formula on w.
// It is useful so as to feed it to any SAT solver.
// The original names of variables is associated with their DIMACS integer counterparts
// in comments, between the prolog and the set of clauses.
// For instance, if the variable "a" is associated with the index 1, there will be a comment line
// "c a=1".
func Dimacs(f Formula, w io.Writer) error {
cnf := asCnf(f)
nbVars := len(cnf.vars.all)
nbClauses := len(cnf.clauses)
prefix := fmt.Sprintf("p cnf %d %d\n", nbVars, nbClauses)
if _, err := io.WriteString(w, prefix); err != nil {
return fmt.Errorf("could not write DIMACS output: %v", err)
}
var pbVars []string
for v := range cnf.vars.pb {
if !v.dummy {
pbVars = append(pbVars, v.name)
}
}
sort.Sort(sort.StringSlice(pbVars))
for _, v := range pbVars {
idx := cnf.vars.pb[pbVar(v)]
line := fmt.Sprintf("c %s=%d\n", v, idx)
if _, err := io.WriteString(w, line); err != nil {
return fmt.Errorf("could not write DIMACS output: %v", err)
}
}
for _, clause := range cnf.clauses {
strClause := make([]string, len(clause))
for i, lit := range clause {
strClause[i] = strconv.Itoa(lit)
}
line := fmt.Sprintf("%s 0\n", strings.Join(strClause, " "))
if _, err := io.WriteString(w, line); err != nil {
return fmt.Errorf("could not write DIMACS output: %v", err)
}
}
return nil
}
// The "true" constant.
type trueConst struct{}
// True is the constant denoting a tautology.
var True Formula = trueConst{}
func (t trueConst) nnf() Formula { return t }
func (t trueConst) String() string { return "" }
func (t trueConst) Eval(model map[string]bool) bool { return true }
// The "false" constant.
type falseConst struct{}
// False is the constant denoting a contradiction.
var False Formula = falseConst{}
func (f falseConst) nnf() Formula { return f }
func (f falseConst) String() string { return "⊥" }
func (f falseConst) Eval(model map[string]bool) bool { return false }
// Var generates a named boolean variable in a formula.
func Var(name string) Formula {
return pbVar(name)
}
func pbVar(name string) variable {
return variable{name: name, dummy: false}
}
func dummyVar(name string) variable {
return variable{name: name, dummy: true}
}
type variable struct {
name string
dummy bool
}
func (v variable) nnf() Formula {
return lit{signed: false, v: v}
}
func (v variable) String() string {
return v.name
}
func (v variable) Eval(model map[string]bool) bool {
b, ok := model[v.name]
if !ok {
panic(fmt.Errorf("Model lacks binding for variable %s", v.name))
}
return b
}
type lit struct {
v variable
signed bool
}
func (l lit) nnf() Formula {
return l
}
func (l lit) String() string {
if l.signed {
return "not(" + l.v.name + ")"
}
return l.v.name
}
func (l lit) Eval(model map[string]bool) bool {
b := l.v.Eval(model)
if l.signed {
return !b
}
return b
}
// Not represents a negation. It negates the given subformula.
func Not(f Formula) Formula {
return not{f}
}
type not [1]Formula
func (n not) nnf() Formula {
switch f := n[0].(type) {
case variable:
l := f.nnf().(lit)
l.signed = true
return l
case lit:
f.signed = !f.signed
return f
case not:
return f[0].nnf()
case and:
subs := make([]Formula, len(f))
for i, sub := range f {
subs[i] = not{sub}.nnf()
}
return or(subs).nnf()
case or:
subs := make([]Formula, len(f))
for i, sub := range f {
subs[i] = not{sub}.nnf()
}
return and(subs).nnf()
case trueConst:
return False
case falseConst:
return True
default:
panic("invalid formula type")
}
}
func (n not) String() string {
return "not(" + n[0].String() + ")"
}
func (n not) Eval(model map[string]bool) bool {
return !n[0].Eval(model)
}
// And generates a conjunction of subformulas.
func And(subs ...Formula) Formula {
return and(subs)
}
type and []Formula
func (a and) nnf() Formula {
var res and
for _, s := range a {
nnf := s.nnf()
switch nnf := nnf.(type) {
case and: // Simplify: "and"s in the "and" get to the higher level
res = append(res, nnf...)
case trueConst: // True is ignored
case falseConst:
return False
default:
res = append(res, nnf)
}
}
if len(res) == 1 {
return res[0]
}
if len(res) == 0 {
return False
}
return res
}
func (a and) String() string {
strs := make([]string, len(a))
for i, f := range a {
strs[i] = f.String()
}
return "and(" + strings.Join(strs, ", ") + ")"
}
func (a and) Eval(model map[string]bool) (res bool) {
for i, s := range a {
b := s.Eval(model)
if i == 0 {
res = b
} else {
res = res && b
}
}
return
}
// Or generates a disjunction of subformulas.
func Or(subs ...Formula) Formula {
return or(subs)
}
type or []Formula
func (o or) nnf() Formula {
var res or
for _, s := range o {
nnf := s.nnf()
switch nnf := nnf.(type) {
case or: // Simplify: "or"s in the "or" get to the higher level
res = append(res, nnf...)
case falseConst: // False is ignored
case trueConst:
return True
default:
res = append(res, nnf)
}
}
if len(res) == 1 {
return res[0]
}
if len(res) == 0 {
return True
}
return res
}
func (o or) String() string {
strs := make([]string, len(o))
for i, f := range o {
strs[i] = f.String()
}
return "or(" + strings.Join(strs, ", ") + ")"
}
func (o or) Eval(model map[string]bool) (res bool) {
for i, s := range o {
b := s.Eval(model)
if i == 0 {
res = b
} else {
res = res || b
}
}
return
}
// Implies indicates a subformula implies another one.
func Implies(f1, f2 Formula) Formula {
return or{not{f1}, f2}
}
// Eq indicates a subformula is equivalent to another one.
func Eq(f1, f2 Formula) Formula {
return and{or{not{f1}, f2}, or{f1, not{f2}}}
}
// Xor indicates exactly one of the two given subformulas is true.
func Xor(f1, f2 Formula) Formula {
return and{or{not{f1}, not{f2}}, or{f1, f2}}
}
// Unique indicates exactly one of the given variables must be true.
// It might create dummy variables to reduce the number of generated clauses.
func Unique(vars ...string) Formula {
vars2 := make([]variable, len(vars))
for i, v := range vars {
vars2[i] = pbVar(v)
}
return uniqueRec(vars2...)
}
// uniqueSmall generates clauses indicating exactly one of the given variables is true.
// It is suitable when the number of variables is small (typically, <= 4).
func uniqueSmall(vars ...variable) Formula {
res := make([]Formula, 1, 1+(len(vars)*len(vars)-1)/2)
varsAsForms := make([]Formula, len(vars))
for i, v := range vars {
varsAsForms[i] = v
}
res[0] = Or(varsAsForms...)
for i := 0; i < len(vars)-1; i++ {
for j := i + 1; j < len(vars); j++ {
res = append(res, Or(Not(varsAsForms[i]), Not(varsAsForms[j])))
}
}
return And(res...)
}
func uniqueRec(vars ...variable) Formula {
nbVars := len(vars)
if nbVars <= 4 {
return uniqueSmall(vars...)
}
sqrt := math.Sqrt(float64(nbVars))
nbLines := int(sqrt + 0.5)
lines := make([]variable, nbLines)
linesF := make([][]Formula, nbLines)
allNames := make([]string, len(vars))
for i := range vars {
allNames[i] = vars[i].name
}
fullName := strings.Join(allNames, "-")
for i := range lines {
lines[i] = dummyVar(fmt.Sprintf("line-%d-%s", i, fullName))
linesF[i] = []Formula{}
}
nbCols := int(math.Ceil(sqrt))
cols := make([]variable, nbCols)
colsF := make([][]Formula, nbCols)
for i := range cols {
cols[i] = dummyVar(fmt.Sprintf("col-%d-%s", i, fullName))
colsF[i] = []Formula{}
}
res := make([]Formula, 0, 2*nbVars+1)
for i, v := range vars {
linesF[i/nbCols] = append(linesF[i/nbCols], v)
colsF[i%nbCols] = append(colsF[i%nbCols], v)
}
for i := range lines {
res = append(res, Eq(lines[i], Or(linesF[i]...)))
}
for i := range cols {
res = append(res, Eq(cols[i], Or(colsF[i]...)))
}
res = append(res, uniqueRec(lines...))
res = append(res, uniqueRec(cols...))
return And(res...)
}
// vars associate variable names with numeric indices.
type vars struct {
all map[variable]int // all vars, including those created when converting the formula
pb map[variable]int // Only the vars that appeared orinigally in the problem
}
// litValue returns the int value associated with the given problem var.
// If the var was not referenced yet, it is created first.
func (vars *vars) litValue(l lit) int {
val, ok := vars.all[l.v]
if !ok {
val = len(vars.all) + 1
vars.all[l.v] = val
vars.pb[l.v] = val
}
if l.signed {
return -val
}
return val
}
// Dummy creates a dummy variable and returns its associated index.
func (vars *vars) dummy() int {
val := len(vars.all) + 1
vars.all[dummyVar(fmt.Sprintf("dummy-%d", val))] = val
return val
}
// A CNF is the representation of a boolean formula as a conjunction of disjunction.
// It can be solved by a SAT solver.
type cnf struct {
vars vars
clauses [][]int
}
// solve solves the given formula.
// cnf is given to gophersat.
// If it is satisfiable, the function returns a model, associating each variable name with its binding.
// Else, the function returns nil.
func (cnf *cnf) solve() map[string]bool {
pb := solver.ParseSlice(cnf.clauses)
s := solver.New(pb)
if s.Solve() != solver.Sat {
return nil
}
m := s.Model()
vars := make(map[string]bool)
for v, idx := range cnf.vars.pb {
vars[v.name] = m[idx-1]
}
return vars
}
// asCnf returns a CNF representation of the given formula.
func asCnf(f Formula) *cnf {
vars := vars{all: make(map[variable]int), pb: make(map[variable]int)}
clauses := cnfRec(f.nnf(), &vars)
return &cnf{vars: vars, clauses: clauses}
}
// transforms the f NNF formula into a CNF formula.
// nbDummies is the current number of dummy variables created.
// Note: code should be improved, there are a few useless allocs/deallocs
// here and there.
func cnfRec(f Formula, vars *vars) [][]int {
switch f := f.(type) {
case lit:
return [][]int{{vars.litValue(f)}}
case and:
var res [][]int
for _, sub := range f {
res = append(res, cnfRec(sub, vars)...)
}
return res
case or:
var res [][]int
var lits []int
for _, sub := range f {
switch sub := sub.(type) {
case lit:
lits = append(lits, vars.litValue(sub))
case and:
d := vars.dummy()
lits = append(lits, d)
for _, sub2 := range sub {
cnf := cnfRec(sub2, vars)
cnf[0] = append(cnf[0], -d)
res = append(res, cnf...)
}
default:
panic("unexpected or in or")
}
}
res = append(res, lits)
return res
case trueConst: // True clauses are ignored
return [][]int{}
case falseConst: // TODO: improve this. This should simply be declared to make the problem UNSAT.
return [][]int{{}}
default:
panic("invalid NNF formula")
}
}

49
vendor/github.com/crillab/gophersat/bf/doc.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
// Package bf offers facilities to test the satisfiability of generic boolean formula.
//
// SAT solvers usually expect as an input CNF formulas.
// A CNF, or Conjunctive Normal Form, is a set of clauses that must all be true, each clause
// being a set of potentially negated literals. For the clause to be true, at least one of
// these literals must be true.
//
// However, manually translating a given boolean formula to an equivalent CNF is tedious and error-prone.
// This package provides a set of logical connectors to define and solve generic logical formulas.
// Those formulas are then automatically translated to CNF and passed to the gophersat solver.
//
// For example, the following boolean formula:
//
// ¬(a ∧ b) → ((c ¬d) ∧ ¬(c ∧ (e ↔ ¬c)) ∧ ¬(a ⊻ b))
//
// Will be defined with the following code:
//
// f := Not(Implies(And(Var("a"), Var("b")), And(Or(Var("c"), Not(Var("d"))), Not(And(Var("c"), Eq(Var("e"), Not(Var("c"))))), Not(Xor(Var("a"), Var("b"))))))
//
// When calling `Solve(f)`, the following equivalent CNF will be generated:
//
// a ∧ b ∧ (¬c ¬x1) ∧ (d ¬x1) ∧ (c ¬x2) ∧ (¬e ¬c ¬x2) ∧ (e c ¬x2) ∧ (¬a ¬b ¬x3) ∧ (a b ¬x3) ∧ (x1 x2 x3)
//
// Note that this formula is longer than the original one and that some variables were added to it.
// The translation is both polynomial in time and space.
// When fed this CNF as an input, gophersat then returns the following map:
//
// map[a:true b:true c:false d:true e:false]
//
// It is also possible to create boolean formulas using a dedicated syntax. The BNF grammar is as follows:
//
// formula ::= clause { ';' clause }*
// clause ::= implies { '=' implies }*
// implies ::= or { '->' or}*
// or ::= and { '|' and}*
// and ::= not { '&' not}*
// not ::= '^'not | atom
// atom ::= ident | '(' formula ')'
//
// So the formula
//
// ¬(a ∧ b) → ((c ¬d) ∧ ¬(c ∧ (e ↔ ¬c)) ∧ ¬(a ⊻ b))
//
// would be written as
//
// ^(a & b) -> ((c | ^d) & ^(c & (e = ^c)) & ^(a = ^b))
//
// a call to the `Parse` function will then create the associated Formula.
package bf

254
vendor/github.com/crillab/gophersat/bf/parser.go generated vendored Normal file
View File

@@ -0,0 +1,254 @@
package bf
import (
"fmt"
"go/token"
"io"
"text/scanner"
)
type parser struct {
s scanner.Scanner
eof bool // Have we reached eof yet?
token string // Last token read
}
// Parse parses the formula from the given input Reader.
// It returns the corresponding Formula.
// Formulas are written using the following operators (from lowest to highest priority) :
//
// - for a conjunction of clauses ("and"), the ";" operator
//
// - for an equivalence, the "=" operator,
//
// - for an implication, the "->" operator,
//
// - for a disjunction ("or"), the "|" operator,
//
// - for a conjunction ("and"), the "&" operator,
//
// - for a negation, the "^" unary operator.
//
// - for an exactly-one constraint, names of variables between curly braces, eg "{a, b, c}" to specify
// exactly one of the variable a, b or c must be true.
//
// Parentheses can be used to group subformulas.
// Note there are two ways to write conjunctions, one with a low priority, one with a high priority.
// The low-priority one is useful when the user wants to describe a whole formula as a set of smaller formulas
// that must all be true.
func Parse(r io.Reader) (Formula, error) {
var s scanner.Scanner
s.Init(r)
p := parser{s: s}
p.scan()
f, err := p.parseClause()
if err != nil {
return f, err
}
if !p.eof {
return nil, fmt.Errorf("expected EOF, found %q at %v", p.token, p.s.Pos())
}
return f, nil
}
func isOperator(token string) bool {
return token == "=" || token == "->" || token == "|" || token == "&" || token == ";"
}
func (p *parser) scan() {
p.eof = p.eof || (p.s.Scan() == scanner.EOF)
p.token = p.s.TokenText()
}
func (p *parser) parseClause() (f Formula, err error) {
if isOperator(p.token) {
return nil, fmt.Errorf("unexpected token %q at %s", p.token, p.s.Pos())
}
f, err = p.parseEquiv()
if err != nil {
return nil, err
}
if p.eof {
return f, nil
}
if p.token == ";" {
p.scan()
if p.eof {
return f, nil
}
f2, err := p.parseClause()
if err != nil {
return nil, err
}
return And(f, f2), nil
}
return f, nil
}
func (p *parser) parseEquiv() (f Formula, err error) {
if p.eof {
return nil, fmt.Errorf("At position %v, expected expression, found EOF", p.s.Pos())
}
if isOperator(p.token) {
return nil, fmt.Errorf("unexpected token %q at %s", p.token, p.s.Pos())
}
f, err = p.parseImplies()
if err != nil {
return nil, err
}
if p.eof {
return f, nil
}
if p.token == "=" {
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
f2, err := p.parseEquiv()
if err != nil {
return nil, err
}
return Eq(f, f2), nil
}
return f, nil
}
func (p *parser) parseImplies() (f Formula, err error) {
f, err = p.parseOr()
if err != nil {
return nil, err
}
if p.eof {
return f, nil
}
if p.token == "-" {
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
if p.token != ">" {
return nil, fmt.Errorf("invalid token %q at %v", "-"+p.token, p.s.Pos())
}
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
f2, err := p.parseImplies()
if err != nil {
return nil, err
}
return Implies(f, f2), nil
}
return f, nil
}
func (p *parser) parseOr() (f Formula, err error) {
f, err = p.parseAnd()
if err != nil {
return nil, err
}
if p.eof {
return f, nil
}
if p.token == "|" {
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
f2, err := p.parseOr()
if err != nil {
return nil, err
}
return Or(f, f2), nil
}
return f, nil
}
func (p *parser) parseAnd() (f Formula, err error) {
f, err = p.parseNot()
if err != nil {
return nil, err
}
if p.eof {
return f, nil
}
if p.token == "&" {
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
f2, err := p.parseAnd()
if err != nil {
return nil, err
}
return And(f, f2), nil
}
return f, nil
}
func (p *parser) parseNot() (f Formula, err error) {
if isOperator(p.token) {
return nil, fmt.Errorf("unexpected token %q at %s", p.token, p.s.Pos())
}
if p.token == "^" {
p.scan()
if p.eof {
return nil, fmt.Errorf("unexpected EOF")
}
f, err = p.parseNot()
if err != nil {
return nil, err
}
return Not(f), nil
}
f, err = p.parseBasic()
if err != nil {
return nil, err
}
return f, nil
}
func (p *parser) parseBasic() (f Formula, err error) {
if isOperator(p.token) || p.token == ")" {
return nil, fmt.Errorf("unexpected token %q at %s", p.token, p.s.Pos())
}
if p.token == "(" {
p.scan()
f, err = p.parseEquiv()
if err != nil {
return nil, err
}
if p.eof {
return nil, fmt.Errorf("expected closing parenthesis, found EOF at %s", p.s.Pos())
}
if p.token != ")" {
return nil, fmt.Errorf("expected closing parenthesis, found %q at %s", p.token, p.s.Pos())
}
p.scan()
return f, nil
}
if p.token == "{" {
var vars []string
for p.token != "}" {
p.scan()
if p.eof {
return nil, fmt.Errorf("expected identifier, found EOF at %s", p.s.Pos())
}
if token.Lookup(p.token) != token.IDENT {
return nil, fmt.Errorf("expected variable name, found %q at %s", p.token, p.s.Pos())
}
vars = append(vars, p.token)
p.scan()
if p.eof {
return nil, fmt.Errorf("expected comma or closing brace, found EOF at %s", p.s.Pos())
}
if p.token != "}" && p.token != "," {
return nil, fmt.Errorf("expected comma or closing brace, found %q at %v", p.token, p.s.Pos())
}
}
p.scan()
return Unique(vars...), nil
}
defer p.scan()
return Var(p.token), nil
}

27
vendor/github.com/crillab/gophersat/solver/card.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
package solver
// A CardConstr is a cardinality constraint, i.e a set of literals (represented with integer variables) associated with a minimal number of literals that must be true.
// A propositional clause (i.e a disjunction of literals) is a cardinality constraint with a minimal cardinality of 1.
type CardConstr struct {
Lits []int
AtLeast int
}
// AtLeast1 returns a cardinality constraint stating that at least one of the given lits must be true.
// This is the equivalent of a propositional clause.
func AtLeast1(lits ...int) CardConstr {
return CardConstr{Lits: lits, AtLeast: 1}
}
// AtMost1 returns a cardinality constraint stating that at most one of the given lits can be true.
func AtMost1(lits ...int) CardConstr {
for i, lit := range lits {
lits[i] = -lit
}
return CardConstr{Lits: lits, AtLeast: len(lits) - 1}
}
// Exactly1 returns two cardinality constraints stating that exactly one of the given lits must be true.
func Exactly1(lits ...int) []CardConstr {
return []CardConstr{AtLeast1(lits...), AtMost1(lits...)}
}

243
vendor/github.com/crillab/gophersat/solver/clause.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
package solver
import (
"fmt"
"sort"
"strings"
)
// data used in PB constraints.
type pbData struct {
weights []int // weight of each literal. If nil, weights are all 1.
watched []bool // indices of watched literals.
}
// A Clause is a list of Lit, associated with possible data (for learned clauses).
type Clause struct {
lits []Lit
// lbdValue's bits are as follow:
// leftmost bit: learned flag.
// second bit: locked flag (if learned).
// last 30 bits: LBD value (if learned) or minimal cardinality - 1 (if !learned).
// NOTE: actual cardinality is value + 1, since this is the default value and go defaults to 0.
lbdValue uint32
activity float32
pbData *pbData
}
const (
learnedMask uint32 = 1 << 31
lockedMask uint32 = 1 << 30
bothMasks uint32 = learnedMask | lockedMask
)
// NewClause returns a clause whose lits are given as an argument.
func NewClause(lits []Lit) *Clause {
return &Clause{lits: lits}
}
// NewCardClause returns a clause whose lits are given as an argument and
// for which at least 'card' literals must be true.
// Note tha NewClause(lits) is equivalent to NewCardClause(lits, 1).
func NewCardClause(lits []Lit, card int) *Clause {
if card < 1 || card > len(lits) {
panic("Invalid cardinality value")
}
return &Clause{lits: lits, lbdValue: uint32(card - 1)}
}
// Used to sort literals when constructing PB clause.
type weightedLits struct {
lits []Lit
weights []int
}
func (wl weightedLits) Less(i, j int) bool { return wl.weights != nil && wl.weights[i] > wl.weights[j] }
func (wl weightedLits) Len() int { return len(wl.lits) }
func (wl *weightedLits) Swap(i, j int) {
wl.lits[i], wl.lits[j] = wl.lits[j], wl.lits[i]
if wl.weights != nil {
wl.weights[i], wl.weights[j] = wl.weights[j], wl.weights[i]
}
}
// NewPBClause returns a pseudo-boolean clause with the given lits, weights and minimal cardinality.
func NewPBClause(lits []Lit, weights []int, card int) *Clause {
if card < 1 {
panic("Invalid cardinality value")
}
wl := &weightedLits{lits: lits, weights: weights}
sort.Sort(wl)
pbData := pbData{weights: weights, watched: make([]bool, len(lits))}
if pbData.weights == nil {
pbData.weights = make([]int, len(lits))
for i := range pbData.weights {
pbData.weights[i] = 1
}
}
return &Clause{lits: lits, lbdValue: uint32(card - 1), pbData: &pbData}
}
// NewLearnedClause returns a new clause marked as learned.
func NewLearnedClause(lits []Lit) *Clause {
return &Clause{lits: lits, lbdValue: learnedMask}
}
// Cardinality returns the minimum number of literals that must be true to satisfy the clause.
func (c *Clause) Cardinality() int {
if c.Learned() {
return 1
}
return int(c.lbdValue & ^bothMasks) + 1
}
// Learned returns true iff c was a learned clause.
func (c *Clause) Learned() bool {
return c.lbdValue&learnedMask == learnedMask
}
// PseudoBoolean returns true iff c is a pseudo boolean constraint, and not
// just a propositional clause or cardinality constraint.
func (c *Clause) PseudoBoolean() bool {
return c.pbData != nil
}
func (c *Clause) lock() {
c.lbdValue = c.lbdValue | lockedMask
}
func (c *Clause) unlock() {
c.lbdValue = c.lbdValue & ^lockedMask
}
func (c *Clause) lbd() int {
return int(c.lbdValue & ^bothMasks)
}
func (c *Clause) setLbd(lbd int) {
c.lbdValue = (c.lbdValue & bothMasks) | uint32(lbd)
}
func (c *Clause) incLbd() {
c.lbdValue++
}
func (c *Clause) isLocked() bool {
return c.lbdValue&bothMasks == bothMasks
}
// Len returns the nb of lits in the clause.
func (c *Clause) Len() int {
return len(c.lits)
}
// First returns the first lit from the clause.
func (c *Clause) First() Lit {
return c.lits[0]
}
// Second returns the second lit from the clause.
func (c *Clause) Second() Lit {
return c.lits[1]
}
// Get returns the ith literal from the clause.
func (c *Clause) Get(i int) Lit {
return c.lits[i]
}
// Set sets the ith literal of the clause.
func (c *Clause) Set(i int, l Lit) {
c.lits[i] = l
}
// Weight returns the weight of the ith literal.
// In a propositional clause or a cardinality constraint, that value will always be 1.
func (c *Clause) Weight(i int) int {
if c.pbData == nil {
return 1
}
return c.pbData.weights[i]
}
// WeightSum returns the sum of the PB weights.
// If c is a propositional clause, the function will return the length of the clause.
func (c *Clause) WeightSum() int {
if c.pbData == nil {
return len(c.lits)
}
res := 0
for _, w := range c.pbData.weights {
res += w
}
return res
}
// swap swaps the ith and jth lits from the clause.
func (c *Clause) swap(i, j int) {
c.lits[i], c.lits[j] = c.lits[j], c.lits[i]
if c.pbData != nil {
c.pbData.weights[i], c.pbData.weights[j] = c.pbData.weights[j], c.pbData.weights[i]
}
}
// updateCardinality adds "add" to c's cardinality.
// Must not be called on learned clauses!
func (c *Clause) updateCardinality(add int) {
if add < 0 && uint32(-add) > c.lbdValue {
c.lbdValue = 0
} else {
c.lbdValue += uint32(add)
}
}
// removeLit remove the idx'th lit from c.
// The order of literals might be updated, so this function
// should not be called once the whole solver was created,
// only during simplification of the problem.
func (c *Clause) removeLit(idx int) {
c.lits[idx] = c.lits[len(c.lits)-1]
c.lits = c.lits[:len(c.lits)-1]
if c.pbData != nil {
c.pbData.weights[idx] = c.pbData.weights[len(c.pbData.weights)-1]
c.pbData.weights = c.pbData.weights[:len(c.pbData.weights)-1]
}
}
// Shrink reduces the length of the clauses, by removing all lits
// starting from position newLen.
func (c *Clause) Shrink(newLen int) {
c.lits = c.lits[:newLen]
if c.pbData != nil {
c.pbData.weights = c.pbData.weights[:newLen]
c.pbData.watched = c.pbData.watched[:newLen]
}
}
// CNF returns a DIMACS CNF representation of the clause.
func (c *Clause) CNF() string {
res := ""
for _, lit := range c.lits {
res += fmt.Sprintf("%d ", lit.Int())
}
return fmt.Sprintf("%s0", res)
}
// PBString returns a string representation of c as a pseudo-boolean expression.
func (c *Clause) PBString() string {
terms := make([]string, c.Len())
for i, lit := range c.lits {
weight := 1
if c.pbData != nil {
weight = c.pbData.weights[i]
}
val := lit.Int()
sign := ""
if val < 0 {
val = -val
sign = "~"
}
terms[i] = fmt.Sprintf("%d %sx%d", weight, sign, val)
}
return fmt.Sprintf("%s >= %d ;", strings.Join(terms, " +"), c.Cardinality())
}

View File

@@ -0,0 +1,29 @@
package solver
// This file deals with an attempt for an efficient clause allocator/deallocator, to relax GC's work.
const (
nbLitsAlloc = 5000000 // How many literals are initialized at first?
)
type allocator struct {
lits []Lit // A list of lits, that will be sliced to make []Lit
ptrFree int // Index of the first free item in lits
}
var alloc allocator
// newLits returns a slice of lits containing the given literals.
// It is taken from the preinitialized pool if possible,
// or is created from scratch.
func (a *allocator) newLits(lits ...Lit) []Lit {
if a.ptrFree+len(lits) > len(a.lits) {
a.lits = make([]Lit, nbLitsAlloc)
copy(a.lits, lits)
a.ptrFree = len(lits)
return a.lits[:len(lits)]
}
copy(a.lits[a.ptrFree:], lits)
a.ptrFree += len(lits)
return a.lits[a.ptrFree-len(lits) : a.ptrFree]
}

102
vendor/github.com/crillab/gophersat/solver/doc.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
/*
Package solver gives access to a simple SAT and pseudo-boolean solver.
Its input can be either a DIMACS CNF file, a PBS file or a solver.Problem object,
containing the set of clauses to be solved. In the last case,
the problem can be either a set of propositional clauses,
or a set of pseudo-boolean constraints.
No matter the input format,
the solver.Solver will then solve the problem and indicate whether the problem is
satisfiable or not. In the former case, it will be able to provide a model, i.e a set of bindings
for all variables that makes the problem true.
Describing a problem
A problem can be described in several ways:
1. parse a DIMACS stream (io.Reader). If the io.Reader produces the following content:
p cnf 6 7
1 2 3 0
4 5 6 0
-1 -4 0
-2 -5 0
-3 -6 0
-1 -3 0
-4 -6 0
the programmer can create the Problem by doing:
pb, err := solver.ParseCNF(f)
2. create the equivalent list of list of literals. The problem above can be created programatically this way:
clauses := [][]int{
[]int{1, 2, 3},
[]int{4, 5, 6},
[]int{-1, -4},
[]int{-2, -5},
[]int{-3, -6},
[]int{-1, -3},
[]int{-4, -6},
}
pb := solver.ParseSlice(clauses)
3. create a list of cardinality constraints (CardConstr), if the problem to be solved is better represented this way.
For instance, the problem stating that at least two literals must be true among the literals 1, 2, 3 and 4 could be described as a set of clauses:
clauses := [][]int{
[]int{1, 2, 3},
[]int{2, 3, 4},
[]int{1, 2, 4},
[]int{1, 3, 4},
}
pb := solver.ParseSlice(clauses)
The number of clauses necessary to describe such a constrain can grow exponentially. Alternatively, it is possible to describe the same this way:
constrs := []CardConstr{
{Lits: []int{1, 2, 3, 4}, AtLeast: 2},
}
pb := solver.ParseCardConstrs(clauses)
Note that a propositional clause has an implicit cardinality constraint of 1, since at least one of its literals must be true.
4. parse a PBS stream (io.Reader). If the io.Reader contains the following problem:
2 ~x1 +1 x2 +1 x3 >= 3 ;
the programmer can create the Problem by doing:
pb, err := solver.ParsePBS(f)
5. create a list of PBConstr. For instance, the following set of one PBConstrs will generate the same problem as above:
constrs := []PBConstr{GtEq([]int{1, 2, 3}, []int{2, 1, 1}, 3)}
pb := solver.ParsePBConstrs(constrs)
Solving a problem
To solve a problem, one simply creates a solver with said problem.
The solve() method then solves the problem and returns the corresponding status: Sat or Unsat.
s := solver.New(pb)
status := s.Solve()
If the status was Sat, the programmer can ask for a model, i.e an assignment that makes all the clauses of the problem true:
m := s.Model()
For the above problem, the status will be Sat and the model can be {false, true, false, true, false, false}.
Alternatively, one can display on stdout the result and model (if any):
s.OutputModel()
For the above problem described in the DIMACS format, the output can be:
SATISFIABLE
-1 2 -3 4 -5 -6
*/
package solver

View File

@@ -0,0 +1,35 @@
package solver
// A Result is a status, either Sat, Unsat or Indet.
// If the status is Sat, the Result also associates a ModelMap with an integer value.
// This value is typically used in optimization processes.
// If the weight is 0, that means all constraints could be solved.
// By definition, in decision problems, the cost will always be 0.
type Result struct {
Status Status
Model []bool
Weight int
}
// Interface is any type implementing a solver.
// The basic Solver defined in this package implements it.
// Any solver that uses the basic solver to solve more complex problems
// (MAXSAT, MUS extraction, etc.) can implement it, too.
type Interface interface {
// Optimal solves or optimizes the problem and returns the best result.
// If the results chan is non nil, it will write the associated model each time one is found.
// It will stop as soon as a model of cost 0 is found, or the problem is not satisfiable anymore.
// The last satisfying model, if any, will be returned with the Sat status.
// If no model at all could be found, the Unsat status will be returned.
// If the solver prematurely stopped, the Indet status will be returned.
// If data is sent to stop, the method may stop prematurely.
// In any case, results will be closed before the function returns.
// NOTE: data sent on stop may be ignored by an implementation.
Optimal(results chan Result, stop chan struct{}) Result
// Enumerate returns the number of models for the problem.
// If the models chan is non nil, it will write the associated model each time one is found.
// If data is sent to stop, the method may stop prematurely.
// In any case, models will be closed before the function returns.
// NOTE: data sent on stop may be ignored by an implementation.
Enumerate(models chan []bool, stop chan struct{}) int
}

89
vendor/github.com/crillab/gophersat/solver/lbd.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
package solver
const (
nbMaxRecent = 50 // How many recent LBD values we consider; "X" in papers about LBD.
triggerRestartK = 0.8
nbMaxTrail = 5000 // How many elements in queueTrail we consider; "Y" in papers about LBD.
postponeRestartT = 1.4
)
type queueData struct {
totalNb int // Current total nb of values considered
totalSum int // Sum of all values so far
nbRecent int // NB of values used in the array
ptr int // current index of oldest value in the array
recentAvg float64 // Average value
}
// lbdStats is a structure dealing with recent LBD evolutions.
type lbdStats struct {
lbdData queueData
trailData queueData
recentVals [nbMaxRecent]int // Last LBD values
recentTrails [nbMaxTrail]int // Last trail lengths
}
// mustRestart is true iff recent LBDs are much smaller on average than average of all LBDs.
func (l *lbdStats) mustRestart() bool {
if l.lbdData.nbRecent < nbMaxRecent {
return false
}
return l.lbdData.recentAvg*triggerRestartK > float64(l.lbdData.totalSum)/float64(l.lbdData.totalNb)
}
// addConflict adds information about a conflict that just happened.
func (l *lbdStats) addConflict(trailSz int) {
td := &l.trailData
td.totalNb++
td.totalSum += trailSz
if td.nbRecent < nbMaxTrail {
l.recentTrails[td.nbRecent] = trailSz
old := float64(td.nbRecent)
new := old + 1
td.recentAvg = (td.recentAvg*old)/new + float64(trailSz)/new
td.nbRecent++
} else {
old := l.recentTrails[td.ptr]
l.recentTrails[td.ptr] = trailSz
td.ptr++
if td.ptr == nbMaxTrail {
td.ptr = 0
}
td.recentAvg = td.recentAvg - float64(old)/nbMaxTrail + float64(trailSz)/nbMaxTrail
}
if td.nbRecent == nbMaxTrail && l.lbdData.nbRecent == nbMaxRecent && trailSz > int(postponeRestartT*td.recentAvg) {
// Too many good assignments: postpone restart
l.clear()
}
}
// addLbd adds information about a recent learned clause's LBD.
// TODO: this is very close to addConflicts's code, this should probably be rewritten/merged.
func (l *lbdStats) addLbd(lbd int) {
ld := &l.lbdData
ld.totalNb++
ld.totalSum += lbd
if ld.nbRecent < nbMaxRecent {
l.recentVals[ld.nbRecent] = lbd
old := float64(ld.nbRecent)
new := old + 1
ld.recentAvg = (ld.recentAvg*old)/new + float64(lbd)/new
ld.nbRecent++
} else {
old := l.recentVals[ld.ptr]
l.recentVals[ld.ptr] = lbd
ld.ptr++
if ld.ptr == nbMaxRecent {
ld.ptr = 0
}
ld.recentAvg = ld.recentAvg - float64(old)/nbMaxRecent + float64(lbd)/nbMaxRecent
}
}
// clear clears last values. It should be called after a restart.
func (l *lbdStats) clear() {
l.lbdData.ptr = 0
l.lbdData.nbRecent = 0
l.lbdData.recentAvg = 0.0
}

121
vendor/github.com/crillab/gophersat/solver/learn.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
package solver
// computeLbd computes and sets c's LBD (Literal Block Distance).
func (c *Clause) computeLbd(model Model) {
c.setLbd(1)
curLvl := abs(model[c.Get(0).Var()])
for i := 0; i < c.Len(); i++ {
lit := c.Get(i)
if lvl := abs(model[lit.Var()]); lvl != curLvl {
curLvl = lvl
c.incLbd()
}
}
}
// addClauseLits is a helper function for learnClause.
// It deals with lits from the conflict clause.
func (s *Solver) addClauseLits(confl *Clause, lvl decLevel, met, metLvl []bool, lits *[]Lit) int {
nbLvl := 0
for i := 0; i < confl.Len(); i++ {
l := confl.Get(i)
v := l.Var()
if s.litStatus(l) != Unsat {
// In clauses where cardinality > 1, some lits might be true in the conflict clause: ignore them
continue
}
met[v] = true
s.varBumpActivity(v)
if abs(s.model[v]) == lvl {
metLvl[v] = true
nbLvl++
} else if abs(s.model[v]) != 1 {
*lits = append(*lits, l)
}
}
return nbLvl
}
var bufLits = make([]Lit, 10000) // Buffer for lits in learnClause. Used to reduce allocations.
// learnClause creates a conflict clause and returns either:
// the clause itself, if its len is at least 2.
// a nil clause and a unit literal, if its len is exactly 1.
func (s *Solver) learnClause(confl *Clause, lvl decLevel) (learned *Clause, unit Lit) {
s.clauseBumpActivity(confl)
lits := bufLits[:1] // Not 0: make room for asserting literal
buf := make([]bool, s.nbVars*2) // Buffer for met and metLvl; reduces allocs/deallocs
met := buf[:s.nbVars] // List of all vars already met
metLvl := buf[s.nbVars:] // List of all vars from current level to deal with
// nbLvl is the nb of vars in lvl currently used
nbLvl := s.addClauseLits(confl, lvl, met, metLvl, &lits)
ptr := len(s.trail) - 1 // Pointer in propagation trail
for nbLvl > 1 { // We will stop once we only have one lit from current level.
for !metLvl[s.trail[ptr].Var()] {
if abs(s.model[s.trail[ptr].Var()]) == lvl { // This var was deduced afterwards and was not a reason for the conflict
met[s.trail[ptr].Var()] = true
}
ptr--
}
v := s.trail[ptr].Var()
ptr--
nbLvl--
if reason := s.reason[v]; reason != nil {
s.clauseBumpActivity(reason)
for i := 0; i < reason.Len(); i++ {
lit := reason.Get(i)
if v2 := lit.Var(); !met[v2] {
if s.litStatus(lit) != Unsat {
continue
}
met[v2] = true
s.varBumpActivity(v2)
if abs(s.model[v2]) == lvl {
metLvl[v2] = true
nbLvl++
} else if abs(s.model[v2]) != 1 {
lits = append(lits, lit)
}
}
}
}
}
for _, l := range s.trail { // Look for last lit from lvl and use it as asserting lit
if metLvl[l.Var()] {
lits[0] = l.Negation()
break
}
}
s.varDecayActivity()
s.clauseDecayActivity()
sortLiterals(lits, s.model)
sz := s.minimizeLearned(met, lits)
if sz == 1 {
return nil, lits[0]
}
learned = NewLearnedClause(alloc.newLits(lits[0:sz]...))
learned.computeLbd(s.model)
return learned, -1
}
// minimizeLearned reduces (if possible) the length of the learned clause and returns the size
// of the new list of lits.
func (s *Solver) minimizeLearned(met []bool, learned []Lit) int {
sz := 1
for i := 1; i < len(learned); i++ {
if reason := s.reason[learned[i].Var()]; reason == nil {
learned[sz] = learned[i]
sz++
} else {
for k := 0; k < reason.Len(); k++ {
lit := reason.Get(k)
if !met[lit.Var()] && abs(s.model[lit.Var()]) > 1 {
learned[sz] = learned[i]
sz++
break
}
}
}
}
return sz
}

171
vendor/github.com/crillab/gophersat/solver/parser.go generated vendored Normal file
View File

@@ -0,0 +1,171 @@
package solver
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// ParseSlice parse a slice of slice of lits and returns the equivalent problem.
// The argument is supposed to be a well-formed CNF.
func ParseSlice(cnf [][]int) *Problem {
var pb Problem
for _, line := range cnf {
switch len(line) {
case 0:
pb.Status = Unsat
return &pb
case 1:
if line[0] == 0 {
panic("null unit clause")
}
lit := IntToLit(int32(line[0]))
v := lit.Var()
if int(v) >= pb.NbVars {
pb.NbVars = int(v) + 1
}
pb.Units = append(pb.Units, lit)
default:
lits := make([]Lit, len(line))
for j, val := range line {
if val == 0 {
panic("null literal in clause %q")
}
lits[j] = IntToLit(int32(val))
if v := int(lits[j].Var()); v >= pb.NbVars {
pb.NbVars = v + 1
}
}
pb.Clauses = append(pb.Clauses, NewClause(lits))
}
}
pb.Model = make([]decLevel, pb.NbVars)
for _, unit := range pb.Units {
v := unit.Var()
if pb.Model[v] == 0 {
if unit.IsPositive() {
pb.Model[v] = 1
} else {
pb.Model[v] = -1
}
} else if pb.Model[v] > 0 != unit.IsPositive() {
pb.Status = Unsat
return &pb
}
}
pb.simplify()
return &pb
}
// readInt reads an int from r.
// 'b' is the last read byte. It can be a space, a '-' or a digit.
// The int can be negated.
// All spaces before the int value are ignored.
// Can return EOF.
func readInt(b *byte, r *bufio.Reader) (res int, err error) {
for err == nil && (*b == ' ' || *b == '\t' || *b == '\n' || *b == '\r') {
*b, err = r.ReadByte()
}
if err == io.EOF {
return res, io.EOF
}
if err != nil {
return res, fmt.Errorf("could not read digit: %v", err)
}
neg := 1
if *b == '-' {
neg = -1
*b, err = r.ReadByte()
if err != nil {
return 0, fmt.Errorf("cannot read int: %v", err)
}
}
for err == nil {
if *b < '0' || *b > '9' {
return 0, fmt.Errorf("cannot read int: %q is not a digit", *b)
}
res = 10*res + int(*b-'0')
*b, err = r.ReadByte()
if *b == ' ' || *b == '\t' || *b == '\n' || *b == '\r' {
break
}
}
res *= neg
return res, err
}
func parseHeader(r *bufio.Reader) (nbVars, nbClauses int, err error) {
line, err := r.ReadString('\n')
if err != nil {
return 0, 0, fmt.Errorf("cannot read header: %v", err)
}
fields := strings.Fields(line)
if len(fields) < 3 {
return 0, 0, fmt.Errorf("invalid syntax %q in header", line)
}
nbVars, err = strconv.Atoi(fields[1])
if err != nil {
return 0, 0, fmt.Errorf("nbvars not an int : %q", fields[1])
}
nbClauses, err = strconv.Atoi(fields[2])
if err != nil {
return 0, 0, fmt.Errorf("nbClauses not an int : '%s'", fields[2])
}
return nbVars, nbClauses, nil
}
// ParseCNF parses a CNF file and returns the corresponding Problem.
func ParseCNF(f io.Reader) (*Problem, error) {
r := bufio.NewReader(f)
var (
nbClauses int
pb Problem
)
b, err := r.ReadByte()
for err == nil {
if b == 'c' { // Ignore comment
b, err = r.ReadByte()
for err == nil && b != '\n' {
b, err = r.ReadByte()
}
} else if b == 'p' { // Parse header
pb.NbVars, nbClauses, err = parseHeader(r)
if err != nil {
return nil, fmt.Errorf("cannot parse CNF header: %v", err)
}
pb.Model = make([]decLevel, pb.NbVars)
pb.Clauses = make([]*Clause, 0, nbClauses)
} else {
lits := make([]Lit, 0, 3) // Make room for some lits to improve performance
for {
val, err := readInt(&b, r)
if err == io.EOF {
if len(lits) != 0 { // This is not a trailing space at the end...
return nil, fmt.Errorf("unfinished clause while EOF found")
}
break // When there are only several useless spaces at the end of the file, that is ok
}
if err != nil {
return nil, fmt.Errorf("cannot parse clause: %v", err)
}
if val == 0 {
pb.Clauses = append(pb.Clauses, NewClause(lits))
break
} else {
if val > pb.NbVars || -val > pb.NbVars {
return nil, fmt.Errorf("invalid literal %d for problem with %d vars only", val, pb.NbVars)
}
lits = append(lits, IntToLit(int32(val)))
}
}
}
b, err = r.ReadByte()
}
if err != io.EOF {
return nil, err
}
pb.simplify2()
return &pb, nil
}

262
vendor/github.com/crillab/gophersat/solver/parser_pb.go generated vendored Normal file
View File

@@ -0,0 +1,262 @@
package solver
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// ParseCardConstrs parses the given cardinality constraints.
// Will panic if a zero value appears in the literals.
func ParseCardConstrs(constrs []CardConstr) *Problem {
var pb Problem
for _, constr := range constrs {
card := constr.AtLeast
if card <= 0 { // Clause is trivially SAT, ignore
continue
}
if len(constr.Lits) < card { // Clause cannot be satsfied
pb.Status = Unsat
return &pb
}
if len(constr.Lits) == card { // All lits must be true
for i := range constr.Lits {
if constr.Lits[i] == 0 {
panic("literal 0 found in clause")
}
lit := IntToLit(int32(constr.Lits[i]))
v := lit.Var()
if int(v) >= pb.NbVars {
pb.NbVars = int(v) + 1
}
pb.Units = append(pb.Units, lit)
}
} else {
lits := make([]Lit, len(constr.Lits))
for j, val := range constr.Lits {
if val == 0 {
panic("literal 0 found in clause")
}
lits[j] = IntToLit(int32(val))
if v := int(lits[j].Var()); v >= pb.NbVars {
pb.NbVars = v + 1
}
}
pb.Clauses = append(pb.Clauses, NewCardClause(lits, card))
}
}
pb.Model = make([]decLevel, pb.NbVars)
for _, unit := range pb.Units {
v := unit.Var()
if pb.Model[v] == 0 {
if unit.IsPositive() {
pb.Model[v] = 1
} else {
pb.Model[v] = -1
}
} else if pb.Model[v] > 0 != unit.IsPositive() {
pb.Status = Unsat
return &pb
}
}
pb.simplifyCard()
return &pb
}
// ParsePBConstrs parses and returns a PB problem from PBConstr values.
func ParsePBConstrs(constrs []PBConstr) *Problem {
var pb Problem
for _, constr := range constrs {
for i := range constr.Lits {
lit := IntToLit(int32(constr.Lits[i]))
v := lit.Var()
if int(v) >= pb.NbVars {
pb.NbVars = int(v) + 1
}
}
card := constr.AtLeast
if card <= 0 { // Clause is trivially SAT, ignore
continue
}
sumW := constr.WeightSum()
if sumW < card { // Clause cannot be satsfied
pb.Status = Unsat
return &pb
}
if sumW == card { // All lits must be true
for i := range constr.Lits {
lit := IntToLit(int32(constr.Lits[i]))
found := false
for _, u := range pb.Units {
if u == lit {
found = true
break
}
}
if !found {
pb.Units = append(pb.Units, lit)
}
}
} else {
lits := make([]Lit, len(constr.Lits))
for j, val := range constr.Lits {
lits[j] = IntToLit(int32(val))
}
pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card))
}
}
pb.Model = make([]decLevel, pb.NbVars)
for _, unit := range pb.Units {
v := unit.Var()
if pb.Model[v] == 0 {
if unit.IsPositive() {
pb.Model[v] = 1
} else {
pb.Model[v] = -1
}
} else if pb.Model[v] > 0 != unit.IsPositive() {
pb.Status = Unsat
return &pb
}
}
pb.simplifyPB()
return &pb
}
// parsePBOptim parses the "min:" instruction.
func (pb *Problem) parsePBOptim(fields []string, line string) error {
weights, lits, err := pb.parseTerms(fields[1:], line)
if err != nil {
return err
}
pb.minLits = make([]Lit, len(lits))
for i, lit := range lits {
pb.minLits[i] = IntToLit(int32(lit))
}
pb.minWeights = weights
return nil
}
func (pb *Problem) parsePBLine(line string) error {
if line[len(line)-1] != ';' {
return fmt.Errorf("line %q does not end with semicolon", line)
}
fields := strings.Fields(line[:len(line)-1])
if len(fields) == 0 {
return fmt.Errorf("empty line in file")
}
if fields[0] == "min:" { // Optimization constraint
return pb.parsePBOptim(fields, line)
}
return pb.parsePBConstrLine(fields, line)
}
func (pb *Problem) parsePBConstrLine(fields []string, line string) error {
if len(fields) < 3 {
return fmt.Errorf("invalid syntax %q", line)
}
operator := fields[len(fields)-2]
if operator != ">=" && operator != "=" {
return fmt.Errorf("invalid operator %q in %q: expected \">=\" or \"=\"", operator, line)
}
rhs, err := strconv.Atoi(fields[len(fields)-1])
if err != nil {
return fmt.Errorf("invalid value %q in %q: %v", fields[len(fields)-1], line, err)
}
weights, lits, err := pb.parseTerms(fields[:len(fields)-2], line)
if err != nil {
return err
}
var constrs []PBConstr
if operator == ">=" {
constrs = []PBConstr{GtEq(lits, weights, rhs)}
} else {
constrs = Eq(lits, weights, rhs)
}
for _, constr := range constrs {
card := constr.AtLeast
sumW := constr.WeightSum()
if sumW < card { // Clause cannot be satsfied
pb.Status = Unsat
return nil
}
if sumW == card { // All lits must be true
for i := range constr.Lits {
lit := IntToLit(int32(constr.Lits[i]))
pb.Units = append(pb.Units, lit)
}
} else {
lits := make([]Lit, len(constr.Lits))
for j, val := range constr.Lits {
lits[j] = IntToLit(int32(val))
}
pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card))
}
}
return nil
}
func (pb *Problem) parseTerms(terms []string, line string) (weights []int, lits []int, err error) {
weights = make([]int, 0, len(terms)/2)
lits = make([]int, 0, len(terms)/2)
i := 0
for i < len(terms) {
var l string
w, err := strconv.Atoi(terms[i])
if err != nil {
l = terms[i]
if !strings.HasPrefix(l, "x") && !strings.HasPrefix(l, "~x") {
return nil, nil, fmt.Errorf("invalid weight %q in %q: %v", terms[i*2], line, err)
}
// This is a weightless lit, i.e a lit with weight 1.
weights = append(weights, 1)
} else {
weights = append(weights, w)
i++
l = terms[i]
if !strings.HasPrefix(l, "x") && !strings.HasPrefix(l, "~x") || len(l) < 2 {
return nil, nil, fmt.Errorf("invalid variable name %q in %q", l, line)
}
}
var lit int
if l[0] == '~' {
lit, err = strconv.Atoi(l[2:])
lits = append(lits, -lit)
} else {
lit, err = strconv.Atoi(l[1:])
lits = append(lits, lit)
}
if err != nil {
return nil, nil, fmt.Errorf("invalid variable %q in %q: %v", l, line, err)
}
if lit > pb.NbVars {
pb.NbVars = lit
}
i++
}
return weights, lits, nil
}
// ParseOPB parses a file corresponding to the OPB syntax.
// See http://www.cril.univ-artois.fr/PB16/format.pdf for more details.
func ParseOPB(f io.Reader) (*Problem, error) {
scanner := bufio.NewScanner(f)
var pb Problem
for scanner.Scan() {
line := scanner.Text()
if line == "" || line[0] == '*' {
continue
}
if err := pb.parsePBLine(line); err != nil {
return nil, err
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("could not parse OPB: %v", err)
}
pb.Model = make([]decLevel, pb.NbVars)
pb.simplifyPB()
return &pb, nil
}

101
vendor/github.com/crillab/gophersat/solver/pb.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package solver
// A PBConstr is a Pseudo-Boolean constraint.
type PBConstr struct {
Lits []int // List of literals, designed with integer values. A positive value means the literal is true, a negative one it is false.
Weights []int // Weight of each lit from Lits. If nil, all lits == 1
AtLeast int // Sum of all lits must be at least this value
}
// WeightSum returns the sum of the weight of all terms.
func (c PBConstr) WeightSum() int {
if c.Weights == nil { // All weights = 1
return len(c.Lits)
}
res := 0
for _, w := range c.Weights {
res += w
}
return res
}
// Clause returns the clause associated with the given constraint.
func (c PBConstr) Clause() *Clause {
lits := make([]Lit, len(c.Lits))
for i, val := range c.Lits {
lits[i] = IntToLit(int32(val))
}
return NewPBClause(lits, c.Weights, c.AtLeast)
}
// PropClause returns a PB constraint equivalent to a propositional clause: at least one of the given
// literals must be true.
// It takes ownership of lits.
func PropClause(lits ...int) PBConstr {
return PBConstr{Lits: lits, AtLeast: 1}
}
// AtLeast returns a PB constraint stating that at least n literals must be true.
// It takes ownership of lits.
func AtLeast(lits []int, n int) PBConstr {
return PBConstr{Lits: lits, AtLeast: n}
}
// AtMost returns a PB constraint stating that at most n literals can be true.
// It takes ownership of lits.
func AtMost(lits []int, n int) PBConstr {
for i := range lits {
lits[i] = -lits[i]
}
return PBConstr{Lits: lits, AtLeast: len(lits) - n}
}
// GtEq returns a PB constraint stating that the sum of all literals multiplied by their weight
// must be at least n.
// Will panic if len(weights) != len(lits).
func GtEq(lits []int, weights []int, n int) PBConstr {
if len(weights) != 0 && len(lits) != len(weights) {
panic("not as many lits as weights")
}
for i := range weights {
if weights[i] < 0 {
weights[i] = -weights[i]
n += weights[i]
lits[i] = -lits[i]
}
}
return PBConstr{Lits: lits, Weights: weights, AtLeast: n}
}
// LtEq returns a PB constraint stating that the sum of all literals multiplied by their weight
// must be at most n.
// Will panic if len(weights) != len(lits).
func LtEq(lits []int, weights []int, n int) PBConstr {
sum := 0
for i := range lits {
lits[i] = -lits[i]
sum += weights[i]
}
n = sum - n
return GtEq(lits, weights, n)
}
// Eq returns a set of PB constraints stating that the sum of all literals multiplied by their weight
// must be exactly n.
// Will panic if len(weights) != len(lits).
func Eq(lits []int, weights []int, n int) []PBConstr {
lits2 := make([]int, len(lits))
weights2 := make([]int, len(weights))
copy(lits2, lits)
copy(weights2, weights)
ge := GtEq(lits2, weights2, n)
le := LtEq(lits, weights, n)
var res []PBConstr
if ge.AtLeast > 0 {
res = append(res, ge)
}
if le.AtLeast > 0 {
res = append(res, le)
}
return res
}

View File

@@ -0,0 +1,183 @@
package solver
//
// import "log"
//
// // Subsumes returns true iff c subsumes c2.
// func (c *Clause) Subsumes(c2 *Clause) bool {
// if c.Len() > c2.Len() {
// return false
// }
// for _, lit := range c.lits {
// found := false
// for _, lit2 := range c2.lits {
// if lit == lit2 {
// found = true
// break
// }
// if lit2 > lit {
// return false
// }
// }
// if !found {
// return false
// }
// }
// return true
// }
//
// // SelfSubsumes returns true iff c self-subsumes c2.
// func (c *Clause) SelfSubsumes(c2 *Clause) bool {
// oneNeg := false
// for _, lit := range c.lits {
// found := false
// for _, lit2 := range c2.lits {
// if lit == lit2 {
// found = true
// break
// }
// if lit == lit2.Negation() {
// if oneNeg { // We want exactly one, but this is the second
// return false
// }
// oneNeg = true
// found = true
// break
// }
// if lit2 > lit { // We won't find it anymore
// return false
// }
// }
// if !found {
// return false
// }
// }
// return oneNeg
// }
//
// // Simplify simplifies the given clause by removing redundant lits.
// // If the clause is trivially satisfied (i.e contains both a lit and its negation),
// // true is returned. Otherwise, false is returned.
// func (c *Clause) Simplify() (isSat bool) {
// c.Sort()
// lits := make([]Lit, 0, len(c.lits))
// i := 0
// for i < len(c.lits) {
// if i < len(c.lits)-1 && c.lits[i] == c.lits[i+1].Negation() {
// return true
// }
// lit := c.lits[i]
// lits = append(lits, lit)
// i++
// for i < len(c.lits) && c.lits[i] == lit {
// i++
// }
// }
// if len(lits) < len(c.lits) {
// c.lits = lits
// }
// return false
// }
//
// // Generate returns a subsumed clause from c and c2, by removing v.
// func (c *Clause) Generate(c2 *Clause, v Var) *Clause {
// c3 := &Clause{lits: make([]Lit, 0, len(c.lits)+len(c2.lits)-2)}
// for _, lit := range c.lits {
// if lit.Var() != v {
// c3.lits = append(c3.lits, lit)
// }
// }
// for _, lit2 := range c2.lits {
// if lit2.Var() != v {
// c3.lits = append(c3.lits, lit2)
// }
// }
// return c3
// }
//
// func (pb *Problem) preprocess() {
// log.Printf("Preprocessing... %d clauses currently", len(pb.Clauses))
// occurs := make([][]int, pb.NbVars*2)
// for i, c := range pb.Clauses {
// for j := 0; j < c.Len(); j++ {
// occurs[c.Get(j)] = append(occurs[c.Get(j)], i)
// }
// }
// modified := true
// neverModified := true
// for modified {
// modified = false
// for i := 0; i < pb.NbVars; i++ {
// if pb.Model[i] != 0 {
// continue
// }
// v := Var(i)
// lit := v.Lit()
// nbLit := len(occurs[lit])
// nbLit2 := len(occurs[lit.Negation()])
// if (nbLit < 10 || nbLit2 < 10) && (nbLit != 0 || nbLit2 != 0) {
// modified = true
// neverModified = false
// // pb.deleted[v] = true
// log.Printf("%d can be removed: %d and %d", lit.Int(), len(occurs[lit]), len(occurs[lit.Negation()]))
// for _, idx1 := range occurs[lit] {
// for _, idx2 := range occurs[lit.Negation()] {
// c1 := pb.Clauses[idx1]
// c2 := pb.Clauses[idx2]
// newC := c1.Generate(c2, v)
// if !newC.Simplify() {
// switch newC.Len() {
// case 0:
// log.Printf("Inferred UNSAT")
// pb.Status = Unsat
// return
// case 1:
// log.Printf("Unit %d", newC.First().Int())
// lit2 := newC.First()
// if lit2.IsPositive() {
// if pb.Model[lit2.Var()] == -1 {
// pb.Status = Unsat
// return
// }
// pb.Model[lit2.Var()] = 1
// } else {
// if pb.Model[lit2.Var()] == 1 {
// pb.Status = Unsat
// return
// }
// pb.Model[lit2.Var()] = -1
// }
// pb.Units = append(pb.Units, lit2)
// default:
// pb.Clauses = append(pb.Clauses, newC)
// }
// }
// }
// }
// nbRemoved := 0
// for _, idx := range occurs[lit] {
// pb.Clauses[idx] = pb.Clauses[len(pb.Clauses)-nbRemoved-1]
// nbRemoved++
// }
// for _, idx := range occurs[lit.Negation()] {
// pb.Clauses[idx] = pb.Clauses[len(pb.Clauses)-nbRemoved-1]
// nbRemoved++
// }
// pb.Clauses = pb.Clauses[:len(pb.Clauses)-nbRemoved]
// log.Printf("clauses=%s", pb.CNF())
// // Redo occurs
// occurs = make([][]int, pb.NbVars*2)
// for i, c := range pb.Clauses {
// for j := 0; j < c.Len(); j++ {
// occurs[c.Get(j)] = append(occurs[c.Get(j)], i)
// }
// }
// continue
// }
// }
// }
// if !neverModified {
// pb.simplify()
// }
// log.Printf("Done. %d clauses now", len(pb.Clauses))
// }

347
vendor/github.com/crillab/gophersat/solver/problem.go generated vendored Normal file
View File

@@ -0,0 +1,347 @@
package solver
import (
"fmt"
)
// A Problem is a list of clauses & a nb of vars.
type Problem struct {
NbVars int // Total nb of vars
Clauses []*Clause // List of non-empty, non-unit clauses
Status Status // Status of the problem. Can be trivially UNSAT (if empty clause was met or inferred by UP) or Indet.
Units []Lit // List of unit literal found in the problem.
Model []decLevel // For each var, its inferred binding. 0 means unbound, 1 means bound to true, -1 means bound to false.
minLits []Lit // For an optimisation problem, the list of lits whose sum must be minimized
minWeights []int // For an optimisation problem, the weight of each lit.
}
// Optim returns true iff pb is an optimisation problem, ie
// a problem for which we not only want to find a model, but also
// the best possible model according to an optimization constraint.
func (pb *Problem) Optim() bool {
return pb.minLits != nil
}
// CNF returns a DIMACS CNF representation of the problem.
func (pb *Problem) CNF() string {
res := fmt.Sprintf("p cnf %d %d\n", pb.NbVars, len(pb.Clauses)+len(pb.Units))
for _, unit := range pb.Units {
res += fmt.Sprintf("%d 0\n", unit.Int())
}
for _, clause := range pb.Clauses {
res += fmt.Sprintf("%s\n", clause.CNF())
}
return res
}
// PBString returns a representation of the problem as a pseudo-boolean problem.
func (pb *Problem) PBString() string {
res := pb.costFuncString()
for _, unit := range pb.Units {
sign := ""
if !unit.IsPositive() {
sign = "~"
unit = unit.Negation()
}
res += fmt.Sprintf("1 %sx%d = 1 ;\n", sign, unit.Int())
}
for _, clause := range pb.Clauses {
res += fmt.Sprintf("%s\n", clause.PBString())
}
return res
}
// SetCostFunc sets the function to minimize when optimizing the problem.
// If all weights are 1, weights can be nil.
// In all other cases, len(lits) must be the same as len(weights).
func (pb *Problem) SetCostFunc(lits []Lit, weights []int) {
if weights != nil && len(lits) != len(weights) {
panic("length of lits and of weights don't match")
}
pb.minLits = lits
pb.minWeights = weights
}
// costFuncString returns a string representation of the cost function of the problem, if any, followed by a \n.
// If there is no cost function, the empty string will be returned.
func (pb *Problem) costFuncString() string {
if pb.minLits == nil {
return ""
}
res := "min: "
for i, lit := range pb.minLits {
w := 1
if pb.minWeights != nil {
w = pb.minWeights[i]
}
sign := ""
if w >= 0 && i != 0 { // No plus sign for the first term or for negative terms.
sign = "+"
}
val := lit.Int()
neg := ""
if val < 0 {
val = -val
neg = "~"
}
res += fmt.Sprintf("%s%d %sx%d", sign, w, neg, val)
}
res += " ;\n"
return res
}
func (pb *Problem) updateStatus(nbClauses int) {
pb.Clauses = pb.Clauses[:nbClauses]
if pb.Status == Indet && nbClauses == 0 {
pb.Status = Sat
}
}
func (pb *Problem) simplify() {
idxClauses := make([][]int, pb.NbVars*2) // For each lit, indexes of clauses it appears in
removed := make([]bool, len(pb.Clauses)) // Clauses that have to be removed
for i := range pb.Clauses {
pb.simplifyClause(i, idxClauses, removed)
if pb.Status == Unsat {
return
}
}
for i := 0; i < len(pb.Units); i++ {
lit := pb.Units[i]
neg := lit.Negation()
clauses := idxClauses[neg]
for j := range clauses {
pb.simplifyClause(j, idxClauses, removed)
}
}
pb.rmClauses(removed)
}
func (pb *Problem) simplifyClause(idx int, idxClauses [][]int, removed []bool) {
c := pb.Clauses[idx]
k := 0
sat := false
for j := 0; j < c.Len(); j++ {
lit := c.Get(j)
v := lit.Var()
if pb.Model[v] == 0 {
c.Set(k, c.Get(j))
k++
idxClauses[lit] = append(idxClauses[lit], idx)
} else if (pb.Model[v] > 0) == lit.IsPositive() {
sat = true
break
}
}
if sat {
removed[idx] = true
return
}
if k == 0 {
pb.Status = Unsat
return
}
if k == 1 {
pb.addUnit(c.First())
if pb.Status == Unsat {
return
}
removed[idx] = true
}
c.Shrink(k)
}
// rmClauses removes clauses that are already satisfied after simplification.
func (pb *Problem) rmClauses(removed []bool) {
j := 0
for i, rm := range removed {
if !rm {
pb.Clauses[j] = pb.Clauses[i]
j++
}
}
pb.Clauses = pb.Clauses[:j]
}
// simplify simplifies the pure SAT problem, i.e runs unit propagation if possible.
func (pb *Problem) simplify2() {
nbClauses := len(pb.Clauses)
restart := true
for restart {
restart = false
i := 0
for i < nbClauses {
c := pb.Clauses[i]
nbLits := c.Len()
clauseSat := false
j := 0
for j < nbLits {
lit := c.Get(j)
if pb.Model[lit.Var()] == 0 {
j++
} else if (pb.Model[lit.Var()] == 1) == lit.IsPositive() {
clauseSat = true
break
} else {
nbLits--
c.Set(j, c.Get(nbLits))
}
}
if clauseSat {
nbClauses--
pb.Clauses[i] = pb.Clauses[nbClauses]
} else if nbLits == 0 {
pb.Status = Unsat
return
} else if nbLits == 1 { // UP
pb.addUnit(c.First())
if pb.Status == Unsat {
return
}
nbClauses--
pb.Clauses[i] = pb.Clauses[nbClauses]
restart = true // Must restart, since this lit might have made one more clause Unit or SAT.
} else { // nb lits unbound > cardinality
if c.Len() != nbLits {
c.Shrink(nbLits)
}
i++
}
}
}
pb.updateStatus(nbClauses)
}
// simplifyCard simplifies the problem, i.e runs unit propagation if possible.
func (pb *Problem) simplifyCard() {
nbClauses := len(pb.Clauses)
restart := true
for restart {
restart = false
i := 0
for i < nbClauses {
c := pb.Clauses[i]
nbLits := c.Len()
card := c.Cardinality()
clauseSat := false
nbSat := 0
j := 0
for j < nbLits {
lit := c.Get(j)
if pb.Model[lit.Var()] == 0 {
j++
} else if (pb.Model[lit.Var()] == 1) == lit.IsPositive() {
nbSat++
if nbSat == card {
clauseSat = true
break
}
} else {
nbLits--
c.Set(j, c.Get(nbLits))
}
}
if clauseSat {
nbClauses--
pb.Clauses[i] = pb.Clauses[nbClauses]
} else if nbLits < card {
pb.Status = Unsat
return
} else if nbLits == card { // UP
pb.addUnits(c, nbLits)
if pb.Status == Unsat {
return
}
nbClauses--
pb.Clauses[i] = pb.Clauses[nbClauses]
restart = true // Must restart, since this lit might have made one more clause Unit or SAT.
} else { // nb lits unbound > cardinality
if c.Len() != nbLits {
c.Shrink(nbLits)
}
i++
}
}
}
pb.updateStatus(nbClauses)
}
func (pb *Problem) simplifyPB() {
modified := true
for modified {
modified = false
i := 0
for i < len(pb.Clauses) {
c := pb.Clauses[i]
j := 0
card := c.Cardinality()
wSum := c.WeightSum()
for j < c.Len() {
lit := c.Get(j)
v := lit.Var()
w := c.Weight(j)
if pb.Model[v] == 0 { // Literal not assigned: is it unit?
if wSum-w < card { // Lit must be true for the clause to be satisfiable
pb.addUnit(lit)
if pb.Status == Unsat {
return
}
c.removeLit(j)
card -= w
c.updateCardinality(-w)
wSum -= w
modified = true
} else {
j++
}
} else { // Bound literal: remove it and update, if needed, cardinality
wSum -= w
if (pb.Model[v] == 1) == lit.IsPositive() {
card -= w
c.updateCardinality(-w)
}
c.removeLit(j)
modified = true
}
}
if card <= 0 { // Clause is Sat
pb.Clauses[i] = pb.Clauses[len(pb.Clauses)-1]
pb.Clauses = pb.Clauses[:len(pb.Clauses)-1]
modified = true
} else if wSum < card {
pb.Clauses = nil
pb.Status = Unsat
return
} else {
i++
}
}
}
if pb.Status == Indet && len(pb.Clauses) == 0 {
pb.Status = Sat
}
}
func (pb *Problem) addUnit(lit Lit) {
if lit.IsPositive() {
if pb.Model[lit.Var()] == -1 {
pb.Status = Unsat
return
}
pb.Model[lit.Var()] = 1
} else {
if pb.Model[lit.Var()] == 1 {
pb.Status = Unsat
return
}
pb.Model[lit.Var()] = -1
}
pb.Units = append(pb.Units, lit)
}
func (pb *Problem) addUnits(c *Clause, nbLits int) {
for i := 0; i < nbLits; i++ {
lit := c.Get(i)
pb.addUnit(lit)
}
}

146
vendor/github.com/crillab/gophersat/solver/queue.go generated vendored Normal file
View File

@@ -0,0 +1,146 @@
/******************************************************************************************[Heap.h]
Copyright (c) 2003-2006, Niklas Een, Niklas Sorensson
Copyright (c) 2007-2010, Niklas Sorensson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**************************************************************************************************/
package solver
// A heap implementation with support for decrease/increase key. This is
// strongly inspired from Minisat's mtl/Heap.h.
type queue struct {
activity []float64 // Activity of each variable. This should be the solver's slice, not a copy.
content []int // Actual content.
indices []int // Reverse queue, i.e position of each item in content; -1 means absence.
}
func newQueue(activity []float64) queue {
q := queue{
activity: activity,
}
for i := range q.activity {
q.insert(i)
}
return q
}
func (q *queue) lt(i, j int) bool {
return q.activity[i] > q.activity[j]
}
// Traversal functions.
func left(i int) int { return i*2 + 1 }
func right(i int) int { return (i + 1) * 2 }
func parent(i int) int { return (i - 1) >> 1 }
func (q *queue) percolateUp(i int) {
x := q.content[i]
p := parent(i)
for i != 0 && q.lt(x, q.content[p]) {
q.content[i] = q.content[p]
q.indices[q.content[p]] = i
i = p
p = parent(p)
}
q.content[i] = x
q.indices[x] = i
}
func (q *queue) percolateDown(i int) {
x := q.content[i]
for left(i) < len(q.content) {
var child int
if right(i) < len(q.content) && q.lt(q.content[right(i)], q.content[left(i)]) {
child = right(i)
} else {
child = left(i)
}
if !q.lt(q.content[child], x) {
break
}
q.content[i] = q.content[child]
q.indices[q.content[i]] = i
i = child
}
q.content[i] = x
q.indices[x] = i
}
func (q *queue) len() int { return len(q.content) }
func (q *queue) empty() bool { return len(q.content) == 0 }
func (q *queue) contains(n int) bool {
return n < len(q.indices) && q.indices[n] >= 0
}
func (q *queue) get(index int) int {
return q.content[index]
}
func (q *queue) decrease(n int) {
q.percolateUp(q.indices[n])
}
func (q *queue) increase(n int) {
q.percolateDown(q.indices[n])
}
func (q *queue) update(n int) {
if !q.contains(n) {
q.insert(n)
} else {
q.percolateUp(q.indices[n])
q.percolateDown(q.indices[n])
}
}
func (q *queue) insert(n int) {
for i := len(q.indices); i <= n; i++ {
q.indices = append(q.indices, -1)
}
q.indices[n] = len(q.content)
q.content = append(q.content, n)
q.percolateUp(q.indices[n])
}
func (q *queue) removeMin() int {
x := q.content[0]
q.content[0] = q.content[len(q.content)-1]
q.indices[q.content[0]] = 0
q.indices[x] = -1
q.content = q.content[:len(q.content)-1]
if len(q.content) > 1 {
q.percolateDown(0)
}
return x
}
// Rebuild the heap from scratch, using the elements in 'ns':
func (q *queue) build(ns []int) {
for i := range q.content {
q.indices[q.content[i]] = -1
}
q.content = q.content[:0]
for i, val := range ns {
q.indices[val] = i
q.content = append(q.content, val)
}
for i := len(q.content)/2 - 1; i >= 0; i-- {
q.percolateDown(i)
}
}

844
vendor/github.com/crillab/gophersat/solver/solver.go generated vendored Normal file
View File

@@ -0,0 +1,844 @@
package solver
import (
"fmt"
"sort"
"strings"
"time"
)
const (
initNbMaxClauses = 2000 // Maximum # of learned clauses, at first.
incrNbMaxClauses = 300 // By how much # of learned clauses is incremented at each conflict.
incrPostponeNbMax = 1000 // By how much # of learned is increased when lots of good clauses are currently learned.
clauseDecay = 0.999 // By how much clauses bumping decays over time.
defaultVarDecay = 0.8 // On each var decay, how much the varInc should be decayed at startup
)
// Stats are statistics about the resolution of the problem.
// They are provided for information purpose only.
type Stats struct {
NbRestarts int
NbConflicts int
NbDecisions int
NbUnitLearned int // How many unit clauses were learned
NbBinaryLearned int // How many binary clauses were learned
NbLearned int // How many clauses were learned
NbDeleted int // How many clauses were deleted
}
// The level a decision was made.
// A negative value means "negative assignement at that level".
// A positive value means "positive assignment at that level".
type decLevel int
// A Model is a binding for several variables.
// It can be totally bound (i.e all vars have a true or false binding)
// or only partially (i.e some vars have no binding yet or their binding has no impact).
// Each var, in order, is associated with a binding. Binding are implemented as
// decision levels:
// - a 0 value means the variable is free,
// - a positive value means the variable was set to true at the given decLevel,
// - a negative value means the variable was set to false at the given decLevel.
type Model []decLevel
func (m Model) String() string {
bound := make(map[int]decLevel)
for i := range m {
if m[i] != 0 {
bound[i+1] = m[i]
}
}
return fmt.Sprintf("%v", bound)
}
// A Solver solves a given problem. It is the main data structure.
type Solver struct {
Verbose bool // Indicates whether the solver should display information during solving or not. False by default
nbVars int
status Status
wl watcherList
trail []Lit // Current assignment stack
model Model // 0 means unbound, other value is a binding
lastModel Model // Placeholder for last model found, useful when looking for several models
activity []float64 // How often each var is involved in conflicts
polarity []bool // Preferred sign for each var
// For each var, clause considered when it was unified
// If the var is not bound yet, or if it was bound by a decision, value is nil.
reason []*Clause
varQueue queue
varInc float64 // On each var bump, how big the increment should be
clauseInc float32 // On each var bump, how big the increment should be
lbdStats lbdStats
Stats Stats // Statistics about the solving process.
minLits []Lit // Lits to minimize if the problem was an optimization problem.
minWeights []int // Weight of each lit to minimize if the problem was an optimization problem.
asumptions []Lit // Literals that are, ideally, true. Useful when trying to minimize a function.
localNbRestarts int // How many restarts since Solve() was called?
varDecay float64 // On each var decay, how much the varInc should be decayed
trailBuf []int // A buffer while cleaning bindings
}
// New makes a solver, given a number of variables and a set of clauses.
// nbVars should be consistent with the content of clauses, i.e.
// the biggest variable in clauses should be >= nbVars.
func New(problem *Problem) *Solver {
if problem.Status == Unsat {
return &Solver{status: Unsat}
}
nbVars := problem.NbVars
s := &Solver{
nbVars: nbVars,
status: problem.Status,
trail: make([]Lit, len(problem.Units), nbVars),
model: problem.Model,
activity: make([]float64, nbVars),
polarity: make([]bool, nbVars),
reason: make([]*Clause, nbVars),
varInc: 1.0,
clauseInc: 1.0,
minLits: problem.minLits,
minWeights: problem.minWeights,
varDecay: defaultVarDecay,
trailBuf: make([]int, nbVars),
}
s.resetOptimPolarity()
s.initOptimActivity()
s.initWatcherList(problem.Clauses)
s.varQueue = newQueue(s.activity)
for i, lit := range problem.Units {
if lit.IsPositive() {
s.model[lit.Var()] = 1
} else {
s.model[lit.Var()] = -1
}
s.trail[i] = lit
}
return s
}
// sets initial activity for optimization variables, if any.
func (s *Solver) initOptimActivity() {
for i, lit := range s.minLits {
w := 1
if s.minWeights != nil {
w = s.minWeights[i]
}
s.activity[lit.Var()] += float64(w)
}
}
// resets polarity of optimization lits so that they are negated by default.
func (s *Solver) resetOptimPolarity() {
if s.minLits != nil {
for _, lit := range s.minLits {
s.polarity[lit.Var()] = !lit.IsPositive() // Try to make lits from the optimization clause false
}
}
}
// Optim returns true iff the underlying problem is an optimization problem (rather than a satisfaction one).
func (s *Solver) Optim() bool {
return s.minLits != nil
}
// OutputModel outputs the model for the problem on stdout.
func (s *Solver) OutputModel() {
if s.status == Sat || s.lastModel != nil {
fmt.Printf("s SATISFIABLE\nv ")
model := s.model
if s.lastModel != nil {
model = s.lastModel
}
for i, val := range model {
if val < 0 {
fmt.Printf("%d ", -i-1)
} else {
fmt.Printf("%d ", i+1)
}
}
fmt.Printf("\n")
} else if s.status == Unsat {
fmt.Printf("s UNSATISFIABLE\n")
} else {
fmt.Printf("s INDETERMINATE\n")
}
}
// litStatus returns whether the literal is made true (Sat) or false (Unsat) by the
// current bindings, or if it is unbounded (Indet).
func (s *Solver) litStatus(l Lit) Status {
assign := s.model[l.Var()]
if assign == 0 {
return Indet
}
if assign > 0 == l.IsPositive() {
return Sat
}
return Unsat
}
func (s *Solver) varDecayActivity() {
s.varInc *= 1 / s.varDecay
}
func (s *Solver) varBumpActivity(v Var) {
s.activity[v] += s.varInc
if s.activity[v] > 1e100 { // Rescaling is needed to avoid overflowing
for i := range s.activity {
s.activity[i] *= 1e-100
}
s.varInc *= 1e-100
}
if s.varQueue.contains(int(v)) {
s.varQueue.decrease(int(v))
}
}
// Decays each clause's activity
func (s *Solver) clauseDecayActivity() {
s.clauseInc *= 1 / clauseDecay
}
// Bumps the given clause's activity.
func (s *Solver) clauseBumpActivity(c *Clause) {
if c.Learned() {
c.activity += s.clauseInc
if c.activity > 1e30 { // Rescale to avoid overflow
for _, c2 := range s.wl.learned {
c2.activity *= 1e-30
}
s.clauseInc *= 1e-30
}
}
}
// Chooses an unbound literal to be tested, or -1
// if all the variables are already bound.
func (s *Solver) chooseLit() Lit {
v := Var(-1)
for v == -1 && !s.varQueue.empty() {
if v2 := Var(s.varQueue.removeMin()); s.model[v2] == 0 { // Ignore already bound vars
v = v2
}
}
if v == -1 {
return Lit(-1)
}
s.Stats.NbDecisions++
return v.SignedLit(!s.polarity[v])
}
func abs(val decLevel) decLevel {
if val < 0 {
return -val
}
return val
}
// Reinitializes bindings (both model & reason) for all variables bound at a decLevel >= lvl.
// TODO: check this method as it has a weird behavior regarding performance.
// TODO: clean-up commented-out code and understand underlying performance pattern.
func (s *Solver) cleanupBindings(lvl decLevel) {
i := 0
for i < len(s.trail) && abs(s.model[s.trail[i].Var()]) <= lvl {
i++
}
/*
for j := len(s.trail) - 1; j >= i; j-- {
lit2 := s.trail[j]
v := lit2.Var()
s.model[v] = 0
if s.reason[v] != nil {
s.reason[v].unlock()
s.reason[v] = nil
}
s.polarity[v] = lit2.IsPositive()
if !s.varQueue.contains(int(v)) {
s.varQueue.insert(int(v))
}
}
s.trail = s.trail[:i]
*/
toInsert := s.trailBuf[:0] // make([]int, 0, len(s.trail)-i)
for j := i; j < len(s.trail); j++ {
lit2 := s.trail[j]
v := lit2.Var()
s.model[v] = 0
if s.reason[v] != nil {
s.reason[v].unlock()
s.reason[v] = nil
}
s.polarity[v] = lit2.IsPositive()
if !s.varQueue.contains(int(v)) {
toInsert = append(toInsert, int(v))
s.varQueue.insert(int(v))
}
}
s.trail = s.trail[:i]
for i := len(toInsert) - 1; i >= 0; i-- {
s.varQueue.insert(toInsert[i])
}
/*for i := len(s.trail) - 1; i >= 0; i-- {
lit := s.trail[i]
v := lit.Var()
if abs(s.model[v]) <= lvl { // All lits in trail before here must keep their status.
s.trail = s.trail[:i+1]
break
}
s.model[v] = 0
if s.reason[v] != nil {
s.reason[v].unlock()
s.reason[v] = nil
}
s.polarity[v] = lit.IsPositive()
if !s.varQueue.contains(int(v)) {
s.varQueue.insert(int(v))
}
}*/
s.resetOptimPolarity()
}
// Given the last learnt clause and the levels at which vars were bound,
// Returns the level to bt to and the literal to bind
func backtrackData(c *Clause, model []decLevel) (btLevel decLevel, lit Lit) {
btLevel = abs(model[c.Get(1).Var()])
return btLevel, c.Get(0)
}
func (s *Solver) rebuildOrderHeap() {
ints := make([]int, s.nbVars)
for v := 0; v < s.nbVars; v++ {
if s.model[v] == 0 {
ints = append(ints, int(v))
}
}
s.varQueue.build(ints)
}
// satClause returns true iff c is satisfied by a literal assigned at top level.
func (s *Solver) satClause(c *Clause) bool {
if c.Len() == 2 || c.Cardinality() != 1 || c.PseudoBoolean() {
// TODO improve this, but it will be ok since we only call this function for removing useless clauses.
return false
}
for i := 0; i < c.Len(); i++ {
lit := c.Get(i)
assign := s.model[lit.Var()]
if assign == 1 && lit.IsPositive() || assign == -1 && !lit.IsPositive() {
return true
}
}
return false
}
// propagate binds the given lit, propagates it and searches for a solution,
// until it is found or a restart is needed.
func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status {
for lit != -1 {
if conflict := s.unifyLiteral(lit, lvl); conflict == nil { // Pick new branch or restart
if s.lbdStats.mustRestart() {
s.lbdStats.clear()
s.cleanupBindings(1)
return Indet
}
if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax {
s.wl.idxReduce = (s.Stats.NbConflicts / s.wl.nbMax) + 1
s.reduceLearned()
s.bumpNbMax()
}
lvl++
lit = s.chooseLit()
} else { // Deal with conflict
s.Stats.NbConflicts++
if s.Stats.NbConflicts%5000 == 0 && s.varDecay < 0.95 {
s.varDecay += 0.01
}
s.lbdStats.addConflict(len(s.trail))
learnt, unit := s.learnClause(conflict, lvl)
if learnt == nil { // Unit clause was learned: this lit is known for sure
s.Stats.NbUnitLearned++
s.lbdStats.addLbd(1)
s.cleanupBindings(1)
s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
if conflict = s.unifyLiteral(unit, 1); conflict != nil { // top-level conflict
s.status = Unsat
return Unsat
}
// s.rmSatClauses()
s.rebuildOrderHeap()
lit = s.chooseLit()
lvl = 2
} else {
if learnt.Len() == 2 {
s.Stats.NbBinaryLearned++
}
s.Stats.NbLearned++
s.lbdStats.addLbd(learnt.lbd())
s.addLearned(learnt)
lvl, lit = backtrackData(learnt, s.model)
s.cleanupBindings(lvl)
s.reason[lit.Var()] = learnt
learnt.lock()
}
}
}
return Sat
}
// Searches until a restart is needed.
func (s *Solver) search() Status {
s.localNbRestarts++
lvl := decLevel(2) // Level starts at 2, for implementation reasons : 1 is for top-level bindings; 0 means "no level assigned yet"
s.status = s.propagateAndSearch(s.chooseLit(), lvl)
return s.status
}
// Solve solves the problem associated with the solver and returns the appropriate status.
func (s *Solver) Solve() Status {
if s.status == Unsat {
return s.status
}
s.status = Indet
//s.lbdStats.clear()
s.localNbRestarts = 0
var end chan struct{}
if s.Verbose {
end = make(chan struct{})
defer close(end)
go func() { // Function displaying stats during resolution
fmt.Printf("c ======================================================================================\n")
fmt.Printf("c | Restarts | Conflicts | Learned | Deleted | Del%% | Reduce | Units learned |\n")
fmt.Printf("c ======================================================================================\n")
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
select {
case <-ticker.C:
case <-end:
return
}
if s.status == Indet {
iter := s.Stats.NbRestarts + 1
nbConfl := s.Stats.NbConflicts
nbReduce := s.wl.idxReduce - 1
nbLearned := len(s.wl.learned)
nbDel := s.Stats.NbDeleted
pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
nbUnit := s.Stats.NbUnitLearned
fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
}
}
}()
}
for s.status == Indet {
s.search()
if s.status == Indet {
s.Stats.NbRestarts++
s.rebuildOrderHeap()
}
}
if s.status == Sat {
s.lastModel = make(Model, len(s.model))
copy(s.lastModel, s.model)
}
if s.Verbose {
end <- struct{}{}
fmt.Printf("c ======================================================================================\n")
}
return s.status
}
// Enumerate returns the total number of models for the given problems.
// if "models" is non-nil, it will write models on it as soon as it discovers them.
// models will be closed at the end of the method.
func (s *Solver) Enumerate(models chan []bool, stop chan struct{}) int {
if models != nil {
defer close(models)
}
s.lastModel = make(Model, len(s.model))
nb := 0
lit := s.chooseLit()
var lvl decLevel
for s.status != Unsat {
for s.status == Indet {
s.search()
if s.status == Indet {
s.Stats.NbRestarts++
}
}
if s.status == Sat {
nb++
if models != nil {
copy(s.lastModel, s.model)
models <- s.Model()
}
s.status = Indet
lits := s.decisionLits()
switch len(lits) {
case 0:
s.status = Unsat
case 1:
s.propagateUnits(lits)
default:
c := NewClause(lits)
s.appendClause(c)
lit = lits[len(lits)-1]
v := lit.Var()
lvl = abs(s.model[v]) - 1
s.cleanupBindings(lvl)
s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
s.propagateAndSearch(lit, lvl)
}
}
}
return nb
}
// CountModels returns the total number of models for the given problem.
func (s *Solver) CountModels() int {
var end chan struct{}
if s.Verbose {
end = make(chan struct{})
defer close(end)
go func() { // Function displaying stats during resolution
fmt.Printf("c ======================================================================================\n")
fmt.Printf("c | Restarts | Conflicts | Learned | Deleted | Del%% | Reduce | Units learned |\n")
fmt.Printf("c ======================================================================================\n")
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
select {
case <-ticker.C:
case <-end:
return
}
if s.status == Indet {
iter := s.Stats.NbRestarts + 1
nbConfl := s.Stats.NbConflicts
nbReduce := s.wl.idxReduce - 1
nbLearned := len(s.wl.learned)
nbDel := s.Stats.NbDeleted
pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
nbUnit := s.Stats.NbUnitLearned
fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
}
}
}()
}
nb := 0
lit := s.chooseLit()
var lvl decLevel
for s.status != Unsat {
for s.status == Indet {
s.search()
if s.status == Indet {
s.Stats.NbRestarts++
}
}
if s.status == Sat {
nb++
if s.Verbose {
fmt.Printf("c found %d model(s)\n", nb)
}
s.status = Indet
lits := s.decisionLits()
switch len(lits) {
case 0:
s.status = Unsat
case 1:
s.propagateUnits(lits)
default:
c := NewClause(lits)
s.appendClause(c)
lit = lits[len(lits)-1]
v := lit.Var()
lvl = abs(s.model[v]) - 1
s.cleanupBindings(lvl)
s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
s.propagateAndSearch(lit, lvl)
}
}
}
if s.Verbose {
end <- struct{}{}
fmt.Printf("c ======================================================================================\n")
}
return nb
}
// decisionLits returns the negation of all decision values once a model was found, ordered by decision levels.
// This will allow for searching other models.
func (s *Solver) decisionLits() []Lit {
lastLit := s.trail[len(s.trail)-1]
lvls := abs(s.model[lastLit.Var()])
lits := make([]Lit, lvls-1)
for i, r := range s.reason {
if lvl := abs(s.model[i]); r == nil && lvl > 1 {
if s.model[i] < 0 {
// lvl-2 : levels beside unit clauses start at 2, not 0 or 1!
lits[lvl-2] = IntToLit(int32(i + 1))
} else {
lits[lvl-2] = IntToLit(int32(-i - 1))
}
}
}
return lits
}
func (s *Solver) propagateUnits(units []Lit) {
for _, unit := range units {
s.lbdStats.addLbd(1)
s.Stats.NbUnitLearned++
s.cleanupBindings(1)
s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
if s.unifyLiteral(unit, 1) != nil {
s.status = Unsat
return
}
s.rebuildOrderHeap()
}
}
// PBString returns a representation of the solver's state as a pseudo-boolean problem.
func (s *Solver) PBString() string {
meta := fmt.Sprintf("* #variable= %d #constraint= %d #learned= %d\n", s.nbVars, len(s.wl.pbClauses), len(s.wl.learned))
minLine := ""
if s.minLits != nil {
terms := make([]string, len(s.minLits))
for i, lit := range s.minLits {
weight := 1
if s.minWeights != nil {
weight = s.minWeights[i]
}
val := lit.Int()
sign := ""
if val < 0 {
val = -val
sign = "~"
}
terms[i] = fmt.Sprintf("%d %sx%d", weight, sign, val)
}
minLine = fmt.Sprintf("min: %s ;\n", strings.Join(terms, " +"))
}
clauses := make([]string, len(s.wl.pbClauses)+len(s.wl.learned))
for i, c := range s.wl.pbClauses {
clauses[i] = c.PBString()
}
for i, c := range s.wl.learned {
clauses[i+len(s.wl.pbClauses)] = c.PBString()
}
for i := 0; i < len(s.model); i++ {
if s.model[i] == 1 {
clauses = append(clauses, fmt.Sprintf("1 x%d = 1 ;", i+1))
} else if s.model[i] == -1 {
clauses = append(clauses, fmt.Sprintf("1 x%d = 0 ;", i+1))
}
}
return meta + minLine + strings.Join(clauses, "\n")
}
// AppendClause appends a new clause to the set of clauses.
// This is not a learned clause, but a clause that is part of the problem added afterwards (during model counting, for instance).
func (s *Solver) AppendClause(clause *Clause) {
s.cleanupBindings(1)
card := clause.Cardinality()
minW := 0
maxW := 0
i := 0
for i < clause.Len() {
lit := clause.Get(i)
switch s.litStatus(lit) {
case Sat:
w := clause.Weight(i)
minW += w
maxW += w
clause.removeLit(i)
clause.updateCardinality(-w)
case Unsat:
clause.removeLit(i)
default:
maxW += clause.Weight(i)
i++
}
}
if minW >= card { // clause is already sat
return
}
if maxW < card { // clause cannot be satisfied
s.status = Unsat
return
}
if maxW == card { // Unit
s.propagateUnits(clause.lits)
} else {
s.appendClause(clause)
}
}
// Model returns a slice that associates, to each variable, its binding.
// If s's status is not Sat, the method will panic.
func (s *Solver) Model() []bool {
if s.lastModel == nil {
panic("cannot call Model() from a non-Sat solver")
}
res := make([]bool, s.nbVars)
for i, lvl := range s.lastModel {
res[i] = lvl > 0
}
return res
}
// Optimal returns the optimal solution, if any.
// If results is non-nil, all solutions will be written to it.
// In any case, results will be closed at the end of the call.
func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) {
if results != nil {
defer close(results)
}
status := s.Solve()
if status == Unsat { // Problem cannot be satisfied at all
res.Status = Unsat
if results != nil {
results <- res
}
return res
}
if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
s.lastModel = make(Model, len(s.model))
copy(s.lastModel, s.model)
res := Result{
Status: Sat,
Model: s.Model(),
Weight: 0,
}
if results != nil {
results <- res
}
return res
}
maxCost := 0
if s.minWeights == nil {
maxCost = len(s.minLits)
} else {
for _, w := range s.minWeights {
maxCost += w
}
}
s.asumptions = make([]Lit, len(s.minLits))
for i, lit := range s.minLits {
s.asumptions[i] = lit.Negation()
}
weights := make([]int, len(s.minWeights))
copy(weights, s.minWeights)
sort.Sort(wLits{lits: s.asumptions, weights: weights})
s.lastModel = make(Model, len(s.model))
var cost int
for status == Sat {
copy(s.lastModel, s.model) // Save this model: it might be the last one
cost = 0
for i, lit := range s.minLits {
if (s.model[lit.Var()] > 0) == lit.IsPositive() {
if s.minWeights == nil {
cost++
} else {
cost += s.minWeights[i]
}
}
}
res = Result{
Status: Sat,
Model: s.Model(),
Weight: cost,
}
if results != nil {
results <- res
}
if cost == 0 {
break
}
// Add a constraint incrementing current best cost
lits2 := make([]Lit, len(s.minLits))
weights2 := make([]int, len(s.minWeights))
copy(lits2, s.asumptions)
copy(weights2, weights)
s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
s.rebuildOrderHeap()
status = s.Solve()
}
return res
}
// Minimize tries to find a model that minimizes the weight of the clause defined as the optimisation clause in the problem.
// If no model can be found, it will return a cost of -1.
// Otherwise, calling s.Model() afterwards will return the model that satisfy the formula, such that no other model with a smaller cost exists.
// If this function is called on a non-optimization problem, it will either return -1, or a cost of 0 associated with a
// satisfying model (ie any model is an optimal model).
func (s *Solver) Minimize() int {
status := s.Solve()
if status == Unsat { // Problem cannot be satisfied at all
return -1
}
if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
return 0
}
maxCost := 0
if s.minWeights == nil {
maxCost = len(s.minLits)
} else {
for _, w := range s.minWeights {
maxCost += w
}
}
s.asumptions = make([]Lit, len(s.minLits))
for i, lit := range s.minLits {
s.asumptions[i] = lit.Negation()
}
weights := make([]int, len(s.minWeights))
copy(weights, s.minWeights)
sort.Sort(wLits{lits: s.asumptions, weights: weights})
s.lastModel = make(Model, len(s.model))
var cost int
for status == Sat {
copy(s.lastModel, s.model) // Save this model: it might be the last one
cost = 0
for i, lit := range s.minLits {
if (s.model[lit.Var()] > 0) == lit.IsPositive() {
if s.minWeights == nil {
cost++
} else {
cost += s.minWeights[i]
}
}
}
if cost == 0 {
return 0
}
if s.Verbose {
fmt.Printf("o %d\n", cost)
}
// Add a constraint incrementing current best cost
lits2 := make([]Lit, len(s.minLits))
weights2 := make([]int, len(s.minWeights))
copy(lits2, s.asumptions)
copy(weights2, weights)
s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
s.rebuildOrderHeap()
status = s.Solve()
}
return cost
}
// functions to sort asumptions for pseudo-boolean minimization clause.
type wLits struct {
lits []Lit
weights []int
}
func (wl wLits) Len() int { return len(wl.lits) }
func (wl wLits) Less(i, j int) bool { return wl.weights[i] > wl.weights[j] }
func (wl wLits) Swap(i, j int) {
wl.lits[i], wl.lits[j] = wl.lits[j], wl.lits[i]
wl.weights[i], wl.weights[j] = wl.weights[j], wl.weights[i]
}

23
vendor/github.com/crillab/gophersat/solver/sort.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package solver
import "sort"
// clauseSorter is a structure to facilitate the sorting of lits in a learned clause
// according to their respective decision levels.
type clauseSorter struct {
lits []Lit
model Model
}
func (cs *clauseSorter) Len() int { return len(cs.lits) }
func (cs *clauseSorter) Less(i, j int) bool {
return abs(cs.model[cs.lits[i].Var()]) > abs(cs.model[cs.lits[j].Var()])
}
func (cs *clauseSorter) Swap(i, j int) { cs.lits[i], cs.lits[j] = cs.lits[j], cs.lits[i] }
// sortLiterals sorts the literals depending on the decision level they were bound
// i.e. abs(model[lits[i]]) <= abs(model[lits[i+1]]).
func sortLiterals(lits []Lit, model []decLevel) {
cs := &clauseSorter{lits, model}
sort.Sort(cs)
}

95
vendor/github.com/crillab/gophersat/solver/types.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
package solver
// Describes basic types and constants that are used in the solver
// Status is the status of a given problem or clause at a given moment.
type Status byte
const (
// Indet means the problem is not proven sat or unsat yet.
Indet = Status(iota)
// Sat means the problem or clause is satisfied.
Sat
// Unsat means the problem or clause is unsatisfied.
Unsat
// Unit is a constant meaning the clause contains only one unassigned literal.
Unit
// Many is a constant meaning the clause contains at least 2 unassigned literals.
Many
)
func (s Status) String() string {
switch s {
case Indet:
return "INDETERMINATE"
case Sat:
return "SAT"
case Unsat:
return "UNSAT"
case Unit:
return "UNIT"
case Many:
return "MANY"
default:
panic("invalid status")
}
}
// Var start at 0 ; thus the CNF variable 1 is encoded as the Var 0.
type Var int32
// Lit start at 0 and are positive ; the sign is the last bit.
// Thus the CNF literal -3 is encoded as 2 * (3-1) + 1 = 5.
type Lit int32
// IntToLit converts a CNF literal to a Lit.
func IntToLit(i int32) Lit {
if i < 0 {
return Lit(2*(-i-1) + 1)
}
return Lit(2 * (i - 1))
}
// IntToVar converts a CNF variable to a Var.
func IntToVar(i int32) Var {
return Var(i - 1)
}
// Lit returns the positive Lit associated to v.
func (v Var) Lit() Lit {
return Lit(v * 2)
}
// SignedLit returns the Lit associated to v, negated if 'signed', positive else.
func (v Var) SignedLit(signed bool) Lit {
if signed {
return Lit(v*2) + 1
}
return Lit(v * 2)
}
// Var returns the variable of l.
func (l Lit) Var() Var {
return Var(l / 2)
}
// Int returns the equivalent CNF literal.
func (l Lit) Int() int32 {
sign := l&1 == 1
res := int32((l / 2) + 1)
if sign {
return -res
}
return res
}
// IsPositive is true iff l is > 0
func (l Lit) IsPositive() bool {
return l%2 == 0
}
// Negation returns -l, i.e the positive version of l if it is negative,
// or the negative version otherwise.
func (l Lit) Negation() Lit {
return l ^ 1
}

448
vendor/github.com/crillab/gophersat/solver/watcher.go generated vendored Normal file
View File

@@ -0,0 +1,448 @@
package solver
import "sort"
type watcher struct {
other Lit // Another lit from the clause
clause *Clause
}
// A watcherList is a structure used to store clauses and propagate unit literals efficiently.
type watcherList struct {
nbMax int // Max # of learned clauses at current moment
idxReduce int // # of calls to reduce + 1
wlistBin [][]watcher // For each literal, a list of binary clauses where its negation appears
wlist [][]watcher // For each literal, a list of non-binary clauses where its negation appears atposition 1 or 2
wlistPb [][]*Clause // For each literal a list of PB or cardinality constraints.
pbClauses []*Clause // All the problem clauses.
learned []*Clause
}
// initWatcherList makes a new watcherList for the solver.
func (s *Solver) initWatcherList(clauses []*Clause) {
nbMax := initNbMaxClauses
newClauses := make([]*Clause, len(clauses))
copy(newClauses, clauses)
s.wl = watcherList{
nbMax: nbMax,
idxReduce: 1,
wlistBin: make([][]watcher, s.nbVars*2),
wlist: make([][]watcher, s.nbVars*2),
wlistPb: make([][]*Clause, s.nbVars*2),
pbClauses: newClauses,
}
for _, c := range clauses {
s.watchClause(c)
}
}
// appendClause appends the clause without checking whether the clause is already satisfiable, unit, or unsatisfiable.
// To perform those checks, call s.AppendClause.
// clause is supposed to be a problem clause, not a learned one.
func (s *Solver) appendClause(clause *Clause) {
s.wl.pbClauses = append(s.wl.pbClauses, clause)
s.watchClause(clause)
}
// bumpNbMax increases the max nb of clauses used.
// It is typically called after a restart.
func (s *Solver) bumpNbMax() {
s.wl.nbMax += incrNbMaxClauses
}
// postponeNbMax increases the max nb of clauses used.
// It is typically called when too many good clauses were learned and a cleaning was expected.
func (s *Solver) postponeNbMax() {
s.wl.nbMax += incrPostponeNbMax
}
// Utilities for sorting according to clauses' LBD and activities.
func (wl *watcherList) Len() int { return len(wl.learned) }
func (wl *watcherList) Swap(i, j int) { wl.learned[i], wl.learned[j] = wl.learned[j], wl.learned[i] }
func (wl *watcherList) Less(i, j int) bool {
ci := wl.learned[i]
cj := wl.learned[j]
lbdI := ci.lbd()
lbdJ := cj.lbd()
// Sort by lbd, break ties by activity
return lbdI > lbdJ || (lbdI == lbdJ && wl.learned[i].activity < wl.learned[j].activity)
}
// Watches the provided clause.
func (s *Solver) watchClause(c *Clause) {
if c.Len() == 2 {
first := c.First()
second := c.Second()
neg0 := first.Negation()
neg1 := second.Negation()
s.wl.wlistBin[neg0] = append(s.wl.wlistBin[neg0], watcher{clause: c, other: second})
s.wl.wlistBin[neg1] = append(s.wl.wlistBin[neg1], watcher{clause: c, other: first})
} else if c.PseudoBoolean() {
w := 0
i := 0
for i < 2 || w <= c.Cardinality() {
lit := c.Get(i)
neg := lit.Negation()
s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
c.pbData.watched[i] = true
w += c.Weight(i)
i++
}
} else if c.Cardinality() > 1 {
for i := 0; i < c.Cardinality()+1; i++ {
lit := c.Get(i)
neg := lit.Negation()
s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
}
} else { // Regular, propositional clause
first := c.First()
second := c.Second()
neg0 := first.Negation()
neg1 := second.Negation()
s.wl.wlist[neg0] = append(s.wl.wlist[neg0], watcher{clause: c, other: second})
s.wl.wlist[neg1] = append(s.wl.wlist[neg1], watcher{clause: c, other: first})
}
}
// unwatch the given learned clause.
// NOTE: since it is only called when c.lbd() > 2, we know for sure
// that c is not a binary clause.
// We also know for sure this is a propositional clause, since only those are learned.
func (s *Solver) unwatchClause(c *Clause) {
for i := 0; i < 2; i++ {
neg := c.Get(i).Negation()
j := 0
length := len(s.wl.wlist[neg])
// We're looking for the index of the clause.
// This will panic if c is not in wlist[neg], but this shouldn't happen.
for s.wl.wlist[neg][j].clause != c {
j++
}
s.wl.wlist[neg][j] = s.wl.wlist[neg][length-1]
s.wl.wlist[neg] = s.wl.wlist[neg][:length-1]
}
}
// reduceLearned removes a few learned clauses that are deemed useless.
func (s *Solver) reduceLearned() {
sort.Sort(&s.wl)
nbLearned := len(s.wl.learned)
length := nbLearned / 2
if s.wl.learned[length].lbd() <= 3 { // Lots of good clauses, postpone reduction
s.postponeNbMax()
}
nbRemoved := 0
for i := 0; i < length; i++ {
c := s.wl.learned[i]
if c.lbd() <= 2 || c.isLocked() {
continue
}
nbRemoved++
s.Stats.NbDeleted++
s.wl.learned[i] = s.wl.learned[nbLearned-nbRemoved]
s.unwatchClause(c)
}
nbLearned -= nbRemoved
s.wl.learned = s.wl.learned[:nbLearned]
}
// Adds the given learned clause and updates watchers.
// If too many clauses have been learned yet, one will be removed.
func (s *Solver) addLearned(c *Clause) {
s.wl.learned = append(s.wl.learned, c)
s.watchClause(c)
s.clauseBumpActivity(c)
}
// If l is negative, -lvl is returned. Else, lvl is returned.
func lvlToSignedLvl(l Lit, lvl decLevel) decLevel {
if l.IsPositive() {
return lvl
}
return -lvl
}
// Removes the first occurrence of c from lst.
// The element *must* be present into lst.
func removeFrom(lst []*Clause, c *Clause) []*Clause {
i := 0
for lst[i] != c {
i++
}
last := len(lst) - 1
lst[i] = lst[last]
return lst[:last]
}
// Propagates literals in the trail starting from the ptrth, and returns a conflict clause, or nil if none arose.
func (s *Solver) propagate(ptr int, lvl decLevel) *Clause {
for ptr < len(s.trail) {
lit := s.trail[ptr]
for _, w := range s.wl.wlistBin[lit] {
v2 := w.other.Var()
if assign := s.model[v2]; assign == 0 { // Other was unbounded: propagate
s.reason[v2] = w.clause
w.clause.lock()
s.model[v2] = lvlToSignedLvl(w.other, lvl)
s.trail = append(s.trail, w.other)
} else if (assign > 0) != w.other.IsPositive() { // Conflict here
return w.clause
}
}
if confl := s.simplifyPropClauses(lit, lvl); confl != nil {
return confl
}
for _, c := range s.wl.wlistPb[lit] {
if c.PseudoBoolean() {
if !s.simplifyPseudoBool(c, lvl) {
return c
}
} else {
if !s.simplifyCardClause(c, lvl) {
return c
}
}
}
ptr++
}
// No unsat clause was met
return nil
}
// Unifies the given literal and returns a conflict clause, or nil if no conflict arose.
func (s *Solver) unifyLiteral(lit Lit, lvl decLevel) *Clause {
s.model[lit.Var()] = lvlToSignedLvl(lit, lvl)
s.trail = append(s.trail, lit)
return s.propagate(len(s.trail)-1, lvl)
}
func (s *Solver) propagateUnit(c *Clause, lvl decLevel, unit Lit) {
v := unit.Var()
s.reason[v] = c
c.lock()
s.model[v] = lvlToSignedLvl(unit, lvl)
s.trail = append(s.trail, unit)
}
func (s *Solver) simplifyPropClauses(lit Lit, lvl decLevel) *Clause {
wl := s.wl.wlist[lit]
j := 0
for i, w := range wl {
if s.litStatus(w.other) == Sat { // blocking literal is SAT? Don't explore clause!
wl[j] = w
j++
continue
}
c := w.clause
// make sure c.Second() is lit
if c.First() == lit.Negation() {
c.swap(0, 1)
}
w2 := watcher{clause: c, other: c.First()}
firstStatus := s.litStatus(c.First())
if firstStatus == Sat { // Clause is already sat
wl[j] = w2
j++
} else {
found := false
for k := 2; k < c.Len(); k++ {
if litK := c.Get(k); s.litStatus(litK) != Unsat {
c.swap(1, k)
neg := litK.Negation()
s.wl.wlist[neg] = append(s.wl.wlist[neg], w2)
found = true
break
}
}
if !found { // No free lit found: unit propagation or UNSAT
wl[j] = w2
j++
if firstStatus == Unsat {
copy(wl[j:], wl[i+1:]) // Copy remaining clauses
s.wl.wlist[lit] = wl[:len(wl)-((i+1)-j)]
return c
}
s.propagateUnit(c, lvl, c.First())
}
}
}
s.wl.wlist[lit] = wl[:j]
return nil
}
// simplifyCardClauses simplifies a clause of cardinality > 1, but with all weights = 1.
// returns false iff the clause cannot be satisfied.
func (s *Solver) simplifyCardClause(clause *Clause, lvl decLevel) bool {
length := clause.Len()
card := clause.Cardinality()
nbTrue := 0
nbFalse := 0
nbUnb := 0
done := false
for i := 0; i < length && !done; i++ {
lit := clause.Get(i)
switch s.litStatus(lit) {
case Indet:
nbUnb++
if nbUnb+nbTrue > card {
done = true
}
case Sat:
nbTrue++
if nbTrue == card {
return true
}
if nbUnb+nbTrue > card {
done = true
}
case Unsat:
nbFalse++
if length-nbFalse < card {
return false
}
}
}
if nbTrue >= card {
return true
}
if nbUnb+nbTrue == card {
// All unbounded lits must be bound to make the clause true
i := 0
for nbUnb > 0 {
lit := clause.Get(i)
if s.model[lit.Var()] == 0 {
s.propagateUnit(clause, lvl, lit)
nbUnb--
} else {
i++
}
}
return true
}
s.swapFalse(clause)
return true
}
// swapFalse swaps enough literals from the clause so that all watching literals are either true or unbounded lits.
// Must only be called when there a at least cardinality + 1 true and unbounded lits.
func (s *Solver) swapFalse(clause *Clause) {
card := clause.Cardinality()
i := 0
j := card + 1
for i < card+1 {
lit := clause.Get(i)
for s.litStatus(lit) != Unsat {
i++
if i == card+1 {
return
}
lit = clause.Get(i)
}
lit = clause.Get(j)
for s.litStatus(lit) == Unsat {
j++
lit = clause.Get(j)
}
ni := &s.wl.wlistPb[clause.Get(i).Negation()]
nj := &s.wl.wlistPb[clause.Get(j).Negation()]
clause.swap(i, j)
*ni = removeFrom(*ni, clause)
*nj = append(*nj, clause)
i++
j++
}
}
// sumWeights returns the sum of weights of the given PB clause, if the clause is not SAT yet.
// If the clause is already SAT, it returns true and the given sum value
// is meaningless.
func (s *Solver) sumWeights(c *Clause) (sum int, sat bool) {
sum = 0
sumSat := 0
card := c.Cardinality()
for i, v := range c.pbData.weights {
if status := s.litStatus(c.Get(i)); status == Indet {
sum += v
} else if status == Sat {
sum += v
sumSat += v
if sumSat >= card {
return sum, true
}
}
}
return sum, false
}
// propagateAll propagates all unbounded literals from c as unit literals
func (s *Solver) propagateAll(c *Clause, lvl decLevel) {
for i := 0; i < c.Len(); i++ {
if lit := c.Get(i); s.litStatus(lit) == Indet {
s.propagateUnit(c, lvl, lit)
}
}
}
// simplifyPseudoBool simplifies a pseudo-boolean constraint.
// propagates unit literals, if any.
// returns false if the clause cannot be satisfied.
func (s *Solver) simplifyPseudoBool(clause *Clause, lvl decLevel) bool {
card := clause.Cardinality()
foundUnit := true
for foundUnit {
sumW, sat := s.sumWeights(clause)
if sat {
return true
}
if sumW < card {
return false
}
if sumW == card {
s.propagateAll(clause, lvl)
return true
}
foundUnit = false
for i := 0; i < clause.Len(); i++ {
lit := clause.Get(i)
if s.litStatus(lit) == Indet && sumW-clause.Weight(i) < card { // lit can't be falsified
s.propagateUnit(clause, lvl, lit)
foundUnit = true
}
}
}
s.updateWatchPB(clause)
return true
}
func (s *Solver) updateWatchPB(clause *Clause) {
weightWatched := 0
i := 0
card := clause.Cardinality()
for weightWatched <= card && i < clause.Len() {
lit := clause.Get(i)
if s.litStatus(lit) == Unsat {
if clause.pbData.watched[i] {
ni := &s.wl.wlistPb[lit.Negation()]
*ni = removeFrom(*ni, clause)
clause.pbData.watched[i] = false
}
} else {
weightWatched += clause.Weight(i)
if !clause.pbData.watched[i] {
ni := &s.wl.wlistPb[lit.Negation()]
*ni = append(*ni, clause)
clause.pbData.watched[i] = true
}
}
i++
}
// If there are some more watched literals, they are now useless
for i := i; i < clause.Len(); i++ {
if clause.pbData.watched[i] {
ni := &s.wl.wlistPb[clause.Get(i).Negation()]
*ni = removeFrom(*ni, clause)
clause.pbData.watched[i] = false
}
}
}

3
vendor/github.com/hpcloud/tail/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
.test
.go

19
vendor/github.com/hpcloud/tail/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,19 @@
language: go
script:
- go test -race -v ./...
go:
- 1.5
- 1.6
- 1.7
- 1.8
- tip
matrix:
allow_failures:
- go: tip
install:
- go get gopkg.in/fsnotify/fsnotify.v1
- go get gopkg.in/tomb.v1

63
vendor/github.com/hpcloud/tail/CHANGES.md generated vendored Normal file
View File

@@ -0,0 +1,63 @@
# API v1 (gopkg.in/hpcloud/tail.v1)
## April, 2016
* Migrated to godep, as depman is not longer supported
* Introduced golang vendoring feature
* Fixed issue [#57](https://github.com/hpcloud/tail/issues/57) related to reopen deleted file
## July, 2015
* Fix inotify watcher leak; remove `Cleanup` (#51)
# API v0 (gopkg.in/hpcloud/tail.v0)
## June, 2015
* Don't return partial lines (PR #40)
* Use stable version of fsnotify (#46)
## July, 2014
* Fix tail for Windows (PR #36)
## May, 2014
* Improved rate limiting using leaky bucket (PR #29)
* Fix odd line splitting (PR #30)
## Apr, 2014
* LimitRate now discards read buffer (PR #28)
* allow reading of longer lines if MaxLineSize is unset (PR #24)
* updated deps.json to latest fsnotify (441bbc86b1)
## Feb, 2014
* added `Config.Logger` to suppress library logging
## Nov, 2013
* add Cleanup to remove leaky inotify watches (PR #20)
## Aug, 2013
* redesigned Location field (PR #12)
* add tail.Tell (PR #14)
## July, 2013
* Rate limiting (PR #10)
## May, 2013
* Detect file deletions/renames in polling file watcher (PR #1)
* Detect file truncation
* Fix potential race condition when reopening the file (issue 5)
* Fix potential blocking of `tail.Stop` (issue 4)
* Fix uncleaned up ChangeEvents goroutines after calling tail.Stop
* Support Follow=false
## Feb, 2013
* Initial open source release

19
vendor/github.com/hpcloud/tail/Dockerfile generated vendored Normal file
View File

@@ -0,0 +1,19 @@
FROM golang
RUN mkdir -p $GOPATH/src/github.com/hpcloud/tail/
ADD . $GOPATH/src/github.com/hpcloud/tail/
# expecting to fetch dependencies successfully.
RUN go get -v github.com/hpcloud/tail
# expecting to run the test successfully.
RUN go test -v github.com/hpcloud/tail
# expecting to install successfully
RUN go install -v github.com/hpcloud/tail
RUN go install -v github.com/hpcloud/tail/cmd/gotail
RUN $GOPATH/bin/gotail -h || true
ENV PATH $GOPATH/bin:$PATH
CMD ["gotail"]

21
vendor/github.com/hpcloud/tail/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,21 @@
# The MIT License (MIT)
# © Copyright 2015 Hewlett Packard Enterprise Development LP
Copyright (c) 2014 ActiveState
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
vendor/github.com/hpcloud/tail/Makefile generated vendored Normal file
View File

@@ -0,0 +1,11 @@
default: test
test: *.go
go test -v -race ./...
fmt:
gofmt -w .
# Run the test in an isolated environment.
fulltest:
docker build -t hpcloud/tail .

28
vendor/github.com/hpcloud/tail/README.md generated vendored Normal file
View File

@@ -0,0 +1,28 @@
[![Build Status](https://travis-ci.org/hpcloud/tail.svg)](https://travis-ci.org/hpcloud/tail)
[![Build status](https://ci.appveyor.com/api/projects/status/vrl3paf9md0a7bgk/branch/master?svg=true)](https://ci.appveyor.com/project/Nino-K/tail/branch/master)
# Go package for tail-ing files
A Go package striving to emulate the features of the BSD `tail` program.
```Go
t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
for line := range t.Lines {
fmt.Println(line.Text)
}
```
See [API documentation](http://godoc.org/github.com/hpcloud/tail).
## Log rotation
Tail comes with full support for truncation/move detection as it is
designed to work with log rotation tools.
## Installing
go get github.com/hpcloud/tail/...
## Windows support
This package [needs assistance](https://github.com/hpcloud/tail/labels/Windows) for full Windows support.

11
vendor/github.com/hpcloud/tail/appveyor.yml generated vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 0.{build}
skip_tags: true
cache: C:\Users\appveyor\AppData\Local\NuGet\Cache
build_script:
- SET GOPATH=c:\workspace
- go test -v -race ./...
test: off
clone_folder: c:\workspace\src\github.com\hpcloud\tail
branches:
only:
- master

7
vendor/github.com/hpcloud/tail/ratelimiter/Licence generated vendored Normal file
View File

@@ -0,0 +1,7 @@
Copyright (C) 2013 99designs
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,97 @@
// Package ratelimiter implements the Leaky Bucket ratelimiting algorithm with memcached and in-memory backends.
package ratelimiter
import (
"time"
)
type LeakyBucket struct {
Size uint16
Fill float64
LeakInterval time.Duration // time.Duration for 1 unit of size to leak
Lastupdate time.Time
Now func() time.Time
}
func NewLeakyBucket(size uint16, leakInterval time.Duration) *LeakyBucket {
bucket := LeakyBucket{
Size: size,
Fill: 0,
LeakInterval: leakInterval,
Now: time.Now,
Lastupdate: time.Now(),
}
return &bucket
}
func (b *LeakyBucket) updateFill() {
now := b.Now()
if b.Fill > 0 {
elapsed := now.Sub(b.Lastupdate)
b.Fill -= float64(elapsed) / float64(b.LeakInterval)
if b.Fill < 0 {
b.Fill = 0
}
}
b.Lastupdate = now
}
func (b *LeakyBucket) Pour(amount uint16) bool {
b.updateFill()
var newfill float64 = b.Fill + float64(amount)
if newfill > float64(b.Size) {
return false
}
b.Fill = newfill
return true
}
// The time at which this bucket will be completely drained
func (b *LeakyBucket) DrainedAt() time.Time {
return b.Lastupdate.Add(time.Duration(b.Fill * float64(b.LeakInterval)))
}
// The duration until this bucket is completely drained
func (b *LeakyBucket) TimeToDrain() time.Duration {
return b.DrainedAt().Sub(b.Now())
}
func (b *LeakyBucket) TimeSinceLastUpdate() time.Duration {
return b.Now().Sub(b.Lastupdate)
}
type LeakyBucketSer struct {
Size uint16
Fill float64
LeakInterval time.Duration // time.Duration for 1 unit of size to leak
Lastupdate time.Time
}
func (b *LeakyBucket) Serialise() *LeakyBucketSer {
bucket := LeakyBucketSer{
Size: b.Size,
Fill: b.Fill,
LeakInterval: b.LeakInterval,
Lastupdate: b.Lastupdate,
}
return &bucket
}
func (b *LeakyBucketSer) DeSerialise() *LeakyBucket {
bucket := LeakyBucket{
Size: b.Size,
Fill: b.Fill,
LeakInterval: b.LeakInterval,
Lastupdate: b.Lastupdate,
Now: time.Now,
}
return &bucket
}

60
vendor/github.com/hpcloud/tail/ratelimiter/memory.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package ratelimiter
import (
"errors"
"time"
)
const (
GC_SIZE int = 100
GC_PERIOD time.Duration = 60 * time.Second
)
type Memory struct {
store map[string]LeakyBucket
lastGCCollected time.Time
}
func NewMemory() *Memory {
m := new(Memory)
m.store = make(map[string]LeakyBucket)
m.lastGCCollected = time.Now()
return m
}
func (m *Memory) GetBucketFor(key string) (*LeakyBucket, error) {
bucket, ok := m.store[key]
if !ok {
return nil, errors.New("miss")
}
return &bucket, nil
}
func (m *Memory) SetBucketFor(key string, bucket LeakyBucket) error {
if len(m.store) > GC_SIZE {
m.GarbageCollect()
}
m.store[key] = bucket
return nil
}
func (m *Memory) GarbageCollect() {
now := time.Now()
// rate limit GC to once per minute
if now.Unix() >= m.lastGCCollected.Add(GC_PERIOD).Unix() {
for key, bucket := range m.store {
// if the bucket is drained, then GC
if bucket.DrainedAt().Unix() < now.Unix() {
delete(m.store, key)
}
}
m.lastGCCollected = now
}
}

View File

@@ -0,0 +1,6 @@
package ratelimiter
type Storage interface {
GetBucketFor(string) (*LeakyBucket, error)
SetBucketFor(string, LeakyBucket) error
}

437
vendor/github.com/hpcloud/tail/tail.go generated vendored Normal file
View File

@@ -0,0 +1,437 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package tail
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"time"
"github.com/hpcloud/tail/ratelimiter"
"github.com/hpcloud/tail/util"
"github.com/hpcloud/tail/watch"
"gopkg.in/tomb.v1"
)
var (
ErrStop = errors.New("tail should now stop")
)
type Line struct {
Text string
Time time.Time
Err error // Error from tail
}
// NewLine returns a Line with present time.
func NewLine(text string) *Line {
return &Line{text, time.Now(), nil}
}
// SeekInfo represents arguments to `os.Seek`
type SeekInfo struct {
Offset int64
Whence int // os.SEEK_*
}
type logger interface {
Fatal(v ...interface{})
Fatalf(format string, v ...interface{})
Fatalln(v ...interface{})
Panic(v ...interface{})
Panicf(format string, v ...interface{})
Panicln(v ...interface{})
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
}
// Config is used to specify how a file must be tailed.
type Config struct {
// File-specifc
Location *SeekInfo // Seek to this location before tailing
ReOpen bool // Reopen recreated files (tail -F)
MustExist bool // Fail early if the file does not exist
Poll bool // Poll for file changes instead of using inotify
Pipe bool // Is a named pipe (mkfifo)
RateLimiter *ratelimiter.LeakyBucket
// Generic IO
Follow bool // Continue looking for new lines (tail -f)
MaxLineSize int // If non-zero, split longer lines into multiple lines
// Logger, when nil, is set to tail.DefaultLogger
// To disable logging: set field to tail.DiscardingLogger
Logger logger
}
type Tail struct {
Filename string
Lines chan *Line
Config
file *os.File
reader *bufio.Reader
watcher watch.FileWatcher
changes *watch.FileChanges
tomb.Tomb // provides: Done, Kill, Dying
lk sync.Mutex
}
var (
// DefaultLogger is used when Config.Logger == nil
DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
// DiscardingLogger can be used to disable logging output
DiscardingLogger = log.New(ioutil.Discard, "", 0)
)
// TailFile begins tailing the file. Output stream is made available
// via the `Tail.Lines` channel. To handle errors during tailing,
// invoke the `Wait` or `Err` method after finishing reading from the
// `Lines` channel.
func TailFile(filename string, config Config) (*Tail, error) {
if config.ReOpen && !config.Follow {
util.Fatal("cannot set ReOpen without Follow.")
}
t := &Tail{
Filename: filename,
Lines: make(chan *Line),
Config: config,
}
// when Logger was not specified in config, use default logger
if t.Logger == nil {
t.Logger = log.New(os.Stderr, "", log.LstdFlags)
}
if t.Poll {
t.watcher = watch.NewPollingFileWatcher(filename)
} else {
t.watcher = watch.NewInotifyFileWatcher(filename)
}
if t.MustExist {
var err error
t.file, err = OpenFile(t.Filename)
if err != nil {
return nil, err
}
}
go t.tailFileSync()
return t, nil
}
// Return the file's current position, like stdio's ftell().
// But this value is not very accurate.
// it may readed one line in the chan(tail.Lines),
// so it may lost one line.
func (tail *Tail) Tell() (offset int64, err error) {
if tail.file == nil {
return
}
offset, err = tail.file.Seek(0, os.SEEK_CUR)
if err != nil {
return
}
tail.lk.Lock()
defer tail.lk.Unlock()
if tail.reader == nil {
return
}
offset -= int64(tail.reader.Buffered())
return
}
// Stop stops the tailing activity.
func (tail *Tail) Stop() error {
tail.Kill(nil)
return tail.Wait()
}
// StopAtEOF stops tailing as soon as the end of the file is reached.
func (tail *Tail) StopAtEOF() error {
tail.Kill(errStopAtEOF)
return tail.Wait()
}
var errStopAtEOF = errors.New("tail: stop at eof")
func (tail *Tail) close() {
close(tail.Lines)
tail.closeFile()
}
func (tail *Tail) closeFile() {
if tail.file != nil {
tail.file.Close()
tail.file = nil
}
}
func (tail *Tail) reopen() error {
tail.closeFile()
for {
var err error
tail.file, err = OpenFile(tail.Filename)
if err != nil {
if os.IsNotExist(err) {
tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
if err == tomb.ErrDying {
return err
}
return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
}
continue
}
return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
}
break
}
return nil
}
func (tail *Tail) readLine() (string, error) {
tail.lk.Lock()
line, err := tail.reader.ReadString('\n')
tail.lk.Unlock()
if err != nil {
// Note ReadString "returns the data read before the error" in
// case of an error, including EOF, so we return it as is. The
// caller is expected to process it if err is EOF.
return line, err
}
line = strings.TrimRight(line, "\n")
return line, err
}
func (tail *Tail) tailFileSync() {
defer tail.Done()
defer tail.close()
if !tail.MustExist {
// deferred first open.
err := tail.reopen()
if err != nil {
if err != tomb.ErrDying {
tail.Kill(err)
}
return
}
}
// Seek to requested location on first open of the file.
if tail.Location != nil {
_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
if err != nil {
tail.Killf("Seek error on %s: %s", tail.Filename, err)
return
}
}
tail.openReader()
var offset int64
var err error
// Read line by line.
for {
// do not seek in named pipes
if !tail.Pipe {
// grab the position in case we need to back up in the event of a half-line
offset, err = tail.Tell()
if err != nil {
tail.Kill(err)
return
}
}
line, err := tail.readLine()
// Process `line` even if err is EOF.
if err == nil {
cooloff := !tail.sendLine(line)
if cooloff {
// Wait a second before seeking till the end of
// file when rate limit is reached.
msg := ("Too much log activity; waiting a second " +
"before resuming tailing")
tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
select {
case <-time.After(time.Second):
case <-tail.Dying():
return
}
if err := tail.seekEnd(); err != nil {
tail.Kill(err)
return
}
}
} else if err == io.EOF {
if !tail.Follow {
if line != "" {
tail.sendLine(line)
}
return
}
if tail.Follow && line != "" {
// this has the potential to never return the last line if
// it's not followed by a newline; seems a fair trade here
err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
if err != nil {
tail.Kill(err)
return
}
}
// When EOF is reached, wait for more data to become
// available. Wait strategy is based on the `tail.watcher`
// implementation (inotify or polling).
err := tail.waitForChanges()
if err != nil {
if err != ErrStop {
tail.Kill(err)
}
return
}
} else {
// non-EOF error
tail.Killf("Error reading %s: %s", tail.Filename, err)
return
}
select {
case <-tail.Dying():
if tail.Err() == errStopAtEOF {
continue
}
return
default:
}
}
}
// waitForChanges waits until the file has been appended, deleted,
// moved or truncated. When moved or deleted - the file will be
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
if tail.changes == nil {
pos, err := tail.file.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
if err != nil {
return err
}
}
select {
case <-tail.changes.Modified:
return nil
case <-tail.changes.Deleted:
tail.changes = nil
if tail.ReOpen {
// XXX: we must not log from a library.
tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
if err := tail.reopen(); err != nil {
return err
}
tail.Logger.Printf("Successfully reopened %s", tail.Filename)
tail.openReader()
return nil
} else {
tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
return ErrStop
}
case <-tail.changes.Truncated:
// Always reopen truncated files (Follow is true)
tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
if err := tail.reopen(); err != nil {
return err
}
tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
tail.openReader()
return nil
case <-tail.Dying():
return ErrStop
}
panic("unreachable")
}
func (tail *Tail) openReader() {
if tail.MaxLineSize > 0 {
// add 2 to account for newline characters
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
} else {
tail.reader = bufio.NewReader(tail.file)
}
}
func (tail *Tail) seekEnd() error {
return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
}
func (tail *Tail) seekTo(pos SeekInfo) error {
_, err := tail.file.Seek(pos.Offset, pos.Whence)
if err != nil {
return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
}
// Reset the read buffer whenever the file is re-seek'ed
tail.reader.Reset(tail.file)
return nil
}
// sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line string) bool {
now := time.Now()
lines := []string{line}
// Split longer lines
if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
lines = util.PartitionString(line, tail.MaxLineSize)
}
for _, line := range lines {
tail.Lines <- &Line{line, now, nil}
}
if tail.Config.RateLimiter != nil {
ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
if !ok {
tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
tail.Filename)
return false
}
}
return true
}
// Cleanup removes inotify watches added by the tail package. This function is
// meant to be invoked from a process's exit handler. Linux kernel may not
// automatically remove inotify watches after the process exits.
func (tail *Tail) Cleanup() {
watch.Cleanup(tail.Filename)
}

11
vendor/github.com/hpcloud/tail/tail_posix.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// +build linux darwin freebsd netbsd openbsd
package tail
import (
"os"
)
func OpenFile(name string) (file *os.File, err error) {
return os.Open(name)
}

12
vendor/github.com/hpcloud/tail/tail_windows.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build windows
package tail
import (
"github.com/hpcloud/tail/winfile"
"os"
)
func OpenFile(name string) (file *os.File, err error) {
return winfile.OpenFile(name, os.O_RDONLY, 0)
}

48
vendor/github.com/hpcloud/tail/util/util.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package util
import (
"fmt"
"log"
"os"
"runtime/debug"
)
type Logger struct {
*log.Logger
}
var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)}
// fatal is like panic except it displays only the current goroutine's stack.
func Fatal(format string, v ...interface{}) {
// https://github.com/hpcloud/log/blob/master/log.go#L45
LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack()))
os.Exit(1)
}
// partitionString partitions the string into chunks of given size,
// with the last chunk of variable size.
func PartitionString(s string, chunkSize int) []string {
if chunkSize <= 0 {
panic("invalid chunkSize")
}
length := len(s)
chunks := 1 + length/chunkSize
start := 0
end := chunkSize
parts := make([]string, 0, chunks)
for {
if end > length {
end = length
}
parts = append(parts, s[start:end])
if end == length {
break
}
start, end = end, end+chunkSize
}
return parts
}

36
vendor/github.com/hpcloud/tail/watch/filechanges.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package watch
type FileChanges struct {
Modified chan bool // Channel to get notified of modifications
Truncated chan bool // Channel to get notified of truncations
Deleted chan bool // Channel to get notified of deletions/renames
}
func NewFileChanges() *FileChanges {
return &FileChanges{
make(chan bool, 1), make(chan bool, 1), make(chan bool, 1)}
}
func (fc *FileChanges) NotifyModified() {
sendOnlyIfEmpty(fc.Modified)
}
func (fc *FileChanges) NotifyTruncated() {
sendOnlyIfEmpty(fc.Truncated)
}
func (fc *FileChanges) NotifyDeleted() {
sendOnlyIfEmpty(fc.Deleted)
}
// sendOnlyIfEmpty sends on a bool channel only if the channel has no
// backlog to be read by other goroutines. This concurrency pattern
// can be used to notify other goroutines if and only if they are
// looking for it (i.e., subsequent notifications can be compressed
// into one).
func sendOnlyIfEmpty(ch chan bool) {
select {
case ch <- true:
default:
}
}

135
vendor/github.com/hpcloud/tail/watch/inotify.go generated vendored Normal file
View File

@@ -0,0 +1,135 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package watch
import (
"fmt"
"os"
"path/filepath"
"github.com/hpcloud/tail/util"
"gopkg.in/fsnotify/fsnotify.v1"
"gopkg.in/tomb.v1"
)
// InotifyFileWatcher uses inotify to monitor file changes.
type InotifyFileWatcher struct {
Filename string
Size int64
}
func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
return fw
}
func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
err := WatchCreate(fw.Filename)
if err != nil {
return err
}
defer RemoveWatchCreate(fw.Filename)
// Do a real check now as the file might have been created before
// calling `WatchFlags` above.
if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) {
// file exists, or stat returned an error.
return err
}
events := Events(fw.Filename)
for {
select {
case evt, ok := <-events:
if !ok {
return fmt.Errorf("inotify watcher has been closed")
}
evtName, err := filepath.Abs(evt.Name)
if err != nil {
return err
}
fwFilename, err := filepath.Abs(fw.Filename)
if err != nil {
return err
}
if evtName == fwFilename {
return nil
}
case <-t.Dying():
return tomb.ErrDying
}
}
panic("unreachable")
}
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
err := Watch(fw.Filename)
if err != nil {
return nil, err
}
changes := NewFileChanges()
fw.Size = pos
go func() {
events := Events(fw.Filename)
for {
prevSize := fw.Size
var evt fsnotify.Event
var ok bool
select {
case evt, ok = <-events:
if !ok {
RemoveWatch(fw.Filename)
return
}
case <-t.Dying():
RemoveWatch(fw.Filename)
return
}
switch {
case evt.Op&fsnotify.Remove == fsnotify.Remove:
fallthrough
case evt.Op&fsnotify.Rename == fsnotify.Rename:
RemoveWatch(fw.Filename)
changes.NotifyDeleted()
return
//With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod)
case evt.Op&fsnotify.Chmod == fsnotify.Chmod:
fallthrough
case evt.Op&fsnotify.Write == fsnotify.Write:
fi, err := os.Stat(fw.Filename)
if err != nil {
if os.IsNotExist(err) {
RemoveWatch(fw.Filename)
changes.NotifyDeleted()
return
}
// XXX: report this error back to the user
util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
}
fw.Size = fi.Size()
if prevSize > 0 && prevSize > fw.Size {
changes.NotifyTruncated()
} else {
changes.NotifyModified()
}
prevSize = fw.Size
}
}
}()
return changes, nil
}

248
vendor/github.com/hpcloud/tail/watch/inotify_tracker.go generated vendored Normal file
View File

@@ -0,0 +1,248 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package watch
import (
"log"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/hpcloud/tail/util"
"gopkg.in/fsnotify/fsnotify.v1"
)
type InotifyTracker struct {
mux sync.Mutex
watcher *fsnotify.Watcher
chans map[string]chan fsnotify.Event
done map[string]chan bool
watchNums map[string]int
watch chan *watchInfo
remove chan *watchInfo
error chan error
}
type watchInfo struct {
op fsnotify.Op
fname string
}
func (this *watchInfo) isCreate() bool {
return this.op == fsnotify.Create
}
var (
// globally shared InotifyTracker; ensures only one fsnotify.Watcher is used
shared *InotifyTracker
// these are used to ensure the shared InotifyTracker is run exactly once
once = sync.Once{}
goRun = func() {
shared = &InotifyTracker{
mux: sync.Mutex{},
chans: make(map[string]chan fsnotify.Event),
done: make(map[string]chan bool),
watchNums: make(map[string]int),
watch: make(chan *watchInfo),
remove: make(chan *watchInfo),
error: make(chan error),
}
go shared.run()
}
logger = log.New(os.Stderr, "", log.LstdFlags)
)
// Watch signals the run goroutine to begin watching the input filename
func Watch(fname string) error {
return watch(&watchInfo{
fname: fname,
})
}
// Watch create signals the run goroutine to begin watching the input filename
// if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate
func WatchCreate(fname string) error {
return watch(&watchInfo{
op: fsnotify.Create,
fname: fname,
})
}
func watch(winfo *watchInfo) error {
// start running the shared InotifyTracker if not already running
once.Do(goRun)
winfo.fname = filepath.Clean(winfo.fname)
shared.watch <- winfo
return <-shared.error
}
// RemoveWatch signals the run goroutine to remove the watch for the input filename
func RemoveWatch(fname string) error {
return remove(&watchInfo{
fname: fname,
})
}
// RemoveWatch create signals the run goroutine to remove the watch for the input filename
func RemoveWatchCreate(fname string) error {
return remove(&watchInfo{
op: fsnotify.Create,
fname: fname,
})
}
func remove(winfo *watchInfo) error {
// start running the shared InotifyTracker if not already running
once.Do(goRun)
winfo.fname = filepath.Clean(winfo.fname)
shared.mux.Lock()
done := shared.done[winfo.fname]
if done != nil {
delete(shared.done, winfo.fname)
close(done)
}
shared.mux.Unlock()
shared.remove <- winfo
return <-shared.error
}
// Events returns a channel to which FileEvents corresponding to the input filename
// will be sent. This channel will be closed when removeWatch is called on this
// filename.
func Events(fname string) <-chan fsnotify.Event {
shared.mux.Lock()
defer shared.mux.Unlock()
return shared.chans[fname]
}
// Cleanup removes the watch for the input filename if necessary.
func Cleanup(fname string) error {
return RemoveWatch(fname)
}
// watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating
// a new Watcher if the previous Watcher was closed.
func (shared *InotifyTracker) addWatch(winfo *watchInfo) error {
shared.mux.Lock()
defer shared.mux.Unlock()
if shared.chans[winfo.fname] == nil {
shared.chans[winfo.fname] = make(chan fsnotify.Event)
}
if shared.done[winfo.fname] == nil {
shared.done[winfo.fname] = make(chan bool)
}
fname := winfo.fname
if winfo.isCreate() {
// Watch for new files to be created in the parent directory.
fname = filepath.Dir(fname)
}
var err error
// already in inotify watch
if shared.watchNums[fname] == 0 {
err = shared.watcher.Add(fname)
}
if err == nil {
shared.watchNums[fname]++
}
return err
}
// removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
// corresponding events channel.
func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error {
shared.mux.Lock()
ch := shared.chans[winfo.fname]
if ch != nil {
delete(shared.chans, winfo.fname)
close(ch)
}
fname := winfo.fname
if winfo.isCreate() {
// Watch for new files to be created in the parent directory.
fname = filepath.Dir(fname)
}
shared.watchNums[fname]--
watchNum := shared.watchNums[fname]
if watchNum == 0 {
delete(shared.watchNums, fname)
}
shared.mux.Unlock()
var err error
// If we were the last ones to watch this file, unsubscribe from inotify.
// This needs to happen after releasing the lock because fsnotify waits
// synchronously for the kernel to acknowledge the removal of the watch
// for this file, which causes us to deadlock if we still held the lock.
if watchNum == 0 {
err = shared.watcher.Remove(fname)
}
return err
}
// sendEvent sends the input event to the appropriate Tail.
func (shared *InotifyTracker) sendEvent(event fsnotify.Event) {
name := filepath.Clean(event.Name)
shared.mux.Lock()
ch := shared.chans[name]
done := shared.done[name]
shared.mux.Unlock()
if ch != nil && done != nil {
select {
case ch <- event:
case <-done:
}
}
}
// run starts the goroutine in which the shared struct reads events from its
// Watcher's Event channel and sends the events to the appropriate Tail.
func (shared *InotifyTracker) run() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
util.Fatal("failed to create Watcher")
}
shared.watcher = watcher
for {
select {
case winfo := <-shared.watch:
shared.error <- shared.addWatch(winfo)
case winfo := <-shared.remove:
shared.error <- shared.removeWatch(winfo)
case event, open := <-shared.watcher.Events:
if !open {
return
}
shared.sendEvent(event)
case err, open := <-shared.watcher.Errors:
if !open {
return
} else if err != nil {
sysErr, ok := err.(*os.SyscallError)
if !ok || sysErr.Err != syscall.EINTR {
logger.Printf("Error in Watcher Error channel: %s", err)
}
}
}
}
}

118
vendor/github.com/hpcloud/tail/watch/polling.go generated vendored Normal file
View File

@@ -0,0 +1,118 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package watch
import (
"os"
"runtime"
"time"
"github.com/hpcloud/tail/util"
"gopkg.in/tomb.v1"
)
// PollingFileWatcher polls the file for changes.
type PollingFileWatcher struct {
Filename string
Size int64
}
func NewPollingFileWatcher(filename string) *PollingFileWatcher {
fw := &PollingFileWatcher{filename, 0}
return fw
}
var POLL_DURATION time.Duration
func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
for {
if _, err := os.Stat(fw.Filename); err == nil {
return nil
} else if !os.IsNotExist(err) {
return err
}
select {
case <-time.After(POLL_DURATION):
continue
case <-t.Dying():
return tomb.ErrDying
}
}
panic("unreachable")
}
func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
origFi, err := os.Stat(fw.Filename)
if err != nil {
return nil, err
}
changes := NewFileChanges()
var prevModTime time.Time
// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
// the fatal (below) with tomb's Kill.
fw.Size = pos
go func() {
prevSize := fw.Size
for {
select {
case <-t.Dying():
return
default:
}
time.Sleep(POLL_DURATION)
fi, err := os.Stat(fw.Filename)
if err != nil {
// Windows cannot delete a file if a handle is still open (tail keeps one open)
// so it gives access denied to anything trying to read it until all handles are released.
if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) {
// File does not exist (has been deleted).
changes.NotifyDeleted()
return
}
// XXX: report this error back to the user
util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
}
// File got moved/renamed?
if !os.SameFile(origFi, fi) {
changes.NotifyDeleted()
return
}
// File got truncated?
fw.Size = fi.Size()
if prevSize > 0 && prevSize > fw.Size {
changes.NotifyTruncated()
prevSize = fw.Size
continue
}
// File got bigger?
if prevSize > 0 && prevSize < fw.Size {
changes.NotifyModified()
prevSize = fw.Size
continue
}
prevSize = fw.Size
// File was appended to (changed)?
modTime := fi.ModTime()
if modTime != prevModTime {
prevModTime = modTime
changes.NotifyModified()
}
}
}()
return changes, nil
}
func init() {
POLL_DURATION = 250 * time.Millisecond
}

20
vendor/github.com/hpcloud/tail/watch/watch.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
package watch
import "gopkg.in/tomb.v1"
// FileWatcher monitors file-level events.
type FileWatcher interface {
// BlockUntilExists blocks until the file comes into existence.
BlockUntilExists(*tomb.Tomb) error
// ChangeEvents reports on changes to a file, be it modification,
// deletion, renames or truncations. Returned FileChanges group of
// channels will be closed, thus become unusable, after a deletion
// or truncation event.
// In order to properly report truncations, ChangeEvents requires
// the caller to pass their current offset in the file.
ChangeEvents(*tomb.Tomb, int64) (*FileChanges, error)
}

92
vendor/github.com/hpcloud/tail/winfile/winfile.go generated vendored Normal file
View File

@@ -0,0 +1,92 @@
// +build windows
package winfile
import (
"os"
"syscall"
"unsafe"
)
// issue also described here
//https://codereview.appspot.com/8203043/
// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L218
func Open(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
if len(path) == 0 {
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
}
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
}
var access uint32
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
case syscall.O_RDONLY:
access = syscall.GENERIC_READ
case syscall.O_WRONLY:
access = syscall.GENERIC_WRITE
case syscall.O_RDWR:
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
}
if mode&syscall.O_CREAT != 0 {
access |= syscall.GENERIC_WRITE
}
if mode&syscall.O_APPEND != 0 {
access &^= syscall.GENERIC_WRITE
access |= syscall.FILE_APPEND_DATA
}
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
var sa *syscall.SecurityAttributes
if mode&syscall.O_CLOEXEC == 0 {
sa = makeInheritSa()
}
var createmode uint32
switch {
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
createmode = syscall.CREATE_NEW
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
createmode = syscall.CREATE_ALWAYS
case mode&syscall.O_CREAT == syscall.O_CREAT:
createmode = syscall.OPEN_ALWAYS
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
createmode = syscall.TRUNCATE_EXISTING
default:
createmode = syscall.OPEN_EXISTING
}
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
return h, e
}
// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L211
func makeInheritSa() *syscall.SecurityAttributes {
var sa syscall.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
return &sa
}
// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_windows.go#L133
func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
r, e := Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
if e != nil {
return nil, e
}
return os.NewFile(uintptr(r), name), nil
}
// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_posix.go#L61
func syscallMode(i os.FileMode) (o uint32) {
o |= uint32(i.Perm())
if i&os.ModeSetuid != 0 {
o |= syscall.S_ISUID
}
if i&os.ModeSetgid != 0 {
o |= syscall.S_ISGID
}
if i&os.ModeSticky != 0 {
o |= syscall.S_ISVTX
}
// No mapping for Go's ModeTemporary (plan9 only).
return
}

3
vendor/github.com/jinzhu/copier/Guardfile generated vendored Normal file
View File

@@ -0,0 +1,3 @@
guard 'gotest' do
watch(%r{\.go$})
end

20
vendor/github.com/jinzhu/copier/License generated vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Jinzhu
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

100
vendor/github.com/jinzhu/copier/README.md generated vendored Normal file
View File

@@ -0,0 +1,100 @@
# Copier
I am a copier, I copy everything from one to another
[![wercker status](https://app.wercker.com/status/9d44ad2d4e6253929c8fb71359effc0b/s/master "wercker status")](https://app.wercker.com/project/byKey/9d44ad2d4e6253929c8fb71359effc0b)
## Features
* Copy from field to field with same name
* Copy from method to field with same name
* Copy from field to method with same name
* Copy from slice to slice
* Copy from struct to slice
## Usage
```go
package main
import (
"fmt"
"github.com/jinzhu/copier"
)
type User struct {
Name string
Role string
Age int32
}
func (user *User) DoubleAge() int32 {
return 2 * user.Age
}
type Employee struct {
Name string
Age int32
DoubleAge int32
EmployeId int64
SuperRule string
}
func (employee *Employee) Role(role string) {
employee.SuperRule = "Super " + role
}
func main() {
var (
user = User{Name: "Jinzhu", Age: 18, Role: "Admin"}
users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin"}, {Name: "jinzhu 2", Age: 30, Role: "Dev"}}
employee = Employee{}
employees = []Employee{}
)
copier.Copy(&employee, &user)
fmt.Printf("%#v \n", employee)
// Employee{
// Name: "Jinzhu", // Copy from field
// Age: 18, // Copy from field
// DoubleAge: 36, // Copy from method
// EmployeeId: 0, // Ignored
// SuperRule: "Super Admin", // Copy to method
// }
// Copy struct to slice
copier.Copy(&employees, &user)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"}
// }
// Copy slice to slice
employees = []Employee{}
copier.Copy(&employees, &users)
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"},
// {Name: "jinzhu 2", Age: 30, DoubleAge: 60, EmployeId: 0, SuperRule: "Super Dev"},
// }
}
```
## Contributing
You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
# Author
**jinzhu**
* <http://github.com/jinzhu>
* <wosmvp@gmail.com>
* <http://twitter.com/zhangjinzhu>
## License
Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License).

185
vendor/github.com/jinzhu/copier/copier.go generated vendored Normal file
View File

@@ -0,0 +1,185 @@
package copier
import (
"database/sql"
"errors"
"reflect"
)
// Copy copy things
func Copy(toValue interface{}, fromValue interface{}) (err error) {
var (
isSlice bool
amount = 1
from = indirect(reflect.ValueOf(fromValue))
to = indirect(reflect.ValueOf(toValue))
)
if !to.CanAddr() {
return errors.New("copy to value is unaddressable")
}
// Return is from value is invalid
if !from.IsValid() {
return
}
// Just set it if possible to assign
if from.Type().AssignableTo(to.Type()) {
to.Set(from)
return
}
fromType := indirectType(from.Type())
toType := indirectType(to.Type())
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
return
}
if to.Kind() == reflect.Slice {
isSlice = true
if from.Kind() == reflect.Slice {
amount = from.Len()
}
}
for i := 0; i < amount; i++ {
var dest, source reflect.Value
if isSlice {
// source
if from.Kind() == reflect.Slice {
source = indirect(from.Index(i))
} else {
source = indirect(from)
}
// dest
dest = indirect(reflect.New(toType).Elem())
} else {
source = indirect(from)
dest = indirect(to)
}
// Copy from field to field or method
for _, field := range deepFields(fromType) {
name := field.Name
if fromField := source.FieldByName(name); fromField.IsValid() {
// has field
if toField := dest.FieldByName(name); toField.IsValid() {
if toField.CanSet() {
if !set(toField, fromField) {
if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil {
return err
}
}
}
} else {
// try to set to method
var toMethod reflect.Value
if dest.CanAddr() {
toMethod = dest.Addr().MethodByName(name)
} else {
toMethod = dest.MethodByName(name)
}
if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) {
toMethod.Call([]reflect.Value{fromField})
}
}
}
}
// Copy from method to field
for _, field := range deepFields(toType) {
name := field.Name
var fromMethod reflect.Value
if source.CanAddr() {
fromMethod = source.Addr().MethodByName(name)
} else {
fromMethod = source.MethodByName(name)
}
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 {
if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() {
values := fromMethod.Call([]reflect.Value{})
if len(values) >= 1 {
set(toField, values[0])
}
}
}
}
if isSlice {
if dest.Addr().Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest.Addr()))
} else if dest.Type().AssignableTo(to.Type().Elem()) {
to.Set(reflect.Append(to, dest))
}
}
}
return
}
func deepFields(reflectType reflect.Type) []reflect.StructField {
var fields []reflect.StructField
if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
for i := 0; i < reflectType.NumField(); i++ {
v := reflectType.Field(i)
if v.Anonymous {
fields = append(fields, deepFields(v.Type)...)
} else {
fields = append(fields, v)
}
}
}
return fields
}
func indirect(reflectValue reflect.Value) reflect.Value {
for reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
return reflectValue
}
func indirectType(reflectType reflect.Type) reflect.Type {
for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice {
reflectType = reflectType.Elem()
}
return reflectType
}
func set(to, from reflect.Value) bool {
if from.IsValid() {
if to.Kind() == reflect.Ptr {
//set `to` to nil if from is nil
if from.Kind() == reflect.Ptr && from.IsNil() {
to.Set(reflect.Zero(to.Type()))
return true
} else if to.IsNil() {
to.Set(reflect.New(to.Type().Elem()))
}
to = to.Elem()
}
if from.Type().ConvertibleTo(to.Type()) {
to.Set(from.Convert(to.Type()))
} else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok {
err := scanner.Scan(from.Interface())
if err != nil {
return false
}
} else if from.Kind() == reflect.Ptr {
return set(to, from.Elem())
} else {
return false
}
}
return true
}

23
vendor/github.com/jinzhu/copier/wercker.yml generated vendored Normal file
View File

@@ -0,0 +1,23 @@
box: golang
build:
steps:
- setup-go-workspace
# Gets the dependencies
- script:
name: go get
code: |
go get
# Build the project
- script:
name: go build
code: |
go build ./...
# Test the project
- script:
name: go test
code: |
go test ./...

7
vendor/github.com/onsi/ginkgo/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,7 @@
.DS_Store
TODO
tmp/**/*
*.coverprofile
.vscode
.idea/
*.log

15
vendor/github.com/onsi/ginkgo/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
- tip
install:
- go get -v -t ./...
- go get golang.org/x/tools/cmd/cover
- go get github.com/onsi/gomega
- go install github.com/onsi/ginkgo/ginkgo
- export PATH=$PATH:$HOME/gopath/bin
script: $HOME/gopath/bin/ginkgo -r --randomizeAllSpecs --randomizeSuites --race --trace && go vet

218
vendor/github.com/onsi/ginkgo/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,218 @@
## 1.8.0
### New Features
- allow config of the vet flag for `go test` (#562) [3cd45fa]
- Support projects using go modules [d56ee76]
### Fixes and Minor Improvements
- chore(godoc): fixes typos in Measurement funcs [dbaca8e]
- Optimize focus to avoid allocations [f493786]
- Ensure generated test file names are underscored [505cc35]
## 1.7.0
### New Features
- Add JustAfterEach (#484) [0d4f080]
### Fixes
- Correctly round suite time in junit reporter [2445fc1]
- Avoid using -i argument to go test for Golang 1.10+ [46bbc26]
## 1.6.0
### New Features
- add --debug flag to emit node output to files (#499) [39febac]
### Fixes
- fix: for `go vet` to pass [69338ec]
- docs: fix for contributing instructions [7004cb1]
- consolidate and streamline contribution docs (#494) [d848015]
- Make generated Junit file compatable with "Maven Surefire" (#488) [e51bee6]
- all: gofmt [000d317]
- Increase eventually timeout to 30s [c73579c]
- Clarify asynchronous test behaviour [294d8f4]
- Travis badge should only show master [26d2143]
## 1.5.0 5/10/2018
### New Features
- Supports go v1.10 (#443, #446, #451) [e873237, 468e89e, e37dbfe, a37f4c0, c0b857d, bca5260, 4177ca8]
- Add a When() synonym for Context() (#386) [747514b, 7484dad, 7354a07, dd826c8]
- Re-add noisySkippings flag [652e15c]
- Allow coverage to be displayed for focused specs (#367) [11459a8]
- Handle -outputdir flag (#364) [228e3a8]
- Handle -coverprofile flag (#355) [43392d5]
### Fixes
- When using custom reporters register the custom reporters *before* the default reporter. This allows users to see the output of any print statements in their customer reporters. (#365) [8382b23]
- When running a test and calculating the coverage using the `-coverprofile` and `-outputdir` flags, Ginkgo fails with an error if the directory does not exist. This is due to an [issue in go 1.10](https://github.com/golang/go/issues/24588) (#446) [b36a6e0]
- `unfocus` command ignores vendor folder (#459) [e5e551c, c556e43, a3b6351, 9a820dd]
- Ignore packages whose tests are all ignored by go (#456) [7430ca7, 6d8be98]
- Increase the threshold when checking time measuments (#455) [2f714bf, 68f622c]
- Fix race condition in coverage tests (#423) [a5a8ff7, ab9c08b]
- Add an extra new line after reporting spec run completion for test2json [874520d]
- added name name field to junit reported testsuite [ae61c63]
- Do not set the run time of a spec when the dryRun flag is used (#438) [457e2d9, ba8e856]
- Process FWhen and FSpecify when unfocusing (#434) [9008c7b, ee65bd, df87dfe]
- Synchronise the access to the state of specs to avoid race conditions (#430) [7d481bc, ae6829d]
- Added Duration on GinkgoTestDescription (#383) [5f49dad, 528417e, 0747408, 329d7ed]
- Fix Ginkgo stack trace on failure for Specify (#415) [b977ede, 65ca40e, 6c46eb8]
- Update README with Go 1.6+, Golang -> Go (#409) [17f6b97, bc14b66, 20d1598]
- Use fmt.Errorf instead of errors.New(fmt.Sprintf (#401) [a299f56, 44e2eaa]
- Imports in generated code should follow conventions (#398) [0bec0b0, e8536d8]
- Prevent data race error when Recording a benchmark value from multiple go routines (#390) [c0c4881, 7a241e9]
- Replace GOPATH in Environment [4b883f0]
## 1.4.0 7/16/2017
- `ginkgo` now provides a hint if you accidentally forget to run `ginkgo bootstrap` to generate a `*_suite_test.go` file that actually invokes the Ginkgo test runner. [#345](https://github.com/onsi/ginkgo/pull/345)
- thanks to improvements in `go test -c` `ginkgo` no longer needs to fix Go's compilation output to ensure compilation errors are expressed relative to the CWD. [#357]
- `ginkgo watch -watchRegExp=...` allows you to specify a custom regular expression to watch. Only files matching the regular expression are watched for changes (the default is `\.go$`) [#356]
- `ginkgo` now always emits compilation output. Previously, only failed compilation output was printed out. [#277]
- `ginkgo -requireSuite` now fails the test run if there are `*_test.go` files but `go test` fails to detect any tests. Typically this means you forgot to run `ginkgo bootstrap` to generate a suite file. [#344]
- `ginkgo -timeout=DURATION` allows you to adjust the timeout for the entire test suite (default is 24 hours) [#248]
## 1.3.0 3/28/2017
Improvements:
- Significantly improved parallel test distribution. Now instead of pre-sharding test cases across workers (which can result in idle workers and poor test performance) Ginkgo uses a shared queue to keep all workers busy until all tests are complete. This improves test-time performance and consistency.
- `Skip(message)` can be used to skip the current test.
- Added `extensions/table` - a Ginkgo DSL for [Table Driven Tests](http://onsi.github.io/ginkgo/#table-driven-tests)
- Add `GinkgoRandomSeed()` - shorthand for `config.GinkgoConfig.RandomSeed`
- Support for retrying flaky tests with `--flakeAttempts`
- `ginkgo ./...` now recurses as you'd expect
- Added `Specify` a synonym for `It`
- Support colorise on Windows
- Broader support for various go compilation flags in the `ginkgo` CLI
Bug Fixes:
- Ginkgo tests now fail when you `panic(nil)` (#167)
## 1.2.0 5/31/2015
Improvements
- `ginkgo -coverpkg` calls down to `go test -coverpkg` (#160)
- `ginkgo -afterSuiteHook COMMAND` invokes the passed-in `COMMAND` after a test suite completes (#152)
- Relaxed requirement for Go 1.4+. `ginkgo` now works with Go v1.3+ (#166)
## 1.2.0-beta
Ginkgo now requires Go 1.4+
Improvements:
- Call reporters in reverse order when announcing spec completion -- allows custom reporters to emit output before the default reporter does.
- Improved focus behavior. Now, this:
```golang
FDescribe("Some describe", func() {
It("A", func() {})
FIt("B", func() {})
})
```
will run `B` but *not* `A`. This tends to be a common usage pattern when in the thick of writing and debugging tests.
- When `SIGINT` is received, Ginkgo will emit the contents of the `GinkgoWriter` before running the `AfterSuite`. Useful for debugging stuck tests.
- When `--progress` is set, Ginkgo will write test progress (in particular, Ginkgo will say when it is about to run a BeforeEach, AfterEach, It, etc...) to the `GinkgoWriter`. This is useful for debugging stuck tests and tests that generate many logs.
- Improved output when an error occurs in a setup or teardown block.
- When `--dryRun` is set, Ginkgo will walk the spec tree and emit to its reporter *without* actually running anything. Best paired with `-v` to understand which specs will run in which order.
- Add `By` to help document long `It`s. `By` simply writes to the `GinkgoWriter`.
- Add support for precompiled tests:
- `ginkgo build <path-to-package>` will now compile the package, producing a file named `package.test`
- The compiled `package.test` file can be run directly. This runs the tests in series.
- To run precompiled tests in parallel, you can run: `ginkgo -p package.test`
- Support `bootstrap`ping and `generate`ing [Agouti](http://agouti.org) specs.
- `ginkgo generate` and `ginkgo bootstrap` now honor the package name already defined in a given directory
- The `ginkgo` CLI ignores `SIGQUIT`. Prevents its stack dump from interlacing with the underlying test suite's stack dump.
- The `ginkgo` CLI now compiles tests into a temporary directory instead of the package directory. This necessitates upgrading to Go v1.4+.
- `ginkgo -notify` now works on Linux
Bug Fixes:
- If --skipPackages is used and all packages are skipped, Ginkgo should exit 0.
- Fix tempfile leak when running in parallel
- Fix incorrect failure message when a panic occurs during a parallel test run
- Fixed an issue where a pending test within a focused context (or a focused test within a pending context) would skip all other tests.
- Be more consistent about handling SIGTERM as well as SIGINT
- When interupted while concurrently compiling test suites in the background, Ginkgo now cleans up the compiled artifacts.
- Fixed a long standing bug where `ginkgo -p` would hang if a process spawned by one of the Ginkgo parallel nodes does not exit. (Hooray!)
## 1.1.0 (8/2/2014)
No changes, just dropping the beta.
## 1.1.0-beta (7/22/2014)
New Features:
- `ginkgo watch` now monitors packages *and their dependencies* for changes. The depth of the dependency tree can be modified with the `-depth` flag.
- Test suites with a programmatic focus (`FIt`, `FDescribe`, etc...) exit with non-zero status code, even when they pass. This allows CI systems to detect accidental commits of focused test suites.
- `ginkgo -p` runs the testsuite in parallel with an auto-detected number of nodes.
- `ginkgo -tags=TAG_LIST` passes a list of tags down to the `go build` command.
- `ginkgo --failFast` aborts the test suite after the first failure.
- `ginkgo generate file_1 file_2` can take multiple file arguments.
- Ginkgo now summarizes any spec failures that occured at the end of the test run.
- `ginkgo --randomizeSuites` will run tests *suites* in random order using the generated/passed-in seed.
Improvements:
- `ginkgo -skipPackage` now takes a comma-separated list of strings. If the *relative path* to a package matches one of the entries in the comma-separated list, that package is skipped.
- `ginkgo --untilItFails` no longer recompiles between attempts.
- Ginkgo now panics when a runnable node (`It`, `BeforeEach`, `JustBeforeEach`, `AfterEach`, `Measure`) is nested within another runnable node. This is always a mistake. Any test suites that panic because of this change should be fixed.
Bug Fixes:
- `ginkgo boostrap` and `ginkgo generate` no longer fail when dealing with `hyphen-separated-packages`.
- parallel specs are now better distributed across nodes - fixed a crashing bug where (for example) distributing 11 tests across 7 nodes would panic
## 1.0.0 (5/24/2014)
New Features:
- Add `GinkgoParallelNode()` - shorthand for `config.GinkgoConfig.ParallelNode`
Improvements:
- When compilation fails, the compilation output is rewritten to present a correct *relative* path. Allows ⌘-clicking in iTerm open the file in your text editor.
- `--untilItFails` and `ginkgo watch` now generate new random seeds between test runs, unless a particular random seed is specified.
Bug Fixes:
- `-cover` now generates a correctly combined coverprofile when running with in parallel with multiple `-node`s.
- Print out the contents of the `GinkgoWriter` when `BeforeSuite` or `AfterSuite` fail.
- Fix all remaining race conditions in Ginkgo's test suite.
## 1.0.0-beta (4/14/2014)
Breaking changes:
- `thirdparty/gomocktestreporter` is gone. Use `GinkgoT()` instead
- Modified the Reporter interface
- `watch` is now a subcommand, not a flag.
DSL changes:
- `BeforeSuite` and `AfterSuite` for setting up and tearing down test suites.
- `AfterSuite` is triggered on interrupt (`^C`) as well as exit.
- `SynchronizedBeforeSuite` and `SynchronizedAfterSuite` for setting up and tearing down singleton resources across parallel nodes.
CLI changes:
- `watch` is now a subcommand, not a flag
- `--nodot` flag can be passed to `ginkgo generate` and `ginkgo bootstrap` to avoid dot imports. This explicitly imports all exported identifiers in Ginkgo and Gomega. Refreshing this list can be done by running `ginkgo nodot`
- Additional arguments can be passed to specs. Pass them after the `--` separator
- `--skipPackage` flag takes a regexp and ignores any packages with package names passing said regexp.
- `--trace` flag prints out full stack traces when errors occur, not just the line at which the error occurs.
Misc:
- Start using semantic versioning
- Start maintaining changelog
Major refactor:
- Pull out Ginkgo's internal to `internal`
- Rename `example` everywhere to `spec`
- Much more!

33
vendor/github.com/onsi/ginkgo/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,33 @@
# Contributing to Ginkgo
Your contributions to Ginkgo are essential for its long-term maintenance and improvement.
- Please **open an issue first** - describe what problem you are trying to solve and give the community a forum for input and feedback ahead of investing time in writing code!
- Ensure adequate test coverage:
- When adding to the Ginkgo library, add unit and/or integration tests (under the `integration` folder).
- When adding to the Ginkgo CLI, note that there are very few unit tests. Please add an integration test.
- Update the documentation. Ginko uses `godoc` comments and documentation on the `gh-pages` branch.
If relevant, please submit a docs PR to that branch alongside your code PR.
Thanks for supporting Ginkgo!
## Setup
Fork the repo, then:
```
go get github.com/onsi/ginkgo
go get github.com/onsi/gomega/...
cd $GOPATH/src/github.com/onsi/ginkgo
git remote add fork git@github.com:<NAME>/ginkgo.git
ginkgo -r -p # ensure tests are green
go vet ./... # ensure linter is happy
```
## Making the PR
- go to a new branch `git checkout -b my-feature`
- make your changes
- run tests and linter again (see above)
- `git push fork`
- open PR 🎉

20
vendor/github.com/onsi/ginkgo/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2013-2014 Onsi Fakhouri
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

121
vendor/github.com/onsi/ginkgo/README.md generated vendored Normal file
View File

@@ -0,0 +1,121 @@
![Ginkgo: A Go BDD Testing Framework](http://onsi.github.io/ginkgo/images/ginkgo.png)
[![Build Status](https://travis-ci.org/onsi/ginkgo.svg?branch=master)](https://travis-ci.org/onsi/ginkgo)
Jump to the [docs](http://onsi.github.io/ginkgo/) to learn more. To start rolling your Ginkgo tests *now* [keep reading](#set-me-up)!
If you have a question, comment, bug report, feature request, etc. please open a GitHub issue.
## Feature List
- Ginkgo uses Go's `testing` package and can live alongside your existing `testing` tests. It's easy to [bootstrap](http://onsi.github.io/ginkgo/#bootstrapping-a-suite) and start writing your [first tests](http://onsi.github.io/ginkgo/#adding-specs-to-a-suite)
- Structure your BDD-style tests expressively:
- Nestable [`Describe`, `Context` and `When` container blocks](http://onsi.github.io/ginkgo/#organizing-specs-with-containers-describe-and-context)
- [`BeforeEach` and `AfterEach` blocks](http://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach) for setup and teardown
- [`It` and `Specify` blocks](http://onsi.github.io/ginkgo/#individual-specs-) that hold your assertions
- [`JustBeforeEach` blocks](http://onsi.github.io/ginkgo/#separating-creation-and-configuration-justbeforeeach) that separate creation from configuration (also known as the subject action pattern).
- [`BeforeSuite` and `AfterSuite` blocks](http://onsi.github.io/ginkgo/#global-setup-and-teardown-beforesuite-and-aftersuite) to prep for and cleanup after a suite.
- A comprehensive test runner that lets you:
- Mark specs as [pending](http://onsi.github.io/ginkgo/#pending-specs)
- [Focus](http://onsi.github.io/ginkgo/#focused-specs) individual specs, and groups of specs, either programmatically or on the command line
- Run your tests in [random order](http://onsi.github.io/ginkgo/#spec-permutation), and then reuse random seeds to replicate the same order.
- Break up your test suite into parallel processes for straightforward [test parallelization](http://onsi.github.io/ginkgo/#parallel-specs)
- `ginkgo`: a command line interface with plenty of handy command line arguments for [running your tests](http://onsi.github.io/ginkgo/#running-tests) and [generating](http://onsi.github.io/ginkgo/#generators) test files. Here are a few choice examples:
- `ginkgo -nodes=N` runs your tests in `N` parallel processes and print out coherent output in realtime
- `ginkgo -cover` runs your tests using Go's code coverage tool
- `ginkgo convert` converts an XUnit-style `testing` package to a Ginkgo-style package
- `ginkgo -focus="REGEXP"` and `ginkgo -skip="REGEXP"` allow you to specify a subset of tests to run via regular expression
- `ginkgo -r` runs all tests suites under the current directory
- `ginkgo -v` prints out identifying information for each tests just before it runs
And much more: run `ginkgo help` for details!
The `ginkgo` CLI is convenient, but purely optional -- Ginkgo works just fine with `go test`
- `ginkgo watch` [watches](https://onsi.github.io/ginkgo/#watching-for-changes) packages *and their dependencies* for changes, then reruns tests. Run tests immediately as you develop!
- Built-in support for testing [asynchronicity](http://onsi.github.io/ginkgo/#asynchronous-tests)
- Built-in support for [benchmarking](http://onsi.github.io/ginkgo/#benchmark-tests) your code. Control the number of benchmark samples as you gather runtimes and other, arbitrary, bits of numerical information about your code.
- [Completions for Sublime Text](https://github.com/onsi/ginkgo-sublime-completions): just use [Package Control](https://sublime.wbond.net/) to install `Ginkgo Completions`.
- [Completions for VSCode](https://github.com/onsi/vscode-ginkgo): just use VSCode's extension installer to install `vscode-ginkgo`.
- Straightforward support for third-party testing libraries such as [Gomock](https://code.google.com/p/gomock/) and [Testify](https://github.com/stretchr/testify). Check out the [docs](http://onsi.github.io/ginkgo/#third-party-integrations) for details.
- A modular architecture that lets you easily:
- Write [custom reporters](http://onsi.github.io/ginkgo/#writing-custom-reporters) (for example, Ginkgo comes with a [JUnit XML reporter](http://onsi.github.io/ginkgo/#generating-junit-xml-output) and a TeamCity reporter).
- [Adapt an existing matcher library (or write your own!)](http://onsi.github.io/ginkgo/#using-other-matcher-libraries) to work with Ginkgo
## [Gomega](http://github.com/onsi/gomega): Ginkgo's Preferred Matcher Library
Ginkgo is best paired with Gomega. Learn more about Gomega [here](http://onsi.github.io/gomega/)
## [Agouti](http://github.com/sclevine/agouti): A Go Acceptance Testing Framework
Agouti allows you run WebDriver integration tests. Learn more about Agouti [here](http://agouti.org)
## Set Me Up!
You'll need the Go command-line tools. Ginkgo is tested with Go 1.6+, but preferably you should get the latest. Follow the [installation instructions](https://golang.org/doc/install) if you don't have it installed.
```bash
go get -u github.com/onsi/ginkgo/ginkgo # installs the ginkgo CLI
go get -u github.com/onsi/gomega/... # fetches the matcher library
cd path/to/package/you/want/to/test
ginkgo bootstrap # set up a new ginkgo suite
ginkgo generate # will create a sample test file. edit this file and add your tests then...
go test # to run your tests
ginkgo # also runs your tests
```
## I'm new to Go: What are my testing options?
Of course, I heartily recommend [Ginkgo](https://github.com/onsi/ginkgo) and [Gomega](https://github.com/onsi/gomega). Both packages are seeing heavy, daily, production use on a number of projects and boast a mature and comprehensive feature-set.
With that said, it's great to know what your options are :)
### What Go gives you out of the box
Testing is a first class citizen in Go, however Go's built-in testing primitives are somewhat limited: The [testing](http://golang.org/pkg/testing) package provides basic XUnit style tests and no assertion library.
### Matcher libraries for Go's XUnit style tests
A number of matcher libraries have been written to augment Go's built-in XUnit style tests. Here are two that have gained traction:
- [testify](https://github.com/stretchr/testify)
- [gocheck](http://labix.org/gocheck)
You can also use Ginkgo's matcher library [Gomega](https://github.com/onsi/gomega) in [XUnit style tests](http://onsi.github.io/gomega/#using-gomega-with-golangs-xunitstyle-tests)
### BDD style testing frameworks
There are a handful of BDD-style testing frameworks written for Go. Here are a few:
- [Ginkgo](https://github.com/onsi/ginkgo) ;)
- [GoConvey](https://github.com/smartystreets/goconvey)
- [Goblin](https://github.com/franela/goblin)
- [Mao](https://github.com/azer/mao)
- [Zen](https://github.com/pranavraja/zen)
Finally, @shageman has [put together](https://github.com/shageman/gotestit) a comprehensive comparison of Go testing libraries.
Go explore!
## License
Ginkgo is MIT-Licensed
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)

14
vendor/github.com/onsi/ginkgo/RELEASING.md generated vendored Normal file
View File

@@ -0,0 +1,14 @@
A Ginkgo release is a tagged git sha and a GitHub release. To cut a release:
1. Ensure CHANGELOG.md is up to date.
- Use `git log --pretty=format:'- %s [%h]' HEAD...vX.X.X` to list all the commits since the last release
- Categorize the changes into
- Breaking Changes (requires a major version)
- New Features (minor version)
- Fixes (fix version)
- Maintenance (which in general should not be mentioned in `CHANGELOG.md` as they have no user impact)
1. Update `VERSION` in `config/config.go`
1. Create a commit with the version number as the commit message (e.g. `v1.3.0`)
1. Tag the commit with the version number as the tag name (e.g. `v1.3.0`)
1. Push the commit and tag to GitHub
1. Create a new [GitHub release](https://help.github.com/articles/creating-releases/) with the version number as the tag (e.g. `v1.3.0`). List the key changes in the release notes.

200
vendor/github.com/onsi/ginkgo/config/config.go generated vendored Normal file
View File

@@ -0,0 +1,200 @@
/*
Ginkgo accepts a number of configuration options.
These are documented [here](http://onsi.github.io/ginkgo/#the_ginkgo_cli)
You can also learn more via
ginkgo help
or (I kid you not):
go test -asdf
*/
package config
import (
"flag"
"time"
"fmt"
)
const VERSION = "1.8.0"
type GinkgoConfigType struct {
RandomSeed int64
RandomizeAllSpecs bool
RegexScansFilePath bool
FocusString string
SkipString string
SkipMeasurements bool
FailOnPending bool
FailFast bool
FlakeAttempts int
EmitSpecProgress bool
DryRun bool
DebugParallel bool
ParallelNode int
ParallelTotal int
SyncHost string
StreamHost string
}
var GinkgoConfig = GinkgoConfigType{}
type DefaultReporterConfigType struct {
NoColor bool
SlowSpecThreshold float64
NoisyPendings bool
NoisySkippings bool
Succinct bool
Verbose bool
FullTrace bool
}
var DefaultReporterConfig = DefaultReporterConfigType{}
func processPrefix(prefix string) string {
if prefix != "" {
prefix = prefix + "."
}
return prefix
}
func Flags(flagSet *flag.FlagSet, prefix string, includeParallelFlags bool) {
prefix = processPrefix(prefix)
flagSet.Int64Var(&(GinkgoConfig.RandomSeed), prefix+"seed", time.Now().Unix(), "The seed used to randomize the spec suite.")
flagSet.BoolVar(&(GinkgoConfig.RandomizeAllSpecs), prefix+"randomizeAllSpecs", false, "If set, ginkgo will randomize all specs together. By default, ginkgo only randomizes the top level Describe, Context and When groups.")
flagSet.BoolVar(&(GinkgoConfig.SkipMeasurements), prefix+"skipMeasurements", false, "If set, ginkgo will skip any measurement specs.")
flagSet.BoolVar(&(GinkgoConfig.FailOnPending), prefix+"failOnPending", false, "If set, ginkgo will mark the test suite as failed if any specs are pending.")
flagSet.BoolVar(&(GinkgoConfig.FailFast), prefix+"failFast", false, "If set, ginkgo will stop running a test suite after a failure occurs.")
flagSet.BoolVar(&(GinkgoConfig.DryRun), prefix+"dryRun", false, "If set, ginkgo will walk the test hierarchy without actually running anything. Best paired with -v.")
flagSet.StringVar(&(GinkgoConfig.FocusString), prefix+"focus", "", "If set, ginkgo will only run specs that match this regular expression.")
flagSet.StringVar(&(GinkgoConfig.SkipString), prefix+"skip", "", "If set, ginkgo will only run specs that do not match this regular expression.")
flagSet.BoolVar(&(GinkgoConfig.RegexScansFilePath), prefix+"regexScansFilePath", false, "If set, ginkgo regex matching also will look at the file path (code location).")
flagSet.IntVar(&(GinkgoConfig.FlakeAttempts), prefix+"flakeAttempts", 1, "Make up to this many attempts to run each spec. Please note that if any of the attempts succeed, the suite will not be failed. But any failures will still be recorded.")
flagSet.BoolVar(&(GinkgoConfig.EmitSpecProgress), prefix+"progress", false, "If set, ginkgo will emit progress information as each spec runs to the GinkgoWriter.")
flagSet.BoolVar(&(GinkgoConfig.DebugParallel), prefix+"debug", false, "If set, ginkgo will emit node output to files when running in parallel.")
if includeParallelFlags {
flagSet.IntVar(&(GinkgoConfig.ParallelNode), prefix+"parallel.node", 1, "This worker node's (one-indexed) node number. For running specs in parallel.")
flagSet.IntVar(&(GinkgoConfig.ParallelTotal), prefix+"parallel.total", 1, "The total number of worker nodes. For running specs in parallel.")
flagSet.StringVar(&(GinkgoConfig.SyncHost), prefix+"parallel.synchost", "", "The address for the server that will synchronize the running nodes.")
flagSet.StringVar(&(GinkgoConfig.StreamHost), prefix+"parallel.streamhost", "", "The address for the server that the running nodes should stream data to.")
}
flagSet.BoolVar(&(DefaultReporterConfig.NoColor), prefix+"noColor", false, "If set, suppress color output in default reporter.")
flagSet.Float64Var(&(DefaultReporterConfig.SlowSpecThreshold), prefix+"slowSpecThreshold", 5.0, "(in seconds) Specs that take longer to run than this threshold are flagged as slow by the default reporter.")
flagSet.BoolVar(&(DefaultReporterConfig.NoisyPendings), prefix+"noisyPendings", true, "If set, default reporter will shout about pending tests.")
flagSet.BoolVar(&(DefaultReporterConfig.NoisySkippings), prefix+"noisySkippings", true, "If set, default reporter will shout about skipping tests.")
flagSet.BoolVar(&(DefaultReporterConfig.Verbose), prefix+"v", false, "If set, default reporter print out all specs as they begin.")
flagSet.BoolVar(&(DefaultReporterConfig.Succinct), prefix+"succinct", false, "If set, default reporter prints out a very succinct report")
flagSet.BoolVar(&(DefaultReporterConfig.FullTrace), prefix+"trace", false, "If set, default reporter prints out the full stack trace when a failure occurs")
}
func BuildFlagArgs(prefix string, ginkgo GinkgoConfigType, reporter DefaultReporterConfigType) []string {
prefix = processPrefix(prefix)
result := make([]string, 0)
if ginkgo.RandomSeed > 0 {
result = append(result, fmt.Sprintf("--%sseed=%d", prefix, ginkgo.RandomSeed))
}
if ginkgo.RandomizeAllSpecs {
result = append(result, fmt.Sprintf("--%srandomizeAllSpecs", prefix))
}
if ginkgo.SkipMeasurements {
result = append(result, fmt.Sprintf("--%sskipMeasurements", prefix))
}
if ginkgo.FailOnPending {
result = append(result, fmt.Sprintf("--%sfailOnPending", prefix))
}
if ginkgo.FailFast {
result = append(result, fmt.Sprintf("--%sfailFast", prefix))
}
if ginkgo.DryRun {
result = append(result, fmt.Sprintf("--%sdryRun", prefix))
}
if ginkgo.FocusString != "" {
result = append(result, fmt.Sprintf("--%sfocus=%s", prefix, ginkgo.FocusString))
}
if ginkgo.SkipString != "" {
result = append(result, fmt.Sprintf("--%sskip=%s", prefix, ginkgo.SkipString))
}
if ginkgo.FlakeAttempts > 1 {
result = append(result, fmt.Sprintf("--%sflakeAttempts=%d", prefix, ginkgo.FlakeAttempts))
}
if ginkgo.EmitSpecProgress {
result = append(result, fmt.Sprintf("--%sprogress", prefix))
}
if ginkgo.DebugParallel {
result = append(result, fmt.Sprintf("--%sdebug", prefix))
}
if ginkgo.ParallelNode != 0 {
result = append(result, fmt.Sprintf("--%sparallel.node=%d", prefix, ginkgo.ParallelNode))
}
if ginkgo.ParallelTotal != 0 {
result = append(result, fmt.Sprintf("--%sparallel.total=%d", prefix, ginkgo.ParallelTotal))
}
if ginkgo.StreamHost != "" {
result = append(result, fmt.Sprintf("--%sparallel.streamhost=%s", prefix, ginkgo.StreamHost))
}
if ginkgo.SyncHost != "" {
result = append(result, fmt.Sprintf("--%sparallel.synchost=%s", prefix, ginkgo.SyncHost))
}
if ginkgo.RegexScansFilePath {
result = append(result, fmt.Sprintf("--%sregexScansFilePath", prefix))
}
if reporter.NoColor {
result = append(result, fmt.Sprintf("--%snoColor", prefix))
}
if reporter.SlowSpecThreshold > 0 {
result = append(result, fmt.Sprintf("--%sslowSpecThreshold=%.5f", prefix, reporter.SlowSpecThreshold))
}
if !reporter.NoisyPendings {
result = append(result, fmt.Sprintf("--%snoisyPendings=false", prefix))
}
if !reporter.NoisySkippings {
result = append(result, fmt.Sprintf("--%snoisySkippings=false", prefix))
}
if reporter.Verbose {
result = append(result, fmt.Sprintf("--%sv", prefix))
}
if reporter.Succinct {
result = append(result, fmt.Sprintf("--%ssuccinct", prefix))
}
if reporter.FullTrace {
result = append(result, fmt.Sprintf("--%strace", prefix))
}
return result
}

619
vendor/github.com/onsi/ginkgo/ginkgo_dsl.go generated vendored Normal file
View File

@@ -0,0 +1,619 @@
/*
Ginkgo is a BDD-style testing framework for Golang
The godoc documentation describes Ginkgo's API. More comprehensive documentation (with examples!) is available at http://onsi.github.io/ginkgo/
Ginkgo's preferred matcher library is [Gomega](http://github.com/onsi/gomega)
Ginkgo on Github: http://github.com/onsi/ginkgo
Ginkgo is MIT-Licensed
*/
package ginkgo
import (
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/remote"
"github.com/onsi/ginkgo/internal/suite"
"github.com/onsi/ginkgo/internal/testingtproxy"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/reporters/stenographer"
colorable "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable"
"github.com/onsi/ginkgo/types"
)
const GINKGO_VERSION = config.VERSION
const GINKGO_PANIC = `
Your test failed.
Ginkgo panics to prevent subsequent assertions from running.
Normally Ginkgo rescues this panic so you shouldn't see it.
But, if you make an assertion in a goroutine, Ginkgo can't capture the panic.
To circumvent this, you should call
defer GinkgoRecover()
at the top of the goroutine that caused this panic.
`
const defaultTimeout = 1
var globalSuite *suite.Suite
var globalFailer *failer.Failer
func init() {
config.Flags(flag.CommandLine, "ginkgo", true)
GinkgoWriter = writer.New(os.Stdout)
globalFailer = failer.New()
globalSuite = suite.New(globalFailer)
}
//GinkgoWriter implements an io.Writer
//When running in verbose mode any writes to GinkgoWriter will be immediately printed
//to stdout. Otherwise, GinkgoWriter will buffer any writes produced during the current test and flush them to screen
//only if the current test fails.
var GinkgoWriter io.Writer
//The interface by which Ginkgo receives *testing.T
type GinkgoTestingT interface {
Fail()
}
//GinkgoRandomSeed returns the seed used to randomize spec execution order. It is
//useful for seeding your own pseudorandom number generators (PRNGs) to ensure
//consistent executions from run to run, where your tests contain variability (for
//example, when selecting random test data).
func GinkgoRandomSeed() int64 {
return config.GinkgoConfig.RandomSeed
}
//GinkgoParallelNode returns the parallel node number for the current ginkgo process
//The node number is 1-indexed
func GinkgoParallelNode() int {
return config.GinkgoConfig.ParallelNode
}
//Some matcher libraries or legacy codebases require a *testing.T
//GinkgoT implements an interface analogous to *testing.T and can be used if
//the library in question accepts *testing.T through an interface
//
// For example, with testify:
// assert.Equal(GinkgoT(), 123, 123, "they should be equal")
//
// Or with gomock:
// gomock.NewController(GinkgoT())
//
// GinkgoT() takes an optional offset argument that can be used to get the
// correct line number associated with the failure.
func GinkgoT(optionalOffset ...int) GinkgoTInterface {
offset := 3
if len(optionalOffset) > 0 {
offset = optionalOffset[0]
}
return testingtproxy.New(GinkgoWriter, Fail, offset)
}
//The interface returned by GinkgoT(). This covers most of the methods
//in the testing package's T.
type GinkgoTInterface interface {
Fail()
Error(args ...interface{})
Errorf(format string, args ...interface{})
FailNow()
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Log(args ...interface{})
Logf(format string, args ...interface{})
Failed() bool
Parallel()
Skip(args ...interface{})
Skipf(format string, args ...interface{})
SkipNow()
Skipped() bool
}
//Custom Ginkgo test reporters must implement the Reporter interface.
//
//The custom reporter is passed in a SuiteSummary when the suite begins and ends,
//and a SpecSummary just before a spec begins and just after a spec ends
type Reporter reporters.Reporter
//Asynchronous specs are given a channel of the Done type. You must close or write to the channel
//to tell Ginkgo that your async test is done.
type Done chan<- interface{}
//GinkgoTestDescription represents the information about the current running test returned by CurrentGinkgoTestDescription
// FullTestText: a concatenation of ComponentTexts and the TestText
// ComponentTexts: a list of all texts for the Describes & Contexts leading up to the current test
// TestText: the text in the actual It or Measure node
// IsMeasurement: true if the current test is a measurement
// FileName: the name of the file containing the current test
// LineNumber: the line number for the current test
// Failed: if the current test has failed, this will be true (useful in an AfterEach)
type GinkgoTestDescription struct {
FullTestText string
ComponentTexts []string
TestText string
IsMeasurement bool
FileName string
LineNumber int
Failed bool
Duration time.Duration
}
//CurrentGinkgoTestDescripton returns information about the current running test.
func CurrentGinkgoTestDescription() GinkgoTestDescription {
summary, ok := globalSuite.CurrentRunningSpecSummary()
if !ok {
return GinkgoTestDescription{}
}
subjectCodeLocation := summary.ComponentCodeLocations[len(summary.ComponentCodeLocations)-1]
return GinkgoTestDescription{
ComponentTexts: summary.ComponentTexts[1:],
FullTestText: strings.Join(summary.ComponentTexts[1:], " "),
TestText: summary.ComponentTexts[len(summary.ComponentTexts)-1],
IsMeasurement: summary.IsMeasurement,
FileName: subjectCodeLocation.FileName,
LineNumber: subjectCodeLocation.LineNumber,
Failed: summary.HasFailureState(),
Duration: summary.RunTime,
}
}
//Measurement tests receive a Benchmarker.
//
//You use the Time() function to time how long the passed in body function takes to run
//You use the RecordValue() function to track arbitrary numerical measurements.
//The RecordValueWithPrecision() function can be used alternatively to provide the unit
//and resolution of the numeric measurement.
//The optional info argument is passed to the test reporter and can be used to
// provide the measurement data to a custom reporter with context.
//
//See http://onsi.github.io/ginkgo/#benchmark_tests for more details
type Benchmarker interface {
Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration)
RecordValue(name string, value float64, info ...interface{})
RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{})
}
//RunSpecs is the entry point for the Ginkgo test runner.
//You must call this within a Golang testing TestX(t *testing.T) function.
//
//To bootstrap a test suite you can use the Ginkgo CLI:
//
// ginkgo bootstrap
func RunSpecs(t GinkgoTestingT, description string) bool {
specReporters := []Reporter{buildDefaultReporter()}
return RunSpecsWithCustomReporters(t, description, specReporters)
}
//To run your tests with Ginkgo's default reporter and your custom reporter(s), replace
//RunSpecs() with this method.
func RunSpecsWithDefaultAndCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
specReporters = append(specReporters, buildDefaultReporter())
return RunSpecsWithCustomReporters(t, description, specReporters)
}
//To run your tests with your custom reporter(s) (and *not* Ginkgo's default reporter), replace
//RunSpecs() with this method. Note that parallel tests will not work correctly without the default reporter
func RunSpecsWithCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
writer := GinkgoWriter.(*writer.Writer)
writer.SetStream(config.DefaultReporterConfig.Verbose)
reporters := make([]reporters.Reporter, len(specReporters))
for i, reporter := range specReporters {
reporters[i] = reporter
}
passed, hasFocusedTests := globalSuite.Run(t, description, reporters, writer, config.GinkgoConfig)
if passed && hasFocusedTests && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" {
fmt.Println("PASS | FOCUSED")
os.Exit(types.GINKGO_FOCUS_EXIT_CODE)
}
return passed
}
func buildDefaultReporter() Reporter {
remoteReportingServer := config.GinkgoConfig.StreamHost
if remoteReportingServer == "" {
stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor, config.GinkgoConfig.FlakeAttempts > 1, colorable.NewColorableStdout())
return reporters.NewDefaultReporter(config.DefaultReporterConfig, stenographer)
} else {
debugFile := ""
if config.GinkgoConfig.DebugParallel {
debugFile = fmt.Sprintf("ginkgo-node-%d.log", config.GinkgoConfig.ParallelNode)
}
return remote.NewForwardingReporter(config.DefaultReporterConfig, remoteReportingServer, &http.Client{}, remote.NewOutputInterceptor(), GinkgoWriter.(*writer.Writer), debugFile)
}
}
//Skip notifies Ginkgo that the current spec was skipped.
func Skip(message string, callerSkip ...int) {
skip := 0
if len(callerSkip) > 0 {
skip = callerSkip[0]
}
globalFailer.Skip(message, codelocation.New(skip+1))
panic(GINKGO_PANIC)
}
//Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.)
func Fail(message string, callerSkip ...int) {
skip := 0
if len(callerSkip) > 0 {
skip = callerSkip[0]
}
globalFailer.Fail(message, codelocation.New(skip+1))
panic(GINKGO_PANIC)
}
//GinkgoRecover should be deferred at the top of any spawned goroutine that (may) call `Fail`
//Since Gomega assertions call fail, you should throw a `defer GinkgoRecover()` at the top of any goroutine that
//calls out to Gomega
//
//Here's why: Ginkgo's `Fail` method records the failure and then panics to prevent
//further assertions from running. This panic must be recovered. Ginkgo does this for you
//if the panic originates in a Ginkgo node (an It, BeforeEach, etc...)
//
//Unfortunately, if a panic originates on a goroutine *launched* from one of these nodes there's no
//way for Ginkgo to rescue the panic. To do this, you must remember to `defer GinkgoRecover()` at the top of such a goroutine.
func GinkgoRecover() {
e := recover()
if e != nil {
globalFailer.Panic(codelocation.New(1), e)
}
}
//Describe blocks allow you to organize your specs. A Describe block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe, Context and When blocks. Describe, Context and When blocks are functionally
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
//or method and, within that Describe, outline a number of Contexts and Whens.
func Describe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FDescribe
func FDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PDescribe
func PDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XDescribe
func XDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//Context blocks allow you to organize your specs. A Context block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe, Context and When blocks. Describe, Context and When blocks are functionally
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
//or method and, within that Describe, outline a number of Contexts and Whens.
func Context(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FContext
func FContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PContext
func PContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XContext
func XContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//When blocks allow you to organize your specs. A When block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe, Context and When blocks. Describe, Context and When blocks are functionally
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
//or method and, within that Describe, outline a number of Contexts and Whens.
func When(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FWhen
func FWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PWhen
func PWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XWhen
func XWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//It blocks contain your test code and assertions. You cannot nest any other Ginkgo blocks
//within an It block.
//
//Ginkgo will normally run It blocks synchronously. To perform asynchronous tests, pass a
//function that accepts a Done channel. When you do this, you can also provide an optional timeout.
func It(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeNone, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can focus individual Its using FIt
func FIt(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can mark Its as pending using PIt
func PIt(text string, _ ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Its as pending using XIt
func XIt(text string, _ ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//Specify blocks are aliases for It blocks and allow for more natural wording in situations
//which "It" does not fit into a natural sentence flow. All the same protocols apply for Specify blocks
//which apply to It blocks.
func Specify(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeNone, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can focus individual Specifys using FSpecify
func FSpecify(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can mark Specifys as pending using PSpecify
func PSpecify(text string, is ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Specifys as pending using XSpecify
func XSpecify(text string, is ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//By allows you to better document large Its.
//
//Generally you should try to keep your Its short and to the point. This is not always possible, however,
//especially in the context of integration tests that capture a particular workflow.
//
//By allows you to document such flows. By must be called within a runnable node (It, BeforeEach, Measure, etc...)
//By will simply log the passed in text to the GinkgoWriter. If By is handed a function it will immediately run the function.
func By(text string, callbacks ...func()) {
preamble := "\x1b[1mSTEP\x1b[0m"
if config.DefaultReporterConfig.NoColor {
preamble = "STEP"
}
fmt.Fprintln(GinkgoWriter, preamble+": "+text)
if len(callbacks) == 1 {
callbacks[0]()
}
if len(callbacks) > 1 {
panic("just one callback per By, please")
}
}
//Measure blocks run the passed in body function repeatedly (determined by the samples argument)
//and accumulate metrics provided to the Benchmarker by the body function.
//
//The body function must have the signature:
// func(b Benchmarker)
func Measure(text string, body interface{}, samples int) bool {
globalSuite.PushMeasureNode(text, body, types.FlagTypeNone, codelocation.New(1), samples)
return true
}
//You can focus individual Measures using FMeasure
func FMeasure(text string, body interface{}, samples int) bool {
globalSuite.PushMeasureNode(text, body, types.FlagTypeFocused, codelocation.New(1), samples)
return true
}
//You can mark Measurements as pending using PMeasure
func PMeasure(text string, _ ...interface{}) bool {
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Measurements as pending using XMeasure
func XMeasure(text string, _ ...interface{}) bool {
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//BeforeSuite blocks are run just once before any specs are run. When running in parallel, each
//parallel node process will call BeforeSuite.
//
//BeforeSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
//
//You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level.
func BeforeSuite(body interface{}, timeout ...float64) bool {
globalSuite.SetBeforeSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//AfterSuite blocks are *always* run after all the specs regardless of whether specs have passed or failed.
//Moreover, if Ginkgo receives an interrupt signal (^C) it will attempt to run the AfterSuite before exiting.
//
//When running in parallel, each parallel node process will call AfterSuite.
//
//AfterSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
//
//You may only register *one* AfterSuite handler per test suite. You typically do so in your bootstrap file at the top level.
func AfterSuite(body interface{}, timeout ...float64) bool {
globalSuite.SetAfterSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//SynchronizedBeforeSuite blocks are primarily meant to solve the problem of setting up singleton external resources shared across
//nodes when running tests in parallel. For example, say you have a shared database that you can only start one instance of that
//must be used in your tests. When running in parallel, only one node should set up the database and all other nodes should wait
//until that node is done before running.
//
//SynchronizedBeforeSuite accomplishes this by taking *two* function arguments. The first is only run on parallel node #1. The second is
//run on all nodes, but *only* after the first function completes succesfully. Ginkgo also makes it possible to send data from the first function (on Node 1)
//to the second function (on all the other nodes).
//
//The functions have the following signatures. The first function (which only runs on node 1) has the signature:
//
// func() []byte
//
//or, to run asynchronously:
//
// func(done Done) []byte
//
//The byte array returned by the first function is then passed to the second function, which has the signature:
//
// func(data []byte)
//
//or, to run asynchronously:
//
// func(data []byte, done Done)
//
//Here's a simple pseudo-code example that starts a shared database on Node 1 and shares the database's address with the other nodes:
//
// var dbClient db.Client
// var dbRunner db.Runner
//
// var _ = SynchronizedBeforeSuite(func() []byte {
// dbRunner = db.NewRunner()
// err := dbRunner.Start()
// Ω(err).ShouldNot(HaveOccurred())
// return []byte(dbRunner.URL)
// }, func(data []byte) {
// dbClient = db.NewClient()
// err := dbClient.Connect(string(data))
// Ω(err).ShouldNot(HaveOccurred())
// })
func SynchronizedBeforeSuite(node1Body interface{}, allNodesBody interface{}, timeout ...float64) bool {
globalSuite.SetSynchronizedBeforeSuiteNode(
node1Body,
allNodesBody,
codelocation.New(1),
parseTimeout(timeout...),
)
return true
}
//SynchronizedAfterSuite blocks complement the SynchronizedBeforeSuite blocks in solving the problem of setting up
//external singleton resources shared across nodes when running tests in parallel.
//
//SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all nodes. The second runs only on parallel node #1
//and *only* after all other nodes have finished and exited. This ensures that node 1, and any resources it is running, remain alive until
//all other nodes are finished.
//
//Both functions have the same signature: either func() or func(done Done) to run asynchronously.
//
//Here's a pseudo-code example that complements that given in SynchronizedBeforeSuite. Here, SynchronizedAfterSuite is used to tear down the shared database
//only after all nodes have finished:
//
// var _ = SynchronizedAfterSuite(func() {
// dbClient.Cleanup()
// }, func() {
// dbRunner.Stop()
// })
func SynchronizedAfterSuite(allNodesBody interface{}, node1Body interface{}, timeout ...float64) bool {
globalSuite.SetSynchronizedAfterSuiteNode(
allNodesBody,
node1Body,
codelocation.New(1),
parseTimeout(timeout...),
)
return true
}
//BeforeEach blocks are run before It blocks. When multiple BeforeEach blocks are defined in nested
//Describe and Context blocks the outermost BeforeEach blocks are run first.
//
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func BeforeEach(body interface{}, timeout ...float64) bool {
globalSuite.PushBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//JustBeforeEach blocks are run before It blocks but *after* all BeforeEach blocks. For more details,
//read the [documentation](http://onsi.github.io/ginkgo/#separating_creation_and_configuration_)
//
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func JustBeforeEach(body interface{}, timeout ...float64) bool {
globalSuite.PushJustBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//JustAfterEach blocks are run after It blocks but *before* all AfterEach blocks. For more details,
//read the [documentation](http://onsi.github.io/ginkgo/#separating_creation_and_configuration_)
//
//Like It blocks, JustAfterEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func JustAfterEach(body interface{}, timeout ...float64) bool {
globalSuite.PushJustAfterEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//AfterEach blocks are run after It blocks. When multiple AfterEach blocks are defined in nested
//Describe and Context blocks the innermost AfterEach blocks are run first.
//
//Like It blocks, AfterEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func AfterEach(body interface{}, timeout ...float64) bool {
globalSuite.PushAfterEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
func parseTimeout(timeout ...float64) time.Duration {
if len(timeout) == 0 {
return time.Duration(defaultTimeout * int64(time.Second))
} else {
return time.Duration(timeout[0] * float64(time.Second))
}
}

View File

@@ -0,0 +1,32 @@
package codelocation
import (
"regexp"
"runtime"
"runtime/debug"
"strings"
"github.com/onsi/ginkgo/types"
)
func New(skip int) types.CodeLocation {
_, file, line, _ := runtime.Caller(skip + 1)
stackTrace := PruneStack(string(debug.Stack()), skip)
return types.CodeLocation{FileName: file, LineNumber: line, FullStackTrace: stackTrace}
}
func PruneStack(fullStackTrace string, skip int) string {
stack := strings.Split(fullStackTrace, "\n")
if len(stack) > 2*(skip+1) {
stack = stack[2*(skip+1):]
}
prunedStack := []string{}
re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
for i := 0; i < len(stack)/2; i++ {
if !re.Match([]byte(stack[i*2])) {
prunedStack = append(prunedStack, stack[i*2])
prunedStack = append(prunedStack, stack[i*2+1])
}
}
return strings.Join(prunedStack, "\n")
}

View File

@@ -0,0 +1,151 @@
package containernode
import (
"math/rand"
"sort"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
)
type subjectOrContainerNode struct {
containerNode *ContainerNode
subjectNode leafnodes.SubjectNode
}
func (n subjectOrContainerNode) text() string {
if n.containerNode != nil {
return n.containerNode.Text()
} else {
return n.subjectNode.Text()
}
}
type CollatedNodes struct {
Containers []*ContainerNode
Subject leafnodes.SubjectNode
}
type ContainerNode struct {
text string
flag types.FlagType
codeLocation types.CodeLocation
setupNodes []leafnodes.BasicNode
subjectAndContainerNodes []subjectOrContainerNode
}
func New(text string, flag types.FlagType, codeLocation types.CodeLocation) *ContainerNode {
return &ContainerNode{
text: text,
flag: flag,
codeLocation: codeLocation,
}
}
func (container *ContainerNode) Shuffle(r *rand.Rand) {
sort.Sort(container)
permutation := r.Perm(len(container.subjectAndContainerNodes))
shuffledNodes := make([]subjectOrContainerNode, len(container.subjectAndContainerNodes))
for i, j := range permutation {
shuffledNodes[i] = container.subjectAndContainerNodes[j]
}
container.subjectAndContainerNodes = shuffledNodes
}
func (node *ContainerNode) BackPropagateProgrammaticFocus() bool {
if node.flag == types.FlagTypePending {
return false
}
shouldUnfocus := false
for _, subjectOrContainerNode := range node.subjectAndContainerNodes {
if subjectOrContainerNode.containerNode != nil {
shouldUnfocus = subjectOrContainerNode.containerNode.BackPropagateProgrammaticFocus() || shouldUnfocus
} else {
shouldUnfocus = (subjectOrContainerNode.subjectNode.Flag() == types.FlagTypeFocused) || shouldUnfocus
}
}
if shouldUnfocus {
if node.flag == types.FlagTypeFocused {
node.flag = types.FlagTypeNone
}
return true
}
return node.flag == types.FlagTypeFocused
}
func (node *ContainerNode) Collate() []CollatedNodes {
return node.collate([]*ContainerNode{})
}
func (node *ContainerNode) collate(enclosingContainers []*ContainerNode) []CollatedNodes {
collated := make([]CollatedNodes, 0)
containers := make([]*ContainerNode, len(enclosingContainers))
copy(containers, enclosingContainers)
containers = append(containers, node)
for _, subjectOrContainer := range node.subjectAndContainerNodes {
if subjectOrContainer.containerNode != nil {
collated = append(collated, subjectOrContainer.containerNode.collate(containers)...)
} else {
collated = append(collated, CollatedNodes{
Containers: containers,
Subject: subjectOrContainer.subjectNode,
})
}
}
return collated
}
func (node *ContainerNode) PushContainerNode(container *ContainerNode) {
node.subjectAndContainerNodes = append(node.subjectAndContainerNodes, subjectOrContainerNode{containerNode: container})
}
func (node *ContainerNode) PushSubjectNode(subject leafnodes.SubjectNode) {
node.subjectAndContainerNodes = append(node.subjectAndContainerNodes, subjectOrContainerNode{subjectNode: subject})
}
func (node *ContainerNode) PushSetupNode(setupNode leafnodes.BasicNode) {
node.setupNodes = append(node.setupNodes, setupNode)
}
func (node *ContainerNode) SetupNodesOfType(nodeType types.SpecComponentType) []leafnodes.BasicNode {
nodes := []leafnodes.BasicNode{}
for _, setupNode := range node.setupNodes {
if setupNode.Type() == nodeType {
nodes = append(nodes, setupNode)
}
}
return nodes
}
func (node *ContainerNode) Text() string {
return node.text
}
func (node *ContainerNode) CodeLocation() types.CodeLocation {
return node.codeLocation
}
func (node *ContainerNode) Flag() types.FlagType {
return node.flag
}
//sort.Interface
func (node *ContainerNode) Len() int {
return len(node.subjectAndContainerNodes)
}
func (node *ContainerNode) Less(i, j int) bool {
return node.subjectAndContainerNodes[i].text() < node.subjectAndContainerNodes[j].text()
}
func (node *ContainerNode) Swap(i, j int) {
node.subjectAndContainerNodes[i], node.subjectAndContainerNodes[j] = node.subjectAndContainerNodes[j], node.subjectAndContainerNodes[i]
}

View File

@@ -0,0 +1,92 @@
package failer
import (
"fmt"
"sync"
"github.com/onsi/ginkgo/types"
)
type Failer struct {
lock *sync.Mutex
failure types.SpecFailure
state types.SpecState
}
func New() *Failer {
return &Failer{
lock: &sync.Mutex{},
state: types.SpecStatePassed,
}
}
func (f *Failer) Panic(location types.CodeLocation, forwardedPanic interface{}) {
f.lock.Lock()
defer f.lock.Unlock()
if f.state == types.SpecStatePassed {
f.state = types.SpecStatePanicked
f.failure = types.SpecFailure{
Message: "Test Panicked",
Location: location,
ForwardedPanic: fmt.Sprintf("%v", forwardedPanic),
}
}
}
func (f *Failer) Timeout(location types.CodeLocation) {
f.lock.Lock()
defer f.lock.Unlock()
if f.state == types.SpecStatePassed {
f.state = types.SpecStateTimedOut
f.failure = types.SpecFailure{
Message: "Timed out",
Location: location,
}
}
}
func (f *Failer) Fail(message string, location types.CodeLocation) {
f.lock.Lock()
defer f.lock.Unlock()
if f.state == types.SpecStatePassed {
f.state = types.SpecStateFailed
f.failure = types.SpecFailure{
Message: message,
Location: location,
}
}
}
func (f *Failer) Drain(componentType types.SpecComponentType, componentIndex int, componentCodeLocation types.CodeLocation) (types.SpecFailure, types.SpecState) {
f.lock.Lock()
defer f.lock.Unlock()
failure := f.failure
outcome := f.state
if outcome != types.SpecStatePassed {
failure.ComponentType = componentType
failure.ComponentIndex = componentIndex
failure.ComponentCodeLocation = componentCodeLocation
}
f.state = types.SpecStatePassed
f.failure = types.SpecFailure{}
return failure, outcome
}
func (f *Failer) Skip(message string, location types.CodeLocation) {
f.lock.Lock()
defer f.lock.Unlock()
if f.state == types.SpecStatePassed {
f.state = types.SpecStateSkipped
f.failure = types.SpecFailure{
Message: message,
Location: location,
}
}
}

View File

@@ -0,0 +1,103 @@
package leafnodes
import (
"math"
"time"
"sync"
"github.com/onsi/ginkgo/types"
)
type benchmarker struct {
mu sync.Mutex
measurements map[string]*types.SpecMeasurement
orderCounter int
}
func newBenchmarker() *benchmarker {
return &benchmarker{
measurements: make(map[string]*types.SpecMeasurement, 0),
}
}
func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) {
t := time.Now()
body()
elapsedTime = time.Since(t)
b.mu.Lock()
defer b.mu.Unlock()
measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", 3, info...)
measurement.Results = append(measurement.Results, elapsedTime.Seconds())
return
}
func (b *benchmarker) RecordValue(name string, value float64, info ...interface{}) {
b.mu.Lock()
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", 3, info...)
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) {
b.mu.Lock()
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", units, precision, info...)
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestLabel string, averageLabel string, units string, precision int, info ...interface{}) *types.SpecMeasurement {
measurement, ok := b.measurements[name]
if !ok {
var computedInfo interface{}
computedInfo = nil
if len(info) > 0 {
computedInfo = info[0]
}
measurement = &types.SpecMeasurement{
Name: name,
Info: computedInfo,
Order: b.orderCounter,
SmallestLabel: smallestLabel,
LargestLabel: largestLabel,
AverageLabel: averageLabel,
Units: units,
Precision: precision,
Results: make([]float64, 0),
}
b.measurements[name] = measurement
b.orderCounter++
}
return measurement
}
func (b *benchmarker) measurementsReport() map[string]*types.SpecMeasurement {
b.mu.Lock()
defer b.mu.Unlock()
for _, measurement := range b.measurements {
measurement.Smallest = math.MaxFloat64
measurement.Largest = -math.MaxFloat64
sum := float64(0)
sumOfSquares := float64(0)
for _, result := range measurement.Results {
if result > measurement.Largest {
measurement.Largest = result
}
if result < measurement.Smallest {
measurement.Smallest = result
}
sum += result
sumOfSquares += result * result
}
n := float64(len(measurement.Results))
measurement.Average = sum / n
measurement.StdDeviation = math.Sqrt(sumOfSquares/n - (sum/n)*(sum/n))
}
return b.measurements
}

View File

@@ -0,0 +1,19 @@
package leafnodes
import (
"github.com/onsi/ginkgo/types"
)
type BasicNode interface {
Type() types.SpecComponentType
Run() (types.SpecState, types.SpecFailure)
CodeLocation() types.CodeLocation
}
type SubjectNode interface {
BasicNode
Text() string
Flag() types.FlagType
Samples() int
}

View File

@@ -0,0 +1,47 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type ItNode struct {
runner *runner
flag types.FlagType
text string
}
func NewItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *ItNode {
return &ItNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeIt, componentIndex),
flag: flag,
text: text,
}
}
func (node *ItNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *ItNode) Type() types.SpecComponentType {
return types.SpecComponentTypeIt
}
func (node *ItNode) Text() string {
return node.text
}
func (node *ItNode) Flag() types.FlagType {
return node.flag
}
func (node *ItNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *ItNode) Samples() int {
return 1
}

View File

@@ -0,0 +1,62 @@
package leafnodes
import (
"reflect"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type MeasureNode struct {
runner *runner
text string
flag types.FlagType
samples int
benchmarker *benchmarker
}
func NewMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int, failer *failer.Failer, componentIndex int) *MeasureNode {
benchmarker := newBenchmarker()
wrappedBody := func() {
reflect.ValueOf(body).Call([]reflect.Value{reflect.ValueOf(benchmarker)})
}
return &MeasureNode{
runner: newRunner(wrappedBody, codeLocation, 0, failer, types.SpecComponentTypeMeasure, componentIndex),
text: text,
flag: flag,
samples: samples,
benchmarker: benchmarker,
}
}
func (node *MeasureNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *MeasureNode) MeasurementsReport() map[string]*types.SpecMeasurement {
return node.benchmarker.measurementsReport()
}
func (node *MeasureNode) Type() types.SpecComponentType {
return types.SpecComponentTypeMeasure
}
func (node *MeasureNode) Text() string {
return node.text
}
func (node *MeasureNode) Flag() types.FlagType {
return node.flag
}
func (node *MeasureNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *MeasureNode) Samples() int {
return node.samples
}

View File

@@ -0,0 +1,117 @@
package leafnodes
import (
"fmt"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type runner struct {
isAsync bool
asyncFunc func(chan<- interface{})
syncFunc func()
codeLocation types.CodeLocation
timeoutThreshold time.Duration
nodeType types.SpecComponentType
componentIndex int
failer *failer.Failer
}
func newRunner(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, nodeType types.SpecComponentType, componentIndex int) *runner {
bodyType := reflect.TypeOf(body)
if bodyType.Kind() != reflect.Func {
panic(fmt.Sprintf("Expected a function but got something else at %v", codeLocation))
}
runner := &runner{
codeLocation: codeLocation,
timeoutThreshold: timeout,
failer: failer,
nodeType: nodeType,
componentIndex: componentIndex,
}
switch bodyType.NumIn() {
case 0:
runner.syncFunc = body.(func())
return runner
case 1:
if !(bodyType.In(0).Kind() == reflect.Chan && bodyType.In(0).Elem().Kind() == reflect.Interface) {
panic(fmt.Sprintf("Must pass a Done channel to function at %v", codeLocation))
}
wrappedBody := func(done chan<- interface{}) {
bodyValue := reflect.ValueOf(body)
bodyValue.Call([]reflect.Value{reflect.ValueOf(done)})
}
runner.isAsync = true
runner.asyncFunc = wrappedBody
return runner
}
panic(fmt.Sprintf("Too many arguments to function at %v", codeLocation))
}
func (r *runner) run() (outcome types.SpecState, failure types.SpecFailure) {
if r.isAsync {
return r.runAsync()
} else {
return r.runSync()
}
}
func (r *runner) runAsync() (outcome types.SpecState, failure types.SpecFailure) {
done := make(chan interface{}, 1)
go func() {
finished := false
defer func() {
if e := recover(); e != nil || !finished {
r.failer.Panic(codelocation.New(2), e)
select {
case <-done:
break
default:
close(done)
}
}
}()
r.asyncFunc(done)
finished = true
}()
// If this goroutine gets no CPU time before the select block,
// the <-done case may complete even if the test took longer than the timeoutThreshold.
// This can cause flaky behaviour, but we haven't seen it in the wild.
select {
case <-done:
case <-time.After(r.timeoutThreshold):
r.failer.Timeout(r.codeLocation)
}
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation)
return
}
func (r *runner) runSync() (outcome types.SpecState, failure types.SpecFailure) {
finished := false
defer func() {
if e := recover(); e != nil || !finished {
r.failer.Panic(codelocation.New(2), e)
}
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation)
}()
r.syncFunc()
finished = true
return
}

View File

@@ -0,0 +1,48 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type SetupNode struct {
runner *runner
}
func (node *SetupNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *SetupNode) Type() types.SpecComponentType {
return node.runner.nodeType
}
func (node *SetupNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func NewBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeEach, componentIndex),
}
}
func NewAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterEach, componentIndex),
}
}
func NewJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeJustBeforeEach, componentIndex),
}
}
func NewJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeJustAfterEach, componentIndex),
}
}

View File

@@ -0,0 +1,55 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type SuiteNode interface {
Run(parallelNode int, parallelTotal int, syncHost string) bool
Passed() bool
Summary() *types.SetupSummary
}
type simpleSuiteNode struct {
runner *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func (node *simpleSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
node.outcome, node.failure = node.runner.run()
node.runTime = time.Since(t)
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runner.nodeType,
CodeLocation: node.runner.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func NewBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0),
}
}
func NewAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}

View File

@@ -0,0 +1,90 @@
package leafnodes
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type synchronizedAfterSuiteNode struct {
runnerA *runner
runnerB *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &synchronizedAfterSuiteNode{
runnerA: newRunner(bodyA, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
runnerB: newRunner(bodyB, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}
func (node *synchronizedAfterSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
node.outcome, node.failure = node.runnerA.run()
if parallelNode == 1 {
if parallelTotal > 1 {
node.waitUntilOtherNodesAreDone(syncHost)
}
outcome, failure := node.runnerB.run()
if node.outcome == types.SpecStatePassed {
node.outcome, node.failure = outcome, failure
}
}
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedAfterSuiteNode) waitUntilOtherNodesAreDone(syncHost string) {
for {
if node.canRun(syncHost) {
return
}
time.Sleep(50 * time.Millisecond)
}
}
func (node *synchronizedAfterSuiteNode) canRun(syncHost string) bool {
resp, err := http.Get(syncHost + "/RemoteAfterSuiteData")
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
resp.Body.Close()
afterSuiteData := types.RemoteAfterSuiteData{}
err = json.Unmarshal(body, &afterSuiteData)
if err != nil {
return false
}
return afterSuiteData.CanRun
}

View File

@@ -0,0 +1,181 @@
package leafnodes
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type synchronizedBeforeSuiteNode struct {
runnerA *runner
runnerB *runner
data []byte
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
node := &synchronizedBeforeSuiteNode{}
node.runnerA = newRunner(node.wrapA(bodyA), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
node.runnerB = newRunner(node.wrapB(bodyB), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
return node
}
func (node *synchronizedBeforeSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
defer func() {
node.runTime = time.Since(t)
}()
if parallelNode == 1 {
node.outcome, node.failure = node.runA(parallelTotal, syncHost)
} else {
node.outcome, node.failure = node.waitForA(syncHost)
}
if node.outcome != types.SpecStatePassed {
return false
}
node.outcome, node.failure = node.runnerB.run()
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) runA(parallelTotal int, syncHost string) (types.SpecState, types.SpecFailure) {
outcome, failure := node.runnerA.run()
if parallelTotal > 1 {
state := types.RemoteBeforeSuiteStatePassed
if outcome != types.SpecStatePassed {
state = types.RemoteBeforeSuiteStateFailed
}
json := (types.RemoteBeforeSuiteData{
Data: node.data,
State: state,
}).ToJSON()
http.Post(syncHost+"/BeforeSuiteState", "application/json", bytes.NewBuffer(json))
}
return outcome, failure
}
func (node *synchronizedBeforeSuiteNode) waitForA(syncHost string) (types.SpecState, types.SpecFailure) {
failure := func(message string) types.SpecFailure {
return types.SpecFailure{
Message: message,
Location: node.runnerA.codeLocation,
ComponentType: node.runnerA.nodeType,
ComponentIndex: node.runnerA.componentIndex,
ComponentCodeLocation: node.runnerA.codeLocation,
}
}
for {
resp, err := http.Get(syncHost + "/BeforeSuiteState")
if err != nil || resp.StatusCode != http.StatusOK {
return types.SpecStateFailed, failure("Failed to fetch BeforeSuite state")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return types.SpecStateFailed, failure("Failed to read BeforeSuite state")
}
resp.Body.Close()
beforeSuiteData := types.RemoteBeforeSuiteData{}
err = json.Unmarshal(body, &beforeSuiteData)
if err != nil {
return types.SpecStateFailed, failure("Failed to decode BeforeSuite state")
}
switch beforeSuiteData.State {
case types.RemoteBeforeSuiteStatePassed:
node.data = beforeSuiteData.Data
return types.SpecStatePassed, types.SpecFailure{}
case types.RemoteBeforeSuiteStateFailed:
return types.SpecStateFailed, failure("BeforeSuite on Node 1 failed")
case types.RemoteBeforeSuiteStateDisappeared:
return types.SpecStateFailed, failure("Node 1 disappeared before completing BeforeSuite")
}
time.Sleep(50 * time.Millisecond)
}
}
func (node *synchronizedBeforeSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedBeforeSuiteNode) wrapA(bodyA interface{}) interface{} {
typeA := reflect.TypeOf(bodyA)
if typeA.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its first argument")
}
takesNothing := typeA.NumIn() == 0
takesADoneChannel := typeA.NumIn() == 1 && typeA.In(0).Kind() == reflect.Chan && typeA.In(0).Elem().Kind() == reflect.Interface
returnsBytes := typeA.NumOut() == 1 && typeA.Out(0).Kind() == reflect.Slice && typeA.Out(0).Elem().Kind() == reflect.Uint8
if !((takesNothing || takesADoneChannel) && returnsBytes) {
panic("SynchronizedBeforeSuite's first argument should be a function that returns []byte and either takes no arguments or takes a Done channel.")
}
if takesADoneChannel {
return func(done chan<- interface{}) {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{reflect.ValueOf(done)})
node.data = out[0].Interface().([]byte)
}
}
return func() {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{})
node.data = out[0].Interface().([]byte)
}
}
func (node *synchronizedBeforeSuiteNode) wrapB(bodyB interface{}) interface{} {
typeB := reflect.TypeOf(bodyB)
if typeB.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its second argument")
}
returnsNothing := typeB.NumOut() == 0
takesBytesOnly := typeB.NumIn() == 1 && typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8
takesBytesAndDone := typeB.NumIn() == 2 &&
typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8 &&
typeB.In(1).Kind() == reflect.Chan && typeB.In(1).Elem().Kind() == reflect.Interface
if !((takesBytesOnly || takesBytesAndDone) && returnsNothing) {
panic("SynchronizedBeforeSuite's second argument should be a function that returns nothing and either takes []byte or ([]byte, Done)")
}
if takesBytesAndDone {
return func(done chan<- interface{}) {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data), reflect.ValueOf(done)})
}
}
return func() {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data)})
}
}

View File

@@ -0,0 +1,249 @@
/*
Aggregator is a reporter used by the Ginkgo CLI to aggregate and present parallel test output
coherently as tests complete. You shouldn't need to use this in your code. To run tests in parallel:
ginkgo -nodes=N
where N is the number of nodes you desire.
*/
package remote
import (
"time"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
type configAndSuite struct {
config config.GinkgoConfigType
summary *types.SuiteSummary
}
type Aggregator struct {
nodeCount int
config config.DefaultReporterConfigType
stenographer stenographer.Stenographer
result chan bool
suiteBeginnings chan configAndSuite
aggregatedSuiteBeginnings []configAndSuite
beforeSuites chan *types.SetupSummary
aggregatedBeforeSuites []*types.SetupSummary
afterSuites chan *types.SetupSummary
aggregatedAfterSuites []*types.SetupSummary
specCompletions chan *types.SpecSummary
completedSpecs []*types.SpecSummary
suiteEndings chan *types.SuiteSummary
aggregatedSuiteEndings []*types.SuiteSummary
specs []*types.SpecSummary
startTime time.Time
}
func NewAggregator(nodeCount int, result chan bool, config config.DefaultReporterConfigType, stenographer stenographer.Stenographer) *Aggregator {
aggregator := &Aggregator{
nodeCount: nodeCount,
result: result,
config: config,
stenographer: stenographer,
suiteBeginnings: make(chan configAndSuite, 0),
beforeSuites: make(chan *types.SetupSummary, 0),
afterSuites: make(chan *types.SetupSummary, 0),
specCompletions: make(chan *types.SpecSummary, 0),
suiteEndings: make(chan *types.SuiteSummary, 0),
}
go aggregator.mux()
return aggregator
}
func (aggregator *Aggregator) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
aggregator.suiteBeginnings <- configAndSuite{config, summary}
}
func (aggregator *Aggregator) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
aggregator.beforeSuites <- setupSummary
}
func (aggregator *Aggregator) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
aggregator.afterSuites <- setupSummary
}
func (aggregator *Aggregator) SpecWillRun(specSummary *types.SpecSummary) {
//noop
}
func (aggregator *Aggregator) SpecDidComplete(specSummary *types.SpecSummary) {
aggregator.specCompletions <- specSummary
}
func (aggregator *Aggregator) SpecSuiteDidEnd(summary *types.SuiteSummary) {
aggregator.suiteEndings <- summary
}
func (aggregator *Aggregator) mux() {
loop:
for {
select {
case configAndSuite := <-aggregator.suiteBeginnings:
aggregator.registerSuiteBeginning(configAndSuite)
case setupSummary := <-aggregator.beforeSuites:
aggregator.registerBeforeSuite(setupSummary)
case setupSummary := <-aggregator.afterSuites:
aggregator.registerAfterSuite(setupSummary)
case specSummary := <-aggregator.specCompletions:
aggregator.registerSpecCompletion(specSummary)
case suite := <-aggregator.suiteEndings:
finished, passed := aggregator.registerSuiteEnding(suite)
if finished {
aggregator.result <- passed
break loop
}
}
}
}
func (aggregator *Aggregator) registerSuiteBeginning(configAndSuite configAndSuite) {
aggregator.aggregatedSuiteBeginnings = append(aggregator.aggregatedSuiteBeginnings, configAndSuite)
if len(aggregator.aggregatedSuiteBeginnings) == 1 {
aggregator.startTime = time.Now()
}
if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount {
return
}
aggregator.stenographer.AnnounceSuite(configAndSuite.summary.SuiteDescription, configAndSuite.config.RandomSeed, configAndSuite.config.RandomizeAllSpecs, aggregator.config.Succinct)
totalNumberOfSpecs := 0
if len(aggregator.aggregatedSuiteBeginnings) > 0 {
totalNumberOfSpecs = configAndSuite.summary.NumberOfSpecsBeforeParallelization
}
aggregator.stenographer.AnnounceTotalNumberOfSpecs(totalNumberOfSpecs, aggregator.config.Succinct)
aggregator.stenographer.AnnounceAggregatedParallelRun(aggregator.nodeCount, aggregator.config.Succinct)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerBeforeSuite(setupSummary *types.SetupSummary) {
aggregator.aggregatedBeforeSuites = append(aggregator.aggregatedBeforeSuites, setupSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerAfterSuite(setupSummary *types.SetupSummary) {
aggregator.aggregatedAfterSuites = append(aggregator.aggregatedAfterSuites, setupSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerSpecCompletion(specSummary *types.SpecSummary) {
aggregator.completedSpecs = append(aggregator.completedSpecs, specSummary)
aggregator.specs = append(aggregator.specs, specSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) flushCompletedSpecs() {
if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount {
return
}
for _, setupSummary := range aggregator.aggregatedBeforeSuites {
aggregator.announceBeforeSuite(setupSummary)
}
for _, specSummary := range aggregator.completedSpecs {
aggregator.announceSpec(specSummary)
}
for _, setupSummary := range aggregator.aggregatedAfterSuites {
aggregator.announceAfterSuite(setupSummary)
}
aggregator.aggregatedBeforeSuites = []*types.SetupSummary{}
aggregator.completedSpecs = []*types.SpecSummary{}
aggregator.aggregatedAfterSuites = []*types.SetupSummary{}
}
func (aggregator *Aggregator) announceBeforeSuite(setupSummary *types.SetupSummary) {
aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput)
if setupSummary.State != types.SpecStatePassed {
aggregator.stenographer.AnnounceBeforeSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) announceAfterSuite(setupSummary *types.SetupSummary) {
aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput)
if setupSummary.State != types.SpecStatePassed {
aggregator.stenographer.AnnounceAfterSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) announceSpec(specSummary *types.SpecSummary) {
if aggregator.config.Verbose && specSummary.State != types.SpecStatePending && specSummary.State != types.SpecStateSkipped {
aggregator.stenographer.AnnounceSpecWillRun(specSummary)
}
aggregator.stenographer.AnnounceCapturedOutput(specSummary.CapturedOutput)
switch specSummary.State {
case types.SpecStatePassed:
if specSummary.IsMeasurement {
aggregator.stenographer.AnnounceSuccesfulMeasurement(specSummary, aggregator.config.Succinct)
} else if specSummary.RunTime.Seconds() >= aggregator.config.SlowSpecThreshold {
aggregator.stenographer.AnnounceSuccesfulSlowSpec(specSummary, aggregator.config.Succinct)
} else {
aggregator.stenographer.AnnounceSuccesfulSpec(specSummary)
}
case types.SpecStatePending:
aggregator.stenographer.AnnouncePendingSpec(specSummary, aggregator.config.NoisyPendings && !aggregator.config.Succinct)
case types.SpecStateSkipped:
aggregator.stenographer.AnnounceSkippedSpec(specSummary, aggregator.config.Succinct || !aggregator.config.NoisySkippings, aggregator.config.FullTrace)
case types.SpecStateTimedOut:
aggregator.stenographer.AnnounceSpecTimedOut(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
case types.SpecStatePanicked:
aggregator.stenographer.AnnounceSpecPanicked(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
case types.SpecStateFailed:
aggregator.stenographer.AnnounceSpecFailed(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) registerSuiteEnding(suite *types.SuiteSummary) (finished bool, passed bool) {
aggregator.aggregatedSuiteEndings = append(aggregator.aggregatedSuiteEndings, suite)
if len(aggregator.aggregatedSuiteEndings) < aggregator.nodeCount {
return false, false
}
aggregatedSuiteSummary := &types.SuiteSummary{}
aggregatedSuiteSummary.SuiteSucceeded = true
for _, suiteSummary := range aggregator.aggregatedSuiteEndings {
if suiteSummary.SuiteSucceeded == false {
aggregatedSuiteSummary.SuiteSucceeded = false
}
aggregatedSuiteSummary.NumberOfSpecsThatWillBeRun += suiteSummary.NumberOfSpecsThatWillBeRun
aggregatedSuiteSummary.NumberOfTotalSpecs += suiteSummary.NumberOfTotalSpecs
aggregatedSuiteSummary.NumberOfPassedSpecs += suiteSummary.NumberOfPassedSpecs
aggregatedSuiteSummary.NumberOfFailedSpecs += suiteSummary.NumberOfFailedSpecs
aggregatedSuiteSummary.NumberOfPendingSpecs += suiteSummary.NumberOfPendingSpecs
aggregatedSuiteSummary.NumberOfSkippedSpecs += suiteSummary.NumberOfSkippedSpecs
aggregatedSuiteSummary.NumberOfFlakedSpecs += suiteSummary.NumberOfFlakedSpecs
}
aggregatedSuiteSummary.RunTime = time.Since(aggregator.startTime)
aggregator.stenographer.SummarizeFailures(aggregator.specs)
aggregator.stenographer.AnnounceSpecRunCompletion(aggregatedSuiteSummary, aggregator.config.Succinct)
return true, aggregatedSuiteSummary.SuiteSucceeded
}

View File

@@ -0,0 +1,147 @@
package remote
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
//An interface to net/http's client to allow the injection of fakes under test
type Poster interface {
Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error)
}
/*
The ForwardingReporter is a Ginkgo reporter that forwards information to
a Ginkgo remote server.
When streaming parallel test output, this repoter is automatically installed by Ginkgo.
This is accomplished by passing in the GINKGO_REMOTE_REPORTING_SERVER environment variable to `go test`, the Ginkgo test runner
detects this environment variable (which should contain the host of the server) and automatically installs a ForwardingReporter
in place of Ginkgo's DefaultReporter.
*/
type ForwardingReporter struct {
serverHost string
poster Poster
outputInterceptor OutputInterceptor
debugMode bool
debugFile *os.File
nestedReporter *reporters.DefaultReporter
}
func NewForwardingReporter(config config.DefaultReporterConfigType, serverHost string, poster Poster, outputInterceptor OutputInterceptor, ginkgoWriter *writer.Writer, debugFile string) *ForwardingReporter {
reporter := &ForwardingReporter{
serverHost: serverHost,
poster: poster,
outputInterceptor: outputInterceptor,
}
if debugFile != "" {
var err error
reporter.debugMode = true
reporter.debugFile, err = os.Create(debugFile)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if !config.Verbose {
//if verbose is true then the GinkgoWriter emits to stdout. Don't _also_ redirect GinkgoWriter output as that will result in duplication.
ginkgoWriter.AndRedirectTo(reporter.debugFile)
}
outputInterceptor.StreamTo(reporter.debugFile) //This is not working
stenographer := stenographer.New(false, true, reporter.debugFile)
config.Succinct = false
config.Verbose = true
config.FullTrace = true
reporter.nestedReporter = reporters.NewDefaultReporter(config, stenographer)
}
return reporter
}
func (reporter *ForwardingReporter) post(path string, data interface{}) {
encoded, _ := json.Marshal(data)
buffer := bytes.NewBuffer(encoded)
reporter.poster.Post(reporter.serverHost+path, "application/json", buffer)
}
func (reporter *ForwardingReporter) SpecSuiteWillBegin(conf config.GinkgoConfigType, summary *types.SuiteSummary) {
data := struct {
Config config.GinkgoConfigType `json:"config"`
Summary *types.SuiteSummary `json:"suite-summary"`
}{
conf,
summary,
}
reporter.outputInterceptor.StartInterceptingOutput()
if reporter.debugMode {
reporter.nestedReporter.SpecSuiteWillBegin(conf, summary)
reporter.debugFile.Sync()
}
reporter.post("/SpecSuiteWillBegin", data)
}
func (reporter *ForwardingReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.BeforeSuiteDidRun(setupSummary)
reporter.debugFile.Sync()
}
reporter.post("/BeforeSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecWillRun(specSummary *types.SpecSummary) {
if reporter.debugMode {
reporter.nestedReporter.SpecWillRun(specSummary)
reporter.debugFile.Sync()
}
reporter.post("/SpecWillRun", specSummary)
}
func (reporter *ForwardingReporter) SpecDidComplete(specSummary *types.SpecSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
specSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.SpecDidComplete(specSummary)
reporter.debugFile.Sync()
}
reporter.post("/SpecDidComplete", specSummary)
}
func (reporter *ForwardingReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.AfterSuiteDidRun(setupSummary)
reporter.debugFile.Sync()
}
reporter.post("/AfterSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.outputInterceptor.StopInterceptingAndReturnOutput()
if reporter.debugMode {
reporter.nestedReporter.SpecSuiteDidEnd(summary)
reporter.debugFile.Sync()
}
reporter.post("/SpecSuiteDidEnd", summary)
}

View File

@@ -0,0 +1,13 @@
package remote
import "os"
/*
The OutputInterceptor is used by the ForwardingReporter to
intercept and capture all stdin and stderr output during a test run.
*/
type OutputInterceptor interface {
StartInterceptingOutput() error
StopInterceptingAndReturnOutput() (string, error)
StreamTo(*os.File)
}

View File

@@ -0,0 +1,83 @@
// +build freebsd openbsd netbsd dragonfly darwin linux solaris
package remote
import (
"errors"
"io/ioutil"
"os"
"github.com/hpcloud/tail"
)
func NewOutputInterceptor() OutputInterceptor {
return &outputInterceptor{}
}
type outputInterceptor struct {
redirectFile *os.File
streamTarget *os.File
intercepting bool
tailer *tail.Tail
doneTailing chan bool
}
func (interceptor *outputInterceptor) StartInterceptingOutput() error {
if interceptor.intercepting {
return errors.New("Already intercepting output!")
}
interceptor.intercepting = true
var err error
interceptor.redirectFile, err = ioutil.TempFile("", "ginkgo-output")
if err != nil {
return err
}
// Call a function in ./syscall_dup_*.go
// If building for everything other than linux_arm64,
// use a "normal" syscall.Dup2(oldfd, newfd) call. If building for linux_arm64 (which doesn't have syscall.Dup2)
// call syscall.Dup3(oldfd, newfd, 0). They are nearly identical, see: http://linux.die.net/man/2/dup3
syscallDup(int(interceptor.redirectFile.Fd()), 1)
syscallDup(int(interceptor.redirectFile.Fd()), 2)
if interceptor.streamTarget != nil {
interceptor.tailer, _ = tail.TailFile(interceptor.redirectFile.Name(), tail.Config{Follow: true})
interceptor.doneTailing = make(chan bool)
go func() {
for line := range interceptor.tailer.Lines {
interceptor.streamTarget.Write([]byte(line.Text + "\n"))
}
close(interceptor.doneTailing)
}()
}
return nil
}
func (interceptor *outputInterceptor) StopInterceptingAndReturnOutput() (string, error) {
if !interceptor.intercepting {
return "", errors.New("Not intercepting output!")
}
interceptor.redirectFile.Close()
output, err := ioutil.ReadFile(interceptor.redirectFile.Name())
os.Remove(interceptor.redirectFile.Name())
interceptor.intercepting = false
if interceptor.streamTarget != nil {
interceptor.tailer.Stop()
interceptor.tailer.Cleanup()
<-interceptor.doneTailing
interceptor.streamTarget.Sync()
}
return string(output), err
}
func (interceptor *outputInterceptor) StreamTo(out *os.File) {
interceptor.streamTarget = out
}

View File

@@ -0,0 +1,36 @@
// +build windows
package remote
import (
"errors"
"os"
)
func NewOutputInterceptor() OutputInterceptor {
return &outputInterceptor{}
}
type outputInterceptor struct {
intercepting bool
}
func (interceptor *outputInterceptor) StartInterceptingOutput() error {
if interceptor.intercepting {
return errors.New("Already intercepting output!")
}
interceptor.intercepting = true
// not working on windows...
return nil
}
func (interceptor *outputInterceptor) StopInterceptingAndReturnOutput() (string, error) {
// not working on windows...
interceptor.intercepting = false
return "", nil
}
func (interceptor *outputInterceptor) StreamTo(*os.File) {}

224
vendor/github.com/onsi/ginkgo/internal/remote/server.go generated vendored Normal file
View File

@@ -0,0 +1,224 @@
/*
The remote package provides the pieces to allow Ginkgo test suites to report to remote listeners.
This is used, primarily, to enable streaming parallel test output but has, in principal, broader applications (e.g. streaming test output to a browser).
*/
package remote
import (
"encoding/json"
"io/ioutil"
"net"
"net/http"
"sync"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
)
/*
Server spins up on an automatically selected port and listens for communication from the forwarding reporter.
It then forwards that communication to attached reporters.
*/
type Server struct {
listener net.Listener
reporters []reporters.Reporter
alives []func() bool
lock *sync.Mutex
beforeSuiteData types.RemoteBeforeSuiteData
parallelTotal int
counter int
}
//Create a new server, automatically selecting a port
func NewServer(parallelTotal int) (*Server, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
return &Server{
listener: listener,
lock: &sync.Mutex{},
alives: make([]func() bool, parallelTotal),
beforeSuiteData: types.RemoteBeforeSuiteData{Data: nil, State: types.RemoteBeforeSuiteStatePending},
parallelTotal: parallelTotal,
}, nil
}
//Start the server. You don't need to `go s.Start()`, just `s.Start()`
func (server *Server) Start() {
httpServer := &http.Server{}
mux := http.NewServeMux()
httpServer.Handler = mux
//streaming endpoints
mux.HandleFunc("/SpecSuiteWillBegin", server.specSuiteWillBegin)
mux.HandleFunc("/BeforeSuiteDidRun", server.beforeSuiteDidRun)
mux.HandleFunc("/AfterSuiteDidRun", server.afterSuiteDidRun)
mux.HandleFunc("/SpecWillRun", server.specWillRun)
mux.HandleFunc("/SpecDidComplete", server.specDidComplete)
mux.HandleFunc("/SpecSuiteDidEnd", server.specSuiteDidEnd)
//synchronization endpoints
mux.HandleFunc("/BeforeSuiteState", server.handleBeforeSuiteState)
mux.HandleFunc("/RemoteAfterSuiteData", server.handleRemoteAfterSuiteData)
mux.HandleFunc("/counter", server.handleCounter)
mux.HandleFunc("/has-counter", server.handleHasCounter) //for backward compatibility
go httpServer.Serve(server.listener)
}
//Stop the server
func (server *Server) Close() {
server.listener.Close()
}
//The address the server can be reached it. Pass this into the `ForwardingReporter`.
func (server *Server) Address() string {
return "http://" + server.listener.Addr().String()
}
//
// Streaming Endpoints
//
//The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters`
func (server *Server) readAll(request *http.Request) []byte {
defer request.Body.Close()
body, _ := ioutil.ReadAll(request.Body)
return body
}
func (server *Server) RegisterReporters(reporters ...reporters.Reporter) {
server.reporters = reporters
}
func (server *Server) specSuiteWillBegin(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var data struct {
Config config.GinkgoConfigType `json:"config"`
Summary *types.SuiteSummary `json:"suite-summary"`
}
json.Unmarshal(body, &data)
for _, reporter := range server.reporters {
reporter.SpecSuiteWillBegin(data.Config, data.Summary)
}
}
func (server *Server) beforeSuiteDidRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var setupSummary *types.SetupSummary
json.Unmarshal(body, &setupSummary)
for _, reporter := range server.reporters {
reporter.BeforeSuiteDidRun(setupSummary)
}
}
func (server *Server) afterSuiteDidRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var setupSummary *types.SetupSummary
json.Unmarshal(body, &setupSummary)
for _, reporter := range server.reporters {
reporter.AfterSuiteDidRun(setupSummary)
}
}
func (server *Server) specWillRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var specSummary *types.SpecSummary
json.Unmarshal(body, &specSummary)
for _, reporter := range server.reporters {
reporter.SpecWillRun(specSummary)
}
}
func (server *Server) specDidComplete(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var specSummary *types.SpecSummary
json.Unmarshal(body, &specSummary)
for _, reporter := range server.reporters {
reporter.SpecDidComplete(specSummary)
}
}
func (server *Server) specSuiteDidEnd(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var suiteSummary *types.SuiteSummary
json.Unmarshal(body, &suiteSummary)
for _, reporter := range server.reporters {
reporter.SpecSuiteDidEnd(suiteSummary)
}
}
//
// Synchronization Endpoints
//
func (server *Server) RegisterAlive(node int, alive func() bool) {
server.lock.Lock()
defer server.lock.Unlock()
server.alives[node-1] = alive
}
func (server *Server) nodeIsAlive(node int) bool {
server.lock.Lock()
defer server.lock.Unlock()
alive := server.alives[node-1]
if alive == nil {
return true
}
return alive()
}
func (server *Server) handleBeforeSuiteState(writer http.ResponseWriter, request *http.Request) {
if request.Method == "POST" {
dec := json.NewDecoder(request.Body)
dec.Decode(&(server.beforeSuiteData))
} else {
beforeSuiteData := server.beforeSuiteData
if beforeSuiteData.State == types.RemoteBeforeSuiteStatePending && !server.nodeIsAlive(1) {
beforeSuiteData.State = types.RemoteBeforeSuiteStateDisappeared
}
enc := json.NewEncoder(writer)
enc.Encode(beforeSuiteData)
}
}
func (server *Server) handleRemoteAfterSuiteData(writer http.ResponseWriter, request *http.Request) {
afterSuiteData := types.RemoteAfterSuiteData{
CanRun: true,
}
for i := 2; i <= server.parallelTotal; i++ {
afterSuiteData.CanRun = afterSuiteData.CanRun && !server.nodeIsAlive(i)
}
enc := json.NewEncoder(writer)
enc.Encode(afterSuiteData)
}
func (server *Server) handleCounter(writer http.ResponseWriter, request *http.Request) {
c := spec_iterator.Counter{}
server.lock.Lock()
c.Index = server.counter
server.counter = server.counter + 1
server.lock.Unlock()
json.NewEncoder(writer).Encode(c)
}
func (server *Server) handleHasCounter(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte(""))
}

View File

@@ -0,0 +1,11 @@
// +build linux,arm64
package remote
import "syscall"
// linux_arm64 doesn't have syscall.Dup2 which ginkgo uses, so
// use the nearly identical syscall.Dup3 instead
func syscallDup(oldfd int, newfd int) (err error) {
return syscall.Dup3(oldfd, newfd, 0)
}

View File

@@ -0,0 +1,9 @@
// +build solaris
package remote
import "golang.org/x/sys/unix"
func syscallDup(oldfd int, newfd int) (err error) {
return unix.Dup2(oldfd, newfd)
}

View File

@@ -0,0 +1,11 @@
// +build !linux !arm64
// +build !windows
// +build !solaris
package remote
import "syscall"
func syscallDup(oldfd int, newfd int) (err error) {
return syscall.Dup2(oldfd, newfd)
}

247
vendor/github.com/onsi/ginkgo/internal/spec/spec.go generated vendored Normal file
View File

@@ -0,0 +1,247 @@
package spec
import (
"fmt"
"io"
"time"
"sync"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
)
type Spec struct {
subject leafnodes.SubjectNode
focused bool
announceProgress bool
containers []*containernode.ContainerNode
state types.SpecState
runTime time.Duration
startTime time.Time
failure types.SpecFailure
previousFailures bool
stateMutex *sync.Mutex
}
func New(subject leafnodes.SubjectNode, containers []*containernode.ContainerNode, announceProgress bool) *Spec {
spec := &Spec{
subject: subject,
containers: containers,
focused: subject.Flag() == types.FlagTypeFocused,
announceProgress: announceProgress,
stateMutex: &sync.Mutex{},
}
spec.processFlag(subject.Flag())
for i := len(containers) - 1; i >= 0; i-- {
spec.processFlag(containers[i].Flag())
}
return spec
}
func (spec *Spec) processFlag(flag types.FlagType) {
if flag == types.FlagTypeFocused {
spec.focused = true
} else if flag == types.FlagTypePending {
spec.setState(types.SpecStatePending)
}
}
func (spec *Spec) Skip() {
spec.setState(types.SpecStateSkipped)
}
func (spec *Spec) Failed() bool {
return spec.getState() == types.SpecStateFailed || spec.getState() == types.SpecStatePanicked || spec.getState() == types.SpecStateTimedOut
}
func (spec *Spec) Passed() bool {
return spec.getState() == types.SpecStatePassed
}
func (spec *Spec) Flaked() bool {
return spec.getState() == types.SpecStatePassed && spec.previousFailures
}
func (spec *Spec) Pending() bool {
return spec.getState() == types.SpecStatePending
}
func (spec *Spec) Skipped() bool {
return spec.getState() == types.SpecStateSkipped
}
func (spec *Spec) Focused() bool {
return spec.focused
}
func (spec *Spec) IsMeasurement() bool {
return spec.subject.Type() == types.SpecComponentTypeMeasure
}
func (spec *Spec) Summary(suiteID string) *types.SpecSummary {
componentTexts := make([]string, len(spec.containers)+1)
componentCodeLocations := make([]types.CodeLocation, len(spec.containers)+1)
for i, container := range spec.containers {
componentTexts[i] = container.Text()
componentCodeLocations[i] = container.CodeLocation()
}
componentTexts[len(spec.containers)] = spec.subject.Text()
componentCodeLocations[len(spec.containers)] = spec.subject.CodeLocation()
runTime := spec.runTime
if runTime == 0 && !spec.startTime.IsZero() {
runTime = time.Since(spec.startTime)
}
return &types.SpecSummary{
IsMeasurement: spec.IsMeasurement(),
NumberOfSamples: spec.subject.Samples(),
ComponentTexts: componentTexts,
ComponentCodeLocations: componentCodeLocations,
State: spec.getState(),
RunTime: runTime,
Failure: spec.failure,
Measurements: spec.measurementsReport(),
SuiteID: suiteID,
}
}
func (spec *Spec) ConcatenatedString() string {
s := ""
for _, container := range spec.containers {
s += container.Text() + " "
}
return s + spec.subject.Text()
}
func (spec *Spec) Run(writer io.Writer) {
if spec.getState() == types.SpecStateFailed {
spec.previousFailures = true
}
spec.startTime = time.Now()
defer func() {
spec.runTime = time.Since(spec.startTime)
}()
for sample := 0; sample < spec.subject.Samples(); sample++ {
spec.runSample(sample, writer)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
func (spec *Spec) getState() types.SpecState {
spec.stateMutex.Lock()
defer spec.stateMutex.Unlock()
return spec.state
}
func (spec *Spec) setState(state types.SpecState) {
spec.stateMutex.Lock()
defer spec.stateMutex.Unlock()
spec.state = state
}
func (spec *Spec) runSample(sample int, writer io.Writer) {
spec.setState(types.SpecStatePassed)
spec.failure = types.SpecFailure{}
innerMostContainerIndexToUnwind := -1
defer func() {
for i := innerMostContainerIndexToUnwind; i >= 0; i-- {
container := spec.containers[i]
for _, justAfterEach := range container.SetupNodesOfType(types.SpecComponentTypeJustAfterEach) {
spec.announceSetupNode(writer, "JustAfterEach", container, justAfterEach)
justAfterEachState, justAfterEachFailure := justAfterEach.Run()
if justAfterEachState != types.SpecStatePassed && spec.state == types.SpecStatePassed {
spec.state = justAfterEachState
spec.failure = justAfterEachFailure
}
}
}
for i := innerMostContainerIndexToUnwind; i >= 0; i-- {
container := spec.containers[i]
for _, afterEach := range container.SetupNodesOfType(types.SpecComponentTypeAfterEach) {
spec.announceSetupNode(writer, "AfterEach", container, afterEach)
afterEachState, afterEachFailure := afterEach.Run()
if afterEachState != types.SpecStatePassed && spec.getState() == types.SpecStatePassed {
spec.setState(afterEachState)
spec.failure = afterEachFailure
}
}
}
}()
for i, container := range spec.containers {
innerMostContainerIndexToUnwind = i
for _, beforeEach := range container.SetupNodesOfType(types.SpecComponentTypeBeforeEach) {
spec.announceSetupNode(writer, "BeforeEach", container, beforeEach)
s, f := beforeEach.Run()
spec.failure = f
spec.setState(s)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
for _, container := range spec.containers {
for _, justBeforeEach := range container.SetupNodesOfType(types.SpecComponentTypeJustBeforeEach) {
spec.announceSetupNode(writer, "JustBeforeEach", container, justBeforeEach)
s, f := justBeforeEach.Run()
spec.failure = f
spec.setState(s)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
spec.announceSubject(writer, spec.subject)
s, f := spec.subject.Run()
spec.failure = f
spec.setState(s)
}
func (spec *Spec) announceSetupNode(writer io.Writer, nodeType string, container *containernode.ContainerNode, setupNode leafnodes.BasicNode) {
if spec.announceProgress {
s := fmt.Sprintf("[%s] %s\n %s\n", nodeType, container.Text(), setupNode.CodeLocation().String())
writer.Write([]byte(s))
}
}
func (spec *Spec) announceSubject(writer io.Writer, subject leafnodes.SubjectNode) {
if spec.announceProgress {
nodeType := ""
switch subject.Type() {
case types.SpecComponentTypeIt:
nodeType = "It"
case types.SpecComponentTypeMeasure:
nodeType = "Measure"
}
s := fmt.Sprintf("[%s] %s\n %s\n", nodeType, subject.Text(), subject.CodeLocation().String())
writer.Write([]byte(s))
}
}
func (spec *Spec) measurementsReport() map[string]*types.SpecMeasurement {
if !spec.IsMeasurement() || spec.Failed() {
return map[string]*types.SpecMeasurement{}
}
return spec.subject.(*leafnodes.MeasureNode).MeasurementsReport()
}

144
vendor/github.com/onsi/ginkgo/internal/spec/specs.go generated vendored Normal file
View File

@@ -0,0 +1,144 @@
package spec
import (
"math/rand"
"regexp"
"sort"
)
type Specs struct {
specs []*Spec
names []string
hasProgrammaticFocus bool
RegexScansFilePath bool
}
func NewSpecs(specs []*Spec) *Specs {
names := make([]string, len(specs))
for i, spec := range specs {
names[i] = spec.ConcatenatedString()
}
return &Specs{
specs: specs,
names: names,
}
}
func (e *Specs) Specs() []*Spec {
return e.specs
}
func (e *Specs) HasProgrammaticFocus() bool {
return e.hasProgrammaticFocus
}
func (e *Specs) Shuffle(r *rand.Rand) {
sort.Sort(e)
permutation := r.Perm(len(e.specs))
shuffledSpecs := make([]*Spec, len(e.specs))
names := make([]string, len(e.specs))
for i, j := range permutation {
shuffledSpecs[i] = e.specs[j]
names[i] = e.names[j]
}
e.specs = shuffledSpecs
e.names = names
}
func (e *Specs) ApplyFocus(description string, focusString string, skipString string) {
if focusString == "" && skipString == "" {
e.applyProgrammaticFocus()
} else {
e.applyRegExpFocusAndSkip(description, focusString, skipString)
}
}
func (e *Specs) applyProgrammaticFocus() {
e.hasProgrammaticFocus = false
for _, spec := range e.specs {
if spec.Focused() && !spec.Pending() {
e.hasProgrammaticFocus = true
break
}
}
if e.hasProgrammaticFocus {
for _, spec := range e.specs {
if !spec.Focused() {
spec.Skip()
}
}
}
}
// toMatch returns a byte[] to be used by regex matchers. When adding new behaviours to the matching function,
// this is the place which we append to.
func (e *Specs) toMatch(description string, i int) []byte {
if i > len(e.names) {
return nil
}
if e.RegexScansFilePath {
return []byte(
description + " " +
e.names[i] + " " +
e.specs[i].subject.CodeLocation().FileName)
} else {
return []byte(
description + " " +
e.names[i])
}
}
func (e *Specs) applyRegExpFocusAndSkip(description string, focusString string, skipString string) {
var focusFilter *regexp.Regexp
if focusString != "" {
focusFilter = regexp.MustCompile(focusString)
}
var skipFilter *regexp.Regexp
if skipString != "" {
skipFilter = regexp.MustCompile(skipString)
}
for i, spec := range e.specs {
matchesFocus := true
matchesSkip := false
toMatch := e.toMatch(description, i)
if focusFilter != nil {
matchesFocus = focusFilter.Match([]byte(toMatch))
}
if skipFilter != nil {
matchesSkip = skipFilter.Match([]byte(toMatch))
}
if !matchesFocus || matchesSkip {
spec.Skip()
}
}
}
func (e *Specs) SkipMeasurements() {
for _, spec := range e.specs {
if spec.IsMeasurement() {
spec.Skip()
}
}
}
//sort.Interface
func (e *Specs) Len() int {
return len(e.specs)
}
func (e *Specs) Less(i, j int) bool {
return e.names[i] < e.names[j]
}
func (e *Specs) Swap(i, j int) {
e.names[i], e.names[j] = e.names[j], e.names[i]
e.specs[i], e.specs[j] = e.specs[j], e.specs[i]
}

View File

@@ -0,0 +1,55 @@
package spec_iterator
func ParallelizedIndexRange(length int, parallelTotal int, parallelNode int) (startIndex int, count int) {
if length == 0 {
return 0, 0
}
// We have more nodes than tests. Trivial case.
if parallelTotal >= length {
if parallelNode > length {
return 0, 0
} else {
return parallelNode - 1, 1
}
}
// This is the minimum amount of tests that a node will be required to run
minTestsPerNode := length / parallelTotal
// This is the maximum amount of tests that a node will be required to run
// The algorithm guarantees that this would be equal to at least the minimum amount
// and at most one more
maxTestsPerNode := minTestsPerNode
if length%parallelTotal != 0 {
maxTestsPerNode++
}
// Number of nodes that will have to run the maximum amount of tests per node
numMaxLoadNodes := length % parallelTotal
// Number of nodes that precede the current node and will have to run the maximum amount of tests per node
var numPrecedingMaxLoadNodes int
if parallelNode > numMaxLoadNodes {
numPrecedingMaxLoadNodes = numMaxLoadNodes
} else {
numPrecedingMaxLoadNodes = parallelNode - 1
}
// Number of nodes that precede the current node and will have to run the minimum amount of tests per node
var numPrecedingMinLoadNodes int
if parallelNode <= numMaxLoadNodes {
numPrecedingMinLoadNodes = 0
} else {
numPrecedingMinLoadNodes = parallelNode - numMaxLoadNodes - 1
}
// Evaluate the test start index and number of tests to run
startIndex = numPrecedingMaxLoadNodes*maxTestsPerNode + numPrecedingMinLoadNodes*minTestsPerNode
if parallelNode > numMaxLoadNodes {
count = minTestsPerNode
} else {
count = maxTestsPerNode
}
return
}

View File

@@ -0,0 +1,59 @@
package spec_iterator
import (
"encoding/json"
"fmt"
"net/http"
"github.com/onsi/ginkgo/internal/spec"
)
type ParallelIterator struct {
specs []*spec.Spec
host string
client *http.Client
}
func NewParallelIterator(specs []*spec.Spec, host string) *ParallelIterator {
return &ParallelIterator{
specs: specs,
host: host,
client: &http.Client{},
}
}
func (s *ParallelIterator) Next() (*spec.Spec, error) {
resp, err := s.client.Get(s.host + "/counter")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
var counter Counter
err = json.NewDecoder(resp.Body).Decode(&counter)
if err != nil {
return nil, err
}
if counter.Index >= len(s.specs) {
return nil, ErrClosed
}
return s.specs[counter.Index], nil
}
func (s *ParallelIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *ParallelIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return -1, false
}
func (s *ParallelIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
return -1, false
}

View File

@@ -0,0 +1,45 @@
package spec_iterator
import (
"github.com/onsi/ginkgo/internal/spec"
)
type SerialIterator struct {
specs []*spec.Spec
index int
}
func NewSerialIterator(specs []*spec.Spec) *SerialIterator {
return &SerialIterator{
specs: specs,
index: 0,
}
}
func (s *SerialIterator) Next() (*spec.Spec, error) {
if s.index >= len(s.specs) {
return nil, ErrClosed
}
spec := s.specs[s.index]
s.index += 1
return spec, nil
}
func (s *SerialIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *SerialIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return len(s.specs), true
}
func (s *SerialIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
count := 0
for _, s := range s.specs {
if !s.Skipped() && !s.Pending() {
count += 1
}
}
return count, true
}

View File

@@ -0,0 +1,47 @@
package spec_iterator
import "github.com/onsi/ginkgo/internal/spec"
type ShardedParallelIterator struct {
specs []*spec.Spec
index int
maxIndex int
}
func NewShardedParallelIterator(specs []*spec.Spec, total int, node int) *ShardedParallelIterator {
startIndex, count := ParallelizedIndexRange(len(specs), total, node)
return &ShardedParallelIterator{
specs: specs,
index: startIndex,
maxIndex: startIndex + count,
}
}
func (s *ShardedParallelIterator) Next() (*spec.Spec, error) {
if s.index >= s.maxIndex {
return nil, ErrClosed
}
spec := s.specs[s.index]
s.index += 1
return spec, nil
}
func (s *ShardedParallelIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *ShardedParallelIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return s.maxIndex - s.index, true
}
func (s *ShardedParallelIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
count := 0
for i := s.index; i < s.maxIndex; i += 1 {
if !s.specs[i].Skipped() && !s.specs[i].Pending() {
count += 1
}
}
return count, true
}

View File

@@ -0,0 +1,20 @@
package spec_iterator
import (
"errors"
"github.com/onsi/ginkgo/internal/spec"
)
var ErrClosed = errors.New("no more specs to run")
type SpecIterator interface {
Next() (*spec.Spec, error)
NumberOfSpecsPriorToIteration() int
NumberOfSpecsToProcessIfKnown() (int, bool)
NumberOfSpecsThatWillBeRunIfKnown() (int, bool)
}
type Counter struct {
Index int `json:"index"`
}

View File

@@ -0,0 +1,15 @@
package specrunner
import (
"crypto/rand"
"fmt"
)
func randomID() string {
b := make([]byte, 8)
_, err := rand.Read(b)
if err != nil {
return ""
}
return fmt.Sprintf("%x-%x-%x-%x", b[0:2], b[2:4], b[4:6], b[6:8])
}

View File

@@ -0,0 +1,411 @@
package specrunner
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
Writer "github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
"time"
)
type SpecRunner struct {
description string
beforeSuiteNode leafnodes.SuiteNode
iterator spec_iterator.SpecIterator
afterSuiteNode leafnodes.SuiteNode
reporters []reporters.Reporter
startTime time.Time
suiteID string
runningSpec *spec.Spec
writer Writer.WriterInterface
config config.GinkgoConfigType
interrupted bool
processedSpecs []*spec.Spec
lock *sync.Mutex
}
func New(description string, beforeSuiteNode leafnodes.SuiteNode, iterator spec_iterator.SpecIterator, afterSuiteNode leafnodes.SuiteNode, reporters []reporters.Reporter, writer Writer.WriterInterface, config config.GinkgoConfigType) *SpecRunner {
return &SpecRunner{
description: description,
beforeSuiteNode: beforeSuiteNode,
iterator: iterator,
afterSuiteNode: afterSuiteNode,
reporters: reporters,
writer: writer,
config: config,
suiteID: randomID(),
lock: &sync.Mutex{},
}
}
func (runner *SpecRunner) Run() bool {
if runner.config.DryRun {
runner.performDryRun()
return true
}
runner.reportSuiteWillBegin()
signalRegistered := make(chan struct{})
go runner.registerForInterrupts(signalRegistered)
<-signalRegistered
suitePassed := runner.runBeforeSuite()
if suitePassed {
suitePassed = runner.runSpecs()
}
runner.blockForeverIfInterrupted()
suitePassed = runner.runAfterSuite() && suitePassed
runner.reportSuiteDidEnd(suitePassed)
return suitePassed
}
func (runner *SpecRunner) performDryRun() {
runner.reportSuiteWillBegin()
if runner.beforeSuiteNode != nil {
summary := runner.beforeSuiteNode.Summary()
summary.State = types.SpecStatePassed
runner.reportBeforeSuite(summary)
}
for {
spec, err := runner.iterator.Next()
if err == spec_iterator.ErrClosed {
break
}
if err != nil {
fmt.Println("failed to iterate over tests:\n" + err.Error())
break
}
runner.processedSpecs = append(runner.processedSpecs, spec)
summary := spec.Summary(runner.suiteID)
runner.reportSpecWillRun(summary)
if summary.State == types.SpecStateInvalid {
summary.State = types.SpecStatePassed
}
runner.reportSpecDidComplete(summary, false)
}
if runner.afterSuiteNode != nil {
summary := runner.afterSuiteNode.Summary()
summary.State = types.SpecStatePassed
runner.reportAfterSuite(summary)
}
runner.reportSuiteDidEnd(true)
}
func (runner *SpecRunner) runBeforeSuite() bool {
if runner.beforeSuiteNode == nil || runner.wasInterrupted() {
return true
}
runner.writer.Truncate()
conf := runner.config
passed := runner.beforeSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
if !passed {
runner.writer.DumpOut()
}
runner.reportBeforeSuite(runner.beforeSuiteNode.Summary())
return passed
}
func (runner *SpecRunner) runAfterSuite() bool {
if runner.afterSuiteNode == nil {
return true
}
runner.writer.Truncate()
conf := runner.config
passed := runner.afterSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
if !passed {
runner.writer.DumpOut()
}
runner.reportAfterSuite(runner.afterSuiteNode.Summary())
return passed
}
func (runner *SpecRunner) runSpecs() bool {
suiteFailed := false
skipRemainingSpecs := false
for {
spec, err := runner.iterator.Next()
if err == spec_iterator.ErrClosed {
break
}
if err != nil {
fmt.Println("failed to iterate over tests:\n" + err.Error())
suiteFailed = true
break
}
runner.processedSpecs = append(runner.processedSpecs, spec)
if runner.wasInterrupted() {
break
}
if skipRemainingSpecs {
spec.Skip()
}
if !spec.Skipped() && !spec.Pending() {
if passed := runner.runSpec(spec); !passed {
suiteFailed = true
}
} else if spec.Pending() && runner.config.FailOnPending {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
suiteFailed = true
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
} else {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
}
if spec.Failed() && runner.config.FailFast {
skipRemainingSpecs = true
}
}
return !suiteFailed
}
func (runner *SpecRunner) runSpec(spec *spec.Spec) (passed bool) {
maxAttempts := 1
if runner.config.FlakeAttempts > 0 {
// uninitialized configs count as 1
maxAttempts = runner.config.FlakeAttempts
}
for i := 0; i < maxAttempts; i++ {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
runner.runningSpec = spec
spec.Run(runner.writer)
runner.runningSpec = nil
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
if !spec.Failed() {
return true
}
}
return false
}
func (runner *SpecRunner) CurrentSpecSummary() (*types.SpecSummary, bool) {
if runner.runningSpec == nil {
return nil, false
}
return runner.runningSpec.Summary(runner.suiteID), true
}
func (runner *SpecRunner) registerForInterrupts(signalRegistered chan struct{}) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
close(signalRegistered)
<-c
signal.Stop(c)
runner.markInterrupted()
go runner.registerForHardInterrupts()
runner.writer.DumpOutWithHeader(`
Received interrupt. Emitting contents of GinkgoWriter...
---------------------------------------------------------
`)
if runner.afterSuiteNode != nil {
fmt.Fprint(os.Stderr, `
---------------------------------------------------------
Received interrupt. Running AfterSuite...
^C again to terminate immediately
`)
runner.runAfterSuite()
}
runner.reportSuiteDidEnd(false)
os.Exit(1)
}
func (runner *SpecRunner) registerForHardInterrupts() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
fmt.Fprintln(os.Stderr, "\nReceived second interrupt. Shutting down.")
os.Exit(1)
}
func (runner *SpecRunner) blockForeverIfInterrupted() {
runner.lock.Lock()
interrupted := runner.interrupted
runner.lock.Unlock()
if interrupted {
select {}
}
}
func (runner *SpecRunner) markInterrupted() {
runner.lock.Lock()
defer runner.lock.Unlock()
runner.interrupted = true
}
func (runner *SpecRunner) wasInterrupted() bool {
runner.lock.Lock()
defer runner.lock.Unlock()
return runner.interrupted
}
func (runner *SpecRunner) reportSuiteWillBegin() {
runner.startTime = time.Now()
summary := runner.suiteWillBeginSummary()
for _, reporter := range runner.reporters {
reporter.SpecSuiteWillBegin(runner.config, summary)
}
}
func (runner *SpecRunner) reportBeforeSuite(summary *types.SetupSummary) {
for _, reporter := range runner.reporters {
reporter.BeforeSuiteDidRun(summary)
}
}
func (runner *SpecRunner) reportAfterSuite(summary *types.SetupSummary) {
for _, reporter := range runner.reporters {
reporter.AfterSuiteDidRun(summary)
}
}
func (runner *SpecRunner) reportSpecWillRun(summary *types.SpecSummary) {
runner.writer.Truncate()
for _, reporter := range runner.reporters {
reporter.SpecWillRun(summary)
}
}
func (runner *SpecRunner) reportSpecDidComplete(summary *types.SpecSummary, failed bool) {
if failed && len(summary.CapturedOutput) == 0 {
summary.CapturedOutput = string(runner.writer.Bytes())
}
for i := len(runner.reporters) - 1; i >= 1; i-- {
runner.reporters[i].SpecDidComplete(summary)
}
if failed {
runner.writer.DumpOut()
}
runner.reporters[0].SpecDidComplete(summary)
}
func (runner *SpecRunner) reportSuiteDidEnd(success bool) {
summary := runner.suiteDidEndSummary(success)
summary.RunTime = time.Since(runner.startTime)
for _, reporter := range runner.reporters {
reporter.SpecSuiteDidEnd(summary)
}
}
func (runner *SpecRunner) countSpecsThatRanSatisfying(filter func(ex *spec.Spec) bool) (count int) {
count = 0
for _, spec := range runner.processedSpecs {
if filter(spec) {
count++
}
}
return count
}
func (runner *SpecRunner) suiteDidEndSummary(success bool) *types.SuiteSummary {
numberOfSpecsThatWillBeRun := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return !ex.Skipped() && !ex.Pending()
})
numberOfPendingSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Pending()
})
numberOfSkippedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Skipped()
})
numberOfPassedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Passed()
})
numberOfFlakedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Flaked()
})
numberOfFailedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Failed()
})
if runner.beforeSuiteNode != nil && !runner.beforeSuiteNode.Passed() && !runner.config.DryRun {
var known bool
numberOfSpecsThatWillBeRun, known = runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
if !known {
numberOfSpecsThatWillBeRun = runner.iterator.NumberOfSpecsPriorToIteration()
}
numberOfFailedSpecs = numberOfSpecsThatWillBeRun
}
return &types.SuiteSummary{
SuiteDescription: runner.description,
SuiteSucceeded: success,
SuiteID: runner.suiteID,
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
NumberOfTotalSpecs: len(runner.processedSpecs),
NumberOfSpecsThatWillBeRun: numberOfSpecsThatWillBeRun,
NumberOfPendingSpecs: numberOfPendingSpecs,
NumberOfSkippedSpecs: numberOfSkippedSpecs,
NumberOfPassedSpecs: numberOfPassedSpecs,
NumberOfFailedSpecs: numberOfFailedSpecs,
NumberOfFlakedSpecs: numberOfFlakedSpecs,
}
}
func (runner *SpecRunner) suiteWillBeginSummary() *types.SuiteSummary {
numTotal, known := runner.iterator.NumberOfSpecsToProcessIfKnown()
if !known {
numTotal = -1
}
numToRun, known := runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
if !known {
numToRun = -1
}
return &types.SuiteSummary{
SuiteDescription: runner.description,
SuiteID: runner.suiteID,
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
NumberOfTotalSpecs: numTotal,
NumberOfSpecsThatWillBeRun: numToRun,
NumberOfPendingSpecs: -1,
NumberOfSkippedSpecs: -1,
NumberOfPassedSpecs: -1,
NumberOfFailedSpecs: -1,
NumberOfFlakedSpecs: -1,
}
}

190
vendor/github.com/onsi/ginkgo/internal/suite/suite.go generated vendored Normal file
View File

@@ -0,0 +1,190 @@
package suite
import (
"math/rand"
"net/http"
"time"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/internal/specrunner"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
)
type ginkgoTestingT interface {
Fail()
}
type Suite struct {
topLevelContainer *containernode.ContainerNode
currentContainer *containernode.ContainerNode
containerIndex int
beforeSuiteNode leafnodes.SuiteNode
afterSuiteNode leafnodes.SuiteNode
runner *specrunner.SpecRunner
failer *failer.Failer
running bool
}
func New(failer *failer.Failer) *Suite {
topLevelContainer := containernode.New("[Top Level]", types.FlagTypeNone, types.CodeLocation{})
return &Suite{
topLevelContainer: topLevelContainer,
currentContainer: topLevelContainer,
failer: failer,
containerIndex: 1,
}
}
func (suite *Suite) Run(t ginkgoTestingT, description string, reporters []reporters.Reporter, writer writer.WriterInterface, config config.GinkgoConfigType) (bool, bool) {
if config.ParallelTotal < 1 {
panic("ginkgo.parallel.total must be >= 1")
}
if config.ParallelNode > config.ParallelTotal || config.ParallelNode < 1 {
panic("ginkgo.parallel.node is one-indexed and must be <= ginkgo.parallel.total")
}
r := rand.New(rand.NewSource(config.RandomSeed))
suite.topLevelContainer.Shuffle(r)
iterator, hasProgrammaticFocus := suite.generateSpecsIterator(description, config)
suite.runner = specrunner.New(description, suite.beforeSuiteNode, iterator, suite.afterSuiteNode, reporters, writer, config)
suite.running = true
success := suite.runner.Run()
if !success {
t.Fail()
}
return success, hasProgrammaticFocus
}
func (suite *Suite) generateSpecsIterator(description string, config config.GinkgoConfigType) (spec_iterator.SpecIterator, bool) {
specsSlice := []*spec.Spec{}
suite.topLevelContainer.BackPropagateProgrammaticFocus()
for _, collatedNodes := range suite.topLevelContainer.Collate() {
specsSlice = append(specsSlice, spec.New(collatedNodes.Subject, collatedNodes.Containers, config.EmitSpecProgress))
}
specs := spec.NewSpecs(specsSlice)
specs.RegexScansFilePath = config.RegexScansFilePath
if config.RandomizeAllSpecs {
specs.Shuffle(rand.New(rand.NewSource(config.RandomSeed)))
}
specs.ApplyFocus(description, config.FocusString, config.SkipString)
if config.SkipMeasurements {
specs.SkipMeasurements()
}
var iterator spec_iterator.SpecIterator
if config.ParallelTotal > 1 {
iterator = spec_iterator.NewParallelIterator(specs.Specs(), config.SyncHost)
resp, err := http.Get(config.SyncHost + "/has-counter")
if err != nil || resp.StatusCode != http.StatusOK {
iterator = spec_iterator.NewShardedParallelIterator(specs.Specs(), config.ParallelTotal, config.ParallelNode)
}
} else {
iterator = spec_iterator.NewSerialIterator(specs.Specs())
}
return iterator, specs.HasProgrammaticFocus()
}
func (suite *Suite) CurrentRunningSpecSummary() (*types.SpecSummary, bool) {
return suite.runner.CurrentSpecSummary()
}
func (suite *Suite) SetBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.beforeSuiteNode != nil {
panic("You may only call BeforeSuite once!")
}
suite.beforeSuiteNode = leafnodes.NewBeforeSuiteNode(body, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.afterSuiteNode != nil {
panic("You may only call AfterSuite once!")
}
suite.afterSuiteNode = leafnodes.NewAfterSuiteNode(body, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.beforeSuiteNode != nil {
panic("You may only call BeforeSuite once!")
}
suite.beforeSuiteNode = leafnodes.NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.afterSuiteNode != nil {
panic("You may only call AfterSuite once!")
}
suite.afterSuiteNode = leafnodes.NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
}
func (suite *Suite) PushContainerNode(text string, body func(), flag types.FlagType, codeLocation types.CodeLocation) {
container := containernode.New(text, flag, codeLocation)
suite.currentContainer.PushContainerNode(container)
previousContainer := suite.currentContainer
suite.currentContainer = container
suite.containerIndex++
body()
suite.containerIndex--
suite.currentContainer = previousContainer
}
func (suite *Suite) PushItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call It from within a Describe, Context or When", codeLocation)
}
suite.currentContainer.PushSubjectNode(leafnodes.NewItNode(text, body, flag, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int) {
if suite.running {
suite.failer.Fail("You may only call Measure from within a Describe, Context or When", codeLocation)
}
suite.currentContainer.PushSubjectNode(leafnodes.NewMeasureNode(text, body, flag, codeLocation, samples, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call BeforeEach from within a Describe, Context or When", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call JustBeforeEach from within a Describe, Context or When", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewJustBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call JustAfterEach from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewJustAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call AfterEach from within a Describe, Context or When", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}

View File

@@ -0,0 +1,76 @@
package testingtproxy
import (
"fmt"
"io"
)
type failFunc func(message string, callerSkip ...int)
func New(writer io.Writer, fail failFunc, offset int) *ginkgoTestingTProxy {
return &ginkgoTestingTProxy{
fail: fail,
offset: offset,
writer: writer,
}
}
type ginkgoTestingTProxy struct {
fail failFunc
offset int
writer io.Writer
}
func (t *ginkgoTestingTProxy) Error(args ...interface{}) {
t.fail(fmt.Sprintln(args...), t.offset)
}
func (t *ginkgoTestingTProxy) Errorf(format string, args ...interface{}) {
t.fail(fmt.Sprintf(format, args...), t.offset)
}
func (t *ginkgoTestingTProxy) Fail() {
t.fail("failed", t.offset)
}
func (t *ginkgoTestingTProxy) FailNow() {
t.fail("failed", t.offset)
}
func (t *ginkgoTestingTProxy) Fatal(args ...interface{}) {
t.fail(fmt.Sprintln(args...), t.offset)
}
func (t *ginkgoTestingTProxy) Fatalf(format string, args ...interface{}) {
t.fail(fmt.Sprintf(format, args...), t.offset)
}
func (t *ginkgoTestingTProxy) Log(args ...interface{}) {
fmt.Fprintln(t.writer, args...)
}
func (t *ginkgoTestingTProxy) Logf(format string, args ...interface{}) {
t.Log(fmt.Sprintf(format, args...))
}
func (t *ginkgoTestingTProxy) Failed() bool {
return false
}
func (t *ginkgoTestingTProxy) Parallel() {
}
func (t *ginkgoTestingTProxy) Skip(args ...interface{}) {
fmt.Println(args...)
}
func (t *ginkgoTestingTProxy) Skipf(format string, args ...interface{}) {
t.Skip(fmt.Sprintf(format, args...))
}
func (t *ginkgoTestingTProxy) SkipNow() {
}
func (t *ginkgoTestingTProxy) Skipped() bool {
return false
}

View File

@@ -0,0 +1,36 @@
package writer
type FakeGinkgoWriter struct {
EventStream []string
}
func NewFake() *FakeGinkgoWriter {
return &FakeGinkgoWriter{
EventStream: []string{},
}
}
func (writer *FakeGinkgoWriter) AddEvent(event string) {
writer.EventStream = append(writer.EventStream, event)
}
func (writer *FakeGinkgoWriter) Truncate() {
writer.EventStream = append(writer.EventStream, "TRUNCATE")
}
func (writer *FakeGinkgoWriter) DumpOut() {
writer.EventStream = append(writer.EventStream, "DUMP")
}
func (writer *FakeGinkgoWriter) DumpOutWithHeader(header string) {
writer.EventStream = append(writer.EventStream, "DUMP_WITH_HEADER: "+header)
}
func (writer *FakeGinkgoWriter) Bytes() []byte {
writer.EventStream = append(writer.EventStream, "BYTES")
return nil
}
func (writer *FakeGinkgoWriter) Write(data []byte) (n int, err error) {
return 0, nil
}

View File

@@ -0,0 +1,89 @@
package writer
import (
"bytes"
"io"
"sync"
)
type WriterInterface interface {
io.Writer
Truncate()
DumpOut()
DumpOutWithHeader(header string)
Bytes() []byte
}
type Writer struct {
buffer *bytes.Buffer
outWriter io.Writer
lock *sync.Mutex
stream bool
redirector io.Writer
}
func New(outWriter io.Writer) *Writer {
return &Writer{
buffer: &bytes.Buffer{},
lock: &sync.Mutex{},
outWriter: outWriter,
stream: true,
}
}
func (w *Writer) AndRedirectTo(writer io.Writer) {
w.redirector = writer
}
func (w *Writer) SetStream(stream bool) {
w.lock.Lock()
defer w.lock.Unlock()
w.stream = stream
}
func (w *Writer) Write(b []byte) (n int, err error) {
w.lock.Lock()
defer w.lock.Unlock()
n, err = w.buffer.Write(b)
if w.redirector != nil {
w.redirector.Write(b)
}
if w.stream {
return w.outWriter.Write(b)
}
return n, err
}
func (w *Writer) Truncate() {
w.lock.Lock()
defer w.lock.Unlock()
w.buffer.Reset()
}
func (w *Writer) DumpOut() {
w.lock.Lock()
defer w.lock.Unlock()
if !w.stream {
w.buffer.WriteTo(w.outWriter)
}
}
func (w *Writer) Bytes() []byte {
w.lock.Lock()
defer w.lock.Unlock()
b := w.buffer.Bytes()
copied := make([]byte, len(b))
copy(copied, b)
return copied
}
func (w *Writer) DumpOutWithHeader(header string) {
w.lock.Lock()
defer w.lock.Unlock()
if !w.stream && w.buffer.Len() > 0 {
w.outWriter.Write([]byte(header))
w.buffer.WriteTo(w.outWriter)
}
}

View File

@@ -0,0 +1,84 @@
/*
Ginkgo's Default Reporter
A number of command line flags are available to tweak Ginkgo's default output.
These are documented [here](http://onsi.github.io/ginkgo/#running_tests)
*/
package reporters
import (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
type DefaultReporter struct {
config config.DefaultReporterConfigType
stenographer stenographer.Stenographer
specSummaries []*types.SpecSummary
}
func NewDefaultReporter(config config.DefaultReporterConfigType, stenographer stenographer.Stenographer) *DefaultReporter {
return &DefaultReporter{
config: config,
stenographer: stenographer,
}
}
func (reporter *DefaultReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
reporter.stenographer.AnnounceSuite(summary.SuiteDescription, config.RandomSeed, config.RandomizeAllSpecs, reporter.config.Succinct)
if config.ParallelTotal > 1 {
reporter.stenographer.AnnounceParallelRun(config.ParallelNode, config.ParallelTotal, reporter.config.Succinct)
} else {
reporter.stenographer.AnnounceNumberOfSpecs(summary.NumberOfSpecsThatWillBeRun, summary.NumberOfTotalSpecs, reporter.config.Succinct)
}
}
func (reporter *DefaultReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
reporter.stenographer.AnnounceBeforeSuiteFailure(setupSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
}
func (reporter *DefaultReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
reporter.stenographer.AnnounceAfterSuiteFailure(setupSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
}
func (reporter *DefaultReporter) SpecWillRun(specSummary *types.SpecSummary) {
if reporter.config.Verbose && !reporter.config.Succinct && specSummary.State != types.SpecStatePending && specSummary.State != types.SpecStateSkipped {
reporter.stenographer.AnnounceSpecWillRun(specSummary)
}
}
func (reporter *DefaultReporter) SpecDidComplete(specSummary *types.SpecSummary) {
switch specSummary.State {
case types.SpecStatePassed:
if specSummary.IsMeasurement {
reporter.stenographer.AnnounceSuccesfulMeasurement(specSummary, reporter.config.Succinct)
} else if specSummary.RunTime.Seconds() >= reporter.config.SlowSpecThreshold {
reporter.stenographer.AnnounceSuccesfulSlowSpec(specSummary, reporter.config.Succinct)
} else {
reporter.stenographer.AnnounceSuccesfulSpec(specSummary)
}
case types.SpecStatePending:
reporter.stenographer.AnnouncePendingSpec(specSummary, reporter.config.NoisyPendings && !reporter.config.Succinct)
case types.SpecStateSkipped:
reporter.stenographer.AnnounceSkippedSpec(specSummary, reporter.config.Succinct || !reporter.config.NoisySkippings, reporter.config.FullTrace)
case types.SpecStateTimedOut:
reporter.stenographer.AnnounceSpecTimedOut(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
case types.SpecStatePanicked:
reporter.stenographer.AnnounceSpecPanicked(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
case types.SpecStateFailed:
reporter.stenographer.AnnounceSpecFailed(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
reporter.specSummaries = append(reporter.specSummaries, specSummary)
}
func (reporter *DefaultReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.stenographer.SummarizeFailures(reporter.specSummaries)
reporter.stenographer.AnnounceSpecRunCompletion(summary, reporter.config.Succinct)
}

View File

@@ -0,0 +1,59 @@
package reporters
import (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
//FakeReporter is useful for testing purposes
type FakeReporter struct {
Config config.GinkgoConfigType
BeginSummary *types.SuiteSummary
BeforeSuiteSummary *types.SetupSummary
SpecWillRunSummaries []*types.SpecSummary
SpecSummaries []*types.SpecSummary
AfterSuiteSummary *types.SetupSummary
EndSummary *types.SuiteSummary
SpecWillRunStub func(specSummary *types.SpecSummary)
SpecDidCompleteStub func(specSummary *types.SpecSummary)
}
func NewFakeReporter() *FakeReporter {
return &FakeReporter{
SpecWillRunSummaries: make([]*types.SpecSummary, 0),
SpecSummaries: make([]*types.SpecSummary, 0),
}
}
func (fakeR *FakeReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
fakeR.Config = config
fakeR.BeginSummary = summary
}
func (fakeR *FakeReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
fakeR.BeforeSuiteSummary = setupSummary
}
func (fakeR *FakeReporter) SpecWillRun(specSummary *types.SpecSummary) {
if fakeR.SpecWillRunStub != nil {
fakeR.SpecWillRunStub(specSummary)
}
fakeR.SpecWillRunSummaries = append(fakeR.SpecWillRunSummaries, specSummary)
}
func (fakeR *FakeReporter) SpecDidComplete(specSummary *types.SpecSummary) {
if fakeR.SpecDidCompleteStub != nil {
fakeR.SpecDidCompleteStub(specSummary)
}
fakeR.SpecSummaries = append(fakeR.SpecSummaries, specSummary)
}
func (fakeR *FakeReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
fakeR.AfterSuiteSummary = setupSummary
}
func (fakeR *FakeReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
fakeR.EndSummary = summary
}

View File

@@ -0,0 +1,152 @@
/*
JUnit XML Reporter for Ginkgo
For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output
*/
package reporters
import (
"encoding/xml"
"fmt"
"math"
"os"
"strings"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
TestCases []JUnitTestCase `xml:"testcase"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Errors int `xml:"errors,attr"`
Time float64 `xml:"time,attr"`
}
type JUnitTestCase struct {
Name string `xml:"name,attr"`
ClassName string `xml:"classname,attr"`
FailureMessage *JUnitFailureMessage `xml:"failure,omitempty"`
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
Time float64 `xml:"time,attr"`
SystemOut string `xml:"system-out,omitempty"`
}
type JUnitFailureMessage struct {
Type string `xml:"type,attr"`
Message string `xml:",chardata"`
}
type JUnitSkipped struct {
XMLName xml.Name `xml:"skipped"`
}
type JUnitReporter struct {
suite JUnitTestSuite
filename string
testSuiteName string
}
//NewJUnitReporter creates a new JUnit XML reporter. The XML will be stored in the passed in filename.
func NewJUnitReporter(filename string) *JUnitReporter {
return &JUnitReporter{
filename: filename,
}
}
func (reporter *JUnitReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
reporter.suite = JUnitTestSuite{
Name: summary.SuiteDescription,
TestCases: []JUnitTestCase{},
}
reporter.testSuiteName = summary.SuiteDescription
}
func (reporter *JUnitReporter) SpecWillRun(specSummary *types.SpecSummary) {
}
func (reporter *JUnitReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("BeforeSuite", setupSummary)
}
func (reporter *JUnitReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("AfterSuite", setupSummary)
}
func failureMessage(failure types.SpecFailure) string {
return fmt.Sprintf("%s\n%s\n%s", failure.ComponentCodeLocation.String(), failure.Message, failure.Location.String())
}
func (reporter *JUnitReporter) handleSetupSummary(name string, setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
testCase := JUnitTestCase{
Name: name,
ClassName: reporter.testSuiteName,
}
testCase.FailureMessage = &JUnitFailureMessage{
Type: reporter.failureTypeForState(setupSummary.State),
Message: failureMessage(setupSummary.Failure),
}
testCase.SystemOut = setupSummary.CapturedOutput
testCase.Time = setupSummary.RunTime.Seconds()
reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
}
}
func (reporter *JUnitReporter) SpecDidComplete(specSummary *types.SpecSummary) {
testCase := JUnitTestCase{
Name: strings.Join(specSummary.ComponentTexts[1:], " "),
ClassName: reporter.testSuiteName,
}
if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked {
testCase.FailureMessage = &JUnitFailureMessage{
Type: reporter.failureTypeForState(specSummary.State),
Message: failureMessage(specSummary.Failure),
}
testCase.SystemOut = specSummary.CapturedOutput
}
if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending {
testCase.Skipped = &JUnitSkipped{}
}
testCase.Time = specSummary.RunTime.Seconds()
reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
}
func (reporter *JUnitReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.suite.Tests = summary.NumberOfSpecsThatWillBeRun
reporter.suite.Time = math.Trunc(summary.RunTime.Seconds()*1000) / 1000
reporter.suite.Failures = summary.NumberOfFailedSpecs
reporter.suite.Errors = 0
file, err := os.Create(reporter.filename)
if err != nil {
fmt.Printf("Failed to create JUnit report file: %s\n\t%s", reporter.filename, err.Error())
}
defer file.Close()
file.WriteString(xml.Header)
encoder := xml.NewEncoder(file)
encoder.Indent(" ", " ")
err = encoder.Encode(reporter.suite)
if err != nil {
fmt.Printf("Failed to generate JUnit report\n\t%s", err.Error())
}
}
func (reporter *JUnitReporter) failureTypeForState(state types.SpecState) string {
switch state {
case types.SpecStateFailed:
return "Failure"
case types.SpecStateTimedOut:
return "Timeout"
case types.SpecStatePanicked:
return "Panic"
default:
return ""
}
}

15
vendor/github.com/onsi/ginkgo/reporters/reporter.go generated vendored Normal file
View File

@@ -0,0 +1,15 @@
package reporters
import (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
type Reporter interface {
SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary)
BeforeSuiteDidRun(setupSummary *types.SetupSummary)
SpecWillRun(specSummary *types.SpecSummary)
SpecDidComplete(specSummary *types.SpecSummary)
AfterSuiteDidRun(setupSummary *types.SetupSummary)
SpecSuiteDidEnd(summary *types.SuiteSummary)
}

View File

@@ -0,0 +1,64 @@
package stenographer
import (
"fmt"
"strings"
)
func (s *consoleStenographer) colorize(colorCode string, format string, args ...interface{}) string {
var out string
if len(args) > 0 {
out = fmt.Sprintf(format, args...)
} else {
out = format
}
if s.color {
return fmt.Sprintf("%s%s%s", colorCode, out, defaultStyle)
} else {
return out
}
}
func (s *consoleStenographer) printBanner(text string, bannerCharacter string) {
fmt.Fprintln(s.w, text)
fmt.Fprintln(s.w, strings.Repeat(bannerCharacter, len(text)))
}
func (s *consoleStenographer) printNewLine() {
fmt.Fprintln(s.w, "")
}
func (s *consoleStenographer) printDelimiter() {
fmt.Fprintln(s.w, s.colorize(grayColor, "%s", strings.Repeat("-", 30)))
}
func (s *consoleStenographer) print(indentation int, format string, args ...interface{}) {
fmt.Fprint(s.w, s.indent(indentation, format, args...))
}
func (s *consoleStenographer) println(indentation int, format string, args ...interface{}) {
fmt.Fprintln(s.w, s.indent(indentation, format, args...))
}
func (s *consoleStenographer) indent(indentation int, format string, args ...interface{}) string {
var text string
if len(args) > 0 {
text = fmt.Sprintf(format, args...)
} else {
text = format
}
stringArray := strings.Split(text, "\n")
padding := ""
if indentation >= 0 {
padding = strings.Repeat(" ", indentation)
}
for i, s := range stringArray {
stringArray[i] = fmt.Sprintf("%s%s", padding, s)
}
return strings.Join(stringArray, "\n")
}

View File

@@ -0,0 +1,142 @@
package stenographer
import (
"sync"
"github.com/onsi/ginkgo/types"
)
func NewFakeStenographerCall(method string, args ...interface{}) FakeStenographerCall {
return FakeStenographerCall{
Method: method,
Args: args,
}
}
type FakeStenographer struct {
calls []FakeStenographerCall
lock *sync.Mutex
}
type FakeStenographerCall struct {
Method string
Args []interface{}
}
func NewFakeStenographer() *FakeStenographer {
stenographer := &FakeStenographer{
lock: &sync.Mutex{},
}
stenographer.Reset()
return stenographer
}
func (stenographer *FakeStenographer) Calls() []FakeStenographerCall {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
return stenographer.calls
}
func (stenographer *FakeStenographer) Reset() {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
stenographer.calls = make([]FakeStenographerCall, 0)
}
func (stenographer *FakeStenographer) CallsTo(method string) []FakeStenographerCall {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
results := make([]FakeStenographerCall, 0)
for _, call := range stenographer.calls {
if call.Method == method {
results = append(results, call)
}
}
return results
}
func (stenographer *FakeStenographer) registerCall(method string, args ...interface{}) {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
stenographer.calls = append(stenographer.calls, NewFakeStenographerCall(method, args...))
}
func (stenographer *FakeStenographer) AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) {
stenographer.registerCall("AnnounceSuite", description, randomSeed, randomizingAll, succinct)
}
func (stenographer *FakeStenographer) AnnounceAggregatedParallelRun(nodes int, succinct bool) {
stenographer.registerCall("AnnounceAggregatedParallelRun", nodes, succinct)
}
func (stenographer *FakeStenographer) AnnounceParallelRun(node int, nodes int, succinct bool) {
stenographer.registerCall("AnnounceParallelRun", node, nodes, succinct)
}
func (stenographer *FakeStenographer) AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) {
stenographer.registerCall("AnnounceNumberOfSpecs", specsToRun, total, succinct)
}
func (stenographer *FakeStenographer) AnnounceTotalNumberOfSpecs(total int, succinct bool) {
stenographer.registerCall("AnnounceTotalNumberOfSpecs", total, succinct)
}
func (stenographer *FakeStenographer) AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) {
stenographer.registerCall("AnnounceSpecRunCompletion", summary, succinct)
}
func (stenographer *FakeStenographer) AnnounceSpecWillRun(spec *types.SpecSummary) {
stenographer.registerCall("AnnounceSpecWillRun", spec)
}
func (stenographer *FakeStenographer) AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceBeforeSuiteFailure", summary, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceAfterSuiteFailure", summary, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceCapturedOutput(output string) {
stenographer.registerCall("AnnounceCapturedOutput", output)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulSpec(spec *types.SpecSummary) {
stenographer.registerCall("AnnounceSuccesfulSpec", spec)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) {
stenographer.registerCall("AnnounceSuccesfulSlowSpec", spec, succinct)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) {
stenographer.registerCall("AnnounceSuccesfulMeasurement", spec, succinct)
}
func (stenographer *FakeStenographer) AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) {
stenographer.registerCall("AnnouncePendingSpec", spec, noisy)
}
func (stenographer *FakeStenographer) AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSkippedSpec", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecTimedOut", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecPanicked", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecFailed", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) SummarizeFailures(summaries []*types.SpecSummary) {
stenographer.registerCall("SummarizeFailures", summaries)
}

View File

@@ -0,0 +1,572 @@
/*
The stenographer is used by Ginkgo's reporters to generate output.
Move along, nothing to see here.
*/
package stenographer
import (
"fmt"
"io"
"runtime"
"strings"
"github.com/onsi/ginkgo/types"
)
const defaultStyle = "\x1b[0m"
const boldStyle = "\x1b[1m"
const redColor = "\x1b[91m"
const greenColor = "\x1b[32m"
const yellowColor = "\x1b[33m"
const cyanColor = "\x1b[36m"
const grayColor = "\x1b[90m"
const lightGrayColor = "\x1b[37m"
type cursorStateType int
const (
cursorStateTop cursorStateType = iota
cursorStateStreaming
cursorStateMidBlock
cursorStateEndBlock
)
type Stenographer interface {
AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool)
AnnounceAggregatedParallelRun(nodes int, succinct bool)
AnnounceParallelRun(node int, nodes int, succinct bool)
AnnounceTotalNumberOfSpecs(total int, succinct bool)
AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool)
AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool)
AnnounceSpecWillRun(spec *types.SpecSummary)
AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool)
AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool)
AnnounceCapturedOutput(output string)
AnnounceSuccesfulSpec(spec *types.SpecSummary)
AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool)
AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool)
AnnouncePendingSpec(spec *types.SpecSummary, noisy bool)
AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool)
SummarizeFailures(summaries []*types.SpecSummary)
}
func New(color bool, enableFlakes bool, writer io.Writer) Stenographer {
denoter := "•"
if runtime.GOOS == "windows" {
denoter = "+"
}
return &consoleStenographer{
color: color,
denoter: denoter,
cursorState: cursorStateTop,
enableFlakes: enableFlakes,
w: writer,
}
}
type consoleStenographer struct {
color bool
denoter string
cursorState cursorStateType
enableFlakes bool
w io.Writer
}
var alternatingColors = []string{defaultStyle, grayColor}
func (s *consoleStenographer) AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) {
if succinct {
s.print(0, "[%d] %s ", randomSeed, s.colorize(boldStyle, description))
return
}
s.printBanner(fmt.Sprintf("Running Suite: %s", description), "=")
s.print(0, "Random Seed: %s", s.colorize(boldStyle, "%d", randomSeed))
if randomizingAll {
s.print(0, " - Will randomize all specs")
}
s.printNewLine()
}
func (s *consoleStenographer) AnnounceParallelRun(node int, nodes int, succinct bool) {
if succinct {
s.print(0, "- node #%d ", node)
return
}
s.println(0,
"Parallel test node %s/%s.",
s.colorize(boldStyle, "%d", node),
s.colorize(boldStyle, "%d", nodes),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceAggregatedParallelRun(nodes int, succinct bool) {
if succinct {
s.print(0, "- %d nodes ", nodes)
return
}
s.println(0,
"Running in parallel across %s nodes",
s.colorize(boldStyle, "%d", nodes),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) {
if succinct {
s.print(0, "- %d/%d specs ", specsToRun, total)
s.stream()
return
}
s.println(0,
"Will run %s of %s specs",
s.colorize(boldStyle, "%d", specsToRun),
s.colorize(boldStyle, "%d", total),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceTotalNumberOfSpecs(total int, succinct bool) {
if succinct {
s.print(0, "- %d specs ", total)
s.stream()
return
}
s.println(0,
"Will run %s specs",
s.colorize(boldStyle, "%d", total),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) {
if succinct && summary.SuiteSucceeded {
s.print(0, " %s %s ", s.colorize(greenColor, "SUCCESS!"), summary.RunTime)
return
}
s.printNewLine()
color := greenColor
if !summary.SuiteSucceeded {
color = redColor
}
s.println(0, s.colorize(boldStyle+color, "Ran %d of %d Specs in %.3f seconds", summary.NumberOfSpecsThatWillBeRun, summary.NumberOfTotalSpecs, summary.RunTime.Seconds()))
status := ""
if summary.SuiteSucceeded {
status = s.colorize(boldStyle+greenColor, "SUCCESS!")
} else {
status = s.colorize(boldStyle+redColor, "FAIL!")
}
flakes := ""
if s.enableFlakes {
flakes = " | " + s.colorize(yellowColor+boldStyle, "%d Flaked", summary.NumberOfFlakedSpecs)
}
s.print(0,
"%s -- %s | %s | %s | %s\n",
status,
s.colorize(greenColor+boldStyle, "%d Passed", summary.NumberOfPassedSpecs),
s.colorize(redColor+boldStyle, "%d Failed", summary.NumberOfFailedSpecs)+flakes,
s.colorize(yellowColor+boldStyle, "%d Pending", summary.NumberOfPendingSpecs),
s.colorize(cyanColor+boldStyle, "%d Skipped", summary.NumberOfSkippedSpecs),
)
}
func (s *consoleStenographer) AnnounceSpecWillRun(spec *types.SpecSummary) {
s.startBlock()
for i, text := range spec.ComponentTexts[1 : len(spec.ComponentTexts)-1] {
s.print(0, s.colorize(alternatingColors[i%2], text)+" ")
}
indentation := 0
if len(spec.ComponentTexts) > 2 {
indentation = 1
s.printNewLine()
}
index := len(spec.ComponentTexts) - 1
s.print(indentation, s.colorize(boldStyle, spec.ComponentTexts[index]))
s.printNewLine()
s.print(indentation, s.colorize(lightGrayColor, spec.ComponentCodeLocations[index].String()))
s.printNewLine()
s.midBlock()
}
func (s *consoleStenographer) AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.announceSetupFailure("BeforeSuite", summary, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.announceSetupFailure("AfterSuite", summary, succinct, fullTrace)
}
func (s *consoleStenographer) announceSetupFailure(name string, summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.startBlock()
var message string
switch summary.State {
case types.SpecStateFailed:
message = "Failure"
case types.SpecStatePanicked:
message = "Panic"
case types.SpecStateTimedOut:
message = "Timeout"
}
s.println(0, s.colorize(redColor+boldStyle, "%s [%.3f seconds]", message, summary.RunTime.Seconds()))
indentation := s.printCodeLocationBlock([]string{name}, []types.CodeLocation{summary.CodeLocation}, summary.ComponentType, 0, summary.State, true)
s.printNewLine()
s.printFailure(indentation, summary.State, summary.Failure, fullTrace)
s.endBlock()
}
func (s *consoleStenographer) AnnounceCapturedOutput(output string) {
if output == "" {
return
}
s.startBlock()
s.println(0, output)
s.midBlock()
}
func (s *consoleStenographer) AnnounceSuccesfulSpec(spec *types.SpecSummary) {
s.print(0, s.colorize(greenColor, s.denoter))
s.stream()
}
func (s *consoleStenographer) AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) {
s.printBlockWithMessage(
s.colorize(greenColor, "%s [SLOW TEST:%.3f seconds]", s.denoter, spec.RunTime.Seconds()),
"",
spec,
succinct,
)
}
func (s *consoleStenographer) AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) {
s.printBlockWithMessage(
s.colorize(greenColor, "%s [MEASUREMENT]", s.denoter),
s.measurementReport(spec, succinct),
spec,
succinct,
)
}
func (s *consoleStenographer) AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) {
if noisy {
s.printBlockWithMessage(
s.colorize(yellowColor, "P [PENDING]"),
"",
spec,
false,
)
} else {
s.print(0, s.colorize(yellowColor, "P"))
s.stream()
}
}
func (s *consoleStenographer) AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) {
// Skips at runtime will have a non-empty spec.Failure. All others should be succinct.
if succinct || spec.Failure == (types.SpecFailure{}) {
s.print(0, s.colorize(cyanColor, "S"))
s.stream()
} else {
s.startBlock()
s.println(0, s.colorize(cyanColor+boldStyle, "S [SKIPPING]%s [%.3f seconds]", s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds()))
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct)
s.printNewLine()
s.printSkip(indentation, spec.Failure)
s.endBlock()
}
}
func (s *consoleStenographer) AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s... Timeout", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s! Panic", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s Failure", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) SummarizeFailures(summaries []*types.SpecSummary) {
failingSpecs := []*types.SpecSummary{}
for _, summary := range summaries {
if summary.HasFailureState() {
failingSpecs = append(failingSpecs, summary)
}
}
if len(failingSpecs) == 0 {
return
}
s.printNewLine()
s.printNewLine()
plural := "s"
if len(failingSpecs) == 1 {
plural = ""
}
s.println(0, s.colorize(redColor+boldStyle, "Summarizing %d Failure%s:", len(failingSpecs), plural))
for _, summary := range failingSpecs {
s.printNewLine()
if summary.HasFailureState() {
if summary.TimedOut() {
s.print(0, s.colorize(redColor+boldStyle, "[Timeout...] "))
} else if summary.Panicked() {
s.print(0, s.colorize(redColor+boldStyle, "[Panic!] "))
} else if summary.Failed() {
s.print(0, s.colorize(redColor+boldStyle, "[Fail] "))
}
s.printSpecContext(summary.ComponentTexts, summary.ComponentCodeLocations, summary.Failure.ComponentType, summary.Failure.ComponentIndex, summary.State, true)
s.printNewLine()
s.println(0, s.colorize(lightGrayColor, summary.Failure.Location.String()))
}
}
}
func (s *consoleStenographer) startBlock() {
if s.cursorState == cursorStateStreaming {
s.printNewLine()
s.printDelimiter()
} else if s.cursorState == cursorStateMidBlock {
s.printNewLine()
}
}
func (s *consoleStenographer) midBlock() {
s.cursorState = cursorStateMidBlock
}
func (s *consoleStenographer) endBlock() {
s.printDelimiter()
s.cursorState = cursorStateEndBlock
}
func (s *consoleStenographer) stream() {
s.cursorState = cursorStateStreaming
}
func (s *consoleStenographer) printBlockWithMessage(header string, message string, spec *types.SpecSummary, succinct bool) {
s.startBlock()
s.println(0, header)
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, types.SpecComponentTypeInvalid, 0, spec.State, succinct)
if message != "" {
s.printNewLine()
s.println(indentation, message)
}
s.endBlock()
}
func (s *consoleStenographer) printSpecFailure(message string, spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.startBlock()
s.println(0, s.colorize(redColor+boldStyle, "%s%s [%.3f seconds]", message, s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds()))
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct)
s.printNewLine()
s.printFailure(indentation, spec.State, spec.Failure, fullTrace)
s.endBlock()
}
func (s *consoleStenographer) failureContext(failedComponentType types.SpecComponentType) string {
switch failedComponentType {
case types.SpecComponentTypeBeforeSuite:
return " in Suite Setup (BeforeSuite)"
case types.SpecComponentTypeAfterSuite:
return " in Suite Teardown (AfterSuite)"
case types.SpecComponentTypeBeforeEach:
return " in Spec Setup (BeforeEach)"
case types.SpecComponentTypeJustBeforeEach:
return " in Spec Setup (JustBeforeEach)"
case types.SpecComponentTypeAfterEach:
return " in Spec Teardown (AfterEach)"
}
return ""
}
func (s *consoleStenographer) printSkip(indentation int, spec types.SpecFailure) {
s.println(indentation, s.colorize(cyanColor, spec.Message))
s.printNewLine()
s.println(indentation, spec.Location.String())
}
func (s *consoleStenographer) printFailure(indentation int, state types.SpecState, failure types.SpecFailure, fullTrace bool) {
if state == types.SpecStatePanicked {
s.println(indentation, s.colorize(redColor+boldStyle, failure.Message))
s.println(indentation, s.colorize(redColor, failure.ForwardedPanic))
s.println(indentation, failure.Location.String())
s.printNewLine()
s.println(indentation, s.colorize(redColor, "Full Stack Trace"))
s.println(indentation, failure.Location.FullStackTrace)
} else {
s.println(indentation, s.colorize(redColor, failure.Message))
s.printNewLine()
s.println(indentation, failure.Location.String())
if fullTrace {
s.printNewLine()
s.println(indentation, s.colorize(redColor, "Full Stack Trace"))
s.println(indentation, failure.Location.FullStackTrace)
}
}
}
func (s *consoleStenographer) printSpecContext(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int {
startIndex := 1
indentation := 0
if len(componentTexts) == 1 {
startIndex = 0
}
for i := startIndex; i < len(componentTexts); i++ {
if (state.IsFailure() || state == types.SpecStateSkipped) && i == failedComponentIndex {
color := redColor
if state == types.SpecStateSkipped {
color = cyanColor
}
blockType := ""
switch failedComponentType {
case types.SpecComponentTypeBeforeSuite:
blockType = "BeforeSuite"
case types.SpecComponentTypeAfterSuite:
blockType = "AfterSuite"
case types.SpecComponentTypeBeforeEach:
blockType = "BeforeEach"
case types.SpecComponentTypeJustBeforeEach:
blockType = "JustBeforeEach"
case types.SpecComponentTypeAfterEach:
blockType = "AfterEach"
case types.SpecComponentTypeIt:
blockType = "It"
case types.SpecComponentTypeMeasure:
blockType = "Measurement"
}
if succinct {
s.print(0, s.colorize(color+boldStyle, "[%s] %s ", blockType, componentTexts[i]))
} else {
s.println(indentation, s.colorize(color+boldStyle, "%s [%s]", componentTexts[i], blockType))
s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i]))
}
} else {
if succinct {
s.print(0, s.colorize(alternatingColors[i%2], "%s ", componentTexts[i]))
} else {
s.println(indentation, componentTexts[i])
s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i]))
}
}
indentation++
}
return indentation
}
func (s *consoleStenographer) printCodeLocationBlock(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int {
indentation := s.printSpecContext(componentTexts, componentCodeLocations, failedComponentType, failedComponentIndex, state, succinct)
if succinct {
if len(componentTexts) > 0 {
s.printNewLine()
s.print(0, s.colorize(lightGrayColor, "%s", componentCodeLocations[len(componentCodeLocations)-1]))
}
s.printNewLine()
indentation = 1
} else {
indentation--
}
return indentation
}
func (s *consoleStenographer) orderedMeasurementKeys(measurements map[string]*types.SpecMeasurement) []string {
orderedKeys := make([]string, len(measurements))
for key, measurement := range measurements {
orderedKeys[measurement.Order] = key
}
return orderedKeys
}
func (s *consoleStenographer) measurementReport(spec *types.SpecSummary, succinct bool) string {
if len(spec.Measurements) == 0 {
return "Found no measurements"
}
message := []string{}
orderedKeys := s.orderedMeasurementKeys(spec.Measurements)
if succinct {
message = append(message, fmt.Sprintf("%s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples)))
for _, key := range orderedKeys {
measurement := spec.Measurements[key]
message = append(message, fmt.Sprintf(" %s - %s: %s%s, %s: %s%s ± %s%s, %s: %s%s",
s.colorize(boldStyle, "%s", measurement.Name),
measurement.SmallestLabel,
s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest),
measurement.Units,
measurement.AverageLabel,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average),
measurement.Units,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation),
measurement.Units,
measurement.LargestLabel,
s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest),
measurement.Units,
))
}
} else {
message = append(message, fmt.Sprintf("Ran %s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples)))
for _, key := range orderedKeys {
measurement := spec.Measurements[key]
info := ""
if measurement.Info != nil {
message = append(message, fmt.Sprintf("%v", measurement.Info))
}
message = append(message, fmt.Sprintf("%s:\n%s %s: %s%s\n %s: %s%s\n %s: %s%s ± %s%s",
s.colorize(boldStyle, "%s", measurement.Name),
info,
measurement.SmallestLabel,
s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest),
measurement.Units,
measurement.LargestLabel,
s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest),
measurement.Units,
measurement.AverageLabel,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average),
measurement.Units,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation),
measurement.Units,
))
}
}
return strings.Join(message, "\n")
}

Some files were not shown because too many files have changed in this diff Show More