Skip to content

Add option to skip pretty.Sprint in MatchStandaloneSnapshot #153

@tinovyatkin

Description

@tinovyatkin

Problem

MatchStandaloneSnapshot passes input through pretty.Sprint (github.com/kr/pretty) before writing and comparing snapshots. pretty.Sprint wraps all output through a tabwriter (formatter.go#L56) which silently expands tab bytes (0x09) into spaces.

This means snapshot files don't contain the exact bytes the program under test produces. For our use case — a Dockerfile linter/formatter that outputs tab-indented continuation lines — the .snap.Dockerfile files show 4 spaces where the actual output has tabs.

The comparison still passes because pretty.Sprint is applied symmetrically (both to the new input and when the snapshot was written), but the snapshot files on disk are not faithful representations of the actual output.

Why the existing API can't work around this

  • All internal helpers (snapshotPath, prettyDiff, upsertStandaloneSnapshot, standaloneTestsRegistry) are unexported, so callers can't reuse them selectively.
  • pretty.Sprint wraps output through tabwriter unconditionally when force: true (which Sprint always sets). Implementing fmt.GoStringer or fmt.Formatter on the input type doesn't help — the output still flows through the tabwriter.
  • Post-processing the written file doesn't work either: on the next run, getPrevStandaloneSnapshot reads raw bytes from disk while pretty.Sprint(input) produces the mangled version, causing an asymmetric mismatch.

Proposed solution

A new config option (e.g. snaps.Raw()) that tells MatchStandaloneSnapshot to use fmt.Sprint (or just a string conversion) instead of pretty.Sprint:

snaps.WithConfig(snaps.Raw(), snaps.Ext(".Dockerfile")).MatchStandaloneSnapshot(t, content)

The implementation change in matchStandaloneSnapshot would be minimal:

// current
snapshot := pretty.Sprint(input)

// with Raw() option
var snapshot string
if c.raw {
    snapshot = fmt.Sprint(input)
} else {
    snapshot = pretty.Sprint(input)
}

Current workaround

We maintain a small helper that reimplements the standalone snapshot write/read/compare cycle without pretty.Sprint: https://github.com/wharflab/tally/blob/main/internal/testutil/snapshot.go

This works but duplicates path computation and snapshot management logic that go-snaps already handles well.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions