mirror of
https://github.com/numtide/treefmt.git
synced 2024-11-25 21:02:04 +03:00
feat: match before checking cache
This changes the flow of processing to make unmatched behaviour more consistent. Before, we had been: - traversing the filesystem - comparing with the cache and only emitting files which had changed - applying the matching rules to determine which formatters should be applied to a given file - applying the formatters Now, we do the following: - traverse the filesystem - apply the matching rules to determine which formatters should be applied to a given file - compare with the cache and only emit files which have changed for formatting - apply the formatters It does mean we are applying the matching rules against files which we may not have to format, but in testing against Nixpkgs the performance impact appears negligible. This makes sense since most of the processing time will be spent in the formatters, not applying some globs to file paths. Signed-off-by: Brian McGee <brian@bmcgee.ie>
This commit is contained in:
parent
ed8979ed39
commit
71b262fd68
@ -205,33 +205,34 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string)
|
||||
return fmt.Errorf("failed to create walker: %w", err)
|
||||
}
|
||||
|
||||
// start traversing
|
||||
files := make([]*walk.File, BatchSize)
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
|
||||
for {
|
||||
// read the next batch
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
n, err := reader.Read(ctx, files)
|
||||
|
||||
for idx := 0; idx < n; idx++ {
|
||||
file := files[idx]
|
||||
|
||||
// check if this file is new or has changed when compared to the cache entry
|
||||
if file.Cache == nil || file.Cache.HasChanged(file.Info) {
|
||||
filesCh <- file
|
||||
statz.Add(stats.Emitted, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// ensure context is cancelled to release resources
|
||||
cancel()
|
||||
|
||||
// pass each file into the file channel for processing
|
||||
for idx := 0; idx < n; idx++ {
|
||||
filesCh <- files[idx]
|
||||
}
|
||||
|
||||
if errors.Is(err, io.EOF) {
|
||||
// we have finished traversing
|
||||
break
|
||||
} else if err != nil {
|
||||
// something went wrong
|
||||
log.Errorf("failed to read files: %v", err)
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// indicate no further files for processing
|
||||
close(filesCh)
|
||||
|
||||
// wait for everything to complete
|
||||
@ -263,6 +264,8 @@ func applyFormatters(
|
||||
// formatters which should be applied to their respective files
|
||||
batches := make(map[string][]*format.Task)
|
||||
|
||||
// apply check if the given batch key has enough tasks to trigger processing
|
||||
// flush is used to force processing regardless of the number of tasks
|
||||
apply := func(key string, flush bool) {
|
||||
// lookup the batch and exit early if it's empty
|
||||
batch := batches[key]
|
||||
@ -304,6 +307,7 @@ func applyFormatters(
|
||||
}
|
||||
}
|
||||
|
||||
// tryApply batches tasks by their batch key and processes the batch if there is enough ready
|
||||
tryApply := func(task *format.Task) {
|
||||
// append to batch
|
||||
key := task.BatchKey
|
||||
@ -314,53 +318,68 @@ func applyFormatters(
|
||||
|
||||
return func() error {
|
||||
defer func() {
|
||||
// close processed channel
|
||||
// indicate processing has finished
|
||||
close(formattedCh)
|
||||
}()
|
||||
|
||||
// parse unmatched log level
|
||||
unmatchedLevel, err := log.ParseLevel(cfg.OnUnmatched)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid on-unmatched value: %w", err)
|
||||
}
|
||||
|
||||
// iterate the files channel
|
||||
// iterate the file channel
|
||||
for file := range filesCh {
|
||||
|
||||
// a list of formatters that match this file
|
||||
var matches []*format.Formatter
|
||||
|
||||
// first check if this file has been globally excluded
|
||||
if format.PathMatches(file.RelPath, globalExcludes) {
|
||||
log.Debugf("path matched global excludes: %s", file.RelPath)
|
||||
// mark it as processed and continue to the next
|
||||
formattedCh <- &format.Task{
|
||||
File: file,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// check if any formatters are interested in this file
|
||||
var matches []*format.Formatter
|
||||
for _, formatter := range formatters {
|
||||
if formatter.Wants(file) {
|
||||
matches = append(matches, formatter)
|
||||
} else {
|
||||
// otherwise, check if any formatters are interested in it
|
||||
for _, formatter := range formatters {
|
||||
if formatter.Wants(file) {
|
||||
matches = append(matches, formatter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// see if any formatters matched
|
||||
// indicates no further processing
|
||||
var release bool
|
||||
|
||||
// check if there were no matches
|
||||
if len(matches) == 0 {
|
||||
|
||||
// log that there was no match, exiting with an error if the unmatched level was set to fatal
|
||||
if unmatchedLevel == log.FatalLevel {
|
||||
return fmt.Errorf("no formatter for path: %s", file.RelPath)
|
||||
}
|
||||
|
||||
log.Logf(unmatchedLevel, "no formatter for path: %s", file.RelPath)
|
||||
// mark it as processed and continue to the next
|
||||
formattedCh <- &format.Task{
|
||||
File: file,
|
||||
}
|
||||
|
||||
// no further processing
|
||||
release = true
|
||||
} else {
|
||||
// record the match
|
||||
// record there was a match
|
||||
statz.Add(stats.Matched, 1)
|
||||
// create a new format task, add it to a batch based on its batch key and try to apply if the batch is full
|
||||
task := format.NewTask(file, matches)
|
||||
tryApply(&task)
|
||||
|
||||
// check if the file is new or has changed when compared to the cache entry
|
||||
if file.Cache == nil || file.Cache.HasChanged(file.Info) {
|
||||
// if so, generate a format task, add it to the relevant batch (by batch key) and try to process
|
||||
task := format.NewTask(file, matches)
|
||||
tryApply(&task)
|
||||
} else {
|
||||
// indicate no further processing
|
||||
release = true
|
||||
}
|
||||
}
|
||||
|
||||
if release {
|
||||
// release the file as there is no more processing to be done on it
|
||||
if err := file.Release(); err != nil {
|
||||
return fmt.Errorf("failed to release file: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -398,16 +417,20 @@ func postProcessing(
|
||||
break LOOP
|
||||
}
|
||||
|
||||
// check if the file has changed
|
||||
// grab the underlying file reference
|
||||
file := task.File
|
||||
|
||||
// check if the file has changed
|
||||
changed, newInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statz.Add(stats.Formatted, 1)
|
||||
|
||||
if changed {
|
||||
// record the change
|
||||
statz.Add(stats.Formatted, 1)
|
||||
// record that a change in the underlying file occurred
|
||||
statz.Add(stats.Changed, 1)
|
||||
|
||||
logMethod := log.Debug
|
||||
if cfg.FailOnChange {
|
||||
@ -434,8 +457,8 @@ func postProcessing(
|
||||
}
|
||||
}
|
||||
|
||||
// if fail on change has been enabled, check that no files were actually formatted, throwing an error if so
|
||||
if cfg.FailOnChange && statz.Value(stats.Formatted) != 0 {
|
||||
// if fail on change has been enabled, check that no files were actually changed, throwing an error if so
|
||||
if cfg.FailOnChange && statz.Value(stats.Changed) != 0 {
|
||||
return ErrFailOnChange
|
||||
}
|
||||
|
||||
|
395
cmd/root_test.go
395
cmd/root_test.go
@ -72,24 +72,24 @@ func TestOnUnmatched(t *testing.T) {
|
||||
var out []byte
|
||||
|
||||
// default is warn
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter")
|
||||
as.NoError(err)
|
||||
checkOutput("WARN", out)
|
||||
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "--on-unmatched", "warn")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "warn")
|
||||
as.NoError(err)
|
||||
checkOutput("WARN", out)
|
||||
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-u", "error")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-u", "error")
|
||||
as.NoError(err)
|
||||
checkOutput("ERRO", out)
|
||||
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-v", "--on-unmatched", "info")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-v", "--on-unmatched", "info")
|
||||
as.NoError(err)
|
||||
checkOutput("INFO", out)
|
||||
|
||||
t.Setenv("TREEFMT_ON_UNMATCHED", "debug")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-vv")
|
||||
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-vv")
|
||||
as.NoError(err)
|
||||
checkOutput("DEBU", out)
|
||||
}
|
||||
@ -182,25 +182,53 @@ func TestSpecifyingFormatters(t *testing.T) {
|
||||
setup()
|
||||
_, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 3, 3)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 3,
|
||||
stats.Changed: 3,
|
||||
})
|
||||
|
||||
setup()
|
||||
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "elm,nix")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 2, 2)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 2,
|
||||
})
|
||||
|
||||
setup()
|
||||
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "-f", "ruby,nix")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 2, 2)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 2,
|
||||
})
|
||||
|
||||
setup()
|
||||
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "nix")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 1, 1)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 1,
|
||||
})
|
||||
|
||||
// test bad names
|
||||
setup()
|
||||
|
||||
_, _, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "foo")
|
||||
as.Errorf(err, "formatter not found in config: foo")
|
||||
|
||||
@ -228,7 +256,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// globally exclude nix files
|
||||
cfg.Excludes = []string{"*.nix"}
|
||||
@ -236,7 +270,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 31, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 31,
|
||||
stats.Formatted: 31,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// add haskell files to the global exclude
|
||||
cfg.Excludes = []string{"*.nix", "*.hs"}
|
||||
@ -244,7 +284,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 25, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 25,
|
||||
stats.Formatted: 25,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
echo := cfg.FormatterConfigs["echo"]
|
||||
|
||||
@ -254,7 +300,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 23, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 23,
|
||||
stats.Formatted: 23,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// remove go files from the echo formatter via env
|
||||
t.Setenv("TREEFMT_FORMATTER_ECHO_EXCLUDES", "*.py,*.go")
|
||||
@ -262,7 +314,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 22, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 22,
|
||||
stats.Formatted: 22,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
t.Setenv("TREEFMT_FORMATTER_ECHO_EXCLUDES", "") // reset
|
||||
|
||||
@ -272,7 +330,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 1, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// add js files to echo formatter via env
|
||||
t.Setenv("TREEFMT_FORMATTER_ECHO_INCLUDES", "*.elm,*.js")
|
||||
@ -280,7 +344,13 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 2, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrjRootEnvVariable(t *testing.T) {
|
||||
@ -303,7 +373,13 @@ func TestPrjRootEnvVariable(t *testing.T) {
|
||||
t.Setenv("PRJ_ROOT", tempDir)
|
||||
_, statz, err := treefmt(t, "--config-file", configPath)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
@ -327,34 +403,76 @@ func TestCache(t *testing.T) {
|
||||
test.WriteConfig(t, configPath, cfg)
|
||||
_, statz, err := treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// clear cache
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// clear cache
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// no cache
|
||||
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestChangeWorkingDirectory(t *testing.T) {
|
||||
@ -388,13 +506,25 @@ func TestChangeWorkingDirectory(t *testing.T) {
|
||||
// this should fail if the working directory hasn't been changed first
|
||||
_, statz, err := treefmt(t, "-C", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// use env
|
||||
t.Setenv("TREEFMT_WORKING_DIR", tempDir)
|
||||
_, statz, err = treefmt(t, "-c")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestFailOnChange(t *testing.T) {
|
||||
@ -467,31 +597,61 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
args := []string{"--config-file", configPath, "--tree-root", tempDir}
|
||||
_, statz, err := treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 3, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 3,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// tweak mod time of elm formatter
|
||||
as.NoError(test.RecreateSymlink(t, binPath+"/"+"elm-format"))
|
||||
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 3, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 3,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// check cache is working
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// tweak mod time of python formatter
|
||||
as.NoError(test.RecreateSymlink(t, binPath+"/"+"black"))
|
||||
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 3, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 3,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// check cache is working
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 3,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// add go formatter
|
||||
cfg.FormatterConfigs["go"] = &config.Formatter{
|
||||
@ -503,12 +663,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 4, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 4,
|
||||
stats.Formatted: 4,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// check cache is working
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 4,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// remove python formatter
|
||||
delete(cfg.FormatterConfigs, "python")
|
||||
@ -516,12 +688,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 2, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// check cache is working
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// remove elm formatter
|
||||
delete(cfg.FormatterConfigs, "elm")
|
||||
@ -529,12 +713,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 1, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// check cache is working
|
||||
_, statz, err = treefmt(t, args...)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 0, 0, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 0,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGitWorktree(t *testing.T) {
|
||||
@ -569,10 +765,16 @@ func TestGitWorktree(t *testing.T) {
|
||||
wt, err := repo.Worktree()
|
||||
as.NoError(err, "failed to get git worktree")
|
||||
|
||||
run := func(traversed int32, emitted int32, matched int32, formatted int32) {
|
||||
run := func(traversed int32, matched int32, formatted int32, changed int32) {
|
||||
_, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, traversed, emitted, matched, formatted)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: traversed,
|
||||
stats.Matched: matched,
|
||||
stats.Formatted: formatted,
|
||||
stats.Changed: changed,
|
||||
})
|
||||
}
|
||||
|
||||
// run before adding anything to the worktree
|
||||
@ -592,9 +794,15 @@ func TestGitWorktree(t *testing.T) {
|
||||
run(28, 28, 28, 0)
|
||||
|
||||
// walk with filesystem instead of git
|
||||
// we should traverse more files since we will look in the .git folder
|
||||
_, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--walk", "filesystem")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 60, 60, 60, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 60,
|
||||
stats.Matched: 60,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// capture current cwd, so we can replace it after the test is finished
|
||||
cwd, err := os.Getwd()
|
||||
@ -608,15 +816,30 @@ func TestGitWorktree(t *testing.T) {
|
||||
// format specific sub paths
|
||||
_, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "-vv")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 2, 2, 2, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 2,
|
||||
stats.Matched: 2,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "haskell")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 9, 9, 9, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 9,
|
||||
stats.Matched: 9,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "haskell", "ruby")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 10, 10, 10, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 10,
|
||||
stats.Matched: 10,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// try with a bad path
|
||||
_, _, err = treefmt(t, "-C", tempDir, "-c", "haskell", "foo")
|
||||
@ -628,11 +851,21 @@ func TestGitWorktree(t *testing.T) {
|
||||
|
||||
_, statz, err = treefmt(t, "-C", tempDir, "-c", "haskell", "foo.txt")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 8, 8, 8, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 8,
|
||||
stats.Matched: 8,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
_, statz, err = treefmt(t, "-C", tempDir, "-c", "foo.txt")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 1, 1, 1, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 1,
|
||||
stats.Matched: 1,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestPathsArg(t *testing.T) {
|
||||
@ -677,19 +910,38 @@ func TestPathsArg(t *testing.T) {
|
||||
// without any path args
|
||||
_, statz, err := treefmt(t)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// specify some explicit paths
|
||||
_, statz, err = treefmt(t, "-c", "elm/elm.json", "haskell/Nested/Foo.hs")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 2, 2, 2, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 2,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// specify an absolute path
|
||||
absoluteInternalPath, err := filepath.Abs("elm/elm.json")
|
||||
as.NoError(err)
|
||||
|
||||
_, statz, err = treefmt(t, "-c", absoluteInternalPath)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 1, 1, 1, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 1,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// specify a bad path
|
||||
_, _, err = treefmt(t, "-c", "elm/elm.json", "haskell/Nested/Bar.hs")
|
||||
@ -742,7 +994,13 @@ func TestStdin(t *testing.T) {
|
||||
|
||||
out, statz, err := treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin", "test.nix")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 1, 1, 1, 1)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 1,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 1,
|
||||
})
|
||||
|
||||
// the nix formatters should have reduced the example to the following
|
||||
as.Equal(`{ ...}: "hello"
|
||||
@ -767,7 +1025,13 @@ func TestStdin(t *testing.T) {
|
||||
|
||||
out, statz, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin", "test.md")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 1, 1, 1, 1)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 1,
|
||||
stats.Matched: 1,
|
||||
stats.Formatted: 1,
|
||||
stats.Changed: 1,
|
||||
})
|
||||
|
||||
as.Equal(`| col1 | col2 |
|
||||
| ------ | --------- |
|
||||
@ -881,7 +1145,13 @@ func TestRunInSubdir(t *testing.T) {
|
||||
// without any path args, should reformat the whole tree
|
||||
_, statz, err := treefmt(t)
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 32, 32, 32, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 32,
|
||||
stats.Matched: 32,
|
||||
stats.Formatted: 32,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
|
||||
// specify some explicit paths, relative to the tree root
|
||||
// this should not work, as we're in a subdirectory
|
||||
@ -891,7 +1161,13 @@ func TestRunInSubdir(t *testing.T) {
|
||||
// specify some explicit paths, relative to the current directory
|
||||
_, statz, err = treefmt(t, "-c", "elm.json", "../haskell/Nested/Foo.hs")
|
||||
as.NoError(err)
|
||||
assertStats(t, as, statz, 2, 2, 2, 0)
|
||||
|
||||
assertStats(t, as, statz, map[stats.Type]int32{
|
||||
stats.Traversed: 2,
|
||||
stats.Matched: 2,
|
||||
stats.Formatted: 2,
|
||||
stats.Changed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) {
|
||||
@ -945,10 +1221,15 @@ func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) {
|
||||
return out, statz, nil
|
||||
}
|
||||
|
||||
func assertStats(t *testing.T, as *require.Assertions, statz *stats.Stats, traversed int32, emitted int32, matched int32, formatted int32) {
|
||||
func assertStats(
|
||||
t *testing.T,
|
||||
as *require.Assertions,
|
||||
statz *stats.Stats,
|
||||
expected map[stats.Type]int32,
|
||||
) {
|
||||
t.Helper()
|
||||
as.Equal(traversed, statz.Value(stats.Traversed), "stats.traversed")
|
||||
as.Equal(emitted, statz.Value(stats.Emitted), "stats.emitted")
|
||||
as.Equal(matched, statz.Value(stats.Matched), "stats.matched")
|
||||
as.Equal(formatted, statz.Value(stats.Formatted), "stats.formatted")
|
||||
|
||||
for k, v := range expected {
|
||||
as.Equal(v, statz.Value(k), k.String())
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate enumer -type=Type -text -transform=snake -output=./stats_type.go
|
||||
type Type int
|
||||
|
||||
const (
|
||||
Traversed Type = iota
|
||||
Emitted
|
||||
Matched
|
||||
Formatted
|
||||
Changed
|
||||
)
|
||||
|
||||
type Stats struct {
|
||||
@ -44,9 +45,9 @@ func (s *Stats) Print() {
|
||||
fmt.Printf(
|
||||
strings.Join(components, "\n"),
|
||||
s.Value(Traversed),
|
||||
s.Value(Emitted),
|
||||
s.Value(Matched),
|
||||
s.Value(Formatted),
|
||||
s.Value(Changed),
|
||||
s.Elapsed().Round(time.Millisecond),
|
||||
)
|
||||
}
|
||||
@ -55,9 +56,9 @@ func New() Stats {
|
||||
// init counters
|
||||
counters := make(map[Type]*atomic.Int32)
|
||||
counters[Traversed] = &atomic.Int32{}
|
||||
counters[Emitted] = &atomic.Int32{}
|
||||
counters[Matched] = &atomic.Int32{}
|
||||
counters[Formatted] = &atomic.Int32{}
|
||||
counters[Changed] = &atomic.Int32{}
|
||||
|
||||
return Stats{
|
||||
start: time.Now(),
|
||||
|
98
stats/stats_type.go
Normal file
98
stats/stats_type.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Code generated by "enumer -type=Type -text -transform=snake -output=./stats_type.go"; DO NOT EDIT.
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _TypeName = "traversedmatchedformattedchanged"
|
||||
|
||||
var _TypeIndex = [...]uint8{0, 9, 16, 25, 32}
|
||||
|
||||
const _TypeLowerName = "traversedmatchedformattedchanged"
|
||||
|
||||
func (i Type) String() string {
|
||||
if i < 0 || i >= Type(len(_TypeIndex)-1) {
|
||||
return fmt.Sprintf("Type(%d)", i)
|
||||
}
|
||||
return _TypeName[_TypeIndex[i]:_TypeIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _TypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[Traversed-(0)]
|
||||
_ = x[Matched-(1)]
|
||||
_ = x[Formatted-(2)]
|
||||
_ = x[Changed-(3)]
|
||||
}
|
||||
|
||||
var _TypeValues = []Type{Traversed, Matched, Formatted, Changed}
|
||||
|
||||
var _TypeNameToValueMap = map[string]Type{
|
||||
_TypeName[0:9]: Traversed,
|
||||
_TypeLowerName[0:9]: Traversed,
|
||||
_TypeName[9:16]: Matched,
|
||||
_TypeLowerName[9:16]: Matched,
|
||||
_TypeName[16:25]: Formatted,
|
||||
_TypeLowerName[16:25]: Formatted,
|
||||
_TypeName[25:32]: Changed,
|
||||
_TypeLowerName[25:32]: Changed,
|
||||
}
|
||||
|
||||
var _TypeNames = []string{
|
||||
_TypeName[0:9],
|
||||
_TypeName[9:16],
|
||||
_TypeName[16:25],
|
||||
_TypeName[25:32],
|
||||
}
|
||||
|
||||
// TypeString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func TypeString(s string) (Type, error) {
|
||||
if val, ok := _TypeNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _TypeNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to Type values", s)
|
||||
}
|
||||
|
||||
// TypeValues returns all values of the enum
|
||||
func TypeValues() []Type {
|
||||
return _TypeValues
|
||||
}
|
||||
|
||||
// TypeStrings returns a slice of all String values of the enum
|
||||
func TypeStrings() []string {
|
||||
strs := make([]string, len(_TypeNames))
|
||||
copy(strs, _TypeNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAType returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i Type) IsAType() bool {
|
||||
for _, v := range _TypeValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface for Type
|
||||
func (i Type) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for Type
|
||||
func (i *Type) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
*i, err = TypeString(string(text))
|
||||
return err
|
||||
}
|
@ -81,7 +81,7 @@ func TestFilesystemReader(t *testing.T) {
|
||||
|
||||
as.Equal(32, count)
|
||||
as.Equal(int32(32), statz.Value(stats.Traversed))
|
||||
as.Equal(int32(0), statz.Value(stats.Emitted))
|
||||
as.Equal(int32(0), statz.Value(stats.Matched))
|
||||
as.Equal(int32(0), statz.Value(stats.Formatted))
|
||||
as.Equal(int32(0), statz.Value(stats.Changed))
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func TestGitReader(t *testing.T) {
|
||||
|
||||
as.Equal(32, count)
|
||||
as.Equal(int32(32), statz.Value(stats.Traversed))
|
||||
as.Equal(int32(0), statz.Value(stats.Emitted))
|
||||
as.Equal(int32(0), statz.Value(stats.Matched))
|
||||
as.Equal(int32(0), statz.Value(stats.Formatted))
|
||||
as.Equal(int32(0), statz.Value(stats.Changed))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user