Repository: cockroachdb/pebble
Pull request #2165 (2022-12-05 00:01:11)

internal: Allow building on riscv64 architecture
This PR adds `riscv64` to the 64bit build tags, in order to allow building Pebble on 64-bit RISC-V CPUs. All tests are passing on riscv64:

```
ok  	github.com/cockroachdb/pebble	93.903s
ok  	github.com/cockroachdb/pebble/bloom	0.245s
?   	github.com/cockroachdb/pebble/cmd/pebble	[no test files]
?   	github.com/cockroachdb/pebble/internal/ackseq	[no test files]
ok  	github.com/cockroachdb/pebble/internal/arenaskl	0.435s
ok  	github.com/cockroachdb/pebble/internal/base	0.211s
ok  	github.com/cockroachdb/pebble/internal/batchskl	0.335s
?   	github.com/cockroachdb/pebble/internal/bytealloc	[no test files]
ok  	github.com/cockroachdb/pebble/internal/cache	1.502s
?   	github.com/cockroachdb/pebble/internal/crc	[no test files]
ok  	github.com/cockroachdb/pebble/internal/datadriven	0.151s
?   	github.com/cockroachdb/pebble/internal/errorfs	[no test files]
ok  	github.com/cockroachdb/pebble/internal/fastrand	0.081s [no tests to run]
?   	github.com/cockroachdb/pebble/internal/humanize	[no test files]
ok  	github.com/cockroachdb/pebble/internal/intern	0.101s
?   	github.com/cockroachdb/pebble/internal/invariants	[no test files]
ok  	github.com/cockroachdb/pebble/internal/keyspan	1.517s
ok  	github.com/cockroachdb/pebble/internal/lint	35.749s
ok  	github.com/cockroachdb/pebble/internal/manifest	23.583s
?   	github.com/cockroachdb/pebble/internal/manual	[no test files]
ok  	github.com/cockroachdb/pebble/internal/metamorphic	38.040s
ok  	github.com/cockroachdb/pebble/internal/metamorphic/crossversion	0.140s
ok  	github.com/cockroachdb/pebble/internal/mkbench	1.676s
?   	github.com/cockroachdb/pebble/internal/pacertoy/pebble	[no test files]
?   	github.com/cockroachdb/pebble/internal/pacertoy/rocksdb	[no test files]
?   	github.com/cockroachdb/pebble/internal/private	[no test files]
ok  	github.com/cockroachdb/pebble/internal/randvar	21.615s
?   	github.com/cockroachdb/pebble/internal/rangedel	[no test files]
ok  	github.com/cockroachdb/pebble/internal/rangekey	1.627s
ok  	github.com/cockroachdb/pebble/internal/rate	0.469s
ok  	github.com/cockroachdb/pebble/internal/rawalloc	0.078s [no tests to run]
ok  	github.com/cockroachdb/pebble/internal/testkeys	1.177s
?   	github.com/cockroachdb/pebble/rangekey	[no test files]
ok  	github.com/cockroachdb/pebble/record	15.577s
ok  	github.com/cockroachdb/pebble/replay	0.200s
ok  	github.com/cockroachdb/pebble/sstable	184.118s
ok  	github.com/cockroachdb/pebble/tool	0.891s
ok  	github.com/cockroachdb/pebble/tool/logs	0.230s
ok  	github.com/cockroachdb/pebble/vfs	3.998s
ok  	github.com/cockroachdb/pebble/vfs/atomicfs	0.170s
```

cockroach-teamcity: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cockroachdb/pebble/2165)
<!-- Reviewable:end -->


Patch:
diff --git a/go.mod b/go.mod
index 8ccdf262..32098e14 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ require (
 	github.com/DataDog/zstd v1.4.5
 	github.com/HdrHistogram/hdrhistogram-go v1.1.2
 	github.com/cespare/xxhash/v2 v2.1.2
+	github.com/cockroachdb/datadriven v1.0.2
 	github.com/cockroachdb/errors v1.8.1
 	github.com/cockroachdb/redact v1.0.8
 	github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9
diff --git a/go.sum b/go.sum
index 45fa6910..360272d0 100644
--- a/go.sum
+++ b/go.sum
@@ -69,6 +69,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
+github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
+github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
 github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM=
 github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y=
 github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac=
diff --git a/internal/datadriven/datadriven.go b/internal/datadriven/datadriven.go
deleted file mode 100644
index 926950b4..00000000
--- a/internal/datadriven/datadriven.go
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2018 The Cockroach Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-// implied. See the License for the specific language governing
-// permissions and limitations under the License.
-
-package datadriven // import "github.com/cockroachdb/pebble/internal/datadriven"
-
-import (
-	"bufio"
-	"flag"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"testing"
-
-	"github.com/cockroachdb/errors"
-)
-
-var (
-	rewriteTestFiles = flag.Bool(
-		"rewrite", false,
-		"ignore the expected results and rewrite the test files with the actual results from this "+
-			"run. Used to update tests when a change affects many cases; please verify the testfile "+
-			"diffs carefully!",
-	)
-)
-
-// RunTest invokes a data-driven test. The test cases are contained in a
-// separate test file and are dynamically loaded, parsed, and executed by this
-// testing framework. By convention, test files are typically located in a
-// sub-directory called "testdata". Each test file has the following format:
-//
-//   <command>[,<command>...] [arg | arg=val | arg=(val1, val2, ...)]...
-//   <input to the command>
-//   ----
-//   <expected results>
-//
-// The command input can contain blank lines. However, by default, the expected
-// results cannot contain blank lines. This alternate syntax allows the use of
-// blank lines:
-//
-//   <command>[,<command>...] [arg | arg=val | arg=(val1, val2, ...)]...
-//   <input to the command>
-//   ----
-//   ----
-//   <expected results>
-//
-//   <more expected results>
-//   ----
-//   ----
-//
-// To execute data-driven tests, pass the path of the test file as well as a
-// function which can interpret and execute whatever commands are present in
-// the test file. The framework invokes the function, passing it information
-// about the test case in a TestData struct. The function then returns the
-// actual results of the case, which this function compares with the expected
-// results, and either succeeds or fails the test.
-func RunTest(t *testing.T, path string, f func(d *TestData) string) {
-	t.Helper()
-	file, err := os.OpenFile(path, os.O_RDWR, 0644 /* irrelevant */)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		_ = file.Close()
-	}()
-
-	runTestInternal(t, path, file, f, *rewriteTestFiles)
-}
-
-// RunTestFromString is a version of RunTest which takes the contents of a test
-// directly.
-func RunTestFromString(t *testing.T, input string, f func(d *TestData) string) {
-	t.Helper()
-	runTestInternal(t, "<string>" /* optionalPath */, strings.NewReader(input), f, *rewriteTestFiles)
-}
-
-func runTestInternal(
-	t *testing.T, sourceName string, reader io.Reader, f func(d *TestData) string, rewrite bool,
-) {
-	t.Helper()
-
-	r := newTestDataReader(t, sourceName, reader, rewrite)
-	for r.Next(t) {
-		d := &r.data
-		actual := func() string {
-			defer func() {
-				if r := recover(); r != nil {
-					fmt.Printf("\npanic during %s:\n%s\n", d.Pos, d.Input)
-					panic(r)
-				}
-			}()
-			s := f(d)
-			if n := len(s); n > 0 && s[n-1] != '\n' {
-				s += "\n"
-			}
-			return s
-		}()
-
-		if r.rewrite != nil {
-			r.emit("----")
-			if hasBlankLine(actual) {
-				r.emit("----")
-				r.rewrite.WriteString(actual)
-				r.emit("----")
-				r.emit("----")
-			} else {
-				r.emit(actual)
-			}
-		} else if d.Expected != actual {
-			t.Fatalf("\n%s: %s\nexpected:\n%s\nfound:\n%s", d.Pos, d.Input, d.Expected, actual)
-		} else if testing.Verbose() {
-			input := d.Input
-			if input == "" {
-				input = "<no input to command>"
-			}
-			// TODO(tbg): it's awkward to reproduce the args, but it would be helpful.
-			fmt.Printf("\n%s:\n%s [%d args]\n%s\n----\n%s", d.Pos, d.Cmd, len(d.CmdArgs), input, actual)
-		}
-	}
-
-	if r.rewrite != nil {
-		data := r.rewrite.Bytes()
-		if l := len(data); l > 2 && data[l-1] == '\n' && data[l-2] == '\n' {
-			data = data[:l-1]
-		}
-		if dest, ok := reader.(*os.File); ok {
-			if _, err := dest.WriteAt(data, 0); err != nil {
-				t.Fatal(err)
-			}
-			if err := dest.Truncate(int64(len(data))); err != nil {
-				t.Fatal(err)
-			}
-			if err := dest.Sync(); err != nil {
-				t.Fatal(err)
-			}
-		} else {
-			t.Logf("input is not a file; rewritten output is:\n%s", data)
-		}
-	}
-}
-
-// Walk goes through all the files in a subdirectory, creating subtests to match
-// the file hierarchy; for each "leaf" file, the given function is called.
-//
-// This can be used in conjunction with RunTest. For example:
-//
-//    datadriven.Walk(t, path, func (t *testing.T, path string) {
-//      // initialize per-test state
-//      datadriven.RunTest(t, path, func (d *datadriven.TestData) {
-//       // ...
-//      }
-//    }
-//
-//   Files:
-//     testdata/typing
-//     testdata/logprops/scan
-//     testdata/logprops/select
-//
-//   If path is "testdata/typing", the function is called once and no subtests
-//   care created.
-//
-//   If path is "testdata/logprops", the function is called two times, in
-//   separate subtests /scan, /select.
-//
-//   If path is "testdata", the function is called three times, in subtest
-//   hierarchy /typing, /logprops/scan, /logprops/select.
-//
-func Walk(t *testing.T, path string, f func(t *testing.T, path string)) {
-	finfo, err := os.Stat(path)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !finfo.IsDir() {
-		f(t, path)
-		return
-	}
-	files, err := os.ReadDir(path)
-	if err != nil {
-		t.Fatal(err)
-	}
-	for _, file := range files {
-		t.Run(file.Name(), func(t *testing.T) {
-			Walk(t, filepath.Join(path, file.Name()), f)
-		})
-	}
-}
-
-// TestData contains information about one data-driven test case that was
-// parsed from the test file.
-type TestData struct {
-	Pos string // reader and line number
-
-	// Cmd is the first string on the directive line (up to the first whitespace).
-	Cmd string
-
-	CmdArgs []CmdArg
-
-	Input    string
-	Expected string
-}
-
-// ScanArgs looks up the first CmdArg matching the given key and scans it into
-// the given destinations in order. If the arg does not exist, the number of
-// destinations does not match that of the arguments, or a destination can not
-// be populated from its matching value, a fatal error results.
-//
-// For example, for a TestData originating from
-//
-// cmd arg1=50 arg2=yoruba arg3=(50, 50, 50)
-//
-// the following would be valid:
-//
-// var i1, i2, i3, i4 int
-// var s string
-// td.ScanArgs(t, "arg1", &i1)
-// td.ScanArgs(t, "arg2", &s)
-// td.ScanArgs(t, "arg3", &i2, &i3, &i4)
-func (td *TestData) ScanArgs(t *testing.T, key string, dests ...interface{}) {
-	t.Helper()
-	arg := td.findArg(key)
-	if arg == nil {
-		t.Fatalf("missing argument: %s", key)
-	}
-	err := arg.scan(dests...)
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-// HasArg determines if `key` appears in CmdArgs.
-func (td *TestData) HasArg(key string) bool {
-	return td.findArg(key) != nil
-}
-
-func (td *TestData) findArg(key string) *CmdArg {
-	for i := range td.CmdArgs {
-		if td.CmdArgs[i].Key == key {
-			return &td.CmdArgs[i]
-		}
-	}
-	return nil
-}
-
-// CmdArg contains information about an argument on the directive line. An
-// argument is specified in one of the following forms:
-//  - argument
-//  - argument=value
-//  - argument=(values, ...)
-type CmdArg struct {
-	Key  string
-	Vals []string
-}
-
-func (arg CmdArg) String() string {
-	switch len(arg.Vals) {
-	case 0:
-		return arg.Key
-
-	case 1:
-		return fmt.Sprintf("%s=%s", arg.Key, arg.Vals[0])
-
-	default:
-		return fmt.Sprintf("%s=(%s)", arg.Key, strings.Join(arg.Vals, ", "))
-	}
-}
-
-func (arg CmdArg) scan(dests ...interface{}) error {
-	if len(dests) != len(arg.Vals) {
-		return errors.Errorf("%s: got %d destinations, but %d values", arg.Key, len(dests), len(arg.Vals))
-	}
-
-	for i := range dests {
-		val := arg.Vals[i]
-		switch dest := dests[i].(type) {
-		case *string:
-			*dest = val
-		case *int:
-			n, err := strconv.ParseInt(val, 10, 64)
-			if err != nil {
-				return err
-			}
-			*dest = int(n) // assume 64bit ints
-		case *uint64:
-			n, err := strconv.ParseUint(val, 10, 64)
-			if err != nil {
-				return err
-			}
-			*dest = n
-		case *bool:
-			b, err := strconv.ParseBool(val)
-			if err != nil {
-				return err
-			}
-			*dest = b
-		default:
-			return errors.Errorf("unsupported type %T for destination #%d (might be easy to add it)", dest, i+1)
-		}
-	}
-	return nil
-}
-
-// Fatalf wraps a fatal testing error with test file position information, so
-// that it's easy to locate the source of the error.
-func (td TestData) Fatalf(tb testing.TB, format string, args ...interface{}) {
-	tb.Helper()
-	tb.Fatalf("%s: %s", td.Pos, fmt.Sprintf(format, args...))
-}
-
-func hasBlankLine(s string) bool {
-	scanner := bufio.NewScanner(strings.NewReader(s))
-	for scanner.Scan() {
-		if strings.TrimSpace(scanner.Text()) == "" {
-			return true
-		}
-	}
-	return false
-}
diff --git a/internal/datadriven/line_scanner.go b/internal/datadriven/line_scanner.go
deleted file mode 100644
index 67681dcf..00000000
--- a/internal/datadriven/line_scanner.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The Cockroach Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-// implied. See the License for the specific language governing
-// permissions and limitations under the License.
-
-package datadriven
-
-import (
-	"bufio"
-	"io"
-)
-
-type lineScanner struct {
-	*bufio.Scanner
-	line int
-}
-
-func newLineScanner(r io.Reader) *lineScanner {
-	return &lineScanner{
-		Scanner: bufio.NewScanner(r),
-		line:    0,
-	}
-}
-
-func (l *lineScanner) Scan() bool {
-	ok := l.Scanner.Scan()
-	if ok {
-		l.line++
-	}
-	return ok
-}
diff --git a/internal/manual/manual_64bit.go b/internal/manual/manual_64bit.go
index 5f3793cb..8c08232b 100644
--- a/internal/manual/manual_64bit.go
+++ b/internal/manual/manual_64bit.go
@@ -2,8 +2,8 @@
 // of this source code is governed by a BSD-style license that can be found in
 // the LICENSE file.
 
-//go:build amd64 || arm64 || arm64be || ppc64 || ppc64le || mips64 || mips64le || s390x || sparc64
-// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64
+//go:build amd64 || arm64 || arm64be || ppc64 || ppc64le || mips64 || mips64le || s390x || sparc64 || riscv64
+// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64 riscv64
 
 package manual
 
diff --git a/internal/rawalloc/rawalloc_64bit.go b/internal/rawalloc/rawalloc_64bit.go
index ea1d952f..c97ba336 100644
--- a/internal/rawalloc/rawalloc_64bit.go
+++ b/internal/rawalloc/rawalloc_64bit.go
@@ -12,8 +12,8 @@
 // implied. See the License for the specific language governing
 // permissions and limitations under the License.
 
-//go:build amd64 || arm64 || arm64be || ppc64 || ppc64le || mips64 || mips64le || s390x || sparc64
-// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64
+//go:build amd64 || arm64 || arm64be || ppc64 || ppc64le || mips64 || mips64le || s390x || sparc64 || riscv64
+// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64 riscv64
 
 package rawalloc
 
diff --git a/merging_iter.go b/merging_iter.go
index 60bd2ea6..f72b7a18 100644
--- a/merging_iter.go
+++ b/merging_iter.go
@@ -659,15 +659,48 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
 				if l.largestUserKey != nil && m.heap.cmp(l.largestUserKey, seekKey) < 0 {
 					seekKey = l.largestUserKey
 				}
-				// This seek is not directly due to a SeekGE call, so we don't
-				// know enough about the underlying iterator positions, and so
-				// we keep the try-seek-using-next optimization disabled.
+				// This seek is not directly due to a SeekGE call, so we don't know
+				// enough about the underlying iterator positions, and so we keep the
+				// try-seek-using-next optimization disabled. Additionally, if we're in
+				// prefix-seek mode and a re-seek would have moved us past the original
+				// prefix, we can remove all merging iter levels below the rangedel
+				// tombstone's level and return immediately instead of re-seeking. This
+				// is correct since those levels cannot provide a key that matches the
+				// prefix, and is also visible. Additionally, this is important to make
+				// subsequent `TrySeekUsingNext` work correctly, as a re-seek on a
+				// different prefix could have resulted in this iterator skipping visible
+				// keys at prefixes in between m.prefix and seekKey, that are currently
+				// not in the heap due to a bloom filter mismatch.
 				//
 				// Additionally, we set the relative-seek flag. This is
 				// important when iterating with lazy combined iteration. If
 				// there's a range key between this level's current file and the
 				// file the seek will land on, we need to detect it in order to
 				// trigger construction of the combined iterator.
+				if m.prefix != nil {
+					if n := m.split(seekKey); !bytes.Equal(m.prefix, seekKey[:n]) {
+						for i := item.index; i < len(m.levels); i++ {
+							// Remove this level from the heap. Setting iterKey and iterValue
+							// to their zero values should be sufficient for initMinHeap to not
+							// re-initialize the heap with them in it. Other fields in
+							// mergingIterLevel can remain as-is; the iter/rangeDelIter needs
+							// to stay intact for future trySeekUsingNexts to work, the level
+							// iter boundary context is owned by the levelIter which is not
+							// being repositioned, and any tombstones in these levels will be
+							// irrelevant for us anyway.
+							m.levels[i].iterKey = nil
+							m.levels[i].iterValue = base.LazyValue{}
+						}
+						// TODO(bilal): Consider a more efficient way of removing levels from
+						// the heap without reinitializing all of it. This would likely
+						// necessitate tracking the heap positions of each mergingIterHeap
+						// item in the mergingIterLevel, and then swapping that item in the
+						// heap with the last-positioned heap item, and shrinking the heap by
+						// one.
+						m.initMinHeap()
+						return true
+					}
+				}
 				m.seekGE(seekKey, item.index, base.SeekGEFlagsNone.EnableRelativeSeek())
 				return true
 			}
