rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "bytes"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "amadan.net/rastrillo/fiesta"
11 "amadan.net/rastrillo/fiesta/internal/build"
12)
13
14// fiestaBuild runs `fiesta build -out out` as main would, and returns
15// its error.
16func fiestaBuild(t *testing.T, out string) error {
17 t.Helper()
18 var stderr bytes.Buffer
19 return run([]string{"build", "-out", out}, &stderr)
20}
21
22// TestBuildRefusesADirectoryItDoesNotOwn: the version directory under
23// -out is refused, untouched, when it holds anything and no stamp;
24// taken, emptied and rewritten when it holds the stamp; and created
25// when it is absent. What it writes is the dist, file for file.
26func TestBuildRefusesADirectoryItDoesNotOwn(t *testing.T) {
27 out := t.TempDir()
28 root := filepath.Join(out, fiesta.Version)
29 stray := filepath.Join(root, "notes.txt")
30 if err := os.MkdirAll(root, 0o755); err != nil {
31 t.Fatal(err)
32 }
33 if err := os.WriteFile(stray, []byte("mine"), 0o644); err != nil {
34 t.Fatal(err)
35 }
36
37 err := fiestaBuild(t, out)
38 if err == nil {
39 t.Fatalf("fiesta build took %s, which holds a file it did not write and no %s stamp", root, stampName)
40 }
41 if !strings.Contains(err.Error(), stampName) {
42 t.Errorf("the refusal does not name the %s stamp: %v", stampName, err)
43 }
44 if b, err := os.ReadFile(stray); err != nil || string(b) != "mine" {
45 t.Errorf("the refused directory was touched: %s is %q, %v", stray, b, err)
46 }
47 if entries, _ := os.ReadDir(root); len(entries) != 1 {
48 t.Errorf("the refused directory holds %d entries, want the one stray file", len(entries))
49 }
50
51 if err := os.WriteFile(filepath.Join(root, stampName), []byte("This directory is written by fiesta build, an earlier run of it.\n"), 0o644); err != nil {
52 t.Fatal(err)
53 }
54 if err := fiestaBuild(t, out); err != nil {
55 t.Fatalf("fiesta build refused a directory holding its own stamp: %v", err)
56 }
57 if _, err := os.Stat(stray); !os.IsNotExist(err) {
58 t.Errorf("the stray file survived a build into a directory the command owns: %v", err)
59 }
60 if b, err := os.ReadFile(filepath.Join(root, stampName)); err != nil || string(b) != stampBody("fiesta build") {
61 t.Errorf("the stamp was not rewritten: %q, %v", b, err)
62 }
63
64 dist, err := build.Dist(fiesta.FS(), fiesta.Version)
65 if err != nil {
66 t.Fatal(err)
67 }
68 if len(dist) == 0 {
69 t.Fatal("the dist is empty")
70 }
71 for name, want := range dist {
72 got, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(name)))
73 if err != nil {
74 t.Errorf("the build did not write %s: %v", name, err)
75 continue
76 }
77 if !bytes.Equal(got, want) {
78 t.Errorf("%s on disk differs from the dist", name)
79 }
80 }
81 var written int
82 err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
83 if err == nil && !d.IsDir() && filepath.Base(p) != stampName {
84 written++
85 }
86 return err
87 })
88 if err != nil {
89 t.Fatal(err)
90 }
91 if written != len(dist) {
92 t.Errorf("%s holds %d files besides the stamp, the dist has %d", root, written, len(dist))
93 }
94
95 fresh := filepath.Join(t.TempDir(), "site", "fiesta")
96 if err := fiestaBuild(t, fresh); err != nil {
97 t.Fatalf("fiesta build could not create %s: %v", fresh, err)
98 }
99 if _, err := os.Stat(filepath.Join(fresh, fiesta.Version, "fiesta.css")); err != nil {
100 t.Errorf("a build into an absent directory wrote no fiesta.css: %v", err)
101 }
102}
103
104// TestBuildNeedsItsFlag: no -out is an error that says so, as is a
105// command the binary does not have.
106func TestBuildNeedsItsFlag(t *testing.T) {
107 var stderr bytes.Buffer
108 if err := run([]string{"build"}, &stderr); err == nil || !strings.Contains(err.Error(), "-out") {
109 t.Errorf("build with no -out: %v", err)
110 }
111 if err := run([]string{"paint"}, &stderr); err == nil || !strings.Contains(err.Error(), "paint") {
112 t.Errorf("an unknown command: %v", err)
113 }
114 if err := run(nil, &stderr); err == nil {
115 t.Error("no command at all passed")
116 }
117}
118
119// fiestaGallery runs `fiesta gallery -out out` as main would, and
120// returns its error.
121func fiestaGallery(t *testing.T, out string) error {
122 t.Helper()
123 var stderr bytes.Buffer
124 return run([]string{"gallery", "-out", out}, &stderr)
125}
126
127// TestGalleryRefusesARelease: `fiesta gallery -out dist/<version>`,
128// pointed at the directory a build wrote, is refused untouched. The
129// stamp there is fiesta build's, and a stamp says whose directory it
130// is, not merely that some command once wrote it.
131func TestGalleryRefusesARelease(t *testing.T) {
132 out := t.TempDir()
133 if err := fiestaBuild(t, out); err != nil {
134 t.Fatal(err)
135 }
136 release := filepath.Join(out, fiesta.Version)
137 err := fiestaGallery(t, release)
138 if err == nil {
139 t.Fatalf("fiesta gallery emptied %s, a release fiesta build wrote", release)
140 }
141 for _, want := range []string{release, "fiesta build"} {
142 if !strings.Contains(err.Error(), want) {
143 t.Errorf("the refusal does not say %q: %v", want, err)
144 }
145 }
146 if _, err := os.Stat(filepath.Join(release, "fiesta.css")); err != nil {
147 t.Errorf("the release lost fiesta.css: %v", err)
148 }
149 if b, err := os.ReadFile(filepath.Join(release, stampName)); err != nil || string(b) != stampBody("fiesta build") {
150 t.Errorf("the release's stamp was rewritten: %q, %v", b, err)
151 }
152}
153
154// TestGalleryRefusesADirectoryHoldingARelease: a gallery directory
155// that a build has since written a release inside is refused by the
156// next gallery run, naming the release's stamp, and the release is
157// left as it was. A stamp below the directory is something else's
158// output, whichever command's it is.
159func TestGalleryRefusesADirectoryHoldingARelease(t *testing.T) {
160 out := t.TempDir()
161 if err := fiestaGallery(t, out); err != nil {
162 t.Fatal(err)
163 }
164 if err := fiestaBuild(t, out); err != nil {
165 t.Fatal(err)
166 }
167 release := filepath.Join(out, fiesta.Version)
168 err := fiestaGallery(t, out)
169 if err == nil {
170 t.Fatalf("fiesta gallery emptied %s, which held the release %s", out, release)
171 }
172 if !strings.Contains(err.Error(), filepath.Join(release, stampName)) {
173 t.Errorf("the refusal does not name %s: %v", filepath.Join(release, stampName), err)
174 }
175 if _, err := os.Stat(filepath.Join(release, "fiesta.css")); err != nil {
176 t.Errorf("the release lost fiesta.css: %v", err)
177 }
178 if _, err := os.Stat(filepath.Join(out, "index.html")); err != nil {
179 t.Errorf("the refused gallery directory was touched: %v", err)
180 }
181}
182
183// TestGalleryRefusesALinkedDirectoryHoldingARelease: the reviewer's
184// route round the check. A symbolic link to a gallery directory that
185// a release has been built inside is refused like the directory
186// itself, naming the release's stamp by its real path, and the release
187// is left as it was. The link is resolved before anything is read,
188// walked or emptied, so every step sees the same real directory.
189func TestGalleryRefusesALinkedDirectoryHoldingARelease(t *testing.T) {
190 base := t.TempDir()
191 site := filepath.Join(base, "site")
192 if err := fiestaGallery(t, site); err != nil {
193 t.Fatal(err)
194 }
195 if err := fiestaBuild(t, site); err != nil {
196 t.Fatal(err)
197 }
198 link := filepath.Join(base, "link")
199 if err := os.Symlink(site, link); err != nil {
200 t.Fatal(err)
201 }
202 release := filepath.Join(site, fiesta.Version)
203 err := fiestaGallery(t, link)
204 if err == nil {
205 t.Fatalf("fiesta gallery emptied %s through the link %s, and with it the release %s", site, link, release)
206 }
207 real, err2 := filepath.EvalSymlinks(filepath.Join(release, stampName))
208 if err2 != nil {
209 t.Fatalf("the release's stamp is gone: %v", err2)
210 }
211 if !strings.Contains(err.Error(), real) {
212 t.Errorf("the refusal does not name %s: %v", real, err)
213 }
214 if _, err := os.Stat(filepath.Join(release, "fiesta.css")); err != nil {
215 t.Errorf("the release lost fiesta.css: %v", err)
216 }
217}
218
219// TestGalleryRefusesALinkInsideItsDirectory: a symbolic link inside a
220// directory the command owns is refused by name, whatever it points
221// at, and what it points at is left alone. The commands never write a
222// link, so one inside their output is not theirs; refusing every link
223// is the one rule that both never follows a link out of the tree and
224// never empties a foreign-stamped directory through one.
225func TestGalleryRefusesALinkInsideItsDirectory(t *testing.T) {
226 base := t.TempDir()
227 site := filepath.Join(base, "site")
228 if err := fiestaGallery(t, site); err != nil {
229 t.Fatal(err)
230 }
231 dist := filepath.Join(base, "dist")
232 if err := fiestaBuild(t, dist); err != nil {
233 t.Fatal(err)
234 }
235 release := filepath.Join(dist, fiesta.Version)
236 link := filepath.Join(site, "release")
237 if err := os.Symlink(release, link); err != nil {
238 t.Fatal(err)
239 }
240 err := fiestaGallery(t, site)
241 if err == nil {
242 t.Fatalf("fiesta gallery emptied %s with the link %s in it", site, link)
243 }
244 if !strings.Contains(err.Error(), link) {
245 t.Errorf("the refusal does not name the link %s: %v", link, err)
246 }
247 if _, err := os.Lstat(link); err != nil {
248 t.Errorf("the link was removed: %v", err)
249 }
250 if _, err := os.Stat(filepath.Join(release, "fiesta.css")); err != nil {
251 t.Errorf("the release behind the link lost fiesta.css: %v", err)
252 }
253 if _, err := os.Stat(filepath.Join(site, "index.html")); err != nil {
254 t.Errorf("the refused directory was touched: %v", err)
255 }
256}
257