|
1 | 1 | package guard |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
| 5 | + "context" |
4 | 6 | "errors" |
5 | 7 | "os" |
6 | 8 | "path/filepath" |
| 9 | + "slices" |
7 | 10 | "strings" |
8 | 11 | "testing" |
9 | 12 |
|
| 13 | + "github.com/donislawdev/TestingFilesGenerator/internal/cli" |
10 | 14 | "github.com/donislawdev/TestingFilesGenerator/internal/damage" |
11 | 15 | "github.com/donislawdev/TestingFilesGenerator/internal/engine" |
12 | 16 | "github.com/donislawdev/TestingFilesGenerator/internal/format" |
@@ -313,6 +317,91 @@ targets: |
313 | 317 | } |
314 | 318 | } |
315 | 319 |
|
| 320 | +// The command line refuses that pair as well, and writes nothing. |
| 321 | +// |
| 322 | +// The guard above asks recipe.Parse and only recipe.Parse, and that was enough |
| 323 | +// to be green through a build where this was broken. Measured on 2026-09-09 on |
| 324 | +// the 0.3.0 binary: the recipe was refused with code 3 while |
| 325 | +// --damage zero-head --expected accept ended with code 0 and left a manifest |
| 326 | +// on disk saying a deliberately damaged file should be accepted - the tool |
| 327 | +// lying in the one place its value lives. O199. |
| 328 | +// |
| 329 | +// Both halves are needed. Asking only the refusal would pass for a build that |
| 330 | +// refuses damage beside any expectation at all, and asking only that files are |
| 331 | +// written would pass for one that refuses nothing - so the second loop is the |
| 332 | +// wall detector and the first is the hole detector. |
| 333 | +// |
| 334 | +// The count of files is asked rather than the exit code alone: a refusal that |
| 335 | +// arrives after the writing has started is a refusal that came too late, and |
| 336 | +// the code by itself cannot tell those apart. |
| 337 | +func TestTheCommandLineRefusesDamageBesideAcceptToo(t *testing.T) { |
| 338 | + run := func(t *testing.T, extra ...string) (int, string, int) { |
| 339 | + t.Helper() |
| 340 | + dir := t.TempDir() |
| 341 | + var out, errOut bytes.Buffer |
| 342 | + args := append([]string{ |
| 343 | + "generate", "--format", "txt", "--size", "100", |
| 344 | + "--damage", damage.ZeroHead, "--out", dir, |
| 345 | + }, extra...) |
| 346 | + code := cli.Run(context.Background(), args, &out, &errOut) |
| 347 | + written, err := os.ReadDir(dir) |
| 348 | + if err != nil { |
| 349 | + t.Fatalf("reading the output directory: %v", err) |
| 350 | + } |
| 351 | + return code, errOut.String(), len(written) |
| 352 | + } |
| 353 | + |
| 354 | + code, said, files := run(t, "--expected", damage.RuledOutExpectation) |
| 355 | + if code != cli.ExitUsage { |
| 356 | + t.Errorf("damage beside %q ended with %d, expected %d - two flags that cancel each other are a fault in the invocation\nstderr: %s", |
| 357 | + damage.RuledOutExpectation, code, cli.ExitUsage, said) |
| 358 | + } |
| 359 | + if files != 0 { |
| 360 | + t.Errorf("the run was refused and still left %d file(s) behind", files) |
| 361 | + } |
| 362 | + if !strings.Contains(said, damage.RuledOutExpectation) { |
| 363 | + t.Errorf("the refusal does not name the word it turned down: %s", said) |
| 364 | + } |
| 365 | + |
| 366 | + // The other three are legitimate questions about a broken file, and a |
| 367 | + // build refusing them would be a wall rather than this rule. |
| 368 | + for _, outcome := range []string{"reject", "sanitize", "unspecified"} { |
| 369 | + if code, said, _ := run(t, "--expected", outcome); code != cli.ExitOK { |
| 370 | + t.Errorf("--expected %s beside damage ended with %d rather than %d: %s", |
| 371 | + outcome, code, cli.ExitOK, said) |
| 372 | + } |
| 373 | + } |
| 374 | + // And the expectation on its own is untouched. Every case above carries a |
| 375 | + // damage, so a build that refused accept for every run whatsoever would |
| 376 | + // look correct from all of them. |
| 377 | + dir := t.TempDir() |
| 378 | + var out, errOut bytes.Buffer |
| 379 | + if code := cli.Run(context.Background(), []string{ |
| 380 | + "generate", "--format", "txt", "--size", "100", |
| 381 | + "--expected", damage.RuledOutExpectation, "--out", dir, |
| 382 | + }, &out, &errOut); code != cli.ExitOK { |
| 383 | + t.Errorf("--expected %s with nothing damaged ended with %d rather than %d, which makes this a wall rather than a rule about damage: %s", |
| 384 | + damage.RuledOutExpectation, code, cli.ExitOK, errOut.String()) |
| 385 | + } |
| 386 | +} |
| 387 | + |
| 388 | +// The outcome damage rules out is the one the manifest and the recipe know. |
| 389 | +// |
| 390 | +// Three spellings of one word live in three packages that cannot import each |
| 391 | +// other - damage sits beside manifest rather than under it - so this compares |
| 392 | +// them rather than leaving them to drift. A rename in one place turns this red |
| 393 | +// instead of quietly producing a build where nothing is ever refused. |
| 394 | +func TestTheOutcomeDamageRulesOutIsTheOneTheManifestKnows(t *testing.T) { |
| 395 | + if damage.RuledOutExpectation != manifest.OutcomeAccept { |
| 396 | + t.Errorf("damage rules out %q and the manifest calls it %q, so nothing would ever match", |
| 397 | + damage.RuledOutExpectation, manifest.OutcomeAccept) |
| 398 | + } |
| 399 | + if !slices.Contains(recipe.Outcomes(), damage.RuledOutExpectation) { |
| 400 | + t.Errorf("damage rules out %q and a recipe does not accept that word at all: %v", |
| 401 | + damage.RuledOutExpectation, recipe.Outcomes()) |
| 402 | + } |
| 403 | +} |
| 404 | + |
316 | 405 | // A damage the build does not know is refused while the recipe is read, and |
317 | 406 | // the refusal names what there is. |
318 | 407 | // |
|
0 commit comments