🔀 Merge branch 'rel/0.0.11' into stable

This commit is contained in:
Dan Jones 2026-02-28 16:17:51 -06:00
commit a761ab138b
20 changed files with 373 additions and 297 deletions

View file

@ -15,7 +15,7 @@
- **Types**: Use explicit types (e.g., `int64`, `float64`). Convert numbers appropriately when unmarshaling JSON - **Types**: Use explicit types (e.g., `int64`, `float64`). Convert numbers appropriately when unmarshaling JSON
- **Naming**: PascalCase for exported, camelCase for unexported. Use descriptive names - **Naming**: PascalCase for exported, camelCase for unexported. Use descriptive names
- **Error handling**: Return wrapped errors with context. Define custom errors in models/errors.go. Use `ErrorIs` for error checking - **Error handling**: Return wrapped errors with context. Define custom errors in models/errors.go. Use `ErrorIs` for error checking
- **Testing**: Use testify/assert. Table-driven tests with helper functions. Test both marshal/unmarshal for encoding types - **Testing**: Use github.com/nalgeon/be. Table-driven tests with helper functions. Test both marshal/unmarshal for encoding types
- **Concurrency**: Use channels and goroutines with WaitGroups for parallel processing (see entry.go patterns) - **Concurrency**: Use channels and goroutines with WaitGroups for parallel processing (see entry.go patterns)
- **Comments**: Include license header on cmd files. Document exported functions and types - **Comments**: Include license header on cmd files. Document exported functions and types

View file

@ -1,5 +1,12 @@
# Changelog # Changelog
## [0.0.11] - 2026-02-13
- ✨ Add support for mixed-level nested keys with dot/blank handling
- ✨ Refactor append_test to use standalone test functions
- ✨ Migrate from testify to nalgeon/be testing library
- ✨ Add bep.JSON helper for JSON assertion in tests
## [0.0.10] - 2026-02-10 ## [0.0.10] - 2026-02-10
- ✨ Implement full support for nested fields in Meta and Entry marshalling - ✨ Implement full support for nested fields in Meta and Entry marshalling

View file

@ -5,14 +5,13 @@ import (
fp "path/filepath" fp "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
"github.com/stretchr/testify/require"
) )
func TestDefaultConfig(t *testing.T) { func TestDefaultConfig(t *testing.T) {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
inDir := fp.Join(home, "my-log") inDir := fp.Join(home, "my-log")
c, err := DefaultConfig() c, err := DefaultConfig()
require.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, inDir, c.Input.Path) be.Equal(t, c.Input.Path, inDir)
} }

View file

@ -5,8 +5,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
"github.com/stretchr/testify/require"
) )
func TestLoad(t *testing.T) { func TestLoad(t *testing.T) {
@ -16,8 +15,8 @@ func TestLoad(t *testing.T) {
fmt.Fprint(f, `[input] fmt.Fprint(f, `[input]
ext = "log"`) ext = "log"`)
c, err := Load() c, err := Load()
require.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, "log", c.Input.Ext) be.Equal(t, c.Input.Ext, "log")
} }
func TestLoadBadFile(t *testing.T) { func TestLoadBadFile(t *testing.T) {
@ -26,15 +25,15 @@ func TestLoadBadFile(t *testing.T) {
defer f.Close() defer f.Close()
fmt.Fprint(f, `{"not":"toml"}`) fmt.Fprint(f, `{"not":"toml"}`)
_, err := Load() _, err := Load()
assert.Error(t, err) be.Err(t, err)
} }
func TestLoadIgnoreMissingFile(t *testing.T) { func TestLoadIgnoreMissingFile(t *testing.T) {
def, _ := DefaultConfig() def, _ := DefaultConfig()
ConfigPath = "/not/a/real/file" ConfigPath = "/not/a/real/file"
c, err := Load() c, err := Load()
require.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, def, c) be.Equal(t, c, def)
} }
func TestOverride(t *testing.T) { func TestOverride(t *testing.T) {
@ -43,30 +42,30 @@ func TestOverride(t *testing.T) {
"input.ext": "~", "input.ext": "~",
} }
c, err := Load() c, err := Load()
require.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, Overrides["input.path"], c.Input.Path) be.Equal(t, c.Input.Path, Overrides["input.path"])
assert.Equal(t, "txt", c.Input.Ext) be.Equal(t, c.Input.Ext, "txt")
} }
func TestOverrideJson(t *testing.T) { func TestOverrideJson(t *testing.T) {
Overrides = map[string]string{"input.ext": `{"a":"b"}`} Overrides = map[string]string{"input.ext": `{"a":"b"}`}
c, err := Load() c, err := Load()
require.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, "txt", c.Input.Ext) be.Equal(t, c.Input.Ext, "txt")
} }
func TestTimeParse(t *testing.T) { func TestTimeParse(t *testing.T) {
Overrides = map[string]string{"input.ext": "now"} Overrides = map[string]string{"input.ext": "now"}
c, err := Load() c, err := Load()
assert.ErrorContains(t, err, "incompatible types: TOML value has type time.Time; destination has type string") be.Err(t, err, "incompatible types: TOML value has type time.Time; destination has type string")
assert.Equal(t, "txt", c.Input.Ext) be.Equal(t, c.Input.Ext, "txt")
} }
func TestStdoutMissing(t *testing.T) { func TestStdoutMissing(t *testing.T) {
var oo Outputs = map[string]Output{} var oo Outputs = map[string]Output{}
std, en := oo.Stdout() std, en := oo.Stdout()
assert.False(t, en) be.True(t, !en)
assert.Equal(t, Stdout{}, std) be.Equal(t, std, Stdout{})
} }
func TestStdoutLoad(t *testing.T) { func TestStdoutLoad(t *testing.T) {
@ -76,8 +75,8 @@ func TestStdoutLoad(t *testing.T) {
defer os.Unsetenv("LOG_STDOUT_ENABLED") defer os.Unsetenv("LOG_STDOUT_ENABLED")
c, _ := Load() c, _ := Load()
std, en := c.Outputs.Stdout() std, en := c.Outputs.Stdout()
assert.True(t, en) be.True(t, en)
assert.Equal(t, "json", std.Format) be.Equal(t, std.Format, "json")
} }
func TestFormatJson(t *testing.T) { func TestFormatJson(t *testing.T) {
@ -86,9 +85,9 @@ func TestFormatJson(t *testing.T) {
} }
js := ff.Json() js := ff.Json()
assert.True(t, js.PrettyPrint) be.True(t, js.PrettyPrint)
ff = Formatters{} ff = Formatters{}
js = ff.Json() js = ff.Json()
assert.False(t, js.PrettyPrint) be.True(t, !js.PrettyPrint)
} }

View file

