forked from robertkrimen/otto
-
Notifications
You must be signed in to change notification settings - Fork 0
/
terst.go
1172 lines (1037 loc) · 29.5 KB
/
terst.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This file was AUTOMATICALLY GENERATED by terst-import from github.com/robertkrimen/terst
/*
Package terst is a terse (terst = test + terse), easy-to-use testing library for Go.
terst is compatible with (and works via) the standard testing package: http://golang.org/pkg/testing
import (
"testing"
. "github.com/robertkrimen/terst"
)
func Test(t *testing.T) {
Terst(t) // Associate terst methods with t (the current testing.T)
Is(getApple(), "apple") // Pass
Is(getOrange(), "orange") // Fail: emits nice-looking diagnostic
Compare(1, ">", 0) // Pass
Compare(1, "==", 1.0) // Pass
}
func getApple() string {
return "apple"
}
func getOrange() string {
return "apple" // Intentional mistake
}
At the top of your testing function, call Terst(), passing the testing.T you receive as the first argument:
func TestExample(t *testing.T) {
Terst(t)
...
}
After you initialize with the given *testing.T, you can use the following to test:
Is
IsNot
Equal
Unequal
IsTrue
IsFalse
Like
Unlike
Compare
Each of the methods above can take an additional (optional) argument,
which is a string describing the test. If the test fails, this
description will be included with the test output For example:
Is(2 + 2, float32(5), "This result is Doubleplusgood")
--- FAIL: Test (0.00 seconds)
test.go:17: This result is Doubleplusgood
Failed test (Is)
got: 4 (int)
expected: 5 (float32)
Future
- Add Catch() for testing panic()
- Add Same() for testing via .DeepEqual && == (without panicking?)
- Add StrictCompare to use {}= scoping
- Add BigCompare for easier math/big.Int testing?
- Support the complex type in Compare()
- Equality test for NaN?
- Better syntax for At*
- Need IsType/TypeIs
*/
package terst
import (
"fmt"
"math/big"
"os"
"reflect"
"regexp"
"runtime"
"strings"
"testing"
"unsafe"
)
func (self *Tester) hadResult(result bool, test *test, onFail func()) bool {
if self.selfTesting {
expect := true
if self.failIsPassing {
expect = false
}
if expect != result {
self.Log(fmt.Sprintf("Expect %v but got %v (%v) (%v) (%v)\n", expect, result, test.kind, test.have, test.want))
onFail()
self._fail()
}
return result
}
if !result {
onFail()
self._fail()
}
return result
}
// IsTrue is DEPRECATED by:
//
// Is(..., true)
//
func IsTrue(have bool, description ...interface{}) bool {
return terstTester().IsTrue(have, description...)
}
// IsTrue is DEPRECATED by:
//
// Is(..., true)
//
func (self *Tester) IsTrue(have bool, description ...interface{}) bool {
return self.trueOrFalse(true, have, description...)
}
// IsFalse is DEPRECATED by:
//
// Is(..., false)
//
func IsFalse(have bool, description ...interface{}) bool {
return terstTester().IsFalse(have, description...)
}
// IsFalse is DEPRECATED by:
//
// Is(..., false)
//
func (self *Tester) IsFalse(have bool, description ...interface{}) bool {
return self.trueOrFalse(false, have, description...)
}
func (self *Tester) trueOrFalse(want bool, have bool, description ...interface{}) bool {
kind := "IsTrue"
if want == false {
kind = "IsFalse"
}
test := newTest(kind, have, want, description)
didPass := have == want
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForIsTrue(test))
})
}
// Fail will fail immediately, reporting a test failure with the (optional) description
func Fail(description ...interface{}) bool {
return terstTester().Fail(description...)
}
// Fail will fail immediately, reporting a test failure with the (optional) description
func (self *Tester) Fail(description ...interface{}) bool {
return self.fail(description...)
}
func (self *Tester) fail(description ...interface{}) bool {
kind := "Fail"
test := newTest(kind, false, false, description)
didPass := false
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForFail(test))
})
}
// FailNow will fail immediately, triggering testing.FailNow() and optionally reporting a test failure with description
func FailNow(description ...interface{}) bool {
return terstTester().FailNow(description...)
}
// FailNow will fail immediately, triggering testing.FailNow() and optionally reporting a test failure with description
func (self *Tester) FailNow(description ...interface{}) bool {
return self.failNow(description...)
}
func (self *Tester) failNow(description ...interface{}) bool {
if len(description) > 0 {
kind := "FailNow"
test := newTest(kind, false, false, description)
didPass := false
self.hadResult(didPass, test, func() {
self.Log(self.failMessageForFail(test))
})
}
self.TestingT.FailNow()
return false
}
// Equal tests have against want via ==:
//
// Equal(have, want) // Pass if have == want
//
// No special coercion or type inspection is done.
//
// If the type is incomparable (e.g. type mismatch) this will panic.
func Equal(have, want interface{}, description ...interface{}) bool {
return terstTester().Equal(have, want, description...)
}
func (self *Tester) Equal(have, want interface{}, description ...interface{}) bool {
return self.equal(have, want, description...)
}
func (self *Tester) equal(have, want interface{}, description ...interface{}) bool {
test := newTest("==", have, want, description)
didPass := have == want
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForEqual(test))
})
}
// Unequal tests have against want via !=:
//
// Unequal(have, want) // Pass if have != want
//
// No special coercion or type inspection is done.
//
// If the type is incomparable (e.g. type mismatch) this will panic.
func Unequal(have, want interface{}, description ...interface{}) bool {
return terstTester().Unequal(have, want, description...)
}
func (self *Tester) Unequal(have, want interface{}, description ...interface{}) bool {
return self.unequal(have, want, description...)
}
func (self *Tester) unequal(have, want interface{}, description ...interface{}) bool {
test := newTest("!=", have, want, description)
didPass := have != want
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForIs(test))
})
}
// Is tests <have> against <want> in different ways, depending on the
// type of <want>.
//
// If <want> is a string, then it will first convert
// <have> to a string before doing the comparison:
//
// Is(fmt.Sprintf("%v", have), want) // Pass if have == want
//
// Otherwise, Is is a shortcut for:
//
// Compare(have, "==", want)
//
// If <want> is a slice, struct, or similar, Is will perform a reflect.DeepEqual() comparison.
func Is(have, want interface{}, description ...interface{}) bool {
return terstTester().Is(have, want, description...)
}
// TODO "slice, struct, or similar" What is similar?
func (self *Tester) Is(have, want interface{}, description ...interface{}) bool {
return self.isOrIsNot(true, have, want, description...)
}
// IsNot tests <have> against <want> in different ways, depending on the
// type of <want>.
//
// If <want> is a string, then it will first convert
// <have> to a string before doing the comparison:
//
// IsNot(fmt.Sprintf("%v", have), want) // Pass if have != want
//
// Otherwise, Is is a shortcut for:
//
// Compare(have, "!=", want)
//
// If <want> is a slice, struct, or similar, Is will perform a reflect.DeepEqual() comparison.
func IsNot(have, want interface{}, description ...interface{}) bool {
return terstTester().IsNot(have, want, description...)
}
// TODO "slice, struct, or similar" What is similar?
func (self *Tester) IsNot(have, want interface{}, description ...interface{}) bool {
return self.isOrIsNot(false, have, want, description...)
}
func (self *Tester) isOrIsNot(wantIs bool, have, want interface{}, description ...interface{}) bool {
test := newTest("Is", have, want, description)
if !wantIs {
test.kind = "IsNot"
}
didPass := false
switch want.(type) {
case string:
didPass = stringValue(have) == want
default:
didPass, _ = compare(have, "{}* ==", want)
}
if !wantIs {
didPass = !didPass
}
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForIs(test))
})
}
// Like tests <have> against <want> in different ways, depending on the
// type of <want>.
//
// If <want> is a string, then it will first convert
// <have> to a string before doing a regular expression comparison:
//
// Like(fmt.Sprintf("%v", have), want) // Pass if regexp.Match(want, have)
//
// Otherwise, Like is a shortcut for:
//
// Compare(have, "{}~ ==", want)
//
// If <want> is a slice, struct, or similar, Like will perform a reflect.DeepEqual() comparison.
func Like(have, want interface{}, description ...interface{}) bool {
return terstTester().Like(have, want, description...)
}
func (self *Tester) Like(have, want interface{}, description ...interface{}) bool {
return self.likeOrUnlike(true, have, want, description...)
}
// Unlike tests <have> against <want> in different ways, depending on the
// type of <want>.
//
// If <want> is a string, then it will first convert
// <have> to a string before doing a regular expression comparison:
//
// Unlike(fmt.Sprintf("%v", have), want) // Pass if !regexp.Match(want, have)
//
// Otherwise, Unlike is a shortcut for:
//
// Compare(have, "{}~ !=", want)
//
// If <want> is a slice, struct, or similar, Unlike will perform a reflect.DeepEqual() comparison.
func Unlike(have, want interface{}, description ...interface{}) bool {
return terstTester().Unlike(have, want, description...)
}
func (self *Tester) Unlike(have, want interface{}, description ...interface{}) bool {
return self.likeOrUnlike(false, have, want, description...)
}
func (self *Tester) likeOrUnlike(wantLike bool, have, want interface{}, description ...interface{}) bool {
test := newTest("Like", have, want, description)
if !wantLike {
test.kind = "Unlike"
}
didPass := false
switch want0 := want.(type) {
case string:
haveString := stringValue(have)
didPass, error := regexp.Match(want0, []byte(haveString))
if !wantLike {
didPass = !didPass
}
if error != nil {
panic("regexp.Match(" + want0 + ", ...): " + error.Error())
}
want = fmt.Sprintf("(?:%v)", want) // Make it look like a regular expression
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForMatch(test, stringValue(have), stringValue(want), wantLike))
})
}
didPass, operator := compare(have, "{}~ ==", want)
if !wantLike {
didPass = !didPass
}
test.operator = operator
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForLike(test, stringValue(have), stringValue(want), wantLike))
})
}
// Compare will compare <have> to <want> with the given operator. The operator can be one of the following:
//
// ==
// !=
// <
// <=
// >
// >=
//
// Compare is not strict when comparing numeric types,
// and will make a best effort to promote <have> and <want> to the
// same type.
//
// Compare will promote int and uint to big.Int for testing
// against each other.
//
// Compare will promote int, uint, and float to float64 for
// float testing.
//
// For example:
//
// Compare(float32(1.0), "<", int8(2)) // A valid test
//
// result := float32(1.0) < int8(2) // Will not compile because of the type mismatch
//
func Compare(have interface{}, operator string, want interface{}, description ...interface{}) bool {
return terstTester().Compare(have, operator, want, description...)
}
func (self *Tester) Compare(have interface{}, operator string, want interface{}, description ...interface{}) bool {
return self.compare(have, operator, want, description...)
}
func (self *Tester) compare(left interface{}, operatorString string, right interface{}, description ...interface{}) bool {
operatorString = strings.TrimSpace(operatorString)
test := newTest("Compare "+operatorString, left, right, description)
didPass, operator := compare(left, operatorString, right)
test.operator = operator
return self.hadResult(didPass, test, func() {
self.Log(self.failMessageForCompare(test))
})
}
type (
compareScope int
)
const (
compareScopeEqual compareScope = iota
compareScopeTilde
compareScopeAsterisk
)
type compareOperator struct {
scope compareScope
comparison string
}
var newCompareOperatorRE *regexp.Regexp = regexp.MustCompile(`^\s*(?:((?:{}|#)[*~=])\s+)?(==|!=|<|<=|>|>=)\s*$`)
func newCompareOperator(operatorString string) compareOperator {
if operatorString == "" {
return compareOperator{compareScopeEqual, ""}
}
result := newCompareOperatorRE.FindStringSubmatch(operatorString)
if result == nil {
panic(fmt.Errorf("Unable to parse %v into a compareOperator", operatorString))
}
scope := compareScopeAsterisk
switch result[1] {
case "#*", "{}*":
scope = compareScopeAsterisk
case "#~", "{}~":
scope = compareScopeTilde
case "#=", "{}=":
scope = compareScopeEqual
}
comparison := result[2]
return compareOperator{scope, comparison}
}
func compare(left interface{}, operatorString string, right interface{}) (bool, compareOperator) {
pass := true
operator := newCompareOperator(operatorString)
comparator := newComparator(left, operator, right)
// FIXME Confusing
switch operator.comparison {
case "==":
pass = comparator.IsEqual()
case "!=":
pass = !comparator.IsEqual()
default:
if comparator.HasOrder() {
switch operator.comparison {
case "<":
pass = comparator.Compare() == -1
case "<=":
pass = comparator.Compare() <= 0
case ">":
pass = comparator.Compare() == 1
case ">=":
pass = comparator.Compare() >= 0
default:
panic(fmt.Errorf("Compare operator (%v) is invalid", operator.comparison))
}
} else {
pass = false
}
}
return pass, operator
}
// Compare / Comparator
type compareKind int
const (
kindInterface compareKind = iota
kindInteger
kindUnsignedInteger
kindFloat
kindString
kindBoolean
)
func comparatorValue(value interface{}) (reflect.Value, compareKind) {
reflectValue := reflect.ValueOf(value)
kind := kindInterface
switch value.(type) {
case int, int8, int16, int32, int64:
kind = kindInteger
case uint, uint8, uint16, uint32, uint64:
kind = kindUnsignedInteger
case float32, float64:
kind = kindFloat
case string:
kind = kindString
case bool:
kind = kindBoolean
}
return reflectValue, kind
}
func toFloat(value reflect.Value) float64 {
switch result := value.Interface().(type) {
case int, int8, int16, int32, int64:
return float64(value.Int())
case uint, uint8, uint16, uint32, uint64:
return float64(value.Uint())
case float32, float64:
return float64(value.Float())
default:
panic(fmt.Errorf("toFloat( %v )", result))
}
panic(0)
}
func toInteger(value reflect.Value) *big.Int {
switch result := value.Interface().(type) {
case int, int8, int16, int32, int64:
return big.NewInt(value.Int())
case uint, uint8, uint16, uint32, uint64:
yield := big.NewInt(0)
yield.SetString(fmt.Sprintf("%v", value.Uint()), 10)
return yield
default:
panic(fmt.Errorf("toInteger( %v )", result))
}
panic(0)
}
func toString(value reflect.Value) string {
switch result := value.Interface().(type) {
case string:
return result
default:
panic(fmt.Errorf("toString( %v )", result))
}
panic(0)
}
func toBoolean(value reflect.Value) bool {
switch result := value.Interface().(type) {
case bool:
return result
default:
panic(fmt.Errorf("toBoolean( %v )", result))
}
panic(0)
}
type aComparator interface {
Compare() int
HasOrder() bool
IsEqual() bool
CompareScope() compareScope
}
type baseComparator struct {
hasOrder bool
operator compareOperator
}
func (self *baseComparator) Compare() int {
panic(fmt.Errorf("Invalid .Compare()"))
}
func (self *baseComparator) HasOrder() bool {
return self.hasOrder
}
func (self *baseComparator) CompareScope() compareScope {
return self.operator.scope
}
func comparatorWithOrder(operator compareOperator) *baseComparator {
return &baseComparator{true, operator}
}
func comparatorWithoutOrder(operator compareOperator) *baseComparator {
return &baseComparator{false, operator}
}
type interfaceComparator struct {
*baseComparator
left interface{}
right interface{}
}
func (self *interfaceComparator) IsEqual() bool {
if self.CompareScope() != compareScopeEqual {
return reflect.DeepEqual(self.left, self.right)
}
return self.left == self.right
}
type floatComparator struct {
*baseComparator
left float64
right float64
}
func (self *floatComparator) Compare() int {
if self.left == self.right {
return 0
} else if self.left < self.right {
return -1
}
return 1
}
func (self *floatComparator) IsEqual() bool {
return self.left == self.right
}
type integerComparator struct {
*baseComparator
left *big.Int
right *big.Int
}
func (self *integerComparator) Compare() int {
return self.left.Cmp(self.right)
}
func (self *integerComparator) IsEqual() bool {
return 0 == self.left.Cmp(self.right)
}
type stringComparator struct {
*baseComparator
left string
right string
}
func (self *stringComparator) Compare() int {
if self.left == self.right {
return 0
} else if self.left < self.right {
return -1
}
return 1
}
func (self *stringComparator) IsEqual() bool {
return self.left == self.right
}
type booleanComparator struct {
*baseComparator
left bool
right bool
}
func (self *booleanComparator) IsEqual() bool {
return self.left == self.right
}
func newComparator(left interface{}, operator compareOperator, right interface{}) aComparator {
leftValue, _ := comparatorValue(left)
rightValue, rightKind := comparatorValue(right)
// The simplest comparator is comparing interface{} =? interface{}
targetKind := kindInterface
// Are left and right of the same kind?
// (reflect.Value.Kind() is different from compareKind)
scopeEqual := leftValue.Kind() == rightValue.Kind()
scopeTilde := false
scopeAsterisk := false
if scopeEqual {
targetKind = rightKind // Since left and right are the same, the targetKind is Integer/Float/String/Boolean
} else {
// Examine the prefix of reflect.Value.Kind().String() to see if there is a similarity of
// the left value to right value
lk := leftValue.Kind().String()
hasPrefix := func(prefix string) bool {
return strings.HasPrefix(lk, prefix)
}
switch right.(type) {
case float32, float64:
// Right is float*
if hasPrefix("float") {
// Left is also float*
targetKind = kindFloat
scopeTilde = true
} else if hasPrefix("int") || hasPrefix("uint") {
// Left is a kind of numeric (int* or uint*)
targetKind = kindFloat
scopeAsterisk = true
} else {
// Otherwise left is a non-numeric
}
case uint, uint8, uint16, uint32, uint64:
// Right is uint*
if hasPrefix("uint") {
// Left is also uint*
targetKind = kindInteger
scopeTilde = true
} else if hasPrefix("int") {
// Left is an int* (a numeric)
targetKind = kindInteger
scopeAsterisk = true
} else if hasPrefix("float") {
// Left is an float* (a numeric)
targetKind = kindFloat
scopeAsterisk = true
} else {
// Otherwise left is a non-numeric
}
case int, int8, int16, int32, int64:
// Right is int*
if hasPrefix("int") {
// Left is also int*
targetKind = kindInteger
scopeTilde = true
} else if hasPrefix("uint") {
// Left is a uint* (a numeric)
targetKind = kindInteger
scopeAsterisk = true
} else if hasPrefix("float") {
// Left is an float* (a numeric)
targetKind = kindFloat
scopeAsterisk = true
} else {
// Otherwise left is a non-numeric
}
default:
// Right is a non-numeric
// Can only really compare string to string or boolean to boolean, so
// we will either have a string/boolean/interfaceComparator
}
}
/*fmt.Println("%v %v %v %v %s %s", operator.scope, same, sibling, family, leftValue, rightValue)*/
{
mismatch := false
switch operator.scope {
case compareScopeEqual:
mismatch = !scopeEqual
case compareScopeTilde:
mismatch = !scopeEqual && !scopeTilde
case compareScopeAsterisk:
mismatch = !scopeEqual && !scopeTilde && !scopeAsterisk
}
if mismatch {
targetKind = kindInterface
}
}
switch targetKind {
case kindFloat:
return &floatComparator{
comparatorWithOrder(operator),
toFloat(leftValue),
toFloat(rightValue),
}
case kindInteger:
return &integerComparator{
comparatorWithOrder(operator),
toInteger(leftValue),
toInteger(rightValue),
}
case kindString:
return &stringComparator{
comparatorWithOrder(operator),
toString(leftValue),
toString(rightValue),
}
case kindBoolean:
return &booleanComparator{
comparatorWithoutOrder(operator),
toBoolean(leftValue),
toBoolean(rightValue),
}
}
// As a last resort, we can always compare left (interface{}) to right (interface{})
return &interfaceComparator{
comparatorWithoutOrder(operator),
left,
right,
}
}
// failMessage*
func (self *Tester) failMessageForIsTrue(test *test) string {
test.findFileLineFunction(self)
return formatMessage(`
%s:%d: %s
Failed test (%s)
got: %s
expected: %s
`, test.file, test.line, test.Description(), test.kind, stringValue(test.have), stringValue(test.want))
}
func (self *Tester) failMessageForFail(test *test) string {
test.findFileLineFunction(self)
return formatMessage(`
%s:%d: %s
Failed test (%s)
`, test.file, test.line, test.Description(), test.kind)
}
func typeKindString(value interface{}) string {
reflectValue := reflect.ValueOf(value)
kind := reflectValue.Kind().String()
result := fmt.Sprintf("%T", value)
if kind == result {
if kind == "string" {
return ""
}
return fmt.Sprintf(" (%T)", value)
}
return fmt.Sprintf(" (%T=%s)", value, kind)
}
func (self *Tester) failMessageForCompare(test *test) string {
test.findFileLineFunction(self)
return formatMessage(`
%s:%d: %s
Failed test (%s)
%v%s
%s
%v%s
`, test.file, test.line, test.Description(), test.kind, test.have, typeKindString(test.have), test.operator.comparison, test.want, typeKindString(test.want))
}
func (self *Tester) failMessageForEqual(test *test) string {
return self.failMessageForIs(test)
}
func (self *Tester) failMessageForIs(test *test) string {
test.findFileLineFunction(self)
return formatMessage(`
%s:%d: %v
Failed test (%s)
got: %v%s
expected: %v%s
`, test.file, test.line, test.Description(), test.kind, test.have, typeKindString(test.have), test.want, typeKindString(test.want))
}
func (self *Tester) failMessageForMatch(test *test, have, want string, wantMatch bool) string {
test.findFileLineFunction(self)
expect := " like"
if !wantMatch {
expect = "unlike"
}
return formatMessage(`
%s:%d: %s
Failed test (%s)
got: %v%s
%s: %s
`, test.file, test.line, test.Description(), test.kind, have, typeKindString(have), expect, want)
}
func (self *Tester) failMessageForLike(test *test, have, want string, wantLike bool) string {
test.findFileLineFunction(self)
if !wantLike {
want = "Anything else"
}
return formatMessage(`
%s:%d: %s
Failed test (%s)
got: %v%s
expected: %v%s
`, test.file, test.line, test.Description(), test.kind, have, typeKindString(have), want, typeKindString(want))
}
// ...
type Tester struct {
TestingT *testing.T
sanityChecking bool
selfTesting bool
failIsPassing bool
testEntry uintptr
focusEntry uintptr
}
var _terstTester *Tester = nil
func findTestEntry() uintptr {
height := 2
for {
functionPC, _, _, ok := runtime.Caller(height)
function := runtime.FuncForPC(functionPC)
functionName := function.Name()
if !ok {
return 0
}
if index := strings.LastIndex(functionName, ".Test"); index >= 0 {
// Assume we have an instance of TestXyzzy in a _test file
return function.Entry()
}
height += 1
}
return 0
}
// Focus will focus the entry point of the test to the current method.
//
// This is important for test failures in getting feedback on which line was at fault.
//
// Consider the following scenario:
//
// func testingMethod( ... ) {
// Is( ..., ... )
// }
//
// func TestExample(t *testing.T) {
// Terst(t)
//
// testingMethod( ... )
// testingMethod( ... ) // If something in testingMethod fails, this line number will come up
// testingMethod( ... )
// }
//
// By default, when a test fails, terst will report the outermost line that led to the failure.
// Usually this is what you want, but if you need to drill down, you can by inserting a special
// call at the top of your testing method:
//
// func testingMethod( ... ) {
// Terst().Focus() // Grab the global Tester and tell it to focus on this method
// Is( ..., ... ) // Now if this test fails, this line number will come up
// }
//
func (self *Tester) Focus() {
pc, _, _, ok := runtime.Caller(1)
if ok {
function := runtime.FuncForPC(pc)
self.focusEntry = function.Entry()
}
}
// Terst(*testing.T)
//
// Create a new terst Tester and return it. Associate calls to Is, Compare, Like, etc. with the newly created terst.
//
// Terst()
//
// Return the current Tester (if any).
//
// Terst(nil)
//
// Clear out the current Tester (if any).
func Terst(terst ...interface{}) *Tester {
if len(terst) == 0 {
return terstTester()
} else {
if terst[0] == nil {
_terstTester = nil
return nil
}
_terstTester = newTester(terst[0].(*testing.T))
_terstTester.enableSanityChecking()
_terstTester.testEntry = findTestEntry()
_terstTester.focusEntry = _terstTester.testEntry
}
return _terstTester
}
func terstTester() *Tester {
if _terstTester == nil {
panic("_terstTester == nil")
}
return _terstTester.checkSanity()
}
func newTester(t *testing.T) *Tester {
return &Tester{
TestingT: t,
}
}
func formatMessage(message string, argumentList ...interface{}) string {
message = fmt.Sprintf(message, argumentList...)
message = strings.TrimLeft(message, "\n")
message = strings.TrimRight(message, " \n")
return message + "\n\n"
}