{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/go/txtar/",
  "description": "txtar is a tiny plain-text archive format Russ Cox introduced in 2018 for multi-file test fixtures. The Go Playground, cmd/go's script tests, gopls's marker tests, and rsc.io/rf all reach for it.",
  "path": "/go/txtar/",
  "publishedAt": "2026-05-10T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "Testing",
    "Tooling"
  ],
  "textContent": "I ran into [txtar] today while poking around cmd/go's [testdata] directory and got curious\nabout why every test file looked like it had a tiny diff embedded in it. Turns out it's a\ntrivial archive format [Russ Cox introduced in 2018], and once I noticed it I started seeing\nit everywhere in Go tooling.\n\nA txtar archive looks like this:\n\nTwo file markers, two files, a free-text comment up top. That's the whole format. The\npackage doc says it outright: \"There are no possible syntax errors in a txtar archive.\"\n\nThe format\n\nThe [package doc comment] is the spec. The rules:\n\n- A marker line is exactly -- FILENAME -- at the start of a line. Three bytes --<space>\n  open the marker, three bytes <space>-- close it.\n- Anything before the first marker is the comment.\n- File data runs from one marker to the next, or to EOF.\n- Whitespace inside the marker is trimmed, so --   foo.go   -- parses as foo.go.\n- A missing trailing newline on the final file is treated as if it were there.\n\nThe format is text only. The package doc [explicitly] rules out binary content, file modes,\nsymlinks, and any escape mechanism for the marker syntax. The escape gap matters when a file\nbody happens to contain a line beginning with --  and ending with  --. The parser treats\nit as a new marker, and there's no way to quote it. Reach for tar or zip when any of\nthat matters.\n\nIt stays this small because it was [purpose-built] around three goals listed in the package\ndoc: stay hand-editable, store trees of text files for go command test cases, and diff\ncleanly in git history and code reviews. It first landed inside the early vgo modules\nprototype in 2018.\n\nParse it from a string\n\nThe Go API lives in [golang.org/x/tools/txtar]. Two types and two functions cover the common\ncase:\n\nParse doesn't return an error. The format can't fail to parse.\n\nParse returns slices that alias the input. Mutating the bytes you passed in will corrupt\nthe archive on the next read.\n\nRead it from disk\n\nParseFile does what you'd expect:\n\nSame Archive, just fed by os.ReadFile instead of a string literal.\n\nMount it as an fs.FS\n\ntxtar.FS, [added in July 2024], hands you a read-only fs.FS view over the archive\nwithout ever touching disk:\n\nAnything that takes an fs.FS works against the archive directly. A parser, a template\nengine, or a static-site generator reads its fixtures straight from the archive in memory.\nYou don't need a tempdir or an extraction step.\n\nFormat it back to bytes\n\ntxtar.Format(Archive) []byte is the inverse of Parse:\n\nThe package doesn't ship a write-files-to-disk helper. The canonical pattern is to walk\nar.Files, validate each path stays inside your destination, and write yourself. Go's\n[cmd/internal/script] package has an ExtractFiles method that does exactly that. The\n[golang.org/x/exp/cmd/txtar] CLI is another option, with txtar -x for extracting and\ntxtar <path> for archiving a file or directory.\n\nA golden test in one file\n\nSay you've got a function Format(in []byte) []byte that pretty-prints some text format you\ncare about. JSON, SQL, markdown, whatever. You want to feed it a stack of inputs without\nscattering ten-line files all over testdata. One txtar archive per case covers it. The\ncomment up top says what's being tested, -- in -- is the input, -- want -- is the\nexpected output.\n\ntestdata/empty_object.txt:\n\ntestdata/nested.txt:\n\nThe test globs the directory, parses each archive, and compares Format(in) against want:\n\nAdding a case is one new file in testdata/. The comment documents intent, and the input\nand expected output sit side by side. You skip the per-test setup boilerplate and the\nseparate golden/ directory that always drifts out of sync.\n\nThe same shape works for a multi-file fixture. Add a -- go.mod -- and three -- .go --\nfiles to one archive and you've got a hermetic mini-module to feed your linter, refactorer,\nor codegen tool. That's what cmd/go's script tests and gopls's marker tests do, with\nhundreds of fixtures each.\n\nWhen the comment is a script\n\nThe previous example used the archive as static data. cmd/go's script tests use it\ndifferently: the comment is a sequence of commands to run, and the file entries are the\nworkspace those commands operate on. [rogpeppe/go-internal/testscript] is the same engine\npackaged as a library you can call from any test.\n\nSay you want a smoke test for tree. You hand it a small project shaped via the file\nentries, and assert on the tree it prints. One archive does both jobs:\n\ntestdata/tree.txt:\n\nexec and cmp are testscript commands, not shell. exec runs a process, cmp compares\nits captured stdout against the file named want. testscript materializes every file entry\ninto a fresh temp directory before the script runs, so tree walks the four files above.\nThe -I want flag tells tree to skip the want file itself, since it'd otherwise show up\nin the listing.\n\nThe Go side is one function:\n\ntestscript.Run globs testdata/.txt, parses each archive, drops the file entries into a\nfresh temp directory, runs the script line by line, and reports a diff if cmp fails.\nAdding another case is one more .txt archive under testdata/.\n\nIn the wild\n\nThe 900+ .txt files in [src/cmd/go/testdata/script/] are txtar archives. The comment up\ntop is the script the test runs, and the files below are the workspace it runs in. The\n[README] in that directory says \"Each script is a text archive.\"\n\nSharing a multi-file snippet on the Go Playground encodes a txtar archive into the share\nURL. The code is in [playground/txtar.go], added by Brad Fitzpatrick in 2019. The pre-marker\ncomment is treated as prog.go, which keeps single-file shares backwards compatible.\n\ngopls's marker tests under [gopls/internal/test/marker/testdata/] are txtar files too. One\narchive packs the Go source, the golden output, a -- flags -- section, and the gopls\nsettings into one end-to-end LSP test case.\n\nRuss Cox's refactoring tool [rsc.io/rf] keeps each test case as a txtar archive. The comment\nis the refactor command, the files are the input, and -- stdout -- plus -- stderr --\ncarry the expected output.\n\n[rsc.io/script] and [rogpeppe/go-internal/testscript] both extract the script engine from\ncmd/go so you can run script fixtures in your own packages. Russ covers them in [Go\nTesting By Example] under \"Use txtar for multi-file test cases\".\n\nThe pattern repeats across most of them. Each archive holds one test case, with the script\nor input on top, the workspace below, and a -- name -- section for golden output when you\nneed one. gopls's marker tests put everything in named files like flags and\nsettings.json instead of a top/bottom split.\n\nWhy it caught on\n\nA directory full of fixture files is hard to review and harder to paste into a bug report.\ntxtar collapses all of it into one plain-text file that diffs cleanly in Gerrit and GitHub\nand drops into a chat or an issue. With txtar.FS you don't need to extract anything to run\na test against it.\n\nThe format is small enough that you'd implement it in an afternoon. The reason to use the\nupstream package is that everyone else in Go tooling already does, so your fixtures are\nportable to testscript, rsc.io/script, the Playground, and anything else that adds a\ntxtar reader later.\n\n> [!Gist]\n>\n> - txtar is -- filename -- markers separating file bodies, with free text on top as a\n>   comment. The format is text only, has no possible syntax errors, and uses the package\n>   doc comment as its spec.\n> - The package is golang.org/x/tools/txtar. Parse reads bytes, ParseFile reads a\n>   path, Format writes back. txtar.FS mounts an archive as a read-only fs.FS so tests\n>   can run without ever touching disk.\n> - [rogpeppe/go-internal/testscript] runs an archive as a script: the comment becomes\n>   shell-like commands, the file entries become the workspace those commands run against.\n>   Use it to drive a CLI test from one fixture per case.\n> - It's the file shape behind cmd/go's 900+ script tests, the Go Playground's multi-file\n>   shares, gopls's marker tests, and rsc.io/rf. Reach for it when you want one\n>   PR-friendly file per test case.\n\n\n\n\n[txtar]:\n    https://pkg.go.dev/golang.org/x/tools/txtar\n\n[testdata]:\n    https://github.com/golang/go/tree/master/src/cmd/go/testdata\n\n[Russ Cox introduced in 2018]:\n    https://go-review.googlesource.com/123359\n\n[package doc comment]:\n    https://github.com/golang/tools/blob/master/txtar/archive.go\n\n[explicitly]:\n    https://github.com/golang/tools/blob/a3954b5c7496c91c1095bd368722a4e80d793f28/txtar/archive.go#L13-L15\n\n[purpose-built]:\n    https://research.swtch.com/testing\n\n[golang.org/x/tools/txtar]:\n    https://pkg.go.dev/golang.org/x/tools/txtar\n\n[added in July 2024]:\n    https://go-review.googlesource.com/c/tools/+/598756\n\n[cmd/internal/script]:\n    https://github.com/golang/go/blob/master/src/cmd/internal/script/state.go\n\n[golang.org/x/exp/cmd/txtar]:\n    https://pkg.go.dev/golang.org/x/exp/cmd/txtar\n\n[src/cmd/go/testdata/script/]:\n    https://github.com/golang/go/tree/master/src/cmd/go/testdata/script\n\n[README]:\n    https://go.dev/src/cmd/go/testdata/script/README\n\n[playground/txtar.go]:\n    https://github.com/golang/playground/blob/master/txtar.go\n\n[gopls/internal/test/marker/testdata/]:\n    https://github.com/golang/tools/tree/master/gopls/internal/test/marker/testdata\n\n[rsc.io/rf]:\n    https://github.com/rsc/rf/tree/main/testdata\n\n[rsc.io/script]:\n    https://pkg.go.dev/rsc.io/script\n\n[rogpeppe/go-internal/testscript]:\n    https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript\n\n[Go Testing By Example]:\n    https://research.swtch.com/testing",
  "title": "A tour of txtar"
}