From 3e92a125ed5e0d5e9c2c217ca263d7205d6d58fd Mon Sep 17 00:00:00 2001 From: Michael Whatcott Date: Fri, 6 Oct 2023 10:01:32 -0600 Subject: [PATCH 1/3] New package 'must' signals that a failure immediately ends a test case. ...via `*testing.T.Fatalf`. --- must/must.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++ must/must_test.go | 17 ++++++++++ 2 files changed, 96 insertions(+) create mode 100644 must/must.go create mode 100644 must/must_test.go diff --git a/must/must.go b/must/must.go new file mode 100644 index 0000000..6163b6b --- /dev/null +++ b/must/must.go @@ -0,0 +1,79 @@ +package must + +import "github.com/smarty/assertions" + +func fatal(so assertions.SoFunc) assertions.SoFunc { + return func(actual any, expected ...any) string { + result := so(actual, expected...) + if len(result) > 0 { + return "<>" + result + } + return result + } +} + +var ( + AlmostEqual = fatal(assertions.ShouldAlmostEqual) + BeBetween = fatal(assertions.ShouldBeBetween) + BeBetweenOrEqual = fatal(assertions.ShouldBeBetweenOrEqual) + BeBlank = fatal(assertions.ShouldBeBlank) + BeChronological = fatal(assertions.ShouldBeChronological) + BeEmpty = fatal(assertions.ShouldBeEmpty) + BeError = fatal(assertions.ShouldBeError) + BeFalse = fatal(assertions.ShouldBeFalse) + BeGreaterThan = fatal(assertions.ShouldBeGreaterThan) + BeGreaterThanOrEqualTo = fatal(assertions.ShouldBeGreaterThanOrEqualTo) + BeIn = fatal(assertions.ShouldBeIn) + BeLessThan = fatal(assertions.ShouldBeLessThan) + BeLessThanOrEqualTo = fatal(assertions.ShouldBeLessThanOrEqualTo) + BeNil = fatal(assertions.ShouldBeNil) + BeTrue = fatal(assertions.ShouldBeTrue) + BeZeroValue = fatal(assertions.ShouldBeZeroValue) + Contain = fatal(assertions.ShouldContain) + ContainKey = fatal(assertions.ShouldContainKey) + ContainSubstring = fatal(assertions.ShouldContainSubstring) + EndWith = fatal(assertions.ShouldEndWith) + Equal = fatal(assertions.ShouldEqual) + EqualJSON = fatal(assertions.ShouldEqualJSON) + EqualTrimSpace = fatal(assertions.ShouldEqualTrimSpace) + EqualWithout = fatal(assertions.ShouldEqualWithout) + HappenAfter = fatal(assertions.ShouldHappenAfter) + HappenBefore = fatal(assertions.ShouldHappenBefore) + HappenBetween = fatal(assertions.ShouldHappenBetween) + HappenOnOrAfter = fatal(assertions.ShouldHappenOnOrAfter) + HappenOnOrBefore = fatal(assertions.ShouldHappenOnOrBefore) + HappenOnOrBetween = fatal(assertions.ShouldHappenOnOrBetween) + HappenWithin = fatal(assertions.ShouldHappenWithin) + HaveLength = fatal(assertions.ShouldHaveLength) + HaveSameTypeAs = fatal(assertions.ShouldHaveSameTypeAs) + Implement = fatal(assertions.ShouldImplement) + NotAlmostEqual = fatal(assertions.ShouldNotAlmostEqual) + NotBeBetween = fatal(assertions.ShouldNotBeBetween) + NotBeBetweenOrEqual = fatal(assertions.ShouldNotBeBetweenOrEqual) + NotBeBlank = fatal(assertions.ShouldNotBeBlank) + NotBeChronological = fatal(assertions.ShouldNotBeChronological) + NotBeEmpty = fatal(assertions.ShouldNotBeEmpty) + NotBeIn = fatal(assertions.ShouldNotBeIn) + NotBeNil = fatal(assertions.ShouldNotBeNil) + NotBeZeroValue = fatal(assertions.ShouldNotBeZeroValue) + NotContain = fatal(assertions.ShouldNotContain) + NotContainKey = fatal(assertions.ShouldNotContainKey) + NotContainSubstring = fatal(assertions.ShouldNotContainSubstring) + NotEndWith = fatal(assertions.ShouldNotEndWith) + NotEqual = fatal(assertions.ShouldNotEqual) + NotHappenOnOrBetween = fatal(assertions.ShouldNotHappenOnOrBetween) + NotHappenWithin = fatal(assertions.ShouldNotHappenWithin) + NotHaveSameTypeAs = fatal(assertions.ShouldNotHaveSameTypeAs) + NotImplement = fatal(assertions.ShouldNotImplement) + NotPanic = fatal(assertions.ShouldNotPanic) + NotPanicWith = fatal(assertions.ShouldNotPanicWith) + NotPointTo = fatal(assertions.ShouldNotPointTo) + NotResemble = fatal(assertions.ShouldNotResemble) + NotStartWith = fatal(assertions.ShouldNotStartWith) + Panic = fatal(assertions.ShouldPanic) + PanicWith = fatal(assertions.ShouldPanicWith) + PointTo = fatal(assertions.ShouldPointTo) + Resemble = fatal(assertions.ShouldResemble) + StartWith = fatal(assertions.ShouldStartWith) + Wrap = fatal(assertions.ShouldWrap) +) diff --git a/must/must_test.go b/must/must_test.go new file mode 100644 index 0000000..5e9bb0b --- /dev/null +++ b/must/must_test.go @@ -0,0 +1,17 @@ +package must + +import ( + "strings" + "testing" +) + +func TestFatal(t *testing.T) { + output := Equal(1, 1) + if strings.HasPrefix(output, "<>") { + t.Error("Unexpected 'fatal' prefix") + } + output = Equal(1, 2) + if !strings.HasPrefix(output, "<>") { + t.Error("Missing expected 'fatal' prefix from output:", output) + } +} From d538b0c7b2385db411de21f57a5e2057a99bd558 Mon Sep 17 00:00:00 2001 From: Michael Whatcott Date: Tue, 17 Oct 2023 12:16:31 -0600 Subject: [PATCH 2/3] Assertion responds to 'must' (fatal) signals. --- doc.go | 11 ++++++++++- doc_test.go | 25 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index e872d4a..89e1682 100644 --- a/doc.go +++ b/doc.go @@ -16,6 +16,7 @@ package assertions import ( "fmt" "runtime" + "strings" ) // By default, we use a no-op serializer. The actual Serializer provides a JSON @@ -39,6 +40,7 @@ func GoConveyMode(yes bool) { type testingT interface { Error(args ...any) + Fatal(args ...any) } type Assertion struct { @@ -57,13 +59,20 @@ func (this *Assertion) Failed() bool { return this.failed } +const FatalPrefix = "<>" + // So calls the standalone So function and additionally, calls t.Error in failure scenarios. func (this *Assertion) So(actual any, assert SoFunc, expected ...any) bool { ok, result := So(actual, assert, expected...) if !ok { this.failed = true _, file, line, _ := runtime.Caller(1) - this.t.Error(fmt.Sprintf("\n%s:%d\n%s", file, line, result)) + report := fmt.Sprintf("\n%s:%d\n%s", file, line, result) + if strings.HasPrefix(result, FatalPrefix) { + this.t.Fatal(report) + } else { + this.t.Error(report) + } } return ok } diff --git a/doc_test.go b/doc_test.go index c9679db..25f3d3f 100644 --- a/doc_test.go +++ b/doc_test.go @@ -49,6 +49,23 @@ func TestFailingAssertion(t *testing.T) { } } +func TestFatalAssertion(t *testing.T) { + fake := &FakeT{buffer: new(bytes.Buffer)} + assertion := New(fake) + passed := assertion.So(1, MustEqual, 2) + + if passed { + t.Error("Assertion passed when it should have failed.") + } + if !fake.fatal { + t.Error("Expected assertion to fail fatally.") + } +} + +func MustEqual(actual any, expected ...any) string { + return "<>" +} + func TestFailingGroupsOfAssertions(t *testing.T) { fake := &FakeT{buffer: new(bytes.Buffer)} assertion1 := New(fake) @@ -67,8 +84,14 @@ func TestFailingGroupsOfAssertions(t *testing.T) { type FakeT struct { buffer *bytes.Buffer + fatal bool } func (this *FakeT) Error(args ...any) { - fmt.Fprint(this.buffer, args...) + _, _ = fmt.Fprint(this.buffer, args...) +} + +func (this *FakeT) Fatal(args ...any) { + _, _ = fmt.Fprint(this.buffer, args...) + this.fatal = true } From b1f164a9e66ba2e14842b94961e7e8dbc416d4c3 Mon Sep 17 00:00:00 2001 From: Michael Whatcott Date: Tue, 17 Oct 2023 12:36:39 -0600 Subject: [PATCH 3/3] No need to export. --- doc.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc.go b/doc.go index 89e1682..c5b61e9 100644 --- a/doc.go +++ b/doc.go @@ -59,16 +59,15 @@ func (this *Assertion) Failed() bool { return this.failed } -const FatalPrefix = "<>" - // So calls the standalone So function and additionally, calls t.Error in failure scenarios. func (this *Assertion) So(actual any, assert SoFunc, expected ...any) bool { ok, result := So(actual, assert, expected...) if !ok { + const fatalPrefix = "<>" this.failed = true _, file, line, _ := runtime.Caller(1) report := fmt.Sprintf("\n%s:%d\n%s", file, line, result) - if strings.HasPrefix(result, FatalPrefix) { + if strings.HasPrefix(result, fatalPrefix) { this.t.Fatal(report) } else { this.t.Error(report)