[go: up one dir, main page]

Skip to content

Commit

Permalink
Add variable substitution and colored outputs (nytimes#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
yunzhu-li authored Jul 10, 2019
1 parent cefaa31 commit 24504a7
Show file tree
Hide file tree
Showing 332 changed files with 232,398 additions and 33 deletions.
50 changes: 49 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ Examples

### Configurations

Environment variables:
A few global configurations (applied to all tests) can be specified by
environment variables:

- `TEST_DIRECTORY`: Local directory that contains the test definition YAML
files. Default: `tests`
Expand All @@ -85,9 +86,29 @@ Environment variables:
- `TEST_PRINT_FAILED_ONLY`: Only print failed tests. Valid values: `false` or
`true`. Default: `false`.

### Environment variable substitution

This program supports variable substitution from environment variables in YAML
files. This is useful for handling secrets or dynamic values.
Visit [here](https://github.com/drone/envsubst/blob/master/README) for
supported functions.

Example:

```yaml
tests:
- description: 'get current user'
request:
path: '/user'
headers:
authorization: 'token ${SECRET_AUTH_TOKEN}'
response:
statusCodes: [200]
```

### Full test example

Required fields:
Required fields for each test:

- `description`
- `request.path`
Expand All @@ -109,7 +130,7 @@ tests:
path: '/' # Path to hit. Required
headers: # Headers
x-test-header-0: 'abc'
x-test-header-1: 'def'
x-test: '${REQ_TEST}' # Environment variable substitution
body: '' # Request body. Processed as string
response: # Expected response
Expand All @@ -119,7 +140,7 @@ tests:
server: '^ECS$' # Header name : regular expression
cache-control: '.+'
notPresent: # Specify headers not expected to exist.
- 'set-cookie' # Not regular expressions
- 'set-cookie' # These are not regular expressions
- 'x-frame-options'
body: # Response body
patterns: # Response body has to match all patterns in this list in order to pass test
Expand Down
22 changes: 6 additions & 16 deletions coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package main

import (
"fmt"
"sync"
)

Expand All @@ -36,27 +35,22 @@ func RunTests(tests []*Test, config *Config) bool {

result := RunTest(t, config.Host)

// Acquire lock before accessing shared variables and writing output
// Code in critical section should not perform network I/O
// Acquire lock before accessing shared variables and writing output.
// Code in critical section should not perform network I/O.
mux.Lock()
testInfoString := GenerateTestInfoString(t)

if result.Skipped {
skipped++
if !config.PrintFailedTestsOnly {
fmt.Printf("skipped: %s\n", testInfoString)
PrintTestResult(t, result)
}
} else if len(result.Errors) > 0 {
failed++
fmt.Printf("\nfailed: %s\n", testInfoString)
for _, err := range result.Errors {
fmt.Printf("%s\n", err.Error())
}
fmt.Println("")
PrintTestResult(t, result)
} else {
passed++
if !config.PrintFailedTestsOnly {
fmt.Printf("passed: %s\n", testInfoString)
PrintTestResult(t, result)
}
}
mux.Unlock()
Expand All @@ -68,11 +62,7 @@ func RunTests(tests []*Test, config *Config) bool {
sem <- 0
}

fmt.Printf("\n%d passed\n", passed)
fmt.Printf("%d failed\n", failed)
if skipped > 0 {
fmt.Printf("%d skipped\n", skipped)
}
PrintTestSummary(passed, failed, skipped)

if failed > 0 {
return false
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var (

func main() {
// Print version info
fmt.Printf("httptest: %s %s %s\n\n", BuildCommit, BuildBranch, BuildTime)
fmt.Printf("httptest: %s %s %s\n", BuildCommit, BuildBranch, BuildTime)

// Get and apply config
config, err := FromEnv()
Expand Down
57 changes: 57 additions & 0 deletions output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2019 The New York Times Company
//
// 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 main

import (
"fmt"

"github.com/fatih/color"
)

// PrintTestResult prints result of a single test
func PrintTestResult(test *Test, result *TestResult) {
color.NoColor = false
fmt.Println("")

// Print colored status text
if result.Skipped {
color.HiBlue("SKIPPED")
} else if len(result.Errors) < 1 {
color.HiGreen("PASSED")
} else {
color.HiRed("FAILED")
}

// Print test info
fmt.Printf("%s\n", fmt.Sprintf("%s | %s | [%s]", test.Filename, test.Description, test.Request.Path))

// Print all errors
if len(result.Errors) > 0 {
fmt.Printf("errors:\n")
for _, err := range result.Errors {
fmt.Printf("%s\n", err.Error())
}
}
}

// PrintTestSummary prints summary info for multiple tests
func PrintTestSummary(passed, failed, skipped int) {
fmt.Printf(
"\n%s passed\n%s failed\n%s skipped\n",
color.HiGreenString("%d", passed),
color.HiRedString("%d", failed),
color.HiBlueString("%d", skipped),
)
}
11 changes: 10 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"path"
"path/filepath"

"github.com/drone/envsubst"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -100,13 +101,21 @@ func ParseAllTestsInDirectory(root string) ([]*Test, error) {
}

func parseTestFile(filePath string) ([]*Test, error) {
// Read file into buffer
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("ioutil: %v", err)
}

// Environment variable substitution
yamlString, err := envsubst.EvalEnv(string(data))
if err != nil {
return nil, fmt.Errorf("unable to parse file %s: %v", filePath, err)
}

// Parse as YAML
tf := TestFile{}
err = yaml.Unmarshal(data, &tf)
err = yaml.Unmarshal([]byte(yamlString), &tf)
if err != nil {
return nil, fmt.Errorf("unable to parse file %s: %v", filePath, err)
}
Expand Down
18 changes: 8 additions & 10 deletions tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,17 @@ type TestResult struct {
Errors []error
}

// GenerateTestInfoString generates a human-readable string indicates the test
func GenerateTestInfoString(test *Test) string {
return fmt.Sprintf("%s | %s | [%s]", test.Filename, test.Description, test.Request.Path)
}

// RunTest runs single test
func RunTest(test *Test, defaultHost string) *TestResult {
result := &TestResult{}

// Validate test and assign default values
if err := preProcessTest(test, defaultHost); err != nil {
result.Errors = append(result.Errors, err)
return result
}

// Check test conditions and skip if not met
conditionsMet, err := validateConditions(test)
if err != nil {
result.Errors = append(result.Errors, err)
Expand All @@ -54,10 +51,6 @@ func RunTest(test *Test, defaultHost string) *TestResult {
return result
}

if !strings.HasPrefix(test.Request.Path, "/") {
result.Errors = append(result.Errors, fmt.Errorf("request.path must start with /"))
return result
}
url := test.Request.Scheme + "://" + test.Request.Host + test.Request.Path

var body io.Reader
Expand All @@ -71,7 +64,7 @@ func RunTest(test *Test, defaultHost string) *TestResult {
Headers: test.Request.Headers,
Body: body,
Attempts: 1,
TimeoutSeconds: 5,
TimeoutSeconds: 30,
SkipCertVerification: test.SkipCertVerification,
}

Expand All @@ -87,6 +80,7 @@ func RunTest(test *Test, defaultHost string) *TestResult {
return result
}

// preProcessTest validates test and assigns default values
func preProcessTest(test *Test, defaultHost string) error {
// Scheme
scheme := stringValue(test.Request.Scheme, "https")
Expand Down Expand Up @@ -114,6 +108,10 @@ func preProcessTest(test *Test, defaultHost string) error {
return fmt.Errorf("request path is required")
}

if !strings.HasPrefix(test.Request.Path, "/") {
return fmt.Errorf("request.path must start with /")
}

return nil
}

Expand Down
8 changes: 8 additions & 0 deletions vendor/github.com/drone/envsubst/.drone.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/drone/envsubst/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/drone/envsubst/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 24504a7

Please sign in to comment.