github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/manifest/version_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  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  
    15  	"github.com/cockroachdb/errors"
    16  	"github.com/cockroachdb/redact"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/zuoyebang/bitalostable/internal/base"
    19  	"github.com/zuoyebang/bitalostable/internal/datadriven"
    20  	"github.com/zuoyebang/bitalostable/internal/testkeys"
    21  	"github.com/zuoyebang/bitalostable/vfs"
    22  )
    23  
    24  func levelMetadata(level int, files ...*FileMetadata) LevelMetadata {
    25  	return makeLevelMetadata(base.DefaultComparer.Compare, level, files)
    26  }
    27  
    28  func ikey(s string) InternalKey {
    29  	return base.MakeInternalKey([]byte(s), 0, base.InternalKeyKindSet)
    30  }
    31  
    32  func TestIkeyRange(t *testing.T) {
    33  	cmp := base.DefaultComparer.Compare
    34  	testCases := []struct {
    35  		input, want string
    36  	}{
    37  		{
    38  			"",
    39  			"-",
    40  		},
    41  		{
    42  			"a-e",
    43  			"a-e",
    44  		},
    45  		{
    46  			"a-e a-e",
    47  			"a-e",
    48  		},
    49  		{
    50  			"c-g a-e",
    51  			"a-g",
    52  		},
    53  		{
    54  			"a-e c-g a-e",
    55  			"a-g",
    56  		},
    57  		{
    58  			"b-d f-g",
    59  			"b-g",
    60  		},
    61  		{
    62  			"d-e b-d",
    63  			"b-e",
    64  		},
    65  		{
    66  			"e-e",
    67  			"e-e",
    68  		},
    69  		{
    70  			"f-g e-e d-e c-g b-d a-e",
    71  			"a-g",
    72  		},
    73  	}
    74  	for _, tc := range testCases {
    75  		var f []*FileMetadata
    76  		if tc.input != "" {
    77  			for i, s := range strings.Split(tc.input, " ") {
    78  				m := (&FileMetadata{
    79  					FileNum: base.FileNum(i),
    80  				}).ExtendPointKeyBounds(cmp, ikey(s[0:1]), ikey(s[2:3]))
    81  				f = append(f, m)
    82  			}
    83  		}
    84  		levelMetadata := makeLevelMetadata(base.DefaultComparer.Compare, 0, f)
    85  
    86  		sm, la := KeyRange(base.DefaultComparer.Compare, levelMetadata.Iter())
    87  		got := string(sm.UserKey) + "-" + string(la.UserKey)
    88  		if got != tc.want {
    89  			t.Errorf("KeyRange(%q) = %q, %q", tc.input, got, tc.want)
    90  		}
    91  	}
    92  }
    93  
    94  func TestOverlaps(t *testing.T) {
    95  	var v *Version
    96  	cmp := testkeys.Comparer.Compare
    97  	fmtKey := testkeys.Comparer.FormatKey
    98  	datadriven.RunTest(t, "testdata/overlaps", func(d *datadriven.TestData) string {
    99  		switch d.Cmd {
   100  		case "define":
   101  			var err error
   102  			v, err = ParseVersionDebug(cmp, fmtKey, 64>>10 /* flush split bytes */, d.Input)
   103  			if err != nil {
   104  				return err.Error()
   105  			}
   106  			return v.String()
   107  		case "overlaps":
   108  			var level int
   109  			var start, end string
   110  			var exclusiveEnd bool
   111  			d.ScanArgs(t, "level", &level)
   112  			d.ScanArgs(t, "start", &start)
   113  			d.ScanArgs(t, "end", &end)
   114  			d.ScanArgs(t, "exclusive-end", &exclusiveEnd)
   115  			var buf bytes.Buffer
   116  			v.Overlaps(level, testkeys.Comparer.Compare, []byte(start), []byte(end), exclusiveEnd).Each(func(f *FileMetadata) {
   117  				fmt.Fprintf(&buf, "%s\n", f.DebugString(base.DefaultFormatter, false))
   118  			})
   119  			return buf.String()
   120  		default:
   121  			return fmt.Sprintf("unknown command: %s", d.Cmd)
   122  		}
   123  	})
   124  }
   125  
   126  func TestContains(t *testing.T) {
   127  	cmp := base.DefaultComparer.Compare
   128  	newFileMeta := func(fileNum base.FileNum, size uint64, smallest, largest base.InternalKey) *FileMetadata {
   129  		m := (&FileMetadata{
   130  			FileNum: fileNum,
   131  			Size:    size,
   132  		}).ExtendPointKeyBounds(cmp, smallest, largest)
   133  		return m
   134  	}
   135  	m00 := newFileMeta(
   136  		700,
   137  		1,
   138  		base.ParseInternalKey("b.SET.7008"),
   139  		base.ParseInternalKey("e.SET.7009"),
   140  	)
   141  	m01 := newFileMeta(
   142  		701,
   143  		1,
   144  		base.ParseInternalKey("c.SET.7018"),
   145  		base.ParseInternalKey("f.SET.7019"),
   146  	)
   147  	m02 := newFileMeta(
   148  		702,
   149  		1,
   150  		base.ParseInternalKey("f.SET.7028"),
   151  		base.ParseInternalKey("g.SET.7029"),
   152  	)
   153  	m03 := newFileMeta(
   154  		703,
   155  		1,
   156  		base.ParseInternalKey("x.SET.7038"),
   157  		base.ParseInternalKey("y.SET.7039"),
   158  	)
   159  	m04 := newFileMeta(
   160  		704,
   161  		1,
   162  		base.ParseInternalKey("n.SET.7048"),
   163  		base.ParseInternalKey("p.SET.7049"),
   164  	)
   165  	m05 := newFileMeta(
   166  		705,
   167  		1,
   168  		base.ParseInternalKey("p.SET.7058"),
   169  		base.ParseInternalKey("p.SET.7059"),
   170  	)
   171  	m06 := newFileMeta(
   172  		706,
   173  		1,
   174  		base.ParseInternalKey("p.SET.7068"),
   175  		base.ParseInternalKey("u.SET.7069"),
   176  	)
   177  	m07 := newFileMeta(
   178  		707,
   179  		1,
   180  		base.ParseInternalKey("r.SET.7078"),
   181  		base.ParseInternalKey("s.SET.7079"),
   182  	)
   183  
   184  	m10 := newFileMeta(
   185  		710,
   186  		1,
   187  		base.ParseInternalKey("d.SET.7108"),
   188  		base.ParseInternalKey("g.SET.7109"),
   189  	)
   190  	m11 := newFileMeta(
   191  		711,
   192  		1,
   193  		base.ParseInternalKey("g.SET.7118"),
   194  		base.ParseInternalKey("j.SET.7119"),
   195  	)
   196  	m12 := newFileMeta(
   197  		712,
   198  		1,
   199  		base.ParseInternalKey("n.SET.7128"),
   200  		base.ParseInternalKey("p.SET.7129"),
   201  	)
   202  	m13 := newFileMeta(
   203  		713,
   204  		1,
   205  		base.ParseInternalKey("p.SET.7148"),
   206  		base.ParseInternalKey("p.SET.7149"),
   207  	)
   208  	m14 := newFileMeta(
   209  		714,
   210  		1,
   211  		base.ParseInternalKey("p.SET.7138"),
   212  		base.ParseInternalKey("u.SET.7139"),
   213  	)
   214  
   215  	v := Version{
   216  		Levels: [NumLevels]LevelMetadata{
   217  			0: levelMetadata(0, m00, m01, m02, m03, m04, m05, m06, m07),
   218  			1: levelMetadata(1, m10, m11, m12, m13, m14),
   219  		},
   220  	}
   221  
   222  	testCases := []struct {
   223  		level int
   224  		file  *FileMetadata
   225  		want  bool
   226  	}{
   227  		// Level 0: m00=b-e, m01=c-f, m02=f-g, m03=x-y, m04=n-p, m05=p-p, m06=p-u, m07=r-s.
   228  		// Note that:
   229  		//   - the slice isn't sorted (e.g. m02=f-g, m03=x-y, m04=n-p),
   230  		//   - m00 and m01 overlap (not just touch),
   231  		//   - m06 contains m07,
   232  		//   - m00, m01 and m02 transitively overlap/touch each other, and
   233  		//   - m04, m05, m06 and m07 transitively overlap/touch each other.
   234  		{0, m00, true},
   235  		{0, m01, true},
   236  		{0, m02, true},
   237  		{0, m03, true},
   238  		{0, m04, true},
   239  		{0, m05, true},
   240  		{0, m06, true},
   241  		{0, m07, true},
   242  		{0, m10, false},
   243  		{0, m11, false},
   244  		{0, m12, false},
   245  		{0, m13, false},
   246  		{0, m14, false},
   247  		{1, m00, false},
   248  		{1, m01, false},
   249  		{1, m02, false},
   250  		{1, m03, false},
   251  		{1, m04, false},
   252  		{1, m05, false},
   253  		{1, m06, false},
   254  		{1, m07, false},
   255  		{1, m10, true},
   256  		{1, m11, true},
   257  		{1, m12, true},
   258  		{1, m13, true},
   259  		{1, m14, true},
   260  
   261  		// Level 2: empty.
   262  		{2, m00, false},
   263  		{2, m14, false},
   264  	}
   265  
   266  	for _, tc := range testCases {
   267  		got := v.Contains(tc.level, cmp, tc.file)
   268  		if got != tc.want {
   269  			t.Errorf("level=%d, file=%s\ngot %t\nwant %t", tc.level, tc.file, got, tc.want)
   270  		}
   271  	}
   272  }
   273  
   274  func TestVersionUnref(t *testing.T) {
   275  	list := &VersionList{}
   276  	list.Init(&sync.Mutex{})
   277  	v := &Version{Deleted: func([]*FileMetadata) {}}
   278  	v.Ref()
   279  	list.PushBack(v)
   280  	v.Unref()
   281  	if !list.Empty() {
   282  		t.Fatalf("expected version list to be empty")
   283  	}
   284  }
   285  
   286  func TestCheckOrdering(t *testing.T) {
   287  	cmp := base.DefaultComparer.Compare
   288  	fmtKey := base.DefaultComparer.FormatKey
   289  	datadriven.RunTest(t, "testdata/version_check_ordering",
   290  		func(d *datadriven.TestData) string {
   291  			switch d.Cmd {
   292  			case "check-ordering":
   293  				v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input)
   294  				if err != nil {
   295  					return err.Error()
   296  				}
   297  				// L0 files compare on sequence numbers. Use the seqnums from the
   298  				// smallest / largest bounds for the table.
   299  				v.Levels[0].Slice().Each(func(m *FileMetadata) {
   300  					m.SmallestSeqNum = m.Smallest.SeqNum()
   301  					m.LargestSeqNum = m.Largest.SeqNum()
   302  				})
   303  				if err = v.CheckOrdering(cmp, base.DefaultFormatter); err != nil {
   304  					return err.Error()
   305  				}
   306  				return "OK"
   307  
   308  			default:
   309  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   310  			}
   311  		})
   312  }
   313  
   314  func TestCheckConsistency(t *testing.T) {
   315  	const dir = "./test"
   316  	mem := vfs.NewMem()
   317  	mem.MkdirAll(dir, 0755)
   318  
   319  	cmp := base.DefaultComparer.Compare
   320  	fmtKey := base.DefaultComparer.FormatKey
   321  	parseMeta := func(s string) (*FileMetadata, error) {
   322  		if len(s) == 0 {
   323  			return nil, nil
   324  		}
   325  		parts := strings.Split(s, ":")
   326  		if len(parts) != 2 {
   327  			return nil, errors.Errorf("malformed table spec: %q", s)
   328  		}
   329  		fileNum, err := strconv.Atoi(strings.TrimSpace(parts[0]))
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		size, err := strconv.Atoi(strings.TrimSpace(parts[1]))
   334  		if err != nil {
   335  			return nil, err
   336  		}
   337  		return &FileMetadata{
   338  			FileNum: base.FileNum(fileNum),
   339  			Size:    uint64(size),
   340  		}, nil
   341  	}
   342  
   343  	datadriven.RunTest(t, "testdata/version_check_consistency",
   344  		func(d *datadriven.TestData) string {
   345  			switch d.Cmd {
   346  			case "check-consistency":
   347  				var filesByLevel [NumLevels][]*FileMetadata
   348  				var files *[]*FileMetadata
   349  
   350  				for _, data := range strings.Split(d.Input, "\n") {
   351  					switch data {
   352  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   353  						level, err := strconv.Atoi(data[1:])
   354  						if err != nil {
   355  							return err.Error()
   356  						}
   357  						files = &filesByLevel[level]
   358  
   359  					default:
   360  						m, err := parseMeta(data)
   361  						if err != nil {
   362  							return err.Error()
   363  						}
   364  						if m != nil {
   365  							*files = append(*files, m)
   366  						}
   367  					}
   368  				}
   369  
   370  				redactErr := false
   371  				for _, arg := range d.CmdArgs {
   372  					switch v := arg.String(); v {
   373  					case "redact":
   374  						redactErr = true
   375  					default:
   376  						return fmt.Sprintf("unknown argument: %q", v)
   377  					}
   378  				}
   379  
   380  				v := NewVersion(cmp, fmtKey, 0, filesByLevel)
   381  				err := v.CheckConsistency(dir, mem)
   382  				if err != nil {
   383  					if redactErr {
   384  						redacted := redact.Sprint(err).Redact()
   385  						return string(redacted)
   386  					}
   387  					return err.Error()
   388  				}
   389  				return "OK"
   390  
   391  			case "build":
   392  				for _, data := range strings.Split(d.Input, "\n") {
   393  					m, err := parseMeta(data)
   394  					if err != nil {
   395  						return err.Error()
   396  					}
   397  					path := base.MakeFilepath(mem, dir, base.FileTypeTable, m.FileNum)
   398  					_ = mem.Remove(path)
   399  					f, err := mem.Create(path)
   400  					if err != nil {
   401  						return err.Error()
   402  					}
   403  					_, err = f.Write(make([]byte, m.Size))
   404  					if err != nil {
   405  						return err.Error()
   406  					}
   407  					f.Close()
   408  				}
   409  				return ""
   410  
   411  			default:
   412  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   413  			}
   414  		})
   415  }
   416  
   417  func TestExtendBounds(t *testing.T) {
   418  	cmp := base.DefaultComparer.Compare
   419  	parseBounds := func(line string) (lower, upper InternalKey) {
   420  		parts := strings.Split(line, "-")
   421  		if len(parts) == 1 {
   422  			parts = strings.Split(parts[0], ":")
   423  			start, end := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
   424  			lower = base.ParseInternalKey(start)
   425  			switch k := lower.Kind(); k {
   426  			case base.InternalKeyKindRangeDelete:
   427  				upper = base.MakeRangeDeleteSentinelKey([]byte(end))
   428  			case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete:
   429  				upper = base.MakeExclusiveSentinelKey(k, []byte(end))
   430  			default:
   431  				panic(fmt.Sprintf("unknown kind %s with end key", k))
   432  			}
   433  		} else {
   434  			l, u := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
   435  			lower, upper = base.ParseInternalKey(l), base.ParseInternalKey(u)
   436  		}
   437  		return
   438  	}
   439  	format := func(m *FileMetadata) string {
   440  		var b bytes.Buffer
   441  		var smallest, largest string
   442  		switch m.boundTypeSmallest {
   443  		case boundTypePointKey:
   444  			smallest = "point"
   445  		case boundTypeRangeKey:
   446  			smallest = "range"
   447  		default:
   448  			return fmt.Sprintf("unknown bound type %d", m.boundTypeSmallest)
   449  		}
   450  		switch m.boundTypeLargest {
   451  		case boundTypePointKey:
   452  			largest = "point"
   453  		case boundTypeRangeKey:
   454  			largest = "range"
   455  		default:
   456  			return fmt.Sprintf("unknown bound type %d", m.boundTypeLargest)
   457  		}
   458  		bounds, err := m.boundsMarker()
   459  		if err != nil {
   460  			panic(err)
   461  		}
   462  		fmt.Fprintf(&b, "%s\n", m.DebugString(base.DefaultFormatter, true))
   463  		fmt.Fprintf(&b, "  bounds: (smallest=%s,largest=%s) (0x%08b)\n", smallest, largest, bounds)
   464  		return b.String()
   465  	}
   466  	m := &FileMetadata{}
   467  	datadriven.RunTest(t, "testdata/file_metadata_bounds", func(d *datadriven.TestData) string {
   468  		switch d.Cmd {
   469  		case "reset":
   470  			m = &FileMetadata{}
   471  			return ""
   472  		case "extend-point-key-bounds":
   473  			u, l := parseBounds(d.Input)
   474  			m.ExtendPointKeyBounds(cmp, u, l)
   475  			return format(m)
   476  		case "extend-range-key-bounds":
   477  			u, l := parseBounds(d.Input)
   478  			m.ExtendRangeKeyBounds(cmp, u, l)
   479  			return format(m)
   480  		default:
   481  			return fmt.Sprintf("unknown command %s\n", d.Cmd)
   482  		}
   483  	})
   484  }
   485  
   486  func TestFileMetadata_ParseRoundTrip(t *testing.T) {
   487  	testCases := []struct {
   488  		name   string
   489  		input  string
   490  		output string
   491  	}{
   492  		{
   493  			name:  "point keys only",
   494  			input: "000001:[a#0,SET-z#0,DEL] points:[a#0,SET-z#0,DEL]",
   495  		},
   496  		{
   497  			name:  "range keys only",
   498  			input: "000001:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL] ranges:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL]",
   499  		},
   500  		{
   501  			name:  "point and range keys",
   502  			input: "000001:[a#0,RANGEKEYSET-d#0,DEL] points:[b#0,SET-d#0,DEL] ranges:[a#0,RANGEKEYSET-c#0,RANGEKEYDEL]",
   503  		},
   504  		{
   505  			name:   "whitespace",
   506  			input:  " 000001 : [ a#0,SET - z#0,DEL] points : [ a#0,SET - z#0,DEL] ",
   507  			output: "000001:[a#0,SET-z#0,DEL] points:[a#0,SET-z#0,DEL]",
   508  		},
   509  	}
   510  	for _, tc := range testCases {
   511  		t.Run(tc.name, func(t *testing.T) {
   512  			m, err := ParseFileMetadataDebug(tc.input)
   513  			require.NoError(t, err)
   514  			err = m.Validate(base.DefaultComparer.Compare, base.DefaultFormatter)
   515  			require.NoError(t, err)
   516  			got := m.DebugString(base.DefaultFormatter, true)
   517  			want := tc.input
   518  			if tc.output != "" {
   519  				want = tc.output
   520  			}
   521  			require.Equal(t, want, got)
   522  		})
   523  	}
   524  }