From e32b5a2efcc81709fdac6248f4f1509b4243721e Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Fri, 18 Oct 2024 16:41:43 +0100 Subject: [PATCH] feat: improve fail on change test No longer relies on an arbitrary sleep. Signed-off-by: Brian McGee --- cmd/root_test.go | 132 ++++++++++++++++++++-------- nix/packages/treefmt/formatters.nix | 16 +++- 2 files changed, 108 insertions(+), 40 deletions(-) diff --git a/cmd/root_test.go b/cmd/root_test.go index dddd2e59..094938cf 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -9,7 +9,6 @@ import ( "path" "path/filepath" "regexp" - "slices" "strings" "testing" "time" @@ -575,28 +574,100 @@ func TestChangeWorkingDirectory(t *testing.T) { func TestFailOnChange(t *testing.T) { as := require.New(t) - tempDir := test.TempExamples(t) - configPath := tempDir + "/touch.toml" - - // test without any excludes - cfg := &config.Config{ - FormatterConfigs: map[string]*config.Formatter{ - "touch": { - Command: "touch", - Includes: []string{"*"}, + t.Run("change size", func(t *testing.T) { + tempDir := test.TempExamples(t) + configPath := filepath.Join(tempDir, "treefmt.toml") + + cfg := &config.Config{ + FormatterConfigs: map[string]*config.Formatter{ + "append": { + // test-fmt-append is a helper defined in nix/packages/treefmt/formatters.nix which lets us append + // an arbitrary value to a list of files + Command: "test-fmt-append", + Options: []string{"hello"}, + Includes: []string{"elm/*"}, + }, }, - }, - } + } + test.WriteConfig(t, configPath, cfg) - test.WriteConfig(t, configPath, cfg) - _, _, err := treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir) - as.ErrorIs(err, formatCmd.ErrFailOnChange) + _, statz, err := treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir) + as.ErrorIs(err, formatCmd.ErrFailOnChange) - // test with no cache - t.Setenv("TREEFMT_FAIL_ON_CHANGE", "true") - test.WriteConfig(t, configPath, cfg) - _, _, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache") - as.ErrorIs(err, formatCmd.ErrFailOnChange) + assertStats(t, as, statz, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 2, + }) + + // cached + _, statz, err = treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir) + as.NoError(err) + + assertStats(t, as, statz, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 0, + stats.Changed: 0, + }) + }) + + t.Run("change modtime", func(t *testing.T) { + tempDir := test.TempExamples(t) + configPath := filepath.Join(tempDir, "treefmt.toml") + + dateFormat := "2006 01 02 15:04.05" + replacer := strings.NewReplacer(" ", "", ":", "") + + formatTime := func(t time.Time) string { + // go date formats are stupid + return replacer.Replace(t.Format(dateFormat)) + } + + writeConfig := func() { + // new mod time is in the next second + modTime := time.Now().Truncate(time.Second).Add(time.Second) + + cfg := &config.Config{ + FormatterConfigs: map[string]*config.Formatter{ + "append": { + // test-fmt-modtime is a helper defined in nix/packages/treefmt/formatters.nix which lets us set + // a file's modtime to an arbitrary date. + // in this case, we move it forward more than a second so that our second level modtime comparison + // will detect it as a change. + Command: "test-fmt-modtime", + Options: []string{formatTime(modTime)}, + Includes: []string{"haskell/*"}, + }, + }, + } + test.WriteConfig(t, configPath, cfg) + } + + writeConfig() + + _, statz, err := treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir) + as.ErrorIs(err, formatCmd.ErrFailOnChange) + + assertStats(t, as, statz, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 7, + stats.Formatted: 7, + stats.Changed: 7, + }) + + // cached + _, statz, err = treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir) + as.NoError(err) + + assertStats(t, as, statz, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 7, + stats.Formatted: 0, + stats.Changed: 0, + }) + }) } func TestBustCacheOnFormatterChange(t *testing.T) { @@ -1133,17 +1204,17 @@ func TestDeterministicOrderingInPipeline(t *testing.T) { // a and b should execute in lexicographical order // c should execute first since it has a priority of 1 "fmt-a": { - Command: "test-fmt", + Command: "test-fmt-append", Options: []string{"fmt-a"}, Includes: []string{"*.py"}, }, "fmt-b": { - Command: "test-fmt", + Command: "test-fmt-append", Options: []string{"fmt-b"}, Includes: []string{"*.py"}, }, "fmt-c": { - Command: "test-fmt", + Command: "test-fmt-append", Options: []string{"fmt-c"}, Includes: []string{"*.py"}, Priority: 1, @@ -1306,21 +1377,6 @@ func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) { root.SetOut(tempOut) root.SetErr(tempOut) - failOnChange := os.Getenv("TREEFMT_FAIL_ON_CHANGE") == "true" || - slices.Index(args, "--fail-on-change") != -1 - - if failOnChange { - // record the start time - start := time.Now() - - defer func() { - // Wait until we tick over into the next second before continuing. - // This ensures we correctly detect changes as treefmt compares modtime at second level precision. - waitUntil := start.Truncate(time.Second).Add(time.Second) - time.Sleep(time.Until(waitUntil)) - }() - } - // execute the command cmdErr := root.Execute() diff --git a/nix/packages/treefmt/formatters.nix b/nix/packages/treefmt/formatters.nix index 6d155a4a..409edc8b 100644 --- a/nix/packages/treefmt/formatters.nix +++ b/nix/packages/treefmt/formatters.nix @@ -18,9 +18,9 @@ with pkgs; [ opentofu dos2unix yamlfmt - # util for unit testing + # utils for unit testing (pkgs.writeShellApplication { - name = "test-fmt"; + name = "test-fmt-append"; text = '' VALUE="$1" shift @@ -31,4 +31,16 @@ with pkgs; [ done ''; }) + (pkgs.writeShellApplication { + name = "test-fmt-modtime"; + text = '' + VALUE="$1" + shift + + # append value to each file + for FILE in "$@"; do + touch -t "$VALUE" "$FILE" + done + ''; + }) ]