util/testenv: add ArtifactDir, Attr, Output methods to TB

The TB interface exists to mirror testing.TB without importing the
testing package, but it had fallen behind: Go 1.25 added Attr and
Output, and Go 1.26 added ArtifactDir. Add the missing methods and a
reflection-based test that TB has every exported method of testing.TB,
so future additions to testing.TB fail a test instead of silently
diverging. It can't be a compile-time assertion because testing.TB has
an unexported method.

Updates #16330
Updates #18682

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9ba093afefdf3a6311ef4648bc1a13add9af453d
This commit is contained in:
Brad Fitzpatrick
2026-07-20 21:20:11 -07:00
committed by Brad Fitzpatrick
parent a7cb5745a2
commit 4ad1243332
2 changed files with 26 additions and 0 deletions
+4
View File
@@ -8,6 +8,7 @@ package testenv
import ( import (
"context" "context"
"flag" "flag"
"io"
"tailscale.com/types/lazy" "tailscale.com/types/lazy"
) )
@@ -23,6 +24,8 @@ func InTest() bool {
// TB is testing.TB, to avoid importing "testing" in non-test code. // TB is testing.TB, to avoid importing "testing" in non-test code.
type TB interface { type TB interface {
ArtifactDir() string
Attr(key, value string)
Cleanup(func()) Cleanup(func())
Error(args ...any) Error(args ...any)
Errorf(format string, args ...any) Errorf(format string, args ...any)
@@ -35,6 +38,7 @@ type TB interface {
Log(args ...any) Log(args ...any)
Logf(format string, args ...any) Logf(format string, args ...any)
Name() string Name() string
Output() io.Writer
Setenv(key, value string) Setenv(key, value string)
Chdir(dir string) Chdir(dir string)
Skip(args ...any) Skip(args ...any)
+22
View File
@@ -4,6 +4,7 @@
package testenv package testenv
import ( import (
"reflect"
"testing" "testing"
"tailscale.com/tstest/deptest" "tailscale.com/tstest/deptest"
@@ -29,3 +30,24 @@ func TestInParallelTestFalse(t *testing.T) {
t.Fatal("InParallelTest should return false before t.Parallel has been called") t.Fatal("InParallelTest should return false before t.Parallel has been called")
} }
} }
// TestMatchesTestingTB verifies that TB has every exported method of
// testing.TB, with matching signatures. It can't be a compile-time
// assertion because testing.TB has an unexported method.
func TestMatchesTestingTB(t *testing.T) {
want := reflect.TypeFor[testing.TB]()
got := reflect.TypeFor[TB]()
for m := range want.Methods() {
if m.PkgPath != "" {
continue // unexported
}
gm, ok := got.MethodByName(m.Name)
if !ok {
t.Errorf("TB lacks method %s%v", m.Name, m.Type)
continue
}
if gm.Type != m.Type {
t.Errorf("TB method %s has type %v; want %v", m.Name, gm.Type, m.Type)
}
}
}