mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
testing: add Attr
Add a new Attr method to testing.TB that emits a test attribute. An attribute is an arbitrary key/value pair. Fixes #43936 Change-Id: I7ef299efae41f2cf39f2dc61ad4cdd4c3975cdb6 Reviewed-on: https://go-review.googlesource.com/c/go/+/662437 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
parent
763963505e
commit
3cc8b532f9
7 changed files with 117 additions and 0 deletions
4
api/next/43936.txt
Normal file
4
api/next/43936.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
pkg testing, method (*B) Attr(string, string) #43936
|
||||
pkg testing, method (*F) Attr(string, string) #43936
|
||||
pkg testing, method (*T) Attr(string, string) #43936
|
||||
pkg testing, type TB interface, Attr(string, string) #43936
|
10
doc/next/6-stdlib/99-minor/testing/43936.md
Normal file
10
doc/next/6-stdlib/99-minor/testing/43936.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
The new methods [T.Attr], [B.Attr], and [F.Attr] emit an
|
||||
attribute to the test log. An attribute is an arbitrary
|
||||
key and value associated with a test.
|
||||
|
||||
For example, in a test named `TestAttr`,
|
||||
`t.Attr("key", "value")` emits:
|
||||
|
||||
```
|
||||
=== ATTR TestAttr key value
|
||||
```
|
|
@ -36,6 +36,8 @@ type event struct {
|
|||
Elapsed *float64 `json:",omitempty"`
|
||||
Output *textBytes `json:",omitempty"`
|
||||
FailedBuild string `json:",omitempty"`
|
||||
Key string `json:",omitempty"`
|
||||
Value string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// textBytes is a hack to get JSON to emit a []byte as a string
|
||||
|
@ -177,6 +179,7 @@ var (
|
|||
[]byte("=== PASS "),
|
||||
[]byte("=== FAIL "),
|
||||
[]byte("=== SKIP "),
|
||||
[]byte("=== ATTR "),
|
||||
}
|
||||
|
||||
reports = [][]byte{
|
||||
|
@ -333,6 +336,11 @@ func (c *Converter) handleInputLine(line []byte) {
|
|||
c.output.write(origLine)
|
||||
return
|
||||
}
|
||||
if action == "attr" {
|
||||
var rest string
|
||||
name, rest, _ = strings.Cut(name, " ")
|
||||
e.Key, e.Value, _ = strings.Cut(rest, " ")
|
||||
}
|
||||
// === update.
|
||||
// Finish any pending PASS/FAIL reports.
|
||||
c.needMarker = sawMarker
|
||||
|
|
15
src/cmd/internal/test2json/testdata/attr.json
vendored
Normal file
15
src/cmd/internal/test2json/testdata/attr.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{"Action":"start"}
|
||||
{"Action":"run","Test":"TestAttr"}
|
||||
{"Action":"output","Test":"TestAttr","Output":"=== RUN TestAttr\n"}
|
||||
{"Action":"attr","Test":"TestAttr","Key":"key","Value":"value"}
|
||||
{"Action":"output","Test":"TestAttr","Output":"=== ATTR TestAttr key value\n"}
|
||||
{"Action":"run","Test":"TestAttr/sub"}
|
||||
{"Action":"output","Test":"TestAttr/sub","Output":"=== RUN TestAttr/sub\n"}
|
||||
{"Action":"attr","Test":"TestAttr/sub","Key":"key","Value":"value"}
|
||||
{"Action":"output","Test":"TestAttr/sub","Output":"=== ATTR TestAttr/sub key value\n"}
|
||||
{"Action":"output","Test":"TestAttr","Output":"--- PASS: TestAttr (0.00s)\n"}
|
||||
{"Action":"output","Test":"TestAttr/sub","Output":" --- PASS: TestAttr/sub (0.00s)\n"}
|
||||
{"Action":"pass","Test":"TestAttr/sub"}
|
||||
{"Action":"pass","Test":"TestAttr"}
|
||||
{"Action":"output","Output":"PASS\n"}
|
||||
{"Action":"pass"}
|
7
src/cmd/internal/test2json/testdata/attr.test
vendored
Normal file
7
src/cmd/internal/test2json/testdata/attr.test
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
=== RUN TestAttr
|
||||
=== ATTR TestAttr key value
|
||||
=== RUN TestAttr/sub
|
||||
=== ATTR TestAttr/sub key value
|
||||
--- PASS: TestAttr (0.00s)
|
||||
--- PASS: TestAttr/sub (0.00s)
|
||||
PASS
|
|
@ -879,6 +879,7 @@ func fmtDuration(d time.Duration) string {
|
|||
|
||||
// TB is the interface common to [T], [B], and [F].
|
||||
type TB interface {
|
||||
Attr(key, value string)
|
||||
Cleanup(func())
|
||||
Error(args ...any)
|
||||
Errorf(format string, args ...any)
|
||||
|
@ -1491,6 +1492,31 @@ func (c *common) Context() context.Context {
|
|||
return c.ctx
|
||||
}
|
||||
|
||||
// Attr emits a test attribute associated with this test.
|
||||
//
|
||||
// The key must not contain whitespace.
|
||||
// The value must not contain newlines or carriage returns.
|
||||
//
|
||||
// The meaning of different attribute keys is left up to
|
||||
// continuous integration systems and test frameworks.
|
||||
//
|
||||
// Test attributes are emitted immediately in the test log,
|
||||
// but they are intended to be treated as unordered.
|
||||
func (c *common) Attr(key, value string) {
|
||||
if strings.ContainsFunc(key, unicode.IsSpace) {
|
||||
c.Errorf("disallowed whitespace in attribute key %q", key)
|
||||
return
|
||||
}
|
||||
if strings.ContainsAny(value, "\r\n") {
|
||||
c.Errorf("disallowed newline in attribute value %q", value)
|
||||
return
|
||||
}
|
||||
if c.chatty == nil {
|
||||
return
|
||||
}
|
||||
c.chatty.Updatef(c.name, "=== ATTR %s %v %v\n", c.name, key, value)
|
||||
}
|
||||
|
||||
// panicHandling controls the panic handling used by runCleanup.
|
||||
type panicHandling int
|
||||
|
||||
|
|
|
@ -975,6 +975,53 @@ func TestContext(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// TestAttrExample is used by TestAttrSet,
|
||||
// and also serves as a convenient test to run that sets an attribute.
|
||||
func TestAttrExample(t *testing.T) {
|
||||
t.Attr("key", "value")
|
||||
}
|
||||
|
||||
func TestAttrSet(t *testing.T) {
|
||||
out := string(runTest(t, "TestAttrExample"))
|
||||
|
||||
want := "=== ATTR TestAttrExample key value\n"
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("expected output containing %q, got:\n%q", want, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttrInvalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
key string
|
||||
value string
|
||||
}{
|
||||
{"k ey", "value"},
|
||||
{"k\tey", "value"},
|
||||
{"k\rey", "value"},
|
||||
{"k\ney", "value"},
|
||||
{"key", "val\rue"},
|
||||
{"key", "val\nue"},
|
||||
}
|
||||
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
t.Attr(test.key, test.value)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
out := string(runTest(t, "TestAttrInvalid"))
|
||||
|
||||
for i := range tests {
|
||||
want := fmt.Sprintf("--- FAIL: TestAttrInvalid/%v ", i)
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("expected output containing %q, got:\n%q", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBenchmarkBLoopIterationCorrect(t *testing.T) {
|
||||
out := runTest(t, "BenchmarkBLoopPrint")
|
||||
c := bytes.Count(out, []byte("Printing from BenchmarkBLoopPrint"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue