Skip to content

Commit 9323de3

Browse files
mslintonadg
authored andcommitted
testing: implement 'Unordered Output' in Examples.
Adds a type of output to Examples that allows tests to have unordered output. This is intended to help clarify when the output of a command will produce a fixed return, but that return might not be in an constant order. Examples where this is useful would be documenting the rand.Perm() call, or perhaps the (os.File).Readdir(), both of which can not guarantee order, but can guarantee the elements of the output. Fixes #10149 Change-Id: Iaf0cf1580b686afebd79718ed67ea744f5ed9fc5 Reviewed-on: https://go-review.googlesource.com/19280 Reviewed-by: Andrew Gerrand <[email protected]>
1 parent a9c48f3 commit 9323de3

File tree

5 files changed

+95
-33
lines changed

5 files changed

+95
-33
lines changed

src/cmd/go/alldocs.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,10 +1537,11 @@ A benchmark function is one named BenchmarkXXX and should have the signature,
15371537
15381538
An example function is similar to a test function but, instead of using
15391539
*testing.T to report success or failure, prints output to os.Stdout.
1540-
That output is compared against the function's "Output:" comment, which
1541-
must be the last comment in the function body (see example below). An
1542-
example with no such comment, or with no text after "Output:" is compiled
1543-
but not executed.
1540+
If the last comment in the function starts with "Output:" then the output
1541+
is compared exactly against the comment (see examples below). If the last
1542+
comment begins with "Unordered output:" then the output is compared to the
1543+
comment, however the order of the lines is ignored. An example with no such
1544+
comment, or with no text after "Output:" is compiled but not executed.
15441545
15451546
Godoc displays the body of ExampleXXX to demonstrate the use
15461547
of the function, constant, or variable XXX. An example of a method M with
@@ -1556,6 +1557,19 @@ Here is an example of an example:
15561557
// this example.
15571558
}
15581559
1560+
Here is another example where the ordering of the output is ignored:
1561+
1562+
func ExamplePerm() {
1563+
for _, value := range Perm(4) {
1564+
fmt.Println(value)
1565+
}
1566+
// Unordered output: 4
1567+
// 2
1568+
// 1
1569+
// 3
1570+
// 0
1571+
}
1572+
15591573
The entire test file is presented as the example when it contains a single
15601574
example function, at least one other function, type, variable, or constant
15611575
declaration, and no test or benchmark functions.

src/cmd/go/test.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,11 @@ A benchmark function is one named BenchmarkXXX and should have the signature,
311311
312312
An example function is similar to a test function but, instead of using
313313
*testing.T to report success or failure, prints output to os.Stdout.
314-
That output is compared against the function's "Output:" comment, which
315-
must be the last comment in the function body (see example below). An
316-
example with no such comment, or with no text after "Output:" is compiled
317-
but not executed.
314+
If the last comment in the function starts with "Output:" then the output
315+
is compared exactly against the comment (see examples below). If the last
316+
comment begins with "Unordered output:" then the output is compared to the
317+
comment, however the order of the lines is ignored. An example with no such
318+
comment, or with no text after "Output:" is compiled but not executed.
318319
319320
Godoc displays the body of ExampleXXX to demonstrate the use
320321
of the function, constant, or variable XXX. An example of a method M with
@@ -330,6 +331,20 @@ Here is an example of an example:
330331
// this example.
331332
}
332333
334+
Here is another example where the ordering of the output is ignored:
335+
336+
func ExamplePerm() {
337+
for _, value := range Perm(4) {
338+
fmt.Println(value)
339+
}
340+
341+
// Unordered output: 4
342+
// 2
343+
// 1
344+
// 3
345+
// 0
346+
}
347+
333348
The entire test file is presented as the example when it contains a single
334349
example function, at least one other function, type, variable, or constant
335350
declaration, and no test or benchmark functions.
@@ -1323,9 +1338,10 @@ func (t *testFuncs) Tested() string {
13231338
}
13241339

13251340
type testFunc struct {
1326-
Package string // imported package name (_test or _xtest)
1327-
Name string // function name
1328-
Output string // output, for examples
1341+
Package string // imported package name (_test or _xtest)
1342+
Name string // function name
1343+
Output string // output, for examples
1344+
Unordered bool // output is allowed to be unordered.
13291345
}
13301346

13311347
var testFileSet = token.NewFileSet()
@@ -1349,21 +1365,21 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
13491365
if t.TestMain != nil {
13501366
return errors.New("multiple definitions of TestMain")
13511367
}
1352-
t.TestMain = &testFunc{pkg, name, ""}
1368+
t.TestMain = &testFunc{pkg, name, "", false}
13531369
*doImport, *seen = true, true
13541370
case isTest(name, "Test"):
13551371
err := checkTestFunc(n, "T")
13561372
if err != nil {
13571373
return err
13581374
}
1359-
t.Tests = append(t.Tests, testFunc{pkg, name, ""})
1375+
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
13601376
*doImport, *seen = true, true
13611377
case isTest(name, "Benchmark"):
13621378
err := checkTestFunc(n, "B")
13631379
if err != nil {
13641380
return err
13651381
}
1366-
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""})
1382+
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
13671383
*doImport, *seen = true, true
13681384
}
13691385
}
@@ -1375,7 +1391,7 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
13751391
// Don't run examples with no output.
13761392
continue
13771393
}
1378-
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output})
1394+
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
13791395
*seen = true
13801396
}
13811397
return nil
@@ -1435,7 +1451,7 @@ var benchmarks = []testing.InternalBenchmark{
14351451
14361452
var examples = []testing.InternalExample{
14371453
{{range .Examples}}
1438-
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}},
1454+
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
14391455
{{end}}
14401456
}
14411457

