1use Mojo::Base -strict;
2
3use Test::More;
4use Carp qw(croak);
5use Config;
6use Mojo::Asset::File;
7use Mojo::Asset::Memory;
8use Mojo::File qw(path tempdir);
9
10subtest 'File asset' => sub {
11  my $file = Mojo::Asset::File->new;
12  is $file->size, 0, 'file is empty';
13  is $file->mtime, (stat $file->handle)[9], 'right mtime';
14  is $file->slurp, '', 'file is empty';
15  $file->add_chunk('abc');
16  is $file->contains('abc'), 0,  '"abc" at position 0';
17  is $file->contains('bc'),  1,  '"bc" at position 1';
18  is $file->contains('db'),  -1, 'does not contain "db"';
19  is $file->size, 3, 'right size';
20  is $file->mtime, (stat $file->handle)[9], 'right mtime';
21  is $file->to_file, $file, 'same object';
22
23  my $path = $file->path;
24  ok -e $path, 'temporary file exists';
25  undef $file;
26  ok !-e $path, 'temporary file has been cleaned up';
27};
28
29subtest 'Memory asset' => sub {
30  my $mem = Mojo::Asset::Memory->new;
31  $mem->add_chunk('abc');
32  is $mem->contains('abc'), 0,  '"abc" at position 0';
33  is $mem->contains('bc'),  1,  '"bc" at position 1';
34  is $mem->contains('db'),  -1, 'does not contain "db"';
35  is $mem->size,  3, 'right size';
36  is $mem->mtime, $^T, 'right mtime';
37  is $mem->mtime, Mojo::Asset::Memory->new->mtime, 'same mtime';
38  my $mtime = $mem->mtime;
39  is $mem->mtime($mtime + 23)->mtime, $mtime + 23, 'right mtime';
40};
41
42subtest 'Asset upgrade from memory to file' => sub {
43  my $mem = Mojo::Asset::Memory->new;
44  $mem->add_chunk('abcdef');
45  isa_ok $mem->to_file, 'Mojo::Asset::File', 'right class';
46  is $mem->to_file->slurp, $mem->slurp, 'same content';
47  my $file = $mem->to_file;
48  my $path = $file->path;
49  ok -e $path, 'file exists';
50  undef $file;
51  ok !-e $path, 'file has been cleaned up';
52};
53
54subtest 'Empty file asset' => sub {
55  my $file = Mojo::Asset::File->new;
56  is $file->size, 0, 'asset is empty';
57  is $file->get_chunk(0), '', 'no content';
58  is $file->slurp, '', 'no content';
59  is $file->contains('a'), -1, 'does not contain "a"';
60};
61
62subtest 'Empty memory asset' => sub {
63  my $mem = Mojo::Asset::Memory->new;
64  is $mem->size, 0, 'asset is empty';
65  is $mem->get_chunk(0), '', 'no content';
66  is $mem->slurp, '', 'no content';
67  ok !$mem->is_range, 'no range';
68  is $mem->contains('a'), -1, 'does not contain "a"';
69};
70
71subtest 'File asset range support (a[bcdefabc])' => sub {
72  my $file = Mojo::Asset::File->new(start_range => 1);
73  ok $file->is_range, 'has range';
74  $file->add_chunk('abcdefabc');
75  is $file->contains('bcdef'), 0,  '"bcdef" at position 0';
76  is $file->contains('cdef'),  1,  '"cdef" at position 1';
77  is $file->contains('abc'),   5,  '"abc" at position 5';
78  is $file->contains('db'),    -1, 'does not contain "db"';
79};
80
81subtest 'Memory asset range support (a[bcdefabc])' => sub {
82  my $mem = Mojo::Asset::Memory->new(start_range => 1);
83  ok $mem->is_range, 'has range';
84  $mem->add_chunk('abcdefabc');
85  is $mem->contains('bcdef'), 0,  '"bcdef" at position 0';
86  is $mem->contains('cdef'),  1,  '"cdef" at position 1';
87  is $mem->contains('abc'),   5,  '"abc" at position 5';
88  is $mem->contains('db'),    -1, 'does not contain "db"';
89};
90
91subtest 'File asset range support (ab[cdefghi]jk)' => sub {
92  my $file = Mojo::Asset::File->new(start_range => 2, end_range => 8);
93  ok $file->is_range, 'has range';
94  $file->add_chunk('abcdefghijk');
95  is $file->contains('cdefghi'), 0,         '"cdefghi" at position 0';
96  is $file->contains('fghi'),    3,         '"fghi" at position 3';
97  is $file->contains('f'),       3,         '"f" at position 3';
98  is $file->contains('hi'),      5,         '"hi" at position 5';
99  is $file->contains('db'),      -1,        'does not contain "db"';
100  is $file->get_chunk(0),        'cdefghi', 'chunk from position 0';
101  is $file->get_chunk(1),        'defghi',  'chunk from position 1';
102  is $file->get_chunk(5),        'hi',      'chunk from position 5';
103  is $file->get_chunk(0, 2), 'cd',  'chunk from position 0 (2 bytes)';
104  is $file->get_chunk(1, 3), 'def', 'chunk from position 1 (3 bytes)';
105  is $file->get_chunk(5, 1), 'h',   'chunk from position 5 (1 byte)';
106  is $file->get_chunk(5, 3), 'hi',  'chunk from position 5 (2 byte)';
107};
108
109subtest 'Memory asset range support (ab[cdefghi]jk)' => sub {
110  my $mem = Mojo::Asset::Memory->new(start_range => 2, end_range => 8);
111  ok $mem->is_range, 'has range';
112  $mem->add_chunk('abcdefghijk');
113  is $mem->contains('cdefghi'), 0,         '"cdefghi" at position 0';
114  is $mem->contains('fghi'),    3,         '"fghi" at position 3';
115  is $mem->contains('f'),       3,         '"f" at position 3';
116  is $mem->contains('hi'),      5,         '"hi" at position 5';
117  is $mem->contains('ij'),      -1,        'does not contain "ij"';
118  is $mem->contains('db'),      -1,        'does not contain "db"';
119  is $mem->get_chunk(0),        'cdefghi', 'chunk from position 0';
120  is $mem->get_chunk(1),        'defghi',  'chunk from position 1';
121  is $mem->get_chunk(5),        'hi',      'chunk from position 5';
122  is $mem->get_chunk(0, 2), 'cd',  'chunk from position 0 (2 bytes)';
123  is $mem->get_chunk(1, 3), 'def', 'chunk from position 1 (3 bytes)';
124  is $mem->get_chunk(5, 1), 'h',   'chunk from position 5 (1 byte)';
125  is $mem->get_chunk(5, 3), 'hi',  'chunk from position 5 (2 byte)';
126};
127
128subtest 'Huge file asset' => sub {
129  my $file = Mojo::Asset::File->new;
130  ok !$file->is_range, 'no range';
131  $file->add_chunk('a' x 131072);
132  $file->add_chunk('b');
133  $file->add_chunk('c' x 131072);
134  $file->add_chunk('ddd');
135  is $file->contains('a'),    0,      '"a" at position 0';
136  is $file->contains('b'),    131072, '"b" at position 131072';
137  is $file->contains('c'),    131073, '"c" at position 131073';
138  is $file->contains('abc'),  131071, '"abc" at position 131071';
139  is $file->contains('ccdd'), 262143, '"ccdd" at position 262143';
140  is $file->contains('dd'),   262145, '"dd" at position 262145';
141  is $file->contains('ddd'),  262145, '"ddd" at position 262145';
142  is $file->contains('e'),    -1,     'does not contain "e"';
143  is $file->contains('a' x 131072), 0,      '"a" x 131072 at position 0';
144  is $file->contains('c' x 131072), 131073, '"c" x 131072 at position 131073';
145  is $file->contains('b' . ('c' x 131072) . "ddd"), 131072, '"b" . ("c" x 131072) . "ddd" at position 131072';
146};
147
148subtest 'Huge file asset with range' => sub {
149  my $file = Mojo::Asset::File->new(start_range => 1, end_range => 262146);
150  $file->add_chunk('a' x 131072);
151  $file->add_chunk('b');
152  $file->add_chunk('c' x 131072);
153  $file->add_chunk('ddd');
154  is $file->contains('a'),    0,      '"a" at position 0';
155  is $file->contains('b'),    131071, '"b" at position 131071';
156  is $file->contains('c'),    131072, '"c" at position 131072';
157  is $file->contains('abc'),  131070, '"abc" at position 131070';
158  is $file->contains('ccdd'), 262142, '"ccdd" at position 262142';
159  is $file->contains('dd'),   262144, '"dd" at position 262144';
160  is $file->contains('ddd'),  -1,     'does not contain "ddd"';
161  is $file->contains('b' . ('c' x 131072) . 'ddd'), -1, 'does not contain "b" . ("c" x 131072) . "ddd"';
162};
163
164subtest 'Move memory asset to file' => sub {
165  my $mem  = Mojo::Asset::Memory->new->add_chunk('abc');
166  my $tmp  = Mojo::Asset::File->new->add_chunk('x');
167  my $path = $tmp->path;
168  ok -e $path, 'file exists';
169  undef $tmp;
170  ok !-e $path, 'file has been cleaned up';
171  is $mem->move_to($path)->slurp, 'abc', 'right content';
172  ok -e $path, 'file exists';
173  ok unlink($path), 'unlinked file';
174  ok !-e $path, 'file has been cleaned up';
175  is(Mojo::Asset::Memory->new->move_to($path)->slurp, '', 'no content');
176  ok -e $path, 'file exists';
177  ok unlink($path), 'unlinked file';
178  ok !-e $path, 'file has been cleaned up';
179};
180
181subtest 'Move file asset to file' => sub {
182  my $file = Mojo::Asset::File->new;
183  $file->add_chunk('bcd');
184  my $tmp = Mojo::Asset::File->new;
185  $tmp->add_chunk('x');
186  isnt $file->path, $tmp->path, 'different paths';
187  my $path = $tmp->path;
188  ok -e $path, 'file exists';
189  undef $tmp;
190  ok !-e $path, 'file has been cleaned up';
191  is $file->move_to($path)->slurp, 'bcd', 'right content';
192  undef $file;
193  ok -e $path, 'file exists';
194  ok unlink($path), 'unlinked file';
195  ok !-e $path, 'file has been cleaned up';
196  is(Mojo::Asset::File->new->move_to($path)->slurp, '', 'no content');
197  ok -e $path, 'file exists';
198  ok unlink($path), 'unlinked file';
199  ok !-e $path, 'file has been cleaned up';
200};
201
202subtest 'Upgrade' => sub {
203  my $asset = Mojo::Asset::Memory->new(max_memory_size => 5, auto_upgrade => 1);
204  my $upgrade;
205  $asset->on(upgrade => sub { $upgrade++ });
206  $asset = $asset->add_chunk('lala');
207  ok !$upgrade, 'upgrade event has not been emitted';
208  ok !$asset->is_file, 'stored in memory';
209  $asset = $asset->add_chunk('lala');
210  is $upgrade, 1, 'upgrade event has been emitted once';
211  ok $asset->is_file, 'stored in file';
212  $asset = $asset->add_chunk('lala');
213  is $upgrade, 1, 'upgrade event was not emitted again';
214  ok $asset->is_file, 'stored in file';
215  is $asset->slurp,   'lalalalalala', 'right content';
216  ok $asset->cleanup, 'file will be cleaned up';
217  $asset = Mojo::Asset::Memory->new(max_memory_size => 5);
218  $asset = $asset->add_chunk('lala');
219  ok !$asset->is_file, 'stored in memory';
220  $asset = $asset->add_chunk('lala');
221  ok !$asset->is_file, 'stored in memory';
222};
223
224subtest 'Change temporary directory during upgrade' => sub {
225  my $tmpdir = tempdir;
226  my $mem    = Mojo::Asset::Memory->new(auto_upgrade => 1, max_memory_size => 10);
227  $mem->on(
228    upgrade => sub {
229      my ($mem, $file) = @_;
230      $file->tmpdir($tmpdir);
231    }
232  );
233  my $file = $mem->add_chunk('aaaaaaaaaaa');
234  ok $file->is_file, 'stored in file';
235  is $file->slurp, 'aaaaaaaaaaa', 'right content';
236  is path($file->path)->dirname, $tmpdir, 'right directory';
237};
238
239subtest 'Temporary directory' => sub {
240  local $ENV{MOJO_TMPDIR} = my $tmpdir = tempdir;
241  my $file = Mojo::Asset::File->new;
242  is($file->tmpdir, $tmpdir, 'same directory');
243  $file->add_chunk('works!');
244  is $file->slurp, 'works!', 'right content';
245  is path($file->path)->dirname, $tmpdir, 'same directory';
246};
247
248subtest 'Custom temporary file' => sub {
249  my $tmpdir = tempdir;
250  my $path   = $tmpdir->child('test.file');
251  ok !-e $path, 'file does not exist';
252  my $file = Mojo::Asset::File->new(path => $path);
253  is $file->path, $path, 'right path';
254  ok !-e $path, 'file still does not exist';
255  $file->add_chunk('works!');
256  ok -e $path, 'file exists';
257  is $file->slurp, 'works!', 'right content';
258  undef $file;
259  ok !-e $path, 'file has been cleaned up';
260};
261
262subtest 'Temporary file without cleanup' => sub {
263  my $file = Mojo::Asset::File->new(cleanup => 0)->add_chunk('test');
264  ok $file->is_file, 'stored in file';
265  is $file->slurp,   'test', 'right content';
266  is $file->size,    4,      'right size';
267  is $file->mtime, (stat $file->handle)[9], 'right mtime';
268  is $file->contains('es'), 1, '"es" at position 1';
269  my $path = $file->path;
270  undef $file;
271  ok -e $path, 'file exists';
272  ok unlink($path), 'unlinked file';
273  ok !-e $path, 'file has been cleaned up';
274};
275
276subtest 'Incomplete write' => sub {
277  no warnings 'redefine';
278  local *IO::Handle::syswrite = sub { $! = 0; 2 };
279  eval { Mojo::Asset::File->new->add_chunk('test') };
280  like $@, qr/Can't write to asset: .*/, 'right error';
281};
282
283subtest 'Forked process' => sub {
284  plan skip_all => 'Real fork is required!' if $Config{d_pseudofork};
285  my $file = Mojo::Asset::File->new->add_chunk('Fork test!');
286  my $path = $file->path;
287  ok -e $path, 'file exists';
288  is $file->slurp, 'Fork test!', 'right content';
289  croak "Can't fork: $!" unless defined(my $pid = fork);
290  exit 0                 unless $pid;
291  waitpid $pid, 0 if $pid;
292  ok -e $path, 'file still exists';
293  is $file->slurp, 'Fork test!', 'right content';
294  undef $file;
295  ok !-e $path, 'file has been cleaned up';
296};
297
298subtest 'Abstract methods' => sub {
299  eval { Mojo::Asset->add_chunk };
300  like $@, qr/Method "add_chunk" not implemented by subclass/, 'right error';
301  eval { Mojo::Asset->contains };
302  like $@, qr/Method "contains" not implemented by subclass/, 'right error';
303  eval { Mojo::Asset->get_chunk };
304  like $@, qr/Method "get_chunk" not implemented by subclass/, 'right error';
305  eval { Mojo::Asset->move_to };
306  like $@, qr/Method "move_to" not implemented by subclass/, 'right error';
307  eval { Mojo::Asset->mtime };
308  like $@, qr/Method "mtime" not implemented by subclass/, 'right error';
309  eval { Mojo::Asset->size };
310  like $@, qr/Method "size" not implemented by subclass/, 'right error';
311  eval { Mojo::Asset->slurp };
312  like $@, qr/Method "slurp" not implemented by subclass/, 'right error';
313  eval { Mojo::Asset->to_file };
314  like $@, qr/Method "to_file" not implemented by subclass/, 'right error';
315};
316
317done_testing();
318