package lang import ( "os" "path/filepath" "reflect" "strings" "testing" ) // realRoot is the in-repo langpack root, relative to this package's test cwd (internal/lang/). const realRoot = "../../configs/langpacks" // TestLoadResolvesRealZhRu loads the REAL zh→ru pack from disk and pins a spot-check of the moved data // (the const→file move must be byte-faithful; the full-book parity that pins the exact miner output is // the stand-gated TestMinerFullBookParity, so this is the CI-runnable anchor). It exercises the real // disk-loading path end to end. func TestLoadResolvesRealZhRu(t *testing.T) { p, err := Load(realRoot, "zh", "ru") if err != nil { t.Fatalf("Load(zh,ru): %v", err) } if p.Pair != "zh-ru" { t.Errorf("Pair = %q, want zh-ru", p.Pair) } // Source morphology spot-check (byte-faithful move). if !p.SurnamesSingle['赵'] || !p.SurnamesSingle['方'] { t.Error("surnames-single missing 赵/方") } if p.SurnamesSingle['凝'] { t.Error("surnames-single must NOT contain 凝 (discarded — not a surname)") } if !p.SurnamesCompound["欧阳"] { t.Error("surnames-compound missing 欧阳") } if p.SurnamesCompound["古月"] { t.Error("surnames-compound must NOT contain 古月 (pair-14 §1: a 蛊真人 clan, moved to the book overlay)") } if len(p.TitleSuffix) == 0 || p.TitleSuffix[0] != "公子" { t.Errorf("title-suffix order not preserved: %v", p.TitleSuffix) } if !p.TopoSuffix['山'] || !p.Numeral['三'] || !p.GradePrefix['甲'] || !p.AliasParticle['的'] { t.Error("rune-set membership missing an expected char (山/三/甲/的)") } // pair-14 moves: title-formant + sentence-terminator rune sets, and the Palladius phonotactic sets. if !p.TitleFormant['等'] || !p.TitleFormant['转'] || !p.TitleFormant['阶'] { t.Error("title-formant missing 等/转/阶") } if !p.SentenceTerminator['。'] || !p.SentenceTerminator['!'] || !p.SentenceTerminator['?'] { t.Error("sentence-terminator missing 。/!/?") } if !p.Palladius.Retroflex["zh"] || !p.Palladius.VFinal["v"] || !p.Palladius.VFinalInitial["j"] { t.Error("palladius phonotactics missing retroflex zh / vfinal v / vfinal_initial j") } // Pair transliteration spot-check. if p.Palladius.Initials["b"] != "б" || p.Palladius.Initials["zh"] != "чж" { t.Errorf("palladius initials wrong: b=%q zh=%q", p.Palladius.Initials["b"], p.Palladius.Initials["zh"]) } if p.Palladius.SpecialI["zhi"] != "чжи" { t.Errorf("palladius special_i zhi = %q, want чжи", p.Palladius.SpecialI["zhi"]) } if !strings.HasPrefix(p.Version(), packAlgoVersion+"-") { t.Errorf("Version() = %q, want %s-", p.Version(), packAlgoVersion) } } // TestRoutesByPairToDifferentBytes is the load-bearing generality proof: a SECOND pair, dropped as data // (no pipeline/ edit, no recompile), routes to DIFFERENT bytes and a DIFFERENT version. Proves "add a // language = data only" — the pair is a genuine routing key, not a baked-in zh constant. func TestRoutesByPairToDifferentBytes(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") real, err := Load(realRoot, "zh", "ru") if err != nil { t.Fatalf("load real: %v", err) } synth, err := Load(root, "xx", "yy") if err != nil { t.Fatalf("load synthetic xx-yy: %v", err) } // The synthetic pair carries its OWN surnames (not zh's), proving the resolver routes by pair, not // to a hardcoded set. if !synth.SurnamesSingle['甴'] { t.Error("synthetic pack must carry its own surname 甴") } if synth.SurnamesSingle['赵'] { t.Error("synthetic pack must NOT inherit zh's surname 赵 (would mean a baked-in constant)") } if !real.SurnamesSingle['赵'] || real.SurnamesSingle['甴'] { t.Error("real zh pack routing leaked/borrowed synthetic bytes") } if real.Version() == synth.Version() { t.Errorf("different-byte packs must have different versions (real=%s synth=%s)", real.Version(), synth.Version()) } } // TestBookOverlayUnionsAndReVersions pins the pair-14 §1 book-overlay contract: an overlay UNIONS its // private canon onto the shared pack (additive — the base entries survive, the overlay entry is added) and // SHIFTS Version() (a book-canon edit is a loud --resnapshot for that book), while a no-overlay Load is // byte-identical to before. Guards the exact mechanism the miner's {古月:22} parity rides. func TestBookOverlayUnionsAndReVersions(t *testing.T) { base, err := Load(realRoot, "zh", "ru") if err != nil { t.Fatalf("load base: %v", err) } if base.SurnamesCompound["古月"] { t.Fatal("shared pack must not carry 古月 (it is a book clan)") } overlay := t.TempDir() if err := os.MkdirAll(filepath.Join(overlay, "zh"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(overlay, "zh", "surnames-compound.txt"), []byte("# book canon\n古月\n"), 0o644); err != nil { t.Fatal(err) } ext, err := LoadWithOverlay(realRoot, "zh", "ru", overlay) if err != nil { t.Fatalf("load with overlay: %v", err) } if !ext.SurnamesCompound["古月"] { t.Error("overlay must add 古月 to the effective pack") } if !ext.SurnamesCompound["欧阳"] { t.Error("overlay must UNION (keep the shared 欧阳), not replace") } if ext.Version() == base.Version() { t.Errorf("overlay must shift Version() (base=%s overlay=%s)", base.Version(), ext.Version()) } // An empty overlayRoot is exactly Load — same version, no re-hash. same, err := LoadWithOverlay(realRoot, "zh", "ru", "") if err != nil { t.Fatal(err) } if same.Version() != base.Version() { t.Errorf("empty overlay must equal Load (%s != %s)", same.Version(), base.Version()) } // A MISNAMED overlay file must FAIL LOUD, not be silently ignored (review finding, scale lens): a typo'd // canon file that never reaches the miner would degrade recall with no signal. bad := t.TempDir() if err := os.MkdirAll(filepath.Join(bad, "zh"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(bad, "zh", "surname-compound.txt"), []byte("古月\n"), 0o644); err != nil { // typo: missing 's' t.Fatal(err) } if _, err := LoadWithOverlay(realRoot, "zh", "ru", bad); err == nil { t.Error("a misnamed overlay file must fail loud (silently-ignored canon would degrade recall)") } else if !strings.Contains(err.Error(), "surname-compound.txt") { t.Errorf("error must name the unexpected file, got: %v", err) } } // TestFailsLoudOnMissingPair pins the fail-loud contract: a pair with no pack directory errors, naming the // missing file — never a silent empty pack (mirrors the prompt seam's PromptPathFor fail-loud). This is // the load-time invariant a live consumer relies on (fail before billing). func TestFailsLoudOnMissingPair(t *testing.T) { _, err := Load(realRoot, "ja", "ru") if err == nil { t.Fatal("Load(ja,ru) must fail loud (no ja langpack), got nil error") } if !strings.Contains(err.Error(), "ja-ru") { t.Errorf("error must name the missing pair ja-ru, got: %v", err) } } // TestFailsLoudOnEmptyTable pins the "never silently empty" contract: a present-but-empty (comment-only) // required file is a corrupt pack and must fail loud at load — not parse to an empty set that silently // disables a miner channel once packs are hand-authored (R1). func TestFailsLoudOnEmptyTable(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") // Blank out one required table (keep the file present, drop its content). if err := os.WriteFile(filepath.Join(root, "xx", "surnames-single.txt"), []byte("# emptied by a bad edit\n"), 0o644); err != nil { t.Fatal(err) } _, err := Load(root, "xx", "yy") if err == nil { t.Fatal("an empty required table must fail loud, got nil error") } if !strings.Contains(err.Error(), "surnames-single") { t.Errorf("error must name the empty table, got: %v", err) } } // writeSyntheticPack writes a minimal, VALID pack for (src, tgt) under root with bytes distinct from zh — // enough for Load to succeed and for the routing assertion to bite. func writeSyntheticPack(t *testing.T, root, src, tgt string) { t.Helper() pair := src + "-" + tgt files := map[string]string{ filepath.Join(src, "surnames-single.txt"): "# synthetic\n甴甶甹\n", filepath.Join(src, "surnames-compound.txt"): "# synthetic\n甲乙\n", filepath.Join(src, "title-suffix.txt"): "# synthetic\n阁下\n", filepath.Join(src, "ordinal-title.txt"): "# synthetic\n第甲\n", filepath.Join(src, "rank-word.txt"): "# synthetic\n級\n", filepath.Join(src, "topo-suffix.txt"): "# synthetic\n峰\n", filepath.Join(src, "grade-prefix.txt"): "# synthetic\n子丑\n", filepath.Join(src, "numeral.txt"): "# synthetic\n壹貳\n", filepath.Join(src, "alias-particle.txt"): "# synthetic\n之乎\n", filepath.Join(src, "title-formant.txt"): "# synthetic\n甼\n", filepath.Join(src, "sentence-terminator.txt"): "# synthetic\n。\n", filepath.Join(pair, "palladius.txt"): "# synthetic\ninitials\tb\tб\nfinals\ta\tа\nyw\tyi\tи\nspecial_i\tzhi\tчжи\n", filepath.Join(pair, "palladius-phonotactics.txt"): "# synthetic\nretroflex\tzh\nvfinal\tv\nvfinal_initial\tj\n", } for rel, body := range files { p := filepath.Join(root, rel) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o644); err != nil { t.Fatal(err) } } } // --- pack-15: the overlay's fail-loud one level UP + the Palladius category contract ----------------- // TestOverlayRootFailsLoud pins the pack-15 guards ABOVE the per-file check: langpack_extend is an // explicit opt-in, so a path that does not exist (a typo, a moved book directory) must STOP the load // rather than degrade recall in silence, and an overlay root may hold nothing but its two expected // subdirectories (a data file dropped at the root, or a mistyped pair dir, was silently ignored before). func TestOverlayRootFailsLoud(t *testing.T) { t.Run("missing root", func(t *testing.T) { _, err := LoadWithOverlay(realRoot, "zh", "ru", filepath.Join(t.TempDir(), "nope")) if err == nil { t.Fatal("a missing overlay root must fail loud (explicit opt-in — a typo would silently drop the book canon)") } if !strings.Contains(err.Error(), "nope") { t.Errorf("error must name the path it looked for, got: %v", err) } }) t.Run("file at the root", func(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "zh"), 0o755); err != nil { t.Fatal(err) } // The canon file dropped at the ROOT instead of inside zh/ — the fold never looks here. if err := os.WriteFile(filepath.Join(root, "surnames-compound.txt"), []byte("古月\n"), 0o644); err != nil { t.Fatal(err) } if _, err := LoadWithOverlay(realRoot, "zh", "ru", root); err == nil { t.Error("a data file at the overlay root must fail loud (it is silently ignored otherwise)") } else if !strings.Contains(err.Error(), "surnames-compound.txt") { t.Errorf("error must name the unexpected entry, got: %v", err) } }) t.Run("mistyped pair directory", func(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "zh-rus"), 0o755); err != nil { // typo: zh-rus t.Fatal(err) } if _, err := LoadWithOverlay(realRoot, "zh", "ru", root); err == nil { t.Error("a mistyped pair directory must fail loud") } else if !strings.Contains(err.Error(), "zh-rus") { t.Errorf("error must name the unexpected directory, got: %v", err) } }) } // TestOverlayKeyCollisionFailsLoud pins D39.23: an overlay may only ADD. A Palladius key the shared pair // pack already defines would have been silently overridden by the overlay's value — override is not // ratified, so the collision is loud and names the key and both values. func TestOverlayKeyCollisionFailsLoud(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") overlay := t.TempDir() if err := os.MkdirAll(filepath.Join(overlay, "xx-yy"), 0o755); err != nil { t.Fatal(err) } // `initials b` is already б in the base pack; the overlay redefines it as п. if err := os.WriteFile(filepath.Join(overlay, "xx-yy", "palladius.txt"), []byte("initials\tb\tп\n"), 0o644); err != nil { t.Fatal(err) } _, err := LoadWithOverlay(root, "xx", "yy", overlay) if err == nil { t.Fatal("a base↔overlay key collision must fail loud (override is not ratified — D39.23)") } for _, want := range []string{"initials", `"b"`, "б", "п"} { if !strings.Contains(err.Error(), want) { t.Errorf("collision error must mention %q, got: %v", want, err) } } // An overlay that only ADDS a new key is fine (the additive contract still holds). ok := t.TempDir() if err := os.MkdirAll(filepath.Join(ok, "xx-yy"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(ok, "xx-yy", "palladius.txt"), []byte("initials\tp\tп\n"), 0o644); err != nil { t.Fatal(err) } p, err := LoadWithOverlay(root, "xx", "yy", ok) if err != nil { t.Fatalf("an additive overlay must load: %v", err) } if p.Palladius.Initials["b"] != "б" || p.Palladius.Initials["p"] != "п" { t.Errorf("additive overlay must keep the base key and add the new one: %v", p.Palladius.Initials) } } // TestPalladiusParserToleratesUnknownCategory pins the owner addendum (24.07): the GENERIC parser is // category-agnostic — an unknown category is data it does not interpret, never a parse error. Which // categories are required (and which are consumed) is the CONSUMER's call, asserted separately below. func TestPalladiusParserToleratesUnknownCategory(t *testing.T) { cats, err := parseCategoryRows([]byte("initials\tb\tб\nfuture_channel\tx\ty\nretroflex\tzh\n")) if err != nil { t.Fatalf("the generic parser must not reject an unknown category: %v", err) } if cats["initials"]["b"] != "б" || cats["retroflex"]["zh"] != "" || cats["future_channel"]["x"] != "y" { t.Errorf("parser must return every category verbatim, got %v", cats) } // A malformed ROW is still an error (the parser's own contract), naming the physical line. if _, err := parseCategoryRows([]byte("initials\n")); err == nil { t.Error("a row with too few fields must fail loud") } } // TestPalladiusRequiredCategoriesFailLoud pins the consumer half: a required category missing from the // data (here: emptied) is caught by validate() at load, and a category NOTHING consumes (a typo like // `initals`) is caught by the consumer's merge — the two failure modes the generic parser deliberately // does not judge. func TestPalladiusRequiredCategoriesFailLoud(t *testing.T) { t.Run("required category missing", func(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") // Drop the `finals` category (keep the file, keep the other categories). if err := os.WriteFile(filepath.Join(root, "xx-yy", "palladius.txt"), []byte("initials\tb\tб\nyw\tyi\tи\nspecial_i\tzhi\tчжи\n"), 0o644); err != nil { t.Fatal(err) } _, err := Load(root, "xx", "yy") if err == nil { t.Fatal("a missing REQUIRED Palladius category must fail loud at load (validate)") } if !strings.Contains(err.Error(), "palladius/finals") { t.Errorf("error must name the empty required table, got: %v", err) } }) t.Run("typo'd category is not consumed", func(t *testing.T) { root := t.TempDir() writeSyntheticPack(t, root, "xx", "yy") if err := os.WriteFile(filepath.Join(root, "xx-yy", "palladius.txt"), []byte("initals\tb\tб\nfinals\ta\tа\nyw\tyi\tи\nspecial_i\tzhi\tчжи\n"), 0o644); err != nil { // typo: initals t.Fatal(err) } _, err := Load(root, "xx", "yy") if err == nil { t.Fatal("a category nothing consumes must fail loud (it would silently leave its table empty)") } if !strings.Contains(err.Error(), "initals") { t.Errorf("error must name the unconsumed category, got: %v", err) } }) } // TestTerminatorClassesAreOneSource pins the pack-15 dedup: the source chunker and the coverage gate no // longer keep a private copy each of the terminator switches — both read these classes. The CJK class // splits zero-width; the generic class needs whitespace. A rune of either class is a terminator. func TestTerminatorClassesAreOneSource(t *testing.T) { term := DefaultTerminators() for _, r := range []rune{'。', '!', '?'} { if !term.IsCJK(r) || !term.IsTerminator(r) { t.Errorf("%q must be a CJK terminator", string(r)) } } for _, r := range []rune{'.', '!', '?', '…'} { if term.IsCJK(r) { t.Errorf("%q must NOT be zero-width-splitting (the ellipsis/ASCII class needs whitespace)", string(r)) } if !term.IsTerminator(r) { t.Errorf("%q must be a terminator", string(r)) } } for _, r := range []rune{',', '、', 'a', '「'} { if term.IsTerminator(r) { t.Errorf("%q must not be a terminator", string(r)) } } } // TestChapterMarkerIsData pins the pack-15 move of 第 out of the ingest regex literal into cjk-section: // the marker is data beside the numerals it precedes. func TestChapterMarkerIsData(t *testing.T) { sec := DefaultCJKSection() if got := sec.ChapterMarkerClass(); got != "第" { t.Errorf("chapter marker class = %q, want 第", got) } } // --- genre glossary (pack-20 / D39.42 п.1) ------------------------------------------------------- // TestGenreGlossaryLoadsAndScopesByGenre pins the pair/genre reference data: it reaches the pack, its // rows keep their authored order, and the optional genre label scopes them. Unlabelled rows are the // pair-wide conventions every book of the pair reads. func TestGenreGlossaryLoadsAndScopesByGenre(t *testing.T) { p, err := Load("../../configs/langpacks", "zh", "ru") if err != nil { t.Fatalf("load: %v", err) } // The MECHANISM is pinned, never the CONTENT. The file is an unsigned draft curated by the owner, and // polygon package seven has rows of it out for a signature decision — a test naming a specific rendering // would make editing one line cost a test edit, which is exactly the cementing the addendum of 26.07 // forbids. So: the pack loads a glossary, every row is well-formed, and nothing is asserted about which // words are in it. if len(p.GenreGlossary) == 0 { t.Fatal("the shipped zh-ru pack must carry a genre glossary (the terminologist's industry anchor)") } for _, g := range p.GenreGlossary { if strings.TrimSpace(g.Src) == "" || strings.TrimSpace(g.Dst) == "" { t.Fatalf("every shipped row must carry both sides: %#v", g) } } // An unlabelled row applies to every genre, including one the file never mentions. if len(p.GenreGlossaryFor("ранобэ")) != len(p.GenreGlossary) { t.Fatalf("unlabelled rows must apply to any genre: %d of %d", len(p.GenreGlossaryFor("ранобэ")), len(p.GenreGlossary)) } } func TestGenreGlossaryParseAndScoping(t *testing.T) { rows, err := parseGenreGlossary([]byte("# c\n甲\tальфа\n乙\tбета\tсянься\n\n丙\tгамма\tРоман\n")) if err != nil { t.Fatalf("parse: %v", err) } want := []GenreTerm{{Src: "甲", Dst: "альфа"}, {Src: "乙", Dst: "бета", Genre: "сянься"}, {Src: "丙", Dst: "гамма", Genre: "Роман"}} if !reflect.DeepEqual(rows, want) { t.Fatalf("parse = %#v, want %#v", rows, want) } p := &Pack{GenreGlossary: rows} // Case-folded, trimmed label match; unlabelled rows always apply. got := p.GenreGlossaryFor(" роман ") if len(got) != 2 || got[0].Src != "甲" || got[1].Src != "丙" { t.Fatalf("genre scoping = %#v", got) } if len(p.GenreGlossaryFor("")) != 1 { t.Fatalf("a book with no genre reads only the pair-wide rows, got %#v", p.GenreGlossaryFor("")) } // A malformed row is a LOUD refusal, never a silently dropped convention. for _, bad := range []string{"甲\n", "甲\t\n", "\tальфа\n", "甲\tальфа\n甲\tбета\n"} { if _, err := parseGenreGlossary([]byte(bad)); err == nil { t.Fatalf("malformed glossary %q must fail loud", bad) } } } // TestGenreGlossaryAbsenceIsFree pins the optional contract: a pair WITHOUT the file loads fine and its // pack version is unaffected by the feature existing — shipping the terminologist re-bills nobody. func TestGenreGlossaryAbsenceIsFree(t *testing.T) { root := t.TempDir() mirrorPackWithout(t, "../../configs/langpacks", root, "zh-ru/genre-glossary.txt") p, err := Load(root, "zh", "ru") if err != nil { t.Fatalf("a pair with no genre glossary must load: %v", err) } if p.GenreGlossary != nil { t.Fatalf("absent file → nil glossary, got %#v", p.GenreGlossary) } if len(p.GenreGlossaryFor("любой")) != 0 { t.Fatal("a pack with no glossary must yield no anchor") } } // mirrorPackWithout copies a langpack tree to dst, skipping one relative path. func mirrorPackWithout(t *testing.T, src, dst, skip string) { t.Helper() err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { if err != nil { return err } rel, rerr := filepath.Rel(src, path) if rerr != nil { return rerr } if info.IsDir() { return os.MkdirAll(filepath.Join(dst, rel), 0o755) } if filepath.ToSlash(rel) == skip { return nil } b, rerr := os.ReadFile(path) if rerr != nil { return rerr } return os.WriteFile(filepath.Join(dst, rel), b, 0o644) }) if err != nil { t.Fatal(err) } } // TestGenreGlossaryEditDoesNotMoveThePackVersion is the addendum's «правка строки не должна стоить // пере-капчера», enforced where it actually costs money: the pack version is folded into the run snapshot, // so if this file were hashed, the curator signing one industry rendering would re-bill every book of the // pair — and a langpack move is not a bank-only move, so none of it could re-pin at $0. Runs over a COPY of // the shipped pack, so it pins the real schema rather than a fixture that might drift from it. func TestGenreGlossaryEditDoesNotMoveThePackVersion(t *testing.T) { root := t.TempDir() for _, dir := range []string{"zh", "zh-ru"} { copyPackDir(t, filepath.Join("../../configs/langpacks", dir), filepath.Join(root, dir)) } gg := filepath.Join(root, "zh-ru", "genre-glossary.txt") if _, err := os.Stat(gg); err != nil { t.Skipf("the shipped pair ships no genre glossary: %v", err) } p1, err := Load(root, "zh", "ru") if err != nil { t.Fatal(err) } before := len(p1.GenreGlossary) if err := os.WriteFile(gg, []byte("# edited by the curator\n甲乙丙\tальфа-бета\n"), 0o644); err != nil { t.Fatal(err) } p2, err := Load(root, "zh", "ru") if err != nil { t.Fatal(err) } if p1.Version() != p2.Version() { t.Fatalf("an editorial glossary edit must not move the pack version (it would re-bill every book of the pair):\n %s\n %s", p1.Version(), p2.Version()) } if len(p2.GenreGlossary) != 1 || p2.GenreGlossary[0].Dst != "альфа-бета" { t.Fatalf("…and the edit must still take effect for the terminologist (was %d rows): %+v", before, p2.GenreGlossary) } // A file that DOES shape a snapshot-folded stage must still move the version — the exemption is one file // wide, not a hole in the fold. sur := filepath.Join(root, "zh", "surnames-single.txt") b, err := os.ReadFile(sur) if err != nil { t.Fatal(err) } if err := os.WriteFile(sur, append(b, []byte("\n仇\n")...), 0o644); err != nil { t.Fatal(err) } p3, err := Load(root, "zh", "ru") if err != nil { t.Fatal(err) } if p3.Version() == p2.Version() { t.Fatal("a miner-table edit MUST move the pack version (it shapes a folded stage)") } } func copyPackDir(t *testing.T, from, to string) { t.Helper() if err := os.MkdirAll(to, 0o755); err != nil { t.Fatal(err) } entries, err := os.ReadDir(from) if err != nil { t.Fatal(err) } for _, e := range entries { if e.IsDir() { continue } b, err := os.ReadFile(filepath.Join(from, e.Name())) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(to, e.Name()), b, 0o644); err != nil { t.Fatal(err) } } }