Merge pull request #40 from rogpeppe/007-more-general-run
make C.Run more general
Francesco Banconi authored 4 years ago
GitHub committed 4 years ago
3 | 3 | |
4 | 4 | import ( |
5 | 5 | "fmt" |
6 | "reflect" | |
6 | 7 | "strings" |
7 | 8 | "sync" |
8 | 9 | "testing" |
149 | 150 | return c.check(c.TB.Fatal, checker, got, args) |
150 | 151 | } |
151 | 152 | |
153 | var ( | |
154 | stringType = reflect.TypeOf("") | |
155 | boolType = reflect.TypeOf(true) | |
156 | tbType = reflect.TypeOf(new(testing.TB)).Elem() | |
157 | ) | |
158 | ||
152 | 159 | // Run runs f as a subtest of t called name. It's a wrapper around |
153 | // *testing.T.Run that provides the quicktest checker to f. When | |
160 | // the Run method of c.TB that provides the quicktest checker to f. When | |
154 | 161 | // the function completes, c.Done will be called to run any |
155 | 162 | // functions registered with c.Defer. |
156 | 163 | // |
157 | // For instance: | |
164 | // c.TB must implement a Run method of the following form: | |
165 | // | |
166 | // Run(string, func(T)) bool | |
167 | // | |
168 | // where T is any type that is assignable to testing.TB. | |
169 | // Implementations include *testing.T, *testing.B and *C itself. | |
170 | // | |
171 | // The TB field in the subtest will hold the value passed | |
172 | // by Run to its argument function. | |
158 | 173 | // |
159 | 174 | // func TestFoo(t *testing.T) { |
160 | 175 | // c := qt.New(t) |
165 | 180 | // } |
166 | 181 | // |
167 | 182 | // A panic is raised when Run is called and the embedded concrete type does not |
168 | // implement Run, for instance if TB's concrete type is a benchmark. | |
183 | // implement a Run method with a correct signature. | |
169 | 184 | func (c *C) Run(name string, f func(c *C)) bool { |
170 | r, ok := c.TB.(interface { | |
171 | Run(string, func(*testing.T)) bool | |
172 | }) | |
173 | if !ok { | |
174 | panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T", c.TB)) | |
175 | } | |
176 | return r.Run(name, func(t *testing.T) { | |
177 | c2 := New(t) | |
185 | badType := func(m string) { | |
186 | panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T (%s)", c.TB, m)) | |
187 | } | |
188 | m := reflect.ValueOf(c.TB).MethodByName("Run") | |
189 | if !m.IsValid() { | |
190 | // c.TB doesn't implement a Run method. | |
191 | badType("no Run method") | |
192 | } | |
193 | mt := m.Type() | |
194 | if mt.NumIn() != 2 || | |
195 | mt.In(0) != stringType || | |
196 | mt.NumOut() != 1 || | |
197 | mt.Out(0) != boolType { | |
198 | // The Run method doesn't have the right argument counts and types. | |
199 | badType("wrong argument count for Run method") | |
200 | } | |
201 | farg := mt.In(1) | |
202 | if farg.Kind() != reflect.Func || | |
203 | farg.NumIn() != 1 || | |
204 | farg.NumOut() != 0 || | |
205 | !farg.In(0).AssignableTo(tbType) { | |
206 | // The first argument to the Run function arg isn't right. | |
207 | badType("bad first argument type for Run method") | |
208 | } | |
209 | fv := reflect.MakeFunc(farg, func(args []reflect.Value) []reflect.Value { | |
210 | c2 := New(args[0].Interface().(testing.TB)) | |
178 | 211 | defer c2.Done() |
179 | 212 | c2.SetFormat(c.getFormat()) |
180 | 213 | f(c2) |
214 | return nil | |
181 | 215 | }) |
216 | return m.Call([]reflect.Value{reflect.ValueOf(name), fv})[0].Interface().(bool) | |
182 | 217 | } |
183 | 218 | |
184 | 219 | // Parallel signals that this test is to be run in parallel with (and only with) other parallel tests. |
380 | 380 | assertBool(t, ok, true) |
381 | 381 | } |
382 | 382 | |
383 | func TestCRunOnBenchmark(t *testing.T) { | |
384 | called := false | |
385 | testing.Benchmark(func(b *testing.B) { | |
386 | c := qt.New(b) | |
387 | c.Run("c", func(c *qt.C) { | |
388 | b1, ok := c.TB.(*testing.B) | |
389 | if !ok { | |
390 | t.Errorf("c.TB is type %T not *testing.B", c.TB) | |
391 | return | |
392 | } | |
393 | if b1 == b { | |
394 | t.Errorf("c.TB hasn't been given a new B value") | |
395 | return | |
396 | } | |
397 | called = true | |
398 | }) | |
399 | }) | |
400 | if !called { | |
401 | t.Fatalf("sub-benchmark was never called") | |
402 | } | |
403 | } | |
404 | ||
405 | // wrongRun1 has Run method with wrong arg count. | |
406 | type wrongRun1 struct { | |
407 | testing.TB | |
408 | } | |
409 | ||
410 | func (wrongRun1) Run() {} | |
411 | ||
412 | // wrongRun2 has no Run method. | |
413 | type wrongRun2 struct { | |
414 | testing.TB | |
415 | } | |
416 | ||
417 | // wrongRun3 has Run method that takes a type not | |
418 | // assignable to testing.TB. | |
419 | type wrongRun3 struct { | |
420 | testing.TB | |
421 | } | |
422 | ||
423 | func (wrongRun3) Run(string, func(string)) bool { return false } | |
424 | ||
425 | // wrongRun4 has Run method that doesn't return bool. | |
426 | type wrongRun4 struct { | |
427 | testing.TB | |
428 | } | |
429 | ||
430 | func (wrongRun4) Run(string, func(*testing.T)) {} | |
431 | ||
432 | var CRunPanicTests = []struct { | |
433 | tb testing.TB | |
434 | expectPanic string | |
435 | }{{ | |
436 | tb: wrongRun1{}, | |
437 | expectPanic: "wrong argument count for Run method", | |
438 | }, { | |
439 | tb: wrongRun2{}, | |
440 | expectPanic: "no Run method", | |
441 | }, { | |
442 | tb: wrongRun3{}, | |
443 | expectPanic: "bad first argument type for Run method", | |
444 | }, { | |
445 | tb: wrongRun4{}, | |
446 | expectPanic: "wrong argument count for Run method", | |
447 | }} | |
448 | ||
383 | 449 | func TestCRunPanic(t *testing.T) { |
384 | c := qt.New(&testing.B{}) | |
385 | var run bool | |
386 | defer func() { | |
387 | r := recover() | |
388 | if r != "cannot execute Run with underlying concrete type *testing.B" { | |
389 | t.Fatalf("unexpected panic recover: %v", r) | |
390 | } | |
391 | }() | |
392 | c.Run("panic", func(innerC *qt.C) {}) | |
393 | assertBool(t, run, true) | |
450 | for _, test := range CRunPanicTests { | |
451 | t.Run(fmt.Sprintf("%T", test.tb), func(t *testing.T) { | |
452 | c := qt.New(test.tb) | |
453 | defer func() { | |
454 | got := recover() | |
455 | want := fmt.Sprintf( | |
456 | "cannot execute Run with underlying concrete type %T (%s)", | |
457 | test.tb, test.expectPanic, | |
458 | ) | |
459 | if got != want { | |
460 | t.Fatalf("unexpected panic recover message; got %q want %q", got, want) | |
461 | } | |
462 | }() | |
463 | c.Run("panic", func(innerC *qt.C) {}) | |
464 | }) | |
465 | } | |
394 | 466 | } |
395 | 467 | |
396 | 468 | func TestCRunFormat(t *testing.T) { |
510 | 582 | c.Assert(outerDefer, qt.Equals, 0) |
511 | 583 | } |
512 | 584 | |
585 | type customT struct { | |
586 | *testing.T | |
587 | data int | |
588 | } | |
589 | ||
590 | func (t *customT) Run(name string, f func(*customT)) bool { | |
591 | return t.T.Run(name, func(t1 *testing.T) { | |
592 | f(&customT{t1, t.data}) | |
593 | }) | |
594 | } | |
595 | ||
596 | func TestCRunCustomType(t *testing.T) { | |
597 | ct := &customT{t, 99} | |
598 | c := qt.New(ct) | |
599 | called := 0 | |
600 | c.Run("test", func(c *qt.C) { | |
601 | called++ | |
602 | ct1, ok := c.TB.(*customT) | |
603 | if !ok { | |
604 | t.Error("TB isn't expected type") | |
605 | } | |
606 | if ct1.data != ct.data { | |
607 | t.Errorf("data not copied correctly; got %v want %v", ct1.data, ct.data) | |
608 | } | |
609 | if ct1 == ct { | |
610 | t.Errorf("old instance passed, not new") | |
611 | } | |
612 | }) | |
613 | if called != 1 { | |
614 | t.Fatalf("subtest was called %d times, not once", called) | |
615 | } | |
616 | } | |
617 | ||
513 | 618 | func checkResult(t *testing.T, ok bool, got, want string) { |
514 | 619 | if want != "" { |
515 | 620 | assertPrefix(t, got, want+"stack:\n") |