blob: 117b4e44a73c6e356fb0be054bf2014406556a68 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 modules_test
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"reflect"
"sort"
"strconv"
"strings"
"syscall"
"testing"
"time"
"v.io/v23"
"v.io/v23/verror"
"v.io/x/ref"
"v.io/x/ref/lib/exec"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/test"
"v.io/x/ref/test/modules"
"v.io/x/ref/test/testutil"
_ "v.io/x/ref/runtime/factories/generic"
)
// We must call TestMain ourselves because using v23 test generate
// creates an import cycle for this package.
func TestMain(m *testing.M) {
test.Init()
modules.DispatchAndExitIfChild()
os.Exit(m.Run())
}
var ignoreStdin = modules.Register(func(*modules.Env, ...string) error {
<-time.After(time.Minute)
return nil
}, "ignoreStdin")
var echo = modules.Register(func(env *modules.Env, args ...string) error {
for _, a := range args {
fmt.Fprintf(env.Stdout, "stdout: %s\n", a)
fmt.Fprintf(env.Stderr, "stderr: %s\n", a)
}
return nil
}, "Echo")
var pipeEcho = modules.Register(pipeEchoFunc, "pipeEcho")
func pipeEchoFunc(env *modules.Env, args ...string) error {
scanner := bufio.NewScanner(env.Stdin)
for scanner.Scan() {
fmt.Fprintf(env.Stdout, "%p: %s\n", pipeEchoFunc, scanner.Text())
}
return nil
}
var lifo = modules.Register(lifoFunc, "lifo")
func lifoFunc(env *modules.Env, args ...string) error {
scanner := bufio.NewScanner(env.Stdin)
scanner.Scan()
msg := scanner.Text()
modules.WaitForEOF(env.Stdin)
fmt.Fprintf(env.Stdout, "%p: %s\n", lifoFunc, msg)
return nil
}
var printBlessing = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.V23Init()
defer shutdown()
blessing := v23.GetPrincipal(ctx).BlessingStore().Default()
fmt.Fprintf(env.Stdout, "%s", blessing)
return nil
}, "printBlessing")
var envTest = modules.Register(func(env *modules.Env, args ...string) error {
for _, a := range args {
if v := env.Vars[a]; len(v) > 0 {
fmt.Fprintf(env.Stdout, "%s\n", a+"="+v)
} else {
fmt.Fprintf(env.Stderr, "missing %s\n", a)
}
}
modules.WaitForEOF(env.Stdin)
fmt.Fprintf(env.Stdout, "done\n")
return nil
}, "envTest")
const printEnvArgPrefix = "PRINTENV_ARG="
var printEnv = modules.Register(func(env *modules.Env, args ...string) error {
for _, a := range args {
fmt.Fprintf(env.Stdout, "%s%s\n", printEnvArgPrefix, a)
}
for k, v := range env.Vars {
fmt.Fprintf(env.Stdout, "%q\n", k+"="+v)
}
return nil
}, "printEnv")
var errorMain = modules.Register(func(env *modules.Env, args ...string) error {
return fmt.Errorf("an error")
}, "errorMain")
func waitForInput(scanner *bufio.Scanner) bool {
ch := make(chan struct{})
go func(ch chan<- struct{}) {
scanner.Scan()
ch <- struct{}{}
}(ch)
select {
case <-ch:
return true
case <-time.After(10 * time.Second):
return false
}
}
func testProgram(t *testing.T, sh *modules.Shell, prog modules.Program, key, val string) {
h, err := sh.Start(nil, prog, key)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer func() {
var stdout, stderr bytes.Buffer
sh.Cleanup(&stdout, &stderr)
want := ""
if testing.Verbose() {
want = "---- Shell Cleanup ----\n---- Cleanup calling cancelCtx ----\n---- Shell Cleanup Complete ----\n"
}
if got := stdout.String(); got != "" && got != want {
t.Errorf("got %q, want %q", got, want)
}
if got := stderr.String(); got != "" && got != want {
t.Errorf("got %q, want %q", got, want)
}
}()
scanner := bufio.NewScanner(h.Stdout())
if !waitForInput(scanner) {
t.Errorf("timeout")
return
}
if got, want := scanner.Text(), key+"="+val; got != want {
t.Errorf("got %q, want %q", got, want)
}
h.CloseStdin()
if !waitForInput(scanner) {
t.Fatalf("timeout")
return
}
if got, want := scanner.Text(), "done"; got != want {
t.Errorf("got %q, want %q", got, want)
}
if err := h.Shutdown(nil, nil); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
func getBlessing(t *testing.T, sh *modules.Shell, env ...string) string {
h, err := sh.Start(env, printBlessing)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
if !waitForInput(scanner) {
t.Errorf("timeout")
return ""
}
return scanner.Text()
}
func getCustomBlessing(t *testing.T, sh *modules.Shell, creds *modules.CustomCredentials) string {
h, err := sh.StartWithOpts(sh.DefaultStartOpts().WithCustomCredentials(creds), nil, printBlessing)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
if !waitForInput(scanner) {
t.Errorf("timeout")
return ""
}
return scanner.Text()
}
func TestChild(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
key, val := "simpleVar", "foo & bar"
sh.SetVar(key, val)
testProgram(t, sh, envTest, key, val)
}
func TestAgent(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stdout, os.Stderr)
a := getBlessing(t, sh)
b := getBlessing(t, sh)
if a != b {
t.Errorf("Expected same blessing for children, got %s and %s", a, b)
}
}
func TestCustomPrincipal(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
p, err := vsecurity.NewPrincipal()
if err != nil {
t.Fatal(err)
}
b, err := p.BlessSelf("myshell")
if err != nil {
t.Fatal(err)
}
if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
t.Fatal(err)
}
cleanDebug := p.BlessingStore().DebugString()
sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stdout, os.Stderr)
if got, want := getBlessing(t, sh), "myshell/child"; got != want {
t.Errorf("Bad blessing. Got %q, want %q", got, want)
}
newDebug := p.BlessingStore().DebugString()
if cleanDebug != newDebug {
t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
}
}
func TestCustomCredentials(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
root := testutil.NewIDProvider("myshell")
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatal(err)
}
defer sh.Cleanup(os.Stdout, os.Stderr)
newCreds := func(ext string) *modules.CustomCredentials {
p, err := sh.NewCustomCredentials()
if err != nil {
t.Fatal(err)
}
b, err := root.NewBlessings(p.Principal(), ext)
if err != nil {
t.Fatal(err)
}
if err := vsecurity.SetDefaultBlessings(p.Principal(), b); err != nil {
t.Fatal(err)
}
return p
}
a := newCreds("a")
cleanDebug := a.Principal().BlessingStore().DebugString()
blessing := getCustomBlessing(t, sh, a)
if blessing != "myshell/a" {
t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
}
b := newCreds("bar")
blessing = getCustomBlessing(t, sh, b)
if blessing != "myshell/bar" {
t.Errorf("Bad blessing. Expected myshell/bar, go %q", blessing)
}
// Make sure we can re-use credentials
blessing = getCustomBlessing(t, sh, a)
if blessing != "myshell/a" {
t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
}
newDebug := a.Principal().BlessingStore().DebugString()
if cleanDebug != newDebug {
t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
}
}
func createCredentials(blessing string) (string, error) {
dir, err := ioutil.TempDir("", "TestNoAgent_v23_credentials")
if err != nil {
return "", err
}
p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
if err != nil {
os.RemoveAll(dir)
return "", err
}
b, err := p.BlessSelf(blessing)
if err != nil {
os.RemoveAll(dir)
return "", err
}
if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
os.RemoveAll(dir)
return "", err
}
return dir, nil
}
func TestNoAgent(t *testing.T) {
const noagent = "noagent"
creds, err := createCredentials(noagent)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(creds)
sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stdout, os.Stderr)
if got, want := getBlessing(t, sh, fmt.Sprintf("%s=%s", ref.EnvCredentials, creds)), noagent; got != want {
t.Errorf("Bad blessing. Got %q, want %q", got, want)
}
}
func TestChildNoRegistration(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stderr, os.Stderr)
key, val := "simpleVar", "foo & bar"
sh.SetVar(key, val)
testProgram(t, sh, envTest, key, val)
_, err = sh.Start(nil, modules.Program("non-existent-program"), "random", "args")
if err == nil {
fmt.Fprintf(os.Stderr, "Failed: %v\n", err)
t.Fatalf("expected error")
}
}
func TestErrorChild(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
h, err := sh.Start(nil, errorMain)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if got, want := h.Shutdown(nil, nil), "exit status 1"; got == nil || got.Error() != want {
t.Errorf("got %q, want %q", got, want)
}
}
func testShutdown(t *testing.T, sh *modules.Shell, prog modules.Program) {
result := ""
args := []string{"a", "b c", "ddd"}
if _, err := sh.Start(nil, prog, args...); err != nil {
t.Fatalf("unexpected error: %s", err)
}
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
sh.Cleanup(&stdoutBuf, &stderrBuf)
var stdoutOutput, stderrOutput string
for _, a := range args {
stdoutOutput += fmt.Sprintf("stdout: %s\n", a)
stderrOutput += fmt.Sprintf("stderr: %s\n", a)
}
if got, want := stdoutBuf.String(), stdoutOutput+result; got != want {
t.Errorf("got %q want %q", got, want)
}
stderrBuf.ReadString('\n') // Skip past the random # generator output
if got, want := stderrBuf.String(), stderrOutput; got != want {
t.Errorf("got %q want %q", got, want)
}
}
func TestShutdownSubprocess(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, false, t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
testShutdown(t, sh, echo)
}
// TestShutdownSubprocessIgnoreStdin verifies that Shutdown doesn't wait
// forever if a child does not die upon closing stdin; but instead times out and
// returns an appropriate error.
func TestShutdownSubprocessIgnoreStdin(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, false, t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts := sh.DefaultStartOpts()
opts.ShutdownTimeout = time.Second
h, err := sh.StartWithOpts(opts, nil, ignoreStdin)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
var stdoutBuf, stderrBuf bytes.Buffer
if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || verror.ErrorID(err) != exec.ErrTimeout.ID {
t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout.ID)
}
if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
t.Errorf("Kill failed: %v", err)
}
}
// TestStdoutRace exemplifies a potential race between reading from child's
// stdout and closing stdout in Wait (called by Shutdown).
//
// NOTE: triggering the actual --race failure is hard, even if the
// implementation inappropriately sets stdout to the file that is to be closed
// in Wait.
func TestStdoutRace(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts := sh.DefaultStartOpts()
opts.ShutdownTimeout = time.Second
h, err := sh.StartWithOpts(opts, nil, ignoreStdin)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
ch := make(chan error, 1)
go func() {
buf := make([]byte, 5)
// This will block since the child is not writing anything on
// stdout.
_, err := h.Stdout().Read(buf)
ch <- err
}()
// Give the goroutine above a chance to run, so that we're blocked on
// stdout.Read.
<-time.After(time.Second)
// Cleanup should close stdout, and unblock the goroutine.
sh.Cleanup(nil, nil)
if got, want := <-ch, io.EOF; got != want {
t.Errorf("Expected %v, got %v instead", want, got)
}
if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
t.Errorf("Kill failed: %v", err)
}
}
func find(want string, in []string) bool {
for _, a := range in {
if a == want {
return true
}
}
return false
}
func TestEnvelope(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
sh.SetVar("a", "1")
sh.SetVar("b", "2")
args := []string{"oh", "ah"}
h, err := sh.Start(nil, printEnv, args...)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
childArgs, childEnv := []string{}, []string{}
for scanner.Scan() {
o := scanner.Text()
if strings.HasPrefix(o, printEnvArgPrefix) {
childArgs = append(childArgs, strings.TrimPrefix(o, printEnvArgPrefix))
} else {
childEnv = append(childEnv, o)
}
}
shArgs, shEnv := sh.ProgramEnvelope(nil, printEnv, args...)
for i, ev := range shEnv {
shEnv[i] = fmt.Sprintf("%q", ev)
}
for _, want := range args {
if !find(want, childArgs) {
t.Errorf("failed to find %q in %s", want, childArgs)
}
if !find(want, shArgs) {
t.Errorf("failed to find %q in %s", want, shArgs)
}
}
for _, want := range shEnv {
if !find(want, childEnv) {
t.Errorf("failed to find %s in %#v", want, childEnv)
}
}
for _, want := range childEnv {
if want == "\""+exec.ExecVersionVariable+"=\"" {
continue
}
if !find(want, shEnv) {
t.Errorf("failed to find %s in %#v", want, shEnv)
}
}
}
func TestEnvMerge(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
sh.SetVar("a", "1")
os.Setenv("a", "wrong, should be 1")
sh.SetVar("b", "2 also wrong")
os.Setenv("b", "wrong, should be 2")
h, err := sh.Start([]string{"b=2"}, printEnv)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
for scanner.Scan() {
o := scanner.Text()
if strings.HasPrefix(o, "a=") {
if got, want := o, "a=1"; got != want {
t.Errorf("got: %q, want %q", got, want)
}
}
if strings.HasPrefix(o, "b=") {
if got, want := o, "b=2"; got != want {
t.Errorf("got: %q, want %q", got, want)
}
}
}
}
func TestNoExec(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecProgram(), nil, modules.Program("/bin/echo"), "hello", "world")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
scanner.Scan()
if got, want := scanner.Text(), "hello world"; got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
func TestExternal(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
cookie := strconv.Itoa(rand.Int())
sh.SetConfigKey("cookie", cookie)
h, err := sh.StartWithOpts(sh.DefaultStartOpts().ExternalProgram(), nil, modules.Program(os.Args[0]), "--test.run=TestExternalTestHelper")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
scanner := bufio.NewScanner(h.Stdout())
scanner.Scan()
if got, want := scanner.Text(), fmt.Sprintf("cookie: %s", cookie); got != want {
h.Shutdown(os.Stderr, os.Stderr)
t.Fatalf("got %v, want %v", got, want)
}
}
// TestExternalTestHelper is used by TestExternal above and has not utility
// as a test in it's own right.
func TestExternalTestHelper(t *testing.T) {
child, err := exec.GetChildHandle()
if err != nil {
return
}
child.SetReady()
val, err := child.Config.Get("cookie")
if err != nil {
t.Fatalf("failed to get child handle: %s", err)
}
fmt.Printf("cookie: %s\n", val)
}
func TestPipe(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
opts := sh.DefaultStartOpts()
opts.Stdin = r
h, err := sh.StartWithOpts(opts, nil, pipeEcho)
if err != nil {
t.Fatal(err)
}
cookie := strconv.Itoa(rand.Int())
go func(w *os.File, s string) {
fmt.Fprintf(w, "hello world\n")
fmt.Fprintf(w, "%s\n", s)
w.Close()
}(w, cookie)
scanner := bufio.NewScanner(h.Stdout())
want := []string{
fmt.Sprintf("%p: hello world", pipeEchoFunc),
fmt.Sprintf("%p: %s", pipeEchoFunc, cookie),
}
i := 0
for scanner.Scan() {
if got, want := scanner.Text(), want[i]; got != want {
t.Fatalf("got %v, want %v", got, want)
}
i++
}
if got, want := i, 2; got != want {
t.Fatalf("got %v, want %v", got, want)
}
if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
t.Fatal(err)
}
r.Close()
}
func TestLIFO(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(nil, nil)
cases := []string{"a", "b", "c"}
for _, msg := range cases {
h, err := sh.Start(nil, lifo)
if err != nil {
t.Fatal(err)
}
fmt.Fprintf(h.Stdin(), "%s\n", msg)
}
var buf bytes.Buffer
if err := sh.Cleanup(&buf, nil); err != nil {
t.Fatal(err)
}
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
if got, want := len(lines), len(cases); got != want {
t.Fatalf("got %v, want %v", got, want)
}
sort.Sort(sort.Reverse(sort.StringSlice(cases)))
for i, msg := range cases {
if got, want := lines[i], fmt.Sprintf("%p: %s", lifoFunc, msg); got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
}
func TestStartOpts(t *testing.T) {
sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts := modules.StartOpts{
External: true,
}
sh.SetDefaultStartOpts(opts)
def := sh.DefaultStartOpts()
if got, want := def.External, opts.External; got != want {
t.Fatalf("got %v, want %v", got, want)
}
def.External = false
if got, want := def, (modules.StartOpts{}); !reflect.DeepEqual(got, want) {
t.Fatalf("got %v, want %v", got, want)
}
// Verify that the shell retains a copy.
opts.External = false
opts.ExecProtocol = true
def = sh.DefaultStartOpts()
if got, want := def.External, true; got != want {
t.Fatalf("got %v, want %v", got, want)
}
if got, want := def.ExecProtocol, false; got != want {
t.Fatalf("got %v, want %v", got, want)
}
sh.SetDefaultStartOpts(opts)
def = sh.DefaultStartOpts()
if got, want := def.ExecProtocol, true; got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
func TestEmbeddedSession(t *testing.T) {
sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
def := sh.DefaultStartOpts()
if def.ExpectTesting == nil {
t.Fatalf("ExpectTesting should be non nil")
}
}
func TestCredentialsAndNoExec(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts := sh.DefaultStartOpts()
opts = opts.NoExecProgram()
creds, err := sh.NewCustomCredentials()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
opts = opts.WithCustomCredentials(creds)
h, err := sh.StartWithOpts(opts, nil, echo, "a")
if got, want := err, modules.ErrNoExecAndCustomCreds; got != want {
t.Fatalf("got %v, want %v", got, want)
}
if got, want := h, modules.Handle(nil); got != want {
t.Fatalf("got %v, want %v", got, want)
}
}