playground: make isTest match its documentation
This fixes a bug for names such as "Test1", noticed while I was
writing a review comment for CL 226757.
Change-Id: I1425e380cb2abbb746b108fd97cd8da8f5d40998
Reviewed-on: https://go-review.googlesource.com/c/playground/+/228791
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
diff --git a/sandbox.go b/sandbox.go
index 07a0f07..7d4c54e 100644
--- a/sandbox.go
+++ b/sandbox.go
@@ -31,6 +31,8 @@
"sync"
"text/template"
"time"
+ "unicode"
+ "unicode/utf8"
"cloud.google.com/go/compute/metadata"
"github.com/bradfitz/gomemcache/memcache"
@@ -190,7 +192,7 @@
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
-// We don't want TesticularCancer.
+// We don't want mistaken Testimony or erroneous Benchmarking.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
@@ -198,7 +200,8 @@
if len(name) == len(prefix) { // "Test" is ok
return true
}
- return ast.IsExported(name[len(prefix):])
+ r, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(r)
}
// getTestProg returns source code that executes all valid tests and examples in src.
diff --git a/sandbox_test.go b/sandbox_test.go
new file mode 100644
index 0000000..f090a48
--- /dev/null
+++ b/sandbox_test.go
@@ -0,0 +1,183 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "go/token"
+ "os"
+ "os/exec"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+// TestIsTest verifies that the isTest helper function matches
+// exactly (and only) the names of functions recognized as tests.
+func TestIsTest(t *testing.T) {
+ cmd := exec.Command(os.Args[0], "-test.list=.")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
+ }
+ t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
+
+ isTestFunction := map[string]bool{}
+ lines := strings.Split(string(out), "\n")
+ for _, line := range lines {
+ isTestFunction[strings.TrimSpace(line)] = true
+ }
+
+ for _, tc := range []struct {
+ prefix string
+ f interface{}
+ want bool
+ }{
+ {"Test", Test, true},
+ {"Test", TestIsTest, true},
+ {"Test", Test1IsATest, true},
+ {"Test", TestÑIsATest, true},
+
+ {"Test", TestisNotATest, false},
+
+ {"Example", Example, true},
+ {"Example", ExampleTest, true},
+ {"Example", Example_isAnExample, true},
+ {"Example", ExampleTest_isAnExample, true},
+
+ // Example_noOutput has a valid example function name but lacks an output
+ // declaration, but the isTest function operates only on the test name
+ // so it cannot detect that the function is not a test.
+
+ {"Example", Example1IsAnExample, true},
+ {"Example", ExampleisNotAnExample, false},
+
+ {"Benchmark", Benchmark, true},
+ {"Benchmark", BenchmarkNop, true},
+ {"Benchmark", Benchmark1IsABenchmark, true},
+
+ {"Benchmark", BenchmarkisNotABenchmark, false},
+ } {
+ name := nameOf(t, tc.f)
+ t.Run(name, func(t *testing.T) {
+ if tc.want != isTestFunction[name] {
+ t.Fatalf(".want (%v) is inconsistent with -test.list", tc.want)
+ }
+ if !strings.HasPrefix(name, tc.prefix) {
+ t.Fatalf("%q is not a prefix of %v", tc.prefix, name)
+ }
+
+ got := isTest(name, tc.prefix)
+ if got != tc.want {
+ t.Errorf(`isTest(%q, %q) = %v; want %v`, name, tc.prefix, got, tc.want)
+ }
+ })
+ }
+}
+
+// nameOf returns the runtime-reported name of function f.
+func nameOf(t *testing.T, f interface{}) string {
+ t.Helper()
+
+ v := reflect.ValueOf(f)
+ if v.Kind() != reflect.Func {
+ t.Fatalf("%v is not a function", f)
+ }
+
+ rf := runtime.FuncForPC(v.Pointer())
+ if rf == nil {
+ t.Fatalf("%v.Pointer() is not a known function", f)
+ }
+
+ fullName := rf.Name()
+ parts := strings.Split(fullName, ".")
+
+ name := parts[len(parts)-1]
+ if !token.IsIdentifier(name) {
+ t.Fatalf("%q is not a valid identifier", name)
+ }
+ return name
+}
+
+// TestisNotATest is not a test function, despite appearances.
+//
+// Please ignore any lint or vet warnings for this function.
+func TestisNotATest(t *testing.T) {
+ panic("This is not a valid test function.")
+}
+
+// Test11IsATest is a valid test function.
+func Test1IsATest(t *testing.T) {
+}
+
+// Test is a test with a minimal name.
+func Test(t *testing.T) {
+}
+
+// TestÑIsATest is a test with an interesting Unicode name.
+func TestÑIsATest(t *testing.T) {
+}
+
+func Example() {
+ // Output:
+}
+
+func ExampleTest() {
+ // This is an example for the function Test.
+ // ❤ recursion.
+ Test(nil)
+
+ // Output:
+}
+
+func Example1IsAnExample() {
+ // Output:
+}
+
+// ExampleisNotAnExample is not an example function, despite appearances.
+//
+// Please ignore any lint or vet warnings for this function.
+func ExampleisNotAnExample() {
+ panic("This is not a valid example function.")
+
+ // Output:
+ // None. (This is not really an example function.)
+}
+
+func Example_isAnExample() {
+ // Output:
+}
+
+func ExampleTest_isAnExample() {
+ Test(nil)
+
+ // Output:
+}
+
+func Example_noOutput() {
+ // No output declared: should be compiled but not run.
+}
+
+func Benchmark(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ }
+}
+
+func BenchmarkNop(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ }
+}
+
+func Benchmark1IsABenchmark(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ }
+}
+
+// BenchmarkisNotABenchmark is not a benchmark function, despite appearances.
+//
+// Please ignore any lint or vet warnings for this function.
+func BenchmarkisNotABenchmark(b *testing.B) {
+ panic("This is not a valid benchmark function.")
+}