@ -3,37 +3,47 @@ package files
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"testing" "testing"
"time" "time"
"codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/config"
"codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/models"
"github.com/stretchr/testify/suite" "github.com/nalgeon/be"
) )
func TestAppend(t *testing.T) { func TestAppend(tt *testing.T) {
suite.Run(t, new(AppendTestSuite)) tt.Run("success", func(t *testing.T) {
t.Run("single", appendTestSingle)
t.Run("two", appendTestTwoEntries)
t.Run("new_line", appendTestAddNewLine)
t.Run("no-new-line", appendTestDontAddNewLine)
t.Run("dot-folder", appendTestDotFolder)
t.Run("no-dot-folder", appendTestDotFolderNo)
t.Run("no-ext", appendTestNoExt)
})
tt.Run("failure", func(t *testing.T) {
t.Run("badEntry", appendTestBadEntry)
t.Run("load-err", appendTestConfLoadErr)
t.Run("mkdir-err", appendTestMkdirErr)
t.Run("append-log-err", appendTestOpenErr)
})
} }
type AppendTestSuite struct { func setupAppendTest(t *testing.T) string {
suite.Suite t.Helper()
dir string dir := t.ArtifactDir()
} config.Overrides["input.path"] = dir
func (s *AppendTestSuite) SetupSuite() {
s.dir, _ = os.MkdirTemp("", "append-test")
config.Overrides["input.path"] = s.dir
config.Overrides["input.ext"] = "log" config.Overrides["input.ext"] = "log"
t.Cleanup(func() {
delete(config.Overrides, "input.path")
delete(config.Overrides, "input.ext")
})
return dir
} }
func (s *AppendTestSuite) TearDownSuite() { func appendTestSingle(t *testing.T) {
os.RemoveAll(s.dir) dir := setupAppendTest(t)
delete(config.Overrides, "input.path")
delete(config.Overrides, "input.ext")
}
func (s *AppendTestSuite) TestSuccess() {
defer os.Remove(s.dir + "/test.log")
when := time.Now().Local() when := time.Now().Local()
e := models.Entry{ e := models.Entry{
Title: "Jimmy", Title: "Jimmy",
@ -48,18 +58,17 @@ func (s *AppendTestSuite) TestSuccess() {
Entries: []models.Entry{e}, Entries: []models.Entry{e},
} }
err := Append(l) err := Append(l)
s.Require().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/test.log") by, err := os.ReadFile(dir + "/test.log")
by, err := os.ReadFile(s.dir + "/test.log") be.Err(t, err, nil)
st := string(by) st := string(by)
s.Require().NoError(err) be.True(t, strings.Contains(st, "Jimmy\n"))
s.Assert().Contains(st, "Jimmy\n") be.True(t, strings.Contains(st, "\n@foo 42"))
s.Assert().Contains(st, "\n@foo 42") be.True(t, strings.Contains(st, "\n@bar true"))
s.Assert().Contains(st, "\n@bar true")
} }
func (s *AppendTestSuite) TestTwoEntries() { func appendTestTwoEntries(t *testing.T) {
defer os.Remove(s.dir + "/test.log") dir := setupAppendTest(t)
when := time.Now().Local() when := time.Now().Local()
whens := when.Format(models.DateFormat) whens := when.Format(models.DateFormat)
e := []models.Entry{ e := []models.Entry{
@ -71,17 +80,16 @@ func (s *AppendTestSuite) TestTwoEntries() {
Entries: e, Entries: e,
} }
err := Append(l) err := Append(l)
s.Assert().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/test.log") by, _ := os.ReadFile(dir + "/test.log")
by, _ := os.ReadFile(s.dir + "/test.log")
st := string(by) st := string(by)
s.Assert().Contains(st, fmt.Sprintf("@begin %s - one", whens)) be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - one", whens)))
s.Assert().Contains(st, fmt.Sprintf("@begin %s - two", whens)) be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - two", whens)))
} }
func (s *AppendTestSuite) TestAddNewLine() { func appendTestAddNewLine(t *testing.T) {
defer os.Remove(s.dir + "/test.log") dir := setupAppendTest(t)
os.WriteFile(s.dir+"/test.log", []byte("foo"), 0644) os.WriteFile(dir+"/test.log", []byte("foo"), 0644)
when := time.Now().Local() when := time.Now().Local()
whens := when.Format(models.DateFormat) whens := when.Format(models.DateFormat)
e := []models.Entry{ e := []models.Entry{
@ -92,16 +100,15 @@ func (s *AppendTestSuite) TestAddNewLine() {
Entries: e, Entries: e,
} }
err := Append(l) err := Append(l)
s.Assert().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/test.log") by, _ := os.ReadFile(dir + "/test.log")
by, _ := os.ReadFile(s.dir + "/test.log")
exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens) exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens)
s.Assert().Equal(exp, string(by)) be.Equal(t, string(by), exp)
} }
func (s *AppendTestSuite) TestDontAddNewLine() { func appendTestDontAddNewLine(t *testing.T) {
defer os.Remove(s.dir + "/test.log") dir := setupAppendTest(t)
os.WriteFile(s.dir+"/test.log", []byte("foo\n"), 0644) os.WriteFile(dir+"/test.log", []byte("foo\n"), 0644)
when := time.Now().Local() when := time.Now().Local()
whens := when.Format(models.DateFormat) whens := when.Format(models.DateFormat)
e := []models.Entry{ e := []models.Entry{
@ -112,15 +119,14 @@ func (s *AppendTestSuite) TestDontAddNewLine() {
Entries: e, Entries: e,
} }
err := Append(l) err := Append(l)
s.Assert().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/test.log") by, _ := os.ReadFile(dir + "/test.log")
by, _ := os.ReadFile(s.dir + "/test.log")
exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens) exp := fmt.Sprintf("foo\n@begin %s - one\n@id jimmy @end\n", whens)
s.Assert().Equal(exp, string(by)) be.Equal(t, string(by), exp)
} }
func (s *AppendTestSuite) TestFailEntry() { func appendTestBadEntry(t *testing.T) {
defer os.Remove(s.dir + "/test.log") dir := setupAppendTest(t)
e := models.Entry{ e := models.Entry{
Title: "Jimmy", Title: "Jimmy",
} }
@ -129,14 +135,18 @@ func (s *AppendTestSuite) TestFailEntry() {
Entries: []models.Entry{e}, Entries: []models.Entry{e},
} }
err := Append(l) err := Append(l)
s.Assert().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/test.log") by, _ := os.ReadFile(dir + "/test.log")
by, _ := os.ReadFile(s.dir + "/test.log") be.Equal(t, by, []byte{})
s.Assert().Equal([]byte{}, by)
} }
func (s *AppendTestSuite) TestDotFolder() { func appendTestDotFolder(t *testing.T) {
config.Overrides["input.dotFolder"] = "true" config.Overrides["input.dotFolder"] = "true"
t.Cleanup(func() {
delete(config.Overrides, "input.dotFolder")
})
dir := setupAppendTest(t)
e := models.Entry{ e := models.Entry{
Title: "something", Title: "something",
Date: time.Now(), Date: time.Now(),
@ -146,16 +156,20 @@ func (s *AppendTestSuite) TestDotFolder() {
Entries: []models.Entry{e}, Entries: []models.Entry{e},
} }
err := Append(l) err := Append(l)
s.Require().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/sub/test.log") by, err := os.ReadFile(dir + "/sub/test.log")
by, err := os.ReadFile(s.dir + "/sub/test.log") be.Err(t, err, nil)
st := string(by) st := string(by)
s.Require().NoError(err) be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title)))
s.Assert().Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title))
} }
func (s *AppendTestSuite) TestDotFolderNo() { func appendTestDotFolderNo(t *testing.T) {
config.Overrides["input.dotFolder"] = "false" config.Overrides["input.dotFolder"] = "false"
t.Cleanup(func() {
delete(config.Overrides, "input.dotFolder")
})
dir := setupAppendTest(t)
e := models.Entry{ e := models.Entry{
Title: "another", Title: "another",
Date: time.Now(), Date: time.Now(),
@ -165,19 +179,20 @@ func (s *AppendTestSuite) TestDotFolderNo() {
Entries: []models.Entry{e}, Entries: []models.Entry{e},
} }
err := Append(l) err := Append(l)
s.Require().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/sub.test.log") by, err := os.ReadFile(dir + "/sub.test.log")
by, err := os.ReadFile(s.dir + "/sub.test.log") be.Err(t, err, nil)
st := string(by) st := string(by)
s.Require().NoError(err) be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title)))
s.Assert().Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title))
} }
func (s *AppendTestSuite) TestNoExt() { func appendTestNoExt(t *testing.T) {
dir := setupAppendTest(t)
config.Overrides["input.ext"] = "" config.Overrides["input.ext"] = ""
defer func() { t.Cleanup(func() {
config.Overrides["input.ext"] = "log" config.Overrides["input.ext"] = "log"
}() })
e := models.Entry{ e := models.Entry{
Title: "baz", Title: "baz",
Date: time.Now(), Date: time.Now(),
@ -187,48 +202,48 @@ func (s *AppendTestSuite) TestNoExt() {
Entries: []models.Entry{e}, Entries: []models.Entry{e},
} }
err := Append(l) err := Append(l)
s.Require().NoError(err) be.Err(t, err, nil)
s.Require().FileExists(s.dir + "/foobar") by, err := os.ReadFile(dir + "/foobar")
by, err := os.ReadFile(s.dir + "/foobar") be.Err(t, err, nil)
st := string(by) st := string(by)
s.Require().NoError(err) be.True(t, strings.Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title)))
s.Assert().Contains(st, fmt.Sprintf("@begin %s - %s", e.Date.Format(models.DateFormat), e.Title))
} }
func (s *AppendTestSuite) TestConfLoadErr() { func appendTestConfLoadErr(t *testing.T) {
dir := t.ArtifactDir()
currConf := config.ConfigPath currConf := config.ConfigPath
tmp, _ := os.CreateTemp("", "app-conf-*.toml") tmp, _ := os.CreateTemp(dir, "app-conf-*.toml")
fname := tmp.Name() fname := tmp.Name()
defer tmp.Close() t.Cleanup(func() { tmp.Close() })
defer os.Remove(fname) t.Cleanup(func() { os.Remove(fname) })
fmt.Fprintln(tmp, `{"not":"toml"}`) fmt.Fprintln(tmp, `{"not":"toml"}`)
config.ConfigPath = fname config.ConfigPath = fname
defer func(path string) { t.Cleanup(func() { config.ConfigPath = currConf })
config.ConfigPath = path
}(currConf)
err := Append(models.Log{}) err := Append(models.Log{})
s.Assert().ErrorContains(err, "toml") be.Err(t, err, "toml")
} }
func (s *AppendTestSuite) TestMkdirErr() { func appendTestMkdirErr(t *testing.T) {
// Don't run this test as root // Don't run this test as root
config.Overrides["input.path"] = "/var/my-logs-test" config.Overrides["input.path"] = "/var/my-logs-test"
defer func(path string) { t.Cleanup(func() { delete(config.Overrides, "input.path") })
config.Overrides["input.path"] = path
}(s.dir)
err := Append(models.Log{}) err := Append(models.Log{})
s.Assert().ErrorContains(err, "permission denied") be.Err(t, err, "permission denied")
} }
func (s *AppendTestSuite) TestOpenErr() { func appendTestOpenErr(t *testing.T) {
dir := setupAppendTest(t)
l := models.Log{ l := models.Log{
Name: "test-open-err", Name: "test-open-err",
} }
fname := s.dir + "/test-open-err.log" fname := dir + "/test-open-err.log"
os.MkdirAll(s.dir, 0750) os.MkdirAll(dir, 0750)
f, _ := os.Create(fname) f, _ := os.Create(fname)
f.Close() f.Close()
os.Chmod(fname, 0400) // read only os.Chmod(fname, 0400)
err := Append(l) err := Append(l)
s.Assert().ErrorContains(err, "permission denied") be.Err(t, err, "permission denied")
} }

