1package main23import (4 "os"5 "path/filepath"6 "testing"7)89func samplePlan() tilePlan {10 return tilePlan{11 AssetPath: "tunic__world",12 SourcePath: "tunic/world/default-v2",13 MaxFullZoom: 12,14 MaxSourceZoom: 13,15 Interpolate: true,16 PreferredFormat: "jpg",17 Bounds: &contentBounds{X: 16, Y: 32, Width: 512, Height: 512},18 Levels: map[int][]tileFile{19 12: {20 {Record: tileRecord{X: 1, Y: 1, ContentHash: "aaa"}, Format: "jpg"},21 {Record: tileRecord{X: 1, Y: 2, ContentHash: "bbb"}, Format: "jpg"},22 },23 13: {24 {Record: tileRecord{X: 2, Y: 2, ContentHash: "ccc"}, Format: "jpg"},25 },26 },27 }28}2930// The stamp decides whether half a minute of derivation is skipped, so it has31// to say the same thing about the same capture and something different about32// every part of it that changes what would be written.33func TestPlanStampAnswersForEveryInput(t *testing.T) {34 base := planStamp(samplePlan())35 if base == planStamp(tilePlan{}) {36 t.Fatal("an empty plan stamps the same as a captured one")37 }3839 // The order tiles happen to be listed in is not an input.40 shuffled := samplePlan()41 shuffled.Levels[12] = []tileFile{shuffled.Levels[12][1], shuffled.Levels[12][0]}42 if got := planStamp(shuffled); got != base {43 t.Error("the same tiles in another order stamp differently")44 }4546 changes := map[string]func(*tilePlan){47 "a tile recaptured": func(plan *tilePlan) {48 plan.Levels[12][0].Record.ContentHash = "zzz"49 },50 "a tile added": func(plan *tilePlan) {51 plan.Levels[12] = append(plan.Levels[12],52 tileFile{Record: tileRecord{X: 9, Y: 9, ContentHash: "ddd"}, Format: "jpg"})53 },54 "a level captured deeper": func(plan *tilePlan) { plan.MaxSourceZoom = 14 },55 "the complete level moved": func(plan *tilePlan) { plan.MaxFullZoom = 11 },56 "the content bounds moved": func(plan *tilePlan) { plan.Bounds.Width = 1024 },57 "the reduction changed": func(plan *tilePlan) { plan.Interpolate = false },58 "the format changed": func(plan *tilePlan) { plan.PreferredFormat = "png" },59 "the layer renamed": func(plan *tilePlan) { plan.AssetPath = "tunic__other" },60 }61 for name, change := range changes {62 t.Run(name, func(t *testing.T) {63 plan := samplePlan()64 change(&plan)65 if planStamp(plan) == base {66 t.Errorf("%s stamps the same as before it", name)67 }68 })69 }70}7172// The tool derives the pyramid, so a change to the tool has to invalidate one.73func TestToolStampIsRecorded(t *testing.T) {74 stamp := toolStamp()75 if stamp == "" || stamp == "unstamped" {76 t.Fatalf("tool stamp = %q, so nothing would notice the tool changing", stamp)77 }78 if planStamp(samplePlan()) == "" {79 t.Fatal("a plan stamped as nothing")80 }81}8283func TestLinkTreeBringsEveryFileAcross(t *testing.T) {84 from, to := filepath.Join(t.TempDir(), "from"), filepath.Join(t.TempDir(), "to")85 for _, name := range []string{"3/0/0.jpg", "3/0/1.jpg", "4/1/0.png"} {86 path := filepath.Join(from, name)87 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {88 t.Fatal(err)89 }90 if err := os.WriteFile(path, []byte(name), 0o644); err != nil {91 t.Fatal(err)92 }93 }9495 tiles, err := linkTree(from, to)96 if err != nil {97 t.Fatal(err)98 }99 if tiles != 3 {100 t.Errorf("linked %d tiles, want 3", tiles)101 }102 for _, name := range []string{"3/0/0.jpg", "3/0/1.jpg", "4/1/0.png"} {103 data, err := os.ReadFile(filepath.Join(to, name))104 if err != nil {105 t.Fatal(err)106 }107 if string(data) != name {108 t.Errorf("%s = %q, want %q", name, data, name)109 }110 }111}112113// A run installs what it derived and takes out what the archive no longer114// offers, leaving everything it carried where it already was.115func TestInstallPyramidsKeepsCarriedAndPrunesRetired(t *testing.T) {116 temp, output := t.TempDir(), t.TempDir()117 write := func(root, name string, body string) {118 path := filepath.Join(root, name)119 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {120 t.Fatal(err)121 }122 if err := os.WriteFile(path, []byte(body), 0o644); err != nil {123 t.Fatal(err)124 }125 }126 write(output, "carried/3/0/0.jpg", "kept")127 write(output, "derived/3/0/0.jpg", "old")128 write(output, "retired/3/0/0.jpg", "gone")129 write(temp, "derived/3/0/0.jpg", "new")130131 out := manifest{Lenses: []lensManifest{132 {AssetPath: "carried", SourcePath: "a"},133 {AssetPath: "derived", SourcePath: "b"},134 }}135 if err := installPyramids(temp, output, out, map[string]bool{"derived": true}); err != nil {136 t.Fatal(err)137 }138139 for name, want := range map[string]string{140 "carried/3/0/0.jpg": "kept",141 "derived/3/0/0.jpg": "new",142 } {143 data, err := os.ReadFile(filepath.Join(output, name))144 if err != nil {145 t.Fatal(err)146 }147 if string(data) != want {148 t.Errorf("%s = %q, want %q", name, data, want)149 }150 }151 if _, err := os.Stat(filepath.Join(output, "retired")); !os.IsNotExist(err) {152 t.Error("a layer the archive no longer offers was left behind")153 }154 if _, err := os.Stat(filepath.Join(output, "index.json")); err != nil {155 t.Error("the index was not written")156 }157}