1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package testing
6
7import (
8	"fmt"
9	"slices"
10	"strings"
11	"time"
12)
13
14type InternalExample struct {
15	Name      string
16	F         func()
17	Output    string
18	Unordered bool
19}
20
21// RunExamples is an internal function but exported because it is cross-package;
22// it is part of the implementation of the "go test" command.
23func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool) {
24	_, ok = runExamples(matchString, examples)
25	return ok
26}
27
28func runExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ran, ok bool) {
29	ok = true
30
31	m := newMatcher(matchString, *match, "-test.run", *skip)
32
33	var eg InternalExample
34	for _, eg = range examples {
35		_, matched, _ := m.fullName(nil, eg.Name)
36		if !matched {
37			continue
38		}
39		ran = true
40		if !runExample(eg) {
41			ok = false
42		}
43	}
44
45	return ran, ok
46}
47
48func sortLines(output string) string {
49	lines := strings.Split(output, "\n")
50	slices.Sort(lines)
51	return strings.Join(lines, "\n")
52}
53
54// processRunResult computes a summary and status of the result of running an example test.
55// stdout is the captured output from stdout of the test.
56// recovered is the result of invoking recover after running the test, in case it panicked.
57//
58// If stdout doesn't match the expected output or if recovered is non-nil, it'll print the cause of failure to stdout.
59// If the test is chatty/verbose, it'll print a success message to stdout.
60// If recovered is non-nil, it'll panic with that value.
61// If the test panicked with nil, or invoked runtime.Goexit, it'll be
62// made to fail and panic with errNilPanicOrGoexit
63func (eg *InternalExample) processRunResult(stdout string, timeSpent time.Duration, finished bool, recovered any) (passed bool) {
64	passed = true
65	dstr := fmtDuration(timeSpent)
66	var fail string
67	got := strings.TrimSpace(stdout)
68	want := strings.TrimSpace(eg.Output)
69	if eg.Unordered {
70		if sortLines(got) != sortLines(want) && recovered == nil {
71			fail = fmt.Sprintf("got:\n%s\nwant (unordered):\n%s\n", stdout, eg.Output)
72		}
73	} else {
74		if got != want && recovered == nil {
75			fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want)
76		}
77	}
78	if fail != "" || !finished || recovered != nil {
79		fmt.Printf("%s--- FAIL: %s (%s)\n%s", chatty.prefix(), eg.Name, dstr, fail)
80		passed = false
81	} else if chatty.on {
82		fmt.Printf("%s--- PASS: %s (%s)\n", chatty.prefix(), eg.Name, dstr)
83	}
84
85	if chatty.on && chatty.json {
86		fmt.Printf("%s=== NAME   %s\n", chatty.prefix(), "")
87	}
88
89	if recovered != nil {
90		// Propagate the previously recovered result, by panicking.
91		panic(recovered)
92	} else if !finished {
93		panic(errNilPanicOrGoexit)
94	}
95
96	return
97}
98