Skip to content

Commit

Permalink
Merge pull request #345 from numtide/fix/fail-on-change
Browse files Browse the repository at this point in the history
fix: use second precision when comparing file mod times
  • Loading branch information
brianmcgee authored Jul 7, 2024
2 parents 23e563b + 85ce0a2 commit 65152cb
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 52 deletions.
21 changes: 14 additions & 7 deletions cli/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"runtime"
"runtime/pprof"
"syscall"
"time"

"git.numtide.com/numtide/treefmt/format"
"git.numtide.com/numtide/treefmt/stats"
Expand Down Expand Up @@ -389,20 +390,26 @@ func (f *Format) detectFormatted(ctx context.Context) func() error {
return nil
}

// look up current file info
currentInfo, err := os.Stat(file.Path)
// check if the file has changed
changed, newInfo, err := file.HasChanged()
if err != nil {
return fmt.Errorf("failed to stat processed file: %w", err)
return err
}

// check if the file has changed
if !(file.Info.ModTime() == currentInfo.ModTime() && file.Info.Size() == currentInfo.Size()) {
if changed {
// record the change
stats.Add(stats.Formatted, 1)
// log the change for diagnostics
log.Debugf("file has been changed: %s", file.Path)
log.Debug(
"file has changed",
"path", file.Path,
"prev_size", file.Info.Size(),
"current_size", newInfo.Size(),
"prev_mod_time", file.Info.ModTime().Truncate(time.Second),
"current_mod_time", newInfo.ModTime().Truncate(time.Second),
)
// update the file info
file.Info = currentInfo
file.Info = newInfo
}

// mark as processed
Expand Down
64 changes: 34 additions & 30 deletions cli/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"regexp"
"testing"
"time"

"git.numtide.com/numtide/treefmt/config"
"git.numtide.com/numtide/treefmt/format"
Expand Down Expand Up @@ -159,22 +160,22 @@ func TestSpecifyingFormatters(t *testing.T) {
setup()
_, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 3, 3)
assertStats(t, as, 32, 32, 3, 3)

setup()
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "elm,nix")
as.NoError(err)
assertStats(t, as, 31, 31, 2, 2)
assertStats(t, as, 32, 32, 2, 2)

setup()
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "-f", "ruby,nix")
as.NoError(err)
assertStats(t, as, 31, 31, 2, 2)
assertStats(t, as, 32, 32, 2, 2)

setup()
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "nix")
as.NoError(err)
assertStats(t, as, 31, 31, 1, 1)
assertStats(t, as, 32, 32, 1, 1)

// test bad names
setup()
Expand Down Expand Up @@ -204,23 +205,23 @@ func TestIncludesAndExcludes(t *testing.T) {
test.WriteConfig(t, configPath, cfg)
_, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)

// globally exclude nix files
cfg.Global.Excludes = []string{"*.nix"}

test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 30, 0)
assertStats(t, as, 32, 32, 31, 0)

// add haskell files to the global exclude
cfg.Global.Excludes = []string{"*.nix", "*.hs"}

test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 24, 0)
assertStats(t, as, 32, 32, 25, 0)

echo := cfg.Formatters["echo"]

Expand All @@ -230,31 +231,31 @@ func TestIncludesAndExcludes(t *testing.T) {
test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 22, 0)
assertStats(t, as, 32, 32, 23, 0)

// remove go files from the echo formatter
echo.Excludes = []string{"*.py", "*.go"}

test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 21, 0)
assertStats(t, as, 32, 32, 22, 0)

// adjust the includes for echo to only include elm files
echo.Includes = []string{"*.elm"}

test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 1, 0)
assertStats(t, as, 32, 32, 1, 0)

// add js files to echo formatter
echo.Includes = []string{"*.elm", "*.js"}

test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 2, 0)
assertStats(t, as, 32, 32, 2, 0)
}

func TestCache(t *testing.T) {
Expand All @@ -281,7 +282,7 @@ func TestCache(t *testing.T) {
test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)

out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
Expand All @@ -290,7 +291,7 @@ func TestCache(t *testing.T) {
// clear cache
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)

out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
Expand All @@ -299,7 +300,7 @@ func TestCache(t *testing.T) {
// clear cache
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)

out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
Expand All @@ -308,7 +309,7 @@ func TestCache(t *testing.T) {
// no cache
_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)
}

