@ -43,15 +43,25 @@ const (
defaultArraySeparator = ","
)
type tests struct {
TestItems [ ] * testItem ` yaml:"test_items" `
BinOp binOp ` yaml:"bin_op" `
}
type testItem struct {
Flag string
Path string
Output string
Value string
Set bool
Compare compare
Flag string
Path string
Output string
Value string
Set bool
Compare compare
isMultipleOutput bool
isConfigSetting bool
}
type pathTestItem testItem
type flagTestItem testItem
type compare struct {
Op string
Value string
@ -59,6 +69,7 @@ type compare struct {
type testOutput struct {
testResult bool
flagFound bool
actualResult string
ExpectedResult string
}
@ -67,99 +78,124 @@ func failTestItem(s string) *testOutput {
return & testOutput { testResult : false , actualResult : s }
}
func ( t * testItem ) execute ( s string , isMultipleOutput bool ) * testOutput {
result := & testOutput { }
s = strings . TrimRight ( s , " \n" )
func ( t testItem ) flagValue ( ) string {
if t . isConfigSetting {
return t . Path
}
// If the test has output that should be evaluated for each row
if isMultipleOutput {
output := strings . Split ( s , "\n" )
for _ , op := range output {
result = t . evaluate ( op )
// If the test failed for the current row, no need to keep testing for this output
if ! result . testResult {
break
return t . Flag
}
func ( t testItem ) findValue ( s string ) ( match bool , value string , err error ) {
if t . isConfigSetting {
pt := pathTestItem ( t )
return pt . findValue ( s )
}
ft := flagTestItem ( t )
return ft . findValue ( s )
}
func ( t flagTestItem ) findValue ( s string ) ( match bool , value string , err error ) {
if s == "" || t . Flag == "" {
return
}
match = strings . Contains ( s , t . Flag )
if match {
// Expects flags in the form;
// --flag=somevalue
// flag: somevalue
// --flag
// somevalue
pttn := ` ( ` + t . Flag + ` )(=|: *)*([^\s]*) * `
flagRe := regexp . MustCompile ( pttn )
vals := flagRe . FindStringSubmatch ( s )
if len ( vals ) > 0 {
if vals [ 3 ] != "" {
value = vals [ 3 ]
} else {
// --bool-flag
if strings . HasPrefix ( t . Flag , "--" ) {
value = "true"
} else {
value = vals [ 1 ]
}
}
} else {
err = fmt . Errorf ( "invalid flag in testItem definition: %s" , s )
}
} else {
result = t . evaluate ( s )
}
glog . V ( 3 ) . Infof ( "In flagTestItem.findValue %s, match %v, s %s, t.Flag %s" , value , match , s , t . Flag )
return result
return match , value , err
}
func ( t pathTestItem ) findValue ( s string ) ( match bool , value string , err error ) {
var jsonInterface interface { }
err = unmarshal ( s , & jsonInterface )
if err != nil {
return false , "" , fmt . Errorf ( "failed to load YAML or JSON from input \"%s\": %v" , s , err )
}
value , err = executeJSONPath ( t . Path , & jsonInterface )
if err != nil {
return false , "" , fmt . Errorf ( "unable to parse path expression \"%s\": %v" , t . Path , err )
}
glog . V ( 3 ) . Infof ( "In pathTestItem.findValue %s" , value )
match = ( value != "" )
return match , value , err
}
func ( t * testItem ) evaluate ( s string ) * testOutput {
func ( t testItem ) execu te( s string ) * testOutput {
result := & testOutput { }
var match bool
var flagVal string
s = strings . TrimRight ( s , " \n" )
if t . Flag != "" {
// Flag comparison: check if the flag is present in the input
match = strings . Contains ( s , t . Flag )
// If the test has output that should be evaluated for each row
var output [ ] string
if t . isMultipleOutput {
output = strings . Split ( s , "\n" )
} else {
// Path != "" - we don't know whether it's YAML or JSON but
// we can just try one then the other
var jsonInterface interface { }
if t . Path != "" {
err := unmarshal ( s , & jsonInterface )
if err != nil {
fmt . Fprintf ( os . Stderr , "failed to load YAML or JSON from provided input \"%s\": %v\n" , s , err )
return failTestItem ( "failed to load YAML or JSON" )
}
output = [ ] string { s }
}
for _ , op := range output {
result = t . evaluate ( op )
// If the test failed for the current row, no need to keep testing for this output
if ! result . testResult {
break
}
}
jsonpathResult , err := executeJSONPath ( t . Path , & jsonInterface )
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to parse path expression \"%s\": %v\n" , t . Path , err )
return failTestItem ( "error executing path expression" )
}
match = ( jsonpathResult != "" )
flagVal = jsonpathResult
return result
}
func ( t testItem ) evaluate ( s string ) * testOutput {
result := & testOutput { }
match , value , err := t . findValue ( s )
if err != nil {
fmt . Fprintf ( os . Stderr , err . Error ( ) )
return failTestItem ( err . Error ( ) )
}
if t . Set {
isset := match
if isset && t . Compare . Op != "" {
if t . Flag != "" {
// Expects flags in the form;
// --flag=somevalue
// flag: somevalue
// --flag
// somevalue
pttn := ` ( ` + t . Flag + ` )(=|: *)*([^\s]*) * `
flagRe := regexp . MustCompile ( pttn )
vals := flagRe . FindStringSubmatch ( s )
if len ( vals ) > 0 {
if vals [ 3 ] != "" {
flagVal = vals [ 3 ]
} else {
// --bool-flag
if strings . HasPrefix ( t . Flag , "--" ) {
flagVal = "true"
} else {
flagVal = vals [ 1 ]
}
}
} else {
glog . V ( 1 ) . Infof ( fmt . Sprintf ( "invalid flag in testitem definition" ) )
return failTestItem ( "error invalid flag in testitem definition" )
}
}
result . ExpectedResult , result . testResult = compareOp ( t . Compare . Op , flagVal , t . Compare . Value )
if match && t . Compare . Op != "" {
result . ExpectedResult , result . testResult = compareOp ( t . Compare . Op , value , t . Compare . Value )
} else {
result . ExpectedResult = fmt . Sprintf ( "'%s' is present" , t . Flag )
result . testResult = isset
result . ExpectedResult = fmt . Sprintf ( "'%s' is present" , t . flagValue ( ) )
result . testResult = match
}
} else {
result . ExpectedResult = fmt . Sprintf ( "'%s' is not present" , t . Flag )
notset := ! match
result . testResult = notset
result . ExpectedResult = fmt . Sprintf ( "'%s' is not present" , t . flagValue ( ) )
result . testResult = ! match
}
result . flagFound = match
glog . V ( 3 ) . Info ( fmt . Sprintf ( "flagFound %v" , result . flagFound ) )
return result
}
@ -326,66 +362,6 @@ func splitAndRemoveLastSeparator(s, sep string) []string {
return ts
}
type tests struct {
TestItems [ ] * testItem ` yaml:"test_items" `
BinOp binOp ` yaml:"bin_op" `
}
func ( ts * tests ) execute ( s string , isMultipleOutput bool ) * testOutput {
finalOutput := & testOutput { }
// If no tests are defined return with empty finalOutput.
// This may be the case for checks of type: "skip".
if ts == nil {
return finalOutput
}
res := make ( [ ] testOutput , len ( ts . TestItems ) )
if len ( res ) == 0 {
return finalOutput
}
expectedResultArr := make ( [ ] string , len ( res ) )
for i , t := range ts . TestItems {
res [ i ] = * ( t . execute ( s , isMultipleOutput ) )
expectedResultArr [ i ] = res [ i ] . ExpectedResult
}
var result bool
// If no binary operation is specified, default to AND
switch ts . BinOp {
default :
glog . V ( 2 ) . Info ( fmt . Sprintf ( "unknown binary operator for tests %s\n" , ts . BinOp ) )
finalOutput . actualResult = fmt . Sprintf ( "unknown binary operator for tests %s\n" , ts . BinOp )
return finalOutput
case and , "" :
result = true
for i := range res {
result = result && res [ i ] . testResult
}
// Generate an AND expected result
finalOutput . ExpectedResult = strings . Join ( expectedResultArr , " AND " )
case or :
result = false
for i := range res {
result = result || res [ i ] . testResult
}
// Generate an OR expected result
finalOutput . ExpectedResult = strings . Join ( expectedResultArr , " OR " )
}
finalOutput . testResult = result
finalOutput . actualResult = res [ 0 ] . actualResult
if finalOutput . actualResult == "" {
finalOutput . actualResult = s
}
return finalOutput
}
func toNumeric ( a , b string ) ( c , d int , err error ) {
c , err = strconv . Atoi ( strings . TrimSpace ( a ) )
if err != nil {