@@ -682,45 +715,33 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
 
 // Starting from the current entry, finds the first (next) entry that can be returned.
 func (m *mergingIter) findNextEntry() (*InternalKey, base.LazyValue) {
-	var reseeked bool
 	for m.heap.len() > 0 && m.err == nil {
 		item := &m.heap.items[0]
 		if m.levels[item.index].isSyntheticIterBoundsKey {
 			break
 		}
 		// For prefix iteration, stop if we've already exceeded the iterator's
-		// current prefix. There are two cases where we perform this check.
-		//
-		// 1. We already re-seeked the iterator due to a range tombstone. We
-		//    could amortize the cost of this comparison, by doing it only after
-		//    we have iterated in this for loop a few times. But unless we find
-		//    a performance benefit to that, we do the simple thing and compare
-		//    each time. Note that isNextEntryDeleted already did at least 4 key
-		//    comparisons in order to return true, and additionally at least one
-		//    heap comparison to step to the next entry.
+		// current prefix. There is one case where we perform this check:
 		//
-		//    Note that we cannot move this comparison into the
-		//    isNextEntryDeleted branch. Once isNextEntryDeleted determines a
-		//    key is deleted and seeks the level's iterator, item.key's memory
-		//    is potentially invalid. If the iterator is now exhausted, item.key
-		//    may be garbage.
+		// If the heap root is an ignorable boundary key, Next-ing the iterator will
+		// close the associated file and open the next file in the level. We don't
+		// want to do this unnecessarily because the 'exhausted' file may only appear
+		// exhausted because it failed a bloom filter check. Avoiding Next-ing the
+		// file has a performance benefit of unnecessarily advancing to the next
+		// file, but it's also necessary for correctness of some TrySeekUsingNext
+		// optimizations deeper in the iterator stack:
 		//
-		// 2. If the heap root is an ignorable boundary key, Next-ing the
-		//    iterator will close the associated file and open the next file in
-		//    the level. We don't want to do this unnecessarily because the
-		//    'exhausted' file may only appear exhausted because it failed a
-		//    bloom filter check. Avoiding Next-ing the file has a performance
-		//    benefit of unnecessarily advancing to the next file, but it's also
-		//    necessary for correctness of some TrySeekUsingNext optimizations
-		//    deeper in the iterator stack:
+		// There may exist valid keys between our current prefix (m.prefix) and the
+		// ignorable boundary key (item.key) that were not returned due to the bloom
+		// filter test. A subsequent SeekPrefixGE call with a seek key in the range
+		// (m.prefix - item.key) will have the TrySeekUsingNext flag enabled. The
+		// levelIter relies on having not skipped these keys in order to avoid more
+		// expensive full seeks.
 		//
-		//    There may exist valid keys between our current prefix (m.prefix)
-		//    and the ignorable boundary key (item.key) that were not returned
-		//    due to the bloom filter test. A subsequent SeekPrefixGE call with
-		//    a seek key in the range (m.prefix - item.key) will have the
-		//    TrySeekUsingNext flag enabled. The levelIter relies on having not
-		//    skipped these keys in order to avoid more expensive full seeks.
-		if m.prefix != nil && (reseeked || m.levels[item.index].isIgnorableBoundaryKey) {
+		// While isNextEntryDeleted could re-seek the iterator, that function is
+		// expected to peek at m.prefix and avoid any seeks past the iterator's
+		// prefix if it is set, avoiding the need to do a check here.
+		if m.prefix != nil && m.levels[item.index].isIgnorableBoundaryKey {
 			if n := m.split(item.key.UserKey); !bytes.Equal(m.prefix, item.key.UserKey[:n]) {
 				return nil, base.LazyValue{}
 			}
@@ -729,7 +750,6 @@ func (m *mergingIter) findNextEntry() (*InternalKey, base.LazyValue) {
 		m.addItemStats(item)
 		if m.isNextEntryDeleted(item) {
 			m.stats.PointsCoveredByRangeTombstones++
-			reseeked = true
 			continue
 		}
 		if item.key.Visible(m.snapshot, m.batchSnapshot) &&
diff --git a/sstable/reader.go b/sstable/reader.go
index e5cc3ca5..a556d778 100644
--- a/sstable/reader.go
+++ b/sstable/reader.go
@@ -2897,12 +2897,6 @@ func (r *Reader) readBlock(
 						raState.sequentialFile = f
 						file = f
 					}
-
-					// If we tried to load a table that doesn't exist, panic
-					// immediately.  Something is seriously wrong if a table
-					// doesn't exist.
-					// See cockroachdb/cockroach#56490.
-					base.MustExist(r.fs, r.filename, panicFataler{}, err)
 				}
 			}
 			if raState.sequentialFile == nil {
@@ -3785,9 +3779,3 @@ func (l *Layout) Describe(
 	last := blocks[len(blocks)-1]
 	fmt.Fprintf(w, "%10d  EOF\n", last.Offset+last.Length)
 }
-
-type panicFataler struct{}
-
-func (panicFataler) Fatalf(format string, args ...interface{}) {
-	panic(errors.Errorf(format, args...))
-}
diff --git a/vfs/clone.go b/vfs/clone.go
index 900d7873..5d6edf2d 100644
--- a/vfs/clone.go
+++ b/vfs/clone.go
@@ -12,8 +12,9 @@ import (
 )
 
 type cloneOpts struct {
-	skip func(string) bool
-	sync bool
+	skip    func(string) bool
+	sync    bool
+	tryLink bool
 }
 
 // A CloneOption configures the behavior of Clone.
@@ -28,6 +29,12 @@ func CloneSkip(fn func(string) bool) CloneOption {
 // CloneSync configures Clone to sync files and directories.
 var CloneSync CloneOption = func(o *cloneOpts) { o.sync = true }
 
+// CloneTryLink configures Clone to link files to the destination if the source and
+// destination filesystems are the same. If the source and destination
+// filesystems are not the same or the filesystem does not support linking, then
+// Clone falls back to copying.
+var CloneTryLink CloneOption = func(o *cloneOpts) { o.tryLink = true }
+
 // Clone recursively copies a directory structure from srcFS to dstFS. srcPath
 // specifies the path in srcFS to copy from and must be compatible with the
 // srcFS path format. dstDir is the target directory in dstFS and must be
@@ -49,6 +56,7 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
 		}
 		return false, err
 	}
+	defer srcFile.Close()
 
 	stat, err := srcFile.Stat()
 	if err != nil {
@@ -56,9 +64,6 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
 	}
 
 	if stat.IsDir() {
-		if err := srcFile.Close(); err != nil {
-			return false, err
-		}
 		if err := dstFS.MkdirAll(dstPath, 0755); err != nil {
 			return false, err
 		}
@@ -94,13 +99,25 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
 		return true, nil
 	}
 
+	// If the source and destination filesystems are the same and the user
+	// specified they'd prefer to link if possible, try to use a hardlink,
+	// falling back to copying if it fails.
+	if srcFS == dstFS && o.tryLink {
+		if err := LinkOrCopy(srcFS, srcPath, dstPath); oserror.IsNotExist(err) {
+			// Clone's semantics are such that it returns (false,nil) if the
+			// source does not exist.
+			return false, nil
+		} else if err != nil {
+			return false, err
+		} else {
+			return true, nil
+		}
+	}
+
 	data, err := io.ReadAll(srcFile)
 	if err != nil {
 		return false, err
 	}
-	if err := srcFile.Close(); err != nil {
-		return false, err
-	}
 	dstFile, err := dstFS.Create(dstPath)
 	if err != nil {
 		return false, err
@@ -113,7 +130,6 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
 			return false, err
 		}
 	}
-
 	if err := dstFile.Close(); err != nil {
 		return false, err
 	}
diff --git a/vfs/vfs.go b/vfs/vfs.go
index 70b533aa..5e0571b9 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -274,7 +274,7 @@ func Copy(fs FS, oldname, newname string) error {
 // CopyAcrossFS copies the contents of oldname on srcFS to newname dstFS. If
 // newname exists, it will be overwritten.
 func CopyAcrossFS(srcFS FS, oldname string, dstFS FS, newname string) error {
-	src, err := srcFS.Open(oldname)
+	src, err := srcFS.Open(oldname, SequentialReadsOption)
 	if err != nil {
 		return err
 	}
@@ -295,7 +295,7 @@ func CopyAcrossFS(srcFS FS, oldname string, dstFS FS, newname string) error {
 // LimitedCopy copies up to maxBytes from oldname to newname. If newname
 // exists, it will be overwritten.
 func LimitedCopy(fs FS, oldname, newname string, maxBytes int64) error {
-	src, err := fs.Open(oldname)
+	src, err := fs.Open(oldname, SequentialReadsOption)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/batch_test.go b/batch_test.go
index 303b6809..5c990222 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -16,10 +16,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/batchskl"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/vfs"
@@ -398,7 +398,7 @@ func TestIndexedBatchMutation(t *testing.T) {
 		}
 	}()
 
-	datadriven.RunTest(t, "testdata/indexed_batch_mutation", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/indexed_batch_mutation", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "batch":
 			writeBatch := newBatch(d)
@@ -676,7 +676,7 @@ func TestBatchGet(t *testing.T) {
 			defer d.Close()
 			var b *Batch
 
-			datadriven.RunTest(t, "testdata/batch_get", func(td *datadriven.TestData) string {
+			datadriven.RunTest(t, "testdata/batch_get", func(t *testing.T, td *datadriven.TestData) string {
 				switch td.Cmd {
 				case "define":
 					switch c.method {
@@ -731,7 +731,7 @@ func TestBatchIter(t *testing.T) {
 		for _, testdata := range []string{
 			"testdata/internal_iter_next", "testdata/internal_iter_bounds"} {
 			t.Run(method, func(t *testing.T) {
-				datadriven.RunTest(t, testdata, func(d *datadriven.TestData) string {
+				datadriven.RunTest(t, testdata, func(t *testing.T, d *datadriven.TestData) string {
 					switch d.Cmd {
 					case "define":
 						switch method {
@@ -792,7 +792,7 @@ func TestBatchIter(t *testing.T) {
 func TestBatchRangeOps(t *testing.T) {
 	var b *Batch
 
-	datadriven.RunTest(t, "testdata/batch_range_ops", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/batch_range_ops", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "clear":
 			b = nil
@@ -883,7 +883,7 @@ func TestBatchTooLarge(t *testing.T) {
 
 func TestFlushableBatchIter(t *testing.T) {
 	var b *flushableBatch
-	datadriven.RunTest(t, "testdata/internal_iter_next", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/internal_iter_next", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			batch := newBatch(nil)
@@ -909,7 +909,7 @@ func TestFlushableBatchIter(t *testing.T) {
 
 func TestFlushableBatch(t *testing.T) {
 	var b *flushableBatch
-	datadriven.RunTest(t, "testdata/flushable_batch", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/flushable_batch", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			batch := newBatch(nil)
@@ -995,7 +995,7 @@ func TestFlushableBatchDeleteRange(t *testing.T) {
 	var fb *flushableBatch
 	var input string
 
-	datadriven.RunTest(t, "testdata/delete_range", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/delete_range", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "clear":
 			input = ""
diff --git a/checkpoint_test.go b/checkpoint_test.go
index 54f68c9d..e8c7d88e 100644
--- a/checkpoint_test.go
+++ b/checkpoint_test.go
@@ -12,8 +12,8 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
 )
@@ -34,7 +34,7 @@ func TestCheckpoint(t *testing.T) {
 		L0CompactionThreshold: 10,
 	}
 
-	datadriven.RunTest(t, "testdata/checkpoint", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/checkpoint", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "batch":
 			if len(td.CmdArgs) != 1 {
diff --git a/cleaner_test.go b/cleaner_test.go
index c953fc0f..a7784923 100644
--- a/cleaner_test.go
+++ b/cleaner_test.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
 )
@@ -31,7 +31,7 @@ func TestArchiveCleaner(t *testing.T) {
 		WALDir:  "wal",
 	}
 
-	datadriven.RunTest(t, "testdata/cleaner", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/cleaner", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "batch":
 			if len(td.CmdArgs) != 1 {
diff --git a/compaction_iter_test.go b/compaction_iter_test.go
index ede349bb..e68c672d 100644
--- a/compaction_iter_test.go
+++ b/compaction_iter_test.go
@@ -13,8 +13,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/rangekey"
 )
@@ -135,7 +135,7 @@ func TestCompactionIter(t *testing.T) {
 	}
 
 	runTest := func(t *testing.T, formatVersion FormatMajorVersion) {
-		datadriven.RunTest(t, fileFunc(formatVersion), func(d *datadriven.TestData) string {
+		datadriven.RunTest(t, fileFunc(formatVersion), func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				merge = nil
diff --git a/compaction_picker_test.go b/compaction_picker_test.go
index aaa2693b..1ac45224 100644
--- a/compaction_picker_test.go
+++ b/compaction_picker_test.go
@@ -13,9 +13,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/stretchr/testify/require"
 )
@@ -92,7 +92,7 @@ func diskAvailBytesInf() uint64 {
 
 func TestCompactionPickerByScoreLevelMaxBytes(t *testing.T) {
 	datadriven.RunTest(t, "testdata/compaction_picker_level_max_bytes",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "init":
 				vers, opts, sizes, errMsg := loadVersion(d)
@@ -154,7 +154,7 @@ func TestCompactionPickerTargetLevel(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_picker_target_level",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "init":
 				var errMsg string
@@ -322,7 +322,7 @@ func TestCompactionPickerTargetLevel(t *testing.T) {
 
 func TestCompactionPickerEstimatedCompactionDebt(t *testing.T) {
 	datadriven.RunTest(t, "testdata/compaction_picker_estimated_debt",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "init":
 				vers, opts, sizes, errMsg := loadVersion(d)
@@ -371,7 +371,7 @@ func TestCompactionPickerL0(t *testing.T) {
 	var inProgressCompactions []compactionInfo
 	var pc *pickedCompaction
 
-	datadriven.RunTest(t, "testdata/compaction_picker_L0", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_picker_L0", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			fileMetas := [manifest.NumLevels][]*fileMetadata{}
@@ -611,7 +611,7 @@ func TestCompactionPickerConcurrency(t *testing.T) {
 	var picker *compactionPickerByScore
 	var inProgressCompactions []compactionInfo
 
-	datadriven.RunTest(t, "testdata/compaction_picker_concurrency", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_picker_concurrency", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			fileMetas := [manifest.NumLevels][]*fileMetadata{}
@@ -825,7 +825,7 @@ func TestCompactionPickerPickReadTriggered(t *testing.T) {
 		return m, nil
 	}
 
-	datadriven.RunTest(t, "testdata/compaction_picker_read_triggered", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_picker_read_triggered", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			rcList = readCompactionQueue{}
@@ -974,7 +974,7 @@ func TestPickedCompactionSetupInputs(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_setup_inputs",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "setup-inputs":
 				var availBytes uint64 = math.MaxUint64
@@ -1083,7 +1083,7 @@ func TestPickedCompactionExpandInputs(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_expand_inputs",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				files = nil
@@ -1185,7 +1185,7 @@ func TestCompactionOutputFileSize(t *testing.T) {
 		return m, nil
 	}
 
-	datadriven.RunTest(t, "testdata/compaction_output_file_size", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_output_file_size", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			fileMetas := [manifest.NumLevels][]*fileMetadata{}
diff --git a/compaction_test.go b/compaction_test.go
index 162c6243..9d668394 100644
--- a/compaction_test.go
+++ b/compaction_test.go
@@ -21,10 +21,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/errors/oserror"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/manifest"
@@ -830,7 +830,7 @@ func TestElideRangeTombstone(t *testing.T) {
 }
 
 func TestCompactionTransform(t *testing.T) {
-	datadriven.RunTest(t, "testdata/compaction_transform", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_transform", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "transform":
 			var snapshots []uint64
@@ -1324,7 +1324,7 @@ func TestManualCompaction(t *testing.T) {
 	runTest := func(t *testing.T, testData string, minVersion, maxVersion FormatMajorVersion, verbose bool) {
 		reset(minVersion, maxVersion)
 		var ongoingCompaction *compaction
-		datadriven.RunTest(t, testData, func(td *datadriven.TestData) string {
+		datadriven.RunTest(t, testData, func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "reset":
 				reset(minVersion, maxVersion)
@@ -1597,7 +1597,7 @@ func TestCompactionFindGrandparentLimit(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_find_grandparent_limit",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				grandparents = nil
@@ -1702,7 +1702,7 @@ func TestCompactionFindL0Limit(t *testing.T) {
 	flushSplitBytes := int64(0)
 
 	datadriven.RunTest(t, "testdata/compaction_find_l0_limit",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				fileMetas := [manifest.NumLevels][]*fileMetadata{}
@@ -1790,7 +1790,7 @@ func TestCompactionOutputLevel(t *testing.T) {
 	version := &version{}
 
 	datadriven.RunTest(t, "testdata/compaction_output_level",
-		func(d *datadriven.TestData) (res string) {
+		func(t *testing.T, d *datadriven.TestData) (res string) {
 			defer func() {
 				if r := recover(); r != nil {
 					res = fmt.Sprintln(r)
@@ -1831,7 +1831,7 @@ func TestCompactionAtomicUnitBounds(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_atomic_unit_bounds",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				files = manifest.LevelSlice{}
@@ -1944,7 +1944,7 @@ func TestCompactionDeleteOnlyHints(t *testing.T) {
 	var err error
 	var opts *Options
 	datadriven.RunTest(t, "testdata/compaction_delete_only_hints",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "define":
 				opts, err = reset()
@@ -2163,7 +2163,7 @@ func TestCompactionTombstones(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_tombstones",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "define":
 				if d != nil {
@@ -2278,7 +2278,7 @@ func TestCompactionReadTriggeredQueue(t *testing.T) {
 	var queue *readCompactionQueue
 
 	datadriven.RunTest(t, "testdata/read_compaction_queue",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "create":
 				queue = &readCompactionQueue{}
@@ -2372,7 +2372,7 @@ func TestCompactionReadTriggered(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_read_triggered",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "define":
 				if d != nil {
@@ -2491,7 +2491,7 @@ func TestCompactionInuseKeyRanges(t *testing.T) {
 	opts := (*Options)(nil).EnsureDefaults()
 
 	var c *compaction
-	datadriven.RunTest(t, "testdata/compaction_inuse_key_ranges", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compaction_inuse_key_ranges", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			c = &compaction{
@@ -2701,7 +2701,7 @@ func TestCompactionAllowZeroSeqNum(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_allow_zero_seqnum",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "define":
 				if d != nil {
@@ -2806,7 +2806,7 @@ func TestCompactionErrorOnUserKeyOverlap(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_error_on_user_key_overlap",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "error-on-user-key-overlap":
 				c := &compaction{
@@ -2935,7 +2935,7 @@ func TestCompactionCheckOrdering(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_check_ordering",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "check-ordering":
 				c := &compaction{
@@ -3072,7 +3072,7 @@ func TestCompactionOutputSplitters(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/compaction_output_splitters",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "reset":
 				main = nil
@@ -3679,7 +3679,7 @@ func TestMarkedForCompaction(t *testing.T) {
 		d, err = Open("", opts)
 		require.NoError(t, err)
 	}
-	datadriven.RunTest(t, "testdata/marked_for_compaction", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/marked_for_compaction", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			reset()
diff --git a/data_test.go b/data_test.go
index 97382fa1..a39b4ca8 100644
--- a/data_test.go
+++ b/data_test.go
@@ -14,9 +14,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/rangedel"
 	"github.com/cockroachdb/pebble/internal/rangekey"
diff --git a/event_listener_test.go b/event_listener_test.go
index 5a90a39e..72907bbe 100644
--- a/event_listener_test.go
+++ b/event_listener_test.go
@@ -16,9 +16,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/sstable"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/cockroachdb/redact"
@@ -138,7 +138,7 @@ func TestEventListener(t *testing.T) {
 	mem := vfs.NewMem()
 	require.NoError(t, mem.MkdirAll("ext", 0755))
 
-	datadriven.RunTest(t, "testdata/event_listener", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/event_listener", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "open":
 			buf.Reset()
diff --git a/external_iterator_test.go b/external_iterator_test.go
index 967abbe4..ab353627 100644
--- a/external_iterator_test.go
+++ b/external_iterator_test.go
@@ -11,10 +11,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/cache"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/sstable"
 	"github.com/cockroachdb/pebble/vfs"
@@ -34,7 +34,7 @@ func TestExternalIterator(t *testing.T) {
 	require.NoError(t, err)
 	defer func() { require.NoError(t, d.Close()) }()
 
-	datadriven.RunTest(t, "testdata/external_iterator", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/external_iterator", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			mem = vfs.NewMem()
@@ -87,7 +87,7 @@ func TestSimpleLevelIter(t *testing.T) {
 	require.NoError(t, err)
 	defer func() { require.NoError(t, d.Close()) }()
 
-	datadriven.RunTest(t, "testdata/simple_level_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/simple_level_iter", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			mem = vfs.NewMem()
diff --git a/flush_test.go b/flush_test.go
index 209ce29d..edbf1a45 100644
--- a/flush_test.go
+++ b/flush_test.go
@@ -9,8 +9,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
 )
@@ -30,7 +30,7 @@ func TestManualFlush(t *testing.T) {
 		require.NoError(t, d.Close())
 	}()
 
-	datadriven.RunTest(t, "testdata/manual_flush", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/manual_flush", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "batch":
 			b := d.NewBatch()
diff --git a/format_major_version_test.go b/format_major_version_test.go
index dba2a828..24edc847 100644
--- a/format_major_version_test.go
+++ b/format_major_version_test.go
@@ -12,9 +12,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/sstable"
 	"github.com/cockroachdb/pebble/vfs"
@@ -246,7 +246,7 @@ func TestSplitUserKeyMigration(t *testing.T) {
 	}()
 
 	datadriven.RunTest(t, "testdata/format_major_version_split_user_key_migration",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "define":
 				if d != nil {
@@ -347,7 +347,7 @@ func TestPebblev1Migration(t *testing.T) {
 	}()
 
 	datadriven.RunTest(t, "testdata/format_major_version_pebblev1_migration",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch cmd := td.Cmd; cmd {
 			case "open":
 				var version int
diff --git a/ingest_test.go b/ingest_test.go
index 692fe09a..3f3b4fa0 100644
--- a/ingest_test.go
+++ b/ingest_test.go
@@ -16,10 +16,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/errors/oserror"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/rangekey"
@@ -33,7 +33,7 @@ import (
 
 func TestSSTableKeyCompare(t *testing.T) {
 	var buf bytes.Buffer
-	datadriven.RunTest(t, "testdata/sstable_key_compare", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/sstable_key_compare", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "cmp":
 			buf.Reset()
@@ -63,7 +63,7 @@ func TestSSTableKeyCompare(t *testing.T) {
 func TestIngestLoad(t *testing.T) {
 	mem := vfs.NewMem()
 
-	datadriven.RunTest(t, "testdata/ingest_load", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/ingest_load", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "load":
 			writerOpts := sstable.WriterOptions{}
@@ -236,7 +236,7 @@ func TestIngestSortAndVerify(t *testing.T) {
 	}
 
 	t.Run("", func(t *testing.T) {
-		datadriven.RunTest(t, "testdata/ingest_sort_and_verify", func(d *datadriven.TestData) string {
+		datadriven.RunTest(t, "testdata/ingest_sort_and_verify", func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "ingest":
 				var buf bytes.Buffer
@@ -432,7 +432,7 @@ func TestIngestMemtableOverlaps(t *testing.T) {
 				return meta
 			}
 
-			datadriven.RunTest(t, "testdata/ingest_memtable_overlaps", func(d *datadriven.TestData) string {
+			datadriven.RunTest(t, "testdata/ingest_memtable_overlaps", func(t *testing.T, d *datadriven.TestData) string {
 				switch d.Cmd {
 				case "define":
 					b := newBatch(nil)
@@ -559,7 +559,7 @@ func TestIngestTargetLevel(t *testing.T) {
 		return m
 	}
 
-	datadriven.RunTest(t, "testdata/ingest_target_level", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/ingest_target_level", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			if d != nil {
@@ -653,7 +653,7 @@ func TestIngest(t *testing.T) {
 	}
 	reset()
 
-	datadriven.RunTest(t, "testdata/ingest", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/ingest", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			reset()
@@ -1365,7 +1365,7 @@ func TestIngest_UpdateSequenceNumber(t *testing.T) {
 		err    error
 		metas  []*fileMetadata
 	)
-	datadriven.RunTest(t, "testdata/ingest_update_seqnums", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/ingest_update_seqnums", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "starting-seqnum":
 			seqnum, err = strconv.ParseUint(td.Input, 10, 64)
diff --git a/internal/datadriven/datadriven_test.go b/internal/datadriven/datadriven_test.go
deleted file mode 100644
index dcbccec0..00000000
--- a/internal/datadriven/datadriven_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 The Cockroach Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-// implied. See the License for the specific language governing
-// permissions and limitations under the License.
-
-package datadriven
-
-import (
-	"fmt"
-	"testing"
-)
-
-func TestDataDriven(t *testing.T) {
-	input := `
-# NB: we allow duplicate args. It's unclear at this time whether this is useful,
-# either way, ScanArgs simply picks the first occurrence.
-make argTuple=(1, 🍌) argInt=12 argString=greedily argString=totally_ignored
-sentence
-----
-Did the following: make sentence
-1 hungry monkey eats a 🍌
-while 12 other monkeys watch greedily
-`
-
-	RunTestFromString(t, input, func(d *TestData) string {
-		var one int
-		var twelve int
-		var banana string
-		var greedily string
-		d.ScanArgs(t, "argTuple", &one, &banana)
-		d.ScanArgs(t, "argInt", &twelve)
-		d.ScanArgs(t, "argString", &greedily)
-		return fmt.Sprintf("Did the following: %s %s\n%d hungry monkey eats a %s\nwhile %d other monkeys watch %s\n",
-			d.Cmd, d.Input, one, banana, twelve, greedily,
-		)
-	})
-}
diff --git a/internal/datadriven/test_data_reader.go b/internal/datadriven/test_data_reader.go
deleted file mode 100644
index 39a7ce5d..00000000
--- a/internal/datadriven/test_data_reader.go
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2018 The Cockroach Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-// implied. See the License for the specific language governing
-// permissions and limitations under the License.
-
-package datadriven
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"regexp"
-	"strings"
-	"testing"
-)
-
-type testDataReader struct {
-	sourceName string
-	reader     io.Reader
-	scanner    *lineScanner
-	data       TestData
-	rewrite    *bytes.Buffer
-}
-
-func newTestDataReader(
-	t *testing.T, sourceName string, file io.Reader, record bool,
-) *testDataReader {
-	t.Helper()
-
-	var rewrite *bytes.Buffer
-	if record {
-		rewrite = &bytes.Buffer{}
-	}
-	return &testDataReader{
-		sourceName: sourceName,
-		reader:     file,
-		scanner:    newLineScanner(file),
-		rewrite:    rewrite,
-	}
-}
-
-func (r *testDataReader) Next(t *testing.T) bool {
-	t.Helper()
-
-	r.data = TestData{}
-	for r.scanner.Scan() {
-		line := r.scanner.Text()
-		r.emit(line)
-
-		line = strings.TrimSpace(line)
-		if strings.HasPrefix(line, "#") {
-			// Skip comment lines.
-			continue
-		}
-		// Support wrapping directive lines using \, for example:
-		//   build-scalar \
-		//   vars(int)
-		for strings.HasSuffix(line, `\`) && r.scanner.Scan() {
-			nextLine := r.scanner.Text()
-			r.emit(nextLine)
-			line = strings.TrimSuffix(line, `\`) + " " + strings.TrimSpace(nextLine)
-		}
-
-		fields := splitDirectives(t, line)
-		if len(fields) == 0 {
-			continue
-		}
-		cmd := fields[0]
-		r.data.Pos = fmt.Sprintf("%s:%d", r.sourceName, r.scanner.line)
-		r.data.Cmd = cmd
-
-		for _, arg := range fields[1:] {
-			key := arg
-			var vals []string
-			if pos := strings.IndexByte(key, '='); pos >= 0 {
-				key = arg[:pos]
-				val := arg[pos+1:]
-
-				if len(val) > 2 && val[0] == '(' && val[len(val)-1] == ')' {
-					vals = strings.Split(val[1:len(val)-1], ",")
-					for i := range vals {
-						vals[i] = strings.TrimSpace(vals[i])
-					}
-				} else {
-					vals = []string{val}
-				}
-			}
-			r.data.CmdArgs = append(r.data.CmdArgs, CmdArg{Key: key, Vals: vals})
-		}
-
-		var buf bytes.Buffer
-		var separator bool
-		for r.scanner.Scan() {
-			line := r.scanner.Text()
-			if line == "----" {
-				separator = true
-				break
-			}
-
-			r.emit(line)
-			fmt.Fprintln(&buf, line)
-		}
-
-		r.data.Input = strings.TrimSpace(buf.String())
-
-		if separator {
-			r.readExpected()
-		}
-		return true
-	}
-	return false
-}
-
-func (r *testDataReader) readExpected() {
-	var buf bytes.Buffer
-	var line string
-	var allowBlankLines bool
-
-	if r.scanner.Scan() {
-		line = r.scanner.Text()
-		if line == "----" {
-			allowBlankLines = true
-		}
-	}
-
-	if allowBlankLines {
-		// Look for two successive lines of "----" before terminating.
-		for r.scanner.Scan() {
-			line = r.scanner.Text()
-
-			if line == "----" {
-				if r.scanner.Scan() {
-					line2 := r.scanner.Text()
-					if line2 == "----" {
-						break
-					}
-
-					fmt.Fprintln(&buf, line)
-					fmt.Fprintln(&buf, line2)
-					continue
-				}
-			}
-
-			fmt.Fprintln(&buf, line)
-		}
-	} else {
-		// Terminate on first blank line.
-		for {
-			if strings.TrimSpace(line) == "" {
-				break
-			}
-
-			fmt.Fprintln(&buf, line)
-
-			if !r.scanner.Scan() {
-				break
-			}
-
-			line = r.scanner.Text()
-		}
-	}
-
-	r.data.Expected = buf.String()
-}
-
-func (r *testDataReader) emit(s string) {
-	if r.rewrite != nil {
-		r.rewrite.WriteString(s)
-		r.rewrite.WriteString("\n")
-	}
-}
-
-var splitDirectivesRE = regexp.MustCompile(`^ *[a-zA-Z0-9_\/,-\.]+(|=[-a-zA-Z0-9_@]+|=\([^)]*\))( |$)`)
-
-// splits a directive line into tokens, where each token is
-// either:
-//  - a,list,of,things
-//  - argument
-//  - argument=value
-//  - argument=(values, ...)
-func splitDirectives(t *testing.T, line string) []string {
-	var res []string
-
-	for line != "" {
-		str := splitDirectivesRE.FindString(line)
-		if len(str) == 0 {
-			t.Fatalf("cannot parse directive %s\n", line)
-		}
-		res = append(res, strings.TrimSpace(line[0:len(str)]))
-		line = line[len(str):]
-	}
-	return res
-}
diff --git a/internal/keyspan/bounded_test.go b/internal/keyspan/bounded_test.go
index ba1e3d06..71218828 100644
--- a/internal/keyspan/bounded_test.go
+++ b/internal/keyspan/bounded_test.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/stretchr/testify/require"
 )
@@ -38,7 +38,7 @@ func TestBoundedIter(t *testing.T) {
 	var iter BoundedIter
 	var hasPrefix bool
 	var prefix []byte
-	datadriven.RunTest(t, "testdata/bounded_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/bounded_iter", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			var spans []Span
diff --git a/internal/keyspan/defragment_test.go b/internal/keyspan/defragment_test.go
index 3ff3b503..5f985d76 100644
--- a/internal/keyspan/defragment_test.go
+++ b/internal/keyspan/defragment_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/pmezard/go-difflib/difflib"
 )
@@ -34,7 +34,7 @@ func TestDefragmentingIter(t *testing.T) {
 
 	var buf bytes.Buffer
 	var spans []Span
-	datadriven.RunTest(t, "testdata/defragmenting_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/defragmenting_iter", func(t *testing.T, td *datadriven.TestData) string {
 		buf.Reset()
 		switch td.Cmd {
 		case "define":
diff --git a/internal/keyspan/filter_test.go b/internal/keyspan/filter_test.go
index 13ec3c0a..052d3ffc 100644
--- a/internal/keyspan/filter_test.go
+++ b/internal/keyspan/filter_test.go
@@ -9,8 +9,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 )
 
@@ -33,7 +33,7 @@ func TestFilteringIter(t *testing.T) {
 
 	cmp := testkeys.Comparer.Compare
 	var spans []Span
-	datadriven.RunTest(t, "testdata/filtering_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/filtering_iter", func(t *testing.T, td *datadriven.TestData) string {
 		switch cmd := td.Cmd; cmd {
 		case "define":
 			spans = spans[:0]
diff --git a/internal/keyspan/fragmenter_test.go b/internal/keyspan/fragmenter_test.go
index fcc858ba..8458d366 100644
--- a/internal/keyspan/fragmenter_test.go
+++ b/internal/keyspan/fragmenter_test.go
@@ -12,8 +12,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/stretchr/testify/require"
 )
 
@@ -135,7 +135,7 @@ func TestFragmenter(t *testing.T) {
 		return s != nil && s.CoversAt(readSeq, seq)
 	}
 
-	datadriven.RunTest(t, "testdata/fragmenter", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/fragmenter", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			return func() (result string) {
@@ -178,7 +178,7 @@ func TestFragmenter(t *testing.T) {
 }
 
 func TestFragmenterDeleted(t *testing.T) {
-	datadriven.RunTest(t, "testdata/fragmenter_deleted", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/fragmenter_deleted", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			f := &Fragmenter{
@@ -217,7 +217,7 @@ func TestFragmenterTruncateAndFlushTo(t *testing.T) {
 	cmp := base.DefaultComparer.Compare
 	fmtKey := base.DefaultComparer.FormatKey
 
-	datadriven.RunTest(t, "testdata/fragmenter_truncate_and_flush_to", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/fragmenter_truncate_and_flush_to", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			return func() (result string) {
@@ -241,7 +241,7 @@ func TestFragmenter_Values(t *testing.T) {
 	cmp := base.DefaultComparer.Compare
 	fmtKey := base.DefaultComparer.FormatKey
 
-	datadriven.RunTest(t, "testdata/fragmenter_values", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/fragmenter_values", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			return func() (result string) {
@@ -264,7 +264,7 @@ func TestFragmenter_Values(t *testing.T) {
 func TestFragmenter_EmitOrder(t *testing.T) {
 	var buf bytes.Buffer
 
-	datadriven.RunTest(t, "testdata/fragmenter_emit_order", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/fragmenter_emit_order", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			buf.Reset()
diff --git a/internal/keyspan/interleaving_iter_test.go b/internal/keyspan/interleaving_iter_test.go
index a55a674f..678b82a6 100644
--- a/internal/keyspan/interleaving_iter_test.go
+++ b/internal/keyspan/interleaving_iter_test.go
@@ -12,8 +12,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/stretchr/testify/require"
 )
@@ -93,7 +93,7 @@ func runInterleavingIterTest(t *testing.T, filename string) {
 		}
 	}
 
-	datadriven.RunTest(t, filename, func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, filename, func(t *testing.T, td *datadriven.TestData) string {
 		buf.Reset()
 		switch td.Cmd {
 		case "set-masking-threshold":
diff --git a/internal/keyspan/iter_test.go b/internal/keyspan/iter_test.go
index 71c52bbe..c269f3bf 100644
--- a/internal/keyspan/iter_test.go
+++ b/internal/keyspan/iter_test.go
@@ -10,8 +10,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 )
 
 func runFragmentIteratorCmd(iter FragmentIterator, input string, extraInfo func() string) string {
@@ -61,7 +61,7 @@ func runFragmentIteratorCmd(iter FragmentIterator, input string, extraInfo func(
 
 func TestIter(t *testing.T) {
 	var spans []Span
-	datadriven.RunTest(t, "testdata/iter", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iter", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			spans = nil
diff --git a/internal/keyspan/level_iter_test.go b/internal/keyspan/level_iter_test.go
index d125a7a1..0161122b 100644
--- a/internal/keyspan/level_iter_test.go
+++ b/internal/keyspan/level_iter_test.go
@@ -9,8 +9,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/stretchr/testify/require"
 )
@@ -341,7 +341,7 @@ func TestLevelIter(t *testing.T) {
 	var iter FragmentIterator
 	var extraInfo func() string
 
-	datadriven.RunTest(t, "testdata/level_iter", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/level_iter", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			level = level[:0]
diff --git a/internal/keyspan/merging_iter_test.go b/internal/keyspan/merging_iter_test.go
index 00b36980..8d6aee29 100644
--- a/internal/keyspan/merging_iter_test.go
+++ b/internal/keyspan/merging_iter_test.go
@@ -13,8 +13,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/stretchr/testify/require"
 )
@@ -26,7 +26,7 @@ func TestMergingIter(t *testing.T) {
 
 	formatSpan := func(s *Span) { fmt.Fprintln(&buf, s) }
 
-	datadriven.RunTest(t, "testdata/merging_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/merging_iter", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			snapshot := base.InternalKeySeqNumMax
diff --git a/internal/keyspan/seek_test.go b/internal/keyspan/seek_test.go
index 96f0337d..3acd77e4 100644
--- a/internal/keyspan/seek_test.go
+++ b/internal/keyspan/seek_test.go
@@ -11,8 +11,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 )
 
 func TestSeek(t *testing.T) {
@@ -21,7 +21,7 @@ func TestSeek(t *testing.T) {
 	var iter FragmentIterator
 	var buf bytes.Buffer
 
-	datadriven.RunTest(t, "testdata/seek", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/seek", func(t *testing.T, d *datadriven.TestData) string {
 		buf.Reset()
 		switch d.Cmd {
 		case "build":
diff --git a/internal/keyspan/span_test.go b/internal/keyspan/span_test.go
index c7af9bcb..29651fb4 100644
--- a/internal/keyspan/span_test.go
+++ b/internal/keyspan/span_test.go
@@ -11,7 +11,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/stretchr/testify/require"
 )
 
@@ -33,7 +33,7 @@ func TestSpan_ParseRoundtrip(t *testing.T) {
 
 func TestSpan_Visible(t *testing.T) {
 	var s Span
-	datadriven.RunTest(t, "testdata/visible", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/visible", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			s = ParseSpan(d.Input)
@@ -54,7 +54,7 @@ func TestSpan_Visible(t *testing.T) {
 
 func TestSpan_VisibleAt(t *testing.T) {
 	var s Span
-	datadriven.RunTest(t, "testdata/visible_at", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/visible_at", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			s = ParseSpan(d.Input)
@@ -75,7 +75,7 @@ func TestSpan_VisibleAt(t *testing.T) {
 
 func TestSpan_CoversAt(t *testing.T) {
 	var s Span
-	datadriven.RunTest(t, "testdata/covers_at", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/covers_at", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			s = ParseSpan(d.Input)
diff --git a/internal/keyspan/truncate_test.go b/internal/keyspan/truncate_test.go
index 767fab9b..5142f85f 100644
--- a/internal/keyspan/truncate_test.go
+++ b/internal/keyspan/truncate_test.go
@@ -9,8 +9,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 )
 
 func TestTruncate(t *testing.T) {
@@ -18,7 +18,7 @@ func TestTruncate(t *testing.T) {
 	fmtKey := base.DefaultComparer.FormatKey
 	var iter FragmentIterator
 
-	datadriven.RunTest(t, "testdata/truncate", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/truncate", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "build":
 			tombstones := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete)
diff --git a/internal/manifest/l0_sublevels_test.go b/internal/manifest/l0_sublevels_test.go
index fa405d6e..0c7306c1 100644
--- a/internal/manifest/l0_sublevels_test.go
+++ b/internal/manifest/l0_sublevels_test.go
@@ -16,8 +16,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/record"
 	"github.com/stretchr/testify/require"
@@ -262,7 +262,7 @@ func TestL0Sublevels(t *testing.T) {
 	var sublevels *L0Sublevels
 	baseLevel := NumLevels - 1
 
-	datadriven.RunTest(t, "testdata/l0_sublevels", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/l0_sublevels", func(t *testing.T, td *datadriven.TestData) string {
 		pickBaseCompaction := false
 		level := 0
 		addL0FilesOpt := false
diff --git a/internal/manifest/level_metadata_test.go b/internal/manifest/level_metadata_test.go
index c5a6608b..d1136afc 100644
--- a/internal/manifest/level_metadata_test.go
+++ b/internal/manifest/level_metadata_test.go
@@ -10,14 +10,14 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 )
 
 func TestLevelIterator(t *testing.T) {
 	var level LevelSlice
 	datadriven.RunTest(t, "testdata/level_iterator",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				var files []*FileMetadata
diff --git a/internal/manifest/version_edit_test.go b/internal/manifest/version_edit_test.go
index 36685b1c..eb611efc 100644
--- a/internal/manifest/version_edit_test.go
+++ b/internal/manifest/version_edit_test.go
@@ -15,9 +15,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/record"
 	"github.com/kr/pretty"
 	"github.com/stretchr/testify/require"
@@ -316,7 +316,7 @@ func TestVersionEditApply(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/version_edit_apply",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "apply":
 				// TODO(sumeer): move this Version parsing code to utils, to
diff --git a/internal/manifest/version_test.go b/internal/manifest/version_test.go
index bc471ada..9d7d8e55 100644
--- a/internal/manifest/version_test.go
+++ b/internal/manifest/version_test.go
@@ -12,9 +12,9 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/cockroachdb/redact"
@@ -95,7 +95,7 @@ func TestOverlaps(t *testing.T) {
 	var v *Version
 	cmp := testkeys.Comparer.Compare
 	fmtKey := testkeys.Comparer.FormatKey
-	datadriven.RunTest(t, "testdata/overlaps", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/overlaps", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			var err error
@@ -287,7 +287,7 @@ func TestCheckOrdering(t *testing.T) {
 	cmp := base.DefaultComparer.Compare
 	fmtKey := base.DefaultComparer.FormatKey
 	datadriven.RunTest(t, "testdata/version_check_ordering",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "check-ordering":
 				v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input)
@@ -341,7 +341,7 @@ func TestCheckConsistency(t *testing.T) {
 	}
 
 	datadriven.RunTest(t, "testdata/version_check_consistency",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "check-consistency":
 				var filesByLevel [NumLevels][]*FileMetadata
@@ -464,7 +464,7 @@ func TestExtendBounds(t *testing.T) {
 		return b.String()
 	}
 	m := &FileMetadata{}
-	datadriven.RunTest(t, "testdata/file_metadata_bounds", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/file_metadata_bounds", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "reset":
 			m = &FileMetadata{}
diff --git a/internal/metamorphic/crossversion/crossversion_test.go b/internal/metamorphic/crossversion/crossversion_test.go
index 3499f49c..fd91e88b 100644
--- a/internal/metamorphic/crossversion/crossversion_test.go
+++ b/internal/metamorphic/crossversion/crossversion_test.go
@@ -25,8 +25,8 @@ import (
 	"unicode"
 
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/errors/oserror"
 	"github.com/cockroachdb/pebble/internal/metamorphic"
+	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
 )
 
@@ -35,6 +35,7 @@ var (
 	seed         int64
 	versions     pebbleVersions
 	artifactsDir string
+	streamOutput bool
 )
 
 func init() {
@@ -63,6 +64,8 @@ versions.`)
 	flag.StringVar(&artifactsDir, "artifacts", "",
 		`the path to a directory where test artifacts should be
 moved on failure. Defaults to the current working directory.`)
+	flag.BoolVar(&streamOutput, "stream-output", false,
+		`stream TestMeta output to standard output`)
 }
 
 func reproductionCommand() string {
@@ -146,6 +149,11 @@ func runCrossVersion(
 		return err
 	}
 
+	// When run with test parallelism, multiple tests may fail concurrently.
+	// Only one should actually run the test failure logic which copies the root
+	// dir into the artifacts directory.
+	var fatalOnce sync.Once
+
 	// The outer for loop executes once per version being tested. It takes a
 	// list of initial states, populated by the previous version. The inner loop
 	// executes once per initial state, running the metamorphic test against the
@@ -156,7 +164,7 @@ func runCrossVersion(
 	initialStates := []initialState{{}}
 	for i := range versions {
 		t.Logf("Running tests with version %s with %d initial state(s).", versions[i].SHA, len(initialStates))
-		histories, nextInitialStates, err := runVersion(ctx, t, rootDir, versions[i], versionSeeds[i], initialStates)
+		histories, nextInitialStates, err := runVersion(ctx, t, &fatalOnce, rootDir, versions[i], versionSeeds[i], initialStates)
 		if err != nil {
 			return err
 		}
@@ -165,7 +173,7 @@ func runCrossVersion(
 		// version's metamorphic runs used the same seed, so all of the
 		// resulting histories should be identical.
 		if h, diff := metamorphic.CompareHistories(t, histories); h > 0 {
-			fatalf(t, rootDir, "Metamorphic test divergence between %q and %q:\nDiff:\n%s",
+			fatalf(t, &fatalOnce, rootDir, "Metamorphic test divergence between %q and %q:\nDiff:\n%s",
 				nextInitialStates[0].desc, nextInitialStates[h].desc, diff)
 		}
 
@@ -189,6 +197,7 @@ func runCrossVersion(
 func runVersion(
 	ctx context.Context,
 	t *testing.T,
+	fatalOnce *sync.Once,
 	rootDir string,
 	vers pebbleVersion,
 	seed uint64,
@@ -204,7 +213,7 @@ func runVersion(
 		for j, s := range initialStates {
 			j, s := j, s // re-bind loop vars to scope
 
-			runID := fmt.Sprintf("%s_%d_%03d", vers.SHA, seed, j)
+			runID := fmt.Sprintf("%s_%s_%d_%03d", vers.Label, vers.SHA, seed, j)
 			r := metamorphicTestRun{
 				seed:           seed,
 				dir:            filepath.Join(rootDir, runID),
@@ -218,13 +227,13 @@ func runVersion(
 
 				var buf bytes.Buffer
 				var out io.Writer = &buf
-				if testing.Verbose() {
+				if streamOutput {
 					out = io.MultiWriter(out, os.Stderr)
 				}
 				t.Logf("  Running test with version %s with initial state %s.",
 					vers.SHA, s)
 				if err := r.run(ctx, out); err != nil {
-					fatalf(t, rootDir, "Metamorphic test failed: %s\nOutput:%s\n", err, buf.String())
+					fatalf(t, fatalOnce, rootDir, "Metamorphic test failed: %s\nOutput:%s\n", err, buf.String())
 				}
 
 				// dir is a directory containing the ops file and subdirectories for
@@ -258,21 +267,25 @@ func runVersion(
 	return histories, nextInitialStates, err
 }
 
-func fatalf(t testing.TB, dir string, msg string, args ...interface{}) {
-	if artifactsDir == "" {
-		var err error
-		artifactsDir, err = os.Getwd()
-		require.NoError(t, err)
-	}
-	dst := filepath.Join(artifactsDir, filepath.Base(dir))
-	t.Logf("Moving test dir %q to %q.", dir, dst)
-	err := os.Rename(dir, dst)
-	// If the run of the metamorphic test panics or otherwise exits uncleanly,
-	// the source directory may not exist.
-	if !oserror.IsNotExist(err) {
-		t.Error(err)
-	}
-	t.Fatalf(msg, args...)
+func fatalf(t testing.TB, fatalOnce *sync.Once, dir string, msg string, args ...interface{}) {
+	fatalOnce.Do(func() {
+		if artifactsDir == "" {
+			var err error
+			artifactsDir, err = os.Getwd()
+			require.NoError(t, err)
+		}
+		// When run with test parallelism, other subtests may still be running
+		// within subdirectories of `dir`. We copy instead of rename so that those
+		// substests don't also fail when we remove their files out from under them.
+		// Those additional failures would confuse the test output.
+		dst := filepath.Join(artifactsDir, filepath.Base(dir))
+		t.Logf("Copying test dir %q to %q.", dir, dst)
+		_, err := vfs.Clone(vfs.Default, vfs.Default, dir, dst, vfs.CloneTryLink)
+		if err != nil {
+			t.Error(err)
+		}
+		t.Fatalf(msg, args...)
+	})
 }
 
 type metamorphicTestRun struct {
diff --git a/internal/metamorphic/history_test.go b/internal/metamorphic/history_test.go
index 4ed862b4..7403bcd6 100644
--- a/internal/metamorphic/history_test.go
+++ b/internal/metamorphic/history_test.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/pmezard/go-difflib/difflib"
 	"github.com/stretchr/testify/require"
 )
@@ -42,7 +42,7 @@ func TestHistoryFail(t *testing.T) {
 }
 
 func TestReorderHistory(t *testing.T) {
-	datadriven.RunTest(t, "testdata/reorder_history", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/reorder_history", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "reorder":
 			lines := difflib.SplitLines(string(d.Input))
diff --git a/internal/metamorphic/parser_test.go b/internal/metamorphic/parser_test.go
index f05f27f9..a6b33cd1 100644
--- a/internal/metamorphic/parser_test.go
+++ b/internal/metamorphic/parser_test.go
@@ -9,14 +9,14 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/randvar"
 	"github.com/kr/pretty"
 	"github.com/stretchr/testify/require"
 )
 
 func TestParser(t *testing.T) {
-	datadriven.RunTest(t, "testdata/parser", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/parser", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "parse":
 			ops, err := parse([]byte(d.Input))
diff --git a/internal/rangekey/coalesce_test.go b/internal/rangekey/coalesce_test.go
index 94e458e0..fb0768fb 100644
--- a/internal/rangekey/coalesce_test.go
+++ b/internal/rangekey/coalesce_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/pmezard/go-difflib/difflib"
@@ -26,7 +26,7 @@ func TestCoalesce(t *testing.T) {
 	var buf bytes.Buffer
 	cmp := testkeys.Comparer.Compare
 
-	datadriven.RunTest(t, "testdata/coalesce", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/coalesce", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "coalesce":
 			buf.Reset()
@@ -51,7 +51,7 @@ func TestIter(t *testing.T) {
 	var iter keyspan.MergingIter
 	var buf bytes.Buffer
 
-	datadriven.RunTest(t, "testdata/iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iter", func(t *testing.T, td *datadriven.TestData) string {
 		buf.Reset()
 		switch td.Cmd {
 		case "define":
@@ -124,7 +124,7 @@ func TestDefragmenting(t *testing.T) {
 	var spans []keyspan.Span
 	var hasPrefix bool
 	var prefix []byte
-	datadriven.RunTest(t, "testdata/defragmenting_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/defragmenting_iter", func(t *testing.T, td *datadriven.TestData) string {
 		buf.Reset()
 		switch td.Cmd {
 		case "define":
diff --git a/internal/testkeys/testkeys_test.go b/internal/testkeys/testkeys_test.go
index 1f805458..076b5c13 100644
--- a/internal/testkeys/testkeys_test.go
+++ b/internal/testkeys/testkeys_test.go
@@ -9,7 +9,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/stretchr/testify/require"
 )
 
@@ -179,7 +179,7 @@ func TestSuffixLen(t *testing.T) {
 
 func TestDivvy(t *testing.T) {
 	var buf bytes.Buffer
-	datadriven.RunTest(t, "testdata/divvy", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/divvy", func(t *testing.T, d *datadriven.TestData) string {
 		buf.Reset()
 		switch d.Cmd {
 		case "divvy":
diff --git a/iterator_histories_test.go b/iterator_histories_test.go
index e81538cd..7416a70d 100644
--- a/iterator_histories_test.go
+++ b/iterator_histories_test.go
@@ -10,10 +10,10 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/sstable"
 	"github.com/cockroachdb/pebble/vfs"
@@ -50,7 +50,7 @@ func TestIterHistories(t *testing.T) {
 		}
 		defer cleanup()
 
-		datadriven.RunTest(t, path, func(td *datadriven.TestData) string {
+		datadriven.RunTest(t, path, func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "reset":
 				if err := cleanup(); err != nil {
diff --git a/iterator_test.go b/iterator_test.go
index ee97c2b5..451b1a55 100644
--- a/iterator_test.go
+++ b/iterator_test.go
@@ -17,9 +17,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/cockroachdb/pebble/internal/testkeys"
@@ -519,7 +519,7 @@ func TestIterator(t *testing.T) {
 		return it
 	}
 
-	datadriven.RunTest(t, "testdata/iterator", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			merge = nil
@@ -606,7 +606,7 @@ func TestReadSampling(t *testing.T) {
 		}
 	}()
 
-	datadriven.RunTest(t, "testdata/iterator_read_sampling", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_read_sampling", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			if iter != nil {
@@ -773,7 +773,7 @@ func TestIteratorTableFilter(t *testing.T) {
 		}
 	}()
 
-	datadriven.RunTest(t, "testdata/iterator_table_filter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_table_filter", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			if d != nil {
@@ -866,7 +866,7 @@ func TestIteratorNextPrev(t *testing.T) {
 	}
 	reset()
 
-	datadriven.RunTest(t, "testdata/iterator_next_prev", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_next_prev", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			reset()
@@ -938,7 +938,7 @@ func TestIteratorStats(t *testing.T) {
 	}
 	reset()
 
-	datadriven.RunTest(t, "testdata/iterator_stats", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_stats", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "reset":
 			reset()
@@ -1024,7 +1024,7 @@ func TestIteratorSeekOpt(t *testing.T) {
 	}()
 	var seekGEUsingNext, seekPrefixGEUsingNext int
 
-	datadriven.RunTest(t, "testdata/iterator_seek_opt", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_seek_opt", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			if iter != nil {
@@ -1199,7 +1199,7 @@ func TestIteratorSeekOptErrors(t *testing.T) {
 		}
 	}
 
-	datadriven.RunTest(t, "testdata/iterator_seek_opt_errors", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_seek_opt_errors", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			keys = keys[:0]
@@ -1335,7 +1335,7 @@ func TestIteratorBlockIntervalFilter(t *testing.T) {
 	}
 
 	datadriven.RunTest(
-		t, "testdata/iterator_block_interval_filter", func(td *datadriven.TestData) string {
+		t, "testdata/iterator_block_interval_filter", func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "build":
 				var collectors []collector
@@ -1594,7 +1594,7 @@ func TestIteratorBoundsLifetimes(t *testing.T) {
 		}
 	}
 
-	datadriven.RunTest(t, "testdata/iterator_bounds_lifetimes", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/iterator_bounds_lifetimes", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			var err error
diff --git a/level_checker_test.go b/level_checker_test.go
index 347ca36e..a57a17d1 100644
--- a/level_checker_test.go
+++ b/level_checker_test.go
@@ -12,9 +12,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/cockroachdb/pebble/internal/private"
@@ -119,7 +119,7 @@ func TestCheckLevelsCornerCases(t *testing.T) {
 		Name: "fail-merger",
 	}
 
-	datadriven.RunTest(t, "testdata/level_checker", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/level_checker", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			lines := strings.Split(d.Input, "\n")
diff --git a/level_iter_test.go b/level_iter_test.go
index 50e1e1b6..90f9e87c 100644
--- a/level_iter_test.go
+++ b/level_iter_test.go
@@ -11,9 +11,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/cockroachdb/pebble/internal/rangedel"
@@ -40,7 +40,7 @@ func TestLevelIter(t *testing.T) {
 		return &f, nil, nil
 	}
 
-	datadriven.RunTest(t, "testdata/level_iter", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/level_iter", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			iters = nil
@@ -284,7 +284,7 @@ func TestLevelIterBoundaries(t *testing.T) {
 	defer lt.runClear(nil)
 
 	var iter *levelIter
-	datadriven.RunTest(t, "testdata/level_iter_boundaries", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/level_iter_boundaries", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "clear":
 			return lt.runClear(d)
@@ -407,7 +407,7 @@ func TestLevelIterSeek(t *testing.T) {
 	lt := newLevelIterTest()
 	defer lt.runClear(nil)
 
-	datadriven.RunTest(t, "testdata/level_iter_seek", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/level_iter_seek", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "clear":
 			return lt.runClear(d)
diff --git a/mem_table_test.go b/mem_table_test.go
index 6221b6cb..2172297e 100644
--- a/mem_table_test.go
+++ b/mem_table_test.go
@@ -14,10 +14,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/arenaskl"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/rangekey"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/exp/rand"
@@ -253,7 +253,7 @@ func TestMemTableIter(t *testing.T) {
 	var mem *memTable
 	for _, testdata := range []string{
 		"testdata/internal_iter_next", "testdata/internal_iter_bounds"} {
-		datadriven.RunTest(t, testdata, func(d *datadriven.TestData) string {
+		datadriven.RunTest(t, testdata, func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "define":
 				mem = newMemTable(memTableOptions{})
@@ -300,7 +300,7 @@ func TestMemTableDeleteRange(t *testing.T) {
 	var mem *memTable
 	var seqNum uint64
 
-	datadriven.RunTest(t, "testdata/delete_range", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/delete_range", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "clear":
 			mem = nil
diff --git a/merging_iter_test.go b/merging_iter_test.go
index ecf57742..16352f2b 100644
--- a/merging_iter_test.go
+++ b/merging_iter_test.go
@@ -10,9 +10,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/manifest"
 	"github.com/cockroachdb/pebble/internal/rangedel"
@@ -43,7 +43,7 @@ func TestMergingIter(t *testing.T) {
 
 func TestMergingIterSeek(t *testing.T) {
 	var def string
-	datadriven.RunTest(t, "testdata/merging_iter_seek", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/merging_iter_seek", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			def = d.Input
@@ -102,7 +102,7 @@ func TestMergingIterNextPrev(t *testing.T) {
 
 	for _, c := range iterCases {
 		t.Run("", func(t *testing.T) {
-			datadriven.RunTest(t, "testdata/internal_iter_next", func(d *datadriven.TestData) string {
+			datadriven.RunTest(t, "testdata/internal_iter_next", func(t *testing.T, d *datadriven.TestData) string {
 				switch d.Cmd {
 				case "define":
 					// Ignore. We've defined the iterator data above.
@@ -166,7 +166,7 @@ func TestMergingIterCornerCases(t *testing.T) {
 			return iter, rangeDelIter, nil
 		}
 
-	datadriven.RunTest(t, "testdata/merging_iter", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/merging_iter", func(t *testing.T, d *datadriven.TestData) string {
 		switch d.Cmd {
 		case "define":
 			lines := strings.Split(d.Input, "\n")
diff --git a/metrics_test.go b/metrics_test.go
index 787cc5fc..b4685b41 100644
--- a/metrics_test.go
+++ b/metrics_test.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/humanize"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/cockroachdb/redact"
@@ -125,7 +125,7 @@ func TestMetrics(t *testing.T) {
 		}
 	}()
 
-	datadriven.RunTest(t, "testdata/metrics", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/metrics", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "batch":
 			b := d.NewBatch()
diff --git a/pacer_test.go b/pacer_test.go
index 18c5cdaa..ec6a0ac5 100644
--- a/pacer_test.go
+++ b/pacer_test.go
@@ -12,7 +12,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/cockroachdb/pebble/internal/datadriven"
+	"github.com/cockroachdb/datadriven"
 )
 
 type mockPrintLimiter struct {
@@ -36,7 +36,7 @@ func (m *mockPrintLimiter) Burst() int {
 
 func TestCompactionPacerMaybeThrottle(t *testing.T) {
 	datadriven.RunTest(t, "testdata/compaction_pacer_maybe_throttle",
-		func(d *datadriven.TestData) string {
+		func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "init":
 				if len(d.CmdArgs) != 1 {
diff --git a/range_del_test.go b/range_del_test.go
index aee5521d..84da2fe3 100644
--- a/range_del_test.go
+++ b/range_del_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/sstable"
 	"github.com/cockroachdb/pebble/vfs"
@@ -34,7 +34,7 @@ func TestRangeDel(t *testing.T) {
 	opts := &Options{}
 	opts.DisableAutomaticCompactions = true
 
-	datadriven.RunTest(t, "testdata/range_del", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/range_del", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			if d != nil {
diff --git a/snapshot_test.go b/snapshot_test.go
index 39ccdd1d..a1d19ac5 100644
--- a/snapshot_test.go
+++ b/snapshot_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
 )
@@ -60,7 +60,7 @@ func TestSnapshot(t *testing.T) {
 	}
 	defer close()
 
-	datadriven.RunTest(t, "testdata/snapshot", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/snapshot", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			close()
diff --git a/sstable/block_property_test.go b/sstable/block_property_test.go
index 54890b9f..bae88eab 100644
--- a/sstable/block_property_test.go
+++ b/sstable/block_property_test.go
@@ -15,9 +15,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/rangekey"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/stretchr/testify/require"
@@ -873,7 +873,7 @@ func TestBlockProperties(t *testing.T) {
 	}()
 
 	var stats base.InternalIteratorStats
-	datadriven.RunTest(t, "testdata/block_properties", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/block_properties", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "build":
 			if r != nil {
@@ -1033,7 +1033,7 @@ func TestBlockProperties_BoundLimited(t *testing.T) {
 	}()
 
 	var stats base.InternalIteratorStats
-	datadriven.RunTest(t, "testdata/block_properties_boundlimited", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/block_properties_boundlimited", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "build":
 			if r != nil {
diff --git a/sstable/block_test.go b/sstable/block_test.go
index e39c4d2a..403fb6eb 100644
--- a/sstable/block_test.go
+++ b/sstable/block_test.go
@@ -13,8 +13,8 @@ import (
 	"time"
 	"unsafe"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/exp/rand"
 )
@@ -224,7 +224,7 @@ func TestBlockIter2(t *testing.T) {
 
 	for _, r := range []int{1, 2, 3, 4} {
 		t.Run(fmt.Sprintf("restart=%d", r), func(t *testing.T) {
-			datadriven.RunTest(t, "testdata/block", func(d *datadriven.TestData) string {
+			datadriven.RunTest(t, "testdata/block", func(t *testing.T, d *datadriven.TestData) string {
 				switch d.Cmd {
 				case "build":
 					w := &blockWriter{restartInterval: r}
diff --git a/sstable/data_test.go b/sstable/data_test.go
index c6c9e4df..ae8e44ea 100644
--- a/sstable/data_test.go
+++ b/sstable/data_test.go
@@ -11,11 +11,11 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/cache"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/vfs"
 )
diff --git a/sstable/reader_test.go b/sstable/reader_test.go
index c94e1fdf..2128335d 100644
--- a/sstable/reader_test.go
+++ b/sstable/reader_test.go
@@ -18,11 +18,11 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/cache"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/internal/humanize"
 	"github.com/cockroachdb/pebble/internal/testkeys"
@@ -412,7 +412,7 @@ func runTestReader(
 			}
 		}()
 
-		datadriven.RunTest(t, path, func(d *datadriven.TestData) string {
+		datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
 			switch d.Cmd {
 			case "build":
 				if r != nil {
@@ -661,7 +661,7 @@ func TestCompactionIteratorSetupForCompaction(t *testing.T) {
 
 func TestMaybeReadahead(t *testing.T) {
 	var rs readaheadState
-	datadriven.RunTest(t, "testdata/readahead", func(d *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/readahead", func(t *testing.T, d *datadriven.TestData) string {
 		cacheHit := false
 		switch d.Cmd {
 		case "reset":
diff --git a/sstable/writer_rangekey_test.go b/sstable/writer_rangekey_test.go
index 1e7b9432..f80fd583 100644
--- a/sstable/writer_rangekey_test.go
+++ b/sstable/writer_rangekey_test.go
@@ -7,8 +7,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
@@ -90,7 +90,7 @@ func TestWriter_RangeKeys(t *testing.T) {
 		return r, nil
 	}
 
-	datadriven.RunTest(t, "testdata/writer_range_keys", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/writer_range_keys", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "build":
 			if r != nil {
diff --git a/sstable/writer_test.go b/sstable/writer_test.go
index 97e811e6..dd71d32a 100644
--- a/sstable/writer_test.go
+++ b/sstable/writer_test.go
@@ -14,11 +14,11 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/bloom"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/cache"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/humanize"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/vfs"
@@ -83,7 +83,7 @@ func runDataDriven(t *testing.T, file string, tableFormat TableFormat, paralleli
 		return b.String()
 	}
 
-	datadriven.RunTest(t, file, func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, file, func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "build":
 			if r != nil {
@@ -238,7 +238,7 @@ func TestWriterWithValueBlocks(t *testing.T) {
 		return attribute, nil
 	}
 
-	datadriven.RunTest(t, "testdata/writer_value_blocks", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/writer_value_blocks", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "build":
 			if r != nil {
@@ -487,7 +487,7 @@ func TestParallelWriterErrorProp(t *testing.T) {
 func TestSizeEstimate(t *testing.T) {
 	var sizeEstimate sizeEstimate
 	datadriven.RunTest(t, "testdata/size_estimate",
-		func(td *datadriven.TestData) string {
+		func(t *testing.T, td *datadriven.TestData) string {
 			switch td.Cmd {
 			case "init":
 				if len(td.CmdArgs) != 1 {
diff --git a/table_stats_test.go b/table_stats_test.go
index 4408debb..898ddd94 100644
--- a/table_stats_test.go
+++ b/table_stats_test.go
@@ -10,8 +10,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/keyspan"
 	"github.com/cockroachdb/pebble/internal/rangekey"
 	"github.com/cockroachdb/pebble/internal/testkeys"
@@ -45,7 +45,7 @@ func TestTableStats(t *testing.T) {
 		}
 	}()
 
-	datadriven.RunTest(t, "testdata/table_stats", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/table_stats", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "disable":
 			d.mu.Lock()
@@ -146,7 +146,7 @@ func TestTableRangeDeletionIter(t *testing.T) {
 	var m *fileMetadata
 	cmp := base.DefaultComparer.Compare
 	fs := vfs.NewMem()
-	datadriven.RunTest(t, "testdata/table_stats_deletion_iter", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/table_stats_deletion_iter", func(t *testing.T, td *datadriven.TestData) string {
 		switch cmd := td.Cmd; cmd {
 		case "build":
 			f, err := fs.Create("tmp.sst")
diff --git a/testdata/iter_histories/iter_optimizations b/testdata/iter_histories/iter_optimizations
index 9cb12150..a9300955 100644
--- a/testdata/iter_histories/iter_optimizations
+++ b/testdata/iter_histories/iter_optimizations
@@ -625,3 +625,99 @@ seek-prefix-ge c@2
 ----
 b@2: (g, .)
 c@2: (., [c-"c\x00") @5=bar UPDATED)
+
+# Regression test for Cockroachdb#92205. This test constructs this scenario:
+#
+# A DEL in a middle level (L0.0) that we SeekPrefixGE directly for. Note that
+# this DEL is not deleted by any range deletes; it gets exposed to the
+# Iterator. There's a key after this DEL in the L0.0 levelIter, and there's a
+# level above it (L0.1) that has a rangedel deleting that key, but not the DEL
+# we SeekPrefixGE for. In the lowest level, there's a SET at L6 that is to the
+# right of the DEL in L0.0, but is also not deleted by the RANGEDEL in L0.1.
+# Our second SeekPrefixGE will be for this SET. Visualization, where square
+# brackets are files:
+#
+# L0.1                 [dd-ee#RANGEDEL]
+# L0.0    [b#DEL          e#SET]
+# L6            [d#SET]       [f#SET g#SET]
+#
+# When the Iterator encounters the above DEL internal key in the SeekPrefixGE, it
+# calls Iterator.nextUserKey in the Iterator.findNextEntry call that was part of the
+# SeekPrefixGE call. While Iterator.findNextEntry has a conditional to exit
+# out of the loop if we're in prefix iteration and have gone past the prefix,
+# this break only happens _after_ nextUserKey() has run. As a result we Next()
+# the levelIter in L0.0, land on e#SET, and the mergingIter realizes that it
+# is deleted by the rangedel in a higher level (L0.1). The mergingIter does not
+# see d#SET because that sstable was excluded by the bloom filter. We then do a relative
+# seek of all levels below L0.1 to ee (the end key of the rangedel), and in that
+# process we advance the L6 levelIter to the second file.
+#
+# When we do the second SeekPrefixGE for d, the outer Iterator thinks d > b and
+# so TrySeekUsingNext can work. However, the L6 levelIter has already advanced
+# past the file containing d#SET, so we don't surface it even though we should
+# have.
+
+reset bloom-bits-per-key=100
+----
+
+batch commit
+set d@4 foo
+----
+committed 1 keys
+
+flush
+----
+
+compact a-f
+----
+6:
+  000005:[d@4#1,SET-d@4#1,SET]
+
+batch commit
+set f@5 bar
+set g@5 baz
+----
+committed 2 keys
+
+flush
+----
+
+compact e-k
+----
+6:
+  000005:[d@4#1,SET-d@4#1,SET]
+  000007:[f@5#2,SET-g@5#3,SET]
+
+batch commit
+del b@5
+set e@4 foobar
+----
+committed 2 keys
+
+flush
+----
+
+batch commit
+del-range dd ee
+----
+committed 1 keys
+
+flush
+----
+
+lsm
+----
+0.1:
+  000011:[dd#6,RANGEDEL-ee#72057594037927935,RANGEDEL]
+0.0:
+  000009:[b@5#4,DEL-e@4#5,SET]
+6:
+  000005:[d@4#1,SET-d@4#1,SET]
+  000007:[f@5#2,SET-g@5#3,SET]
+
+combined-iter upper=z@3 use-l6-filter
+seek-prefix-ge b@6
+seek-prefix-ge d@5
+----
+.
+d@4: (foo, .)
diff --git a/testdata/merging_iter b/testdata/merging_iter
index a7cddba4..1f41d4d4 100644
--- a/testdata/merging_iter
+++ b/testdata/merging_iter
@@ -540,8 +540,8 @@ seek-prefix-ge c true
 seek-prefix-ge d true
 ----
 a#10,1:a10
-.
-.
+d#10,1:d10
+d#10,1:d10
 d#10,1:d10
 d#10,1:d10
 
@@ -553,8 +553,8 @@ seek-prefix-ge d true
 next
 ----
 a#10,1:a10
-.
-.
+d#10,1:d10
+d#10,1:d10
 d#10,1:d10
 .
 
diff --git a/tool/data_test.go b/tool/data_test.go
index ed289c09..a83eebf8 100644
--- a/tool/data_test.go
+++ b/tool/data_test.go
@@ -13,8 +13,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/spf13/cobra"
@@ -47,7 +47,7 @@ func runTests(t *testing.T, path string) {
 
 		fs := vfs.NewMem()
 		t.Run(name, func(t *testing.T) {
-			datadriven.RunTest(t, path, func(d *datadriven.TestData) string {
+			datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
 				args := []string{d.Cmd}
 				for _, arg := range d.CmdArgs {
 					args = append(args, arg.String())
diff --git a/tool/logs/compaction_test.go b/tool/logs/compaction_test.go
index 45646298..5c85e99f 100644
--- a/tool/logs/compaction_test.go
+++ b/tool/logs/compaction_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/stretchr/testify/require"
 )
 
@@ -180,7 +180,7 @@ func TestCompactionLogs(t *testing.T) {
 	resetFn()
 
 	dir := t.TempDir()
-	datadriven.RunTest(t, "testdata/compactions", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/compactions", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "log":
 			basename := fmt.Sprintf("%d.log", logNum)
diff --git a/vfs/atomicfs/marker_test.go b/vfs/atomicfs/marker_test.go
index 7ef81fba..e1cb2abc 100644
--- a/vfs/atomicfs/marker_test.go
+++ b/vfs/atomicfs/marker_test.go
@@ -14,8 +14,8 @@ import (
 	"sync/atomic"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/stretchr/testify/require"
@@ -64,7 +64,7 @@ func TestMarker(t *testing.T) {
 	memFS := vfs.NewMem()
 
 	var buf bytes.Buffer
-	datadriven.RunTest(t, "testdata/marker", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/marker", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "list":
 			ls, err := memFS.List(td.CmdArgs[0].String())
diff --git a/vfs/testdata/vfs b/vfs/testdata/vfs
index e4659081..450377e7 100644
--- a/vfs/testdata/vfs
+++ b/vfs/testdata/vfs
@@ -76,23 +76,34 @@ mkdir: d/b [<nil>]
 create: d/b/c [<nil>]
 close: c [<nil>]
 
+# NB: This clone does not specify a destination FS, so the clone target will be
+# the same FS. This results in the use of link.
+
 define
-clone d e
+clone d e link
 ----
 open: d [<nil>]
-close: d [<nil>]
 mkdir: e [<nil>]
 open: d/a [<nil>]
-close: d/a [<nil>]
+link: d/a -> e/a [random]
+open: d/a [<nil>]
 create: e/a [<nil>]
+sync: a [<nil>]
 close: a [<nil>]
+close: d/a [<nil>]
+close: d/a [<nil>]
 open: d/b [<nil>]
-close: d/b [<nil>]
 mkdir: e/b [<nil>]
 open: d/b/c [<nil>]
-close: d/b/c [<nil>]
+link: d/b/c -> e/b/c [random]
+open: d/b/c [<nil>]
 create: e/b/c [<nil>]
+sync: c [<nil>]
 close: c [<nil>]
+close: d/b/c [<nil>]
+close: d/b/c [<nil>]
+close: d/b [<nil>]
+close: d [<nil>]
 
 define
 list e
@@ -131,3 +142,47 @@ reuseForWrite x y
 ----
 reuseForWrite: a -> b [<nil>]
 reuseForWrite: x -> y [file does not exist]
+
+# NB: This clone target specified a different FS. This results in no use of
+# link, despite link being provided.
+
+define
+clone d f mem link
+----
+open: d [<nil>]
+mkdir: f [<nil>]
+open: d/a [<nil>]
+create: f/a [<nil>]
+close: a [<nil>]
+close: d/a [<nil>]
+open: d/b [<nil>]
+mkdir: f/b [<nil>]
+open: d/b/c [<nil>]
+create: f/b/c [<nil>]
+close: c [<nil>]
+close: d/b/c [<nil>]
+close: d/b [<nil>]
+close: d [<nil>]
+
+# NB: This clone does not specify link, so all files are copied. It does specify
+# sync, so all files and directories are synced.
+
+define
+clone d g sync
+----
+open: d [<nil>]
+mkdir: g [<nil>]
+open: d/a [<nil>]
+create: g/a [<nil>]
+sync: a [<nil>]
+close: a [<nil>]
+close: d/a [<nil>]
+open: d/b [<nil>]
+mkdir: g/b [<nil>]
+open: d/b/c [<nil>]
+create: g/b/c [<nil>]
+sync: c [<nil>]
+close: c [<nil>]
+close: d/b/c [<nil>]
+close: d/b [<nil>]
+close: d [<nil>]
diff --git a/vfs/vfs_test.go b/vfs/vfs_test.go
index cb25176a..a48127c1 100644
--- a/vfs/vfs_test.go
+++ b/vfs/vfs_test.go
@@ -14,9 +14,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/cockroachdb/datadriven"
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/errors/oserror"
-	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/stretchr/testify/require"
 )
 
@@ -122,7 +122,7 @@ func runTestVFS(t *testing.T, baseFS FS, dir string) {
 	var buf bytes.Buffer
 	fs := loggingFS{FS: baseFS, base: dir, w: &buf}
 
-	datadriven.RunTest(t, "testdata/vfs", func(td *datadriven.TestData) string {
+	datadriven.RunTest(t, "testdata/vfs", func(t *testing.T, td *datadriven.TestData) string {
 		switch td.Cmd {
 		case "define":
 			buf.Reset()
@@ -156,10 +156,27 @@ func runTestVFS(t *testing.T, baseFS FS, dir string) {
 
 				switch parts[0] {
 				case "clone":
-					if len(parts) != 3 {
-						return "clone <src> <dest>"
+					if len(parts) < 3 {
+						return "clone <src> <dest> [disk|mem] [link] [sync]"
+					}
+					dstFS := fs
+					var opts []CloneOption
+					for _, p := range parts[3:] {
+						switch p {
+						case "disk":
+							dstFS = loggingFS{FS: Default, base: dir, w: &buf}
+						case "mem":
+							dstFS = loggingFS{FS: NewMem(), base: dir, w: &buf}
+						case "link":
+							opts = append(opts, CloneTryLink)
+						case "sync":
+							opts = append(opts, CloneSync)
+						default:
+							return fmt.Sprintf("unrecognized argument %q", p)
+						}
 					}
-					_, _ = Clone(fs, fs, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]))
+
+					_, _ = Clone(fs, dstFS, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2]), opts...)
 
 				case "create":
 					if len(parts) != 2 {