From f22b24744092c500a6a88f61bd0c0d6ecc18f0e5 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 22 Feb 2024 09:57:47 -0300 Subject: [PATCH] feat: add exp/golden (#41) Signed-off-by: Carlos Alexandro Becker --- .github/dependabot.yml | 15 ++++- .github/workflows/golden.yml | 30 ++++++++++ README.md | 3 + exp/golden/go.mod | 5 ++ exp/golden/go.sum | 2 + exp/golden/golden.go | 60 +++++++++++++++++++ exp/golden/golden_test.go | 21 +++++++ .../testdata/TestRequireEqualNoUpdate.golden | 1 + .../testdata/TestRequireEqualUpdate.golden | 1 + exp/teatest/go.mod | 7 +-- exp/teatest/go.sum | 4 +- exp/teatest/teatest.go | 34 +---------- exp/teatest/teatest_test.go | 14 ----- go.work | 1 + go.work.sum | 1 + 15 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/golden.yml create mode 100644 exp/golden/go.mod create mode 100644 exp/golden/go.sum create mode 100644 exp/golden/golden.go create mode 100644 exp/golden/golden_test.go create mode 100644 exp/golden/testdata/TestRequireEqualNoUpdate.golden create mode 100644 exp/golden/testdata/TestRequireEqualUpdate.golden diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 831c9332..22e4bd8a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,7 +21,7 @@ updates: prefix: "chore" include: "scope" - package-ecosystem: "gomod" - directory: "/exp/term" + directory: "/exp/teatest" schedule: interval: "daily" labels: @@ -30,7 +30,16 @@ updates: prefix: "chore" include: "scope" - package-ecosystem: "gomod" - directory: "/exp/teatest" + directory: "/exp/golden" + schedule: + interval: "daily" + labels: + - "dependencies" + commit-message: + prefix: "chore" + include: "scope" + - package-ecosystem: "gomod" + directory: "/exp/strings" schedule: interval: "daily" labels: @@ -48,7 +57,7 @@ updates: prefix: "chore" include: "scope" - package-ecosystem: "gomod" - directory: "/exp/strings" + directory: "/exp/term" schedule: interval: "daily" labels: diff --git a/.github/workflows/golden.yml b/.github/workflows/golden.yml new file mode 100644 index 00000000..ded04c46 --- /dev/null +++ b/.github/workflows/golden.yml @@ -0,0 +1,30 @@ +# auto-generated by scripts/dependabot. DO NOT EDIT. +name: golden + +on: + push: + branches: + - main + pull_request: + paths: + - exp/golden/** + - .github/workflows/golden.yml + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ./exp/golden + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./exp/golden/go.mod + cache: true + cache-dependency-path: ./exp/golden.sum + - run: go build -v ./... + - run: go test -race -v ./... diff --git a/README.md b/README.md index bb2bd7f9..45819de9 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,14 @@ into other repositories. Currently the following packages are available: - [`editor`](./editor): open files in text editors +- [`errors`](./errors): `errors.Join` in older Go versions - [`higherorder`](./exp/higherorder): generic higher order functions +- [`golden`](./exp/golden): verify golden files equality - [`ordered`](./exp/ordered): generic `min`, `max`, and `clamp` functions for ordered types - [`slice`](./exp/slice): generic slice utilities - [`strings`](./exp/strings): utilities for working with strings - [`teatest`](./exp/teatest): a library for testing [Bubble Tea](https://github.com/charmbracelet/bubbletea) programs +- [`term`](./exp/term): terminal utilities: ansi, termios, conpty ## Feedback diff --git a/exp/golden/go.mod b/exp/golden/go.mod new file mode 100644 index 00000000..8140e189 --- /dev/null +++ b/exp/golden/go.mod @@ -0,0 +1,5 @@ +module github.com/charmbracelet/x/exp/golden + +go 1.19 + +require github.com/aymanbagabas/go-udiff v0.2.0 diff --git a/exp/golden/go.sum b/exp/golden/go.sum new file mode 100644 index 00000000..c0b88228 --- /dev/null +++ b/exp/golden/go.sum @@ -0,0 +1,2 @@ +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= diff --git a/exp/golden/golden.go b/exp/golden/golden.go new file mode 100644 index 00000000..d3f975b2 --- /dev/null +++ b/exp/golden/golden.go @@ -0,0 +1,60 @@ +package golden + +import ( + "bytes" + "flag" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/aymanbagabas/go-udiff" +) + +var update = flag.Bool("update", false, "update .golden files") + +// RequireEqual is a helper function to assert the given output is +// the expected from the golden files, printing its diff in case it is not. +// +// You can update the golden files by running your tests with the -update flag. +func RequireEqual(tb testing.TB, out []byte) { + tb.Helper() + + out = fixLineEndings(out) + + golden := filepath.Join("testdata", tb.Name()+".golden") + if *update { + if err := os.MkdirAll(filepath.Dir(golden), 0o755); err != nil { //nolint: gomnd + tb.Fatal(err) + } + if err := os.WriteFile(golden, out, 0o600); err != nil { //nolint: gomnd + tb.Fatal(err) + } + } + + path := filepath.Join(tb.TempDir(), tb.Name()+".out") + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { //nolint: gomnd + tb.Fatal(err) + } + if err := os.WriteFile(path, out, 0o600); err != nil { //nolint: gomnd + tb.Fatal(err) + } + + goldenBts, err := os.ReadFile(golden) + if err != nil { + tb.Fatal(err) + } + + goldenBts = fixLineEndings(goldenBts) + diff := udiff.Unified("golden", "run", string(goldenBts), string(out)) + if diff != "" { + tb.Fatalf("output does not match, expected:\n\n%s\n\ngot:\n\n%s\n\ndiff:\n\n%s", string(goldenBts), string(out), diff) + } +} + +func fixLineEndings(in []byte) []byte { + if runtime.GOOS != "windows" { + return in + } + return bytes.ReplaceAll(in, []byte("\r\n"), []byte{'\n'}) +} diff --git a/exp/golden/golden_test.go b/exp/golden/golden_test.go new file mode 100644 index 00000000..a60f04d1 --- /dev/null +++ b/exp/golden/golden_test.go @@ -0,0 +1,21 @@ +package golden + +import "testing" + +func TestRequireEqualUpdate(t *testing.T) { + enableUpdate(t) + RequireEqual(t, []byte("test")) +} + +func TestRequireEqualNoUpdate(t *testing.T) { + RequireEqual(t, []byte("test")) +} + +func enableUpdate(tb testing.TB) { + tb.Helper() + previous := update + *update = true + tb.Cleanup(func() { + update = previous + }) +} diff --git a/exp/golden/testdata/TestRequireEqualNoUpdate.golden b/exp/golden/testdata/TestRequireEqualNoUpdate.golden new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/exp/golden/testdata/TestRequireEqualNoUpdate.golden @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/exp/golden/testdata/TestRequireEqualUpdate.golden b/exp/golden/testdata/TestRequireEqualUpdate.golden new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/exp/golden/testdata/TestRequireEqualUpdate.golden @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/exp/teatest/go.mod b/exp/teatest/go.mod index d12009c9..db0e984e 100644 --- a/exp/teatest/go.mod +++ b/exp/teatest/go.mod @@ -2,10 +2,7 @@ module github.com/charmbracelet/x/exp/teatest go 1.19 -require ( - github.com/aymanbagabas/go-udiff v0.2.0 - github.com/charmbracelet/bubbletea v0.25.0 -) +require github.com/charmbracelet/bubbletea v0.25.0 require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -20,7 +17,7 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.3.8 // indirect ) diff --git a/exp/teatest/go.sum b/exp/teatest/go.sum index ee2105b9..2d0142c7 100644 --- a/exp/teatest/go.sum +++ b/exp/teatest/go.sum @@ -1,7 +1,5 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= -github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= @@ -30,7 +28,7 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= diff --git a/exp/teatest/teatest.go b/exp/teatest/teatest.go index 42f8960d..81ff90fd 100644 --- a/exp/teatest/teatest.go +++ b/exp/teatest/teatest.go @@ -3,19 +3,17 @@ package teatest import ( "bytes" - "flag" "fmt" "io" "os" "os/signal" - "path/filepath" "sync" "syscall" "testing" "time" - "github.com/aymanbagabas/go-udiff" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/exp/golden" ) // Program defines the subset of the tea.Program API we need for testing. @@ -252,8 +250,6 @@ func (tm *TestModel) Type(s string) { } } -var update = flag.Bool("update", false, "update .golden files") - // RequireEqualOutput is a helper function to assert the given output is // the expected from the golden files, printing its diff in case it is not. // @@ -262,33 +258,7 @@ var update = flag.Bool("update", false, "update .golden files") // You can update the golden files by running your tests with the -update flag. func RequireEqualOutput(tb testing.TB, out []byte) { tb.Helper() - - golden := filepath.Join("testdata", tb.Name()+".golden") - if *update { - if err := os.MkdirAll(filepath.Dir(golden), 0o755); err != nil { //nolint: gomnd - tb.Fatal(err) - } - if err := os.WriteFile(golden, out, 0o600); err != nil { //nolint: gomnd - tb.Fatal(err) - } - } - - path := filepath.Join(tb.TempDir(), tb.Name()+".out") - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { //nolint: gomnd - tb.Fatal(err) - } - if err := os.WriteFile(path, out, 0o600); err != nil { //nolint: gomnd - tb.Fatal(err) - } - - goldenBts, err := os.ReadFile(golden) - if err != nil { - tb.Fatal(err) - } - diff := udiff.Unified("golden", "run", string(goldenBts), string(out)) - if diff != "" { - tb.Fatalf("output does not match, diff:\n\n%s", diff) - } + golden.RequireEqual(tb, out) } func safe(rw io.ReadWriter) io.ReadWriter { diff --git a/exp/teatest/teatest_test.go b/exp/teatest/teatest_test.go index 8335cdf2..befcd445 100644 --- a/exp/teatest/teatest_test.go +++ b/exp/teatest/teatest_test.go @@ -8,11 +8,6 @@ import ( "time" ) -func TestRequireEqualOutputUpdate(t *testing.T) { - enableUpdate(t) - RequireEqualOutput(t, []byte("test")) -} - func TestWaitForErrorReader(t *testing.T) { err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { return true @@ -36,12 +31,3 @@ func TestWaitForTimeout(t *testing.T) { t.Fatalf("unexpected error: %s", err.Error()) } } - -func enableUpdate(tb testing.TB) { - tb.Helper() - previous := update - *update = true - tb.Cleanup(func() { - update = previous - }) -} diff --git a/go.work b/go.work index e988ad34..5dc5d56f 100644 --- a/go.work +++ b/go.work @@ -3,6 +3,7 @@ go 1.21 use ( ./editor ./errors + ./exp/golden ./exp/higherorder ./exp/ordered ./exp/slice diff --git a/go.work.sum b/go.work.sum index 6fb54974..bd02bb51 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,4 @@ +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=