| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | func TestInventoryIsClassifiedAndCurrent(t *testing.T) { |
| 11 | root := filepath.Join("..", "..") |
| 12 | inv, err := build(root) |
| 13 | if err != nil { |
| 14 | t.Fatal(err) |
| 15 | } |
| 16 | if u := inv.unclassified(); len(u) > 0 { |
| 17 | t.Fatalf("unclassified entries: %v", u) |
| 18 | } |
| 19 | counts := inv.counts() |
| 20 | for _, kind := range kindOrder { |
| 21 | // native-call and frontend-native tracked the retired shell's direct |
| 22 | // bridge calls; both are legitimately empty under the Electron host. |
| 23 | if len(counts[kind]) == 0 && kind != kindNativeCall && kind != kindFrontendNative { |
| 24 | t.Errorf("no %s entries discovered", kind) |
| 25 | } |
| 26 | } |
| 27 | md, js, err := render(inv) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | for name, want := range map[string][]byte{"INVENTORY.md": md, "inventory.json": js} { |
| 32 | have, err := os.ReadFile(filepath.Join(root, "docs", "desktop-migration", name)) |
| 33 | if err != nil { |
| 34 | t.Fatalf("%s: %v", name, err) |
| 35 | } |
| 36 | if !sameInventory(have, want) { |
| 37 | t.Errorf("%s is stale; run: go run ./tools/desktopinventory", name) |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | func TestShellFileRules(t *testing.T) { |
| 43 | cases := map[string]class{ |
| 44 | "webview2_recovery_windows.go": classDeleteShell, |
| 45 | "tray_loop_windows.go": classMigrateHost, |
| 46 | "main.go": classKeepBusiness, |
| 47 | "sessions.go": "", |
| 48 | } |
| 49 | for name, want := range cases { |
| 50 | got, _, ok := shellFile(name) |
| 51 | if (want == "") == ok || got != want { |
| 52 | t.Errorf("%s: got %q ok=%v, want %q", name, got, ok, want) |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | func TestInventoryWithoutGit(t *testing.T) { |
| 58 | // Source archives and shallow CI checkouts must render the same frozen |
| 59 | // baseline as a developer checkout, without depending on available refs. |
| 60 | root := filepath.Join("..", "..") |
| 61 | withGit, err := build(root) |
| 62 | if err != nil { |
| 63 | t.Fatal(err) |
| 64 | } |
| 65 | _, want, err := render(withGit) |
| 66 | if err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | t.Setenv("PATH", t.TempDir()) |
| 70 | inv, err := build(filepath.Join("..", "..")) |
| 71 | if err != nil { |
| 72 | t.Fatal(err) |
| 73 | } |
| 74 | _, got, err := render(inv) |
| 75 | if err != nil { |
| 76 | t.Fatal(err) |
| 77 | } |
| 78 | if !bytes.Equal(got, want) { |
| 79 | t.Fatal("inventory depends on local Git availability") |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | func TestInventoryComparisonIgnoresOnlySourceLineHints(t *testing.T) { |
| 84 | const baseline = `{"name":"snapshot.sqlite","location":"desktop/session_source_compatibility.go:346","class":"keep-business","owner":"shared"}` |
| 85 | for _, test := range []struct { |
| 86 | name string |
| 87 | old string |
| 88 | new string |
| 89 | same bool |
| 90 | }{ |
| 91 | {"line", ":346", ":367", true}, |
| 92 | {"path", "session_source_compatibility.go", "other.go", false}, |
| 93 | {"entry", "snapshot.sqlite", "other.sqlite", false}, |
| 94 | {"class", "keep-business", "delete-shell", false}, |
| 95 | {"owner", "shared", "other", false}, |
| 96 | } { |
| 97 | t.Run(test.name, func(t *testing.T) { |
| 98 | changed := bytes.ReplaceAll([]byte(baseline), []byte(test.old), []byte(test.new)) |
| 99 | if got := sameInventory([]byte(baseline), changed); got != test.same { |
| 100 | t.Fatalf("sameInventory = %v, want %v", got, test.same) |
| 101 | } |
| 102 | }) |
| 103 | } |
| 104 | if sameInventory([]byte(baseline), nil) { |
| 105 | t.Fatal("removed inventory accepted") |
| 106 | } |
| 107 | if sameInventory([]byte(`{"name":"release:346"}`), []byte(`{"name":"release:367"}`)) { |
| 108 | t.Fatal("non-location number ignored") |
| 109 | } |
| 110 | } |
| 111 |