From e32aba206f46d8811e2d2d3967502ec6f921694f Mon Sep 17 00:00:00 2001 From: Timofey Koolin Date: Thu, 11 May 2023 09:12:58 +0300 Subject: [PATCH] add standard fixtures --- env_generic_sugar_test.go | 5 +- env_test.go | 163 +++++++++----------------------------- internal/testmock.go | 85 ++++++++++++++++++++ internal/testmock_test.go | 140 ++++++++++++++++++++++++++++++++ sf/README.md | 3 + sf/filesystem.go | 19 +++++ sf/filesystem_test.go | 22 +++++ sf/http.go | 13 +++ sf/http_test.go | 23 ++++++ sf/network.go | 34 ++++++++ sf/network_test.go | 25 ++++++ sf/utilities.go | 10 +++ sf/utilities_test.go | 32 ++++++++ 13 files changed, 448 insertions(+), 126 deletions(-) create mode 100644 internal/testmock.go create mode 100644 internal/testmock_test.go create mode 100644 sf/README.md create mode 100644 sf/filesystem.go create mode 100644 sf/filesystem_test.go create mode 100644 sf/http.go create mode 100644 sf/http_test.go create mode 100644 sf/network.go create mode 100644 sf/network_test.go create mode 100644 sf/utilities.go create mode 100644 sf/utilities_test.go diff --git a/env_generic_sugar_test.go b/env_generic_sugar_test.go index 410f6f7..dfa3f31 100644 --- a/env_generic_sugar_test.go +++ b/env_generic_sugar_test.go @@ -4,6 +4,7 @@ package fixenv import ( + "github.com/rekby/fixenv/internal" "testing" "github.com/stretchr/testify/require" @@ -28,7 +29,7 @@ func TestCacheGeneric(t *testing.T) { require.Equal(t, 2, res) }) t.Run("SkipAdditionalCache", func(t *testing.T) { - test := &testMock{TestName: t.Name()} + test := &internal.TestMock{TestName: t.Name()} env := newTestEnv(test) f1 := func() int { @@ -71,7 +72,7 @@ func TestCacheWithCleanupGeneric(t *testing.T) { require.Equal(t, 2, res) }) t.Run("SkipAdditionalCache", func(t *testing.T) { - test := &testMock{TestName: t.Name()} + test := &internal.TestMock{TestName: t.Name()} env := newTestEnv(test) f1 := func() int { diff --git a/env_test.go b/env_test.go index caca509..6457fc3 100644 --- a/env_test.go +++ b/env_test.go @@ -2,7 +2,7 @@ package fixenv import ( "errors" - "fmt" + "github.com/rekby/fixenv/internal" "runtime" "sync" "testing" @@ -11,25 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -type testMock struct { - TestName string - SkipGoexit bool - - m sync.Mutex - cleanups []func() - logs []struct { - format string - args []interface{} - resultString string - } - fatals []struct { - format string - args []interface{} - resultString string - } - skipCount int -} - func (e *EnvT) cloneWithTest(t T) *EnvT { e2 := newEnv(t, e.c, e.m, e.scopes) e2.onCreate() @@ -42,80 +23,13 @@ func newTestEnv(t T) *EnvT { return e } -func (t *testMock) callCleanup() { - for i := len(t.cleanups) - 1; i >= 0; i-- { - t.cleanups[i]() - } -} - -func (t *testMock) Cleanup(f func()) { - t.m.Lock() - defer t.m.Unlock() - - t.cleanups = append(t.cleanups, f) -} - -func (t *testMock) Fatalf(format string, args ...interface{}) { - t.m.Lock() - defer t.m.Unlock() - - t.fatals = append(t.fatals, struct { - format string - args []interface{} - resultString string - }{ - format: format, - args: args, - resultString: fmt.Sprintf(format, args...), - }) - - if !t.SkipGoexit { - runtime.Goexit() - } -} - -func (t *testMock) Logf(format string, args ...interface{}) { - t.m.Lock() - defer t.m.Unlock() - - t.logs = append(t.logs, struct { - format string - args []interface{} - resultString string - }{format: format, args: args, resultString: fmt.Sprintf(format, args...)}) -} - -func (t *testMock) Name() string { - if t.TestName == "" { - return "mock" - } - return t.TestName -} - -func (t *testMock) SkipNow() { - t.m.Lock() - t.skipCount++ - t.m.Unlock() - - if !t.SkipGoexit { - runtime.Goexit() - } -} - -func (t *testMock) Skipped() bool { - t.m.Lock() - defer t.m.Unlock() - - return t.skipCount > 0 -} - func Test_Env__NewEnv(t *testing.T) { t.Run("create_new_env", func(t *testing.T) { initGlobalState() at := assert.New(t) - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() e := New(tMock) at.Equal(tMock, e.t) @@ -124,7 +38,7 @@ func Test_Env__NewEnv(t *testing.T) { at.Equal(globalScopeInfo, e.scopes) at.Len(globalCache.store, 0) at.Len(globalScopeInfo, 1) - at.Len(tMock.cleanups, 1) + at.Len(tMock.Cleanups, 1) }) t.Run("global_info_cleaned", func(t *testing.T) { @@ -136,16 +50,16 @@ func Test_Env__NewEnv(t *testing.T) { t.Run("double_env_same_scope_same_time", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() _ = New(tMock) - at.Len(tMock.fatals, 0) + at.Len(tMock.Fatals, 0) runUntilFatal(func() { _ = New(tMock) }) - at.Len(tMock.fatals, 1) + at.Len(tMock.Fatals, 1) }) t.Run("double_env_similar_scope_different_time", func(t *testing.T) { @@ -245,19 +159,19 @@ func Test_Env_Cache(t *testing.T) { t.Run("fail_on_fixture_err", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() e := newTestEnv(tMock) - at.Len(tMock.fatals, 0) + at.Len(tMock.Fatals, 0) runUntilFatal(func() { testFailedFixture(e) }) - at.Len(tMock.fatals, 1) + at.Len(tMock.Fatals, 1) // log message contains fixture name - at.Contains(tMock.fatals[0].resultString, "testFailedFixture") + at.Contains(tMock.Fatals[0].ResultString, "testFailedFixture") }) t.Run("not_serializable_param", func(t *testing.T) { @@ -267,20 +181,20 @@ func Test_Env_Cache(t *testing.T) { F func() // can't serialize func to json } param := paramT{} - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() e := newTestEnv(tMock) runUntilFatal(func() { e.Cache(param, nil, func() (res interface{}, err error) { return nil, nil }) }) - at.Len(tMock.fatals, 1) + at.Len(tMock.Fatals, 1) }) t.Run("cache_by_caller_func", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} + tMock := &internal.TestMock{TestName: "mock"} e := newTestEnv(tMock) cnt := 0 @@ -299,7 +213,7 @@ func Test_Env_Cache(t *testing.T) { t.Run("different_cache_for_diff_anonim_function", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} + tMock := &internal.TestMock{TestName: "mock"} e := newTestEnv(tMock) cnt := 0 @@ -324,20 +238,20 @@ func Test_Env_Cache(t *testing.T) { t.Run("check_unreachable_code", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock", SkipGoexit: true} + tMock := &internal.TestMock{TestName: "mock", SkipGoexit: true} e := New(tMock) at.Panics(func() { e.Cache(nil, nil, func() (res interface{}, err error) { return nil, ErrSkipTest }) }) - at.Equal(1, tMock.skipCount) + at.Equal(1, tMock.SkipCount) }) } func Test_Env_CacheWithCleanup(t *testing.T) { t.Run("NilCleanup", func(t *testing.T) { - tMock := &testMock{TestName: t.Name()} + tMock := &internal.TestMock{TestName: t.Name()} env := newTestEnv(tMock) callbackCalled := 0 @@ -357,7 +271,7 @@ func Test_Env_CacheWithCleanup(t *testing.T) { }) t.Run("WithCleanup", func(t *testing.T) { - tMock := &testMock{TestName: t.Name()} + tMock := &internal.TestMock{TestName: t.Name()} env := newTestEnv(tMock) callbackCalled := 0 @@ -381,7 +295,7 @@ func Test_Env_CacheWithCleanup(t *testing.T) { require.Equal(t, 1, callbackCalled) require.Equal(t, cleanupCalled, 0) - tMock.callCleanup() + tMock.CallCleanup() require.Equal(t, 1, callbackCalled) require.Equal(t, 1, cleanupCalled) }) @@ -391,8 +305,8 @@ func Test_FixtureWrapper(t *testing.T) { t.Run("ok", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() e := newTestEnv(tMock) key := cacheKey("asd") @@ -413,24 +327,24 @@ func Test_FixtureWrapper(t *testing.T) { cnt = 0 key2 := cacheKey("asd") - cleanupsLen := len(tMock.cleanups) + cleanupsLen := len(tMock.Cleanups) w = e.fixtureCallWrapper(key2, func() (res interface{}, err error) { cnt++ return cnt, nil }, &FixtureOptions{cleanupFunc: func() { }}) - at.Len(tMock.cleanups, cleanupsLen) + at.Len(tMock.Cleanups, cleanupsLen) _, _ = w() at.Equal([]cacheKey{key, key2}, si.cacheKeys) - at.Len(tMock.cleanups, cleanupsLen+1) + at.Len(tMock.Cleanups, cleanupsLen+1) }) t.Run("unknown_scope_info", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} - defer tMock.callCleanup() + tMock := &internal.TestMock{TestName: "mock"} + defer tMock.CallCleanup() e := newTestEnv(tMock) tMock.TestName = "mock2" @@ -443,13 +357,13 @@ func Test_FixtureWrapper(t *testing.T) { // revert test name for good cleanup tMock.TestName = "mock" - at.Len(tMock.fatals, 1) + at.Len(tMock.Fatals, 1) }) } func Test_Env_Skip(t *testing.T) { at := assert.New(t) - tm := &testMock{TestName: "mock"} + tm := &internal.TestMock{TestName: "mock"} tEnv := newTestEnv(tm) skipFixtureCallTimes := 0 @@ -524,7 +438,7 @@ func Test_Env_TearDown(t *testing.T) { t.Run("ok", func(t *testing.T) { at := assert.New(t) - t1 := &testMock{TestName: "mock"} + t1 := &internal.TestMock{TestName: "mock"} // defer t1.callCleanup - direct call e1.tearDown - for test e1 := newTestEnv(t1) @@ -542,7 +456,7 @@ func Test_Env_TearDown(t *testing.T) { at.Len(e1.scopes[makeScopeName(t1.TestName, ScopeTest)].Keys(), 2) at.Len(e1.c.store, 2) - t2 := &testMock{TestName: "mock2"} + t2 := &internal.TestMock{TestName: "mock2"} // defer t2.callCleanup - direct call e2.tearDown - for test e2 := e1.cloneWithTest(t2) @@ -573,7 +487,7 @@ func Test_Env_TearDown(t *testing.T) { t.Run("tearDown on unexisted scope", func(t *testing.T) { at := assert.New(t) - tMock := &testMock{TestName: "mock"} + tMock := &internal.TestMock{TestName: "mock"} // defer tMock.callCleanups. e.tearDown will call directly for test e := newTestEnv(tMock) @@ -583,7 +497,7 @@ func Test_Env_TearDown(t *testing.T) { runUntilFatal(e.tearDown) - at.Len(tMock.fatals, 1) + at.Len(tMock.Fatals, 1) }) } @@ -733,12 +647,13 @@ func Test_ScopeName(t *testing.T) { } func TestNewEnv(t *testing.T) { - tm := &testMock{} + tm := &internal.TestMock{} tm.SkipGoexit = true New(tm) + //goland:noinspection GoDeprecation NewEnv(tm) - if len(tm.fatals) == 0 { + if len(tm.Fatals) == 0 { t.Fatal("bad double login between new and NewEnv") } } diff --git a/internal/testmock.go b/internal/testmock.go new file mode 100644 index 0000000..a00b9a8 --- /dev/null +++ b/internal/testmock.go @@ -0,0 +1,85 @@ +package internal + +import ( + "fmt" + "runtime" + "sync" +) + +const defaultMockTestName = "mock" + +type FormatCall struct { + Format string + Args []interface{} + ResultString string +} + +type TestMock struct { + TestName string + SkipGoexit bool + + M sync.Mutex + Cleanups []func() + Logs []FormatCall + Fatals []FormatCall + SkipCount int +} + +func (t *TestMock) CallCleanup() { + for i := len(t.Cleanups) - 1; i >= 0; i-- { + t.Cleanups[i]() + } +} + +func (t *TestMock) Cleanup(f func()) { + t.M.Lock() + defer t.M.Unlock() + + t.Cleanups = append(t.Cleanups, f) +} + +func (t *TestMock) Fatalf(format string, args ...interface{}) { + t.M.Lock() + defer t.M.Unlock() + + t.Fatals = append(t.Fatals, FormatCall{ + Format: format, + Args: args, + ResultString: fmt.Sprintf(format, args...), + }) + + if !t.SkipGoexit { + runtime.Goexit() + } +} + +func (t *TestMock) Logf(format string, args ...interface{}) { + t.M.Lock() + defer t.M.Unlock() + + t.Logs = append(t.Logs, FormatCall{Format: format, Args: args, ResultString: fmt.Sprintf(format, args...)}) +} + +func (t *TestMock) Name() string { + if t.TestName == "" { + return defaultMockTestName + } + return t.TestName +} + +func (t *TestMock) SkipNow() { + t.M.Lock() + t.SkipCount++ + t.M.Unlock() + + if !t.SkipGoexit { + runtime.Goexit() + } +} + +func (t *TestMock) Skipped() bool { + t.M.Lock() + defer t.M.Unlock() + + return t.SkipCount > 0 +} diff --git a/internal/testmock_test.go b/internal/testmock_test.go new file mode 100644 index 0000000..da0abf4 --- /dev/null +++ b/internal/testmock_test.go @@ -0,0 +1,140 @@ +package internal + +import ( + "fmt" + "reflect" + "testing" +) + +func TestTestMock_CallCleanup(t *testing.T) { + tm := &TestMock{} + called := false + tm.Cleanup(func() { + called = true + }) + tm.CallCleanup() + if !called { + t.Fatal() + } +} + +func TestTestMock_Fatalf(t *testing.T) { + t.Run("SkipExit", func(t *testing.T) { + tm := &TestMock{ + SkipGoexit: true, + } + tm.Fatalf("a: %v", 123) + if !reflect.DeepEqual(tm.Fatals, []FormatCall{ + { + Format: "a: %v", + Args: []interface{}{123}, + ResultString: fmt.Sprintf("a: %v", 123), + }, + }) { + t.Fatal(tm.Fatals) + } + }) + t.Run("WithExit", func(t *testing.T) { + tm := &TestMock{} + called := false + exit := make(chan bool) + go func() { + defer close(exit) + tm.Fatalf("a: %v", 123) + called = true + }() + <-exit + if !reflect.DeepEqual(tm.Fatals, []FormatCall{ + { + Format: "a: %v", + Args: []interface{}{123}, + ResultString: fmt.Sprintf("a: %v", 123), + }, + }) { + t.Fatal(tm.Fatals) + } + if called { + t.Fatal() + } + }) +} + +func TestTestMock_Logf(t *testing.T) { + tm := &TestMock{} + tm.Logf("a: %v", 123) + if !reflect.DeepEqual(tm.Logs, []FormatCall{ + { + Format: "a: %v", + Args: []interface{}{123}, + ResultString: fmt.Sprintf("a: %v", 123), + }, + }) { + t.Fatal(tm.Logs) + } +} + +func TestTestMock_Name(t *testing.T) { + tm := &TestMock{} + if tm.Name() != defaultMockTestName { + t.Fatal(tm.Name()) + } + + tm.TestName = "qwe" + if tm.Name() != "qwe" { + t.Fatal(tm.Name()) + } +} + +func TestTestMock_SkipNow(t *testing.T) { + t.Run("SkipExit", func(t *testing.T) { + tm := &TestMock{SkipGoexit: true} + tm.SkipNow() + if tm.SkipCount != 1 { + t.Fatal(tm.SkipCount) + } + tm.SkipNow() + if tm.SkipCount != 2 { + t.Fatal(tm.SkipCount) + } + }) + t.Run("WithExit", func(t *testing.T) { + tm := &TestMock{} + exit := make(chan bool) + + go func() { + defer close(exit) + tm.SkipNow() + }() + <-exit + if tm.SkipCount != 1 { + t.Fatal(tm.SkipCount) + } + exit = make(chan bool) + + go func() { + defer close(exit) + tm.SkipNow() + }() + <-exit + if tm.SkipCount != 2 { + t.Fatal(tm.SkipCount) + } + }) +} + +func TestTestMock_Skipped(t *testing.T) { + tm := &TestMock{} + if tm.Skipped() { + t.Fatal() + } + + tm.SkipCount = 1 + if !tm.Skipped() { + t.Fatal() + } + + tm.SkipCount = 2 + if !tm.Skipped() { + t.Fatal() + } +} diff --git a/sf/README.md b/sf/README.md new file mode 100644 index 0000000..9c07f3a --- /dev/null +++ b/sf/README.md @@ -0,0 +1,3 @@ +Standsrd fixtures + +Simple common usage fixtures with usage standard libraries only diff --git a/sf/filesystem.go b/sf/filesystem.go new file mode 100644 index 0000000..1bae1f4 --- /dev/null +++ b/sf/filesystem.go @@ -0,0 +1,19 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "os" +) + +func TempDir(e fixenv.Env) string { + return e.CacheWithCleanup(nil, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + dir, err := os.MkdirTemp("", "") + mustNoErr(e, err, "failed to create temp dir: %v", err) + e.T().Logf("Temp dir created: %v", dir) + clean := func() { + _ = os.RemoveAll(dir) + e.T().Logf("Temp dir removed: %v", dir) + } + return dir, clean, nil + }).(string) +} diff --git a/sf/filesystem_test.go b/sf/filesystem_test.go new file mode 100644 index 0000000..1c38976 --- /dev/null +++ b/sf/filesystem_test.go @@ -0,0 +1,22 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "os" + "testing" +) + +func TestTempDir(t *testing.T) { + var dir string + t.Run("subtest", func(t *testing.T) { + e := fixenv.New(t) + dir = TempDir(e) + if _, err := os.Stat(dir); err != nil { + t.Fatalf("failed check tmp dir: %v", err) + } + }) + _, err := os.Stat(dir) + if !os.IsNotExist(err) { + t.Fatalf("Directory must be removed after test finished, have err: %v", err) + } +} diff --git a/sf/http.go b/sf/http.go new file mode 100644 index 0000000..301b41f --- /dev/null +++ b/sf/http.go @@ -0,0 +1,13 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "net/http/httptest" +) + +func HTTPServer(e fixenv.Env) *httptest.Server { + return e.CacheWithCleanup(nil, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + server := httptest.NewServer(nil) + return server, server.Close, nil + }).(*httptest.Server) +} diff --git a/sf/http_test.go b/sf/http_test.go new file mode 100644 index 0000000..ca2c44d --- /dev/null +++ b/sf/http_test.go @@ -0,0 +1,23 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "io" + "net/http" + "testing" +) + +func TestHttpServer(t *testing.T) { + e := fixenv.New(t) + server := HTTPServer(e) + server.Config.Handler = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + _, _ = io.WriteString(writer, "OK") + }) + resp, err := http.Get(server.URL) + mustNoErr(e, err, "failed to get url: %v", err) + content, err := io.ReadAll(resp.Body) + mustNoErr(e, err, "failed to read content: %v", err) + if string(content) != "OK" { + t.Fatal(string(content)) + } +} diff --git a/sf/network.go b/sf/network.go new file mode 100644 index 0000000..d2cd4c4 --- /dev/null +++ b/sf/network.go @@ -0,0 +1,34 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "net" +) + +func FreeLocalTCPAddress(e fixenv.Env) string { + return FreeLocalTCPAddressNamed(e, "") +} + +func FreeLocalTCPAddressNamed(e fixenv.Env, name string) string { + return e.Cache(name, nil, func() (res interface{}, err error) { + listener := LocalTCPListenerNamed(e, "FreeLocalTCPAddressNamed-"+name) + addr := listener.Addr().String() + err = listener.Close() + mustNoErr(e, err, "failed to close temp listener: %v", err) + return addr, nil + }).(string) +} + +func LocalTCPListener(e fixenv.Env) *net.TCPListener { + return LocalTCPListenerNamed(e, "") +} + +func LocalTCPListenerNamed(e fixenv.Env, name string) *net.TCPListener { + return e.CacheWithCleanup(name, nil, func() (res interface{}, cleanup fixenv.FixtureCleanupFunc, err error) { + listener, err := net.Listen("tcp", "localhost:0") + clean := func() { + _ = listener.Close() + } + return listener, clean, err + }).(*net.TCPListener) +} diff --git a/sf/network_test.go b/sf/network_test.go new file mode 100644 index 0000000..6990aa0 --- /dev/null +++ b/sf/network_test.go @@ -0,0 +1,25 @@ +package sf + +import ( + "github.com/rekby/fixenv" + "net" + "testing" +) + +func TestFreeLocalTcpAddress(t *testing.T) { + e := fixenv.New(t) + addr := FreeLocalTCPAddress(e) + listener, err := net.Listen("tcp", addr) + if err != nil { + t.Fatal(err) + } + _ = listener.Close() +} + +func TestLocalTcpListener(t *testing.T) { + e := fixenv.New(t) + listener := LocalTCPListener(e) + conn, err := net.Dial("tcp", listener.Addr().String()) + mustNoErr(e, err, "failed connect to listener: %v", err) + _ = conn.Close() +} diff --git a/sf/utilities.go b/sf/utilities.go new file mode 100644 index 0000000..50e42b5 --- /dev/null +++ b/sf/utilities.go @@ -0,0 +1,10 @@ +package sf + +import "github.com/rekby/fixenv" + +func mustNoErr(e fixenv.Env, err error, format string, args ...interface{}) { + if err == nil { + return + } + e.T().Fatalf(format, args...) +} diff --git a/sf/utilities_test.go b/sf/utilities_test.go new file mode 100644 index 0000000..73ea6e5 --- /dev/null +++ b/sf/utilities_test.go @@ -0,0 +1,32 @@ +package sf + +import ( + "errors" + "github.com/rekby/fixenv" + "github.com/rekby/fixenv/internal" + "testing" +) + +func TestMustNoError(t *testing.T) { + tm := &internal.TestMock{} + t.Cleanup(tm.CallCleanup) + + tm.SkipGoexit = true + e := fixenv.New(tm) + mustNoErr(e, nil, "a: %v", 123) + + if len(tm.Fatals) > 0 { + t.Fatalf("no err must be simple return: %#v", tm.Fatals) + } + + mustNoErr(e, errors.New("asd"), "b: %v", 123) + if len(tm.Fatals) != 1 { + t.Fatalf("fatals: %#v", tm.Fatals) + } + if tm.Fatals[0].Format != "b: %v" { + t.Fatal(tm.Fatals[0].Format) + } + if tm.Fatals[0].Args[0] != 123 { + t.Fatal(tm.Fatals[0].Args[0]) + } +}