Repository: numtide/treefmt (go)

Issue:
Add something like `--show-unmatched` to display files which have not matched any formatters
By default this is displayed at debug level. Perhaps if the flag was specified they should be displayed at a higher level or maybe even warn.

Pull request:
--on-unwatched
By default, if a path does not match any formatter a log message at WARN level will be emitted. A user can change this by providing the `--on-unmatched` or `-u` flag and specifying a log level `debug,info,warn,error,fatal`.

If `fatal`, the process will exit with an error on the first unmatched path encountered.

## Examples

```
❯ nix fmt -- -c -u fatal
traversed 24 files
emitted 24 files for processing
matched 0 files to formatters
formatted 0 files in 514.925µs
treefmt: error: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.env
```

```
❯ nix fmt -- -c -u error
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.env
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.envrc
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.gitignore
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/LICENSE
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/.vitepress/config.mts
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/init.gif
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/just-use-go.png
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/logo.png
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/logo.svg
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/treefmt.gif
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/vhs/init.tape
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/vhs/treefmt.tape
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/flake.lock
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/go.mod
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/go.sum
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/gomod2nix.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/init.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/elm/src/Main.elm
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/go/go.mod
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/Main.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/Setup.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/haskell-frontend.cabal
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Foo.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Main.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Nested/Foo.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Setup.hs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/haskell.cabal
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/treefmt.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/html/scripts/.gitkeep
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/nixpkgs.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/main.py
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/requirements.txt
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/virtualenv_proxy.py
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/ruby/bundler.rb
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/rust/Cargo.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/rust/src/main.rs
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/shell/foo.sh
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/terraform/main.tf
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/terraform/two.tf
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/touch.toml
ERRO format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/treefmt.toml
traversed 99 files
emitted 99 files for processing
matched 58 files to formatters
formatted 0 files in 529.38272ms

```

```
❯ nix fmt -- -c 
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.env
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.envrc
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/.gitignore
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/LICENSE
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/.vitepress/config.mts
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/init.gif
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/just-use-go.png
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/logo.png
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/logo.svg
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/public/treefmt.gif
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/vhs/init.tape
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/docs/vhs/treefmt.tape
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/flake.lock
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/go.mod
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/go.sum
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/gomod2nix.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/init.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/elm/src/Main.elm
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/go/go.mod
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/Main.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/Setup.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell-frontend/haskell-frontend.cabal
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Foo.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Main.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Nested/Foo.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/Setup.hs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/haskell.cabal
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/haskell/treefmt.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/html/scripts/.gitkeep
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/nixpkgs.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/main.py
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/requirements.txt
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/python/virtualenv_proxy.py
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/ruby/bundler.rb
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/rust/Cargo.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/rust/src/main.rs
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/shell/foo.sh
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/terraform/main.tf
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/terraform/two.tf
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/touch.toml
WARN format: no formatter for path: /home/brian/Development/com/github/numtide/treefmt/test/examples/treefmt.toml
traversed 99 files
emitted 99 files for processing
matched 58 files to formatters
formatted 0 files in 554.236855ms

```

Closes #302



Patch:
diff --git a/cli/cli.go b/cli/cli.go
index d399777..9ddec90 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -22,13 +22,15 @@ type Format struct {
 	Version               bool               `name:"version" short:"V" help:"Print version."`
 	Init                  bool               `name:"init" short:"i" help:"Create a new treefmt.toml."`
 
+	OnUnmatched log.Level `name:"on-unmatched" short:"u" default:"warn" help:"Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>."`
+
 	Paths []string `name:"paths" arg:"" type:"path" optional:"" help:"Paths to format. Defaults to formatting the whole tree."`
 	Stdin bool     `help:"Format the context passed in via stdin."`
 
 	CpuProfile string `optional:"" help:"The file into which a cpu profile will be written."`
 }
 
