From e80b22c45aa36d64852262c40b6588a182f0dbfe Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Sat, 15 Feb 2025 16:52:59 -0600 Subject: [PATCH] version 1 --- README.md | 46 +++++++++++++++++- contains.go | 86 ++++++++++++++++++++++++++++++++++ equal.go | 33 +++++++++++++ errors.go | 41 +++++++++++++++++ go.mod | 3 ++ nil.go | 39 ++++++++++++++++ test.go | 7 +++ test_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ true.go | 11 +++++ 9 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 contains.go create mode 100644 equal.go create mode 100644 errors.go create mode 100644 go.mod create mode 100644 nil.go create mode 100644 test.go create mode 100644 test_test.go create mode 100644 true.go diff --git a/README.md b/README.md index 250ea69..85d5af4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ # Assert -Very simple tools for test assertions. \ No newline at end of file +Very simple tools for test assertions. Why doesn't Go have assertions built in? Anyone's guess! +All credit to [Eduard Urbach](https://akyoto.dev). The only reason this package exists is to keep a local copy of it for myself, and to handle stylistic differences. + +## Features + +- Simple API; few brain cells required +- Tiny codebase; little overhead +- Zero dependencies; self-contained, built-in Go code. It's predictable! + +## Installation + +```shell +go get git.sharkk.net/Go/Assert +``` + +## Usage + +```go +assert.Nil(t, nil) +assert.True(t, true) +assert.Equal(t, "Hello", "Hello") +assert.DeepEqual(t, "Hello", "Hello") +assert.Contains(t, "Hello", "ello") +``` + +## Tests + +``` +PASS: TestContains +PASS: TestNotContains +PASS: TestFailContains +PASS: TestFailNotContains +PASS: TestEqual +PASS: TestNotEqual +PASS: TestDeepEqual +PASS: TestFailEqual +PASS: TestFailNotEqual +PASS: TestFailDeepEqual +PASS: TestNil +PASS: TestNotNil +PASS: TestFailNil +PASS: TestFailNotNil +PASS: TestTrue +coverage: 100.0% of statements +``` \ No newline at end of file diff --git a/contains.go b/contains.go new file mode 100644 index 0000000..84437ae --- /dev/null +++ b/contains.go @@ -0,0 +1,86 @@ +package assert + +import ( + "reflect" + "strings" +) + +// Contains asserts that a contains b +func Contains(t test, a any, b any) { + if contains(a, b) { + return + } + + t.Errorf(twoValues, file(), "Contains", a, b) + t.FailNow() +} + +// NotContains asserts that a doesn't contain b +func NotContains(t test, a any, b any) { + if !contains(a, b) { + return + } + + t.Errorf(twoValues, file(), "NotContains", a, b) + t.FailNow() +} + +// contains returns whether container contains the given the element +// It works with strings, maps and slices +func contains(container any, element any) bool { + containerValue := reflect.ValueOf(container) + + switch containerValue.Kind() { + case reflect.String: + elementValue := reflect.ValueOf(element) + return strings.Contains(containerValue.String(), elementValue.String()) + + case reflect.Map: + keys := containerValue.MapKeys() + + for _, key := range keys { + if key.Interface() == element { + return true + } + } + + case reflect.Slice: + elementValue := reflect.ValueOf(element) + + if elementValue.Kind() == reflect.Slice { + elementLength := elementValue.Len() + + if elementLength == 0 { + return true + } + + if elementLength > containerValue.Len() { + return false + } + + matchingElements := 0 + + for i := 0; i < containerValue.Len(); i++ { + if containerValue.Index(i).Interface() == elementValue.Index(matchingElements).Interface() { + matchingElements++ + } else { + matchingElements = 0 + } + + if matchingElements == elementLength { + return true + } + } + + return false + } + + for i := 0; i < containerValue.Len(); i++ { + if containerValue.Index(i).Interface() == element { + return true + } + } + } + + return false +} diff --git a/equal.go b/equal.go new file mode 100644 index 0000000..2f71404 --- /dev/null +++ b/equal.go @@ -0,0 +1,33 @@ +package assert + +import "reflect" + +// Equal asserts that the two given values are equal +func Equal[T comparable](t test, a T, b T) { + if a == b { + return + } + + t.Errorf(twoValues, file(), "Equal", a, b) + t.FailNow() +} + +// NotEqual asserts that the two given values are not equal +func NotEqual[T comparable](t test, a T, b T) { + if a != b { + return + } + + t.Errorf(twoValues, file(), "NotEqual", a, b) + t.FailNow() +} + +// DeepEqual asserts that the two given values are deeply equal +func DeepEqual[T any](t test, a T, b T) { + if reflect.DeepEqual(a, b) { + return + } + + t.Errorf(twoValues, file(), "DeepEqual", a, b) + t.FailNow() +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..f73f53e --- /dev/null +++ b/errors.go @@ -0,0 +1,41 @@ +package assert + +import ( + "runtime/debug" + "strings" +) + +const oneValue = ` +%s +assert.%s + %v +` + +const twoValues = ` +%s +assert.%s + %v + %v +` + +// file returns the first line containing "_test.go" in the debug stack +func file() string { + stack := string(debug.Stack()) + lines := strings.Split(stack, "\n") + name := "" + + for _, line := range lines { + if strings.Contains(line, "_test.go") { + space := strings.LastIndex(line, " ") + + if space != -1 { + line = line[:space] + } + + name = strings.TrimSpace(line) + break + } + } + + return name +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d6661f2 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.sharkk.net/Go/Assert + +go 1.23.5 diff --git a/nil.go b/nil.go new file mode 100644 index 0000000..684ac3a --- /dev/null +++ b/nil.go @@ -0,0 +1,39 @@ +package assert + +import "reflect" + +// Nil asserts that the given value equals nil +func Nil(t test, a any) { + if isNil(a) { + return + } + + t.Errorf(oneValue, file(), "Nil", a) + t.FailNow() +} + +// NotNil asserts that the given value does not equal nil +func NotNil(t test, a any) { + if !isNil(a) { + return + } + + t.Errorf(oneValue, file(), "NotNil", a) + t.FailNow() +} + +// isNil returns true if the object is nil. +func isNil(object any) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + + switch value.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: + return value.IsNil() + } + + return false +} diff --git a/test.go b/test.go new file mode 100644 index 0000000..88d1c99 --- /dev/null +++ b/test.go @@ -0,0 +1,7 @@ +package assert + +// test is the interface used for tests +type test interface { + Errorf(format string, args ...any) + FailNow() +} diff --git a/test_test.go b/test_test.go new file mode 100644 index 0000000..c6816b6 --- /dev/null +++ b/test_test.go @@ -0,0 +1,128 @@ +package assert_test + +import ( + "testing" + + assert "git.sharkk.net/Go/Assert" +) + +type T struct{ A int } + +// noop implements the Test interface with noop functions so you can test the failure cases +type noop struct{} + +func (t *noop) Errorf(format string, args ...any) {} +func (t *noop) FailNow() {} + +// fail creates a new test that is expected to fail +func fail(_ *testing.T) *noop { + return &noop{} +} + +func TestEqual(t *testing.T) { + assert.Equal(t, 0, 0) + assert.Equal(t, "Hello", "Hello") + assert.Equal(t, T{A: 10}, T{A: 10}) +} + +func TestNotEqual(t *testing.T) { + assert.NotEqual(t, 0, 1) + assert.NotEqual(t, "Hello", "World") + assert.NotEqual(t, &T{A: 10}, &T{A: 10}) + assert.NotEqual(t, T{A: 10}, T{A: 20}) +} + +func TestDeepEqual(t *testing.T) { + assert.DeepEqual(t, 0, 0) + assert.DeepEqual(t, "Hello", "Hello") + assert.DeepEqual(t, []byte("Hello"), []byte("Hello")) + assert.DeepEqual(t, T{A: 10}, T{A: 10}) + assert.DeepEqual(t, &T{A: 10}, &T{A: 10}) +} + +func TestFailEqual(t *testing.T) { + assert.Equal(fail(t), 0, 1) +} + +func TestFailNotEqual(t *testing.T) { + assert.NotEqual(fail(t), 0, 0) +} + +func TestFailDeepEqual(t *testing.T) { + assert.DeepEqual(fail(t), "Hello", "World") +} + +func TestTrue(t *testing.T) { + assert.True(t, true) + assert.False(t, false) +} + +func TestContains(t *testing.T) { + assert.Contains(t, "Hello", "H") + assert.Contains(t, "Hello", "Hello") + assert.Contains(t, []string{"Hello", "World"}, "Hello") + assert.Contains(t, []int{1, 2, 3}, 2) + assert.Contains(t, []int{1, 2, 3}, []int{}) + assert.Contains(t, []int{1, 2, 3}, []int{1, 2}) + assert.Contains(t, []byte{'H', 'e', 'l', 'l', 'o'}, byte('e')) + assert.Contains(t, []byte{'H', 'e', 'l', 'l', 'o'}, []byte{'e', 'l'}) + assert.Contains(t, map[string]int{"Hello": 1, "World": 2}, "Hello") +} + +func TestNotContains(t *testing.T) { + assert.NotContains(t, "Hello", "h") + assert.NotContains(t, "Hello", "hello") + assert.NotContains(t, []string{"Hello", "World"}, "hello") + assert.NotContains(t, []int{1, 2, 3}, 4) + assert.NotContains(t, []int{1, 2, 3}, []int{2, 1}) + assert.NotContains(t, []int{1, 2, 3}, []int{1, 2, 3, 4}) + assert.NotContains(t, []byte{'H', 'e', 'l', 'l', 'o'}, byte('a')) + assert.NotContains(t, []byte{'H', 'e', 'l', 'l', 'o'}, []byte{'l', 'e'}) + assert.NotContains(t, map[string]int{"Hello": 1, "World": 2}, "hello") +} + +func TestFailContains(t *testing.T) { + assert.Contains(fail(t), "Hello", "h") +} + +func TestFailNotContains(t *testing.T) { + assert.NotContains(fail(t), "Hello", "H") +} + +func TestNil(t *testing.T) { + var ( + nilPointer *T + nilInterface any + nilSlice []byte + nilMap map[byte]byte + nilChannel chan byte + nilFunction func() + ) + + assert.Nil(t, nil) + assert.Nil(t, nilPointer) + assert.Nil(t, nilInterface) + assert.Nil(t, nilSlice) + assert.Nil(t, nilMap) + assert.Nil(t, nilChannel) + assert.Nil(t, nilFunction) +} + +func TestNotNil(t *testing.T) { + assert.NotNil(t, 0) + assert.NotNil(t, "Hello") + assert.NotNil(t, T{}) + assert.NotNil(t, &T{}) + assert.NotNil(t, make([]byte, 0)) + assert.NotNil(t, make(map[byte]byte)) + assert.NotNil(t, make(chan byte)) + assert.NotNil(t, TestNotNil) +} + +func TestFailNil(t *testing.T) { + assert.Nil(fail(t), 0) +} + +func TestFailNotNil(t *testing.T) { + assert.NotNil(fail(t), nil) +} diff --git a/true.go b/true.go new file mode 100644 index 0000000..d6b8cbb --- /dev/null +++ b/true.go @@ -0,0 +1,11 @@ +package assert + +// True asserts that the given value is true +func True(t test, a bool) { + Equal(t, a, true) +} + +// False asserts that the given value is false +func False(t test, a bool) { + Equal(t, a, false) +}