Skip to content

Commit 10db468

Browse files
authored
Merge pull request #147 from glesica/add-file-assertion
Add file assertion
2 parents 75391aa + 571c5e4 commit 10db468

15 files changed

+150
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Add ability to test suite from a url
44
- Add ability to test suite from stdin
5+
- Add `file` assertion to `stdout` and `stderr`
56

67
# v2.3.0
78

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ For more information take a look at the [quick start](#quick-start), the [exampl
4040
* [line-count](#line-count)
4141
* [not-contains](#not-contains)
4242
* [xml](#xml)
43+
* [file](#file)
4344
- [stderr](#stderr)
4445
- [skip](#skip)
4546
+ [Config](#user-content-config-config)
@@ -167,6 +168,7 @@ tests:
167168
object.attr: hello # Make assertions on json objects
168169
xml:
169170
"//book//auhtor": Steven King # Make assertions on xml documents
171+
file: correct-output.txt
170172
exit-code: 127
171173
skip: false
172174

@@ -529,6 +531,21 @@ cat some.xml:
529531
</book>
530532
```
531533

534+
##### file
535+
536+
`file` is a file path, relative to the working directory that will have
537+
its entire contents matched against the command output. Other than
538+
reading from a file this works the same as [exactly](#exactly).
539+
540+
The example below will always pass.
541+
542+
```yaml
543+
output should match file:
544+
command: cat output.txt
545+
stdout:
546+
file: output.txt
547+
```
548+
532549
#### stderr
533550

534551
See [stdout](#stdout) for more information.

commander_unix.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@ tests:
1616
contains:
1717
- ✓ [local] it should exit with error code
1818
- "- [local] it should skip, was skipped"
19-
line-count: 17
19+
line-count: 19
2020
exit-code: 0
2121

2222
it should assert that commander will fail:
2323
command: ./commander test ./integration/unix/failing_suite.yaml
2424
stdout:
2525
contains:
26+
- ✗ [local] 'file matcher should fail when file and output are different', on property 'Stdout'
2627
- ✗ [local] 'it will fail', on property 'ExitCode'
2728
- ✗ [local] 'test timeout' could not be executed with error message
2829
- Command timed out after 10ms
29-
- "Count: 2, Failed: 2"
30+
- "Count: 3, Failed: 3"
3031
exit-code: 1
3132

3233
it should validate a big output:

examples/_fixtures/output.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
line one
2+
line two

examples/commander.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ tests:
2424
it should skip:
2525
command: echo "I should be skipped"
2626
stdout: I should be skipped
27-
skip: true
27+
skip: true
28+
29+
it should match file output:
30+
command: printf "line one\nline two"
31+
stdout:
32+
file: ../../examples/_fixtures/output.txt
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
first line
2+
second line
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
first line
2+
second line
3+
third line

integration/unix/commander_test.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ tests:
5959
/books/0/author: J. R. R. Tokien
6060
/books/1/author: Joanne K. Rowling
6161

62+
it should assert file contents on stdout:
63+
command: cat ./integration/unix/_fixtures/big_out.txt
64+
stdout:
65+
file: ./integration/unix/_fixtures/big_out.txt
66+
67+
it should assert file contents on stderr:
68+
command: cat ./integration/unix/_fixtures/big_out.txt >&2
69+
stderr:
70+
file: ./integration/unix/_fixtures/big_out.txt
71+
6272
it should inherit from parent env:
6373
config:
6474
inherit-env: true

integration/unix/failing_suite.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ tests:
77
command: sleep 1
88
config:
99
timeout: 10ms
10-
exit-code: 0
10+
exit-code: 0
11+
12+
file matcher should fail when file and output are different:
13+
command: cat ./integration/unix/_fixtures/file_output_1.txt
14+
stdout:
15+
file: ./integration/unix/_fixtures/file_output_0.txt

pkg/matcher/matcher.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/antchfx/xmlquery"
66
"github.com/pmezard/go-difflib/difflib"
77
"github.com/tidwall/gjson"
8+
"io/ioutil"
89
"log"
910
"strings"
1011
)
@@ -20,8 +21,13 @@ const (
2021
NotContains = "notcontains"
2122
JSON = "json"
2223
XML = "xml"
24+
File = "file"
2325
)
2426

27+
// The function used to open files when necessary for matching
28+
// Allows the file IO to be overridden during tests
29+
var ReadFile = ioutil.ReadFile
30+
2531
// NewMatcher creates a new matcher by type
2632
func NewMatcher(matcher string) Matcher {
2733
switch matcher {
@@ -37,6 +43,8 @@ func NewMatcher(matcher string) Matcher {
3743
return JSONMatcher{}
3844
case XML:
3945
return XMLMatcher{}
46+
case File:
47+
return FileMatcher{}
4048
default:
4149
panic(fmt.Sprintf("Validator '%s' does not exist!", matcher))
4250
}
@@ -240,3 +248,32 @@ to be equal to
240248

241249
return result
242250
}
251+
252+
// FileMatcher matches output captured from stdout or stderr
253+
// against the contents of a file
254+
type FileMatcher struct {
255+
}
256+
257+
func (m FileMatcher) Match(got interface{}, expected interface{}) MatcherResult {
258+
expectedText, err := ReadFile(expected.(string))
259+
if err != nil {
260+
panic(err.Error())
261+
}
262+
expectedString := string(expectedText)
263+
264+
result := got == expectedString
265+
266+
diff := difflib.UnifiedDiff{
267+
A: difflib.SplitLines(got.(string)),
268+
B: difflib.SplitLines(expectedString),
269+
FromFile: "Got",
270+
ToFile: "Expected",
271+
Context: 3,
272+
}
273+
diffText, _ := difflib.GetUnifiedDiffString(diff)
274+
275+
return MatcherResult{
276+
Diff: diffText,
277+
Success: result,
278+
}
279+
}

pkg/matcher/matcher_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ func TestTextMatcher_ValidateFails(t *testing.T) {
4848

4949
func TestEqualMatcher_Validate(t *testing.T) {
5050
m := EqualMatcher{}
51-
got := m.Match(1, 1)
51+
got := m.Match(2, 2)
5252
assert.True(t, got.Success)
5353
}
5454

5555
func TestEqualMatcher_ValidateFails(t *testing.T) {
5656
m := EqualMatcher{}
57-
got := m.Match(1, 0)
57+
got := m.Match(2, 3)
5858
assert.False(t, got.Success)
59-
assert.Contains(t, got.Diff, "+0")
60-
assert.Contains(t, got.Diff, "-1")
59+
assert.Contains(t, got.Diff, "+3")
60+
assert.Contains(t, got.Diff, "-2")
6161
}
6262

6363
func TestContainsMatcher_Match(t *testing.T) {
@@ -183,3 +183,24 @@ another`
183183
assert.False(t, r.Success)
184184
assert.Equal(t, diff, r.Diff)
185185
}
186+
187+
func TestFileMatcher_Match(t *testing.T) {
188+
ReadFile = func(filename string) ([]byte, error) {
189+
return []byte("line one\nline two"), nil
190+
}
191+
m := FileMatcher{}
192+
got := m.Match("line one\nline two", "fake.txt")
193+
assert.True(t, got.Success)
194+
assert.Equal(t, "", got.Diff)
195+
}
196+
197+
func TestFileMatcher_ValidateFails(t *testing.T) {
198+
ReadFile = func(filename string) ([]byte, error) {
199+
return []byte("line one\nline two"), nil
200+
}
201+
m := FileMatcher{}
202+
got := m.Match("line one\nline three", "fake.txt")
203+
assert.False(t, got.Success)
204+
assert.Contains(t, got.Diff, "+line two")
205+
assert.Contains(t, got.Diff, "-line three")
206+
}

pkg/runtime/runtime.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type ExpectedOut struct {
9999
NotContains []string `yaml:"not-contains,omitempty"`
100100
JSON map[string]string `yaml:"json,omitempty"`
101101
XML map[string]string `yaml:"xml,omitempty"`
102+
File string `yaml:"file,omitempty"`
102103
}
103104

104105
// CommandUnderTest represents the command under test

pkg/runtime/validator.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ func validateExpectedOut(got string, expected ExpectedOut) matcher.MatcherResult
118118
}
119119
}
120120

121+
if expected.File != "" {
122+
m = matcher.NewMatcher(matcher.File)
123+
if result = m.Match(got, expected.File); !result.Success {
124+
return result
125+
}
126+
}
127+
121128
return result
122129
}
123130

pkg/runtime/validator_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package runtime
33
import (
44
"github.com/commander-cli/commander/pkg/matcher"
55
"github.com/stretchr/testify/assert"
6+
"io/ioutil"
67
"testing"
78
)
89

@@ -192,6 +193,29 @@ test`
192193
assert.Equal(t, diff, r.Diff)
193194
}
194195

196+
func Test_ValidateExpectedOut_ValidateFile(t *testing.T) {
197+
content := "line one"
198+
matcher.ReadFile = func(filename string) ([]byte, error) {
199+
return []byte(content), nil
200+
}
201+
r := validateExpectedOut(content, ExpectedOut{File: "fake.txt"})
202+
assert.True(t, r.Success)
203+
assert.Equal(t, "", r.Diff)
204+
205+
diff := `--- Got
206+
+++ Expected
207+
@@ -1,2 +1 @@
208+
line one
209+
-line two
210+
`
211+
212+
r = validateExpectedOut(content+"\nline two", ExpectedOut{File: "fake.txt"})
213+
assert.False(t, r.Success)
214+
assert.Equal(t, diff, r.Diff)
215+
216+
matcher.ReadFile = ioutil.ReadFile
217+
}
218+
195219
func Test_ValidateExpectedOut_ValidateXML(t *testing.T) {
196220
xml := `<book>
197221
<author>J. R. R. Tolkien</author>

pkg/suite/yaml_suite.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ func (y *YAMLSuiteConf) convertToExpectedOut(value interface{}) runtime.Expected
222222
"lines",
223223
"json",
224224
"xml",
225+
"file",
225226
"not-contains":
226227
break
227228
default:
@@ -242,6 +243,11 @@ func (y *YAMLSuiteConf) convertToExpectedOut(value interface{}) runtime.Expected
242243
exp.Exactly = toString(exactly)
243244
}
244245

246+
// Parse file key
247+
if file := v["file"]; file != nil {
248+
exp.File = toString(file)
249+
}
250+
245251
//Parse line-count key
246252
if lc := v["line-count"]; lc != nil {
247253
exp.LineCount = lc.(int)

0 commit comments

Comments
 (0)