View file

@ -5,13 +5,14 @@ import (
"testing" "testing"
"time" "time"
"codeberg.org/danjones000/my-log/internal/testutil/bep"
"codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/models"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
) )
func TestJsonName(t *testing.T) { func TestJsonName(t *testing.T) {
f, _ := New("json") f, _ := New("json")
assert.Equal(t, "json", f.Name()) be.Equal(t, f.Name(), "json")
} }
func TestJsonMeta(t *testing.T) { func TestJsonMeta(t *testing.T) {
@ -19,8 +20,8 @@ func TestJsonMeta(t *testing.T) {
m := models.Meta{"foo", 42} m := models.Meta{"foo", 42}
exp := `{"foo":42}` exp := `{"foo":42}`
o, err := f.Meta(m) o, err := f.Meta(m)
assert.NoError(t, err) be.Err(t, err, nil)
assert.JSONEq(t, exp, string(o)) bep.JSON(t, o, []byte(exp))
} }
func TestJsonEntry(t *testing.T) { func TestJsonEntry(t *testing.T) {
@ -34,8 +35,8 @@ func TestJsonEntry(t *testing.T) {
} }
exp := fmt.Sprintf(`{"title":"%s","date":"%s","foo":42}`, e.Title, when.Format(time.RFC3339)) exp := fmt.Sprintf(`{"title":"%s","date":"%s","foo":42}`, e.Title, when.Format(time.RFC3339))
o, err := f.Entry(e) o, err := f.Entry(e)
assert.NoError(t, err) be.Err(t, err, nil)
assert.JSONEq(t, exp, string(o)) bep.JSON(t, o, []byte(exp))
} }
func TestJsonLog(t *testing.T) { func TestJsonLog(t *testing.T) {
@ -50,24 +51,24 @@ func TestJsonLog(t *testing.T) {
l := models.Log{"stuff", []models.Entry{e}} l := models.Log{"stuff", []models.Entry{e}}
exp := fmt.Sprintf(`{"%s":[{"title":"%s","date":"%s","foo":42}]}`, l.Name, e.Title, when.Format(time.RFC3339)) exp := fmt.Sprintf(`{"%s":[{"title":"%s","date":"%s","foo":42}]}`, l.Name, e.Title, when.Format(time.RFC3339))
o, err := f.Log(l) o, err := f.Log(l)
assert.NoError(t, err) be.Err(t, err, nil)
assert.JSONEq(t, exp, string(o)) bep.JSON(t, o, []byte(exp))
} }
func TestJsonNoLogs(t *testing.T) { func TestJsonNoLogs(t *testing.T) {
f, _ := New("json") f, _ := New("json")
o, err := f.Logs([]models.Log{}) o, err := f.Logs([]models.Log{})
var exp []byte var exp []byte
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, exp, o) be.Equal(t, o, exp)
} }
func TestJsonErr(t *testing.T) { func TestJsonErr(t *testing.T) {
f, _ := New("json") f, _ := New("json")
o, err := f.Meta(models.Meta{"foo", make(chan bool)}) o, err := f.Meta(models.Meta{"foo", make(chan bool)})
var exp []byte var exp []byte
assert.Error(t, err) be.Err(t, err)
assert.Equal(t, exp, o) be.Equal(t, o, exp)
} }
func TestJsonPretty(t *testing.T) { func TestJsonPretty(t *testing.T) {
@ -76,6 +77,6 @@ func TestJsonPretty(t *testing.T) {
exp := `{ exp := `{
"foo": 42 "foo": 42
}` }`
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, exp, string(o)) be.Equal(t, string(o), exp)
} }

View file

@ -3,20 +3,24 @@ package formatters
import ( import (
"fmt" "fmt"
"os" "os"
"slices"
"testing" "testing"
"codeberg.org/danjones000/my-log/config" "codeberg.org/danjones000/my-log/config"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
) )
func TestKinds(t *testing.T) { func TestKinds(t *testing.T) {
assert.ElementsMatch(t, []string{"plain", "json", "zero"}, Kinds()) kinds := Kinds()
for _, kind := range []string{"plain", "json", "zero"} {
be.True(t, slices.Contains(kinds, kind))
}
} }
func TestNewUnsupported(t *testing.T) { func TestNewUnsupported(t *testing.T) {
f, err := New("nope") f, err := New("nope")
assert.Nil(t, f) be.Equal(t, f, nil)
assert.Error(t, err) be.Err(t, err)
} }
func TestNewCantGetConfig(t *testing.T) { func TestNewCantGetConfig(t *testing.T) {
@ -30,16 +34,16 @@ func TestNewCantGetConfig(t *testing.T) {
fmt.Fprint(f, `{"not":"toml"}`) fmt.Fprint(f, `{"not":"toml"}`)
form, err := New("plain") form, err := New("plain")
assert.Nil(t, form) be.Equal(t, form, nil)
assert.Error(t, err) be.Err(t, err)
form, err = Preferred() form, err = Preferred()
assert.Nil(t, form) be.Equal(t, form, nil)
assert.Error(t, err) be.Err(t, err)
} }
func TestPreferred(t *testing.T) { func TestPreferred(t *testing.T) {
form, err := Preferred() form, err := Preferred()
assert.NotNil(t, form) be.Err(t, err, nil)
assert.NoError(t, err) be.True(t, form != nil)
} }

View file

@ -5,41 +5,41 @@ import (
"time" "time"
"codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/models"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
) )
var empty []byte var empty []byte
func TestNullName(t *testing.T) { func TestNullName(t *testing.T) {
f, err := New("zero") f, err := New("zero")
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, "zero", f.Name()) be.Equal(t, f.Name(), "zero")
} }
func TestNullMeta(t *testing.T) { func TestNullMeta(t *testing.T) {
f, _ := New("zero") f, _ := New("zero")
o, err := f.Meta(models.Meta{"foo", 42}) o, err := f.Meta(models.Meta{"foo", 42})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, empty, o) be.Equal(t, o, empty)
} }
func TestNullEntry(t *testing.T) { func TestNullEntry(t *testing.T) {
f, _ := New("zero") f, _ := New("zero")
o, err := f.Entry(models.Entry{"title", time.Now(), models.Metas{}}) o, err := f.Entry(models.Entry{"title", time.Now(), models.Metas{}})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, empty, o) be.Equal(t, o, empty)
} }
func TestNullLog(t *testing.T) { func TestNullLog(t *testing.T) {
f, _ := New("zero") f, _ := New("zero")
o, err := f.Log(models.Log{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}}) o, err := f.Log(models.Log{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, empty, o) be.Equal(t, o, empty)
} }
func TestNullLogs(t *testing.T) { func TestNullLogs(t *testing.T) {
f, _ := New("zero") f, _ := New("zero")
o, err := f.Logs([]models.Log{{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}}}) o, err := f.Logs([]models.Log{{"jim", []models.Entry{{"title", time.Now(), models.Metas{}}}}})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, empty, o) be.Equal(t, o, empty)
} }

View file

@ -9,8 +9,7 @@ import (
"codeberg.org/danjones000/my-log/models" "codeberg.org/danjones000/my-log/models"
"codeberg.org/danjones000/my-log/tools" "codeberg.org/danjones000/my-log/tools"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
"github.com/stretchr/testify/require"
) )
func TestPlainLogs(t *testing.T) { func TestPlainLogs(t *testing.T) {
@ -31,101 +30,101 @@ func TestPlainLogs(t *testing.T) {
logs := []models.Log{l, l2} logs := []models.Log{l, l2}
f, err := New("plain") f, err := New("plain")
require.NoError(t, err) be.Err(t, err, nil)
out, err := f.Logs(logs) out, err := f.Logs(logs)
require.NoError(t, err) be.Err(t, err, nil)
read := bytes.NewReader(out) read := bytes.NewReader(out)
scan := bufio.NewScanner(read) scan := bufio.NewScanner(read)
scan.Scan() scan.Scan()
line := scan.Text() line := scan.Text()
assert.Equal(t, l.Name, line) be.Equal(t, line, l.Name)
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "#######", line) be.Equal(t, line, "#######")
scan.Scan() scan.Scan()
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Title: "+e[0].Title, line) be.Equal(t, line, "Title: "+e[0].Title)
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Date: "+e[0].Date.Format(tools.DateFormat), line) be.Equal(t, line, "Date: "+e[0].Date.Format(tools.DateFormat))
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "foo: bar", line) be.Equal(t, line, "foo: bar")
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "baz: 42", line) be.Equal(t, line, "baz: 42")
scan.Scan() scan.Scan()
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Title: "+e[1].Title, line) be.Equal(t, line, "Title: "+e[1].Title)
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Date: "+e[1].Date.Format(tools.DateFormat), line) be.Equal(t, line, "Date: "+e[1].Date.Format(tools.DateFormat))
scan.Scan() scan.Scan()
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, l2.Name, line) be.Equal(t, line, l2.Name)
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "#######", line) be.Equal(t, line, "#######")
scan.Scan() scan.Scan()
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Title: "+e2.Title, line) be.Equal(t, line, "Title: "+e2.Title)
scan.Scan() scan.Scan()
line = scan.Text() line = scan.Text()
assert.Equal(t, "Date: "+e2.Date.Format(tools.DateFormat), line) be.Equal(t, line, "Date: "+e2.Date.Format(tools.DateFormat))
more := scan.Scan() more := scan.Scan()
assert.False(t, more) be.True(t, !more)
} }
func TestPlainName(t *testing.T) { func TestPlainName(t *testing.T) {
f, _ := New("plain") f, _ := New("plain")
assert.Equal(t, "plain", f.Name()) be.Equal(t, f.Name(), "plain")
} }
func TestPlainLogNone(t *testing.T) { func TestPlainLogNone(t *testing.T) {
f, _ := New("plain") f, _ := New("plain")
out, err := f.Logs([]models.Log{}) out, err := f.Logs([]models.Log{})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Len(t, out, 0) be.Equal(t, len(out), 0)
} }
func TestPlainLogNoEntries(t *testing.T) { func TestPlainLogNoEntries(t *testing.T) {
f, _ := New("plain") f, _ := New("plain")
out, err := f.Log(models.Log{Name: "foo"}) out, err := f.Log(models.Log{Name: "foo"})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Len(t, out, 0) be.Equal(t, len(out), 0)
} }
func TestPlainMetaEmpty(t *testing.T) { func TestPlainMetaEmpty(t *testing.T) {
f, _ := New("plain") f, _ := New("plain")
out, err := f.Meta(models.Meta{"foo", ""}) out, err := f.Meta(models.Meta{"foo", ""})
assert.NoError(t, err) be.Err(t, err, nil)
assert.Len(t, out, 0) be.Equal(t, len(out), 0)
} }
func TestPlainMetaError(t *testing.T) { func TestPlainMetaError(t *testing.T) {
f, _ := New("plain") f, _ := New("plain")
out, err := f.Meta(models.Meta{"foo", make(chan bool)}) out, err := f.Meta(models.Meta{"foo", make(chan bool)})
assert.Error(t, err) be.Err(t, err)
assert.Len(t, out, 0) be.Equal(t, len(out), 0)
} }
func TestPlainEntry(t *testing.T) { func TestPlainEntry(t *testing.T) {
@ -135,6 +134,6 @@ func TestPlainEntry(t *testing.T) {
Title: "foo", Title: "foo",
Date: now, Date: now,
}) })
assert.NoError(t, err) be.Err(t, err, nil)
assert.Equal(t, fmt.Sprintf("Title: foo\nDate: %s", now.Format(tools.DateFormat)), string(out)) be.Equal(t, string(out), fmt.Sprintf("Title: foo\nDate: %s", now.Format(tools.DateFormat)))
} }

9
go.mod
View file

@ -1,6 +1,6 @@
module codeberg.org/danjones000/my-log module codeberg.org/danjones000/my-log
go 1.21.5 go 1.26.0
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
@ -8,25 +8,20 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/markusmobius/go-dateparser v1.2.3 github.com/markusmobius/go-dateparser v1.2.3
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/nalgeon/be v0.3.0
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elliotchance/pie/v2 v2.7.0 // indirect github.com/elliotchance/pie/v2 v2.7.0 // indirect
github.com/hablullah/go-hijri v1.0.2 // indirect github.com/hablullah/go-hijri v1.0.2 // indirect
github.com/hablullah/go-juliandays v1.0.0 // indirect github.com/hablullah/go-juliandays v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/magefile/mage v1.14.0 // indirect github.com/magefile/mage v1.14.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/tetratelabs/wazero v1.2.1 // indirect github.com/tetratelabs/wazero v1.2.1 // indirect
github.com/wasilibs/go-re2 v1.3.0 // indirect github.com/wasilibs/go-re2 v1.3.0 // indirect
golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

20
go.sum
View file

@ -3,9 +3,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -18,21 +17,16 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE= github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE= github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw= github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw=
github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk= github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/nalgeon/be v0.3.0 h1:QsPANqEtcOD5qT2S3KAtIkDBBn8SXUf/Lb5Bi/z4UqM=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/nalgeon/be v0.3.0/go.mod h1:PMwMuBLopwKJkSHnr2qHyLcZYUTqNejN7A8RAqNWO3E=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
@ -51,7 +45,5 @@ golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,21 @@
package bep
import (
"encoding/json"
"testing"
"github.com/nalgeon/be"
)
func JSON(t *testing.T, got, want []byte) {
t.Helper()
var gotAny, wantAny any
err := json.Unmarshal(want, &wantAny)
be.Err(t, err, nil)
err = json.Unmarshal(got, &gotAny)
be.Err(t, err, nil)
be.Equal(t, gotAny, wantAny)
}

View file

@ -4,11 +4,13 @@ import (
"bufio" "bufio"
"encoding" "encoding"
"encoding/json" "encoding/json"
"regexp"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "codeberg.org/danjones000/my-log/internal/testutil/bep"
"github.com/nalgeon/be"
) )
// Type assertions // Type assertions
@ -88,6 +90,24 @@ func TestEntryMarshal(t *testing.T) {
[]string{"@me:age 43", "@me:name:first Dan", "@me:name:last Jones"}, []string{"@me:age 43", "@me:name:first Dan", "@me:name:last Jones"},
nil, nil,
}, },
{
"double-nested-map-dot",
"Title DM",
when,
[]Meta{{"me", map[string]any{"age": 43, "name": map[string]any{".": "Dan Jones", "nick": "Danny"}}}},
"@begin " + whens + " - Title DM",
[]string{"@me:age 43", "@me:name Dan Jones", "@me:name:nick Danny"},
nil,
},
{
"double-nested-map-blank",
"Title DM",
when,
[]Meta{{"me", map[string]any{"age": 43, "name": map[string]any{"": "Dan Jones", "nick": "Danny"}}}},
"@begin " + whens + " - Title DM",
[]string{"@me:age 43", "@me:name Dan Jones", "@me:name:nick Danny"},
nil,
},
{ {
"nested-keys-in-json", "nested-keys-in-json",
"Title NKJ", "Title NKJ",
@ -108,19 +128,19 @@ func getEntryMarshalTestRunner(title string, date time.Time, fields []Meta, firs
return func(t *testing.T) { return func(t *testing.T) {
en := Entry{title, date, fields} en := Entry{title, date, fields}
o, er := en.MarshalText() o, er := en.MarshalText()
assert.Equal(t, err, er) be.Err(t, er, err)
if first == "" { if first == "" {
return return
} }
os := string(o) os := string(o)
if len(lines) == 0 { if len(lines) == 0 {
assert.Equal(t, first, os) be.Equal(t, os, first)
return return
} }
assert.Regexp(t, first, os) be.True(t, regexp.MustCompile(first).MatchString(os))
for _, line := range lines { for _, line := range lines {
assert.Regexp(t, "(?m)^"+line, os) be.True(t, regexp.MustCompile("(?m)^"+line).MatchString(os))
} }
} }
} }
@ -169,6 +189,14 @@ func TestEntryUnmarshal(t *testing.T) {
[]Meta{{"me:name", "Dan"}, {"me:coder", true}}, []Meta{{"me:name", "Dan"}, {"me:coder", true}},
nil, nil,
}, },
{
"nested-field-dot",
"@begin " + whens + " - A Title\n@me:name Dan Jones\n@me:name:nick Danny\n@me:coder true @end",
"A Title",
when,
[]Meta{{"me:name", "Dan Jones"}, {"me:name:nick", "Danny"}, {"me:coder", true}},
nil,
},
{ {
"json-field", "json-field",
"@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`, "@begin " + whens + " - Some Guy\n" + `@json {"name":"Dan","coder":true} @end`,
@ -189,12 +217,12 @@ func getEntryUnmarshalTestRunner(in string, title string, date time.Time, fields
e := &Entry{} e := &Entry{}
er := e.UnmarshalText([]byte(in)) er := e.UnmarshalText([]byte(in))
if err != nil { if err != nil {
assert.ErrorIs(t, er, err) be.Err(t, er, err)
return return
} }
assert.Equal(t, title, e.Title) be.Equal(t, e.Title, title)
assert.WithinRange(t, e.Date, date.Add(-time.Second), date.Add(time.Second)) be.True(t, e.Date.After(date.Add(-time.Second)) && e.Date.Before(date.Add(time.Second)))
for _, f := range fields { for _, f := range fields {
got := false got := false
for _, m := range e.Fields { for _, m := range e.Fields {
@ -211,7 +239,7 @@ func getEntryUnmarshalTestRunner(in string, title string, date time.Time, fields
break break
} }
} }
assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields) be.True(t, got)
} }
} }
} }
@ -221,13 +249,13 @@ func TestScan(t *testing.T) {
read := strings.NewReader(in) read := strings.NewReader(in)
scan := bufio.NewScanner(read) scan := bufio.NewScanner(read)
scan.Split(scanEntry) scan.Split(scanEntry)
assert.True(t, scan.Scan()) be.True(t, scan.Scan())
assert.Equal(t, "@begin date - Title\nlong", scan.Text()) be.Equal(t, scan.Text(), "@begin date - Title\nlong")
assert.True(t, scan.Scan()) be.True(t, scan.Scan())
assert.Equal(t, "@foo john\njones", scan.Text()) be.Equal(t, scan.Text(), "@foo john\njones")
assert.True(t, scan.Scan()) be.True(t, scan.Scan())
assert.Equal(t, "@bar 42@nobody", scan.Text()) be.Equal(t, scan.Text(), "@bar 42@nobody")
assert.False(t, scan.Scan()) be.True(t, !scan.Scan())
} }
func TestEntryJsonMarshal(t *testing.T) { func TestEntryJsonMarshal(t *testing.T) {
@ -256,6 +284,9 @@ func TestEntryJsonMarshal(t *testing.T) {
{"nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:title", "Sub-title"}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","title":"Sub-title"}}`, nil}, {"nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:title", "Sub-title"}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","title":"Sub-title"}}`, nil},
{"double-nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:name", "Dan"}, {"obj:me:age", 27}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil}, {"double-nested-field", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:name", "Dan"}, {"obj:me:age", 27}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil},
{"nested-plus-json", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me", json.RawMessage(`{"name":"Dan","age":27}`)}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil}, {"nested-plus-json", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me", json.RawMessage(`{"name":"Dan","age":27}`)}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{"name":"Dan","age":27}}}`, nil},
{"nested-part", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me", "Dan"}, {"obj:me:age", 27}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27}}}`, nil},
{"nested-part-order", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:age", 27}, {"obj:me", "Dan"}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27}}}`, nil},
{"nested-part-order-two", "A Title", when, []Meta{{"obj:foo", "bar"}, {"obj:me:age", 27}, {"obj:me", "Dan"}, {"obj:me:cool", true}}, `{"title":"A Title","date":"` + whens + `","obj":{"foo":"bar","me":{".":"Dan","age":27,"cool":true}}}`, nil},
} }
for _, tt := range tests { for _, tt := range tests {
@ -268,10 +299,9 @@ func getEntryJsonMarshalTestRunner(title string, date time.Time, fields []Meta,
e := Entry{title, date, fields} e := Entry{title, date, fields}
o, er := json.Marshal(e) o, er := json.Marshal(e)
if err == nil { if err == nil {
assert.JSONEq(t, out, string(o)) bep.JSON(t, o, []byte(out))
} else { } else {
assert.ErrorIs(t, er, err) be.Err(t, er, err)
} }
} }
} }
@ -347,14 +377,14 @@ func getEntryJsonUnmarshalTestRunner(in, title string, date time.Time, fields []
e := new(Entry) e := new(Entry)
er := e.UnmarshalJSON([]byte(in)) er := e.UnmarshalJSON([]byte(in))
if err != nil { if err != nil {
assert.ErrorIs(t, er, err) be.Err(t, er, err)
return return
} }
assert.Nil(t, er) be.Equal(t, er, nil)
assert.Equal(t, title, e.Title) be.Equal(t, e.Title, title)
assert.WithinRange(t, e.Date, date.Add(-time.Second), date.Add(time.Second)) be.True(t, e.Date.After(date.Add(-time.Second)) && e.Date.Before(date.Add(time.Second)))
assert.Len(t, e.Fields, len(fields)) be.Equal(t, len(e.Fields), len(fields))
for _, f := range fields { for _, f := range fields {
got := false got := false
fTime, isTime := f.Value.(time.Time) fTime, isTime := f.Value.(time.Time)
@ -373,13 +403,13 @@ func getEntryJsonUnmarshalTestRunner(in, title string, date time.Time, fields []
} }
if isTime && m.Key == f.Key { if isTime && m.Key == f.Key {
mTime, _ := mVal.(time.Time) mTime, _ := mVal.(time.Time)
if assert.WithinRange(t, mTime, fTime.Add(-2*time.Second), fTime.Add(2*time.Second)) { if mTime.After(fTime.Add(-2*time.Second)) && mTime.Before(fTime.Add(2*time.Second)) {
got = true got = true
break break
} }
} }
} }
assert.Truef(t, got, "Couldn't find field %+v. We have %+v", f, e.Fields) be.True(t, got)
} }
} }
} }

View file

@ -4,8 +4,7 @@ import (
"encoding" "encoding"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
"github.com/stretchr/testify/require"
) )
var _ encoding.TextUnmarshaler = new(Log) var _ encoding.TextUnmarshaler = new(Log)
@ -32,15 +31,15 @@ const all = first + second + third + fourth + skip + fifth
func TestLogUnmarshalBig(t *testing.T) { func TestLogUnmarshalBig(t *testing.T) {
l := &Log{Name: "test-log"} l := &Log{Name: "test-log"}
err := l.UnmarshalText([]byte(all)) err := l.UnmarshalText([]byte(all))
require.NoError(t, err) be.Err(t, err, nil)
require.Len(t, l.Entries, 5) be.Equal(t, len(l.Entries), 5)
var e Entry var e Entry
var f bool var f bool
if e, f = findEntry(t, l, "This is simple", true); !f { if e, f = findEntry(t, l, "This is simple", true); !f {
return return
} }
assert.Len(t, e.Fields, 0) be.Equal(t, len(e.Fields), 0)
for _, e := range l.Entries { for _, e := range l.Entries {
findMeta(t, e, "ignoreme", true, false) findMeta(t, e, "ignoreme", true, false)
@ -51,28 +50,28 @@ func TestLogUnmarshalIgnoreGarbage(t *testing.T) {
l := &Log{Name: "test-log"} l := &Log{Name: "test-log"}
in := "ignore this\n" + second + "some crap also skip -> " + third + skip in := "ignore this\n" + second + "some crap also skip -> " + third + skip
err := l.UnmarshalText([]byte(in)) err := l.UnmarshalText([]byte(in))
require.NoError(t, err) be.Err(t, err, nil)
require.Len(t, l.Entries, 1) be.Equal(t, len(l.Entries), 1)
en := l.Entries[0] en := l.Entries[0]
assert.Equal(t, "We have one thing here", en.Title) be.Equal(t, en.Title, "We have one thing here")
assert.Len(t, en.Fields, 1) be.Equal(t, len(en.Fields), 1)
assert.Equal(t, "foo", en.Fields[0].Key) be.Equal(t, en.Fields[0].Key, "foo")
assert.Equal(t, "bar", en.Fields[0].Value) be.Equal(t, en.Fields[0].Value, "bar")
} }
func TestLogUnmarshalEmpty(t *testing.T) { func TestLogUnmarshalEmpty(t *testing.T) {
l := &Log{Name: "test-log"} l := &Log{Name: "test-log"}
err := l.UnmarshalText([]byte{}) err := l.UnmarshalText([]byte{})
require.NoError(t, err) be.Err(t, err, nil)
require.Len(t, l.Entries, 0) be.Equal(t, len(l.Entries), 0)
} }
func TestLogUnmarshalBad(t *testing.T) { func TestLogUnmarshalBad(t *testing.T) {
l := &Log{Name: "test-log"} l := &Log{Name: "test-log"}
err := l.UnmarshalText([]byte(badEntry)) err := l.UnmarshalText([]byte(badEntry))
require.NoError(t, err) be.Err(t, err, nil)
require.Len(t, l.Entries, 0) be.Equal(t, len(l.Entries), 0)
} }
func findEntry(t *testing.T, log *Log, title string, shouldFind bool) (Entry, bool) { func findEntry(t *testing.T, log *Log, title string, shouldFind bool) (Entry, bool) {
@ -85,9 +84,9 @@ func findEntry(t *testing.T, log *Log, title string, shouldFind bool) (Entry, bo
} }
} }
if shouldFind { if shouldFind {
found = assert.Truef(t, found, "Unable to found entry %s", title) be.True(t, found)
} else { } else {
found = assert.Falsef(t, found, "Entry %s should not have been found but was", title) be.True(t, !found)
} }
return ret, found return ret, found
@ -103,9 +102,9 @@ func findMeta(t *testing.T, entry Entry, key string, value any, shouldFind bool)
} }
} }
if shouldFind { if shouldFind {
found = assert.Truef(t, found, "Unable to found meta %s", key) be.True(t, found)
} else { } else {
found = assert.Falsef(t, found, "Meta %s should not have been found but was", key) be.True(t, !found)
} }
return ret, found return ret, found

View file

@ -50,12 +50,16 @@ func marshalMap(pre string, mp map[string]any, buff *bytes.Buffer) error {
buff.WriteRune('\n') buff.WriteRune('\n')
} }
idx++ idx++
newKey := pre + ":" + k
if k == "." || k == "" {
newKey = pre
}
if subM, ok := v.(map[string]any); ok { if subM, ok := v.(map[string]any); ok {
if err := marshalMap(pre+":"+k, subM, buff); err != nil { if err := marshalMap(newKey, subM, buff); err != nil {
return err return err
} }
} else { } else {
mSub := Meta{pre + ":" + k, v} mSub := Meta{newKey, v}
if err := mSub.marshalToBuff(buff); err != nil { if err := mSub.marshalToBuff(buff); err != nil {
return err return err
} }

View file

@ -7,7 +7,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "codeberg.org/danjones000/my-log/internal/testutil/bep"
"github.com/nalgeon/be"
) )
// Type assertions // Type assertions
@ -72,8 +73,8 @@ func getMetaTestRunner(key string, value any, out string, err error, newVal any)
if valE, ok := value.(error); !ok || !errors.Is(valE, skipMarshalTest) { if valE, ok := value.(error); !ok || !errors.Is(valE, skipMarshalTest) {
var o []byte var o []byte
o, e = st.MarshalText() o, e = st.MarshalText()
assert.Equal(t, out, string(o)) be.Equal(t, string(o), out)
assert.Equal(t, err, e) be.Equal(t, e, err)
if e != nil { if e != nil {
return return
} }
@ -83,17 +84,16 @@ func getMetaTestRunner(key string, value any, out string, err error, newVal any)
e = n.UnmarshalText([]byte(out)) e = n.UnmarshalText([]byte(out))
} }
if newE, ok := newVal.(error); ok { if newE, ok := newVal.(error); ok {
assert.ErrorIs(t, e, newE) be.Err(t, e, newE)
} else { } else {
assert.Equal(t, key, n.Key) be.Equal(t, n.Key, key)
if ti, ok := newVal.(time.Time); ok { if ti, ok := newVal.(time.Time); ok {
valT, ok := n.Value.(time.Time) valT, ok := n.Value.(time.Time)
if assert.True(t, ok) { be.True(t, ok)
assert.WithinRange(t, valT, ti.Add(-time.Second), ti.Add(time.Second)) be.True(t, valT.After(ti.Add(-time.Second)) && valT.Before(ti.Add(time.Second)))
}
} else { } else {
assert.Equal(t, newVal, n.Value) be.Equal(t, n.Value, newVal)
} }
} }
} }
@ -103,42 +103,42 @@ func TestMetasJson(t *testing.T) {
ms := Metas{{"me", 41}, {"you", false}} ms := Metas{{"me", 41}, {"you", false}}
exp := `{"me":41,"you":false}` exp := `{"me":41,"you":false}`
o, err := json.Marshal(ms) o, err := json.Marshal(ms)
assert.NoError(t, err) be.Err(t, err, nil)
assert.JSONEq(t, exp, string(o)) bep.JSON(t, o, []byte(exp))
} }
func TestMetasJsonUnmarshal(t *testing.T) { func TestMetasJsonUnmarshal(t *testing.T) {
ms := Metas{} ms := Metas{}
in := `{"me":"cool","you":false}` in := `{"me":"cool","you":false}`
err := json.Unmarshal([]byte(in), &ms) err := json.Unmarshal([]byte(in), &ms)
assert.NoError(t, err) be.Err(t, err, nil)
assert.Len(t, ms, 2) be.Equal(t, len(ms), 2)
assert.ElementsMatch(t, Metas{ be.Equal(t, ms, Metas{
{"me", "cool"}, {"me", "cool"},
{"you", false}, {"you", false},
}, ms) })
} }
func TestMetasJsonError(t *testing.T) { func TestMetasJsonError(t *testing.T) {
ms := Metas{} ms := Metas{}
in := "not json" in := "not json"
err := (&ms).UnmarshalJSON([]byte(in)) err := (&ms).UnmarshalJSON([]byte(in))
assert.Error(t, err) be.Err(t, err)
assert.Len(t, ms, 0) be.Equal(t, len(ms), 0)
} }
func TestMetasAppend(t *testing.T) { func TestMetasAppend(t *testing.T) {
ms := Metas{} ms := Metas{}
ms = ms.Append("foo", 42) ms = ms.Append("foo", 42)
assert.Len(t, ms, 1) be.Equal(t, len(ms), 1)
assert.Equal(t, Meta{"foo", 42}, ms[0]) be.Equal(t, ms[0], Meta{"foo", 42})
} }
func TestMetasAppendTo(t *testing.T) { func TestMetasAppendTo(t *testing.T) {
ms := &Metas{} ms := &Metas{}
ms.AppendTo("foo", 42) ms.AppendTo("foo", 42)
assert.Len(t, *ms, 1) be.Equal(t, len(*ms), 1)
assert.Equal(t, Meta{"foo", 42}, (*ms)[0]) be.Equal(t, (*ms)[0], Meta{"foo", 42})
} }
func TestMetasSet(t *testing.T) { func TestMetasSet(t *testing.T) {
@ -196,7 +196,7 @@ func TestMetasSet(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := tt.initial.Set(tt.key, tt.value) result := tt.initial.Set(tt.key, tt.value)
assert.ElementsMatch(t, tt.expected, result) be.Equal(t, result, tt.expected)
}) })
} }
} }
@ -205,14 +205,14 @@ func TestMetasGet(t *testing.T) {
ms := Metas{{"foo", 42}, {"bar", "hello"}} ms := Metas{{"foo", 42}, {"bar", "hello"}}
val, found := ms.Get("foo") val, found := ms.Get("foo")
assert.True(t, found) be.True(t, found)
assert.Equal(t, 42, val) be.Equal(t, val, 42)
val, found = ms.Get("bar") val, found = ms.Get("bar")
assert.True(t, found) be.True(t, found)
assert.Equal(t, "hello", val) be.Equal(t, val, "hello")
val, found = ms.Get("baz") val, found = ms.Get("baz")
assert.False(t, found) be.True(t, !found)
assert.Nil(t, val) be.Equal(t, val, nil)
} }

View file

@ -42,6 +42,7 @@ func (ms Metas) Map() map[string]any {
} }
func parseNestedFields(f map[string]any) { func parseNestedFields(f map[string]any) {
todelete := make([]string, 0, len(f))
for k, v := range f { for k, v := range f {
if strings.Contains(k, ":") { if strings.Contains(k, ":") {
idx := strings.Index(k, ":") idx := strings.Index(k, ":")
@ -50,14 +51,28 @@ func parseNestedFields(f map[string]any) {
nest, ok := f[top].(map[string]any) nest, ok := f[top].(map[string]any)
if !ok { if !ok {
nest = map[string]any{} curr := f[top]
if curr == nil {
nest = map[string]any{}
} else {
nest = map[string]any{".": curr}
}
}
curr, ok := nest[bottom].(map[string]any)
if ok {
curr["."] = v
} else {
nest[bottom] = v
} }
nest[bottom] = v
parseNestedFields(nest) parseNestedFields(nest)
f[top] = nest f[top] = nest
delete(f, k) todelete = append(todelete, k)
} }
} }
for _, k := range todelete {
delete(f, k)
}
} }
// Implements json.Marshaler // Implements json.Marshaler

View file

@ -5,8 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
"github.com/stretchr/testify/require"
) )
const day = time.Hour * 24 const day = time.Hour * 24
@ -58,11 +57,10 @@ func getDateTest(in string, exp time.Time, err string) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
out, er := ParseDate(in) out, er := ParseDate(in)
if err != "" { if err != "" {
assert.ErrorContains(t, er, err) be.Err(t, er, err)
} else { } else {
require.NoError(t, er) be.Err(t, er, nil)
be.Equal(t, out, exp)
assert.Equal(t, exp, out)
} }
} }
} }

View file

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
) )
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
@ -54,20 +54,18 @@ func getParseTestRunner(in string, exp any) func(*testing.T) {
out := ParseString(in) out := ParseString(in)
if expT, ok := exp.(time.Time); ok { if expT, ok := exp.(time.Time); ok {
ti, gotTime := out.(time.Time) ti, gotTime := out.(time.Time)
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") { be.True(t, gotTime)
assert.WithinRange(t, expT, ti.Add(-2*time.Second), ti.Add(2*time.Second)) be.True(t, expT.After(ti.Add(-2*time.Second)) && expT.Before(ti.Add(2*time.Second)))
}
} else { } else {
assert.Equal(t, exp, out) be.Equal(t, out, exp)
} }
out = ParseBytes([]byte(in)) out = ParseBytes([]byte(in))
if expT, ok := exp.(time.Time); ok { if expT, ok := exp.(time.Time); ok {
ti, gotTime := out.(time.Time) ti, gotTime := out.(time.Time)
if assert.True(t, gotTime, "Should have gotten a time.Time, but didn't") { be.True(t, gotTime)
assert.WithinRange(t, expT, ti.Add(-2*time.Second), ti.Add(2*time.Second)) be.True(t, expT.After(ti.Add(-2*time.Second)) && expT.Before(ti.Add(2*time.Second)))
}
} else { } else {
assert.Equal(t, exp, out) be.Equal(t, out, exp)
} }
} }
} }

View file

@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/nalgeon/be"
) )
func TestWriteBuffer(t *testing.T) { func TestWriteBuffer(t *testing.T) {
@ -52,8 +52,8 @@ func getWriteTestRunner(value any, out string, err error) func(*testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
buff := &bytes.Buffer{} buff := &bytes.Buffer{}
n, er := WriteValue(buff, value) n, er := WriteValue(buff, value)
assert.Equal(t, len(out), n) be.Equal(t, n, len(out))
assert.Equal(t, err, er) be.Equal(t, er, err)
assert.Equal(t, out, buff.String()) be.Equal(t, buff.String(), out)
} }
} }