github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/manifest/version_edit_test.go (about)

     1  // Copyright 2012 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 manifest
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"reflect"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/errors"
    20  	"github.com/kr/pretty"
    21  	"github.com/stretchr/testify/require"
    22  	"github.com/zuoyebang/bitalostable/internal/base"
    23  	"github.com/zuoyebang/bitalostable/internal/datadriven"
    24  	"github.com/zuoyebang/bitalostable/record"
    25  )
    26  
    27  func checkRoundTrip(e0 VersionEdit) error {
    28  	var e1 VersionEdit
    29  	buf := new(bytes.Buffer)
    30  	if err := e0.Encode(buf); err != nil {
    31  		return errors.Wrap(err, "encode")
    32  	}
    33  	if err := e1.Decode(buf); err != nil {
    34  		return errors.Wrap(err, "decode")
    35  	}
    36  	if diff := pretty.Diff(e0, e1); diff != nil {
    37  		return errors.Errorf("%s", strings.Join(diff, "\n"))
    38  	}
    39  	return nil
    40  }
    41  
    42  func TestVersionEditRoundTrip(t *testing.T) {
    43  	cmp := base.DefaultComparer.Compare
    44  	m1 := (&FileMetadata{
    45  		FileNum:      805,
    46  		Size:         8050,
    47  		CreationTime: 805030,
    48  	}).ExtendPointKeyBounds(
    49  		cmp,
    50  		base.DecodeInternalKey([]byte("abc\x00\x01\x02\x03\x04\x05\x06\x07")),
    51  		base.DecodeInternalKey([]byte("xyz\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")),
    52  	)
    53  
    54  	m2 := (&FileMetadata{
    55  		FileNum:             806,
    56  		Size:                8060,
    57  		CreationTime:        806040,
    58  		SmallestSeqNum:      3,
    59  		LargestSeqNum:       5,
    60  		MarkedForCompaction: true,
    61  	}).ExtendPointKeyBounds(
    62  		cmp,
    63  		base.DecodeInternalKey([]byte("A\x00\x01\x02\x03\x04\x05\x06\x07")),
    64  		base.DecodeInternalKey([]byte("Z\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")),
    65  	)
    66  
    67  	m3 := (&FileMetadata{
    68  		FileNum:      807,
    69  		Size:         8070,
    70  		CreationTime: 807050,
    71  	}).ExtendRangeKeyBounds(
    72  		cmp,
    73  		base.MakeInternalKey([]byte("aaa"), 0, base.InternalKeyKindRangeKeySet),
    74  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("zzz")),
    75  	)
    76  
    77  	m4 := (&FileMetadata{
    78  		FileNum:        809,
    79  		Size:           8090,
    80  		CreationTime:   809060,
    81  		SmallestSeqNum: 9,
    82  		LargestSeqNum:  11,
    83  	}).ExtendPointKeyBounds(
    84  		cmp,
    85  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
    86  		base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet),
    87  	).ExtendRangeKeyBounds(
    88  		cmp,
    89  		base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet),
    90  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")),
    91  	)
    92  
    93  	testCases := []VersionEdit{
    94  		// An empty version edit.
    95  		{},
    96  		// A complete version edit.
    97  		{
    98  			ComparerName:       "11",
    99  			MinUnflushedLogNum: 22,
   100  			ObsoletePrevLogNum: 33,
   101  			NextFileNum:        44,
   102  			LastSeqNum:         55,
   103  			DeletedFiles: map[DeletedFileEntry]*FileMetadata{
   104  				{
   105  					Level:   3,
   106  					FileNum: 703,
   107  				}: nil,
   108  				{
   109  					Level:   4,
   110  					FileNum: 704,
   111  				}: nil,
   112  			},
   113  			NewFiles: []NewFileEntry{
   114  				{
   115  					Level: 4,
   116  					Meta:  m1,
   117  				},
   118  				{
   119  					Level: 5,
   120  					Meta:  m2,
   121  				},
   122  				{
   123  					Level: 6,
   124  					Meta:  m3,
   125  				},
   126  				{
   127  					Level: 6,
   128  					Meta:  m4,
   129  				},
   130  			},
   131  		},
   132  	}
   133  	for _, tc := range testCases {
   134  		if err := checkRoundTrip(tc); err != nil {
   135  			t.Error(err)
   136  		}
   137  	}
   138  }
   139  
   140  func TestVersionEditDecode(t *testing.T) {
   141  	cmp := base.DefaultComparer.Compare
   142  	m := (&FileMetadata{
   143  		FileNum:        4,
   144  		Size:           986,
   145  		SmallestSeqNum: 3,
   146  		LargestSeqNum:  5,
   147  	}).ExtendPointKeyBounds(
   148  		cmp,
   149  		base.MakeInternalKey([]byte("bar"), 5, base.InternalKeyKindDelete),
   150  		base.MakeInternalKey([]byte("foo"), 4, base.InternalKeyKindSet),
   151  	)
   152  
   153  	testCases := []struct {
   154  		filename     string
   155  		encodedEdits []string
   156  		edits        []VersionEdit
   157  	}{
   158  		// db-stage-1 and db-stage-2 have the same manifest.
   159  		{
   160  			filename: "db-stage-1/MANIFEST-000001",
   161  			encodedEdits: []string{
   162  				"\x02\x00\x03\x02\x04\x00",
   163  			},
   164  			edits: []VersionEdit{
   165  				{
   166  					NextFileNum: 2,
   167  				},
   168  			},
   169  		},
   170  		// db-stage-3 and db-stage-4 have the same manifest.
   171  		{
   172  			filename: "db-stage-3/MANIFEST-000005",
   173  			encodedEdits: []string{
   174  				"\x01\x1aleveldb.BytewiseComparator",
   175  				"\x02\x00",
   176  				"\x02\x04\t\x00\x03\x06\x04\x05d\x00\x04\xda\a\vbar" +
   177  					"\x00\x05\x00\x00\x00\x00\x00\x00\vfoo\x01\x04\x00" +
   178  					"\x00\x00\x00\x00\x00\x03\x05",
   179  			},
   180  			edits: []VersionEdit{
   181  				{
   182  					ComparerName: "leveldb.BytewiseComparator",
   183  				},
   184  				{},
   185  				{
   186  					MinUnflushedLogNum: 4,
   187  					ObsoletePrevLogNum: 0,
   188  					NextFileNum:        6,
   189  					LastSeqNum:         5,
   190  					NewFiles: []NewFileEntry{
   191  						{
   192  							Level: 0,
   193  							Meta:  m,
   194  						},
   195  					},
   196  				},
   197  			},
   198  		},
   199  	}
   200  
   201  	for _, tc := range testCases {
   202  		t.Run("", func(t *testing.T) {
   203  			f, err := os.Open("../../testdata/" + tc.filename)
   204  			if err != nil {
   205  				t.Fatalf("filename=%q: open error: %v", tc.filename, err)
   206  			}
   207  			defer f.Close()
   208  			i, r := 0, record.NewReader(f, 0 /* logNum */)
   209  			for {
   210  				rr, err := r.Next()
   211  				if err == io.EOF {
   212  					break
   213  				}
   214  				if err != nil {
   215  					t.Fatalf("filename=%q i=%d: record reader error: %v", tc.filename, i, err)
   216  				}
   217  				if i >= len(tc.edits) {
   218  					t.Fatalf("filename=%q i=%d: too many version edits", tc.filename, i+1)
   219  				}
   220  
   221  				encodedEdit, err := ioutil.ReadAll(rr)
   222  				if err != nil {
   223  					t.Fatalf("filename=%q i=%d: read error: %v", tc.filename, i, err)
   224  				}
   225  				if s := string(encodedEdit); s != tc.encodedEdits[i] {
   226  					t.Fatalf("filename=%q i=%d: got encoded %q, want %q", tc.filename, i, s, tc.encodedEdits[i])
   227  				}
   228  
   229  				var edit VersionEdit
   230  				err = edit.Decode(bytes.NewReader(encodedEdit))
   231  				if err != nil {
   232  					t.Fatalf("filename=%q i=%d: decode error: %v", tc.filename, i, err)
   233  				}
   234  				if !reflect.DeepEqual(edit, tc.edits[i]) {
   235  					t.Fatalf("filename=%q i=%d: decode\n\tgot  %#v\n\twant %#v\n%s", tc.filename, i, edit, tc.edits[i],
   236  						strings.Join(pretty.Diff(edit, tc.edits[i]), "\n"))
   237  				}
   238  				if err := checkRoundTrip(edit); err != nil {
   239  					t.Fatalf("filename=%q i=%d: round trip: %v", tc.filename, i, err)
   240  				}
   241  
   242  				i++
   243  			}
   244  			if i != len(tc.edits) {
   245  				t.Fatalf("filename=%q: got %d edits, want %d", tc.filename, i, len(tc.edits))
   246  			}
   247  		})
   248  	}
   249  }
   250  
   251  func TestVersionEditEncodeLastSeqNum(t *testing.T) {
   252  	testCases := []struct {
   253  		edit    VersionEdit
   254  		encoded string
   255  	}{
   256  		// If ComparerName is unset, LastSeqNum is only encoded if non-zero.
   257  		{VersionEdit{LastSeqNum: 0}, ""},
   258  		{VersionEdit{LastSeqNum: 1}, "\x04\x01"},
   259  		// For compatibility with RocksDB, if ComparerName is set we always encode
   260  		// LastSeqNum.
   261  		{VersionEdit{ComparerName: "foo", LastSeqNum: 0}, "\x01\x03\x66\x6f\x6f\x04\x00"},
   262  		{VersionEdit{ComparerName: "foo", LastSeqNum: 1}, "\x01\x03\x66\x6f\x6f\x04\x01"},
   263  	}
   264  	for _, c := range testCases {
   265  		t.Run("", func(t *testing.T) {
   266  			var buf bytes.Buffer
   267  			require.NoError(t, c.edit.Encode(&buf))
   268  			if result := buf.String(); c.encoded != result {
   269  				t.Fatalf("expected %x, but found %x", c.encoded, result)
   270  			}
   271  
   272  			if c.edit.ComparerName != "" {
   273  				// Manually decode the version edit so that we can verify the contents
   274  				// even if the LastSeqNum decodes to 0.
   275  				d := versionEditDecoder{strings.NewReader(c.encoded)}
   276  
   277  				// Decode ComparerName.
   278  				tag, err := d.readUvarint()
   279  				require.NoError(t, err)
   280  				if tag != tagComparator {
   281  					t.Fatalf("expected %d, but found %d", tagComparator, tag)
   282  				}
   283  				s, err := d.readBytes()
   284  				require.NoError(t, err)
   285  				if c.edit.ComparerName != string(s) {
   286  					t.Fatalf("expected %q, but found %q", c.edit.ComparerName, s)
   287  				}
   288  
   289  				// Decode LastSeqNum.
   290  				tag, err = d.readUvarint()
   291  				require.NoError(t, err)
   292  				if tag != tagLastSequence {
   293  					t.Fatalf("expected %d, but found %d", tagLastSequence, tag)
   294  				}
   295  				val, err := d.readUvarint()
   296  				require.NoError(t, err)
   297  				if c.edit.LastSeqNum != val {
   298  					t.Fatalf("expected %d, but found %d", c.edit.LastSeqNum, val)
   299  				}
   300  			}
   301  		})
   302  	}
   303  }
   304  
   305  func TestVersionEditApply(t *testing.T) {
   306  	parseMeta := func(s string) (FileMetadata, error) {
   307  		m, err := ParseFileMetadataDebug(s)
   308  		if err != nil {
   309  			return FileMetadata{}, err
   310  		}
   311  		m.SmallestSeqNum = m.Smallest.SeqNum()
   312  		m.LargestSeqNum = m.Largest.SeqNum()
   313  		if m.SmallestSeqNum > m.LargestSeqNum {
   314  			m.SmallestSeqNum, m.LargestSeqNum = m.LargestSeqNum, m.SmallestSeqNum
   315  		}
   316  		return m, nil
   317  	}
   318  
   319  	datadriven.RunTest(t, "testdata/version_edit_apply",
   320  		func(d *datadriven.TestData) string {
   321  			switch d.Cmd {
   322  			case "apply":
   323  				// TODO(sumeer): move this Version parsing code to utils, to
   324  				// avoid repeating it, and make it the inverse of
   325  				// Version.DebugString().
   326  				var v *Version
   327  				ve := &VersionEdit{}
   328  				isVersion := true
   329  				isDelete := true
   330  				var level int
   331  				var err error
   332  				versionFiles := map[base.FileNum]*FileMetadata{}
   333  				for _, data := range strings.Split(d.Input, "\n") {
   334  					data = strings.TrimSpace(data)
   335  					switch data {
   336  					case "edit":
   337  						isVersion = false
   338  					case "delete":
   339  						isVersion = false
   340  						isDelete = true
   341  					case "add":
   342  						isVersion = false
   343  						isDelete = false
   344  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   345  						level, err = strconv.Atoi(data[1:])
   346  						if err != nil {
   347  							return err.Error()
   348  						}
   349  					default:
   350  						if isVersion || !isDelete {
   351  							meta, err := parseMeta(data)
   352  							if err != nil {
   353  								return err.Error()
   354  							}
   355  							if isVersion {
   356  								if v == nil {
   357  									v = new(Version)
   358  									for l := 0; l < NumLevels; l++ {
   359  										v.Levels[l] = makeLevelMetadata(base.DefaultComparer.Compare, l, nil /* files */)
   360  									}
   361  								}
   362  								versionFiles[meta.FileNum] = &meta
   363  								v.Levels[level].tree.insert(&meta)
   364  							} else {
   365  								ve.NewFiles =
   366  									append(ve.NewFiles, NewFileEntry{Level: level, Meta: &meta})
   367  							}
   368  						} else {
   369  							fileNum, err := strconv.Atoi(data)
   370  							if err != nil {
   371  								return err.Error()
   372  							}
   373  							dfe := DeletedFileEntry{Level: level, FileNum: base.FileNum(fileNum)}
   374  							if ve.DeletedFiles == nil {
   375  								ve.DeletedFiles = make(map[DeletedFileEntry]*FileMetadata)
   376  							}
   377  							ve.DeletedFiles[dfe] = versionFiles[dfe.FileNum]
   378  						}
   379  					}
   380  				}
   381  
   382  				if v != nil {
   383  					if err := v.InitL0Sublevels(base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20); err != nil {
   384  						return err.Error()
   385  					}
   386  				}
   387  
   388  				bve := BulkVersionEdit{}
   389  				bve.AddedByFileNum = make(map[base.FileNum]*FileMetadata)
   390  				if err := bve.Accumulate(ve); err != nil {
   391  					return err.Error()
   392  				}
   393  				newv, zombies, err := bve.Apply(v, base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20, 32000)
   394  				if err != nil {
   395  					return err.Error()
   396  				}
   397  
   398  				zombieFileNums := make([]base.FileNum, 0, len(zombies))
   399  				for fileNum := range zombies {
   400  					zombieFileNums = append(zombieFileNums, fileNum)
   401  				}
   402  				sort.Slice(zombieFileNums, func(i, j int) bool {
   403  					return zombieFileNums[i] < zombieFileNums[j]
   404  				})
   405  
   406  				return fmt.Sprintf("%szombies %d\n", newv, zombieFileNums)
   407  
   408  			default:
   409  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   410  			}
   411  		})
   412  }