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

     1  // Copyright 2021 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 atomicfs
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"sync/atomic"
    15  	"testing"
    16  
    17  	"github.com/cockroachdb/datadriven"
    18  	"github.com/cockroachdb/errors"
    19  	"github.com/cockroachdb/pebble/vfs"
    20  	"github.com/cockroachdb/pebble/vfs/errorfs"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestMarker_FilenameRoundtrip(t *testing.T) {
    25  	filenames := []string{
    26  		"marker.foo.000003.MANIFEST-000021",
    27  		"marker.bar.000003.MANIFEST-000021",
    28  		"marker.version.000003.1",
    29  		"marker.version.000003.1.2.3.4",
    30  		"marker.current.500000.MANIFEST-000001",
    31  		"marker.current.18446744073709551615.MANIFEST-000001",
    32  	}
    33  	for _, testFilename := range filenames {
    34  		t.Run(testFilename, func(t *testing.T) {
    35  			name, iter, value, err := parseMarkerFilename(testFilename)
    36  			require.NoError(t, err)
    37  
    38  			filename := markerFilename(name, iter, value)
    39  			require.Equal(t, testFilename, filename)
    40  		})
    41  	}
    42  }
    43  
    44  func TestMarker_Parsefilename(t *testing.T) {
    45  	testCases := map[string]func(require.TestingT, error, ...interface{}){
    46  		"marker.current.000003.MANIFEST-000021":  require.NoError,
    47  		"marker.current.10.MANIFEST-000021":      require.NoError,
    48  		"marker.v.10.1.2.3.4":                    require.NoError,
    49  		"marker.name.18446744073709551615.value": require.NoError,
    50  		"marke.current.000003.MANIFEST-000021":   require.Error,
    51  		"marker.current.foo.MANIFEST-000021":     require.Error,
    52  		"marker.current.ffffff.MANIFEST-000021":  require.Error,
    53  	}
    54  	for filename, assert := range testCases {
    55  		t.Run(filename, func(t *testing.T) {
    56  			_, _, _, err := parseMarkerFilename(filename)
    57  			assert(t, err)
    58  		})
    59  	}
    60  }
    61  
    62  func TestMarker(t *testing.T) {
    63  	markers := map[string]*Marker{}
    64  	memFS := vfs.NewMem()
    65  
    66  	var buf bytes.Buffer
    67  	datadriven.RunTest(t, "testdata/marker", func(t *testing.T, td *datadriven.TestData) string {
    68  		switch td.Cmd {
    69  		case "list":
    70  			ls, err := memFS.List(td.CmdArgs[0].String())
    71  			if err != nil {
    72  				return err.Error()
    73  			}
    74  			sort.Strings(ls)
    75  			buf.Reset()
    76  			for _, filename := range ls {
    77  				fmt.Fprintln(&buf, filename)
    78  			}
    79  			return buf.String()
    80  
    81  		case "locate":
    82  			var dir, marker string
    83  			td.ScanArgs(t, "dir", &dir)
    84  			td.ScanArgs(t, "marker", &marker)
    85  			m, v, err := LocateMarker(memFS, dir, marker)
    86  			if err != nil {
    87  				return err.Error()
    88  			}
    89  			p := memFS.PathJoin(dir, marker)
    90  			if oldMarker := markers[p]; oldMarker != nil {
    91  				if err := oldMarker.Close(); err != nil {
    92  					return err.Error()
    93  				}
    94  			}
    95  
    96  			markers[p] = m
    97  			return v
    98  
    99  		case "mkdir-all":
   100  			if len(td.CmdArgs) != 1 {
   101  				return "usage: mkdir-all <dir>"
   102  			}
   103  			if err := memFS.MkdirAll(td.CmdArgs[0].String(), os.ModePerm); err != nil {
   104  				return err.Error()
   105  			}
   106  			return ""
   107  
   108  		case "move":
   109  			var dir, marker string
   110  			td.ScanArgs(t, "dir", &dir)
   111  			td.ScanArgs(t, "marker", &marker)
   112  			m := markers[memFS.PathJoin(dir, marker)]
   113  			require.NotNil(t, m)
   114  			err := m.Move(td.Input)
   115  			if err != nil {
   116  				return err.Error()
   117  			}
   118  			return ""
   119  
   120  		case "next-iter":
   121  			var dir, marker string
   122  			td.ScanArgs(t, "dir", &dir)
   123  			td.ScanArgs(t, "marker", &marker)
   124  			m := markers[memFS.PathJoin(dir, marker)]
   125  			require.NotNil(t, m)
   126  			return fmt.Sprintf("%d", m.NextIter())
   127  
   128  		case "read":
   129  			var dir, marker string
   130  			td.ScanArgs(t, "dir", &dir)
   131  			td.ScanArgs(t, "marker", &marker)
   132  			v, err := ReadMarker(memFS, dir, marker)
   133  			if err != nil {
   134  				return err.Error()
   135  			}
   136  			return v
   137  
   138  		case "remove-obsolete":
   139  			var dir, marker string
   140  			td.ScanArgs(t, "dir", &dir)
   141  			td.ScanArgs(t, "marker", &marker)
   142  			m := markers[memFS.PathJoin(dir, marker)]
   143  			require.NotNil(t, m)
   144  			obsoleteCount := len(m.obsoleteFiles)
   145  			require.NoError(t, m.RemoveObsolete())
   146  			removedCount := obsoleteCount - len(m.obsoleteFiles)
   147  			return fmt.Sprintf("Removed %d files.", removedCount)
   148  
   149  		case "touch":
   150  			for _, filename := range strings.Split(td.Input, "\n") {
   151  				f, err := memFS.Create(filename)
   152  				if err != nil {
   153  					return err.Error()
   154  				}
   155  				if err := f.Close(); err != nil {
   156  					return err.Error()
   157  				}
   158  			}
   159  			return ""
   160  
   161  		default:
   162  			panic(fmt.Sprintf("unknown command %q", td.Cmd))
   163  		}
   164  	})
   165  }
   166  
   167  func TestMarker_StrictSync(t *testing.T) {
   168  	// Use an in-memory FS that strictly enforces syncs.
   169  	mem := vfs.NewStrictMem()
   170  	syncDir := func(dir string) {
   171  		fdir, err := mem.OpenDir(dir)
   172  		require.NoError(t, err)
   173  		require.NoError(t, fdir.Sync())
   174  		require.NoError(t, fdir.Close())
   175  	}
   176  
   177  	require.NoError(t, mem.MkdirAll("foo", os.ModePerm))
   178  	syncDir("")
   179  	m, v, err := LocateMarker(mem, "foo", "bar")
   180  	require.NoError(t, err)
   181  	require.Equal(t, "", v)
   182  	require.NoError(t, m.Move("hello"))
   183  	require.NoError(t, m.Close())
   184  
   185  	// Discard any unsynced writes to make sure we set up the test
   186  	// preconditions correctly.
   187  	mem.ResetToSyncedState()
   188  	m, v, err = LocateMarker(mem, "foo", "bar")
   189  	require.NoError(t, err)
   190  	require.Equal(t, "hello", v)
   191  	require.NoError(t, m.Move("hello-world"))
   192  	require.NoError(t, m.Close())
   193  
   194  	// Discard any unsynced writes.
   195  	mem.ResetToSyncedState()
   196  	m, v, err = LocateMarker(mem, "foo", "bar")
   197  	require.NoError(t, err)
   198  	require.Equal(t, "hello-world", v)
   199  	require.NoError(t, m.Close())
   200  }
   201  
   202  // TestMarker_FaultTolerance attempts a series of operations on atomic
   203  // markers, injecting errors at successively higher indexed operations.
   204  // It completes when an error is never injected, because the index is
   205  // higher than the number of filesystem operations performed by the
   206  // test.
   207  func TestMarker_FaultTolerance(t *testing.T) {
   208  	done := false
   209  	for i := 1; !done && i < 1000; i++ {
   210  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   211  			var count atomic.Int32
   212  			count.Store(int32(i))
   213  			inj := errorfs.InjectorFunc(func(op errorfs.Op) error {
   214  				// Don't inject on Sync errors. They're fatal.
   215  				if op.Kind == errorfs.OpFileSync {
   216  					return nil
   217  				}
   218  				if v := count.Add(-1); v == 0 {
   219  					return errorfs.ErrInjected
   220  				}
   221  				return nil
   222  			})
   223  
   224  			mem := vfs.NewMem()
   225  			fs := errorfs.Wrap(mem, inj)
   226  			markers := map[string]*Marker{}
   227  			ops := []struct {
   228  				op    string
   229  				name  string
   230  				value string
   231  			}{
   232  				{op: "locate", name: "foo", value: ""},
   233  				{op: "locate", name: "foo", value: ""},
   234  				{op: "locate", name: "bar", value: ""},
   235  				{op: "rm-obsolete", name: "foo"},
   236  				{op: "move", name: "bar", value: "california"},
   237  				{op: "rm-obsolete", name: "bar"},
   238  				{op: "move", name: "bar", value: "california"},
   239  				{op: "move", name: "bar", value: "new-york"},
   240  				{op: "locate", name: "bar", value: "new-york"},
   241  				{op: "move", name: "bar", value: "california"},
   242  				{op: "rm-obsolete", name: "bar"},
   243  				{op: "locate", name: "bar", value: "california"},
   244  				{op: "move", name: "foo", value: "connecticut"},
   245  				{op: "locate", name: "foo", value: "connecticut"},
   246  			}
   247  
   248  			for _, op := range ops {
   249  				runOp := func() error {
   250  					switch op.op {
   251  					case "locate":
   252  						m, v, err := LocateMarker(fs, "", op.name)
   253  						if err != nil {
   254  							return err
   255  						}
   256  						require.NotNil(t, m)
   257  						require.Equal(t, op.value, v)
   258  						if existingMarker := markers[op.name]; existingMarker != nil {
   259  							require.NoError(t, existingMarker.Close())
   260  						}
   261  						markers[op.name] = m
   262  						return nil
   263  					case "move":
   264  						m := markers[op.name]
   265  						require.NotNil(t, m)
   266  						return m.Move(op.value)
   267  					case "rm-obsolete":
   268  						m := markers[op.name]
   269  						require.NotNil(t, m)
   270  						return m.RemoveObsolete()
   271  					default:
   272  						panic("unreachable")
   273  					}
   274  				}
   275  
   276  				// Run the operation, if it fails with the injected
   277  				// error, retry it exactly once. The retry should always
   278  				// succeed.
   279  				err := runOp()
   280  				if errors.Is(err, errorfs.ErrInjected) {
   281  					err = runOp()
   282  				}
   283  				require.NoError(t, err)
   284  			}
   285  
   286  			for _, m := range markers {
   287  				require.NoError(t, m.Close())
   288  			}
   289  
   290  			// Stop if the number of operations in the test case is
   291  			// fewer than `i`.
   292  			done = count.Load() > 0
   293  		})
   294  	}
   295  }