Skip to content

Commit 1eea012

Browse files
committed
Merge branch 'master' into size-limits
2 parents 7789f72 + ac7b24b commit 1eea012

File tree

12 files changed

+497
-3
lines changed

12 files changed

+497
-3
lines changed

spec/baked_file_system_spec.cr

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ class StorageWithHidden
2626
bake_folder "./storage", include_dotfiles: true
2727
end
2828

29+
class EmptyStorage
30+
extend BakedFileSystem
31+
bake_folder "./empty_storage", allow_empty: true
32+
end
33+
34+
class MultiDirStorage
35+
extend BakedFileSystem
36+
bake_folder "./storage"
37+
bake_folder "./empty_storage", allow_empty: true
38+
end
39+
40+
# Edge case storage with files already created on disk
41+
class EdgeCaseStorage
42+
extend BakedFileSystem
43+
bake_folder "./storage_edge_cases"
44+
end
45+
2946
def read_slice(path)
3047
File.open(path, "rb") do |io|
3148
Slice(UInt8).new(io.size).tap do |buf|
@@ -101,6 +118,357 @@ describe BakedFileSystem do
101118
String.new(Storage.get("string_encoding/interpolation.gz").to_slice).should eq "\#{foo} \{% macro %}\n"
102119
end
103120

121+
describe "rewind functionality" do
122+
it "allows reading file multiple times" do
123+
file = Storage.get("lorem.txt")
124+
125+
# First read
126+
first_content = file.gets_to_end
127+
first_content.should_not be_empty
128+
129+
# Rewind
130+
file.rewind
131+
132+
# Second read should be identical
133+
second_content = file.gets_to_end
134+
second_content.should eq(first_content)
135+
end
136+
137+
it "handles multiple consecutive rewinds" do
138+
file = Storage.get("lorem.txt")
139+
140+
content = file.gets_to_end
141+
142+
3.times do
143+
file.rewind
144+
file.gets_to_end.should eq(content)
145+
end
146+
end
147+
148+
it "works with binary files" do
149+
file = Storage.get("images/sidekiq.png")
150+
151+
# Read binary content
152+
first_bytes = file.to_slice
153+
first_bytes.size.should be > 0
154+
155+
file.rewind
156+
157+
# Verify identical binary content
158+
second_bytes = file.to_slice
159+
second_bytes.should eq(first_bytes)
160+
end
161+
162+
it "resets position to beginning" do
163+
file = Storage.get("lorem.txt")
164+
165+
# Read partial content
166+
file.gets(10)
167+
168+
file.rewind
169+
170+
# Should read from beginning
171+
content = file.gets_to_end
172+
content.should start_with("Lorem ipsum")
173+
end
174+
175+
it "works after partial reads" do
176+
file = Storage.get("lorem.txt")
177+
178+
# Multiple partial reads
179+
chunk1 = file.gets(5).to_s
180+
chunk2 = file.gets(5).to_s
181+
182+
file.rewind
183+
184+
# Full read should match start
185+
full_content = file.gets_to_end
186+
full_content.should start_with(chunk1 + chunk2)
187+
end
188+
189+
it "maintains file metadata after rewind" do
190+
file = Storage.get("lorem.txt")
191+
192+
original_path = file.path
193+
original_size = file.size
194+
original_compressed_size = file.compressed_size
195+
196+
file.gets_to_end
197+
file.rewind
198+
199+
# Metadata should be unchanged
200+
file.path.should eq(original_path)
201+
file.size.should eq(original_size)
202+
file.compressed_size.should eq(original_compressed_size)
203+
end
204+
end
205+
206+
describe "allow_empty parameter" do
207+
it "allows baking empty directories when allow_empty: true" do
208+
# Should not raise during module loading
209+
EmptyStorage.files.should be_empty
210+
end
211+
212+
it "returns empty array from files method" do
213+
files = EmptyStorage.files
214+
files.should be_a(Array(BakedFileSystem::BakedFile))
215+
files.size.should eq(0)
216+
end
217+
218+
it "raises NoSuchFileError for non-existent files in empty storage" do
219+
expect_raises(BakedFileSystem::NoSuchFileError) do
220+
EmptyStorage.get("nonexistent.txt")
221+
end
222+
end
223+
224+
it "returns nil with get? for non-existent files in empty storage" do
225+
EmptyStorage.get?("nonexistent.txt").should be_nil
226+
end
227+
228+
it "works with multiple empty directory loads" do
229+
files = MultiDirStorage.files
230+
# Should have files from storage directory only
231+
files.size.should eq(Storage.files.size)
232+
end
233+
end
234+
235+
describe "multiple bake_folder calls" do
236+
it "loads files from all directories" do
237+
files = MultiDirStorage.files
238+
239+
# Should have files from storage directory
240+
MultiDirStorage.get?("lorem.txt").should_not be_nil
241+
MultiDirStorage.get?("images/sidekiq.png").should_not be_nil
242+
end
243+
244+
it "maintains correct file count with multiple bake_folder" do
245+
# Count should be same as storage alone (empty_storage adds nothing)
246+
MultiDirStorage.files.size.should eq(Storage.files.size)
247+
end
248+
249+
it "allows access to files from both directories" do
250+
file1 = MultiDirStorage.get("lorem.txt")
251+
file1.path.should eq("/lorem.txt")
252+
253+
file2 = MultiDirStorage.get("images/sidekiq.png")
254+
file2.path.should eq("/images/sidekiq.png")
255+
end
256+
end
257+
258+
describe "concurrent access" do
259+
it "allows multiple fibers to read same file" do
260+
file_path = "lorem.txt"
261+
channel = Channel(String).new(10)
262+
263+
10.times do
264+
spawn do
265+
file = Storage.get(file_path)
266+
content = file.gets_to_end
267+
channel.send(content)
268+
end
269+
end
270+
271+
# Collect all results
272+
results = Array.new(10) { channel.receive }
273+
274+
# All should be identical
275+
results.uniq.size.should eq(1)
276+
results.first.should_not be_empty
277+
end
278+
279+
it "allows concurrent access to different files" do
280+
channel = Channel(Bool).new(5)
281+
282+
5.times do |i|
283+
spawn do
284+
# Access different files
285+
files = ["lorem.txt", "images/sidekiq.png"]
286+
file = Storage.get(files[i % 2])
287+
content = file.read
288+
channel.send(content.size > 0)
289+
end
290+
end
291+
292+
results = Array.new(5) { channel.receive }
293+
results.all?.should be_true
294+
end
295+
296+
it "handles concurrent rewind operations" do
297+
channel = Channel(String).new(5)
298+
299+
5.times do
300+
spawn do
301+
file = Storage.get("lorem.txt")
302+
file.gets_to_end
303+
file.rewind
304+
content = file.gets_to_end
305+
channel.send(content)
306+
end
307+
end
308+
309+
results = Array.new(5) { channel.receive }
310+
results.uniq.size.should eq(1)
311+
end
312+
313+
it "creates independent file instances" do
314+
# Get two instances of the same file
315+
file1 = Storage.get("lorem.txt")
316+
file2 = Storage.get("lorem.txt")
317+
318+
# Both instances should have the same size and path
319+
file1.path.should eq(file2.path)
320+
file1.size.should eq(file2.size)
321+
file1.compressed_size.should eq(file2.compressed_size)
322+
end
323+
end
324+
325+
describe "large file handling" do
326+
it "works with existing large files from storage" do
327+
# Test with large files like the image
328+
file = Storage.get("images/sidekiq.png")
329+
file.size.should be > 50_000
330+
content = file.gets_to_end
331+
content.bytesize.should eq(file.size)
332+
end
333+
334+
it "supports rewinding on files" do
335+
file = Storage.get("lorem.txt")
336+
first_read = file.gets_to_end
337+
file.rewind
338+
second_read = file.gets_to_end
339+
second_read.should eq(first_read)
340+
end
341+
342+
it "streams files with line iteration" do
343+
file = Storage.get("lorem.txt")
344+
line_count = 0
345+
file.each_line { |_line| line_count += 1 }
346+
line_count.should be > 0
347+
end
348+
end
349+
350+
describe "symbolic link handling" do
351+
it "handles symlinks - baked_file_system documents current behavior" do
352+
# Symlink behavior depends on the platform and how Dir.glob handles them
353+
# This test documents that the system compiles and loads successfully
354+
Storage.files.size.should be > 0
355+
end
356+
end
357+
358+
describe "compression edge cases" do
359+
it "handles .gz files without double compression" do
360+
# We have string_encoding/interpolation.gz in storage
361+
file = Storage.get("string_encoding/interpolation.gz")
362+
file.should_not be_nil
363+
file.compressed?.should be_true
364+
end
365+
366+
it "decompresses text files correctly" do
367+
file = Storage.get("lorem.txt")
368+
content = file.gets_to_end
369+
content.should_not be_empty
370+
content.should contain("Lorem ipsum")
371+
end
372+
373+
it "decompresses binary files correctly" do
374+
file = Storage.get("images/sidekiq.png")
375+
# Read decompressed content
376+
content = file.read
377+
content.bytesize.should eq(file.size)
378+
# Verify we got substantial data
379+
content.bytesize.should be > 50_000
380+
end
381+
382+
it "reports correct sizes for compressed files" do
383+
file = Storage.get("lorem.txt")
384+
# Original size should be larger than compressed
385+
file.size.should be > file.compressed_size
386+
# Original is 669 bytes, compressed should be around 400
387+
file.compressed_size.should be > 100
388+
end
389+
end
390+
391+
describe "path edge cases" do
392+
it "handles files with spaces" do
393+
file = EdgeCaseStorage.get?("with spaces.txt")
394+
file.should_not be_nil
395+
if file
396+
file.path.should eq("/with spaces.txt")
397+
file.gets_to_end.should eq("content with spaces")
398+
end
399+
end
400+
401+
it "handles files with multiple spaces" do
402+
file = EdgeCaseStorage.get?("multiple spaces.txt")
403+
file.should_not be_nil
404+
if file
405+
file.path.should eq("/multiple spaces.txt")
406+
end
407+
end
408+
409+
it "handles unicode filenames - Cyrillic" do
410+
file = EdgeCaseStorage.get?("файл.txt")
411+
file.should_not be_nil
412+
if file
413+
file.path.should eq("/файл.txt")
414+
file.gets_to_end.should eq("содержание")
415+
end
416+
end
417+
418+
it "handles unicode filenames - Chinese" do
419+
file = EdgeCaseStorage.get?("文件.txt")
420+
file.should_not be_nil
421+
if file
422+
file.path.should eq("/文件.txt")
423+
end
424+
end
425+
426+
it "handles special characters - parentheses" do
427+
file = EdgeCaseStorage.get?("file(1).txt")
428+
file.should_not be_nil
429+
if file
430+
file.path.should eq("/file(1).txt")
431+
end
432+
end
433+
434+
it "handles special characters - brackets" do
435+
file = EdgeCaseStorage.get?("file[brackets].txt")
436+
file.should_not be_nil
437+
if file
438+
file.path.should eq("/file[brackets].txt")
439+
end
440+
end
441+
442+
it "handles multiple dots in filename" do
443+
file = EdgeCaseStorage.get?("file.tar.gz")
444+
file.should_not be_nil
445+
if file
446+
file.path.should eq("/file.tar.gz")
447+
end
448+
end
449+
450+
it "handles subdirectories with spaces" do
451+
file = EdgeCaseStorage.get?("subdirectory with spaces/nested file.txt")
452+
file.should_not be_nil
453+
if file
454+
file.path.should eq("/subdirectory with spaces/nested file.txt")
455+
file.gets_to_end.should eq("nested content")
456+
end
457+
end
458+
459+
it "ensures all paths start with /" do
460+
EdgeCaseStorage.files.each do |file|
461+
file.path.should start_with("/")
462+
end
463+
end
464+
465+
it "normalizes path separators to forward slashes" do
466+
EdgeCaseStorage.files.each do |file|
467+
file.path.should_not contain("\\")
468+
end
469+
end
470+
end
471+
104472
describe ManualStorage do
105473
it do
106474
file = ManualStorage.get("hello-world.txt")

0 commit comments

Comments
 (0)