asserts/asserts.go (118 lines of code) (raw):

// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package asserts // import "github.com/GoogleCloudPlatform/marketplace-testrunner/asserts" import ( "fmt" "regexp" "strings" xmlpath "gopkg.in/xmlpath.v2" "bytes" "github.com/GoogleCloudPlatform/marketplace-testrunner/specs" "golang.org/x/net/html" ) // MessageWithContext creates a new message that hierarchically puts // the message within the provided context. func MessageWithContext(msg string, context string) string { return fmt.Sprintf("%v > %v", context, msg) } // BashMessageWithContext creates a new message that hierarchically puts // the message within the provided context. // Provides additional info on exit status code, stdout and stderr // of executed bash command. func BashMessageWithContext(msg string, context string, status int, stdout string, stderr string) string { var sb strings.Builder fmt.Fprintf(&sb, "%v\n", MessageWithContext(msg, context)) fmt.Fprintf(&sb, "> EXIT CODE: %v\n", status) fmt.Fprintf(&sb, "> STDOUT: %v\n", strings.TrimSuffix(stdout, "\n")) fmt.Fprintf(&sb, "> STDERR: %v\n", strings.TrimSuffix(stderr, "\n")) return sb.String() } // DoAssert asserts the value against the rule, returning // an empty string if the assertion succeeds, or the error message. func DoAssert(value interface{}, rule interface{}) string { switch r := rule.(type) { case specs.IntAssert: return doIntAssert(value.(int), r) case *specs.IntAssert: return doIntAssert(value.(int), *r) case specs.StringAssert: return doStringAssert(value.(string), r) case *specs.StringAssert: return doStringAssert(value.(string), *r) case specs.TextContentAssert: return doTextContentAssert(value.(string), r) case *specs.TextContentAssert: return doTextContentAssert(value.(string), *r) default: panic(fmt.Sprintf("Don't know how to handle rule type, %T", rule)) } } func doIntAssert(value int, rule specs.IntAssert) string { if rule.Equals != nil && value != *rule.Equals { return fmt.Sprintf("Should have equaled %d, but was %d", *rule.Equals, value) } if rule.AtLeast != nil && value < *rule.AtLeast { return fmt.Sprintf("Should have been at least %d, but was %d", *rule.AtLeast, value) } if rule.AtMost != nil && value > *rule.AtMost { return fmt.Sprintf("Should have been at most %d, but was %d", *rule.AtMost, value) } if rule.LessThan != nil && value >= *rule.LessThan { return fmt.Sprintf("Should have been less than %d, but was %d", *rule.LessThan, value) } if rule.GreaterThan != nil && value <= *rule.GreaterThan { return fmt.Sprintf("Should have been greater than %d, but was %d", *rule.GreaterThan, value) } if rule.NotEquals != nil && value == *rule.NotEquals { return fmt.Sprintf("Should have been different from %d, but was %d", *rule.NotEquals, value) } return "" } func doStringAssert(value string, rule specs.StringAssert) string { if rule.Exactly != nil && value != *rule.Exactly { return fmt.Sprintf("Should have matched exactly:\n%s\n... but was:\n%s", *rule.Exactly, value) } if rule.NotContains != nil && strings.Contains(value, *rule.NotContains) { return fmt.Sprintf("Should have not contained:\n%s\n... but was:\n%s", *rule.NotContains, value) } if rule.Equals != nil { trimmed := strings.TrimSpace(value) if trimmed != *rule.Equals { return fmt.Sprintf("Should have been:\n%s\n... but was:\n%s", *rule.Equals, trimmed) } } if rule.Contains != nil && !strings.Contains(value, *rule.Contains) { return fmt.Sprintf("Should have contained:\n%s\n... but was:\n%s", *rule.Contains, value) } if rule.Matches != nil { r, err := regexp.Compile(*rule.Matches) if err != nil { return fmt.Sprintf("Regex failed to compile: %s", *rule.Matches) } if !r.MatchString(value) { return fmt.Sprintf("Should have matched regex:\n%s\n... but was:\n%s", *rule.Matches, value) } } return "" } func doTextContentAssert(value string, rule specs.TextContentAssert) string { if rule.Html != nil { if msg := doHtmlAssert(value, *rule.Html); msg != "" { return MessageWithContext(msg, "Html") } } return "" } func doHtmlAssert(value string, rule specs.HtmlAssert) string { // Use net/html to parse first because it can handle some malformed HTML. root, err := html.Parse(strings.NewReader(value)) if err != nil { return "Failed to parse HTML content" } // Reconstruct well-formed XML for xmlpath library. var b bytes.Buffer html.Render(&b, root) fixedHtml := b.String() xmlRoot, err := xmlpath.ParseHTML(strings.NewReader(fixedHtml)) if err != nil { return "Failed to fix and parse HTML to XML" } if rule.Title != nil { title, ok := xmlpath.MustCompile("/html/head/title").String(xmlRoot) if !ok { return "HTML document contains no title" } if msg := doStringAssert(title, *rule.Title); msg != "" { return MessageWithContext(msg, "Title") } } return "" }