rastrillo / fiesta Public

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

Plain git — no account needed to clone.

Download

Download this file

1package gallery
2
3import (
4 "path"
5 "reflect"
6 "sort"
7 "strings"
8 "testing"
9
10 "amadan.net/rastrillo/fiesta"
11)
12
13// maxPageBytes is the most one HTML file of the tree may weigh. Fiesta
14// has no debt table: every page is under it, and a page that is not
15// fails by name.
16const maxPageBytes = 128 << 10
17
18// siteMount is where the site serves the tree, so the pages weighed
19// are the pages a reader is sent, URLs and all.
20const siteMount = "/design-system"
21
22// minTreeHTML is the fewest HTML files a whole tree holds: thirty-six
23// directories, three themes by twelve locales, of at least a hundred
24// pages and frame documents each. A render that came back empty, or
25// with a theme or a locale missing, is under it, so neither gate below
26// can pass by reading nothing.
27const minTreeHTML = 36 * 100
28
29// htmlFiles counts the HTML files in a tree.
30func htmlFiles(files map[string][]byte) int {
31 n := 0
32 for name := range files {
33 if strings.HasSuffix(name, ".html") {
34 n++
35 }
36 }
37 return n
38}
39
40// TestEveryPageStaysUnderItsBudget: a page's weight is what a reader
41// waits for, so it is held per page, not in total: every .html file of
42// the tree, pages and frame documents alike, is at most 128 KiB. The
43// tree's total is logged, being worth knowing and worth nothing as a
44// ceiling. Ported from Rastrillo without its debt table, which Fiesta
45// has no entry for.
46//
47// Already passing when written. The mutation that turns it red is
48// 200 KiB of ballast in pageTemplate's head, a meta element's content,
49// which puts every gallery page over. Not a comment: html/template
50// drops comments from a template's text, so a comment weighs nothing.
51func TestEveryPageStaysUnderItsBudget(t *testing.T) {
52 files := render(t, siteMount)
53 if n := htmlFiles(files); n < minTreeHTML {
54 t.Fatalf("the tree holds %d HTML files; a whole one holds at least %d, so this would weigh too little to mean anything", n, minTreeHTML)
55 }
56 var total, heaviest int
57 var heaviestName string
58 var over []string
59 for name, body := range files {
60 total += len(body)
61 if path.Ext(name) != ".html" {
62 continue
63 }
64 if len(body) > heaviest {
65 heaviest, heaviestName = len(body), name
66 }
67 if len(body) > maxPageBytes {
68 over = append(over, name)
69 }
70 }
71 sort.Strings(over)
72 for i, name := range over {
73 if i == 10 {
74 t.Errorf("… and %d more over the %d-byte budget", len(over)-i, maxPageBytes)
75 break
76 }
77 t.Errorf("%s is %d bytes, over its %d-byte budget", name, len(files[name]), maxPageBytes)
78 }
79 t.Logf("heaviest page: %s at %d bytes; whole tree: %d files (%d HTML), %d bytes (%.2f MiB)",
80 heaviestName, heaviest, len(files), htmlFiles(files), total, float64(total)/(1<<20))
81}
82
83// TestRenderIsDeterministic: two renders of the same source are byte
84// for byte the same, file for file. Every map on the way to the output
85// has to be read in a fixed order; without this gate a map's random
86// order would show as a different byte stream on every build of the
87// site, in thousands of files, for no change at all. The first render
88// is the package's shared one at the site's mount, the second a fresh
89// one, and each must be a whole tree.
90//
91// Already passing when written. The mutation that turns it red is a
92// time.Now() in a template, a "now" func printed into pageTemplate's
93// head: every gallery page then differs between the two renders.
94func TestRenderIsDeterministic(t *testing.T) {
95 first := render(t, siteMount)
96 second, err := Render(fiesta.FS(), Options{Mount: siteMount})
97 if err != nil {
98 t.Fatalf("Render: %v", err)
99 }
100 for i, files := range []map[string][]byte{first, second} {
101 if n := htmlFiles(files); n < minTreeHTML {
102 t.Fatalf("render %d holds %d HTML files; a whole tree holds at least %d, so two empty renders would agree", i+1, n, minTreeHTML)
103 }
104 }
105 if reflect.DeepEqual(first, second) {
106 t.Logf("two renders of %d files agree byte for byte", len(first))
107 return
108 }
109 var diffs []string
110 for name, a := range first {
111 b, ok := second[name]
112 switch {
113 case !ok:
114 diffs = append(diffs, "only in the first: "+name)
115 case string(a) != string(b):
116 diffs = append(diffs, "differs: "+name)
117 }
118 }
119 for name := range second {
120 if _, ok := first[name]; !ok {
121 diffs = append(diffs, "only in the second: "+name)
122 }
123 }
124 sort.Strings(diffs)
125 if len(diffs) > 20 {
126 diffs = append(diffs[:20], "…")
127 }
128 t.Fatalf("Render is not deterministic:\n%s", strings.Join(diffs, "\n"))
129}
130