mirror of https://github.com/golang/go.git
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
|
|
@ -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
|
||||||
|
|
@ -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"`
|
Elapsed *float64 `json:",omitempty"`
|
||||||
Output *textBytes `json:",omitempty"`
|
Output *textBytes `json:",omitempty"`
|
||||||
FailedBuild string `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
|
// textBytes is a hack to get JSON to emit a []byte as a string
|
||||||
|
|
@ -177,6 +179,7 @@ var (
|
||||||
[]byte("=== PASS "),
|
[]byte("=== PASS "),
|
||||||
[]byte("=== FAIL "),
|
[]byte("=== FAIL "),
|
||||||
[]byte("=== SKIP "),
|
[]byte("=== SKIP "),
|
||||||
|
[]byte("=== ATTR "),
|
||||||
}
|
}
|
||||||
|
|
||||||
reports = [][]byte{
|
reports = [][]byte{
|
||||||
|
|
@ -333,6 +336,11 @@ func (c *Converter) handleInputLine(line []byte) {
|
||||||
c.output.write(origLine)
|
c.output.write(origLine)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if action == "attr" {
|
||||||
|
var rest string
|
||||||
|
name, rest, _ = strings.Cut(name, " ")
|
||||||
|
e.Key, e.Value, _ = strings.Cut(rest, " ")
|
||||||
|
}
|
||||||
// === update.
|
// === update.
|
||||||
// Finish any pending PASS/FAIL reports.
|
// Finish any pending PASS/FAIL reports.
|
||||||
c.needMarker = sawMarker
|
c.needMarker = sawMarker
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
@ -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].
|
// TB is the interface common to [T], [B], and [F].
|
||||||
type TB interface {
|
type TB interface {
|
||||||
|
Attr(key, value string)
|
||||||
Cleanup(func())
|
Cleanup(func())
|
||||||
Error(args ...any)
|
Error(args ...any)
|
||||||
Errorf(format string, args ...any)
|
Errorf(format string, args ...any)
|
||||||
|
|
@ -1491,6 +1492,31 @@ func (c *common) Context() context.Context {
|
||||||
return c.ctx
|
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.
|
// panicHandling controls the panic handling used by runCleanup.
|
||||||
type panicHandling int
|
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) {
|
func TestBenchmarkBLoopIterationCorrect(t *testing.T) {
|
||||||
out := runTest(t, "BenchmarkBLoopPrint")
|
out := runTest(t, "BenchmarkBLoopPrint")
|
||||||
c := bytes.Count(out, []byte("Printing from BenchmarkBLoopPrint"))
|
c := bytes.Count(out, []byte("Printing from BenchmarkBLoopPrint"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue