Skip to content

Commit

Permalink
optimize the file path of snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmood committed Oct 24, 2023
1 parent 366d458 commit f4ec4ba
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 86 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*.out
*.test
tmp/
.snapshots/
.got/
1 change: 1 addition & 0 deletions .got/snapshots/TestSnapshots/a.got-snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"ok"
1 change: 1 addition & 0 deletions .got/snapshots/TestSnapshots/b.got-snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
3 changes: 3 additions & 0 deletions .got/snapshots/TestSnapshots/c.got-snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
got_test.C{
Val: 10,
}
14 changes: 0 additions & 14 deletions .snapshots/TestSnapshots.txt

This file was deleted.

4 changes: 0 additions & 4 deletions .snapshots/TestSnapshotsNotUsed.txt

This file was deleted.

21 changes: 21 additions & 0 deletions assertions_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const (
AssertionIsKind
// AssertionCount type
AssertionCount
// AssertionSnapshot type
AssertionSnapshot
)

// AssertionCtx holds the context of an assertion
Expand Down Expand Up @@ -227,6 +229,25 @@ func NewDefaultAssertionError(theme gop.Theme, diffTheme diff.Theme) AssertionEr
count := f(details[1])
return k("should count") + n + k("times, but got") + count
},
AssertionSnapshot: func(details ...interface{}) string {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

x := details[0].(string)
y := details[1].(string)

if diffTheme == nil {
return j(x, k("not =="), y)
}

if hasNewline(x, y) {
df := diff.Format(diff.Tokenize(ctx, gop.StripANSI(x), gop.StripANSI(y)), diffTheme)
return j(x, k("not =="), y, df)
}

dx, dy := diff.TokenizeLine(ctx, gop.StripANSI(x), gop.StripANSI(y))
return diff.Format(dx, diffTheme) + k("not ==") + diff.Format(dy, diffTheme)
},
}

return &defaultAssertionError{fns: fns}
Expand Down
5 changes: 3 additions & 2 deletions got.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"reflect"
"regexp"
"strings"
"sync"

"github.com/ysmood/gop"
"github.com/ysmood/got/lib/diff"
Expand All @@ -31,7 +32,7 @@ type G struct {
Assertions
Utils

snapshots *snapshots
snapshots *sync.Map
}

// Setup returns a helper to init G instance
Expand All @@ -58,7 +59,7 @@ func New(t Testable) G {
t,
Assertions{Testable: t, ErrorHandler: eh},
Utils{t},
&snapshots{},
&sync.Map{},
}

g.loadSnapshots()
Expand Down
6 changes: 6 additions & 0 deletions lib/example/02_advanced_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ func TestTableDriven(t *testing.T) {
})
}
}

func TestSnapshot(t *testing.T) {
g := setup(t)

g.Snapshot("snapshot the map value", map[int]string{1: "1", 2: "2"})
}
8 changes: 8 additions & 0 deletions setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ func (m *mock) check(expected string) {
m.checkWithStyle(false, expected)
}

func (m *mock) reset() {
m.Lock()
defer m.Unlock()

m.failed = false
m.msg = ""
}

func (m *mock) checkWithStyle(visualizeStyle bool, expected string) {
m.Lock()
defer m.Unlock()
Expand Down
94 changes: 33 additions & 61 deletions snapshots.go
Original file line number Diff line number Diff line change
@@ -1,100 +1,72 @@
package got

import (
"encoding/json"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/ysmood/gop"
)

type snapshots struct {
list *sync.Map
file io.ReadWriter
}
const snapshotExt = ".got-snap"

type snapshot struct {
Name string
Value interface{}
value string
used bool
}

// snapshotsFilePath returns the path of the snapshot file for current test.
func (g G) snapshotsFilePath() string {
return filepath.Join(".snapshots", escapeFileName(g.Name())+".txt")
func (g G) snapshotsDir() string {
return filepath.Join(".got", "snapshots", escapeFileName(g.Name()))
}

func (g G) loadSnapshots() {
p := g.snapshotsFilePath()
paths, err := filepath.Glob(filepath.Join(g.snapshotsDir(), "*"+snapshotExt))
g.E(err)

g.snapshots.list = &sync.Map{}

if g.PathExists(p) {
f, err := os.OpenFile(p, os.O_RDWR, 0755)
g.E(err)
g.snapshots.file = f
} else {
return
for _, path := range paths {
g.snapshots.Store(path, snapshot{g.Read(path).String(), false})
}

dec := json.NewDecoder(g.snapshots.file)

g.Cleanup(func() {
g.snapshots.list.Range(func(key, value interface{}) bool {
s := value.(snapshot)
g.snapshots.Range(func(path, data interface{}) bool {
s := data.(snapshot)
if !s.used {
g.Logf("snapshot `%s` is not used", s.Name)
g.E(os.Remove(path.(string)))
}
return true
})
})

for {
var data snapshot
err := dec.Decode(&data)
if err == io.EOF {
return
}
g.E(err)
g.snapshots.list.Store(data.Name, data)
}
}

// Snapshot asserts that x equals the snapshot with the specified name, name should be unique.
// It can only compare JSON serializable types.
// It will create a new snapshot if the name is not found.
// The snapshot will be saved to ".snapshots/{TEST_NAME}" beside the test file,
// TEST_NAME is the current test name.
// To update the snapshot, just delete the corresponding file.
// Snapshot asserts that x equals the snapshot with the specified name, name should be unique under the same test.
// It will create a new snapshot file if the name is not found.
// The snapshot file will be saved to ".got/snapshots/{TEST_NAME}/{name}.got-snap".
// To update the snapshot, just change the name of the snapshot or remove the corresponding snapshot file.
// It will auto-remove the unused snapshot files after the test.
// The snapshot files should be version controlled.
func (g G) Snapshot(name string, x interface{}) {
g.Helper()

if y, ok := g.snapshots.list.Load(name); ok {
s := y.(snapshot)
s.used = true
g.Eq(g.JSON(g.ToJSON(x)), s.Value)
path := filepath.Join(g.snapshotsDir(), escapeFileName(name)+snapshotExt)

xs := gop.Plain(x)

if data, ok := g.snapshots.Load(path); ok {
s := data.(snapshot)
if xs == s.value {
g.snapshots.Store(path, snapshot{xs, true})
} else {
g.Assertions.err(AssertionSnapshot, xs, s.value)
}
return
}

g.snapshots.list.Store(name, x)
g.snapshots.Store(path, snapshot{xs, true})

g.Cleanup(func() {
if g.snapshots.file == nil {
p := g.snapshotsFilePath()

err := os.MkdirAll(filepath.Dir(p), 0755)
g.E(err)

f, err := os.Create(p)
g.E(err)
g.snapshots.file = f
}

enc := json.NewEncoder(g.snapshots.file)
enc.SetIndent("", " ")
g.E(enc.Encode(snapshot{name, x, false}))
g.E(os.MkdirAll(g.snapshotsDir(), 0755))
g.E(os.WriteFile(path, []byte(xs), 0644))
})
}

Expand Down
30 changes: 26 additions & 4 deletions snapshots_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"
"testing"

"github.com/ysmood/gop"
"github.com/ysmood/got"
)

Expand All @@ -19,29 +20,50 @@ func TestSnapshots(t *testing.T) {
g.Snapshot("b", 1)
g.Snapshot("c", C{10})

g.Run("sub", func(g got.G) {
g.Snapshot("d", "ok")
})

m := &mock{t: t, name: t.Name()}
gm := got.New(m)
gm.Snapshot("a", "ok")
gm.Snapshot("a", "no")
m.check(`"no" ⦗not ==⦘ "ok"`)

gm.Snapshot("a", "no\nno")
g.Has(m.msg, "diff chunk")
m.reset()

gm.ErrorHandler = got.NewDefaultAssertionError(gop.ThemeNone, nil)
gm.Snapshot("a", "no")
m.checkWithStyle(true, `"no" ⦗not ==⦘ "ok"`)
}

func TestSnapshotsCreate(t *testing.T) {
err := os.RemoveAll(filepath.Join(".snapshots", "TestSnapshotsCreate.txt"))
path := filepath.FromSlash(".got/snapshots/TestSnapshotsCreate/a.got-snap")
err := os.RemoveAll(path)
if err != nil {
panic(err)
}

g := got.T(t)

g.Cleanup(func() {
g.True(g.PathExists(path))
})

g.Snapshot("a", "ok")
}

func TestSnapshotsNotUsed(t *testing.T) {
path := filepath.FromSlash(".got/snapshots/TestSnapshotsNotUsed/a.got-snap")

g := got.T(t)
g.WriteFile(path, []byte(`1`))

m := &mock{t: t, name: t.Name()}
got.New(m)
m.cleanup()

if m.msg != "snapshot `a` is not used" {
t.Fail()
}
g.False(g.PathExists(path))
}

0 comments on commit f4ec4ba

Please sign in to comment.