Skip to content

Commit 4ea6466

Browse files
authored
fix: delete temporary files on exit (#101)
1 parent 901cfc0 commit 4ea6466

File tree

6 files changed

+71
-23
lines changed

6 files changed

+71
-23
lines changed

internal/app/app_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/charmbracelet/bubbles/cursor"
1212
tea "github.com/charmbracelet/bubbletea"
13+
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/require"
1415

1516
"github.com/hedhyw/json-log-viewer/internal/app"
@@ -31,7 +32,7 @@ func newTestModel(tb testing.TB, content []byte) tea.Model {
3132
require.NoError(tb, err)
3233
model = handleUpdate(model, events.LogEntriesUpdateMsg(entries))
3334

34-
tb.Cleanup(func() { _ = inputSource.Close() })
35+
tb.Cleanup(func() { assert.NoError(tb, inputSource.Close()) })
3536

3637
return model
3738
}

internal/app/stateinitial_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ func TestStateInitial(t *testing.T) {
2020

2121
inputSource, err := source.Reader(bytes.NewReader([]byte{}), config.GetDefaultConfig())
2222
require.NoError(t, err)
23-
t.Cleanup(func() { _ = inputSource.Close() })
23+
24+
t.Cleanup(func() { assert.NoError(t, inputSource.Close()) })
2425

2526
model := app.NewModel(
2627
"-",

internal/app/stateloaded_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ func BenchmarkStateLoadedBig(b *testing.B) {
201201

202202
inputSource, err := source.Reader(contentReader, cfg)
203203
require.NoError(b, err)
204-
b.Cleanup(func() { _ = inputSource.Close() })
204+
205+
b.Cleanup(func() { assert.NoError(b, inputSource.Close()) })
205206

206207
logEntries, err := inputSource.ParseLogEntries()
207208
if err != nil {

internal/pkg/source/entry_test.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,31 +249,32 @@ func TestLazyLogEntriesFilter(t *testing.T) {
249249
{"hello":"world"}
250250
`, term)
251251

252-
createEntries := func() (*source.Source, source.LazyLogEntries, source.LazyLogEntry) {
252+
createEntries := func(tb testing.TB) (source.LazyLogEntries, source.LazyLogEntry) {
253253
source, err := source.Reader(bytes.NewReader([]byte(logs)), config.GetDefaultConfig())
254254
require.NoError(t, err)
255255

256+
tb.Cleanup(func() { assert.NoError(tb, source.Close()) })
257+
256258
logEntries, err := source.ParseLogEntries()
257259
require.NoError(t, err)
258260

259261
logEntry := logEntries.Entries[1]
260262

261-
return source, logEntries, logEntry
263+
return logEntries, logEntry
262264
}
263265

264266
t.Run("all", func(t *testing.T) {
265267
t.Parallel()
266-
source, logEntries, _ := createEntries()
267-
defer source.Close()
268+
269+
logEntries, _ := createEntries(t)
268270

269271
assert.Len(t, logEntries.Entries, logEntries.Len())
270272
})
271273

272274
t.Run("found_exact", func(t *testing.T) {
273275
t.Parallel()
274276

275-
source, logEntries, logEntry := createEntries()
276-
defer source.Close()
277+
logEntries, logEntry := createEntries(t)
277278

278279
filtered, err := logEntries.Filter(term)
279280
require.NoError(t, err)
@@ -286,8 +287,7 @@ func TestLazyLogEntriesFilter(t *testing.T) {
286287
t.Run("found_ignore_case", func(t *testing.T) {
287288
t.Parallel()
288289

289-
source, logEntries, logEntry := createEntries()
290-
defer source.Close()
290+
logEntries, logEntry := createEntries(t)
291291

292292
filtered, err := logEntries.Filter(strings.ToUpper(term))
293293
require.NoError(t, err)
@@ -300,8 +300,7 @@ func TestLazyLogEntriesFilter(t *testing.T) {
300300
t.Run("empty", func(t *testing.T) {
301301
t.Parallel()
302302

303-
source, logEntries, _ := createEntries()
304-
defer source.Close()
303+
logEntries, _ := createEntries(t)
305304

306305
filtered, err := logEntries.Filter("")
307306
require.NoError(t, err)
@@ -311,8 +310,7 @@ func TestLazyLogEntriesFilter(t *testing.T) {
311310
t.Run("not_found", func(t *testing.T) {
312311
t.Parallel()
313312

314-
source, logEntries, _ := createEntries()
315-
defer source.Close()
313+
logEntries, _ := createEntries(t)
316314

317315
filtered, err := logEntries.Filter(term + " - not found!")
318316
require.NoError(t, err)
@@ -323,8 +321,7 @@ func TestLazyLogEntriesFilter(t *testing.T) {
323321
t.Run("seeker_failed", func(t *testing.T) {
324322
t.Parallel()
325323

326-
source, logEntries, _ := createEntries()
327-
defer source.Close()
324+
logEntries, _ := createEntries(t)
328325

329326
fileName := tests.RequireCreateFile(t, []byte(""))
330327

@@ -622,6 +619,8 @@ func parseLazyLogEntry(tb testing.TB, value string, cfg *config.Config) source.L
622619
source, err := source.Reader(strings.NewReader(value), cfg)
623620
require.NoError(tb, err)
624621

622+
tb.Cleanup(func() { assert.NoError(tb, source.Close()) })
623+
625624
logEntries, err := source.ParseLogEntries()
626625
require.NoError(tb, err)
627626
require.Equal(tb, 1, logEntries.Len())
@@ -635,6 +634,8 @@ func parseTableRow(tb testing.TB, value string, cfg *config.Config) table.Row {
635634
source, err := source.Reader(strings.NewReader(value+"\n"), cfg)
636635
require.NoError(tb, err)
637636

637+
tb.Cleanup(func() { assert.NoError(tb, source.Close()) })
638+
638639
logEntries, err := source.ParseLogEntries()
639640
require.NoError(tb, err)
640641
require.Equal(tb, 1, logEntries.Len(), value)

internal/pkg/source/source.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
const (
1717
maxLineSize = 8 * 1024 * 1024
1818

19-
temporaryFilePattern = "jvl-*.log"
19+
temporaryFilePattern = "jlv-*.log"
20+
21+
ErrFileTruncated semerr.Error = "file truncated"
2022
)
2123

2224
type Source struct {
@@ -34,10 +36,29 @@ type Source struct {
3436
name string
3537
// maxSize is the maximum size of the file we will read.
3638
maxSize int64
39+
// temporaryFiles to remove at the end.
40+
temporaryFiles []string
3741
}
3842

43+
// Close implements io.Closer.
44+
//
45+
// It closes and removes temporary files.
3946
func (s *Source) Close() error {
40-
return errors.Join(s.file.Close(), s.Seeker.Close())
47+
errMulti := make([]error, 0, 2+len(s.temporaryFiles))
48+
49+
if s.file != nil {
50+
errMulti = append(errMulti, s.file.Close())
51+
}
52+
53+
if s.Seeker != nil {
54+
errMulti = append(errMulti, s.Seeker.Close())
55+
}
56+
57+
for _, f := range s.temporaryFiles {
58+
errMulti = append(errMulti, os.Remove(f))
59+
}
60+
61+
return errors.Join(errMulti...)
4162
}
4263

4364
// File creates a new Source for reading log entries from a file.
@@ -56,7 +77,7 @@ func File(name string, cfg *config.Config) (*Source, error) {
5677

5778
source.Seeker, err = os.Open(name)
5879
if err != nil {
59-
return nil, errors.Join(err, source.file.Close())
80+
return nil, errors.Join(err, source.Close())
6081
}
6182

6283
source.reader = bufio.NewReaderSize(
@@ -85,13 +106,15 @@ func Reader(input io.Reader, cfg *config.Config) (*Source, error) {
85106
return nil, fmt.Errorf("creating temporary file: %w", err)
86107
}
87108

109+
source.temporaryFiles = append(source.temporaryFiles, source.file.Name())
110+
88111
// The io.TeeReader will write the input to the is.file as it is read.
89112
reader := io.TeeReader(input, source.file)
90113

91114
// We can now seek against the data that is read in the input io.Reader.
92115
source.Seeker, err = os.Open(source.file.Name())
93116
if err != nil {
94-
return nil, errors.Join(err, source.file.Close())
117+
return nil, errors.Join(err, source.Close())
95118
}
96119

97120
reader = io.LimitReader(reader, source.maxSize)
@@ -125,8 +148,6 @@ func (s *Source) CanFollow() bool {
125148
return len(s.name) != 0
126149
}
127150

128-
const ErrFileTruncated semerr.Error = "file truncated"
129-
130151
// readLogEntry reads the next LazyLogEntry from the file.
131152
func (s *Source) readLogEntry() (LazyLogEntry, error) {
132153
for {

internal/pkg/source/source_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package source_test
22

33
import (
44
"bytes"
5+
"os"
56
"strings"
67
"testing"
78
"testing/iotest"
@@ -80,6 +81,7 @@ func TestParseLogEntriesFromReaderLimited(t *testing.T) {
8081

8182
inputSource, err := source.Reader(reader, cfg)
8283
require.NoError(t, err)
84+
8385
defer func() { assert.NoError(t, inputSource.Close()) }()
8486

8587
logEntries, err := inputSource.ParseLogEntries()
@@ -124,6 +126,12 @@ func TestFile(t *testing.T) {
124126
require.NoError(t, err)
125127

126128
assert.True(t, source.CanFollow())
129+
130+
err = source.Close()
131+
require.NoError(t, err)
132+
133+
_, err = os.Stat(fileName)
134+
require.NoError(t, err, "The file should exist after closing")
127135
})
128136

129137
t.Run("not_found", func(t *testing.T) {
@@ -133,3 +141,18 @@ func TestFile(t *testing.T) {
133141
require.Error(t, err)
134142
})
135143
}
144+
145+
func TestReaderTemporaryFilesDeleted(t *testing.T) {
146+
t.Parallel()
147+
148+
source, err := source.Reader(
149+
bytes.NewReader([]byte(t.Name())),
150+
config.GetDefaultConfig(),
151+
)
152+
require.NoError(t, err)
153+
require.NoError(t, source.Close())
154+
155+
_, err = os.Stat(source.Seeker.Name())
156+
require.Error(t, err)
157+
assert.True(t, os.IsNotExist(err), err)
158+
}

0 commit comments

Comments
 (0)