func TestChangeWorkingDirectory(t *testing.T) {
Expand Down Expand Up @@ -342,7 +343,7 @@ func TestChangeWorkingDirectory(t *testing.T) {
// this should fail if the working directory hasn't been changed first
_, err = cmd(t, "-C", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)
}

func TestFailOnChange(t *testing.T) {
Expand All @@ -365,6 +366,9 @@ func TestFailOnChange(t *testing.T) {
_, err := cmd(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir)
as.ErrorIs(err, ErrFailOnChange)

// we have second precision mod time tracking
time.Sleep(time.Second)

// test with no cache
test.WriteConfig(t, configPath, cfg)
_, err = cmd(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
Expand Down Expand Up @@ -411,31 +415,31 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
args := []string{"--config-file", configPath, "--tree-root", tempDir}
_, err := cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 3, 0)
assertStats(t, as, 32, 32, 3, 0)

// tweak mod time of elm formatter
as.NoError(test.RecreateSymlink(t, binPath+"/"+"elm-format"))

_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 3, 0)
assertStats(t, as, 32, 32, 3, 0)

// check cache is working
_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 0, 0, 0)
assertStats(t, as, 32, 0, 0, 0)

// tweak mod time of python formatter
as.NoError(test.RecreateSymlink(t, binPath+"/"+"black"))

_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 3, 0)
assertStats(t, as, 32, 32, 3, 0)

// check cache is working
_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 0, 0, 0)
assertStats(t, as, 32, 0, 0, 0)

// add go formatter
cfg.Formatters["go"] = &config.Formatter{
Expand All @@ -447,38 +451,38 @@ func TestBustCacheOnFormatterChange(t *testing.T) {

_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 4, 0)
assertStats(t, as, 32, 32, 4, 0)

// check cache is working
_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 0, 0, 0)
assertStats(t, as, 32, 0, 0, 0)

// remove python formatter
delete(cfg.Formatters, "python")
test.WriteConfig(t, configPath, cfg)

_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 2, 0)
assertStats(t, as, 32, 32, 2, 0)

// check cache is working
_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 0, 0, 0)
assertStats(t, as, 32, 0, 0, 0)

// remove elm formatter
delete(cfg.Formatters, "elm")
test.WriteConfig(t, configPath, cfg)

_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 31, 1, 0)
assertStats(t, as, 32, 32, 1, 0)

// check cache is working
_, err = cmd(t, args...)
as.NoError(err)
assertStats(t, as, 31, 0, 0, 0)
assertStats(t, as, 32, 0, 0, 0)
}

func TestGitWorktree(t *testing.T) {
Expand Down Expand Up @@ -524,7 +528,7 @@ func TestGitWorktree(t *testing.T) {
// add everything to the worktree
as.NoError(wt.AddGlob("."))
as.NoError(err)
run(31, 31, 31, 0)
run(32, 32, 32, 0)

// remove python directory
as.NoError(wt.RemoveGlob("python/*"))
Expand All @@ -533,7 +537,7 @@ func TestGitWorktree(t *testing.T) {
// walk with filesystem instead of git
_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--walk", "filesystem")
as.NoError(err)
assertStats(t, as, 59, 59, 59, 0)
assertStats(t, as, 61, 61, 61, 0)
}

func TestPathsArg(t *testing.T) {
Expand Down Expand Up @@ -568,7 +572,7 @@ func TestPathsArg(t *testing.T) {
// without any path args
_, err = cmd(t, "-C", tempDir)
as.NoError(err)
assertStats(t, as, 31, 31, 31, 0)
assertStats(t, as, 32, 32, 32, 0)

// specify some explicit paths
_, err = cmd(t, "-C", tempDir, "-c", "elm/elm.json", "haskell/Nested/Foo.hs")
Expand Down
30 changes: 15 additions & 15 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions nix/packages/treefmt/formatters.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ with pkgs; [
statix
deadnix
terraform
dos2unix
yamlfmt
# util for unit testing
(pkgs.writeShellApplication {
name = "test-fmt";
Expand Down
Loading

0 comments on commit 65152cb

Please sign in to comment.