src/go/doc/example.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ type Example struct {
2626
Play *ast.File // a whole program version of the example
2727
Comments []*ast.CommentGroup
2828
Output string // expected output
29-
EmptyOutput bool // expect empty output
30-
Order int // original source code order
29+
Unordered bool
30+
EmptyOutput bool // expect empty output
31+
Order int // original source code order
3132
}
3233

3334
// Examples returns the examples found in the files, sorted by Name field.
@@ -71,14 +72,15 @@ func Examples(files ...*ast.File) []*Example {
7172
if f.Doc != nil {
7273
doc = f.Doc.Text()
7374
}
74-
output, hasOutput := exampleOutput(f.Body, file.Comments)
75+
output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
7576
flist = append(flist, &Example{
7677
Name: name[len("Example"):],
7778
Doc: doc,
7879
Code: f.Body,
7980
Play: playExample(file, f.Body),
8081
Comments: file.Comments,
8182
Output: output,
83+
Unordered: unordered,
8284
EmptyOutput: output == "" && hasOutput,
8385
Order: len(flist),
8486
})
@@ -96,24 +98,27 @@ func Examples(files ...*ast.File) []*Example {
9698
return list
9799
}
98100

99-
var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
101+
var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*(unordered )?output:`)
100102

101103
// Extracts the expected output and whether there was a valid output comment
102-
func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, ok bool) {
104+
func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
103105
if _, last := lastComment(b, comments); last != nil {
104106
// test that it begins with the correct prefix
105107
text := last.Text()
106-
if loc := outputPrefix.FindStringIndex(text); loc != nil {
108+
if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
109+
if loc[2] != -1 {
110+
unordered = true
111+
}
107112
text = text[loc[1]:]
108113
// Strip zero or more spaces followed by \n or a single space.
109114
text = strings.TrimLeft(text, " ")
110115
if len(text) > 0 && text[0] == '\n' {
111116
text = text[1:]
112117
}
113-
return text, true
118+
return text, unordered, true
114119
}
115120
}
116-
return "", false // no suitable comment found
121+
return "", false, false // no suitable comment found
117122
}
118123

119124
// isTest tells whether name looks like a test, example, or benchmark.
@@ -255,7 +260,8 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
255260
}
256261
}
257262

258-
// Strip "Output:" comment and adjust body end position.
263+
// Strip the "Output:" or "Unordered output:" comment and adjust body
264+
// end position.
259265
body, comments = stripOutputComment(body, comments)
260266

261267
// Synthesize import declaration.
@@ -318,10 +324,10 @@ func playExampleFile(file *ast.File) *ast.File {
318324
return &f
319325
}
320326

321-
// stripOutputComment finds and removes an "Output:" comment from body
322-
// and comments, and adjusts the body block's end position.
327+
// stripOutputComment finds and removes the "Output:" or "Unordered output:"
328+
// comment from body and comments, and adjusts the body block's end position.
323329
func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
324-
// Do nothing if no "Output:" comment found.
330+
// Do nothing if there is no "Output:" or "Unordered output:" comment.
325331
i, last := lastComment(body, comments)
326332
if last == nil || !outputPrefix.MatchString(last.Text()) {
327333
return body, comments

src/math/rand/example_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,13 @@ func Example_rand() {
9595
// Int63n(10) 7 6 3
9696
// Perm [1 4 2 3 0] [4 2 1 3 0] [1 2 4 0 3]
9797
}
98+
99+
func ExamplePerm() {
100+
for _, value := range rand.Perm(3) {
101+
fmt.Println(value)
102+
}
103+
104+
// Unordered output: 1
105+
// 2
106+
// 0
107+
}

src/testing/example.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import (
99
"fmt"
1010
"io"
1111
"os"
12+
"sort"
1213
"strings"
1314
"time"
1415
)
1516

1617
type InternalExample struct {
17-
Name string
18-
F func()
19-
Output string
18+
Name string
19+
F func()
20+
Output string
21+
Unordered bool
2022
}
2123

2224
func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool) {
@@ -41,6 +43,12 @@ func RunExamples(matchString func(pat, str string) (bool, error), examples []Int
4143
return
4244
}
4345

46+
func sortLines(output string) string {
47+
lines := strings.Split(output, "\n")
48+
sort.Strings(lines)
49+
return strings.Join(lines, "\n")
50+
}
51+
4452
func runExample(eg InternalExample) (ok bool) {
4553
if *chatty {
4654
fmt.Printf("=== RUN %s\n", eg.Name)
@@ -80,8 +88,16 @@ func runExample(eg InternalExample) (ok bool) {
8088

8189
var fail string
8290
err := recover()
83-
if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e && err == nil {
84-
fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", g, e)
91+
got := strings.TrimSpace(out)
92+
want := strings.TrimSpace(eg.Output)
93+
if eg.Unordered {
94+
if sortLines(got) != sortLines(want) && err == nil {
95+
fail = fmt.Sprintf("got:\n%s\nwant (unordered):\n%s\n", out, eg.Output)
96+
}
97+
} else {
98+
if got != want && err == nil {
99+
fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want)
100+
}
85101
}
86102
if fail != "" || err != nil {
87103
fmt.Printf("--- FAIL: %s (%s)\n%s", eg.Name, dstr, fail)

0 commit comments

Comments
 (0)