From 82db424f6e269214df26837fec93b5db4db957b1 Mon Sep 17 00:00:00 2001 From: Tim De Pauw Date: Wed, 25 Apr 2018 18:14:22 +0200 Subject: [PATCH] Add some tests --- .circleci/config.yml | 30 +++++++++ internal/app/lwc/config.go | 41 ++++++----- internal/app/lwc/config_test.go | 116 ++++++++++++++++++++++++++++++++ internal/app/lwc/output.go | 8 ++- internal/app/lwc/output_test.go | 93 +++++++++++++++++++++++++ internal/app/lwc/root.go | 5 +- internal/app/lwc/scan.go | 18 ++--- internal/app/lwc/scan_test.go | 76 +++++++++++++++++++++ 8 files changed, 357 insertions(+), 30 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 internal/app/lwc/config_test.go create mode 100644 internal/app/lwc/output_test.go create mode 100644 internal/app/lwc/scan_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..2f204d1 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,30 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang:1.10.1-stretch + working_directory: /go/src/github.com/timdp/lwc + environment: + TEST_RESULTS: /tmp/test-results + steps: + - checkout + - run: mkdir -p $TEST_RESULTS + - restore_cache: + keys: + - v1-pkg-cache + - run: go get github.com/jstemmer/go-junit-report + - run: + command: | + trap "go-junit-report < $TEST_RESULTS/go-test.out > $TEST_RESULTS/go-test-report.xml" EXIT + go test -v $( go list ./... ) | tee $TEST_RESULTS/go-test.out + - save_cache: + key: v1-pkg-cache + paths: + - /go/pkg + - store_test_results: + path: /tmp/test-results + when: always + - store_artifacts: + path: /tmp/test-results + destination: test-results + when: always diff --git a/internal/app/lwc/config.go b/internal/app/lwc/config.go index 8894513..4680c10 100644 --- a/internal/app/lwc/config.go +++ b/internal/app/lwc/config.go @@ -2,6 +2,7 @@ package lwc import ( "fmt" + "log" "os" "time" @@ -20,23 +21,33 @@ type Config struct { Help bool Version bool Files []string + g *getopt.Set } -func BuildConfig() Config { - var config Config +func (c *Config) PrintUsage() { + c.g.PrintUsage(os.Stdout) +} + +func BuildConfig(args []string) Config { intervalMs := DEFAULT_INTERVAL - getopt.FlagLong(&config.Lines, "lines", 'l', "print the newline counts") - getopt.FlagLong(&config.Words, "words", 'w', "print the word counts") - getopt.FlagLong(&config.Chars, "chars", 'm', "print the character counts") - getopt.FlagLong(&config.Bytes, "bytes", 'c', "print the byte counts") - getopt.FlagLong(&config.MaxLineLength, "max-line-length", 'L', "print the maximum display width") - getopt.FlagLong(&intervalMs, "interval", 'i', + g := getopt.New() + var config Config + config.g = g + g.FlagLong(&config.Lines, "lines", 'l', "print the newline counts") + g.FlagLong(&config.Words, "words", 'w', "print the word counts") + g.FlagLong(&config.Chars, "chars", 'm', "print the character counts") + g.FlagLong(&config.Bytes, "bytes", 'c', "print the byte counts") + g.FlagLong(&config.MaxLineLength, "max-line-length", 'L', "print the maximum display width") + g.FlagLong(&intervalMs, "interval", 'i', fmt.Sprintf("set update interval in ms (default %d ms)", DEFAULT_INTERVAL)) - getopt.FlagLong(&config.Help, "help", 'h', "display this help and exit") - getopt.FlagLong(&config.Version, "version", 'V', "output version information and exit") - getopt.Parse() - config.Interval = time.Duration(intervalMs * 1e6) - config.Files = getopt.Args() + g.FlagLong(&config.Help, "help", 'h', "display this help and exit") + g.FlagLong(&config.Version, "version", 'V', "output version information and exit") + g.Parse(args) + if intervalMs < 0 { + log.Fatal("Update interval cannot be negative") + } + config.Interval = time.Duration(intervalMs) * time.Millisecond + config.Files = g.Args() if !(config.Lines || config.Words || config.Chars || config.Bytes) { config.Lines = true config.Words = true @@ -44,7 +55,3 @@ func BuildConfig() Config { } return config } - -func PrintUsage() { - getopt.PrintUsage(os.Stdout) -} diff --git a/internal/app/lwc/config_test.go b/internal/app/lwc/config_test.go new file mode 100644 index 0000000..5b3d32a --- /dev/null +++ b/internal/app/lwc/config_test.go @@ -0,0 +1,116 @@ +package lwc + +import ( + "reflect" + "testing" + "time" +) + +type configTest struct { + args []string + expected Config +} + +var configTests = []configTest{ + { + []string{}, + Config{ + true, true, false, true, false, + time.Duration(DEFAULT_INTERVAL) * time.Millisecond, + false, false, + []string{}, + nil, + }, + }, + { + []string{"-w", "--lines"}, + Config{ + true, true, false, false, false, + time.Duration(DEFAULT_INTERVAL) * time.Millisecond, + false, false, + []string{}, + nil, + }, + }, + { + []string{"foo"}, + Config{ + true, true, false, true, false, + time.Duration(DEFAULT_INTERVAL) * time.Millisecond, + false, false, + []string{"foo"}, + nil, + }, + }, + { + []string{"--", "/path/to/file"}, + Config{ + true, true, false, true, false, + time.Duration(DEFAULT_INTERVAL) * time.Millisecond, + false, false, + []string{"/path/to/file"}, + nil, + }, + }, + { + []string{"--max-line-length", "--bytes", "/etc/passwd", "/etc/group"}, + Config{ + false, false, false, true, true, + time.Duration(DEFAULT_INTERVAL) * time.Millisecond, + false, false, + []string{"/etc/passwd", "/etc/group"}, + nil, + }, + }, + { + []string{"-i", "5000"}, + Config{ + true, true, false, true, false, + time.Duration(5000) * time.Millisecond, + false, false, + []string{}, + nil, + }, + }, + { + []string{"--interval=2000"}, + Config{ + true, true, false, true, false, + time.Duration(2000) * time.Millisecond, + false, false, + []string{}, + nil, + }, + }, + { + []string{"--interval", "3000"}, + Config{ + true, true, false, true, false, + time.Duration(3000) * time.Millisecond, + false, false, + []string{}, + nil, + }, + }, + { + []string{"-i", "0"}, + Config{ + true, true, false, true, false, + time.Duration(0), + false, false, + []string{}, + nil, + }, + }, +} + +func TestBuildConfig(t *testing.T) { + for i, test := range configTests { + actual := BuildConfig(append([]string{"lwc"}, test.args...)) + // Clear getopt Set because we don't want to compare it + actual.g = nil + if !reflect.DeepEqual(test.expected, actual) { + t.Errorf("Test #%d failed: expecting %#v, got %#v", i, test.expected, actual) + } + } +} diff --git a/internal/app/lwc/output.go b/internal/app/lwc/output.go index 546763e..7a8645b 100644 --- a/internal/app/lwc/output.go +++ b/internal/app/lwc/output.go @@ -12,7 +12,7 @@ const CARRIAGE_RETURN byte = 13 const LINE_FEED byte = 10 const SPACE byte = 32 -func PrintCounts(counts *[]uint64, label string, cr bool, lf bool) { +func FormatCounts(counts *[]uint64, label string, cr bool, lf bool) string { var sb strings.Builder if cr { sb.WriteByte(CARRIAGE_RETURN) @@ -29,7 +29,11 @@ func PrintCounts(counts *[]uint64, label string, cr bool, lf bool) { if lf { sb.WriteByte(LINE_FEED) } - os.Stdout.WriteString(sb.String()) + return sb.String() +} + +func PrintCounts(counts *[]uint64, label string, cr bool, lf bool) { + os.Stdout.WriteString(FormatCounts(counts, label, cr, lf)) } func PollCounts(name string, counts *[]uint64, interval time.Duration, done chan bool) { diff --git a/internal/app/lwc/output_test.go b/internal/app/lwc/output_test.go new file mode 100644 index 0000000..acbb661 --- /dev/null +++ b/internal/app/lwc/output_test.go @@ -0,0 +1,93 @@ +package lwc + +import ( + "bufio" + "fmt" + "reflect" + "strings" + "testing" +) + +type formatTest struct { + counts []uint64 + label string + cr bool + lf bool +} + +func (t *formatTest) expected() []string { + result := make([]string, len(t.counts)) + for i, num := range t.counts { + result[i] = fmt.Sprintf("%d", num) + } + if t.label != "" { + result = append(result, t.label) + } + return result +} + +func withWithout(b bool) string { + if b { + return "with" + } else { + return "without" + } +} + +func tokenize(str string) []string { + tokens := make([]string, 100) + count := 0 + scanner := bufio.NewScanner(strings.NewReader(str)) + scanner.Split(bufio.ScanWords) + for scanner.Scan() { + tokens[count] = scanner.Text() + count++ + } + return tokens[0:count] +} + +var formatTests = []formatTest{ + { + []uint64{42939}, + "", + false, + false, + }, + { + []uint64{42, 2993}, + "bar", + true, + false, + }, + { + []uint64{90210}, + "baz-quux", + false, + true, + }, + { + []uint64{123, 4567, 899999}, + "/etc/passwd", + true, + true, + }, +} + +func TestFormatCounts(t *testing.T) { + for i, test := range formatTests { + result := FormatCounts(&test.counts, test.label, test.cr, test.lf) + hasCr := strings.HasPrefix(result, "\r") + if test.cr != hasCr { + t.Errorf("Test #%d failed: expecting string %s LF", i, withWithout(test.lf)) + } + hasLf := strings.HasSuffix(result, "\n") + if test.lf != hasLf { + t.Errorf("Test #%d failed: expecting string %s LF", i, withWithout(test.lf)) + } + actual := tokenize(result) + expected := test.expected() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Test #%d failed: expecting %#v, got %#v", i, expected, actual) + } + } +} diff --git a/internal/app/lwc/root.go b/internal/app/lwc/root.go index 91521f9..29ed478 100644 --- a/internal/app/lwc/root.go +++ b/internal/app/lwc/root.go @@ -2,11 +2,12 @@ package lwc import ( "fmt" + "os" ) func Run(version string) { // Read command-line args - config := BuildConfig() + config := BuildConfig(os.Args) switch { case config.Version: @@ -14,7 +15,7 @@ func Run(version string) { fmt.Printf("lwc %s\n", version) case config.Help: // Print usage and exit - PrintUsage() + config.PrintUsage() default: // Process input processors := BuildProcessors(&config) diff --git a/internal/app/lwc/scan.go b/internal/app/lwc/scan.go index db89710..13d2da9 100644 --- a/internal/app/lwc/scan.go +++ b/internal/app/lwc/scan.go @@ -7,6 +7,15 @@ import ( type ScanFunc func(*bufio.Scanner, *uint64, *uint64) +func ScanCount(scanner *bufio.Scanner, count *uint64, total *uint64) { + for scanner.Scan() { + atomic.AddUint64(count, 1) + if total != nil { + atomic.AddUint64(total, 1) + } + } +} + func ScanMaxLength(scanner *bufio.Scanner, count *uint64, total *uint64) { var localMax uint64 var globalMax uint64 @@ -26,12 +35,3 @@ func ScanMaxLength(scanner *bufio.Scanner, count *uint64, total *uint64) { } } } - -func ScanCount(scanner *bufio.Scanner, count *uint64, total *uint64) { - for scanner.Scan() { - atomic.AddUint64(count, 1) - if total != nil { - atomic.AddUint64(total, 1) - } - } -} diff --git a/internal/app/lwc/scan_test.go b/internal/app/lwc/scan_test.go new file mode 100644 index 0000000..a5ae10d --- /dev/null +++ b/internal/app/lwc/scan_test.go @@ -0,0 +1,76 @@ +package lwc + +import ( + "bufio" + "strings" + "testing" +) + +func testScanner(t *testing.T, scan ScanFunc, input string, expectedCount uint64, withTotal bool, initialTotal uint64, expectedTotal uint64) { + var actualCount uint64 + var actualTotalPtr *uint64 + if withTotal { + actualTotalPtr = &initialTotal + } else { + actualTotalPtr = nil + } + scanner := bufio.NewScanner(strings.NewReader(input)) + scanner.Split(bufio.ScanWords) + scan(scanner, &actualCount, actualTotalPtr) + if expectedCount != actualCount { + t.Errorf("Expecting count %d, got %d", expectedCount, actualCount) + } + if withTotal && expectedTotal != *actualTotalPtr { + t.Errorf("Expecting total %d, got %d", expectedTotal, *actualTotalPtr) + } +} + +func TestScanCountWithoutTotal(t *testing.T) { + testScanner(t, + ScanCount, + "one two three four five six", + 6, + false, + 0, + 0) +} + +func TestScanCountWithTotal(t *testing.T) { + testScanner(t, + ScanCount, + "one two three four five six", + 6, + true, + 0, + 6) +} + +func TestScanMaxLengthWithoutTotal(t *testing.T) { + testScanner(t, + ScanMaxLength, + "one two three four five six", + 5, + false, + 0, + 0) +} + +func TestScanMaxLengthWithLowerTotal(t *testing.T) { + testScanner(t, + ScanMaxLength, + "one two three four five six", + 5, + true, + 0, + 5) +} + +func TestScanMaxLengthWithHigherTotal(t *testing.T) { + testScanner(t, + ScanMaxLength, + "one two three four five six", + 5, + true, + 6, + 6) +}