Question and scope
A Go service has intermittent unit, benchmark, and fuzz failures. Developers want request samples, performance summaries, and debug dumps without polluting successful CI runs or letting parallel tests overwrite one another. Using Go 1.26 testing.T.ArtifactDir, testing.B.ArtifactDir, and testing.F.ArtifactDir, design an artifact policy.
With go test -artifacts, Go 1.26 returns a persistent directory under the output directory; without it, the returned temporary directory is removed after the test. Separate code that writes evidence from the decision to retain it, and let the test framework control the directory lifecycle.
Context and boundaries
Focus on Go test code, concurrency isolation, CI archiving, and sensitive data. The CI platform provides upload, permissions, retention, and object storage; state the failure trigger, naming, size limits, redaction, and retry boundaries.
What the interviewer tests
- Whether you distinguish artifact directories for T, B, and F contexts.
- Whether failed tests retain evidence without turning successful runs into permanent noise.
- Whether you handle
t.Parallel, subtests, benchmark loops, and fuzz replay naming. - Whether files are retryable, bounded, archivable, and traceable to a commit and test name.
- Whether tokens, user data, private URLs, and core dumps are kept out of public CI artifacts.
30-second answer
“Every test gets its directory from ArtifactDir; filenames use the test path, run ID, and event sequence rather than guessed workspace paths. Code writes key evidence on failure, threshold breach, or explicit diagnostics. CI enables go test -artifacts and archives the directory, while local defaults use a temporary directory that is cleaned up. Writes use a temporary file and rename, with byte and count limits. A manifest links commit, package, test, platform, hash, and redaction status; upload failure alerts without changing the original test result.”
Step-by-step solution
- Use one artifact entry point. A test receives
testing.T,testing.B, or*testing.Fand calls its correspondingArtifactDir. Do not hard-codeos.TempDir, the working directory, or a private CI path into test logic.
- Separate retention from writing. Test code may write before or after a failure, but persistence is expected only when
go test -artifactsis enabled. Default runs use a temporary directory that is cleaned up; CI explicitly enables artifacts and uploads them.
- Design concurrent names. Build a logical key from package, test, run ID, subtest path, and a monotonic sequence. Sanitize separators and non-printable characters. Parallel instances must never share a fixed
debug.json.
- Write atomically with limits. Create a temporary file in the artifact directory, close it successfully, then rename it. Apply per-file, per-test, and per-run byte and count limits; overflows produce a summary rather than exhausting the runner.
func writeArtifact(t *testing.T, name string, data []byte) {
t.Helper()
dir := t.ArtifactDir()
path := filepath.Join(dir, safeName(name)+".json")
tmp, err := os.CreateTemp(dir, ".partial-")
if err != nil { t.Fatalf("create artifact: %v", err) }
defer tmp.Close()
if _, err := tmp.Write(data); err != nil { t.Fatalf("write artifact: %v", err) }
if err := tmp.Close(); err != nil { t.Fatalf("close artifact: %v", err) }
if err := os.Rename(tmp.Name(), path); err != nil { t.Fatalf("publish artifact: %v", err) }
}The example omits size checks, redaction, and portable filename handling; production code should make those shared test constraints.
- Trigger on failure or thresholds. Failed tests retain a minimal reproducer, request summary, trace identifier, and environment summary. Benchmarks save profiles or samples only on a regression threshold or explicit diagnostic mode. Fuzz tests save a replayable seed and a redacted, bounded input rather than a full sensitive request.
- Index CI artifacts. Emit a manifest containing commit SHA, package, test, Go version, OS/architecture, relative path, size, hash, and redaction status. Isolate uploads by run ID. An upload failure alerts but must not turn a failing assertion into a pass.
- Secure and clean up. Remove tokens, cookies, Authorization headers, personal data, and private hostnames before writing; disable core dumps by default. Use least-privilege reads and short retention, with automatic expiry. Downloads still require content-level review.
- Test the lifecycle. Cover a failing subtest, a parallel subtest, one
go test -artifactsrun, and one default run. Verify successful cleanup, indexable failure evidence, distinct retry IDs, and preservation of local evidence when upload is interrupted.
Model answer
All diagnostic files should originate from T, B, or F ArtifactDir; test logic should not know the physical path. Persistence is controlled by go test -artifacts: CI enables and archives it, while local runs use a temporary directory that is cleaned up. Names include package, test, subtest path, run ID, and sequence to prevent parallel collisions. Writes use a temporary file and rename, with byte and count limits.
Failed tests retain minimal inputs, request summaries, and environment data. Benchmarks retain profiles only when a regression threshold or explicit diagnostic mode triggers. Fuzz tests retain seeds and replayable inputs. Redaction happens before writing, and a manifest records commit, Go version, platform, hashes, and sizes. Upload failure alerts without changing test status. Regression tests cover parallelism, failure, default temporary storage, and persistent -artifacts storage.
Common mistakes
- Mistake: Treating ArtifactDir as permanent → Why it fails: the default temporary directory is removed after the test → Fix: enable
-artifactsin CI and configure archiving. - Mistake: Every parallel test writes
debug.json→ Why it fails: files overwrite or interleave → Fix: name by test path, run ID, and sequence. - Mistake: Uploading a complete HTTP request on failure → Why it fails: tokens and personal data leak → Fix: redact, summarize, and restrict retention.
- Mistake: Letting artifact-write failure make the test pass → Why it fails: failure evidence is lost and environment problems are hidden → Fix: fail or explicitly alert on required evidence without changing assertions.
- Mistake: Writing a profile every benchmark iteration → Why it fails: artifact volume and runtime become unbounded → Fix: collect only on a regression threshold or explicit diagnostics.
Follow-up questions and answers
Why not use os.TempDir directly?
ArtifactDir lets the test framework and CI own lifecycle decisions, gives T, B, and F one contract, and avoids executor-specific paths. os.TempDir is fine for disposable helper files, not as the artifact protocol.
How do fuzz artifacts remain replayable?
Record the Go version, package, test, seed, a hash of the bounded input, and required environment variables. For oversized or sensitive input, store a redacted summary and keep the full evidence only in protected storage.
When should a benchmark write artifacts?
Finish the benchmark and compare its baseline first. Write a profile only after a threshold regression, explicit -bench diagnostics, or failure. Include sample count, CPU, duration, and commit so normal runs do not become permanent archives.
Should an artifact upload failure fail CI?
An assertion failure must fail. Whether an upload failure blocks release depends on the team’s evidence level, but it must at least alert and preserve a local path. Network state must not be disguised as test status.
How do you handle retries?
Generate a distinct run ID for each attempt. Archive keys contain commit, platform, package, test, and attempt. Indexes may group attempts, but raw files never overwrite one another; record whether a retry reused the random seed.
References
- Go 1.26 Release Notes (Go official)
- testing package (Go official)
- Go Release History (Go official)
Interview checklist
Explain ArtifactDir’s lifecycle first, then add concurrent naming, atomic writes, failure triggers, manifests, redaction, CI archiving, and regression tests.
One-sentence takeaway
ArtifactDir provides the lifecycle entry point; reliable diagnostics still need isolation, limits, redaction, indexing, and replayable evidence.
Keep practicing
If CI runs fuzz, benchmark, and integration tests together, design a shared manifest, quota, and failure-priority upload scheduler.