-func ConfigureLogging() {
+func configureLogging() {
 	log.SetReportTimestamp(false)
 
 	if Cli.Verbosity == 0 {
diff --git a/cli/format.go b/cli/format.go
index e1259f6..a9fe764 100644
--- a/cli/format.go
+++ b/cli/format.go
@@ -40,6 +40,9 @@ var (
 )
 
 func (f *Format) Run() (err error) {
+	// set log level and other options
+	configureLogging()
+
 	// cpu profiling
 	if Cli.CpuProfile != "" {
 		cpuProfile, err := os.Create(Cli.CpuProfile)
@@ -355,8 +358,10 @@ func applyFormatters(ctx context.Context) func() error {
 			}
 
 			if len(matches) == 0 {
-				// no match, so we send it direct to the processed channel
-				log.Debugf("no match found: %s", file.Path)
+				if Cli.OnUnmatched == log.FatalLevel {
+					return fmt.Errorf("no formatter for path: %s", file.Path)
+				}
+				log.Logf(Cli.OnUnmatched, "no formatter for path: %s", file.Path)
 				processedCh <- file
 			} else {
 				// record the match
diff --git a/cli/mappers.go b/cli/mappers.go
new file mode 100644
index 0000000..ee7a13a
--- /dev/null
+++ b/cli/mappers.go
@@ -0,0 +1,39 @@
+package cli
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/alecthomas/kong"
+	"github.com/charmbracelet/log"
+)
+
+var Options []kong.Option
+
+func init() {
+	Options = []kong.Option{
+		kong.TypeMapper(reflect.TypeOf(log.DebugLevel), logLevelDecoder()),
+	}
+}
+
+func logLevelDecoder() kong.MapperFunc {
+	return func(ctx *kong.DecodeContext, target reflect.Value) error {
+		t, err := ctx.Scan.PopValue("string")
+		if err != nil {
+			return err
+		}
+		var str string
+		switch v := t.Value.(type) {
+		case string:
+			str = v
+		default:
+			return fmt.Errorf("expected a string but got %q (%T)", t, t.Value)
+		}
+		level, err := log.ParseLevel(str)
+		if err != nil {
+			return fmt.Errorf("failed to parse '%v' as log level: %w", level, err)
+		}
+		target.Set(reflect.ValueOf(level))
+		return nil
+	}
+}
diff --git a/docs/usage.md b/docs/usage.md
index b289113..c477a8f 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -26,6 +26,7 @@ Flags:
   -v, --verbose                         Set the verbosity of logs e.g. -vv ($LOG_LEVEL).
   -V, --version                         Print version.
   -i, --init                            Create a new treefmt.toml.
+  -u, --on-unmatched=warn               Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>.
       --stdin                           Format the context passed in via stdin.
       --cpu-profile=STRING              The file into which a cpu profile will be written.
 ```
@@ -95,9 +96,19 @@ while `-vv` will also show `[DEBUG]` messages.
 
 Create a new `treefmt.toml`.
 
+### `-u --on-unmatched`
+
+Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>.
+
+[default: warn]
+
 ### `--stdin`
 
-Format the content passed in via stdin.
+Format the context passed in via stdin.
+
+### `--cpu-profile`
+
+The file into which a cpu profile will be written.
 
 ### `-V, --version`
 
diff --git a/main.go b/main.go
index e86339e..aca0a06 100644
--- a/main.go
+++ b/main.go
@@ -35,7 +35,6 @@ func main() {
 		}
 	}
 
-	ctx := kong.Parse(&cli.Cli)
-	cli.ConfigureLogging()
+	ctx := kong.Parse(&cli.Cli, cli.Options...)
 	ctx.FatalIfErrorf(ctx.Run())
 }


Test patch:
diff --git a/cli/format_test.go b/cli/format_test.go
index 559accc..f5df561 100644
--- a/cli/format_test.go
+++ b/cli/format_test.go
@@ -2,6 +2,7 @@ package cli
 
 import (
 	"bufio"
+	"fmt"
 	"os"
 	"os/exec"
 	"path"
@@ -9,7 +10,7 @@ import (
 	"regexp"
 	"testing"
 
-	config2 "git.numtide.com/numtide/treefmt/config"
+	"git.numtide.com/numtide/treefmt/config"
 	"git.numtide.com/numtide/treefmt/format"
 	"git.numtide.com/numtide/treefmt/test"
 
@@ -21,6 +22,63 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestOnUnmatched(t *testing.T) {
+	as := require.New(t)
+
+	// capture current cwd, so we can replace it after the test is finished
+	cwd, err := os.Getwd()
+	as.NoError(err)
+
+	t.Cleanup(func() {
+		// return to the previous working directory
+		as.NoError(os.Chdir(cwd))
+	})
+
+	tempDir := test.TempExamples(t)
+
+	paths := []string{
+		"go/go.mod",
+		"haskell/haskell.cabal",
+		"haskell/treefmt.toml",
+		"html/scripts/.gitkeep",
+		"nixpkgs.toml",
+		"python/requirements.txt",
+		"rust/Cargo.toml",
+		"touch.toml",
+		"treefmt.toml",
+	}
+
+	out, err := cmd(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "fatal")
+	as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s/%s", tempDir, paths[0]))
+
+	checkOutput := func(level string, output []byte) {
+		for _, p := range paths {
+			as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s/%s", level, tempDir, p))
+		}
+	}
+
+	// default is warn
+	out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c")
+	as.NoError(err)
+	checkOutput("WARN", out)
+
+	out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "--on-unmatched", "warn")
+	as.NoError(err)
+	checkOutput("WARN", out)
+
+	out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-u", "error")
+	as.NoError(err)
+	checkOutput("ERRO", out)
+
+	out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-v", "--on-unmatched", "info")
+	as.NoError(err)
+	checkOutput("INFO", out)
+
+	out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-vv", "-u", "debug")
+	as.NoError(err)
+	checkOutput("DEBU", out)
+}
+
 func TestCpuProfile(t *testing.T) {
 	as := require.New(t)
 	tempDir := test.TempExamples(t)
@@ -47,8 +105,8 @@ func TestAllowMissingFormatter(t *testing.T) {
 	tempDir := test.TempExamples(t)
 	configPath := tempDir + "/treefmt.toml"
 
-	test.WriteConfig(t, configPath, config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	test.WriteConfig(t, configPath, config.Config{
+		Formatters: map[string]*config.Formatter{
 			"foo-fmt": {
 				Command: "foo-fmt",
 			},
@@ -65,8 +123,8 @@ func TestAllowMissingFormatter(t *testing.T) {
 func TestSpecifyingFormatters(t *testing.T) {
 	as := require.New(t)
 
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"elm": {
 				Command:  "touch",
 				Options:  []string{"-m"},
@@ -131,8 +189,8 @@ func TestIncludesAndExcludes(t *testing.T) {
 	configPath := tempDir + "/touch.toml"
 
 	// test without any excludes
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -203,8 +261,8 @@ func TestCache(t *testing.T) {
 	configPath := tempDir + "/touch.toml"
 
 	// test without any excludes
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -261,8 +319,8 @@ func TestChangeWorkingDirectory(t *testing.T) {
 	configPath := tempDir + "/treefmt.toml"
 
 	// test without any excludes
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -286,8 +344,8 @@ func TestFailOnChange(t *testing.T) {
 	configPath := tempDir + "/touch.toml"
 
 	// test without any excludes
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"touch": {
 				Command:  "touch",
 				Includes: []string{"*"},
@@ -322,8 +380,8 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
 	as.NoError(os.Setenv("PATH", binPath+":"+os.Getenv("PATH")))
 
 	// start with 2 formatters
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"python": {
 				Command:  "black",
 				Includes: []string{"*.py"},
@@ -367,7 +425,7 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
 	assertStats(t, as, 31, 0, 0, 0)
 
 	// add go formatter
-	cfg.Formatters["go"] = &config2.Formatter{
+	cfg.Formatters["go"] = &config.Formatter{
 		Command:  "gofmt",
 		Options:  []string{"-w"},
 		Includes: []string{"*.go"},
@@ -417,8 +475,8 @@ func TestGitWorktree(t *testing.T) {
 	configPath := filepath.Join(tempDir, "/treefmt.toml")
 
 	// basic config
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -484,8 +542,8 @@ func TestPathsArg(t *testing.T) {
 	as.NoError(os.Chdir(tempDir))
 
 	// basic config
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -528,8 +586,8 @@ func TestStdIn(t *testing.T) {
 	as.NoError(os.Chdir(tempDir))
 
 	// basic config
-	cfg := config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	cfg := config.Config{
+		Formatters: map[string]*config.Formatter{
 			"echo": {
 				Command:  "echo",
 				Includes: []string{"*"},
@@ -568,11 +626,20 @@ go/main.go
 func TestDeterministicOrderingInPipeline(t *testing.T) {
 	as := require.New(t)
 
+	// capture current cwd, so we can replace it after the test is finished
+	cwd, err := os.Getwd()
+	as.NoError(err)
+
+	t.Cleanup(func() {
+		// return to the previous working directory
+		as.NoError(os.Chdir(cwd))
+	})
+
 	tempDir := test.TempExamples(t)
 	configPath := tempDir + "/treefmt.toml"
 
-	test.WriteConfig(t, configPath, config2.Config{
-		Formatters: map[string]*config2.Formatter{
+	test.WriteConfig(t, configPath, config.Config{
+		Formatters: map[string]*config.Formatter{
 			// a and b have no priority set, which means they default to 0 and should execute first
 			// a and b should execute in lexicographical order
 			// c should execute first since it has a priority of 1
@@ -595,7 +662,7 @@ func TestDeterministicOrderingInPipeline(t *testing.T) {
 		},
 	})
 
-	_, err := cmd(t, "-C", tempDir)
+	_, err = cmd(t, "-C", tempDir)
 	as.NoError(err)
 
 	matcher := regexp.MustCompile("^fmt-(.*)")
diff --git a/cli/helpers_test.go b/cli/helpers_test.go
index 04c438b..fed4c4d 100644
--- a/cli/helpers_test.go
+++ b/cli/helpers_test.go
@@ -7,6 +7,8 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/charmbracelet/log"
+
 	"git.numtide.com/numtide/treefmt/stats"
 
 	"git.numtide.com/numtide/treefmt/test"
@@ -33,7 +35,7 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
 	t.Helper()
 
 	// create a new kong context
-	p := newKong(t, &Cli)
+	p := newKong(t, &Cli, Options...)
 	ctx, err := p.Parse(args)
 	if err != nil {
 		return nil, err
@@ -50,6 +52,8 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
 	os.Stdout = tempOut
 	os.Stderr = tempOut
 
+	log.SetOutput(tempOut)
+
 	// run the command
 	if err = ctx.Run(); err != nil {
 		return nil, err
@@ -68,6 +72,7 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
 	// swap outputs back
 	os.Stdout = stdout
 	os.Stderr = stderr
+	log.SetOutput(stderr)
 
 	return out, nil
 }