Compare commits
No commits in common. "aac8b11a0125d5cef6b22d06350f9a7cc4e204eb" and "7f1cf242935074ecd905ba13cdfc44c3c0871ef9" have entirely different histories.
aac8b11a01
...
7f1cf24293
26
flake.lock
26
flake.lock
@ -1,26 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1703351344,
|
||||
"narHash": "sha256-9FEelzftkE9UaJ5nqxidaJJPEhe9TPhbypLHmc2Mysc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7790e078f8979a9fcd543f9a47427eeaba38f268",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-23.05",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
43
flake.nix
43
flake.nix
@ -1,43 +0,0 @@
|
||||
{
|
||||
description = "radix development environment";
|
||||
|
||||
# Nixpkgs / NixOS version to use.
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-23.05";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
|
||||
# to work with older version of flakes
|
||||
lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";
|
||||
|
||||
# Generate a user-friendly version number.
|
||||
version = builtins.substring 0 8 lastModifiedDate;
|
||||
|
||||
# System types to support.
|
||||
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
|
||||
|
||||
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
|
||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||
|
||||
# Nixpkgs instantiated for supported system types.
|
||||
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
# Add dependencies that are only needed for development
|
||||
devShells = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
in {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.go
|
||||
pkgs.gotools
|
||||
pkgs.golangci-lint
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -1,3 +1,3 @@
|
||||
module github.com/mediocregopher/mediocre-go-lib/v2
|
||||
|
||||
go 1.20
|
||||
go 1.15
|
||||
|
@ -1,26 +0,0 @@
|
||||
// massert helper assertion methods for tests.
|
||||
package massert
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Equal fatals if reflect.DeepEqual fails.
|
||||
func Equal[T any](t testing.TB, want, got T) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("%#v != %#v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Equalf is like Equal but extra formatting is appended.
|
||||
func Equalf[T any](
|
||||
t testing.TB, want, got T, fmtStr string, fmtArgs ...any,
|
||||
) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
fmtArgs = append([]any{want, got}, fmtArgs...)
|
||||
t.Fatalf("%#v != %#v "+fmtStr, fmtArgs...)
|
||||
}
|
||||
}
|
@ -2,8 +2,9 @@ package mctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
. "testing"
|
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mtest/massert"
|
||||
)
|
||||
|
||||
type testAnnotator [2]string
|
||||
@ -21,36 +22,32 @@ func TestAnnotate(t *T) {
|
||||
aa := Annotations{}
|
||||
EvaluateAnnotations(ctx, aa)
|
||||
|
||||
wantAA := Annotations{
|
||||
massert.Require(t,
|
||||
massert.Equal(Annotations{
|
||||
"a": "foo",
|
||||
"b": "BAR",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wantAA, aa) {
|
||||
t.Fatalf("%#v != %#v", wantAA, aa)
|
||||
}
|
||||
}, aa),
|
||||
)
|
||||
}
|
||||
|
||||
func TestAnnotationsStringMap(t *T) {
|
||||
type A int
|
||||
type B int
|
||||
got := Annotations{
|
||||
aa := Annotations{
|
||||
0: "zero",
|
||||
1: "one",
|
||||
A(2): "two",
|
||||
B(2): "TWO",
|
||||
}.StringMap()
|
||||
}
|
||||
|
||||
want := map[string]string{
|
||||
massert.Require(t,
|
||||
massert.Equal(map[string]string{
|
||||
"0": "zero",
|
||||
"1": "one",
|
||||
"mctx.A(2)": "two",
|
||||
"mctx.B(2)": "TWO",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("%#v != %#v", want, got)
|
||||
}
|
||||
}, aa.StringMap()),
|
||||
)
|
||||
}
|
||||
|
||||
func TestMergeAnnotations(t *T) {
|
||||
@ -63,14 +60,12 @@ func TestMergeAnnotations(t *T) {
|
||||
aa := Annotations{}
|
||||
EvaluateAnnotations(ctx, aa)
|
||||
|
||||
got := aa.StringMap()
|
||||
want := map[string]string{
|
||||
err := massert.Equal(map[string]string{
|
||||
"0": "ZERO",
|
||||
"1": "ONE",
|
||||
"2": "TWO",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("%#v != %#v", want, got)
|
||||
}, aa.StringMap()).Assert()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/internal/massert"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mtest/massert"
|
||||
)
|
||||
|
||||
func TestFullError(t *testing.T) {
|
||||
massert.Equal(t, nil, Wrap(context.Background(), nil))
|
||||
massert.Require(t, massert.Nil(Wrap(context.Background(), nil)))
|
||||
|
||||
ctx := mctx.Annotate(context.Background(),
|
||||
"a", "aaa aaa\n",
|
||||
@ -27,7 +27,7 @@ func TestFullError(t *testing.T) {
|
||||
ccc
|
||||
* d: weird key but ok
|
||||
* line: merr/merr_test.go:22`
|
||||
massert.Equal(t, exp, e.(Error).FullError())
|
||||
massert.Require(t, massert.Equal(exp, e.(Error).FullError()))
|
||||
}
|
||||
|
||||
{
|
||||
@ -39,7 +39,7 @@ func TestFullError(t *testing.T) {
|
||||
ccc
|
||||
* d: weird key but ok
|
||||
* line: merr/merr_test.go:34`
|
||||
massert.Equal(t, exp, e.(Error).FullError())
|
||||
massert.Require(t, massert.Equal(exp, e.(Error).FullError()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,9 +136,10 @@ func TestAsIsError(t *testing.T) {
|
||||
var in Error
|
||||
ok := errors.As(test.in, &in)
|
||||
|
||||
massert.Equalf(
|
||||
t, ok, test.expAs != nil, "test.in:%#v ok:%v", test.in, ok,
|
||||
)
|
||||
massert.Require(t, massert.Comment(
|
||||
massert.Equal(test.expAs != nil, ok),
|
||||
"test.in:%#v ok:%v", test.in, ok,
|
||||
))
|
||||
|
||||
if test.expAs == nil {
|
||||
return
|
||||
@ -151,13 +152,15 @@ func TestAsIsError(t *testing.T) {
|
||||
in.Ctx = nil
|
||||
expAs.Ctx = nil
|
||||
|
||||
massert.Equal(t, expAsAA, inAA)
|
||||
massert.Equal(t, expAs, in)
|
||||
massert.Equalf(
|
||||
t, true, errors.Is(test.in, test.expIs),
|
||||
massert.Require(t,
|
||||
massert.Equal(expAsAA, inAA),
|
||||
massert.Equal(expAs, in),
|
||||
massert.Comment(
|
||||
massert.Equal(true, errors.Is(test.in, test.expIs)),
|
||||
"errors.Is(\ntest.in:%#v,\ntest.expIs:%#v,\n)", test.in, test.expIs,
|
||||
),
|
||||
massert.Equal(test.expStr, test.in.Error()),
|
||||
)
|
||||
massert.Equal(t, test.expStr, test.in.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
136
miter/iter.go
136
miter/iter.go
@ -1,136 +0,0 @@
|
||||
// Package miter implements a simple iterator type, as well as helper types
|
||||
// around it.
|
||||
package miter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrEnd is returned from Iterator's Next method when the iterator has
|
||||
// completed.
|
||||
var ErrEnd = errors.New("end")
|
||||
|
||||
// Iterator is used to visit a sequence of elements one by one.
|
||||
type Iterator[T any] interface {
|
||||
|
||||
// Next will block until:
|
||||
//
|
||||
// - It returns the next item in the sequence
|
||||
// - The Context is canceled and ctx.Err() is returned
|
||||
// - ErrEnd is returned, indicating there are no more items in the sequence
|
||||
// - Some other error is returned, indicating an unexpected error occurred
|
||||
//
|
||||
// If any non-context error is returned then Next should not be called
|
||||
// again.
|
||||
Next(ctx context.Context) (T, error)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type iterFn[T any] func(context.Context) (T, error)
|
||||
|
||||
// FromFunc wraps a function such that it implements Iterator.
|
||||
func FromFunc[T any](fn func(context.Context) (T, error)) Iterator[T] {
|
||||
return iterFn[T](fn)
|
||||
}
|
||||
|
||||
func (f iterFn[T]) Next(ctx context.Context) (T, error) { return f(ctx) }
|
||||
|
||||
// Error returns an Iterator which will always return the given error as the
|
||||
// error result.
|
||||
func Error[T any](err error) Iterator[T] {
|
||||
return FromFunc(func(context.Context) (T, error) {
|
||||
var zero T
|
||||
return zero, err
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ToSlice consumes all items off the given Iterator until ErrEnd, creating and
|
||||
// returning a slice. If the Iterator ever returns any other error then that
|
||||
// error is returned, along with all consumed items so far.
|
||||
func ToSlice[T any](ctx context.Context, i Iterator[T]) ([]T, error) {
|
||||
var s []T
|
||||
for {
|
||||
v, err := i.Next(ctx)
|
||||
if errors.Is(err, ErrEnd) {
|
||||
return s, nil
|
||||
} else if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
s = append(s, v)
|
||||
}
|
||||
}
|
||||
|
||||
type iterSlice[T any] []T
|
||||
|
||||
// FromSlice wraps a slice such that it implements Iterator.
|
||||
func FromSlice[T any](s []T) Iterator[T] {
|
||||
it := iterSlice[T](s)
|
||||
return &it
|
||||
}
|
||||
|
||||
func (s *iterSlice[T]) Next(context.Context) (T, error) {
|
||||
if len(*s) == 0 {
|
||||
var zero T
|
||||
return zero, ErrEnd
|
||||
}
|
||||
|
||||
next := (*s)[0]
|
||||
*s = (*s)[1:]
|
||||
|
||||
return next, nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type iterCh[T any] <-chan T
|
||||
|
||||
// FromChannel wraps a channel such that it implements Iterator.
|
||||
func FromChannel[T any](ch <-chan T) Iterator[T] {
|
||||
return iterCh[T](ch)
|
||||
}
|
||||
|
||||
func (ch iterCh[T]) Next(ctx context.Context) (T, error) {
|
||||
var (
|
||||
v T
|
||||
ok bool
|
||||
)
|
||||
|
||||
select {
|
||||
case v, ok = <-ch:
|
||||
if !ok {
|
||||
return v, ErrEnd
|
||||
}
|
||||
return v, nil
|
||||
|
||||
case <-ctx.Done():
|
||||
return v, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Lazily returns an Iterator which will initialize itself using the given
|
||||
// callback upon the first call to Next. If the callback returns an error then
|
||||
// this error is returned from all calls to Next.
|
||||
//
|
||||
// Lazily is useful for cases where an Iterator requires a Context to
|
||||
// initialize, but no Context is available.
|
||||
func Lazily[T any](
|
||||
fn func(ctx context.Context) (Iterator[T], error),
|
||||
) Iterator[T] {
|
||||
var i Iterator[T]
|
||||
return FromFunc(func(ctx context.Context) (T, error) {
|
||||
if i == nil {
|
||||
var err error
|
||||
if i, err = fn(ctx); err != nil {
|
||||
i = Error[T](err)
|
||||
}
|
||||
}
|
||||
return i.Next(ctx)
|
||||
})
|
||||
}
|
102
miter/util.go
102
miter/util.go
@ -1,102 +0,0 @@
|
||||
package miter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Empty returns the empty Iterator, i.e. one which will immediately produce
|
||||
// ErrEnd.
|
||||
func Empty[T any]() Iterator[T] { return Error[T](ErrEnd) }
|
||||
|
||||
// ForEach calls fn with each item read off the iterator, returning nil once
|
||||
// ErrEnd is returned from the Iterator or function. If the Iterator or function
|
||||
// return any other error then that is returned instead.
|
||||
func ForEach[T any](
|
||||
ctx context.Context, i Iterator[T], fn func(T) error,
|
||||
) error {
|
||||
for {
|
||||
v, err := i.Next(ctx)
|
||||
if errors.Is(err, ErrEnd) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(v); errors.Is(err, ErrEnd) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map will read items from the given Iterator, pass them through the given
|
||||
// mapping function, and produce the mapped items from the returned Iterator.
|
||||
//
|
||||
// If the mapping function returns an error then that is returned from returned
|
||||
// Iterator, and no more calls should be made to it.
|
||||
func Map[T1, T2 any](i Iterator[T1], fn func(T1) (T2, error)) Iterator[T2] {
|
||||
return FromFunc(func(ctx context.Context) (T2, error) {
|
||||
v, err := i.Next(ctx)
|
||||
if err != nil {
|
||||
var zero T2
|
||||
return zero, err
|
||||
}
|
||||
|
||||
return fn(v)
|
||||
})
|
||||
}
|
||||
|
||||
// Concat concats all the given Iterators together into a single larger one.
|
||||
// Each Iterator will be consumed until ErrEnd in turn. Any other errors from
|
||||
// the inner Iterators will be returned as-is from the outer one.
|
||||
func Concat[T any](iters ...Iterator[T]) Iterator[T] {
|
||||
var (
|
||||
i int
|
||||
zero T
|
||||
)
|
||||
|
||||
return FromFunc(func(ctx context.Context) (T, error) {
|
||||
for {
|
||||
if i >= len(iters) {
|
||||
return zero, ErrEnd
|
||||
}
|
||||
|
||||
v, err := iters[i].Next(ctx)
|
||||
if errors.Is(err, ErrEnd) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Filter returns an Iterator which will produce all items from the given
|
||||
// Iterator for which the function returns true. If the function returns any
|
||||
// error then that error is returned as-is.
|
||||
func Filter[T any](
|
||||
i Iterator[T], fn func(context.Context, T) (bool, error),
|
||||
) Iterator[T] {
|
||||
var zero T
|
||||
|
||||
return FromFunc(func(ctx context.Context) (T, error) {
|
||||
for {
|
||||
v, err := i.Next(ctx)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
keep, err := fn(ctx, v)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
if keep {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -6,36 +6,39 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
. "testing"
|
||||
"time"
|
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/internal/massert"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||
"github.com/mediocregopher/mediocre-go-lib/v2/mtest/massert"
|
||||
)
|
||||
|
||||
func TestTruncate(t *T) {
|
||||
massert.Equal(t, "abc", Truncate("abc", 4))
|
||||
massert.Equal(t, "abc", Truncate("abc", 3))
|
||||
massert.Equal(t, "ab...", Truncate("abc", 2))
|
||||
massert.Require(t,
|
||||
massert.Equal("abc", Truncate("abc", 4)),
|
||||
massert.Equal("abc", Truncate("abc", 3)),
|
||||
massert.Equal("ab...", Truncate("abc", 2)),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJSONLogger(t *T) {
|
||||
func TestLogger(t *T) {
|
||||
buf := new(bytes.Buffer)
|
||||
now := time.Now().UTC()
|
||||
td, ts := now.Format(msgTimeFormat), fmt.Sprint(now.UnixNano())
|
||||
|
||||
l := NewLogger(&LoggerOpts{
|
||||
MessageHandler: NewJSONMessageHandler(buf),
|
||||
MessageHandler: NewMessageHandler(buf),
|
||||
Now: func() time.Time { return now },
|
||||
})
|
||||
|
||||
assertOut := func(t *testing.T, expected string) {
|
||||
assertOut := func(expected string) massert.Assertion {
|
||||
expected = strings.ReplaceAll(expected, "<TD>", td)
|
||||
expected = strings.ReplaceAll(expected, "<TS>", ts)
|
||||
out, err := buf.ReadString('\n')
|
||||
massert.Equal(t, nil, err)
|
||||
massert.Equal(t, expected, strings.TrimSpace(out))
|
||||
return massert.All(
|
||||
massert.Nil(err),
|
||||
massert.Equal(expected, strings.TrimSpace(out)),
|
||||
)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
@ -45,17 +48,23 @@ func TestJSONLogger(t *T) {
|
||||
l.Info(ctx, "bar")
|
||||
l.Warn(ctx, "baz", errors.New("ERR"))
|
||||
l.Error(ctx, "buz", errors.New("ERR"))
|
||||
assertOut(t, `{"td":"<TD>","ts":<TS>,"level":"INFO","descr":"bar","level_int":30}`)
|
||||
assertOut(t, `{"td":"<TD>","ts":<TS>,"level":"WARN","descr":"baz","level_int":20,"annotations":{"errMsg":"ERR"}}`)
|
||||
assertOut(t, `{"td":"<TD>","ts":<TS>,"level":"ERROR","descr":"buz","level_int":10,"annotations":{"errMsg":"ERR"}}`)
|
||||
massert.Require(t,
|
||||
assertOut(`{"td":"<TD>","ts":<TS>,"level":"INFO","descr":"bar","level_int":30}`),
|
||||
assertOut(`{"td":"<TD>","ts":<TS>,"level":"WARN","descr":"baz","level_int":20,"annotations":{"errMsg":"ERR"}}`),
|
||||
assertOut(`{"td":"<TD>","ts":<TS>,"level":"ERROR","descr":"buz","level_int":10,"annotations":{"errMsg":"ERR"}}`),
|
||||
)
|
||||
|
||||
// annotate context
|
||||
ctx = mctx.Annotate(ctx, "foo", "bar")
|
||||
l.Info(ctx, "bar")
|
||||
assertOut(t, `{"td":"<TD>","ts":<TS>,"level":"INFO","descr":"bar","level_int":30,"annotations":{"foo":"bar"}}`)
|
||||
massert.Require(t,
|
||||
assertOut(`{"td":"<TD>","ts":<TS>,"level":"INFO","descr":"bar","level_int":30,"annotations":{"foo":"bar"}}`),
|
||||
)
|
||||
|
||||
// add namespace
|
||||
l = l.WithNamespace("ns")
|
||||
l.Info(ctx, "bar")
|
||||
assertOut(t, `{"td":"<TD>","ts":<TS>,"level":"INFO","ns":["ns"],"descr":"bar","level_int":30,"annotations":{"foo":"bar"}}`)
|
||||
massert.Require(t,
|
||||
assertOut(`{"td":"<TD>","ts":<TS>,"level":"INFO","ns":["ns"],"descr":"bar","level_int":30,"annotations":{"foo":"bar"}}`),
|
||||
)
|
||||
}
|
||||
|
462
mtest/massert/massert.go
Normal file
462
mtest/massert/massert.go
Normal file
@ -0,0 +1,462 @@
|
||||
// Package massert implements an assertion framework which is useful in tests.
|
||||
package massert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// AssertErr is an error returned by Assertions which have failed, containing
|
||||
// information about both the reason for failure and the Assertion itself.
|
||||
type AssertErr struct {
|
||||
Err error // The error which occurred
|
||||
Assertion Assertion // The Assertion which failed
|
||||
}
|
||||
|
||||
func fmtBlock(str string) string {
|
||||
if strings.Index(str, "\n") == -1 {
|
||||
return str
|
||||
}
|
||||
return "\n\t" + strings.Replace(str, "\n", "\n\t", -1) + "\n"
|
||||
}
|
||||
|
||||
func fmtMultiBlock(prefix string, elems ...string) string {
|
||||
if len(elems) == 0 {
|
||||
return prefix + "()"
|
||||
} else if len(elems) == 1 {
|
||||
return prefix + "(" + fmtBlock(elems[0]) + ")"
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "%s(\n", prefix)
|
||||
for _, el := range elems {
|
||||
elStr := "\t" + strings.Replace(el, "\n", "\n\t", -1)
|
||||
fmt.Fprintf(buf, "%s,\n", elStr)
|
||||
}
|
||||
fmt.Fprintf(buf, ")")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func fmtMultiDescr(prefix string, aa ...Assertion) string {
|
||||
descrs := make([]string, len(aa))
|
||||
for i := range aa {
|
||||
descrs[i] = aa[i].Description()
|
||||
}
|
||||
return fmtMultiBlock(prefix, descrs...)
|
||||
}
|
||||
|
||||
func fmtStack(frames []runtime.Frame) string {
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tabwriter.NewWriter(buf, 0, 4, 2, ' ', 0)
|
||||
for _, frame := range frames {
|
||||
file := filepath.Base(frame.File)
|
||||
fmt.Fprintf(tw, "%s:%d\t%s\n", file, frame.Line, frame.Function)
|
||||
}
|
||||
if err := tw.Flush(); err != nil {
|
||||
panic(err) // fuck it
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (ae AssertErr) Error() string {
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprintf(buf, "\n")
|
||||
fmt.Fprintf(buf, "Assertion: %s\n", fmtBlock(ae.Assertion.Description()))
|
||||
fmt.Fprintf(buf, "Error: %s\n", fmtBlock(ae.Err.Error()))
|
||||
fmt.Fprintf(buf, "Stack: %s\n", fmtBlock(fmtStack(ae.Assertion.Stack())))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Assertion is an entity which will make some kind of assertion and produce an
|
||||
// error if that assertion does not hold true. The error returned will generally
|
||||
// be of type AssertErr.
|
||||
type Assertion interface {
|
||||
Assert() error
|
||||
Description() string // A description of the Assertion
|
||||
|
||||
// Returns the callstack of where the Assertion was created, ordered from
|
||||
// closest to farthest. This may not necessarily contain the entire
|
||||
// callstack if that would be inconveniently cumbersome.
|
||||
Stack() []runtime.Frame
|
||||
}
|
||||
|
||||
const maxStackLen = 8
|
||||
|
||||
type assertion struct {
|
||||
fn func() error
|
||||
descr string
|
||||
stack []runtime.Frame
|
||||
}
|
||||
|
||||
func newFutureAssertion(assertFn func() error, descr string, skip int) Assertion {
|
||||
pcs := make([]uintptr, maxStackLen)
|
||||
// first skip is for runtime.Callers, second is for newAssertion, third is
|
||||
// for whatever is calling newAssertion
|
||||
numPCs := runtime.Callers(skip+3, pcs)
|
||||
stack := make([]runtime.Frame, 0, maxStackLen)
|
||||
frames := runtime.CallersFrames(pcs[:numPCs])
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
stack = append(stack, frame)
|
||||
if !more || len(stack) == maxStackLen {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
a := &assertion{
|
||||
descr: descr,
|
||||
stack: stack,
|
||||
}
|
||||
a.fn = func() error {
|
||||
err := assertFn()
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if ae, ok := err.(AssertErr); ok {
|
||||
return ae
|
||||
}
|
||||
return AssertErr{
|
||||
Err: err,
|
||||
Assertion: a,
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func newAssertion(assertFn func() error, descr string, skip int) Assertion {
|
||||
err := assertFn()
|
||||
return newFutureAssertion(func() error { return err }, descr, skip+1)
|
||||
}
|
||||
|
||||
func (a *assertion) Assert() error {
|
||||
return a.fn()
|
||||
}
|
||||
|
||||
func (a *assertion) Description() string {
|
||||
return a.descr
|
||||
}
|
||||
|
||||
func (a *assertion) Stack() []runtime.Frame {
|
||||
return a.stack
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Require is a convenience function which performs the Assertions and calls
|
||||
// Fatal on the testing.T instance for the first Assertion which fails.
|
||||
func Require(t *testing.T, aa ...Assertion) {
|
||||
for _, a := range aa {
|
||||
if err := a.Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert is a convenience function which performs the Assertion and calls Error
|
||||
// on the testing.T instance for the first Assertion which fails.
|
||||
func Assert(t *testing.T, aa ...Assertion) {
|
||||
for _, a := range aa {
|
||||
if err := a.Assert(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Assertion wrappers
|
||||
|
||||
// if the Assertion is a wrapper for another, this makes sure that if the
|
||||
// underlying one returns an AssertErr that this Assertion is what ends up in
|
||||
// that AssertErr
|
||||
type wrap struct {
|
||||
Assertion
|
||||
}
|
||||
|
||||
func (wa wrap) Assert() error {
|
||||
err := wa.Assertion.Assert()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
ae := err.(AssertErr)
|
||||
ae.Assertion = wa.Assertion
|
||||
return ae
|
||||
}
|
||||
|
||||
type descrWrap struct {
|
||||
Assertion
|
||||
descr string
|
||||
}
|
||||
|
||||
func (dw descrWrap) Description() string {
|
||||
return dw.descr
|
||||
}
|
||||
|
||||
// Comment prepends a formatted string to the given Assertion's string
|
||||
// description.
|
||||
func Comment(a Assertion, msg string, args ...interface{}) Assertion {
|
||||
msg = strings.TrimSpace(msg)
|
||||
descr := fmt.Sprintf("/* "+msg+" */\n", args...)
|
||||
descr += a.Description()
|
||||
return wrap{descrWrap{Assertion: a, descr: descr}}
|
||||
}
|
||||
|
||||
// Not negates an Assertion, so that it fails if the given Assertion does not,
|
||||
// and vice-versa.
|
||||
func Not(a Assertion) Assertion {
|
||||
fn := func() error {
|
||||
if err := a.Assert(); err == nil {
|
||||
return errors.New("assertion should have failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newAssertion(fn, fmtMultiDescr("Not", a), 0)
|
||||
}
|
||||
|
||||
// Any asserts that at least one of the given Assertions succeeds.
|
||||
func Any(aa ...Assertion) Assertion {
|
||||
fn := func() error {
|
||||
for _, a := range aa {
|
||||
if err := a.Assert(); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("no assertions succeeded")
|
||||
}
|
||||
return newAssertion(fn, fmtMultiDescr("Any", aa...), 0)
|
||||
}
|
||||
|
||||
// AnyOne asserts that exactly one of the given Assertions succeeds.
|
||||
func AnyOne(aa ...Assertion) Assertion {
|
||||
fn := func() error {
|
||||
any := -1
|
||||
for i, a := range aa {
|
||||
if err := a.Assert(); err == nil {
|
||||
if any >= 0 {
|
||||
return fmt.Errorf("assertions indices %d and %d both succeeded", any, i)
|
||||
}
|
||||
any = i
|
||||
}
|
||||
}
|
||||
if any == -1 {
|
||||
return errors.New("no assertions succeeded")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newAssertion(fn, fmtMultiDescr("AnyOne", aa...), 0)
|
||||
}
|
||||
|
||||
// All asserts that at all of the given Assertions succeed. Its Assert method
|
||||
// will return the error of whichever Assertion failed.
|
||||
func All(aa ...Assertion) Assertion {
|
||||
fn := func() error {
|
||||
for _, a := range aa {
|
||||
if err := a.Assert(); err != nil {
|
||||
// newAssertion will pass this error through, so that its
|
||||
// description and callstack is what gets displayed as the
|
||||
// error. This isn't totally consistent with Any's behavior, but
|
||||
// it's fine.
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newAssertion(fn, fmtMultiDescr("All", aa...), 0)
|
||||
}
|
||||
|
||||
// None asserts that all of the given Assertions fail.
|
||||
//
|
||||
// NOTE this is functionally equivalent to doing `Not(Any(aa...))`, but the
|
||||
// error returned is more helpful.
|
||||
func None(aa ...Assertion) Assertion {
|
||||
fn := func() error {
|
||||
for _, a := range aa {
|
||||
if err := a.Assert(); err == nil {
|
||||
return AssertErr{
|
||||
Err: errors.New("assertion should not have succeeded"),
|
||||
Assertion: a,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newAssertion(fn, fmtMultiDescr("None", aa...), 0)
|
||||
}
|
||||
|
||||
// Error returns an Assertion which always fails with the given error.
|
||||
func Error(err error) Assertion {
|
||||
return newAssertion(func() error { return err }, "", 0)
|
||||
}
|
||||
|
||||
// Errorf is like Err but allows for a formatted string.
|
||||
func Errorf(str string, args ...interface{}) Assertion {
|
||||
return Error(fmt.Errorf(str, args...))
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func toStr(i interface{}) string {
|
||||
return fmt.Sprintf("%T(%#v)", i, i)
|
||||
}
|
||||
|
||||
// Equal asserts that the two values are exactly equal, and uses the
|
||||
// reflect.DeepEqual function to determine if they are.
|
||||
func Equal(a, b interface{}) Assertion {
|
||||
return newAssertion(func() error {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
return errors.New("not exactly equal")
|
||||
}
|
||||
return nil
|
||||
}, toStr(a)+" == "+toStr(b), 0)
|
||||
}
|
||||
|
||||
// Nil asserts that the value is nil. This assertion works both if the value is
|
||||
// the untyped nil value (e.g. `Nil(nil)`) or if it's a typed nil value (e.g.
|
||||
// `Nil([]byte(nil))`).
|
||||
func Nil(i interface{}) Assertion {
|
||||
return newAssertion(func() error {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface,
|
||||
reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
}
|
||||
return errors.New("not nil")
|
||||
}, toStr(i)+" is nil", 0)
|
||||
}
|
||||
|
||||
type setKV struct {
|
||||
k, v interface{}
|
||||
}
|
||||
|
||||
func toSet(i interface{}, keyedMap bool) ([]interface{}, error) {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
vv := make([]interface{}, v.Len())
|
||||
for i := range vv {
|
||||
vv[i] = v.Index(i).Interface()
|
||||
}
|
||||
return vv, nil
|
||||
case reflect.Map:
|
||||
keys := v.MapKeys()
|
||||
vv := make([]interface{}, len(keys))
|
||||
for i := range keys {
|
||||
if keyedMap {
|
||||
vv[i] = setKV{
|
||||
k: keys[i].Interface(),
|
||||
v: v.MapIndex(keys[i]).Interface(),
|
||||
}
|
||||
} else {
|
||||
vv[i] = v.MapIndex(keys[i]).Interface()
|
||||
}
|
||||
}
|
||||
return vv, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot turn value of type %s into a set", v.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// Subset asserts that the given subset is a subset of the given set. Both must
|
||||
// be of the same type and may be arrays, slices, or maps.
|
||||
func Subset(set, subset interface{}) Assertion {
|
||||
if reflect.TypeOf(set) != reflect.TypeOf(subset) {
|
||||
panic(errors.New("set and subset aren't of same type"))
|
||||
}
|
||||
|
||||
setVV, err := toSet(set, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
subsetVV, err := toSet(subset, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newAssertion(func() error {
|
||||
// this is obviously not the most efficient way to do this
|
||||
outer:
|
||||
for i := range subsetVV {
|
||||
for j := range setVV {
|
||||
if reflect.DeepEqual(setVV[j], subsetVV[i]) {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("missing element %s", toStr(subsetVV[i]))
|
||||
}
|
||||
return nil
|
||||
}, toStr(set)+" has subset "+toStr(subset), 0)
|
||||
}
|
||||
|
||||
// HasValue asserts that the given set has the given element as a value in it.
|
||||
// The set may be an array, a slice, or a map, and if it's a map then the elem
|
||||
// will need to be a value in it.
|
||||
func HasValue(set, elem interface{}) Assertion {
|
||||
setVV, err := toSet(set, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return newAssertion(func() error {
|
||||
for i := range setVV {
|
||||
if reflect.DeepEqual(setVV[i], elem) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("value not in set")
|
||||
}, toStr(set)+" has value "+toStr(elem), 0)
|
||||
}
|
||||
|
||||
// HasKey asserts that the given set (which must be a map type) has the given
|
||||
// element as a key in it.
|
||||
func HasKey(set, elem interface{}) Assertion {
|
||||
if v := reflect.ValueOf(set); v.Kind() != reflect.Map {
|
||||
panic(fmt.Errorf("type %s is not a map", v.Type()))
|
||||
}
|
||||
setVV, err := toSet(set, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newAssertion(func() error {
|
||||
for _, kv := range setVV {
|
||||
if reflect.DeepEqual(kv.(setKV).k, elem) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("value not a key in the map")
|
||||
}, toStr(set)+" has key "+toStr(elem), 0)
|
||||
}
|
||||
|
||||
// Length asserts that the given set has the given number of elements in it. The
|
||||
// set may be an array, a slice, or a map. A nil value'd set is considered to be
|
||||
// a length of zero.
|
||||
func Length(set interface{}, length int) Assertion {
|
||||
setVV, err := toSet(set, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return newAssertion(func() error {
|
||||
if len(setVV) != length {
|
||||
return fmt.Errorf("set not correct length, is %d", len(setVV))
|
||||
}
|
||||
return nil
|
||||
}, toStr(set)+" has length "+strconv.Itoa(length), 0)
|
||||
}
|
||||
|
||||
// TODO ChanRead(ch interface{}, within time.Duration, callback func(interface{}) error)
|
||||
// TODO ChanBlock(ch interface{}, for time.Duration)
|
||||
// TODO ChanClosed(ch interface{})
|
249
mtest/massert/massert_test.go
Normal file
249
mtest/massert/massert_test.go
Normal file
@ -0,0 +1,249 @@
|
||||
package massert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "testing"
|
||||
)
|
||||
|
||||
func succeed() Assertion {
|
||||
return newAssertion(func() error { return nil }, "Succeed", 0)
|
||||
}
|
||||
|
||||
func fail() Assertion {
|
||||
return newAssertion(func() error { return errors.New("failure") }, "Fail", 0)
|
||||
}
|
||||
|
||||
func TestNot(t *T) {
|
||||
if err := Not(succeed()).Assert(); err == nil {
|
||||
t.Fatal("Not(succeed()) should have failed")
|
||||
}
|
||||
|
||||
if err := Not(fail()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAny(t *T) {
|
||||
if err := Any().Assert(); err == nil {
|
||||
t.Fatal("empty Any should fail")
|
||||
}
|
||||
|
||||
if err := Any(succeed(), succeed()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Any(succeed(), fail()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Any(fail(), fail()).Assert(); err == nil {
|
||||
t.Fatal("Any should have failed with all inner fail Assertions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOne(t *T) {
|
||||
if err := AnyOne().Assert(); err == nil {
|
||||
t.Fatal("empty AnyOne should fail")
|
||||
}
|
||||
|
||||
if err := AnyOne(succeed(), succeed()).Assert(); err == nil {
|
||||
t.Fatal("AnyOne with two succeeds should fail")
|
||||
}
|
||||
|
||||
if err := AnyOne(succeed(), fail()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := AnyOne(fail(), fail()).Assert(); err == nil {
|
||||
t.Fatal("AnyOne should have failed with all inner fail Assertions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *T) {
|
||||
if err := All().Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := All(succeed(), succeed()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := All(succeed(), fail()).Assert(); err == nil {
|
||||
t.Fatal("All should have failed with one inner fail Assertion")
|
||||
}
|
||||
|
||||
if err := All(fail(), fail()).Assert(); err == nil {
|
||||
t.Fatal("All should have failed with all inner fail Assertions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNone(t *T) {
|
||||
if err := None().Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := None(succeed(), succeed()).Assert(); err == nil {
|
||||
t.Fatal("None should have failed with all inner succeed Assertions")
|
||||
}
|
||||
|
||||
if err := None(succeed(), fail()).Assert(); err == nil {
|
||||
t.Fatal("None should have failed with one inner succeed Assertion")
|
||||
}
|
||||
|
||||
if err := None(fail(), fail()).Assert(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqual(t *T) {
|
||||
Require(t,
|
||||
Equal(1, 1),
|
||||
Equal("foo", "foo"),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
Equal(1, 2),
|
||||
Equal(1, int64(1)),
|
||||
Equal(1, uint64(1)),
|
||||
Equal("foo", "bar"),
|
||||
))
|
||||
|
||||
// test that assertions take in the value at the moment the assertion is
|
||||
// made
|
||||
var aa []Assertion
|
||||
m := map[string]int{}
|
||||
m["foo"] = 1
|
||||
aa = append(aa, Equal(1, m["foo"]))
|
||||
m["foo"] = 2
|
||||
aa = append(aa, Equal(2, m["foo"]))
|
||||
Require(t, aa...)
|
||||
}
|
||||
|
||||
func TestNil(t *T) {
|
||||
Require(t,
|
||||
Nil(nil),
|
||||
Nil([]byte(nil)),
|
||||
Nil(map[int]int(nil)),
|
||||
Nil((*struct{})(nil)),
|
||||
Nil(interface{}(nil)),
|
||||
Nil(error(nil)),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
Nil(1),
|
||||
Nil([]byte("foo")),
|
||||
Nil(map[int]int{1: 1}),
|
||||
Nil(&struct{}{}),
|
||||
Nil(interface{}("hi")),
|
||||
Nil(errors.New("some error")),
|
||||
))
|
||||
}
|
||||
|
||||
func TestSubset(t *T) {
|
||||
Require(t,
|
||||
Subset([]int{1, 2, 3}, []int{}),
|
||||
Subset([]int{1, 2, 3}, []int{1}),
|
||||
Subset([]int{1, 2, 3}, []int{2}),
|
||||
Subset([]int{1, 2, 3}, []int{1, 2}),
|
||||
Subset([]int{1, 2, 3}, []int{2, 1}),
|
||||
Subset([]int{1, 2, 3}, []int{1, 2, 3}),
|
||||
Subset([]int{1, 2, 3}, []int{1, 3, 2}),
|
||||
|
||||
Subset(map[int]int{1: 1, 2: 2}, map[int]int{}),
|
||||
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1}),
|
||||
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 2: 2}),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
Subset([]int{}, []int{1, 2, 3}),
|
||||
Subset([]int{1, 2, 3}, []int{4}),
|
||||
Subset([]int{1, 2, 3}, []int{1, 3, 2, 4}),
|
||||
|
||||
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 2}),
|
||||
Subset(map[int]int{1: 1, 2: 2}, map[int]int{1: 1, 3: 3}),
|
||||
))
|
||||
|
||||
// make sure changes don't retroactively fail the assertion
|
||||
m := map[int]int{1: 1, 2: 2}
|
||||
a := Subset(m, map[int]int{1: 1})
|
||||
m[1] = 2
|
||||
Require(t, a)
|
||||
}
|
||||
|
||||
func TestHasValue(t *T) {
|
||||
Require(t,
|
||||
HasValue([]int{1}, 1),
|
||||
HasValue([]int{1, 2}, 1),
|
||||
HasValue([]int{2, 1}, 1),
|
||||
HasValue(map[int]int{1: 1}, 1),
|
||||
HasValue(map[int]int{1: 2}, 2),
|
||||
HasValue(map[int]int{1: 2, 2: 1}, 1),
|
||||
HasValue(map[int]int{1: 2, 2: 2}, 2),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
HasValue([]int{}, 1),
|
||||
HasValue([]int{1}, 2),
|
||||
HasValue([]int{2, 1}, 3),
|
||||
HasValue(map[int]int{}, 1),
|
||||
HasValue(map[int]int{1: 1}, 2),
|
||||
HasValue(map[int]int{1: 2}, 1),
|
||||
HasValue(map[int]int{1: 2, 2: 1}, 3),
|
||||
))
|
||||
|
||||
// make sure changes don't retroactively fail the assertion
|
||||
m := map[int]int{1: 1}
|
||||
a := HasValue(m, 1)
|
||||
m[1] = 2
|
||||
Require(t, a)
|
||||
}
|
||||
|
||||
func TestHasKey(t *T) {
|
||||
Require(t,
|
||||
HasKey(map[int]int{1: 1}, 1),
|
||||
HasKey(map[int]int{1: 1, 2: 2}, 1),
|
||||
HasKey(map[int]int{1: 1, 2: 2}, 2),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
HasKey(map[int]int{}, 1),
|
||||
HasKey(map[int]int{2: 2}, 1),
|
||||
))
|
||||
|
||||
// make sure changes don't retroactively fail the assertion
|
||||
m := map[int]int{1: 1}
|
||||
a := HasKey(m, 1)
|
||||
delete(m, 1)
|
||||
Require(t, a)
|
||||
|
||||
}
|
||||
|
||||
func TestLength(t *T) {
|
||||
Require(t,
|
||||
Length([]int(nil), 0),
|
||||
Length([]int{}, 0),
|
||||
Length([]int{1}, 1),
|
||||
Length([]int{1, 2}, 2),
|
||||
Length(map[int]int(nil), 0),
|
||||
Length(map[int]int{}, 0),
|
||||
Length(map[int]int{1: 1}, 1),
|
||||
Length(map[int]int{1: 1, 2: 2}, 2),
|
||||
)
|
||||
|
||||
Require(t, None(
|
||||
Length([]int(nil), 1),
|
||||
Length([]int{}, 1),
|
||||
Length([]int{1}, 0),
|
||||
Length([]int{1}, 2),
|
||||
Length([]int{1, 2}, 1),
|
||||
Length([]int{1, 2}, 3),
|
||||
Length(map[int]int(nil), 1),
|
||||
Length(map[int]int{}, 1),
|
||||
))
|
||||
|
||||
// make sure changes don't retroactively fail the assertion
|
||||
m := map[int]int{1: 1}
|
||||
a := Length(m, 1)
|
||||
m[2] = 2
|
||||
Require(t, a)
|
||||
}
|
Loading…
Reference in New Issue
Block a user