github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/filenames_test.go (about)

     1  // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package pebble
     6  
     7  import (
     8  	"testing"
     9  
    10  	"github.com/cockroachdb/errors"
    11  	"github.com/cockroachdb/pebble/internal/base"
    12  	"github.com/cockroachdb/pebble/vfs"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  // TestSetCurrentFileCrash tests a crash that occurs during
    17  // a MANIFEST roll, leaving the temporary CURRENT file on
    18  // the filesystem. These temporary files should be cleaned
    19  // up on Open.
    20  func TestSetCurrentFileCrash(t *testing.T) {
    21  	mem := vfs.NewMem()
    22  
    23  	// Initialize a fresh database to write the initial MANIFEST.
    24  	{
    25  		d, err := Open("", &Options{FS: mem})
    26  		require.NoError(t, err)
    27  		require.NoError(t, d.Close())
    28  	}
    29  
    30  	// Open the database again, this time with a FS that
    31  	// errors on Rename and a tiny max manifest file size
    32  	// to force manifest rolls.
    33  	{
    34  		wantErr := errors.New("rename error")
    35  		_, err := Open("", &Options{
    36  			FS:                    renameErrorFS{FS: mem, err: wantErr},
    37  			Logger:                noFatalLogger{t: t},
    38  			MaxManifestFileSize:   1,
    39  			L0CompactionThreshold: 10,
    40  		})
    41  		// Open should fail during a manifest roll,
    42  		// leaving a temp dir on the filesystem.
    43  		if !errors.Is(err, wantErr) {
    44  			t.Fatal(err)
    45  		}
    46  	}
    47  
    48  	// A temp file should be left on the filesystem
    49  	// from the failed Rename of the CURRENT file.
    50  	if temps := allTempFiles(t, mem); len(temps) == 0 {
    51  		t.Fatal("no temp files on the filesystem")
    52  	}
    53  
    54  	// Open the database a third time with a normal
    55  	// filesystem again. It should clean up any temp
    56  	// files on Open.
    57  	{
    58  		d, err := Open("", &Options{
    59  			FS:                    mem,
    60  			MaxManifestFileSize:   1,
    61  			L0CompactionThreshold: 10,
    62  		})
    63  		require.NoError(t, err)
    64  		require.NoError(t, d.Close())
    65  		if temps := allTempFiles(t, mem); len(temps) > 0 {
    66  			t.Fatalf("temporary files still on disk: %#v\n", temps)
    67  		}
    68  	}
    69  }
    70  
    71  func allTempFiles(t *testing.T, fs vfs.FS) []string {
    72  	var files []string
    73  	ls, err := fs.List("")
    74  	require.NoError(t, err)
    75  	for _, f := range ls {
    76  		ft, _, ok := base.ParseFilename(fs, f)
    77  		if ok && ft == fileTypeTemp {
    78  			files = append(files, f)
    79  		}
    80  	}
    81  	return files
    82  }
    83  
    84  type renameErrorFS struct {
    85  	vfs.FS
    86  	err error
    87  }
    88  
    89  func (fs renameErrorFS) Rename(oldname string, newname string) error {
    90  	return fs.err
    91  }
    92  
    93  // noFatalLogger implements Logger, logging to the contained
    94  // *testing.T. Notably it does not panic on calls to Fatalf
    95  // to enable unit tests of fatal logic.
    96  type noFatalLogger struct {
    97  	t *testing.T
    98  }
    99  
   100  func (l noFatalLogger) Infof(format string, args ...interface{}) {
   101  	l.t.Logf(format, args...)
   102  }
   103  
   104  func (l noFatalLogger) Errorf(format string, args ...interface{}) {
   105  	l.t.Logf(format, args...)
   106  }
   107  
   108  func (l noFatalLogger) Fatalf(format string, args ...interface{}) {
   109  	l.t.Logf(format, args...)
   110  }