| @@ -0,0 +1,166 @@ |
| |
1 | package render |
| |
2 | |
| |
3 | import ( |
| |
4 | "errors" |
| |
5 | "io" |
| |
6 | "os" |
| |
7 | "path/filepath" |
| |
8 | "sync" |
| |
9 | "sync/atomic" |
| |
10 | "testing" |
| |
11 | "time" |
| |
12 | |
| |
13 | "gitbay.org/cmc/gallery/internal/format" |
| |
14 | "gitbay.org/cmc/gallery/internal/library" |
| |
15 | ) |
| |
16 | |
| |
17 | type fakeFormat struct { |
| |
18 | calls atomic.Int32 |
| |
19 | width atomic.Int32 |
| |
20 | fail bool |
| |
21 | } |
| |
22 | |
| |
23 | func (f *fakeFormat) Name() string { return "fake" } |
| |
24 | func (f *fakeFormat) Match(string) bool { return true } |
| |
25 | func (f *fakeFormat) Kind() format.Kind { return format.KindImage } |
| |
26 | func (f *fakeFormat) Metadata(string) (format.Meta, error) { return format.Meta{}, nil } |
| |
27 | func (f *fakeFormat) Resize(_ string, w int, dst io.Writer) error { |
| |
28 | f.calls.Add(1) |
| |
29 | f.width.Store(int32(w)) |
| |
30 | time.Sleep(20 * time.Millisecond) |
| |
31 | io.WriteString(dst, "partial") |
| |
32 | if f.fail { |
| |
33 | return errors.New("magick failed") |
| |
34 | } |
| |
35 | io.WriteString(dst, "-done") |
| |
36 | return nil |
| |
37 | } |
| |
38 | |
| |
39 | func item(f format.Format) *library.Item { |
| |
40 | return &library.Item{ |
| |
41 | Album: "tokyo", Name: "a.jpg", Path: "/src/a.jpg", |
| |
42 | ModTime: time.Unix(100, 0), Size: 42, |
| |
43 | Meta: format.Meta{Width: 3000, Height: 2000}, |
| |
44 | Format: f, |
| |
45 | } |
| |
46 | } |
| |
47 | |
| |
48 | func TestAllowed(t *testing.T) { |
| |
49 | for _, w := range Widths { |
| |
50 | if !Allowed(w) { |
| |
51 | t.Errorf("%d should be allowed", w) |
| |
52 | } |
| |
53 | } |
| |
54 | for _, w := range []int{0, 1, 961, 4000} { |
| |
55 | if Allowed(w) { |
| |
56 | t.Errorf("%d should not be allowed", w) |
| |
57 | } |
| |
58 | } |
| |
59 | } |
| |
60 | |
| |
61 | func TestGetGeneratesOnce(t *testing.T) { |
| |
62 | f := &fakeFormat{} |
| |
63 | r := New(t.TempDir(), 4) |
| |
64 | it := item(f) |
| |
65 | |
| |
66 | var wg sync.WaitGroup |
| |
67 | for range 10 { |
| |
68 | wg.Add(1) |
| |
69 | go func() { |
| |
70 | defer wg.Done() |
| |
71 | if _, err := r.Get(it, 960); err != nil { |
| |
72 | t.Error(err) |
| |
73 | } |
| |
74 | }() |
| |
75 | } |
| |
76 | wg.Wait() |
| |
77 | p, err := r.Get(it, 960) |
| |
78 | if err != nil { |
| |
79 | t.Fatal(err) |
| |
80 | } |
| |
81 | if n := f.calls.Load(); n != 1 { |
| |
82 | t.Fatalf("Resize calls = %d, want 1", n) |
| |
83 | } |
| |
84 | if b, _ := os.ReadFile(p); string(b) != "partial-done" { |
| |
85 | t.Fatalf("content = %q", b) |
| |
86 | } |
| |
87 | if p != r.Path(it, 960) { |
| |
88 | t.Fatalf("Get path %s != Path %s", p, r.Path(it, 960)) |
| |
89 | } |
| |
90 | } |
| |
91 | |
| |
92 | func TestGetRejectsWidth(t *testing.T) { |
| |
93 | if _, err := New(t.TempDir(), 1).Get(item(&fakeFormat{}), 961); err == nil { |
| |
94 | t.Fatal("want error") |
| |
95 | } |
| |
96 | } |
| |
97 | |
| |
98 | func TestGetClampsToSource(t *testing.T) { |
| |
99 | f := &fakeFormat{} |
| |
100 | it := item(f) |
| |
101 | it.Meta.Width = 700 |
| |
102 | if _, err := New(t.TempDir(), 1).Get(it, 960); err != nil { |
| |
103 | t.Fatal(err) |
| |
104 | } |
| |
105 | if w := f.width.Load(); w != 700 { |
| |
106 | t.Fatalf("resized to %d, want 700", w) |
| |
107 | } |
| |
108 | } |
| |
109 | |
| |
110 | func TestGetFailureLeavesNothing(t *testing.T) { |
| |
111 | f := &fakeFormat{fail: true} |
| |
112 | r := New(t.TempDir(), 1) |
| |
113 | it := item(f) |
| |
114 | if _, err := r.Get(it, 480); err == nil { |
| |
115 | t.Fatal("want error") |
| |
116 | } |
| |
117 | entries, _ := os.ReadDir(filepath.Dir(r.Path(it, 480))) |
| |
118 | if len(entries) != 0 { |
| |
119 | t.Fatalf("left behind: %v", entries) |
| |
120 | } |
| |
121 | r.Get(it, 480) |
| |
122 | if n := f.calls.Load(); n != 2 { |
| |
123 | t.Fatalf("failure was cached: calls = %d", n) |
| |
124 | } |
| |
125 | } |
| |
126 | |
| |
127 | func TestPathChangesWithSource(t *testing.T) { |
| |
128 | r := New("/cache", 1) |
| |
129 | a := item(nil) |
| |
130 | b := item(nil) |
| |
131 | b.ModTime = time.Unix(200, 0) |
| |
132 | if r.Path(a, 480) == r.Path(b, 480) { |
| |
133 | t.Fatal("path must change with mtime") |
| |
134 | } |
| |
135 | want := filepath.Join("/cache", "img", "tokyo", "a.jpg", "100000000000-42", "480.jpg") |
| |
136 | if got := r.Path(a, 480); got != want { |
| |
137 | t.Fatalf("Path = %s, want %s", got, want) |
| |
138 | } |
| |
139 | } |
| |
140 | |
| |
141 | func TestPrune(t *testing.T) { |
| |
142 | f := &fakeFormat{} |
| |
143 | r := New(t.TempDir(), 1) |
| |
144 | cur := item(f) |
| |
145 | old := item(f) |
| |
146 | old.ModTime = time.Unix(50, 0) |
| |
147 | gone := item(f) |
| |
148 | gone.Name = "gone.jpg" |
| |
149 | for _, it := range []*library.Item{cur, old, gone} { |
| |
150 | if _, err := r.Get(it, 480); err != nil { |
| |
151 | t.Fatal(err) |
| |
152 | } |
| |
153 | } |
| |
154 | lib := &library.Library{Albums: []*library.Album{{Slug: "tokyo", Items: []*library.Item{cur}}}} |
| |
155 | if err := r.Prune(lib); err != nil { |
| |
156 | t.Fatal(err) |
| |
157 | } |
| |
158 | if _, err := os.Stat(r.Path(cur, 480)); err != nil { |
| |
159 | t.Fatalf("current derivative removed: %v", err) |
| |
160 | } |
| |
161 | for _, it := range []*library.Item{old, gone} { |
| |
162 | if _, err := os.Stat(filepath.Dir(r.Path(it, 480))); !os.IsNotExist(err) { |
| |
163 | t.Fatalf("stale dir kept: %s", filepath.Dir(r.Path(it, 480))) |
| |
164 | } |
| |
165 | } |
| |
166 | } |