Help us shape the future of Go! Take the 2026 Go Developer Survey

storetest

package
v0.77.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

Documentation

Overview

Package storetest is the contract every collab.Store keeps, written once and run by each implementation against itself.

Why it exists

The stores in this repository drifted apart, and each divergence was found separately and late. A DirStore refused a zero-length snapshot as the torn write it is; pgstore answered it as a new document. A DirStore checksummed what it wrote; gitstore read a file git was not protecting. A DirStore refused two names a case-folding filesystem cannot tell apart; nothing else had to think about it. None of those are hard to get right. What was missing is a single place that says what right is, and asks every store the same questions.

What it does not do

It asserts what collab.Store documents, and nothing it does not. Where the stores differ on something the contract leaves open — whether an empty document name is refused, say — this stays quiet rather than legislating from a test. A conformance suite that invents requirements makes implementations agree about the suite instead of about the contract.

Using it

func TestConformance(t *testing.T) {
	storetest.Run(t, func(t *testing.T) storetest.Harness {
		return storetest.Harness{Store: collab.NewMemoryStore()}
	})
}

The function is called again for every case, and must return a store with nothing in it: a suite that shared one store would let each case see what the case before it wrote, and a failure would name the wrong one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(t *testing.T, make Make)

Run asks make for a store and puts the contract to it.

Types

type Harness

type Harness struct {
	// Store is the store under test, holding nothing.
	Store collab.Store

	// Corrupt changes the nth byte of what the store holds for document, as a
	// medium that rots would, without going through Save. It reports false when
	// there is no nth byte, which is how the case knows it has been through all
	// of them. nil when a test cannot reach the medium.
	//
	// One byte is not enough to ask about. The first version of this asked
	// about a single byte near the end, and a store with its checksum
	// comparison disabled PASSED: a change there breaks the compressed stream,
	// which the decompressor refuses on its own. The case has to walk the
	// whole thing, or it tests the compressor.
	Corrupt func(document string, nth int) (bool, error)

	// Truncate makes the store hold zero bytes for document, which is what a
	// crash between creating a file and writing it leaves behind. nil when the
	// store cannot be put in that state from outside.
	Truncate func(document string) error

	// Close releases whatever making the store took. May be nil.
	Close func()
}

A Harness is one store to ask the questions of, and what a test may do to the medium underneath it.

The optional fields are nil when the concept does not apply, and the case that needs one skips rather than fails. A MemoryStore cannot rot and cannot be truncated, and saying so by leaving a field nil is clearer than a boolean that has to be read against the store it came from.

type Make

type Make func(t *testing.T) Harness

A Make returns a fresh Harness. It is called once per case.

type T

type T interface {
	Helper()
	Logf(format string, args ...any)
	Errorf(format string, args ...any)
	Fatal(args ...any)
	Fatalf(format string, args ...any)
	Skip(args ...any)
}

A T is the part of testing.T the cases use.

It is an interface so that the suite can be run against stores that are deliberately wrong and the failures counted. Nothing else guards a conformance suite: a case that has stopped asking anything passes exactly as loudly as one that holds. The first version of the rot case here corrupted a single byte near the end of a compressed document, and a store with its checksum comparison switched off PASSED it -- the decompressor refused on its own, so the case tested the decompressor. selftest_test.go is what caught that class of mistake afterwards.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL