ubtest

package
v0.8.0-a.22 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 9 Imported by: 0

README

ubtest

internal/ubtest is the required framework for Unobin syntax-only tests over .ub fixtures. Use it for parser diagnostics, syntax validation, formatting, canonical rendering, type expression parsing, import extraction, and other tests where a .ub file plus .ub.err or .ub.out golden fully describes the expected result.

Use internal/e2etest instead when behavior is visible through the CLI or a compiled factory binary.

Import path

import "github.com/cloudboss/unobin/internal/ubtest"

The package lives under internal because it is test infrastructure for this repo, not a public API.

Fixture layout

All .ub test fixtures must live below a package-local testdata/ub tree and must include a valid or invalid path segment:

pkg/lang/testdata/ub/format/valid/list.ub
pkg/lang/testdata/ub/format/valid/list.ub.out
pkg/lang/parse/testdata/ub/invalid/unclosed-array.ub
pkg/lang/parse/testdata/ub/invalid/unclosed-array.ub.err

Rules:

  • valid fixtures usually have no .ub.err golden.
  • invalid fixtures use a matching .ub.err golden.
  • Formatter or renderer fixtures use .ub.out for expected output.
  • A fixture may have both .ub.err and .ub.out if the driver returns both.
  • Do not put .ub fixtures outside testdata/ub.
  • Do not add inline .ub Go string literals unless the assertion cannot be expressed with a fixture.

Basic usage

A ubtest suite calls Run with a fixture directory and a driver:

func TestFormatFixtures(t *testing.T) {
    ubtest.Run(t, "testdata/ub/format/valid", func(
        name string,
        src []byte,
    ) (string, []string) {
        out, err := Format(src)
        if err != nil {
            return "", []string{err.Error()}
        }
        return string(out), nil
    }, ubtest.Idempotent(), ubtest.Repeat(5))
}

The driver receives:

  • name: fixture path relative to the suite root, without .ub.
  • src: raw fixture bytes.

The driver returns:

  • output: text compared with <fixture>.ub.out.
  • diags: diagnostics compared with <fixture>.ub.err.

A missing .ub.err means the fixture must produce no diagnostics. A missing .ub.out means the fixture must produce no output.

Reading a single fixture

Use these helpers for Go tests that still need direct access to one fixture:

src := ubtest.ReadValidFixture(t, "testdata/ub/check", "root-scope")
raw := ubtest.ReadFixture(t, "testdata/ub/check/invalid/bad-ref.ub")

ReadValidFixture(t, dir, name) reads dir/valid/<name>.ub.

Direct fixture reads are acceptable for pure Go assertions such as exact struct values, spans, pointer identity, or callback order. If the behavior can be checked with Run, prefer Run.

Options

Substring()

Substring() treats each non-empty line in .ub.err as a required substring of the produced diagnostics.

Use it only when the lower layer emits verbose or version-sensitive parser messages. Prefer exact diagnostics everywhere else.

Idempotent()

Idempotent() runs the driver on its own output and checks that the output does not change. Use it for formatters and renderers.

Repeat(n)

Repeat(n) runs every fixture n times and compares each result. The default is 2. Use larger counts for formatters or code that could be nondeterministic.

Updating goldens

Every ubtest suite uses the shared -update flag:

go test ./pkg/lang -run TestFormatFixtures -update -count=1
go test ./pkg/lang -run TestFormatFixtures -count=1

When -update is set:

  • .ub.err is rewritten from the returned diagnostics.
  • .ub.out is rewritten from returned output.
  • Empty goldens are removed.

Inspect every updated golden before committing.

Inline source guard

internal/ubtest includes repo-level tests that reject inline .ub source strings in Go tests and reject misplaced .ub fixtures.

Run the guard after adding or moving fixtures:

go test ./internal/ubtest \
  -run 'TestInlineUB|TestTestFilesAvoidInlineUBSources|TestUBFixturesUseProjectLayout' \
  -count=1

The scanner parses Go test files and flags string literals whose first real line starts with a UB top-level token such as factory:, resources:, inputs:, outputs:, locals:, constraints:, project:, or state-moves:.

If a test truly cannot use a fixture, keep the literal small and make the case specific. Do not add greenlist entries unless there is no fixture-based option.

Choosing ubtest vs e2etest

Use ubtest when the expected result is one of these:

  • parser acceptance or parser diagnostics
  • syntax lowering or validation diagnostics
  • formatter or canonical output
  • type expression parse diagnostics
  • import extraction output
  • direct Go values that are not command-visible

Use e2etest when the expected result is one of these:

  • CLI stdout or stderr
  • generated files
  • dependency file edits
  • compiled factory command behavior
  • plan summaries or plan files
  • state summaries or state files
  • runtime effects, masking, lifecycle, inputs, constraints, or config behavior

When in doubt, choose the user-visible e2e test. Keep package-level ubtest cases for syntax-only behavior and pure internal invariants.

Documentation

Overview

Package ubtest runs file-based tests for .ub source fixtures. A test points Run at a directory of .ub files and supplies a Driver that compiles one fixture through some pipeline stage (parse, validate, reference check, ...).

For each fixture <name>.ub, the diagnostics the driver produces are compared against a <name>.ub.err golden, and any textual output against a <name>.ub.out golden. A fixture with no .ub.err golden must compile cleanly. The -update flag rewrites the goldens from the driver's actual output.

ubtest depends only on the standard library and testify, so every package (including the lang front end it tests) can use it without an import cycle. The driver closure, which lives in the calling test, does the unobin-specific work: parsing the source and reducing an *lang.ErrorList to []string.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadFixture

func ReadFixture(t testing.TB, path string) string

ReadFixture reads a .ub fixture file and returns it as a string.

func ReadValidFixture

func ReadValidFixture(t testing.TB, dir, name string) string

ReadValidFixture reads <name>.ub from the valid fixture directory under dir.

func Run

func Run(t *testing.T, dir string, drive Driver, opts ...Option)

Run discovers every *.ub file under dir (recursively) and runs each through drive as a subtest named by its path relative to dir. Each fixture is run twice and the two results compared, catching nondeterministic drivers. With -update, the goldens are rewritten from actual output instead of asserted.

Types

type Driver

type Driver func(name string, src []byte) (output string, diags []string)

Driver compiles one fixture and reports the result for golden comparison. name is the fixture's path relative to the root, without the .ub extension (e.g. "invalid/unclosed-array"); src is the raw .ub bytes. output is compared against the <name>.ub.out golden; return "" when the stage produces no textual output. diags is the diagnostics the stage produced, in stable order; an empty slice means the input was accepted.

type Option

type Option func(*config)

Option configures Run.

func Idempotent

func Idempotent() Option

Idempotent re-runs the driver on its own output and checks the result is unchanged. Use it for formatters and renderers, where format(format(x)) must equal format(x).

func Repeat

func Repeat(n int) Option

Repeat runs each fixture n times and compares every result. The default is 2.

func Substring

func Substring() Option

Substring matches each line of a .ub.err golden as a substring of the produced diagnostics, instead of requiring the whole diagnostics block to match exactly. Use it for the parser layer, whose raw messages are verbose and version-sensitive; prefer exact matching everywhere else.

Jump to

Keyboard shortcuts

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