diff --git a/is/decorate.go b/is/decorate.go new file mode 100644 index 0000000..648ee3d --- /dev/null +++ b/is/decorate.go @@ -0,0 +1,43 @@ +package is + +import ( + "bytes" + "fmt" + "runtime" + "strings" +) + +// decorate prefixes the string with the file and line of the call site +// and inserts the final newline if needed and indentation tabs for formatting. +// this function was copied from the testing framework. +func decorate(s string) string { + _, file, line, ok := runtime.Caller(3) // decorate + log + public function. + if ok { + // Truncate file name at last file name separator. + if index := strings.LastIndex(file, "/"); index >= 0 { + file = file[index+1:] + } else if index = strings.LastIndex(file, "\\"); index >= 0 { + file = file[index+1:] + } + } else { + file = "???" + line = 1 + } + buf := new(bytes.Buffer) + // Every line is indented at least one tab. + buf.WriteByte('\t') + fmt.Fprintf(buf, "%s:%d: ", file, line) + lines := strings.Split(s, "\n") + if l := len(lines); l > 1 && lines[l-1] == "" { + lines = lines[:l-1] + } + for i, line := range lines { + if i > 0 { + // Second and subsequent lines are indented an extra tab. + buf.WriteString("\n\t\t") + } + buf.WriteString(line) + } + buf.WriteByte('\n') + return buf.String() +} diff --git a/is/is.go b/is/is.go new file mode 100644 index 0000000..a1db2ba --- /dev/null +++ b/is/is.go @@ -0,0 +1,71 @@ +package is + +import ( + "fmt" + "reflect" + "testing" +) + +// TB is an interface that defines an assertion-style +// testing syntax to condense and make more efficient +// the writing of tests. The output of TB is identical +// to the output of the testing framework, allowing any +// scripts, parsers, etc to parse it appropriately. +type TB interface { + // Equal determines if the two arguments are equal. + // If not, a failure is printed. + Equal(actual, expected interface{}) Result + // NotEqual determines if the two arguments are not equal. + // If they are, a failure is printed. + NotEqual(actual, expected interface{}) Result +} + +// tb implements TB +type tb struct { + tb testing.TB +} + +// ensure tb implements TB +var _ TB = (*tb)(nil) + +// New creates a new object that satisfies the TB interface. +func New(testObj testing.TB) TB { + return &tb{tb: testObj} +} + +// equal tests for equality of two objects. +func equal(actual, expected interface{}) bool { + return reflect.DeepEqual(actual, expected) +} + +// printFailure assembles a string from the arguments +// and decorates it, then prints it. +func printFailure(args ...interface{}) { + fmt.Println(decorate(fmt.Sprintln(args...))) +} + +// Equal determines if the two arguments are equal. +// If not, a failure is printed. +func (t *tb) Equal(actual, expected interface{}) Result { + r := &result{tb: t.tb} + if equal(actual, expected) { + r.success = true + } else { + r.success = false + printFailure(actual, "should be equal to", expected) + } + return r +} + +// NotEqual determines if the two arguments are not equal. +// If they are, a failure is printed. +func (t *tb) NotEqual(actual, expected interface{}) Result { + r := &result{tb: t.tb} + if !equal(actual, expected) { + r.success = true + } else { + r.success = false + printFailure(actual, "should not be equal to", expected) + } + return r +} diff --git a/is/is_test.go b/is/is_test.go new file mode 100644 index 0000000..da32b2e --- /dev/null +++ b/is/is_test.go @@ -0,0 +1,45 @@ +package is + +import "testing" + +func TestIsEqual(t *testing.T) { + mockT := &testing.T{} + is := New(mockT) + if is == nil { + t.Fatal("is should not be nil") + } + r := is.Equal(1, 1) + if r == nil { + t.Fatal("Equal should return a result object") + } + if !r.Success() { + t.Fatal("should be equal") + } + if is.Equal(1, 2).Success() { + t.Fatal("1 is not 2") + } + // is.Equal(1,2).Require() cannot be tested as it + // either fails the test or causes a panic and we cannot + // mock testing.TB as there is a private method to prevent that. +} + +func TestIsNotEqual(t *testing.T) { + mockT := &testing.T{} + is := New(mockT) + if is == nil { + t.Fatal("is should not be nil") + } + r := is.NotEqual(1, 2) + if r == nil { + t.Fatal("NotEqual should return a result object") + } + if !r.Success() { + t.Fatal("should not be equal") + } + if is.NotEqual(1, 1).Success() { + t.Fatal("1 is 1") + } + // is.Equal(1,2).Require() cannot be tested as it + // either fails the test or causes a panic and we cannot + // mock testing.TB as there is a private method to prevent that. +} diff --git a/is/result.go b/is/result.go new file mode 100644 index 0000000..eeba894 --- /dev/null +++ b/is/result.go @@ -0,0 +1,36 @@ +package is + +import "testing" + +// Result is an interface that defines methods for +// acting on the result of a test. +type Result interface { + // Success returns the success value of the last test. + Success() bool + // Require causes the testing procedure to abort + // if the test failed. + Require() +} + +// result implements Result +type result struct { + success bool + tb testing.TB +} + +// ensure result implements Result +var _ Result = (*result)(nil) + +// Success returns the success value of the test that +// returned it. +func (r *result) Success() bool { + return r.success +} + +// Require causes the testing procedure to abort +// if the test failed. +func (r *result) Require() { + if r.success == false { + r.tb.FailNow() + } +}