|
| 1 | +// Copyright 2025 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 | + "context" |
| 9 | + "fmt" |
| 10 | + "testing" |
| 11 | + |
| 12 | + "github.com/cockroachdb/datadriven" |
| 13 | + "github.com/cockroachdb/pebble/vfs" |
| 14 | + "github.com/stretchr/testify/require" |
| 15 | +) |
| 16 | + |
| 17 | +func TestEstimateDiskUsage(t *testing.T) { |
| 18 | + mem := vfs.NewMem() |
| 19 | + d, err := Open("", &Options{FS: mem}) |
| 20 | + require.NoError(t, err) |
| 21 | + defer d.Close() |
| 22 | + |
| 23 | + // Test with empty database |
| 24 | + size, err := d.EstimateDiskUsage([]byte("a"), []byte("z")) |
| 25 | + require.NoError(t, err) |
| 26 | + require.Equal(t, uint64(0), size) |
| 27 | + |
| 28 | + require.NoError(t, d.Set([]byte("apple"), []byte("fruit"), nil)) |
| 29 | + require.NoError(t, d.Set([]byte("banana"), []byte("fruit"), nil)) |
| 30 | + require.NoError(t, d.Set([]byte("cherry"), []byte("fruit"), nil)) |
| 31 | + require.NoError(t, d.Set([]byte("date"), []byte("fruit"), nil)) |
| 32 | + require.NoError(t, d.Flush()) |
| 33 | + |
| 34 | + testCases := []struct { |
| 35 | + name string |
| 36 | + start string |
| 37 | + end string |
| 38 | + }{ |
| 39 | + {"full range", "a", "z"}, |
| 40 | + {"partial range", "apple", "cherry"}, |
| 41 | + {"single key range", "banana", "banana"}, |
| 42 | + {"empty range", "zebra", "zebra"}, |
| 43 | + {"overlapping range", "b", "d"}, |
| 44 | + } |
| 45 | + |
| 46 | + for _, tc := range testCases { |
| 47 | + t.Run(tc.name, func(t *testing.T) { |
| 48 | + size, err := d.EstimateDiskUsage([]byte(tc.start), []byte(tc.end)) |
| 49 | + require.NoError(t, err) |
| 50 | + // Size should be non-negative |
| 51 | + require.GreaterOrEqual(t, size, uint64(0)) |
| 52 | + }) |
| 53 | + } |
| 54 | + // Test invalid range (start > end) |
| 55 | + _, err = d.EstimateDiskUsage([]byte("z"), []byte("a")) |
| 56 | + require.Error(t, err) |
| 57 | + require.Contains(t, err.Error(), "invalid key-range") |
| 58 | +} |
| 59 | + |
| 60 | +func TestEstimateDiskUsageByBackingType(t *testing.T) { |
| 61 | + mem := vfs.NewMem() |
| 62 | + d, err := Open("", &Options{FS: mem}) |
| 63 | + require.NoError(t, err) |
| 64 | + defer d.Close() |
| 65 | + |
| 66 | + // Add some data |
| 67 | + require.NoError(t, d.Set([]byte("key1"), []byte("value1"), nil)) |
| 68 | + require.NoError(t, d.Set([]byte("key2"), []byte("value2"), nil)) |
| 69 | + require.NoError(t, d.Flush()) |
| 70 | + |
| 71 | + total, remote, external, err := d.EstimateDiskUsageByBackingType([]byte("a"), []byte("z")) |
| 72 | + require.NoError(t, err) |
| 73 | + |
| 74 | + // Total should be non-negative |
| 75 | + require.GreaterOrEqual(t, total, uint64(0)) |
| 76 | + |
| 77 | + // Remote and external should be subsets of total |
| 78 | + require.LessOrEqual(t, remote, total) |
| 79 | + require.LessOrEqual(t, external, total) |
| 80 | + |
| 81 | + // External should be subset of remote (external implies remote) |
| 82 | + require.LessOrEqual(t, external, remote) |
| 83 | + |
| 84 | + // Test invalid range |
| 85 | + _, _, _, err = d.EstimateDiskUsageByBackingType([]byte("z"), []byte("a")) |
| 86 | + require.Error(t, err) |
| 87 | + require.Contains(t, err.Error(), "invalid key-range") |
| 88 | +} |
| 89 | + |
| 90 | +func TestDiskUsageDataDriven(t *testing.T) { |
| 91 | + fs := vfs.NewMem() |
| 92 | + var d *DB |
| 93 | + defer func() { |
| 94 | + if d != nil { |
| 95 | + _ = d.Close() |
| 96 | + } |
| 97 | + }() |
| 98 | + |
| 99 | + datadriven.RunTest(t, "testdata/disk_usage", func(t *testing.T, td *datadriven.TestData) string { |
| 100 | + switch td.Cmd { |
| 101 | + case "open": |
| 102 | + if d != nil { |
| 103 | + require.NoError(t, d.Close()) |
| 104 | + d = nil |
| 105 | + } |
| 106 | + opts := &Options{FS: fs} |
| 107 | + require.NoError(t, parseDBOptionsArgs(opts, td.CmdArgs)) |
| 108 | + var err error |
| 109 | + d, err = Open("", opts) |
| 110 | + require.NoError(t, err) |
| 111 | + return "" |
| 112 | + |
| 113 | + case "close": |
| 114 | + if d != nil { |
| 115 | + require.NoError(t, d.Close()) |
| 116 | + d = nil |
| 117 | + } |
| 118 | + return "" |
| 119 | + |
| 120 | + case "batch": |
| 121 | + b := d.NewBatch() |
| 122 | + if err := runBatchDefineCmd(td, b); err != nil { |
| 123 | + return err.Error() |
| 124 | + } |
| 125 | + if err := b.Commit(nil); err != nil { |
| 126 | + return err.Error() |
| 127 | + } |
| 128 | + return "" |
| 129 | + |
| 130 | + case "flush": |
| 131 | + if err := d.Flush(); err != nil { |
| 132 | + return err.Error() |
| 133 | + } |
| 134 | + return "" |
| 135 | + |
| 136 | + case "build": |
| 137 | + if err := runBuildCmd(td, d, fs); err != nil { |
| 138 | + return err.Error() |
| 139 | + } |
| 140 | + return "" |
| 141 | + |
| 142 | + case "ingest": |
| 143 | + if err := runIngestCmd(td, d, fs); err != nil { |
| 144 | + return err.Error() |
| 145 | + } |
| 146 | + return "" |
| 147 | + |
| 148 | + case "compact": |
| 149 | + if len(td.CmdArgs) != 2 { |
| 150 | + return "compact <start> <end>" |
| 151 | + } |
| 152 | + l := []byte(td.CmdArgs[0].Key) |
| 153 | + r := []byte(td.CmdArgs[1].Key) |
| 154 | + if err := d.Compact(context.Background(), l, r, false /* parallelize */); err != nil { |
| 155 | + return err.Error() |
| 156 | + } |
| 157 | + return "" |
| 158 | + |
| 159 | + case "estimate-disk-usage": |
| 160 | + // Parse range arguments, default to "a" and "z" if not specified |
| 161 | + start := []byte("a") |
| 162 | + end := []byte("z") |
| 163 | + if len(td.CmdArgs) >= 2 { |
| 164 | + start = []byte(td.CmdArgs[0].Key) |
| 165 | + end = []byte(td.CmdArgs[1].Key) |
| 166 | + } |
| 167 | + |
| 168 | + var expect string |
| 169 | + if arg, ok := td.Arg("expect"); ok && len(arg.Vals) > 0 { |
| 170 | + expect = arg.Vals[0] |
| 171 | + } |
| 172 | + |
| 173 | + size, err := d.EstimateDiskUsage(start, end) |
| 174 | + if err != nil { |
| 175 | + return err.Error() |
| 176 | + } |
| 177 | + |
| 178 | + switch expect { |
| 179 | + case "zero": |
| 180 | + require.Equal(t, uint64(0), size) |
| 181 | + case "non-zero": |
| 182 | + require.Greater(t, size, uint64(0)) |
| 183 | + } |
| 184 | + return "ok" |
| 185 | + |
| 186 | + case "estimate-disk-usage-by-backing-type": |
| 187 | + // Parse range arguments, default to "a" and "z" if not specified |
| 188 | + start := []byte("a") |
| 189 | + end := []byte("z") |
| 190 | + if len(td.CmdArgs) >= 2 { |
| 191 | + start = []byte(td.CmdArgs[0].Key) |
| 192 | + end = []byte(td.CmdArgs[1].Key) |
| 193 | + } |
| 194 | + |
| 195 | + total, remote, external, err := d.EstimateDiskUsageByBackingType(start, end) |
| 196 | + if err != nil { |
| 197 | + return err.Error() |
| 198 | + } |
| 199 | + |
| 200 | + require.LessOrEqual(t, remote, total) |
| 201 | + require.LessOrEqual(t, external, remote) |
| 202 | + |
| 203 | + if arg, ok := td.Arg("expect-total"); ok && len(arg.Vals) > 0 && arg.Vals[0] == "non-zero" { |
| 204 | + require.Greater(t, total, uint64(0)) |
| 205 | + } |
| 206 | + if arg, ok := td.Arg("expect-remote"); ok && len(arg.Vals) > 0 && arg.Vals[0] == "zero" { |
| 207 | + require.Equal(t, uint64(0), remote) |
| 208 | + } |
| 209 | + if arg, ok := td.Arg("expect-external"); ok && len(arg.Vals) > 0 && arg.Vals[0] == "zero" { |
| 210 | + require.Equal(t, uint64(0), external) |
| 211 | + } |
| 212 | + |
| 213 | + return "ok" |
| 214 | + |
| 215 | + default: |
| 216 | + return fmt.Sprintf("unknown command: %s", td.Cmd) |
| 217 | + } |
| 218 | + }) |
| 219 | +} |
| 220 | + |
| 221 | +// Test that the EstimateDiskUsage and EstimateDiskUsageByBackingType should panic when the DB is closed |
| 222 | +func TestEstimateDiskUsageClosedDB(t *testing.T) { |
| 223 | + mem := vfs.NewMem() |
| 224 | + d, err := Open("", &Options{FS: mem}) |
| 225 | + require.NoError(t, err) |
| 226 | + |
| 227 | + require.NoError(t, d.Set([]byte("key"), []byte("value"), nil)) |
| 228 | + |
| 229 | + require.NoError(t, d.Close()) |
| 230 | + |
| 231 | + // Attempting to estimate on closed DB should panic |
| 232 | + require.Panics(t, func() { |
| 233 | + d.EstimateDiskUsage([]byte("a"), []byte("z")) |
| 234 | + }) |
| 235 | + |
| 236 | + require.Panics(t, func() { |
| 237 | + d.EstimateDiskUsageByBackingType([]byte("a"), []byte("z")) |
| 238 | + }) |
| 239 | +} |
0 commit comments