From 0d12bd4fd57c1a57eb13cf8157c1513b8b5f5017 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Thu, 24 Sep 2026 23:31:38 +0200 Subject: [PATCH 1/8] recipe: a space from outside ASCII at the end of a value is kept strings.TrimSpace took every Unicode white space character off the ends of an unquoted value, while YAML counts only the space and the tab. A name beginning with an ideographic space lost it and the file was written under another name, with nothing said (O243). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++ internal/guard/unicodespace_test.go | 106 ++++++++++++++++++++++++++++ internal/recipe/scalar.go | 14 +++- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 internal/guard/unicodespace_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b56e49b5..4193ab8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -507,6 +507,12 @@ because it turns other people's test suites red. ### Fixed +- **A space from outside ASCII at the start or the end of a recipe value is + kept.** A file name beginning with an ideographic space or a no break space + lost it, and the file was written under a different name than the recipe + asked for, with nothing said. A plain space or a tab at the ends of an + unquoted value is still not part of it, as in any YAML file. + - **A file name from 238 to 255 bytes long is written.** Every system stores such a name, and none of them got one: each file is written under a longer temporary name first, and that one was over the limit. The same held for a diff --git a/internal/guard/unicodespace_test.go b/internal/guard/unicodespace_test.go new file mode 100644 index 00000000..e1af8459 --- /dev/null +++ b/internal/guard/unicodespace_test.go @@ -0,0 +1,106 @@ +package guard + +import ( + "strings" + "testing" + "unicode" + + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/all" + "github.com/donislawdev/TestingFilesGenerator/internal/recipe" +) + +// A space from outside ASCII at either end of a name is part of the name. +// +// Measured on 2026-09-24 (O243): a recipe asking for a file whose name starts +// with an ideographic space, U+3000, got a file without it, and nothing said +// so. The parser took each value with strings.TrimSpace, which knows every +// white space character Unicode has, while YAML itself counts only the space +// and the tab. The library handed the character over intact and the reading +// code threw it away. It surfaced through the preset of unusual file names, +// which asks for exactly such a name and came back one short on every format. +// +// The guard beside it in compose_test.go puts every character in the MIDDLE +// of a value, on purpose, because the ends are trimmed. That is why it never +// saw this, and why this one asks about nothing but the ends. +// +// The characters come from the Unicode table rather than from a list typed +// here, so this asks about every one the language knows and not the few +// somebody thought of. ASCII ones are left out: a space or a tab at the end of +// an unquoted YAML value is not part of it by the rules of YAML. +func TestAUnicodeSpaceAtEitherEndOfANameIsKept(t *testing.T) { + var spaces []rune + for _, r16 := range unicode.White_Space.R16 { + for r := rune(r16.Lo); r <= rune(r16.Hi); r += rune(r16.Stride) { + if r > unicode.MaxASCII { + spaces = append(spaces, r) + } + } + } + for _, r32 := range unicode.White_Space.R32 { + for r := rune(r32.Lo); r <= rune(r32.Hi); r += rune(r32.Stride) { + spaces = append(spaces, r) + } + } + // The ideographic space the defect was found with, and the no break space + // every keyboard layout can type, have to be among them, or this is asking + // about some other table. + if !containsRune(spaces, 0x3000) || !containsRune(spaces, 0xA0) { + t.Fatalf("the table gave %d characters and not the two this is about: %U", len(spaces), spaces) + } + + checked := 0 + for _, r := range spaces { + for _, name := range []string{string(r) + "report.txt", "report.txt" + string(r)} { + // The state this guard is about: a name that a reader trimming + // Unicode white space would shorten. Asserted, not assumed. + if strings.TrimSpace(name) == name { + t.Fatalf("%q is not a name that trimming would change, so it tests nothing", name) + } + + written := "version: 1\ntargets:\n - id: t\n format: txt\n size: 1kb\n name: " + name + "\n" + if got, ok := nameReadFrom(t, []byte(written)); ok && got != name { + t.Errorf("a recipe written by hand asked for %+q and read it as %+q", name, got) + } + + composed, err := recipe.Compose(recipe.Document{Targets: []recipe.TargetDraft{{ + ID: "t", Format: "txt", Size: "1kb", Name: name, + }}}) + if err != nil { + t.Errorf("composing a recipe with the name %+q was refused: %v", name, err) + continue + } + if got, ok := nameReadFrom(t, composed); ok && got != name { + t.Errorf("a composed recipe asked for %+q and read it as %+q\n%s", name, got, composed) + } + checked++ + } + } + if checked == 0 { + t.Fatal("no name was checked - this guard would pass without asking anything") + } + t.Logf("%d names, %d characters at the start and at the end", checked, len(spaces)) +} + +// nameReadFrom parses a one target recipe and gives back the name it asks for. +func nameReadFrom(t *testing.T, src []byte) (string, bool) { + t.Helper() + rec, err := recipe.Parse(src, "guard") + if err != nil { + t.Errorf("the recipe was refused: %v\n%s", err, src) + return "", false + } + if len(rec.Targets) != 1 { + t.Errorf("the recipe parsed to %d targets\n%s", len(rec.Targets), src) + return "", false + } + return rec.Targets[0].Name, true +} + +func containsRune(rs []rune, want rune) bool { + for _, r := range rs { + if r == want { + return true + } + } + return false +} diff --git a/internal/recipe/scalar.go b/internal/recipe/scalar.go index d94bf8a0..759fbfc1 100644 --- a/internal/recipe/scalar.go +++ b/internal/recipe/scalar.go @@ -47,6 +47,18 @@ type scalar struct { quoted bool } +// yamlBlank is what may stand around a value without being part of it: the +// space and the tab YAML counts as white, and the line break the rendered node +// ends with. +// +// Not strings.TrimSpace, which was here until 2026-09-24 and knows every white +// space character Unicode has. A name beginning with an ideographic space or a +// no break space lost it on the way in, and the run wrote a file under a +// different name than the recipe asked for without a word (O243). The library +// had handed the character over intact. A space from outside ASCII at the end +// of a value is part of the value in YAML, so it is part of it here. +const yamlBlank = " \t\r\n" + // UnmarshalYAML takes the node as it was written. // // The source text of the node is the whole reason this type works. Measured @@ -71,7 +83,7 @@ type scalar struct { // instead of the node would have shown. func (s *scalar) UnmarshalYAML(n ast.Node) error { b := []byte(n.String()) - t := strings.TrimSpace(string(b)) + t := strings.Trim(string(b), yamlBlank) if len(t) >= 2 { first, last := t[0], t[len(t)-1] if (first == '"' && last == '"') || (first == '\'' && last == '\'') { From 69e34f7f70a3dfee92ce5ad6c689f2bdbcfe062b Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Thu, 24 Sep 2026 23:36:39 +0200 Subject: [PATCH 2/8] recipe: a character nobody can see is composed as an escape recipe.Compose wrote a right to left override, a zero width space or a line separator raw into an unquoted value, so an ejected recipe read as something other than what it held and PyYAML refused it at the line separator (O244). Such a value is written in double quotes with escapes now. Every other value keeps its bytes, and the pinned eject sums did not move. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 10 +++ internal/core/unseen.go | 31 ++++++++++ internal/guard/unseen_test.go | 113 ++++++++++++++++++++++++++++++++++ internal/recipe/compose.go | 67 ++++++++++++++++---- 4 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 internal/core/unseen.go create mode 100644 internal/guard/unseen_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4193ab8a..6682a294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -507,6 +507,16 @@ because it turns other people's test suites red. ### Fixed +- **A recipe the tool writes shows a character nobody can see as an escape.** + `tfg preset eject` wrote a right to left override, a zero width space or a + line separator into the recipe as it was, so the file read as something + other than what it held, and a YAML 1.1 reader such as PyYAML refused it at + the line separator. Such a value is written in double quotes now, with the + character as an escape that every YAML reader turns back into the same + character. Every other value is written as before. A run started in the + window records a different recipe hash only when a value holds such a + character. + - **A space from outside ASCII at the start or the end of a recipe value is kept.** A file name beginning with an ideographic space or a no break space lost it, and the file was written under a different name than the recipe diff --git a/internal/core/unseen.go b/internal/core/unseen.go new file mode 100644 index 00000000..b56de4ab --- /dev/null +++ b/internal/core/unseen.go @@ -0,0 +1,31 @@ +package core + +import "strconv" + +// HoldsUnseen reports whether s holds a character a person reading it cannot +// see: a character that changes the direction of the text around it, one of +// no width, a byte order mark, a separator that breaks a line without being a +// line break, a space that is not the space bar's, a tag character, and every +// other one Go does not count as printable. +// +// It exists because file names are exactly where such characters are put on +// purpose. A name with a right to left override shows its extension in the +// wrong place, one with a zero width space prints as a name it is not, and +// both are test cases this tool writes (docs/NAMES-PRESET-2026-09-24.md). The +// file keeps its name. What a person reads about it has to show the character +// rather than let it act (O241), and a recipe has to carry it in a form that +// can be read and edited (O244). +// +// The class is strconv.IsPrint turned around, and that is a choice: it is the +// class %q escapes, and the refusals of this tool have quoted names with %q +// all along. One rule means one name looks the same in a refusal, in a report +// and in a recipe. A combining mark is printable and stays as it is. The +// space is printable, every other space is not. +func HoldsUnseen(s string) bool { + for _, r := range s { + if !strconv.IsPrint(r) { + return true + } + } + return false +} diff --git a/internal/guard/unseen_test.go b/internal/guard/unseen_test.go new file mode 100644 index 00000000..74c36854 --- /dev/null +++ b/internal/guard/unseen_test.go @@ -0,0 +1,113 @@ +package guard + +import ( + "testing" + "unicode" + + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/all" + "github.com/donislawdev/TestingFilesGenerator/internal/recipe" +) + +// unseenHere is a character a person reading a line cannot see, worked out +// from the Unicode tables rather than asked of the code under test: anything +// that is not a letter, a mark, a number, punctuation or a symbol, apart from +// the plain space. A guard that imported the class from the code would agree +// with it whatever it did. +func unseenHere(r rune) bool { + return r != ' ' && !unicode.In(r, unicode.L, unicode.M, unicode.N, unicode.P, unicode.S) +} + +// unseenSample is the characters these guards ask about: every format +// character, every separator and every control character above the ones a +// recipe refuses outright, from the tables, and one each from private use and +// from the unassigned range. +func unseenSample() []rune { + var out []rune + add := func(t *unicode.RangeTable) { + for _, r16 := range t.R16 { + for r := rune(r16.Lo); r <= rune(r16.Hi); r += rune(r16.Stride) { + out = append(out, r) + } + } + for _, r32 := range t.R32 { + for r := rune(r32.Lo); r <= rune(r32.Hi); r += rune(r32.Stride) { + out = append(out, r) + } + } + } + add(unicode.Cf) + add(unicode.Zl) + add(unicode.Zp) + add(unicode.Zs) + for r := rune(0x80); r <= 0x9F; r++ { + out = append(out, r) + } + out = append(out, 0xE000, 0x0378) + + kept := out[:0] + for _, r := range out { + if unseenHere(r) { + kept = append(kept, r) + } + } + return kept +} + +// A character nobody can see goes into a composed recipe as an escape and +// comes back out as itself. +// +// Measured on 2026-09-24 (O244): the library wrote a right to left override, a +// zero width space, a byte order mark and a line separator raw into unquoted +// values. A person editing the recipe could not see what was in it, and PyYAML +// refused the document at the line separator. The preset of unusual file names +// ejects exactly such a recipe, and a recipe composed in the window can hold one +// too. +// +// Both halves are asked, because either alone is satisfied by the wrong code: +// nothing raw in the source is what writing the value as an empty string would +// also give, and the value coming back is what writing it raw gave before. +func TestACharacterNobodyCanSeeIsWrittenAsAnEscapeAndReadBackAsItself(t *testing.T) { + sample := unseenSample() + // The ones the defect was found with have to be among them, or this is + // asking about some other table. + for _, must := range []rune{0x202E, 0x200B, 0xFEFF, 0x2028, 0x3000, 0xA0, 0xE0068} { + if !containsRune(sample, must) { + t.Fatalf("U+%04X is not in the sample of %d characters", must, len(sample)) + } + } + + checked := 0 + for _, r := range sample { + for _, value := range []string{string(r) + "b.txt", "a" + string(r) + "b.txt", "ab.txt" + string(r)} { + src, err := recipe.Compose(recipe.Document{Targets: []recipe.TargetDraft{{ + ID: "t", Format: "txt", Size: "1kb", Name: value, Group: value, + }}}) + if err != nil { + t.Errorf("U+%04X: composing was refused: %v", r, err) + continue + } + for _, c := range string(src) { + if c != '\n' && unseenHere(c) { + t.Errorf("U+%04X: the composed recipe carries U+%04X raw\n%s", r, c, src) + break + } + } + rec, err := recipe.Parse(src, "composed") + if err != nil { + t.Errorf("U+%04X: the composed recipe was refused: %v\n%s", r, err, src) + continue + } + if got := rec.Targets[0].Name; got != value { + t.Errorf("U+%04X: asked for the name %+q and read %+q\n%s", r, value, got, src) + } + if got := rec.Targets[0].Group; got != value { + t.Errorf("U+%04X: asked for the group %+q and read %+q\n%s", r, value, got, src) + } + checked++ + } + } + if checked < 3*100 { + t.Fatalf("only %d values were checked - the tables gave less than they should", checked) + } + t.Logf("%d characters, %d values composed and read back", len(sample), checked) +} diff --git a/internal/recipe/compose.go b/internal/recipe/compose.go index c06a0b3d..6f277444 100644 --- a/internal/recipe/compose.go +++ b/internal/recipe/compose.go @@ -3,6 +3,7 @@ package recipe import ( "fmt" "strconv" + "unicode/utf8" "github.com/goccy/go-yaml" @@ -111,12 +112,12 @@ func Compose(d Document) ([]byte, error) { doc := yaml.MapSlice{{Key: "version", Value: SchemaVersion}} if d.Seed != "" { - doc = append(doc, yaml.MapItem{Key: "seed", Value: d.Seed}) + doc = append(doc, yaml.MapItem{Key: "seed", Value: written(d.Seed)}) } // The preset before the targets, because that is the order the run // takes them in. if d.Extends != "" { - doc = append(doc, yaml.MapItem{Key: KeyExtends, Value: presetScheme + d.Extends}) + doc = append(doc, yaml.MapItem{Key: KeyExtends, Value: written(presetScheme + d.Extends)}) } if with := withSection(d); len(with) > 0 { doc = append(doc, yaml.MapItem{Key: KeyWith, Value: with}) @@ -160,7 +161,7 @@ func Compose(d Document) ([]byte, error) { func withSection(d Document) yaml.MapSlice { var with yaml.MapSlice for _, name := range sortedKeys(d.With) { - with = append(with, yaml.MapItem{Key: name, Value: d.With[name]}) + with = append(with, yaml.MapItem{Key: name, Value: written(d.With[name])}) } return with } @@ -168,10 +169,10 @@ func withSection(d Document) yaml.MapSlice { func outputSection(d Document) yaml.MapSlice { var out yaml.MapSlice if d.OutDir != "" { - out = append(out, yaml.MapItem{Key: "dir", Value: d.OutDir}) + out = append(out, yaml.MapItem{Key: "dir", Value: written(d.OutDir)}) } if d.Manifest != "" { - out = append(out, yaml.MapItem{Key: "manifest", Value: d.Manifest}) + out = append(out, yaml.MapItem{Key: "manifest", Value: written(d.Manifest)}) } return out } @@ -182,7 +183,7 @@ func targetEntry(t TargetDraft) yaml.MapSlice { entry := yaml.MapSlice{} add := func(key, value string) { if value != "" { - entry = append(entry, yaml.MapItem{Key: key, Value: value}) + entry = append(entry, yaml.MapItem{Key: key, Value: written(value)}) } } // The keys where a number belongs are written as a number. See bareNumber. @@ -210,7 +211,7 @@ func targetEntry(t TargetDraft) yaml.MapSlice { // hashed into the manifest. Two runs of one screen have to compose the // same bytes or recipe_hash would move on its own. for _, name := range sortedKeys(t.Properties) { - props = append(props, yaml.MapItem{Key: name, Value: t.Properties[name]}) + props = append(props, yaml.MapItem{Key: name, Value: written(t.Properties[name])}) } entry = append(entry, yaml.MapItem{Key: "properties", Value: props}) } @@ -219,7 +220,7 @@ func targetEntry(t TargetDraft) yaml.MapSlice { for _, c := range t.Contains { one := yaml.MapSlice{} if c.Format != "" { - one = append(one, yaml.MapItem{Key: "format", Value: c.Format}) + one = append(one, yaml.MapItem{Key: "format", Value: written(c.Format)}) } if c.Count != "" { one = append(one, yaml.MapItem{Key: "count", Value: bareNumber(c.Count)}) @@ -254,11 +255,53 @@ func targetEntry(t TargetDraft) yaml.MapSlice { func bareNumber(value string) any { n, err := strconv.ParseInt(value, 10, 64) if err != nil || n < 0 || value != strconv.FormatInt(n, 10) { - return value + return written(value) } return n } +// written is a value the way the document carries it: as itself, unless it +// holds a character nobody reading the file can see. +// +// Such a value is written in double quotes with that character as an escape, +// and everything else in it as it is. Measured on 2026-09-24 (O244), when the +// preset of unusual file names put a right to left override, a zero width +// space, a byte order mark and a line separator into a recipe: the library +// wrote every one of them raw into an unquoted value. A person editing that +// recipe - which is what the header of an ejected one invites - could not see +// what they were editing, and a YAML 1.1 reader (PyYAML 6.0.3) refused the +// whole document at the line separator, which it takes for a line break. +// +// A value with nothing of the kind is written exactly as before, so no recipe +// this tool composed until then moves by a byte. +func written(value string) any { + if !core.HoldsUnseen(value) || !utf8.ValidString(value) { + return value + } + return escapedText(value) +} + +// escapedText is a value that goes into the document in double quotes, with +// escapes. +type escapedText string + +// MarshalYAML writes the value the way Go quotes a string, and that is YAML's +// double quoted form for everything that can reach here. The library takes +// the bytes as they are rather than choosing a style of its own - measured on +// 2026-09-24 on ten names, each read back as itself by the library, by Parse +// and by PyYAML. +// +// Go writes a quote and a backslash with a backslash in front, a character it +// cannot print as a backslash, a u and four hex digits, or a capital U and +// eight past the first plane, and a control character by its short name or as +// a backslash, an x and two hex digits. YAML reads every one of those the same +// way. The one form where they part is a byte that is not UTF-8, which Go +// writes as an x escape and YAML would read as a character - and written sends +// such a value on unchanged rather than here. +func (e escapedText) MarshalYAML() ([]byte, error) { + return []byte(strconv.Quote(string(e))), nil +} + // expectationEntry writes the short form when there is no reason and the long // one when there is, which is the same choice a person writing the file by hand // makes. Nil when nothing was stated. @@ -271,11 +314,11 @@ func expectationEntry(t TargetDraft) any { case t.Expected == "" && t.ExpectedReason == "": return nil case t.ExpectedReason == "": - return t.Expected + return written(t.Expected) default: return yaml.MapSlice{ - {Key: "outcome", Value: t.Expected}, - {Key: "reason", Value: t.ExpectedReason}, + {Key: "outcome", Value: written(t.Expected)}, + {Key: "reason", Value: written(t.ExpectedReason)}, } } } From 9386d9c85e7c04884ec696b898f48a3b6404f051 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Thu, 24 Sep 2026 23:50:38 +0200 Subject: [PATCH 3/8] cli: a name nobody can read is shown as an escape verify, cleanup, the notes of a run, the collision refusals and the lines about an output directory printed a right to left override or a zero width space as it was, so a report named files other than the ones on the disk (O241). core.Shown writes such a character the way %q would, without quotes, and leaves every other name as it was. The manifest and --json keep the exact name. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 10 + internal/audit/audit.go | 5 +- internal/cli/cleanup.go | 6 +- internal/cli/generate.go | 8 +- internal/cli/verify.go | 7 +- internal/core/unseen.go | 58 +++++- internal/engine/engine.go | 6 +- internal/engine/errors.go | 8 +- internal/engine/names.go | 10 +- internal/engine/preflight.go | 2 +- internal/guard/unseenoutput_test.go | 298 ++++++++++++++++++++++++++++ internal/manifest/manifest.go | 4 +- 12 files changed, 395 insertions(+), 27 deletions(-) create mode 100644 internal/guard/unseenoutput_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6682a294..fc804a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -507,6 +507,16 @@ because it turns other people's test suites red. ### Fixed +- **A report shows a character nobody can see in a file name as an escape.** + `verify`, `cleanup`, the notes of a run, a refusal of two names that collide + or of a taken name, and the lines about an output directory printed such a + character as it was. A right to left override then made the terminal draw + another name than the one on the disk, and a zero width space made two + names look the same. Such a character is printed as an escape now, such as + `\u202e` for a right to left override. A name without one is printed + as before. The manifest and every `--json` report still carry the + exact name. + - **A recipe the tool writes shows a character nobody can see as an escape.** `tfg preset eject` wrote a right to left override, a zero width space or a line separator into the recipe as it was, so the file read as something diff --git a/internal/audit/audit.go b/internal/audit/audit.go index 7591f1d1..890a6ea8 100644 --- a/internal/audit/audit.go +++ b/internal/audit/audit.go @@ -110,6 +110,9 @@ type Difference struct { } func (d Difference) String() string { + // Shown on a copy, so the paths a program compares stay as they are. See + // core.Shown for the name this printed wrongly (O241). + d.Path, d.Want, d.Got = core.Shown(d.Path), core.Shown(d.Want), core.Shown(d.Got) switch d.Kind { case Missing: return fmt.Sprintf("missing %s", d.Path) @@ -191,7 +194,7 @@ func (e *EscapeError) Error() string { "the manifest lists %q, which lands outside %s once the links on the way are followed. "+ "This tool never reads or removes anything outside the directory it was pointed at, so it will not act on this manifest. "+ "Check that the directory is the one the run wrote to, and that nothing inside it points elsewhere.", - e.Path, e.Dir) + e.Path, core.Shown(e.Dir)) } // resolved turns a manifest entry into the path on disk, refusing one that diff --git a/internal/cli/cleanup.go b/internal/cli/cleanup.go index b07aea52..c0bebb0c 100644 --- a/internal/cli/cleanup.go +++ b/internal/cli/cleanup.go @@ -120,10 +120,10 @@ func previewCleanup(cands []audit.Candidate, path, dir string, force, asJSON boo fmt.Fprintf(out, "%s would be removed from %s:\n", core.Count(countRemovable(cands, force), "file", "files"), dir) for _, c := range cands { if c.Removable(force) { - fmt.Fprintf(out, " remove %s\n", c.Path) + fmt.Fprintf(out, " remove %s\n", core.Shown(c.Path)) continue } - fmt.Fprintf(out, " keep %s - %s\n", c.Path, skipNote(c, force)) + fmt.Fprintf(out, " keep %s - %s\n", core.Shown(c.Path), skipNote(c, force)) } fmt.Fprintf(errOut, "Nothing was removed. Run the same command with --yes to remove them.\n") return ExitOK @@ -153,7 +153,7 @@ func applyCleanup(ctx context.Context, cands []audit.Candidate, path, dir string } report.Files = append(report.Files, cleanupEntry{Path: o.Path, Action: "kept", Reason: o.Reason}) if !asJSON { - fmt.Fprintf(errOut, "kept %s - %s\n", o.Path, o.Reason) + fmt.Fprintf(errOut, "kept %s - %s\n", core.Shown(o.Path), core.Shown(o.Reason)) } } // Kept counts every entry that is not removed, which is what the entries diff --git a/internal/cli/generate.go b/internal/cli/generate.go index fd3d674d..4947e800 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -564,7 +564,7 @@ func echoBoundaries(targets []engine.Target, planned []engine.PlannedFile, errOu fmt.Fprintf(errOut, "boundary %q around %s:\n", t.ID, core.ExactBytes(t.BoundaryLimit)) for _, f := range planned { if f.Target == t { - fmt.Fprintf(errOut, " %-26s %s\n", f.Name, core.ExactBytes(f.Plan.Bytes)) + fmt.Fprintf(errOut, " %-26s %s\n", core.Shown(f.Name), core.ExactBytes(f.Plan.Bytes)) } } @@ -656,7 +656,7 @@ func saveManifest(res *engine.Result, opt engine.Options, errOut io.Writer) int // way is a chance for the saver and the claim to mean different files. path := engine.ManifestPath(opt) if err := res.Manifest.Save(path); err != nil { - fmt.Fprintf(errOut, "tfg: cannot write the manifest to %s: %s\n", path, describeError(err)) + fmt.Fprintf(errOut, "tfg: cannot write the manifest to %s: %s\n", core.Shown(path), core.Shown(describeError(err))) // What that leaves behind, because the line above is about the manifest // and the person's problem is the files. Rule 6: a run that wrote files // nothing can remove says so rather than leaving it to be discovered by @@ -667,10 +667,10 @@ func saveManifest(res *engine.Result, opt engine.Options, errOut io.Writer) int if n := len(res.Manifest.Files); n > 0 { fmt.Fprintf(errOut, "tfg: %s written and nothing to record what this run left. Cleanup works from a manifest, so clearing %s is a job by hand.\n", - core.Count(n, "file", "files"), opt.OutDir) + core.Count(n, "file", "files"), core.Shown(opt.OutDir)) } return ExitIO } - fmt.Fprintf(errOut, "manifest: %s\n", path) + fmt.Fprintf(errOut, "manifest: %s\n", core.Shown(path)) return ExitOK } diff --git a/internal/cli/verify.go b/internal/cli/verify.go index aff76db7..af45912a 100644 --- a/internal/cli/verify.go +++ b/internal/cli/verify.go @@ -84,7 +84,7 @@ Flags: // outside the directory used to arrive here as exit code 130. if verifyErr != nil { if !errors.Is(verifyErr, context.Canceled) { - fmt.Fprintf(errOut, "tfg: %s\n", describeError(verifyErr)) + fmt.Fprintf(errOut, "tfg: %s\n", core.Shown(describeError(verifyErr))) return classify(verifyErr) } fmt.Fprintf(errOut, "tfg: verify was interrupted after %s and did not check everything.\n", core.Count(len(diffs), "difference", "differences")) @@ -215,11 +215,11 @@ func echoOtherRuns(diffs []audit.Difference, errOut io.Writer) { for _, name := range names { files := byRecord[name] if len(files) == 0 { - fmt.Fprintf(errOut, "note: %s is another run's record, and nothing else here belongs to it.\n", name) + fmt.Fprintf(errOut, "note: %s is another run's record, and nothing else here belongs to it.\n", core.Shown(name)) continue } fmt.Fprintf(errOut, "note: %s is another run's record. %s here %s to it: %s.\n", - name, core.Count(len(files), "file", "files"), belongs(len(files)), someOf(files)) + core.Shown(name), core.Count(len(files), "file", "files"), belongs(len(files)), someOf(files)) } } @@ -249,6 +249,7 @@ func groupedByRecord(diffs []audit.Difference) map[string][]string { // someOf names the first few and counts the rest. func someOf(names []string) string { + names = core.ShownEach(names) if len(names) <= otherRunExamples { return strings.Join(names, ", ") } diff --git a/internal/core/unseen.go b/internal/core/unseen.go index b56de4ab..9c66003c 100644 --- a/internal/core/unseen.go +++ b/internal/core/unseen.go @@ -1,6 +1,62 @@ package core -import "strconv" +import ( + "strconv" + "strings" + "unicode/utf8" +) + +// Shown is s the way a person should read it: every character HoldsUnseen +// finds, and every byte that is not UTF-8, written as the escape %q would use +// for it, and nothing else changed. No quotes are added. +// +// For every line this tool prints about a name or a path that came from a +// recipe, a preset, a manifest or a directory listing (O241). Measured on +// 2026-09-24: verify reported a missing "photo", right to left override, +// "gpj.txt" as the terminal drew it, which is "phototxt.jpg", and an extra +// "in", zero width space, "voice.txt" as "invoice.txt" - a report naming files +// other than the ones on the disk, two of which could not be told apart. +// +// Without quotes, because a name holding nothing of the kind comes out byte +// for byte as it always did, and every report line of every run that never +// met such a name stays what scripts and people already read. The escape is +// not ambiguous inside a file name: a backslash is refused in one on every +// system (engine/filename.go). In a Windows path it reads as a separator +// followed by a letter and a number, which a person does not mistake for one. +// +// Never for what a program reads. The manifest and every --json report carry +// the name exactly, because a program compares it byte for byte. +func Shown(s string) string { + if !HoldsUnseen(s) && utf8.ValidString(s) { + return s + } + var b strings.Builder + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + switch { + case r == utf8.RuneError && size == 1: + q := strconv.Quote(s[i : i+1]) + b.WriteString(q[1 : len(q)-1]) + case !strconv.IsPrint(r): + q := strconv.QuoteRune(r) + b.WriteString(q[1 : len(q)-1]) + default: + b.WriteString(s[i : i+size]) + } + i += size + } + return b.String() +} + +// ShownEach is Shown for every name of a list, for the lines that name a few +// files one after another. +func ShownEach(names []string) []string { + out := make([]string, len(names)) + for i, name := range names { + out[i] = Shown(name) + } + return out +} // HoldsUnseen reports whether s holds a character a person reading it cannot // see: a character that changes the direction of the text around it, one of diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 976d74f4..e5f5c1e6 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -491,7 +491,7 @@ func Run(ctx context.Context, files []PlannedFile, opt Options) (*Result, error) } if err := os.MkdirAll(opt.OutDir, 0o755); err != nil { - return res, fmt.Errorf("cannot create the output directory %s: %w", opt.OutDir, err) + return res, fmt.Errorf("cannot create the output directory %s: %w", core.Shown(opt.OutDir), err) } // The directory is taken before the manifest name is, and the two are not @@ -511,7 +511,7 @@ func Run(ctx context.Context, files []PlannedFile, opt Options) (*Result, error) if errors.Is(err, fs.ErrExist) { return res, &RunInProgressError{Path: lockPath, Dir: opt.OutDir} } - return res, fmt.Errorf("cannot start a run in %s: %w", opt.OutDir, err) + return res, fmt.Errorf("cannot start a run in %s: %w", core.Shown(opt.OutDir), err) } // Given back however this run ends, including one stopped part way: the // signal cancels the context, Run returns, and this runs. What it cannot @@ -538,7 +538,7 @@ func Run(ctx context.Context, files []PlannedFile, opt Options) (*Result, error) if errors.Is(err, fs.ErrExist) { return res, &CollisionError{Path: manifestPath, Manifest: true} } - return res, fmt.Errorf("cannot start a run in %s: %w", opt.OutDir, err) + return res, fmt.Errorf("cannot start a run in %s: %w", core.Shown(opt.OutDir), err) } // Past this point the run owns the name and may write. Started says so, and diff --git a/internal/engine/errors.go b/internal/engine/errors.go index a53da958..49ef0006 100644 --- a/internal/engine/errors.go +++ b/internal/engine/errors.go @@ -205,7 +205,7 @@ type SpaceError struct { func (e *SpaceError) Error() string { return fmt.Sprintf( "this run needs %d B and %s has %d B free - nothing was written. Ask for fewer files or a smaller size, or write to another disk by changing the output directory", - e.Needed, e.Path, e.Available) + e.Needed, core.Shown(e.Path), e.Available) } // RunInProgressError is refusing to start because another run holds this @@ -228,7 +228,7 @@ type RunInProgressError struct { func (e *RunInProgressError) Error() string { return fmt.Sprintf( "another run is already writing into %s, so this one will not start. Two runs writing into one directory can write over each other's files without either of them saying so. Wait for it to finish, or generate into a different directory. If nothing is running, that run was killed before it could tidy up - remove %s and try again", - e.Dir, e.Path) + core.Shown(e.Dir), core.Shown(e.Path)) } // CollisionError is refusing to write over something that is already there. @@ -249,9 +249,9 @@ func (e *CollisionError) Error() string { if e.Manifest { return fmt.Sprintf( "%s already exists and this run will not write over it. It is the only record of what an earlier run wrote, so replacing it would leave those files with nothing to remove them by. Generate into an empty directory, or move the old manifest aside", - e.Path) + core.Shown(e.Path)) } return fmt.Sprintf( "%s already exists and this run will not write over it. Generate into an empty directory, or remove the file first", - e.Path) + core.Shown(e.Path)) } diff --git a/internal/engine/names.go b/internal/engine/names.go index 80a6c733..16324229 100644 --- a/internal/engine/names.go +++ b/internal/engine/names.go @@ -49,7 +49,7 @@ func claimFileName(names map[string]nameOwner, position int, id, name string) er return &RecipeError{ Setting: core.TargetAddress(position, SettingName), Detail: fmt.Sprintf("target %q produces a file named %s, and that is the name this run gives its manifest", - id, name), + id, core.Shown(name)), Because: "both are written into the output directory, so the file would take the name the manifest needs and the run would end with files and nothing to remove them by", Remedy: "Give the target a name template containing " + indexToken + ", or name the manifest something else", } @@ -134,13 +134,13 @@ func collisionKey(name string) string { func collisionDetail(owner nameOwner, id, name string) string { switch { case owner.name == name: - return fmt.Sprintf("targets %q and %q both produce a file named %s", owner.id, id, name) + return fmt.Sprintf("targets %q and %q both produce a file named %s", owner.id, id, core.Shown(name)) // Spelling before case, because normalising does not touch case and so a // pair that survives this one really is a difference of case. case norm.NFC.String(owner.name) == norm.NFC.String(name): return fmt.Sprintf( "targets %q and %q produce the names %s and %s. Those print the same because they are one name spelled two ways, an accented letter against the plain letter with its accent as a separate character. macOS stores both under one name, so one file would be written over the other and the manifest would describe both", - owner.id, id, owner.name, name) + owner.id, id, core.Shown(owner.name), core.Shown(name)) // Lowercasing rather than strings.EqualFold, and a guard caught the // difference on 2026-08-26. EqualFold folds simply, which puts the LONG s // in the same orbit as s - so "maſs.txt" against "mass.txt" was answered @@ -152,7 +152,7 @@ func collisionDetail(owner nameOwner, id, name string) string { case strings.ToLower(norm.NFC.String(owner.name)) == strings.ToLower(norm.NFC.String(name)): return fmt.Sprintf( "targets %q and %q produce the names %s and %s, which differ only in case. Most filesystems treat those as one file, so one would be written over the other and the manifest would describe both", - owner.id, id, owner.name, name) + owner.id, id, core.Shown(owner.name), core.Shown(name)) default: // The fourth kind, unreachable until collisionKey started folding on // 2026-08-26. It is not a difference of case and not a difference of @@ -161,6 +161,6 @@ func collisionDetail(owner nameOwner, id, name string) string { // accent, which is worse than saying nothing. return fmt.Sprintf( "targets %q and %q produce the names %s and %s. Those are different letters that mean the same one - the sharp s against ss, the long s against s, a ligature against the letters in it. macOS stores both under one name, so one file would be written over the other and the manifest would describe both", - owner.id, id, owner.name, name) + owner.id, id, core.Shown(owner.name), core.Shown(name)) } } diff --git a/internal/engine/preflight.go b/internal/engine/preflight.go index f25608f0..12958e0f 100644 --- a/internal/engine/preflight.go +++ b/internal/engine/preflight.go @@ -50,7 +50,7 @@ func preflight(ctx context.Context, files []PlannedFile, opt Options) error { // "missing", "no permission" and "already there". if info, err := os.Stat(opt.OutDir); err == nil && !info.IsDir() { return &RecipeError{Setting: SettingOutDir, - Detail: fmt.Sprintf("the output directory %s is a file, not a directory", opt.OutDir), + Detail: fmt.Sprintf("the output directory %s is a file, not a directory", core.Shown(opt.OutDir)), Remedy: "Point the output directory at a directory, or at one that does not exist yet and it will be created"} } diff --git a/internal/guard/unseenoutput_test.go b/internal/guard/unseenoutput_test.go new file mode 100644 index 00000000..72f8e997 --- /dev/null +++ b/internal/guard/unseenoutput_test.go @@ -0,0 +1,298 @@ +package guard + +import ( + "bytes" + "context" + "encoding/json" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/donislawdev/TestingFilesGenerator/internal/cli" + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/all" + "github.com/donislawdev/TestingFilesGenerator/internal/recipe" +) + +// What this tool prints about a name a person cannot read shows that name, +// rather than letting it act (O241). +// +// Measured on 2026-09-24: verify reported a missing file named "photo", a +// right to left override, "gpj.txt" - and the terminal drew it as +// "phototxt.jpg" - and an extra "in", a zero width space, "voice.txt" that +// printed as "invoice.txt". The report named files other than the ones on the +// disk, and two of them could not be told apart. The preset of unusual file +// names writes exactly these, so every report about its files would lie in +// the same way. +// +// Asked from outside, through the commands, because the defect is a place that +// prints a name without going through core.Shown, and a new place tomorrow is +// the one nobody will remember. Every guard here asks two things: nothing in +// the text is a character nobody can see, and the escape is there - since +// printing nothing at all would pass the first alone. + +var ( + rightToLeft = string(rune(0x202E)) + zeroWidth = string(rune(0x200B)) + lineSeparator = string(rune(0x2028)) +) + +// escapeOf is how a character is written once it is shown. +func escapeOf(s string) string { + q := strconv.QuoteRune([]rune(s)[0]) + return q[1 : len(q)-1] +} + +// saysNothingUnseen fails when said holds a character nobody can see, other +// than the line breaks that separate what it says. +func saysNothingUnseen(t *testing.T, what, said string) { + t.Helper() + for _, r := range said { + if r != '\n' && r != '\r' && unseenHere(r) { + t.Errorf("%s printed U+%04X raw:\n%s", what, r, said) + return + } + } +} + +// saysEscaped fails when said does not carry the name with its escape. +func saysEscaped(t *testing.T, what, said, stem, unseen, rest string) { + t.Helper() + if want := stem + escapeOf(unseen) + rest; !strings.Contains(said, want) { + t.Errorf("%s does not show %q:\n%s", what, want, said) + } +} + +// unseenRun writes the three names into a directory and gives back the +// directory and the manifest. +func unseenRun(t *testing.T) (string, string) { + t.Helper() + dir := t.TempDir() + var drafts []recipe.TargetDraft + for i, name := range []string{"photo" + rightToLeft + "gpj.txt", "in" + zeroWidth + "voice.txt", "report" + lineSeparator + "ERROR.txt"} { + drafts = append(drafts, recipe.TargetDraft{ID: "t" + strconv.Itoa(i), Format: "txt", Size: "1kb", Name: name}) + } + src, err := recipe.Compose(recipe.Document{Targets: drafts}) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "names.yaml") + if err := os.WriteFile(path, src, 0o600); err != nil { + t.Fatal(err) + } + var out, errOut bytes.Buffer + if code := cli.Run(context.Background(), []string{"generate", path, "--out", dir}, &out, &errOut); code != cli.ExitOK { + t.Fatalf("the run ended %d:\n%s", code, errOut.String()) + } + m := regexp.MustCompile(`(?m)^manifest: (.+)$`).FindStringSubmatch(errOut.String()) + if m == nil { + t.Fatalf("the run did not say where its manifest is:\n%s", errOut.String()) + } + return dir, strings.TrimSpace(m[1]) +} + +// carriesExactly fails unless some string in a JSON report is want, byte for +// byte - the half a program reads, which must not be escaped. +func carriesExactly(t *testing.T, what string, report []byte, want string) { + t.Helper() + var v any + if err := json.Unmarshal(report, &v); err != nil { + t.Errorf("%s is not JSON: %v\n%s", what, err, report) + return + } + if !holdsString(v, func(s string) bool { return strings.HasSuffix(s, want) }) { + t.Errorf("%s does not carry %+q exactly:\n%s", what, want, report) + } +} + +// linesWith is the lines of said that hold word, so a name is looked for on +// the line that says what happened to it. +func linesWith(said, word string) string { + var out []string + for _, line := range strings.Split(said, "\n") { + if strings.Contains(line, word) { + out = append(out, line) + } + } + return strings.Join(out, "\n") +} + +func holdsString(v any, match func(string) bool) bool { + switch x := v.(type) { + case string: + return match(x) + case []any: + for _, e := range x { + if holdsString(e, match) { + return true + } + } + case map[string]any: + for _, e := range x { + if holdsString(e, match) { + return true + } + } + } + return false +} + +func TestANoteAboutAFileShowsANameNobodyCanReadAsAnEscape(t *testing.T) { + // A text file of one byte cannot carry its label, which is a note about + // the file - the same trigger as the guard of notes about one file. + one := runCLI(t, "generate", "--format", "txt", "--size", "1b", "--count", "1", + "--name", "photo"+rightToLeft+"gpj.txt", "--dry-run", "--out", t.TempDir()) + if !strings.Contains(one, "note:") { + t.Fatalf("the run printed no note, so this guard checked nothing:\n%s", one) + } + saysNothingUnseen(t, "a note about one file", one) + saysEscaped(t, "a note about one file", one, "note: photo", rightToLeft, "gpj.txt: ") + + many := runCLI(t, "generate", "--format", "txt", "--size", "1b", "--count", "3", + "--name", "in"+zeroWidth+"voice_{index:04}.txt", "--dry-run", "--out", t.TempDir()) + if !strings.Contains(many, "3 files:") { + t.Fatalf("the run printed no grouped note, so this guard checked nothing:\n%s", many) + } + saysNothingUnseen(t, "a note about three files", many) + saysEscaped(t, "a note about three files", many, "in", zeroWidth, "voice_0001.txt") +} + +func TestVerifyShowsANameNobodyCanReadAsAnEscape(t *testing.T) { + dir, manifestPath := unseenRun(t) + if err := os.Remove(filepath.Join(dir, "photo"+rightToLeft+"gpj.txt")); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "extra"+zeroWidth+".txt"), []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + + var out, errOut bytes.Buffer + if code := cli.Run(context.Background(), []string{"verify", manifestPath}, &out, &errOut); code != cli.ExitVerify { + t.Fatalf("verify ended %d rather than with a mismatch:\n%s%s", code, out.String(), errOut.String()) + } + said := out.String() + errOut.String() + saysNothingUnseen(t, "verify", said) + saysEscaped(t, "verify, the missing file", said, "photo", rightToLeft, "gpj.txt") + saysEscaped(t, "verify, the extra file", said, "extra", zeroWidth, ".txt") + + out.Reset() + errOut.Reset() + cli.Run(context.Background(), []string{"verify", "--json", manifestPath}, &out, &errOut) + carriesExactly(t, "verify --json", []byte(strings.TrimSpace(out.String()+errOut.String())), "photo"+rightToLeft+"gpj.txt") +} + +func TestCleanupShowsANameNobodyCanReadAsAnEscape(t *testing.T) { + dir, manifestPath := unseenRun(t) + // Changed since it was written, so cleanup keeps it and says why. + if err := os.WriteFile(filepath.Join(dir, "in"+zeroWidth+"voice.txt"), []byte("changed"), 0o600); err != nil { + t.Fatal(err) + } + + preview := runCLI(t, "cleanup", manifestPath) + saysNothingUnseen(t, "cleanup", preview) + saysEscaped(t, "cleanup, a file it would remove", linesWith(preview, "remove"), "photo", rightToLeft, "gpj.txt") + saysEscaped(t, "cleanup, a file it would keep", linesWith(preview, "keep"), "in", zeroWidth, "voice.txt") + + removed := runCLI(t, "cleanup", "--yes", manifestPath) + saysNothingUnseen(t, "cleanup --yes", removed) + saysEscaped(t, "cleanup --yes, the file it kept", linesWith(removed, "kept"), "in", zeroWidth, "voice.txt") + if _, err := os.Stat(filepath.Join(dir, "in"+zeroWidth+"voice.txt")); err != nil { + t.Errorf("the changed file was not kept: %v", err) + } +} + +func TestARefusalShowsANameNobodyCanReadAsAnEscape(t *testing.T) { + // Two names that are one file on most systems, told apart only by case. + src, err := recipe.Compose(recipe.Document{Targets: []recipe.TargetDraft{ + {ID: "lower", Format: "txt", Size: "1kb", Name: "a" + zeroWidth + ".txt"}, + {ID: "upper", Format: "txt", Size: "1kb", Name: "A" + zeroWidth + ".txt"}, + }}) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "collide.yaml") + if err := os.WriteFile(path, src, 0o600); err != nil { + t.Fatal(err) + } + refused := runCLI(t, "validate", path) + saysNothingUnseen(t, "a refusal of two names that collide", refused) + saysEscaped(t, "a refusal of two names that collide", refused, "A", zeroWidth, ".txt") + + // A file already there under the name asked for. + dir := t.TempDir() + name := "photo" + rightToLeft + "gpj.txt" + if err := os.WriteFile(filepath.Join(dir, name), []byte("mine"), 0o600); err != nil { + t.Fatal(err) + } + taken := runCLI(t, "generate", "--format", "txt", "--size", "1kb", "--count", "1", "--name", name, "--out", dir) + if !strings.Contains(taken, "already exists") { + t.Fatalf("the run did not refuse the taken name, so this guard checked nothing:\n%s", taken) + } + saysNothingUnseen(t, "a refusal of a taken name", taken) + saysEscaped(t, "a refusal of a taken name", taken, "photo", rightToLeft, "gpj.txt already exists") +} + +// The rest of what a run says about a place: the files of a boundary set, a +// neighbouring run's files, and an output directory that cannot be used. +func TestTheLinesAboutARunShowANameNobodyCanReadAsAnEscape(t *testing.T) { + boundary := runCLI(t, "generate", "--format", "txt", "--boundary", "2kb", + "--name", "b"+rightToLeft+"_{index:04}.txt", "--dry-run", "--out", t.TempDir()) + if !strings.Contains(boundary, "boundary") { + t.Fatalf("the run printed no boundary set, so this guard checked nothing:\n%s", boundary) + } + saysNothingUnseen(t, "a boundary set", boundary) + saysEscaped(t, "a boundary set", linesWith(boundary, "0002"), "b", rightToLeft, "_0002.txt") + + // A second run beside the first, recording itself under its own name. + dir, firstManifest := unseenRun(t) + src, err := recipe.Compose(recipe.Document{Manifest: "record" + zeroWidth + ".json", Targets: []recipe.TargetDraft{ + {ID: "second", Format: "txt", Size: "1kb", Name: "second" + zeroWidth + ".txt"}, + }}) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "second.yaml") + if err := os.WriteFile(path, src, 0o600); err != nil { + t.Fatal(err) + } + second := runCLI(t, "generate", path, "--out", dir) + if !strings.Contains(second, "manifest:") { + t.Fatalf("the second run did not record itself:\n%s", second) + } + saysNothingUnseen(t, "the line naming a run's manifest", second) + saysEscaped(t, "the line naming a run's manifest", linesWith(second, "manifest:"), "record", zeroWidth, ".json") + + neighbour := runCLI(t, "verify", firstManifest) + if !strings.Contains(neighbour, "another run's record") { + t.Fatalf("verify said nothing about the other run, so this guard checked nothing:\n%s", neighbour) + } + saysNothingUnseen(t, "a note about another run", neighbour) + saysEscaped(t, "a note about another run, its file", linesWith(neighbour, "another run"), "second", zeroWidth, ".txt") + saysEscaped(t, "a note about another run, its record", linesWith(neighbour, "another run"), "record", zeroWidth, ".json") + + // An output directory that is a file, and one another run is holding. + parent := t.TempDir() + file := filepath.Join(parent, "out"+rightToLeft+"file") + if err := os.WriteFile(file, []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + notADir := runCLI(t, "generate", "--format", "txt", "--size", "1kb", "--out", file) + saysNothingUnseen(t, "a refusal of an output directory that is a file", notADir) + saysEscaped(t, "a refusal of an output directory that is a file", notADir, "out", rightToLeft, "file is a file") + + held := filepath.Join(parent, "held"+zeroWidth+"dir") + if err := os.MkdirAll(held, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(held, ".tfg-run-lock"), nil, 0o600); err != nil { + t.Fatal(err) + } + busy := runCLI(t, "generate", "--format", "txt", "--size", "1kb", "--out", held) + if !strings.Contains(busy, "another run is already writing") { + t.Fatalf("the run did not refuse a held directory, so this guard checked nothing:\n%s", busy) + } + saysNothingUnseen(t, "a refusal of a held directory", busy) + saysEscaped(t, "a refusal of a held directory", busy, "held", zeroWidth, "dir, so this one") +} diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index 5ddf54bc..cbbdce8b 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -467,9 +467,9 @@ func (n *noteGroups) add(detail, name string) { // to make the large case better. func (g *noteGroup) line(detail string) string { if g.count == 1 { - return fmt.Sprintf("%s: %s", g.first[0], detail) + return fmt.Sprintf("%s: %s", core.Shown(g.first[0]), detail) } - named := strings.Join(g.first, ", ") + named := strings.Join(core.ShownEach(g.first), ", ") if hidden := g.count - len(g.first); hidden > 0 { return fmt.Sprintf("%s: %s Named: %s. %s not named here.", core.Count(g.count, "file", "files"), detail, named, From 73a6cfa214872885a3418434c0dd9d0bd0ae701d Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Thu, 24 Sep 2026 23:54:01 +0200 Subject: [PATCH 4/8] guard: no file in the repository carries a character nobody can see A right to left override or a zero width space in source makes code read one way on screen and another to the compiler. None is in the tree today, and the preset of unusual file names is about to need them as escapes. Co-Authored-By: Claude Opus 5.5 --- internal/guard/hiddencharacters_test.go | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 internal/guard/hiddencharacters_test.go diff --git a/internal/guard/hiddencharacters_test.go b/internal/guard/hiddencharacters_test.go new file mode 100644 index 00000000..7d54506f --- /dev/null +++ b/internal/guard/hiddencharacters_test.go @@ -0,0 +1,98 @@ +package guard + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "unicode/utf8" +) + +// No file in this repository carries a character nobody can see. +// +// The class a reviewer reading a diff cannot catch by reading: a right to left +// override that makes code say one thing on screen and another to the +// compiler - the attack published as Trojan Source - a zero width space that +// makes two identifiers look like one, a byte order mark or a line separator +// inside a string. The preset of unusual file names holds exactly these +// characters as data, so its source is the first place in this tree that +// wants them, and it writes every one of them as an escape. Measured before +// this guard existed, on 2026-09-24: not one tracked file carried such a +// character, so the list of exceptions starts empty. +// +// And the tool that writes this code is the reason it is a guard rather than a +// habit. The editor this project is written with turns a typed backslash-u +// escape into the character itself, silently - it did so twice on the day this +// guard was written, once in a document about this very problem. +// +// A file that is not UTF-8 is not text and is left out, counted. The tab, the +// line feed and the carriage return are what text is laid out with. +func TestNoTrackedFileCarriesACharacterNobodyCanSee(t *testing.T) { + root := repoRoot(t) + listed := strings.Split(gitOutput(t, "ls-files", "-z", "--cached", "--others", "--exclude-standard"), "\x00") + + read, goFiles, binary := 0, 0, 0 + var faults []string + for _, f := range listed { + if f == "" { + continue + } + body, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(f))) + if err != nil { + continue + } + if !utf8.Valid(body) { + binary++ + continue + } + read++ + if strings.HasSuffix(f, ".go") { + goFiles++ + } + if fault := firstHidden(string(body)); fault != "" { + faults = append(faults, f+":"+fault) + } + } + + // The state this is about: the Go source was actually read. A listing + // that came back empty or from the wrong directory would pass below. + if goFiles < 100 { + t.Fatalf("only %d Go files were read (%d text files, %d not UTF-8), so this guard is not looking at this repository", goFiles, read, binary) + } + if len(faults) > 0 { + t.Errorf("%d file(s) carry a character nobody can see. Write it as an escape instead - in Go, a backslash, u and four hex digits:\n %s", + len(faults), strings.Join(faults, "\n ")) + } + t.Logf("%d text files read, %d of them Go, %d not UTF-8 left out", read, goFiles, binary) +} + +// firstHidden is where the first such character of text is, as "line: U+XXXX", +// or "" when there is none. +func firstHidden(text string) string { + line := 1 + for _, r := range text { + switch { + case r == '\n': + line++ + case r == '\t' || r == '\r': + case unseenHere(r): + return fmt.Sprintf("%d: U+%04X", line, r) + } + } + return "" +} + +// The detector finds what it is for, or the guard above passes by finding +// nothing anywhere. Built from rune numbers, since this file is one of the +// files being scanned. +func TestTheHiddenCharacterDetectorFindsWhatItIsFor(t *testing.T) { + for _, r := range []rune{0x202E, 0x200B, 0xFEFF, 0x2028, 0x00A0, 0x3000, 0xE0041} { + if got := firstHidden("ok\nx := \"a" + string(r) + "b\"\n"); got != fmt.Sprintf("2: U+%04X", r) { + t.Errorf("U+%04X on the second line was reported as %q", r, got) + } + } + if got := firstHidden("tab\there, crlf\r\nand a plain space\n"); got != "" { + t.Errorf("ordinary layout was reported as %q", got) + } +} From 6eb1c31e9b05c888f1be1311c5d5a6dd49e9be6b Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Thu, 24 Sep 2026 23:55:53 +0200 Subject: [PATCH 5/8] preset: a preset declares the default of a flag it reads The window took the default of --format from preset.Global, which knew only the pdf of size-boundaries, while each preset applied its own. A second preset reading --format with another default would have made one set from the command line and another from the window. Co-Authored-By: Claude Opus 5.5 --- internal/guard/readdefaults_test.go | 81 +++++++++++++++++++++++++++++ internal/preset/preset.go | 27 ++++++++-- internal/preset/sizeboundaries.go | 3 +- 3 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 internal/guard/readdefaults_test.go diff --git a/internal/guard/readdefaults_test.go b/internal/guard/readdefaults_test.go new file mode 100644 index 00000000..caac2fe6 --- /dev/null +++ b/internal/guard/readdefaults_test.go @@ -0,0 +1,81 @@ +package guard + +import ( + "bytes" + "testing" + + "github.com/donislawdev/TestingFilesGenerator/internal/format" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/parts" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/text" + "github.com/donislawdev/TestingFilesGenerator/internal/preset" +) + +// A flag a preset reads stands in with one value, whichever surface asks. +// +// Until 2026-09-24 the window took the value from preset.Global, which knew +// one default for --format - pdf, the one size-boundaries uses - while each +// preset's Expand applied its own. The second preset to read --format was the +// preset of unusual file names, made of text files by default, so the command +// line would have written text files and the window PDFs from one preset (D1). +// The default is declared by the preset now, and this asks both surfaces for +// it: the set a run makes with the flag left out, and the value the window's +// menu opens on. +func TestEveryFlagAPresetReadsDefaultsToOneValueOnBothSurfaces(t *testing.T) { + _, content := presetScreen(t) + + checked := 0 + for _, p := range preset.All() { + for _, name := range p.Reads { + declared := p.ReadDefaults[name] + + bare, err := preset.Expand(p.ID, preset.Args{}) + if err != nil { + t.Fatalf("%s refused its own defaults: %v", p.ID, err) + } + stated, err := preset.Expand(p.ID, preset.Args{name: declared}) + if err != nil { + t.Fatalf("%s refused --%s %s: %v", p.ID, name, declared, err) + } + if !bytes.Equal(bare.Source, stated.Source) { + t.Errorf("%s declares --%s %s, and leaving the flag out makes a different set", p.ID, name, declared) + } + // The comparison above says nothing if the flag changes nothing, so + // another value has to make another set. + if !anotherValueChangesTheSet(t, p, name, declared, bare.Source) { + t.Errorf("%s makes the same set whatever --%s says, so this guard cannot tell the defaults apart", p.ID, name) + } + + choosePreset(t, content, p.ID) + menu, ok := controlUnder(content, text.SettingLabel(name)).(*parts.Chooser) + if !ok { + t.Errorf("the window draws no menu for --%s of %s", name, p.ID) + continue + } + if menu.Selected != declared { + t.Errorf("the window opens --%s of %s on %q, and the command line uses %q", name, p.ID, menu.Selected, declared) + } + checked++ + } + } + if checked == 0 { + t.Fatal("no preset reads a flag, so this guard checked nothing") + } + t.Logf("%d flag(s) read by a preset, each defaulting to one value on both surfaces", checked) +} + +// anotherValueChangesTheSet is whether some other format makes a different +// set - the first one the preset accepts. +func anotherValueChangesTheSet(t *testing.T, p preset.Preset, name, declared string, bare []byte) bool { + t.Helper() + for _, id := range format.IDs() { + if id == declared { + continue + } + other, err := preset.Expand(p.ID, preset.Args{name: id}) + if err != nil { + continue + } + return !bytes.Equal(other.Source, bare) + } + return false +} diff --git a/internal/preset/preset.go b/internal/preset/preset.go index bffa17b4..64046bd8 100644 --- a/internal/preset/preset.go +++ b/internal/preset/preset.go @@ -57,6 +57,20 @@ type Preset struct { // that is not there. Reads []string + // ReadDefaults is the value this preset gives each flag in Reads when the + // caller leaves it out, keyed by the flag's name. Register refuses a name + // in Reads without one. + // + // Declared here since 2026-09-24, when a second preset came to read + // --format with a default of its own. Until then the window took the + // default from Global, which knew only the pdf of size-boundaries, so the + // preset of unusual file names would have made text files from the command + // line and PDFs from the window - one preset, two sets (D1). Expand applies + // the default itself, and + // TestEveryFlagAPresetReadsDefaultsToOneValueOnBothSurfaces holds the two + // together. + ReadDefaults map[string]string + // Landing marks the preset a surface opens on before anybody has chosen // one. Exactly one preset sets it, and Register refuses a second. // @@ -188,10 +202,8 @@ func (p Preset) Check(args Args) error { // is a property of the build and registration order between two packages is not // something to rely on. // -// The default is the one the presets in this package use. That is true today -// with one preset and it is a coupling rather than a design: a second preset -// reading --format with a different default has to turn this into something the -// preset declares, and the constant it points at is the one place to notice. +// No default here. Which value stands in when nobody gives one belongs to the +// preset reading the flag - ReadDefaults - and Globals puts it in. func Global(name string) (format.Property, bool) { switch name { case "format": @@ -199,7 +211,6 @@ func Global(name string) (format.Property, bool) { Name: "format", Kind: format.PropertyChoice, Choices: format.IDs(), - Default: defaultFormat, Detail: "What kind of file the whole set is made of.", }, true } @@ -216,6 +227,7 @@ func (p Preset) Globals() []format.Property { out := make([]format.Property, 0, len(p.Reads)) for _, name := range p.Reads { if declared, ok := Global(name); ok { + declared.Default = p.ReadDefaults[name] out = append(out, declared) } } @@ -385,6 +397,11 @@ func Register(p Preset) { } landing = p.ID } + for _, name := range p.Reads { + if p.ReadDefaults[name] == "" { + panic(fmt.Sprintf("preset: %s reads --%s and gives it no default", p.ID, name)) + } + } // A parameter IS a format.Property, so a closed set of values is put in the // same order here as it is over there. One rule for both, in the place each // declaration passes through exactly once. diff --git a/internal/preset/sizeboundaries.go b/internal/preset/sizeboundaries.go index 061d9348..e4672dd0 100644 --- a/internal/preset/sizeboundaries.go +++ b/internal/preset/sizeboundaries.go @@ -39,7 +39,8 @@ func init() { Detail: "How far either side of the limit to reach, as a list of sizes.", }, }, - Reads: []string{"format"}, + Reads: []string{"format"}, + ReadDefaults: map[string]string{"format": defaultFormat}, SaidWhenDefaulted: map[string]string{ "limit": "no limit was given, so this set is built around " + defaultLimitText + From 8c842f3898ad43db4aab47981f8ccab1b989a28e Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 00:05:06 +0200 Subject: [PATCH 6/8] preset: filename-handling, fifty names a system did not expect Answers "will my system store, show and give back a file name it did not expect?" with fifty names in seven groups, each written byte for byte on Windows, Linux and macOS: scripts and normalisation, lookalikes and characters nobody can see, leading spaces and dots, metacharacters, names that mean something to a server, names read as values, and names at the length limits. txt unless --format says otherwise, the length names counting the format's extension in. Four names are expected to be accepted, the rest are left to the system's policy with a reason. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 15 + README.md | 3 +- internal/guard/filenamehandling_test.go | 267 ++++++++++++++++++ internal/guard/parity_test.go | 7 + internal/guard/presetbytes_test.go | 5 + .../guard/testdata/screens/preset-menu.png | Bin 122993 -> 121917 bytes .../guard/testdata/screens/preset-menu.xml | 28 +- internal/preset/build.go | 12 + internal/preset/filenamehandling.go | 267 ++++++++++++++++++ internal/preset/uploadset.go | 5 +- web/content/en/site.json | 1 + web/content/pl/site.json | 1 + web/public/docs/index.html | 4 + web/public/pl/dokumentacja/index.html | 4 + 14 files changed, 602 insertions(+), 17 deletions(-) create mode 100644 internal/guard/filenamehandling_test.go create mode 100644 internal/preset/filenamehandling.go diff --git a/CHANGELOG.md b/CHANGELOG.md index fc804a95..88e4c35a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,21 @@ because it turns other people's test suites red. ## [Unreleased] +### Added + +- **A preset for unusual file names: `filename-handling`.** It answers "will + my system store, show and give back a file name it did not expect?" with + fifty names in seven groups: scripts from Polish to Korean, names that look + like other names, leading spaces and dots, shell and SQL metacharacters, + names that mean something to a web server or a desktop, names read as + values, and names at the length limits. Every one is written byte for byte + on Windows, Linux and macOS - measured on NTFS, ext4 and APFS. On another + file system a name may be refused, and the run then ends with code 8 and + names it. The files are `txt` unless `--format` says otherwise, and the + names about length count the format's extension in. Four names are + expected to be accepted, the rest are left to your system's policy with a + reason. + ### Changed - **A file name longer than 255 bytes is refused before anything is written, diff --git a/README.md b/README.md index 7a9dca3a..81ed10fb 100644 --- a/README.md +++ b/README.md @@ -639,7 +639,8 @@ when a number is a placeholder of ours rather than a limit of yours. Presets are ordinary recipes underneath - `tfg preset eject size-boundaries` prints the recipe and you edit it from there. -One preset ships today, `size-boundaries`. More are designed. +`tfg preset list` names every preset your build ships, and the Presets +screen of the window offers the same ones. ## 🖥️ The desktop window diff --git a/internal/guard/filenamehandling_test.go b/internal/guard/filenamehandling_test.go new file mode 100644 index 00000000..011e7f09 --- /dev/null +++ b/internal/guard/filenamehandling_test.go @@ -0,0 +1,267 @@ +package guard + +import ( + "bytes" + "context" + "os" + "path/filepath" + "sort" + "strings" + "testing" + "unicode/utf8" + + "golang.org/x/text/unicode/norm" + + "github.com/donislawdev/TestingFilesGenerator/internal/cli" + "github.com/donislawdev/TestingFilesGenerator/internal/core" + "github.com/donislawdev/TestingFilesGenerator/internal/format" + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/all" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/text" + "github.com/donislawdev/TestingFilesGenerator/internal/manifest" + "github.com/donislawdev/TestingFilesGenerator/internal/preset" + "github.com/donislawdev/TestingFilesGenerator/internal/recipe" +) + +// The preset of unusual file names asks for the fifty names of +// docs/NAMES-PRESET-2026-09-24.md, and each of them in every format. +// +// A name here is the whole point of its file, and a name that arrives a +// character short is a file that tests something else while its manifest +// entry still says what it was meant to test. That happened before this +// preset existed: an ideographic space at the front of a name was trimmed on +// its way through the recipe, in every format (O243). So each name is asked +// after the recipe has been written and read back, which is the name a run +// actually gets - and each is asked by what makes it the name it is, worked out +// here rather than copied from the preset. +func TestTheFileNamePresetAsksForEveryNameInEveryFormat(t *testing.T) { + checked := 0 + for _, id := range format.IDs() { + desc, _ := format.Get(id) + names := namesOfTheSet(t, preset.Args{"format": id}) + if len(names) != 50 { + t.Errorf("%s: the set holds %d names and the list has 50", id, len(names)) + } + folded := map[string]string{} + for target, name := range names { + if !utf8.ValidString(name) || name == "" || len(name) > core.MaxNameBytes { + t.Errorf("%s: %s is %d bytes, or empty, or not UTF-8: %+q", id, target, len(name), name) + } + if other, taken := folded[core.FoldName(name)]; taken { + t.Errorf("%s: %s and %s are one file on a system that folds names", id, target, other) + } + folded[core.FoldName(name)] = target + if fault := nameFault(target, name, desc.Extension); fault != "" { + t.Errorf("%s: %s is %+q, and %s", id, target, name, fault) + } + checked++ + } + } + if checked < 50*20 { + t.Fatalf("only %d names were checked", checked) + } + t.Logf("%d names in %d formats", checked, len(format.IDs())) +} + +// namesOfTheSet expands the preset, reads the recipe back and gives each +// target's name by its id. +func namesOfTheSet(t *testing.T, args preset.Args) map[string]string { + t.Helper() + expanded, err := preset.Expand("filename-handling", args) + if err != nil { + t.Fatalf("the preset refused %v: %v", args, err) + } + rec, err := recipe.Parse(expanded.Source, "filename-handling") + if err != nil { + t.Fatalf("the preset's recipe at %v was refused: %v", args, err) + } + out := map[string]string{} + for _, target := range rec.Targets { + out[target.ID] = target.Name + } + return out +} + +// nameFault says what is wrong with a name of the set, or "" when nothing is. +func nameFault(target, name, ext string) string { + exact := map[string]string{ + "htaccess": ".htaccess", "web_config": "web.config", "dotenv": ".env", + "ds_store": ".DS_Store", "desktop_ini": "desktop.ini", "no_extension": "README", + } + marked := map[string]rune{ + "bidi_override": 0x202E, "zero_width": 0x200B, "no_break_space": 0xA0, + "homoglyph": 0x430, "line_separator": 0x2028, "emoji_zwj": 0x200D, + } + stem := strings.TrimSuffix(name, ext) + switch { + case exact[target] != "": + if name != exact[target] { + return "it means something only as " + exact[target] + } + return "" + case target == "upper_extension": + if name != "REPORT"+strings.ToUpper(ext) { + return "its extension is not the format's in capitals" + } + return "" + case target == "fullwidth_extension": + if name == "report"+ext || norm.NFKC.String(name) != "report"+ext { + return "it is not the format's extension in full width letters" + } + return "" + case !strings.HasSuffix(name, ext): + return "it does not end with the format's extension " + ext + } + switch target { + case "only_extension": + return unless(stem == "", "it is more than the extension") + case "ustar_101": + return unless(len(name) == 101 && strings.Trim(stem, "u") == "", "it is not 101 bytes of u with the extension") + case "max_ascii": + return unless(len(name) == 255 && strings.Trim(stem, "a") == "", "it is not 255 bytes of a with the extension") + case "cjk_bytes": + return unless(strings.Trim(stem, "日") == "" && len(name) <= 255 && len(name)+3 > 255, "it is not as many ideographs as fit in 255 bytes") + case "emoji_bytes": + return unless(strings.Trim(stem, "🎉") == "" && len(name) <= 255 && len(name)+4 > 255, "it is not as many emoji as fit in 255 bytes") + case "nfd", "hangul_nfd": + return unless(norm.NFD.IsNormalString(name) && !norm.NFC.IsNormalString(name), "it is not in the decomposed form") + case "leading_bom": + return unless(strings.HasPrefix(name, string(rune(0xFEFF))), "it does not begin with a byte order mark") + case "leading_ideographic_space": + return unless(strings.HasPrefix(name, string(rune(0x3000))), "it does not begin with an ideographic space") + case "unicode_tags": + return unless(untagged(stem) == "hidden note", "it carries no hidden note in tag characters") + } + if r, ok := marked[target]; ok && !strings.ContainsRune(name, r) { + return "it lacks the character it is about" + } + return "" +} + +func unless(ok bool, fault string) string { + if ok { + return "" + } + return fault +} + +// untagged is the text the tag characters of s spell. +func untagged(s string) string { + var b strings.Builder + for _, r := range s { + if r >= 0xE0020 && r <= 0xE007E { + b.WriteRune(r - 0xE0000) + } + } + return b.String() +} + +// The set promises acceptance only where refusing would be the system's +// fault, and says unspecified everywhere else (MF5), with a reason the +// manifest already has - the owner's decision of 2026-09-24. +func TestTheFileNamePresetPromisesOnlyWhatASystemMustDo(t *testing.T) { + expanded, err := preset.Expand("filename-handling", preset.Args{}) + if err != nil { + t.Fatal(err) + } + rec, err := recipe.Parse(expanded.Source, "filename-handling") + if err != nil { + t.Fatal(err) + } + var accepted []string + for _, target := range rec.Targets { + switch target.Expected { + case "accept": + accepted = append(accepted, target.ID) + case "unspecified": + switch target.ExpectedReason { + case "filename_invalid", "filename_too_long", "filename_traversal": + default: + t.Errorf("%s is unspecified for %q, which is not one of the three name reasons", target.ID, target.ExpectedReason) + } + default: + t.Errorf("%s expects %q, and a name is either accepted or left to the system's policy", target.ID, target.Expected) + } + } + sort.Strings(accepted) + if got := strings.Join(accepted, " "); got != "leading_zeros many_dots null_word upper_extension" { + t.Errorf("the set promises acceptance for %q", got) + } +} + +// Every name is written as it was asked for, here - and CI runs this on +// Windows, Linux and macOS, which is the measurement of +// docs/NAMES-PRESET-2026-09-24.md section 4 kept. +// +// Asked in the default format and in one whose extension is a byte longer, +// because the names that are about length are made to a length with the +// extension, and only the default was ever measured by hand. +func TestTheFileNamePresetWritesEveryNameByteForByte(t *testing.T) { + for _, args := range [][]string{nil, {"--format", "docx"}} { + dir := t.TempDir() + var out, errOut bytes.Buffer + cmd := append([]string{"generate", "--preset", "filename-handling", "--out", dir}, args...) + if code := cli.Run(context.Background(), cmd, &out, &errOut); code != cli.ExitOK { + t.Fatalf("%v ended %d:\n%s", cmd, code, errOut.String()) + } + m, err := manifest.Load(filepath.Join(dir, "manifest.json")) + if err != nil { + t.Fatal(err) + } + var recorded []string + for _, f := range m.Files { + if !f.Materialized { + t.Errorf("%v: %+q was not written", args, f.Name) + } + recorded = append(recorded, f.Name) + } + sort.Strings(recorded) + var onDisk []string + for _, name := range namesIn(t, dir) { + if name != "manifest.json" { + onDisk = append(onDisk, name) + } + } + if len(recorded) != 50 || strings.Join(onDisk, "\x00") != strings.Join(recorded, "\x00") { + t.Errorf("%v: the directory holds %d names and the manifest %d, and they are not the same bytes:\n disk %+q\n manifest %+q", + args, len(onDisk), len(recorded), onDisk, recorded) + } + if code := cli.Run(context.Background(), []string{"verify", filepath.Join(dir, "manifest.json")}, &out, &errOut); code != cli.ExitOK { + t.Errorf("%v: verify ended %d:\n%s", args, code, errOut.String()) + } + } +} + +// One preset, both surfaces, the same files - the names are the point, so the +// window has to write the same fifty. +func TestThePresetScreenWritesTheNamesTheCommandLineWrites(t *testing.T) { + fromCLI, fromWindow := t.TempDir(), t.TempDir() + var out, errOut bytes.Buffer + if code := cli.Run(context.Background(), []string{"generate", "--preset", "filename-handling", "--out", fromCLI}, &out, &errOut); code != cli.ExitOK { + t.Fatalf("the command line refused the preset: exit %d\n%s", code, errOut.String()) + } + + host, content := presetScreen(t) + choosePreset(t, content, "filename-handling") + fill(t, content, text.FieldOutputDir(), fromWindow) + press(t, content, "Generate") + waitForManifest(t, host, fromWindow) + join(host) + + cliNames, windowNames := namesIn(t, fromCLI), namesIn(t, fromWindow) + if len(cliNames) != 51 { + t.Fatalf("the command line wrote %d things and fifty files and a manifest were expected", len(cliNames)) + } + if strings.Join(cliNames, "\x00") != strings.Join(windowNames, "\x00") { + t.Fatalf("the two surfaces wrote different names:\n command line %+q\n window %+q", cliNames, windowNames) + } + for _, name := range cliNames { + if name == "manifest.json" { + continue + } + a, errA := os.ReadFile(filepath.Join(fromCLI, name)) + b, errB := os.ReadFile(filepath.Join(fromWindow, name)) + if errA != nil || errB != nil || !bytes.Equal(a, b) { + t.Errorf("%+q differs between the surfaces (%v, %v)", name, errA, errB) + } + } +} diff --git a/internal/guard/parity_test.go b/internal/guard/parity_test.go index 6471eafa..fc2f051e 100644 --- a/internal/guard/parity_test.go +++ b/internal/guard/parity_test.go @@ -87,6 +87,13 @@ var reachableFromTheWindow = []string{ "preset:upload-validation.far-over", "preset:upload-validation.bulk", + // The preset of unusual file names, 2026-09-24, and the global flag it + // gives txt to. Run from the screen by + // TestThePresetScreenWritesTheNamesTheCommandLineWrites, and the menu's + // first value asked by TestEveryFlagAPresetReadsDefaultsToOneValueOnBothSurfaces. + "preset:filename-handling", + "preset:filename-handling.format", + // A recipe that builds on a preset, since 2026-09-22: the switch and the // menu on the batch screen are the extends key, and the chosen preset's // parameters under it are the with section, drawn from the declaration diff --git a/internal/guard/presetbytes_test.go b/internal/guard/presetbytes_test.go index 1852b3e5..e51eb70d 100644 --- a/internal/guard/presetbytes_test.go +++ b/internal/guard/presetbytes_test.go @@ -67,6 +67,11 @@ func TestEjectingAPresetGivesTheBytesItAlwaysGave(t *testing.T) { {id: "text-encoding", args: preset.Args{"sample": "8kb"}, bytes: 4570, sum: "f87c73864e5f517abb08b50393cd9a1681a90a30560c1d14dfddcf31e8037479"}, {id: "empty-and-minimal", args: preset.Args{}, bytes: 3811, sum: "80641962ac9dfb303f812fd78e0a0d1080f714094d159d7b10448291debb9279"}, {id: "empty-and-minimal", args: preset.Args{"formats": "jpg,png,txt"}, bytes: 743, sum: "4fd23e4b06a2e27ede987ab48a2cc302cc2accb9f948c7d5d25f675681f31f69"}, + // The preset of unusual file names, measured 2026-09-25 on its first + // build: the default, and a format whose extension is a byte longer, + // since the names about length are made to a length with it. + {id: "filename-handling", args: preset.Args{}, bytes: 10324, sum: "fc051b2285c0efed30bc5e19920e6f08e25fc8f95e3b1be0ed8d228a25310e43"}, + {id: "filename-handling", args: preset.Args{"format": "docx"}, bytes: 10418, sum: "65fcae1471cd3b0111cae3dba14cd7d3d39beb6f998dc665b3dbaec898aade86"}, } for _, want := range pinned { diff --git a/internal/guard/testdata/screens/preset-menu.png b/internal/guard/testdata/screens/preset-menu.png index 3f7a4d757f1ce25053badd87168c83748f57be02..68ad1acce2dd8f31df00700e1d1f08aa84eaed57 100644 GIT binary patch delta 78077 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Ψ)=eiDkWlMngI!O1T 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 diff --git a/internal/guard/testdata/screens/preset-menu.xml b/internal/guard/testdata/screens/preset-menu.xml index 197581d7..682b3b60 100644 --- a/internal/guard/testdata/screens/preset-menu.xml +++ b/internal/guard/testdata/screens/preset-menu.xml @@ -393,16 +393,16 @@ - - - - - - + + + + + + - - - + + + @@ -410,17 +410,21 @@ - size-boundaries + filename-handling - tabular-import + size-boundaries - text-encoding + tabular-import + + text-encoding + + upload-validation diff --git a/internal/preset/build.go b/internal/preset/build.go index b62b49ab..ac7e46c8 100644 --- a/internal/preset/build.go +++ b/internal/preset/build.go @@ -182,6 +182,18 @@ func (f setFile) refused() error { return err } +// sampleAtLeast is how big a file about its name or its insides is: the +// sample, or the format's own floor with the label where that is larger, so +// that a format with a high floor cannot turn such a file into a refusal about +// a size. Shared since 2026-09-24 - upload-validation takes four kilobytes +// (sampleFor), the preset of unusual file names one. +func sampleAtLeast(desc format.Descriptor, sample int64) int64 { + if floor := format.SmallestWithLabel(desc); floor > sample { + return floor + } + return sample +} + // request is what refused asks the format - one place, so that a set that // skips a question it has already asked keys it by the question itself. func (f setFile) request() format.Request { diff --git a/internal/preset/filenamehandling.go b/internal/preset/filenamehandling.go new file mode 100644 index 00000000..a0b5943b --- /dev/null +++ b/internal/preset/filenamehandling.go @@ -0,0 +1,267 @@ +package preset + +import ( + "fmt" + "strings" + + "golang.org/x/text/unicode/norm" + + "github.com/donislawdev/TestingFilesGenerator/internal/format" +) + +const ( + namesID = "filename-handling" + + // namesFormat is the format the set is made of when nobody says. The name + // is what this preset is about and the contents are not, so the default is + // the format with the least in it, and --format changes it. + namesFormat = "txt" + + // namesQuestion is announced by the preset AND written into the header of + // an ejected recipe. + namesQuestion = "Will my system store, show and give back a file name it did not expect?" + + // namesSample is how big each file is when its format allows it - small, + // since every file is about its name, and the owner's budget for the set + // is fifty files of a kilobyte. + namesSample = 1 << 10 +) + +func init() { + Register(Preset{ + ID: namesID, + Title: "File name handling", + Question: namesQuestion, + + Reads: []string{"format"}, + ReadDefaults: map[string]string{"format": namesFormat}, + + Requires: []string{"MVP"}, + Catches: []string{ + "a name that looks like a different one on screen, in a log or in a list", + "a name cut, trimmed or rewritten between upload and storage", + "a length limit counted in characters where the storage counts bytes", + }, + + Expand: expandFileNames, + }) +} + +// The fifty names, chosen and measured in docs/NAMES-PRESET-2026-09-24.md. Each +// one catches a class of fault a system that takes files in has been seen to +// have, none repeats a class another one catches, and every one of them is +// written byte for byte on Windows, Linux and macOS - so a file that does not +// arrive, or arrives renamed, is the system under test and not this tool. +// +// A character nobody can see is written here as an escape and never as +// itself, and a guard reads every file of this repository for that +// (TestNoTrackedFileCarriesACharacterNobodyCanSee). A right to left override +// typed into Go source is how code comes to say one thing on screen and +// another to the compiler. +// +// expected is accept for the four names a system would be wrong to refuse, +// and unspecified for the rest, because whether a system takes a name with a +// zero width space in it is its own policy and not a fault (MF5). Only reasons +// the manifest already knows are used - the owner's decision. +var nameCases = []nameCase{ + // How a name goes through bytes, encodings and normalisation. + {id: "polish_diacritics", group: "scripts", stem: "zażółć gęślą jaźń"}, + {id: "cyrillic", group: "scripts", stem: "Отчёт за квартал"}, + {id: "cjk", group: "scripts", stem: "報告書"}, + // Arabic letters with digits, written as escapes so that the line reads + // the same in any editor: an Arabic word, 2024 and v2. + {id: "rtl_with_digits", group: "scripts", stem: "\u062A\u0642\u0631\u064A\u0631 2024 v2"}, + {id: "emoji", group: "scripts", stem: "🎉"}, + {id: "emoji_zwj", group: "scripts", stem: "👨\u200D👩\u200D👧 family"}, + {id: "nfd", group: "scripts", stem: "café", decomposed: true}, + {id: "hangul_nfd", group: "scripts", stem: "한국어 보고서", decomposed: true}, + {id: "case_mapping", group: "scripts", stem: "İstanbul Straße"}, + {id: "fullwidth", group: "scripts", stem: "report"}, + {id: "combining_stack", group: "scripts", stem: "z\u0335\u0321a\u0337l\u0338g\u0336o"}, + + // What is shown is not what is stored. + {id: "bidi_override", group: "lookalike", stem: "photo\u202Egpj"}, + {id: "zero_width", group: "lookalike", stem: "in\u200Bvoice"}, + {id: "no_break_space", group: "lookalike", stem: "annual\u00A0report"}, + {id: "homoglyph", group: "lookalike", stem: "p\u0430ypal"}, + {id: "leading_bom", group: "lookalike", stem: "\uFEFFreport"}, + {id: "line_separator", group: "lookalike", stem: "report\u2028ERROR admin logged in"}, + {id: "unicode_tags", group: "lookalike", stem: "report" + tagged("hidden note")}, + + // Where a name is split, trimmed or hidden. + {id: "leading_space", group: "spaces-and-dots", stem: " leading space"}, + {id: "leading_ideographic_space", group: "spaces-and-dots", stem: "\u3000report"}, + {id: "double_space", group: "spaces-and-dots", stem: "two spaces"}, + {id: "leading_dot", group: "spaces-and-dots", stem: ".hidden"}, + {id: "leading_double_dot", group: "spaces-and-dots", stem: "..report", reason: "filename_traversal"}, + {id: "only_extension", group: "spaces-and-dots"}, + {id: "many_dots", group: "spaces-and-dots", stem: "v1.2.3.final", accepted: true}, + {id: "no_extension", group: "spaces-and-dots", stem: "README", extension: noExtension}, + {id: "upper_extension", group: "spaces-and-dots", stem: "REPORT", extension: upperExtension, accepted: true}, + + // A name that means something to a shell, a query or an address. + {id: "leading_dash", group: "metacharacters", stem: "-rf"}, + {id: "shell_substitution", group: "metacharacters", stem: "$(id) `id`"}, + {id: "shell_separators", group: "metacharacters", stem: "a;b&c"}, + {id: "sql_quote", group: "metacharacters", stem: "'; DROP TABLE files; --"}, + {id: "script_quote", group: "metacharacters", stem: "'-alert(1)-'"}, + {id: "url_encoded_traversal", group: "metacharacters", stem: "..%2F..%2Fetc%2Fpasswd", reason: "filename_traversal"}, + {id: "fullwidth_traversal", group: "metacharacters", stem: "../../etc/passwd", reason: "filename_traversal"}, + {id: "encoded_control", group: "metacharacters", stem: "report%00%0D%0A"}, + {id: "fullwidth_extension", group: "metacharacters", stem: "report", extension: fullwidthExtension}, + {id: "url_specials", group: "metacharacters", stem: "100% a+b #1"}, + {id: "formula", group: "metacharacters", stem: "=1+1"}, + + // Names that mean something to a server or a desktop, whatever the file + // holds - .htaccess with a PDF inside is still .htaccess. + {id: "htaccess", group: "special-names", stem: ".htaccess", extension: noExtension}, + {id: "web_config", group: "special-names", stem: "web.config", extension: noExtension}, + {id: "dotenv", group: "special-names", stem: ".env", extension: noExtension}, + {id: "ds_store", group: "special-names", stem: ".DS_Store", extension: noExtension}, + {id: "desktop_ini", group: "special-names", stem: "desktop.ini", extension: noExtension}, + {id: "office_lock", group: "special-names", stem: "~$report"}, + + // A name somebody reads as a value. + {id: "null_word", group: "values", stem: "null", accepted: true}, + {id: "leading_zeros", group: "values", stem: "007", accepted: true}, + + // Characters, bytes and UTF-16 units are three different numbers. Each is + // made to a length in bytes together with the extension, so the set means + // the same thing whatever --format says: 101 is one past what an ustar + // archive keeps, 255 is the most a file system stores. + {id: "ustar_101", group: "length", fill: "u", bytes: 101, reason: "filename_too_long"}, + {id: "max_ascii", group: "length", fill: "a", bytes: 255, reason: "filename_too_long"}, + {id: "cjk_bytes", group: "length", fill: "日", bytes: 255, reason: "filename_too_long"}, + {id: "emoji_bytes", group: "length", fill: "🎉", bytes: 255, reason: "filename_too_long"}, +} + +// nameCase is one file of the set, described by how its name is made rather +// than by the name, because the extension comes from the format. +type nameCase struct { + id, group string + // stem is the name before the extension. + stem string + // decomposed writes the stem in normalisation form D - an accent as a + // separate character after its letter, a Korean syllable as its letters - + // which is how macOS hands names over and how the source here cannot show + // them apart from the composed form. + decomposed bool + extension extensionRule + // fill and bytes make a name of a length rather than of a spelling: as + // many copies of fill as fit in bytes together with the extension. + fill string + bytes int + // accepted marks a name a system would be wrong to refuse. The rest are + // unspecified, for reason, or for filename_invalid when reason is empty. + accepted bool + reason string +} + +// extensionRule is what follows the stem. +type extensionRule int + +const ( + // formatExtension is the extension of the format, as it is. + formatExtension extensionRule = iota + // noExtension is a name that is complete as it is. + noExtension + // upperExtension is the extension of the format in capitals. + upperExtension + // fullwidthExtension is the extension of the format in full width + // letters, the dots left as they are. + fullwidthExtension +) + +// name is the file name for a format whose extension is ext. +func (c nameCase) name(desc format.Descriptor) (string, error) { + ext := desc.Extension + stem := c.stem + if c.decomposed { + stem = norm.NFD.String(stem) + } + if c.fill != "" { + copies := (c.bytes - len(ext)) / len(c.fill) + if copies < 1 { + return "", &format.PropertyValueError{Format: namesID, Key: "format", Value: desc.ID, + Reason: fmt.Sprintf("its extension %s is too long for a name of %d bytes, which the file %s is about. Choose a format with a shorter extension", ext, c.bytes, c.id)} + } + stem = strings.Repeat(c.fill, copies) + } + switch c.extension { + case noExtension: + return stem, nil + case upperExtension: + return stem + strings.ToUpper(ext), nil + case fullwidthExtension: + return stem + fullwidth(ext), nil + } + return stem + ext, nil +} + +// expectation is the outcome and the reason this file carries. +func (c nameCase) expectation() (string, string) { + switch { + case c.accepted: + return "accept", "" + case c.reason != "": + return "unspecified", c.reason + } + return "unspecified", "filename_invalid" +} + +// fullwidth writes the letters and digits of s in their full width forms, +// which NFKC turns back into the ones they stand for. The dot stays, so +// report.txt still has a dot before its extension. +func fullwidth(s string) string { + var b strings.Builder + for _, r := range s { + if r > ' ' && r <= '~' && r != '.' { + r += 0xFF01 - '!' + } + b.WriteRune(r) + } + return b.String() +} + +// tagged is s in Unicode tag characters: the same letters, invisible, and read +// by a language model as text. The words are neutral on purpose. A classic +// attack carries an instruction, and an instruction here would be one for +// every tool that reviews this source. +func tagged(s string) string { + var b strings.Builder + for _, r := range s { + b.WriteRune(0xE0000 + r) + } + return b.String() +} + +func expandFileNames(args Args) ([]byte, error) { + formatID := namesFormat + if v := args["format"]; v != "" { + formatID = v + } + desc, err := format.Get(formatID) + if err != nil { + return nil, err + } + size := sampleAtLeast(desc, namesSample) + + files := make([]setFile, 0, len(nameCases)) + for _, c := range nameCases { + name, err := c.name(desc) + if err != nil { + return nil, err + } + expected, reason := c.expectation() + files = append(files, setFile{ + id: c.id, name: name, group: c.group, desc: desc, size: size, + expected: expected, reason: reason, + }) + } + // Every file asks the format the same question - one format, one size, the + // label on - so asking it once answers for all fifty (PR7). + if err := files[0].refused(); err != nil { + return nil, err + } + return plan{preset: namesID, question: namesQuestion, targets: draftsOf(files)}.source() +} diff --git a/internal/preset/uploadset.go b/internal/preset/uploadset.go index 096ca9c3..912f1a51 100644 --- a/internal/preset/uploadset.go +++ b/internal/preset/uploadset.go @@ -427,10 +427,7 @@ func wouldReach(floor, size, limit int64) int64 { // answer: 35% of expanding upload-validation, measured 2026-09-23 // (docs/GUI-MEMORY-2026-09-23.md section 4j). func sampleFor(desc format.Descriptor) int64 { - if floor := format.SmallestWithLabel(desc); floor > uploadSample { - return floor - } - return uploadSample + return sampleAtLeast(desc, uploadSample) } // farOverFiles is the one file well past the limit, or none when it was turned diff --git a/web/content/en/site.json b/web/content/en/site.json index 0c10a354..3e52c382 100644 --- a/web/content/en/site.json +++ b/web/content/en/site.json @@ -97,6 +97,7 @@ }, "presets": { "empty-and-minimal": "Does a file that is valid and as small as the format allows get through?", + "filename-handling": "Will my system store, show and give back a file name it did not expect?", "size-boundaries": "Is a size limit enforced exactly where it is declared?", "tabular-import": "Does my table import survive what real tools export?", "text-encoding": "Does my reader know which encoding a file is in, or is it guessing?", diff --git a/web/content/pl/site.json b/web/content/pl/site.json index e63529fd..98a099ff 100644 --- a/web/content/pl/site.json +++ b/web/content/pl/site.json @@ -97,6 +97,7 @@ }, "presets": { "empty-and-minimal": "Czy plik poprawny i najmniejszy, na jaki format pozwala, przechodzi?", + "filename-handling": "Czy mój system zapisze, pokaże i odda nazwę pliku, której się nie spodziewał?", "size-boundaries": "Czy limit rozmiaru działa dokładnie tam, gdzie jest zadeklarowany?", "tabular-import": "Czy import tabeli poradzi sobie z tym, co eksportują prawdziwe narzędzia?", "text-encoding": "Czy mój czytnik wie, w jakim kodowaniu jest plik, czy zgaduje?", diff --git a/web/public/docs/index.html b/web/public/docs/index.html index b5716b89..c74cae05 100644 --- a/web/public/docs/index.html +++ b/web/public/docs/index.html @@ -311,6 +311,10 @@

What is a preset?

empty-and-minimal

Does a file that is valid and as small as the format allows get through?

+
  • +

    filename-handling

    +

    Will my system store, show and give back a file name it did not expect?

    +
  • size-boundaries

    Is a size limit enforced exactly where it is declared?

    diff --git a/web/public/pl/dokumentacja/index.html b/web/public/pl/dokumentacja/index.html index 61922942..6361aa59 100644 --- a/web/public/pl/dokumentacja/index.html +++ b/web/public/pl/dokumentacja/index.html @@ -312,6 +312,10 @@

    Czym jest preset?

    empty-and-minimal

    Czy plik poprawny i najmniejszy, na jaki format pozwala, przechodzi?

  • +
  • +

    filename-handling

    +

    Czy mój system zapisze, pokaże i odda nazwę pliku, której się nie spodziewał?

    +
  • size-boundaries

    Czy limit rozmiaru działa dokładnie tam, gdzie jest zadeklarowany?

    From e4a53c3c10bd215cc68df46ab435d9640111796f Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 00:34:03 +0200 Subject: [PATCH 7/8] review: every message escapes a character nobody can see, and CI describeError returns core.ShownText, so a system error wrapped under our own sentence no longer repeats a path raw, and the window's two refusal texts go through it too. verify and cleanup show the directory and the manifest path escaped. The name preset's guard asks the names with an ASCII character at the edge. One Added heading in the changelog, and a switch that names every extension rule. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 34 +++++++++--------- internal/cli/cleanup.go | 6 ++-- internal/cli/errors.go | 13 +++++++ internal/cli/generate.go | 2 +- internal/cli/verify.go | 10 +++--- internal/core/unseen.go | 20 +++++++++++ internal/guard/filenamehandling_test.go | 10 ++++++ internal/guard/unseenoutput_test.go | 46 ++++++++++++++++++++++--- internal/gui/parts/fields.go | 6 ++-- internal/gui/window/runrefuse.go | 2 +- internal/preset/filenamehandling.go | 10 +++--- 11 files changed, 121 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e4c35a..c1d53389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,21 +14,6 @@ because it turns other people's test suites red. ## [Unreleased] -### Added - -- **A preset for unusual file names: `filename-handling`.** It answers "will - my system store, show and give back a file name it did not expect?" with - fifty names in seven groups: scripts from Polish to Korean, names that look - like other names, leading spaces and dots, shell and SQL metacharacters, - names that mean something to a web server or a desktop, names read as - values, and names at the length limits. Every one is written byte for byte - on Windows, Linux and macOS - measured on NTFS, ext4 and APFS. On another - file system a name may be refused, and the run then ends with code 8 and - names it. The files are `txt` unless `--format` says otherwise, and the - names about length count the format's extension in. Four names are - expected to be accepted, the rest are left to your system's policy with a - reason. - ### Changed - **A file name longer than 255 bytes is refused before anything is written, @@ -322,6 +307,19 @@ because it turns other people's test suites red. ### Added +- **A preset for unusual file names: `filename-handling`.** It answers "will + my system store, show and give back a file name it did not expect?" with + fifty names in seven groups: scripts from Polish to Korean, names that look + like other names, leading spaces and dots, shell and SQL metacharacters, + names that mean something to a web server or a desktop, names read as + values, and names at the length limits. Every one is written byte for byte + on Windows, Linux and macOS - measured on NTFS, ext4 and APFS. On another + file system a name may be refused, and the run then ends with code 8 and + names it. The files are `txt` unless `--format` says otherwise, and the + names about length count the format's extension in. Four names are + expected to be accepted, the rest are left to your system's policy with a + reason. + - **Every format has its full name.** `tfg formats` has a `NAME` column (`jxl` is JPEG XL, `png` Portable Network Graphics), `tfg formats jxl` gives it on a `name` line, and `tfg formats --json` carries it under the new @@ -523,9 +521,9 @@ because it turns other people's test suites red. ### Fixed - **A report shows a character nobody can see in a file name as an escape.** - `verify`, `cleanup`, the notes of a run, a refusal of two names that collide - or of a taken name, and the lines about an output directory printed such a - character as it was. A right to left override then made the terminal draw + `verify`, `cleanup`, the notes of a run, every error message and the + refusals in the window printed such a character as it was, in a file name + and in the name of a folder. A right to left override then made the terminal draw another name than the one on the disk, and a zero width space made two names look the same. Such a character is printed as an escape now, such as `\u202e` for a right to left override. A name without one is printed diff --git a/internal/cli/cleanup.go b/internal/cli/cleanup.go index c0bebb0c..536e4ecf 100644 --- a/internal/cli/cleanup.go +++ b/internal/cli/cleanup.go @@ -117,7 +117,7 @@ func previewCleanup(cands []audit.Candidate, path, dir string, force, asJSON boo return writeJSON(out, errOut, report, ExitOK) } - fmt.Fprintf(out, "%s would be removed from %s:\n", core.Count(countRemovable(cands, force), "file", "files"), dir) + fmt.Fprintf(out, "%s would be removed from %s:\n", core.Count(countRemovable(cands, force), "file", "files"), core.Shown(dir)) for _, c := range cands { if c.Removable(force) { fmt.Fprintf(out, " remove %s\n", core.Shown(c.Path)) @@ -179,7 +179,7 @@ func applyCleanup(ctx context.Context, cands []audit.Candidate, path, dir string if blocked > 0 { fmt.Fprintf(errOut, "tfg: the manifest was kept. It is the only record of %s still on disk.\n", core.Count(blocked, "file", "files")) } else if err := os.Remove(path); err != nil { - fmt.Fprintf(errOut, "tfg: cannot remove the manifest %s: %s\n", path, describeError(err)) + fmt.Fprintf(errOut, "tfg: cannot remove the manifest %s: %s\n", core.Shown(path), describeError(err)) return ExitIO } } @@ -194,7 +194,7 @@ func applyCleanup(ctx context.Context, cands []audit.Candidate, path, dir string return writeJSON(out, errOut, report, ExitOK) } - fmt.Fprintf(out, "%s removed from %s\n", core.Count(removed, "file", "files"), dir) + fmt.Fprintf(out, "%s removed from %s\n", core.Count(removed, "file", "files"), core.Shown(dir)) // A file left behind is not a silent outcome. It was reported above, and // the exit code has to carry it too or a script never learns. diff --git a/internal/cli/errors.go b/internal/cli/errors.go index 2058e0ea..9ed1b352 100644 --- a/internal/cli/errors.go +++ b/internal/cli/errors.go @@ -12,6 +12,7 @@ import ( "syscall" "github.com/donislawdev/TestingFilesGenerator/internal/audit" + "github.com/donislawdev/TestingFilesGenerator/internal/core" "github.com/donislawdev/TestingFilesGenerator/internal/damage" "github.com/donislawdev/TestingFilesGenerator/internal/engine" "github.com/donislawdev/TestingFilesGenerator/internal/format" @@ -33,7 +34,19 @@ import ( // So the system's sentence is swapped for ours and every layer of our own // context above it is kept. The number it carried stays, because a number means // the same thing in every language and it is what somebody puts into a search. +// +// And a character nobody can see is shown rather than left to act, in every +// message at once (O241). A system error wrapped under our own sentence +// repeats the path it failed on in its own words, raw, after our sentence had +// shown it escaped - found in a review on 2026-09-25. One funnel covers every +// command, including the ones that print a message nobody wrote with a name +// in mind. func describeError(err error) string { + return core.ShownText(inOurWords(err)) +} + +// inOurWords is describeError before anything is escaped. +func inOurWords(err error) string { if err == nil { return "" } diff --git a/internal/cli/generate.go b/internal/cli/generate.go index 4947e800..50e25bfd 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -656,7 +656,7 @@ func saveManifest(res *engine.Result, opt engine.Options, errOut io.Writer) int // way is a chance for the saver and the claim to mean different files. path := engine.ManifestPath(opt) if err := res.Manifest.Save(path); err != nil { - fmt.Fprintf(errOut, "tfg: cannot write the manifest to %s: %s\n", core.Shown(path), core.Shown(describeError(err))) + fmt.Fprintf(errOut, "tfg: cannot write the manifest to %s: %s\n", core.Shown(path), describeError(err)) // What that leaves behind, because the line above is about the manifest // and the person's problem is the files. Rule 6: a run that wrote files // nothing can remove says so rather than leaving it to be discovered by diff --git a/internal/cli/verify.go b/internal/cli/verify.go index af45912a..5b207ac1 100644 --- a/internal/cli/verify.go +++ b/internal/cli/verify.go @@ -70,7 +70,7 @@ Flags: dir = filepath.Dir(path) } if info, statErr := os.Stat(dir); statErr != nil || !info.IsDir() { - fmt.Fprintf(errOut, "tfg: cannot read the directory %s. Check the path and that you have permission to read it.\n", dir) + fmt.Fprintf(errOut, "tfg: cannot read the directory %s. Check the path and that you have permission to read it.\n", core.Shown(dir)) return ExitIO } @@ -84,7 +84,7 @@ Flags: // outside the directory used to arrive here as exit code 130. if verifyErr != nil { if !errors.Is(verifyErr, context.Canceled) { - fmt.Fprintf(errOut, "tfg: %s\n", core.Shown(describeError(verifyErr))) + fmt.Fprintf(errOut, "tfg: %s\n", describeError(verifyErr)) return classify(verifyErr) } fmt.Fprintf(errOut, "tfg: verify was interrupted after %s and did not check everything.\n", core.Count(len(diffs), "difference", "differences")) @@ -150,7 +150,7 @@ func reportVerify(diffs []audit.Difference, claimed int, path, dir string, asJSO } if wrong > 0 { - fmt.Fprintf(errOut, "tfg: %s does not match %s - %s:\n", dir, path, core.Count(wrong, "difference", "differences")) + fmt.Fprintf(errOut, "tfg: %s does not match %s - %s:\n", core.Shown(dir), core.Shown(path), core.Count(wrong, "difference", "differences")) echoMismatches(diffs, errOut) echoOtherRuns(diffs, errOut) return ExitVerify @@ -160,11 +160,11 @@ func reportVerify(diffs []audit.Difference, claimed int, path, dir string, asJSO // "everything is fine" about zero files invites somebody to trust a run // that never happened. if claimed == 0 { - fmt.Fprintf(errOut, "%s claims no files, so there was nothing to check.\n", path) + fmt.Fprintf(errOut, "%s claims no files, so there was nothing to check.\n", core.Shown(path)) echoOtherRuns(diffs, errOut) return ExitOK } - fmt.Fprintf(out, "%s matches %s: %s checked\n", dir, path, core.Count(claimed, "file", "files")) + fmt.Fprintf(out, "%s matches %s: %s checked\n", core.Shown(dir), core.Shown(path), core.Count(claimed, "file", "files")) echoOtherRuns(diffs, errOut) return ExitOK } diff --git a/internal/core/unseen.go b/internal/core/unseen.go index 9c66003c..1c0aa497 100644 --- a/internal/core/unseen.go +++ b/internal/core/unseen.go @@ -30,6 +30,24 @@ func Shown(s string) string { if !HoldsUnseen(s) && utf8.ValidString(s) { return s } + return shown(s, false) +} + +// ShownText is Shown for a whole message rather than one name: the line +// breaks and tabs it is laid out with stay as they are, and everything else +// nobody can see is escaped. +// +// For the places a message is turned into words for a person - the command +// line's describeError and the window's refusals - because an error wrapped +// from the operating system repeats the path it failed on in its own words, +// after this tool's sentence has already shown it. Measured on 2026-09-25, +// from a review: "cannot create the output directory" showed the folder +// escaped and the "mkdir" part after it showed it raw. +func ShownText(s string) string { + return shown(s, true) +} + +func shown(s string, layout bool) string { var b strings.Builder for i := 0; i < len(s); { r, size := utf8.DecodeRuneInString(s[i:]) @@ -37,6 +55,8 @@ func Shown(s string) string { case r == utf8.RuneError && size == 1: q := strconv.Quote(s[i : i+1]) b.WriteString(q[1 : len(q)-1]) + case layout && (r == '\n' || r == '\t'): + b.WriteRune(r) case !strconv.IsPrint(r): q := strconv.QuoteRune(r) b.WriteString(q[1 : len(q)-1]) diff --git a/internal/guard/filenamehandling_test.go b/internal/guard/filenamehandling_test.go index 011e7f09..b84ecdc4 100644 --- a/internal/guard/filenamehandling_test.go +++ b/internal/guard/filenamehandling_test.go @@ -126,6 +126,16 @@ func nameFault(target, name, ext string) string { return unless(norm.NFD.IsNormalString(name) && !norm.NFC.IsNormalString(name), "it is not in the decomposed form") case "leading_bom": return unless(strings.HasPrefix(name, string(rune(0xFEFF))), "it does not begin with a byte order mark") + case "leading_space": + return unless(strings.HasPrefix(name, " ") && !strings.HasPrefix(name, " "), "it does not begin with one plain space") + case "double_space": + return unless(strings.Contains(stem, " "), "it does not hold two spaces in a row") + case "leading_dot": + return unless(strings.HasPrefix(name, ".") && !strings.HasPrefix(name, ".."), "it does not begin with one dot") + case "leading_double_dot": + return unless(strings.HasPrefix(name, ".."), "it does not begin with two dots") + case "leading_dash": + return unless(strings.HasPrefix(name, "-"), "it does not begin with a dash") case "leading_ideographic_space": return unless(strings.HasPrefix(name, string(rune(0x3000))), "it does not begin with an ideographic space") case "unicode_tags": diff --git a/internal/guard/unseenoutput_test.go b/internal/guard/unseenoutput_test.go index 72f8e997..8885c503 100644 --- a/internal/guard/unseenoutput_test.go +++ b/internal/guard/unseenoutput_test.go @@ -13,6 +13,7 @@ import ( "github.com/donislawdev/TestingFilesGenerator/internal/cli" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/all" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/text" "github.com/donislawdev/TestingFilesGenerator/internal/recipe" ) @@ -69,7 +70,9 @@ func saysEscaped(t *testing.T, what, said, stem, unseen, rest string) { // directory and the manifest. func unseenRun(t *testing.T) (string, string) { t.Helper() - dir := t.TempDir() + // The directory has such a character too, so every line naming it is asked + // as well - verify and cleanup printed it raw until a review of 2026-09-25. + dir := filepath.Join(t.TempDir(), "run"+zeroWidth) var drafts []recipe.TargetDraft for i, name := range []string{"photo" + rightToLeft + "gpj.txt", "in" + zeroWidth + "voice.txt", "report" + lineSeparator + "ERROR.txt"} { drafts = append(drafts, recipe.TargetDraft{ID: "t" + strconv.Itoa(i), Format: "txt", Size: "1kb", Name: name}) @@ -86,11 +89,12 @@ func unseenRun(t *testing.T) (string, string) { if code := cli.Run(context.Background(), []string{"generate", path, "--out", dir}, &out, &errOut); code != cli.ExitOK { t.Fatalf("the run ended %d:\n%s", code, errOut.String()) } - m := regexp.MustCompile(`(?m)^manifest: (.+)$`).FindStringSubmatch(errOut.String()) - if m == nil { + // Joined here rather than read off the "manifest:" line, which shows the + // directory's character as an escape and so is not a path any more. + if !regexp.MustCompile(`(?m)^manifest: `).MatchString(errOut.String()) { t.Fatalf("the run did not say where its manifest is:\n%s", errOut.String()) } - return dir, strings.TrimSpace(m[1]) + return dir, filepath.Join(dir, "manifest.json") } // carriesExactly fails unless some string in a JSON report is want, byte for @@ -295,4 +299,38 @@ func TestTheLinesAboutARunShowANameNobodyCanReadAsAnEscape(t *testing.T) { } saysNothingUnseen(t, "a refusal of a held directory", busy) saysEscaped(t, "a refusal of a held directory", busy, "held", zeroWidth, "dir, so this one") + + // A directory inside that file cannot be made, and the system's own error + // under ours names the path again - raw, until a review of 2026-09-25. + underAFile := runCLI(t, "generate", "--format", "txt", "--size", "1kb", "--out", filepath.Join(file, "sub")) + if !strings.Contains(underAFile, "cannot create the output directory") { + t.Fatalf("the run did not refuse a directory inside a file, so this guard checked nothing:\n%s", underAFile) + } + saysNothingUnseen(t, "a refusal of a directory inside a file", underAFile) +} + +// The window says the same about an output directory as the command line: a +// folder named with a character nobody can see is shown with the escape, +// under the box it is about and at the foot of the form. +func TestTheWindowShowsADirectoryNobodyCanReadAsAnEscape(t *testing.T) { + parent := t.TempDir() + file := filepath.Join(parent, "out"+rightToLeft+"file") + if err := os.WriteFile(file, []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + for _, out := range []string{file, filepath.Join(file, "sub")} { + host, content := presetScreen(t) + choosePreset(t, content, "filename-handling") + fill(t, content, text.FieldOutputDir(), out) + press(t, content, "Generate") + join(host) + + said := everythingSaid(content) + // The box itself holds what was typed, raw, which is right for a box. + said = strings.ReplaceAll(said, out, "") + if !strings.Contains(said, "out"+escapeOf(rightToLeft)+"file") { + t.Fatalf("the window did not refuse %+q, or refused it without naming it:\n%s", out, said) + } + saysNothingUnseen(t, "the window's refusal of "+out, said) + } } diff --git a/internal/gui/parts/fields.go b/internal/gui/parts/fields.go index 9ae19b26..23841357 100644 --- a/internal/gui/parts/fields.go +++ b/internal/gui/parts/fields.go @@ -5,6 +5,8 @@ import ( "strings" "fyne.io/fyne/v2" + + "github.com/donislawdev/TestingFilesGenerator/internal/core" ) // Field is one labelled control that can say it was the one refused. @@ -559,9 +561,9 @@ func inTheWordsOnScreen(f *Field, err error) string { InTheWordsOf(string) string } if f.Label != "" && errors.As(err, &reworded) { - return reworded.InTheWordsOf(f.Label) + return core.ShownText(reworded.InTheWordsOf(f.Label)) } - return err.Error() + return core.ShownText(err.Error()) } // Clear takes back whatever one field was complaining about. diff --git a/internal/gui/window/runrefuse.go b/internal/gui/window/runrefuse.go index 021729da..6c5db171 100644 --- a/internal/gui/window/runrefuse.go +++ b/internal/gui/window/runrefuse.go @@ -49,7 +49,7 @@ func (r *runner) refuse(err error) { } // About the run rather than about one box, or about a setting this // screen does not draw. The foot of the form is where those belong. - loose = append(loose, one.Error()) + loose = append(loose, core.ShownText(one.Error())) } if len(loose) > 0 { r.problem.Say(strings.Join(loose, "\n\n")) diff --git a/internal/preset/filenamehandling.go b/internal/preset/filenamehandling.go index a0b5943b..594d9cf6 100644 --- a/internal/preset/filenamehandling.go +++ b/internal/preset/filenamehandling.go @@ -187,15 +187,17 @@ func (c nameCase) name(desc format.Descriptor) (string, error) { } stem = strings.Repeat(c.fill, copies) } + suffix := ext switch c.extension { + case formatExtension: case noExtension: - return stem, nil + suffix = "" case upperExtension: - return stem + strings.ToUpper(ext), nil + suffix = strings.ToUpper(ext) case fullwidthExtension: - return stem + fullwidth(ext), nil + suffix = fullwidth(ext) } - return stem + ext, nil + return stem + suffix, nil } // expectation is the outcome and the reason this file carries. From 8d77d2b1eb85685b5282f906961e1495488ccab3 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 00:35:28 +0200 Subject: [PATCH 8/8] review: no second escape under a box, where nothing reaches it raw Every refusal the window puts under the output directory box carries the path escaped by the engine already, so the escape in parts/fields.go had nothing that could make it matter - a mutation removing it stayed green. Co-Authored-By: Claude Opus 5.5 --- internal/gui/parts/fields.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/gui/parts/fields.go b/internal/gui/parts/fields.go index 23841357..9ae19b26 100644 --- a/internal/gui/parts/fields.go +++ b/internal/gui/parts/fields.go @@ -5,8 +5,6 @@ import ( "strings" "fyne.io/fyne/v2" - - "github.com/donislawdev/TestingFilesGenerator/internal/core" ) // Field is one labelled control that can say it was the one refused. @@ -561,9 +559,9 @@ func inTheWordsOnScreen(f *Field, err error) string { InTheWordsOf(string) string } if f.Label != "" && errors.As(err, &reworded) { - return core.ShownText(reworded.InTheWordsOf(f.Label)) + return reworded.InTheWordsOf(f.Label) } - return core.ShownText(err.Error()) + return err.Error() } // Clear takes back whatever one field was complaining about.