Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move test execution to runner file, refactor output #126

Merged
merged 14 commits into from
Jul 12, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 47 additions & 43 deletions pkg/app/test_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (
"log"
"os"
"path"
"sync"

"github.com/SimonBaeumer/commander/pkg/output"
"github.com/SimonBaeumer/commander/pkg/runtime"
"github.com/SimonBaeumer/commander/pkg/suite"
)

var out output.OutputWriter

// TestCommand executes the test argument
// testPath is the path to the test suite config, it can be a dir or file
// titleFilterTitle is the title of test which should be executed, if empty it will execute all tests
Expand All @@ -23,97 +24,100 @@ func TestCommand(testPath string, testFilterTitle string, ctx AddCommandContext)
log.SetOutput(os.Stdout)
}

out = output.NewCliOutput(!ctx.NoColor)

if testPath == "" {
testPath = CommanderFile
}

var results <-chan runtime.TestResult
var result output.Result
var err error
if ctx.Dir {
if testFilterTitle != "" {
return fmt.Errorf("Test may not be filtered when --dir is enabled")
}
fmt.Println("Starting test against directory: " + testPath + "...")
fmt.Println("")
results, err = testDir(testPath)
result, err = testDir(testPath)
} else {
fmt.Println("Starting test file " + testPath + "...")
fmt.Println("")
results, err = testFile(testPath, testFilterTitle)
result, err = testFile(testPath, "", testFilterTitle)
}

if err != nil {
return fmt.Errorf(err.Error())
}

out := output.NewCliOutput(!ctx.NoColor)
if !out.Start(results) {
if !out.PrintSummary(result) {
return fmt.Errorf("Test suite failed, use --verbose for more detailed output")
}

return nil
}

func testDir(directory string) (<-chan runtime.TestResult, error) {
func testDir(directory string) (output.Result, error) {
result := output.Result{}

files, err := ioutil.ReadDir(directory)
if err != nil {
return nil, fmt.Errorf(err.Error())
return result, fmt.Errorf(err.Error())
}

results := make(chan runtime.TestResult)
var wg sync.WaitGroup

wg.Add(1)
go func() {
defer wg.Done()
for _, f := range files {
// Skip reading dirs for now. Should we also check valid file types?
if f.IsDir() {
continue
}

fileResults, err := testFile(path.Join(directory, f.Name()), "")
if err != nil {
panic(fmt.Sprintf("%s: %s", f.Name(), err))
}

for r := range fileResults {
r.FileName = f.Name()
results <- r
}
for _, f := range files {
if f.IsDir() {
continue
}

newResult, err := testFile(path.Join(directory, f.Name()), f.Name(), "")
if err != nil {
return result, err
}
}()

go func(ch chan runtime.TestResult) {
wg.Wait()
close(results)
}(results)
result = convergeResults(result, newResult)
}

return results, nil
return result, nil
}

func testFile(filePath string, title string) (<-chan runtime.TestResult, error) {
func convergeResults(result output.Result, new output.Result) output.Result {
result.TestResults = append(result.TestResults, new.TestResults...)
result.Failed += new.Failed
result.Duration += new.Duration

return result
}

func testFile(filePath string, fileName string, title string) (output.Result, error) {
content, err := readFile(filePath)
if err != nil {
return nil, fmt.Errorf("Error " + err.Error())
return output.Result{}, fmt.Errorf("Error " + err.Error())
}

var s suite.Suite
s = suite.ParseYAML(content)
s := suite.ParseYAML(content, fileName)
result, err := execute(s, title)
if err != nil {
return output.Result{}, fmt.Errorf("Error " + err.Error())
}

return result, nil
}

func execute(s suite.Suite, title string) (output.Result, error) {
tests := s.GetTests()
// Filter tests if test title was given
if title != "" {
test, err := s.GetTestByTitle(title)
if err != nil {
return nil, err
return output.Result{}, err
}
tests = []runtime.TestCase{test}
}

r := runtime.NewRuntime(s.Nodes...)
results := r.Start(tests)
r := runtime.NewRuntime(&out, s.Nodes...)
result := r.Start(tests)

return results, nil
return result, nil
}

func readFile(filePath string) ([]byte, error) {
Expand Down
24 changes: 24 additions & 0 deletions pkg/app/test_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"runtime"
"sync"
"testing"
"time"

"github.com/SimonBaeumer/commander/pkg/output"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -76,6 +78,28 @@ func Test_TestCommand_Dir_FilterTitle(t *testing.T) {
}
}

func Test_ConvergeResults(t *testing.T) {
duration, _ := time.ParseDuration("5s")

result1 := output.Result{
TestResults: []output.TestResult{},
Duration: duration,
Failed: 1,
}

result2 := output.Result{
TestResults: []output.TestResult{},
Duration: duration,
Failed: 0,
}

actual := convergeResults(result1, result2)

expectDur, _ := time.ParseDuration("10s")
assert.Equal(t, expectDur, actual.Duration)
assert.Equal(t, 1, actual.Failed)
}

func captureOutput(f func()) string {
reader, writer, err := os.Pipe()
if err != nil {
Expand Down
125 changes: 52 additions & 73 deletions pkg/output/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,117 +8,96 @@ import (
run "runtime"
"time"

"github.com/SimonBaeumer/commander/pkg/runtime"
"github.com/logrusorgru/aurora"
)

var au aurora.Aurora

// OutputWriter represents the output
type OutputWriter struct {
out io.Writer
color bool
out io.Writer
au aurora.Aurora
template cliTemplate
}

// NewCliOutput creates a new OutputWriter with a stdout writer
func NewCliOutput(color bool) OutputWriter {
return OutputWriter{
out: os.Stdout,
color: color,
}
}

// Start starts the writing sequence
func (w *OutputWriter) Start(results <-chan runtime.TestResult) bool {
au = aurora.NewAurora(w.color)
au := aurora.NewAurora(color)
if run.GOOS == "windows" {
au = aurora.NewAurora(false)
}

failed := 0
var testResults []runtime.TestResult
start := time.Now()
t := newCliTemplate()

for r := range results {
testResults = append(testResults, r)
if r.ValidationResult.Success {
w.printResult(r)
} else {
w.printResult(r)
failed++
}
return OutputWriter{
out: os.Stdout,
au: au,
template: t,
}
}

// Result respresents the aggregation of all TestResults/summary of a runtime
type Result struct {
TestResults []TestResult
Duration time.Duration
Failed int
}

duration := time.Since(start)
// TestResult for output
type TestResult struct {
FileName string
Title string
Node string
Tries int
Success bool
FailedProperty string
Diff string
Error error
}

if failed > 0 {
w.printFailures(testResults)
// PrintSummary prints summary
func (w *OutputWriter) PrintSummary(result Result) bool {
if result.Failed > 0 {
w.printFailures(result.TestResults)
}

w.fprintf("")
w.fprintf(fmt.Sprintf("Duration: %.3fs", duration.Seconds()))
summary := fmt.Sprintf("Count: %d, Failed: %d", len(testResults), failed)
if failed > 0 {
w.fprintf(au.Red(summary))
w.fprintf(w.template.duration(result))
summary := w.template.summary(result)
if result.Failed > 0 {
w.fprintf(w.au.Red(summary))
} else {
w.fprintf(au.Green(summary))
w.fprintf(w.au.Green(summary))
}

return failed == 0
return result.Failed == 0
}

func (w *OutputWriter) printResult(r runtime.TestResult) {
if !r.ValidationResult.Success {
str := fmt.Sprintf("✗ [%s] %s", r.Node, r.TestCase.Title)
str = w.addFile(str, r)
s := w.addTries(str, r)
w.fprintf(au.Red(s))
// PrintResult prints the simple output form of a TestReault
func (w *OutputWriter) PrintResult(r TestResult) {
if !r.Success {
w.fprintf(w.au.Red(w.template.testResult(r)))
return
}
str := fmt.Sprintf("✓ [%s] %s", r.Node, r.TestCase.Title)
str = w.addFile(str, r)
s := w.addTries(str, r)
w.fprintf(s)
w.fprintf(w.template.testResult(r))
}

func (w *OutputWriter) printFailures(results []runtime.TestResult) {
func (w *OutputWriter) printFailures(results []TestResult) {
w.fprintf("")
w.fprintf(au.Bold("Results"))
w.fprintf(au.Bold(""))
w.fprintf(w.au.Bold("Results"))
w.fprintf(w.au.Bold(""))

for _, r := range results {
if r.TestCase.Result.Error != nil {
str := fmt.Sprintf("✗ [%s] '%s' could not be executed with error message:", r.Node, r.TestCase.Title)
str = w.addFile(str, r)
w.fprintf(au.Bold(au.Red(str)))
w.fprintf(r.TestCase.Result.Error.Error())
if r.Error != nil {
w.fprintf(w.au.Bold(w.au.Red(w.template.errors(r))))
w.fprintf(r.Error.Error())
continue
}

if !r.ValidationResult.Success {
str := fmt.Sprintf("✗ [%s] '%s', on property '%s'", r.Node, r.TestCase.Title, r.FailedProperty)
str = w.addFile(str, r)
w.fprintf(au.Bold(au.Red(str)))
w.fprintf(r.ValidationResult.Diff)
if !r.Success {
w.fprintf(w.au.Bold(w.au.Red(w.template.failures(r))))
w.fprintf(r.Diff)
}
}
}

func (w *OutputWriter) addFile(s string, r runtime.TestResult) string {
if r.FileName == "" {
return s
}
s = s[:3] + " [" + r.FileName + "]" + s[3:]
return s
}

func (w *OutputWriter) addTries(s string, r runtime.TestResult) string {
if r.Tries > 1 {
s = fmt.Sprintf("%s, retries %d", s, r.Tries)
}
return s
}

func (w *OutputWriter) fprintf(a ...interface{}) {
if _, err := fmt.Fprintln(w.out, a...); err != nil {
log.Fatal(err)
Expand Down
Loading