diff --git a/README.md b/README.md index 95b8003..8180212 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ jlv << EOF EOF kubectl logs pod/POD_NAME -f | jlv -docker logs 000000000000 | jlv +docker logs -f 000000000000 2>&1 | jlv ``` ### Hotkeys diff --git a/cmd/jlv/main.go b/cmd/jlv/main.go index 9fee1a9..14595f2 100644 --- a/cmd/jlv/main.go +++ b/cmd/jlv/main.go @@ -4,6 +4,7 @@ import ( "bytes" "flag" "fmt" + "io/fs" "os" "path" @@ -42,7 +43,7 @@ func main() { switch flag.NArg() { case 0: - sourceInput, err = getStdinSource(cfg) + sourceInput, err = getStdinSource(cfg, os.Stdin) if err != nil { fatalf("Stdin: %s\n", err) } @@ -60,17 +61,17 @@ func main() { } } -func getStdinSource(cfg *config.Config) (source.Input, error) { - stat, err := os.Stdin.Stat() +func getStdinSource(cfg *config.Config, defaultInput fs.File) (source.Input, error) { + stat, err := defaultInput.Stat() if err != nil { return nil, fmt.Errorf("stat: %w", err) } - if stat.Mode()&os.ModeNamedPipe == 0 { + if stat.Mode()&os.ModeCharDevice != 0 { return readerinput.New(bytes.NewReader(nil), cfg.StdinReadTimeout), nil } - return readerinput.New(os.Stdin, cfg.StdinReadTimeout), nil + return readerinput.New(defaultInput, cfg.StdinReadTimeout), nil } func fatalf(message string, args ...any) { diff --git a/cmd/jlv/main_test.go b/cmd/jlv/main_test.go new file mode 100644 index 0000000..5104a07 --- /dev/null +++ b/cmd/jlv/main_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "bytes" + "context" + "errors" + "io" + "io/fs" + "os" + "testing" + + "github.com/hedhyw/json-log-viewer/internal/pkg/config" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetStdinSource(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + t.Run("ModeNamedPipe", func(t *testing.T) { + t.Parallel() + + content := t.Name() + "\n" + + file := fakeFile{ + Reader: bytes.NewReader([]byte(content)), + StatFileInfo: fakeFileInfo{ + FileMode: os.ModeNamedPipe, + }, + } + + input, err := getStdinSource(config.GetDefaultConfig(), file) + require.NoError(t, err) + + readCloser, err := input.ReadCloser(ctx) + require.NoError(t, err) + + t.Cleanup(func() { assert.NoError(t, readCloser.Close()) }) + + data, err := io.ReadAll(readCloser) + require.NoError(t, err) + assert.Equal(t, content, string(data)) + }) + + t.Run("ModeCharDevice", func(t *testing.T) { + t.Parallel() + + file := fakeFile{ + Reader: bytes.NewReader([]byte(t.Name() + "\n")), + StatFileInfo: fakeFileInfo{ + FileMode: os.ModeCharDevice, + }, + } + + input, err := getStdinSource(config.GetDefaultConfig(), file) + require.NoError(t, err) + + readCloser, err := input.ReadCloser(ctx) + require.NoError(t, err) + + t.Cleanup(func() { assert.NoError(t, readCloser.Close()) }) + + data, err := io.ReadAll(readCloser) + require.NoError(t, err) + assert.Empty(t, data) + }) + + t.Run("Stat_error", func(t *testing.T) { + t.Parallel() + + // nolint: err113 // Test. + errStat := errors.New(t.Name()) + + file := fakeFile{ErrStat: errStat} + + _, err := getStdinSource(config.GetDefaultConfig(), file) + require.Error(t, err) + require.ErrorIs(t, err, errStat) + }) +} + +type fakeFile struct { + io.Closer + io.Reader + + StatFileInfo os.FileInfo + ErrStat error +} + +// Stat implements fs.File. +func (f fakeFile) Stat() (os.FileInfo, error) { + return f.StatFileInfo, f.ErrStat +} + +type fakeFileInfo struct { + fs.FileInfo + FileMode fs.FileMode +} + +// Mode implements fs.FileInfo. +func (f fakeFileInfo) Mode() fs.FileMode { + return f.FileMode +}