15699: Fix fencepost error at end of stream.
[arvados.git] / sdk / ruby / test / test_collection.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 require "arvados/collection"
6 require "minitest/autorun"
7 require "sdk_fixtures"
8
9 class CollectionTest < Minitest::Test
10   include SDKFixtures
11
12   TWO_BY_TWO_BLOCKS = SDKFixtures.random_blocks(2, 9)
13   TWO_BY_TWO_MANIFEST_A =
14     [". #{TWO_BY_TWO_BLOCKS.first} 0:5:f1 5:4:f2\n",
15      "./s1 #{TWO_BY_TWO_BLOCKS.last} 0:5:f1 5:4:f3\n"]
16   TWO_BY_TWO_MANIFEST_S = TWO_BY_TWO_MANIFEST_A.join("")
17
18   def abcde_blocks
19     ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+9", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+9", "cccccccccccccccccccccccccccccccc+9", "dddddddddddddddddddddddddddddddd+9", "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+9"]
20   end
21
22   ### .new
23
24   def test_empty_construction
25     coll = Arv::Collection.new
26     assert_equal("", coll.manifest_text)
27   end
28
29   def test_successful_construction
30     [:SIMPLEST_MANIFEST, :MULTIBLOCK_FILE_MANIFEST, :MULTILEVEL_MANIFEST].
31         each do |manifest_name|
32       manifest_text = SDKFixtures.const_get(manifest_name)
33       coll = Arv::Collection.new(manifest_text)
34       assert_equal(manifest_text, coll.manifest_text,
35                    "did not get same manifest back out from #{manifest_name}")
36     end
37   end
38
39   def test_range_edge_cases
40     [
41       ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1\n",
42       ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2\n",
43       ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file1\n",
44       ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:file1 0:0:file2 0:0:file1\n",
45       ". 0cc175b9c0f1b6a831c399e269772661+1 0:0:file1 1:0:file2 1:0:file1\n",
46     ].each do |txt|
47       coll = Arv::Collection.new(txt)
48       coll.normalize
49       assert_match(/ 0:0:file1/, coll.manifest_text)
50     end
51     [
52       ". d41d8cd98f00b204e9800998ecf8427e+0 1:0:file1\n",
53       ". 0cc175b9c0f1b6a831c399e269772661+1 0:0:file1 2:0:file2 1:0:file1\n",
54     ].each do |txt|
55       assert_raises(RangeError) do
56         coll = Arv::Collection.new(txt)
57         coll.normalize
58       end
59     end
60   end
61
62   def test_non_manifest_construction_error
63     ["word", ". abc def", ". #{random_block} 0:", ". / !"].each do |m_text|
64       assert_raises(ArgumentError,
65                     "built collection from manifest #{m_text.inspect}") do
66         Arv::Collection.new(m_text)
67       end
68     end
69   end
70
71   def test_file_directory_conflict_construction_error
72     assert_raises(ArgumentError) do
73       Arv::Collection.new(NAME_CONFLICT_MANIFEST)
74     end
75   end
76
77   def test_no_implicit_normalization
78     coll = Arv::Collection.new(NONNORMALIZED_MANIFEST)
79     assert_equal(NONNORMALIZED_MANIFEST, coll.manifest_text)
80   end
81
82   ### .normalize
83
84   def test_non_posix_path_handling
85     m_text = "./.. #{random_block(9)} 0:5:. 5:4:..\n"
86     coll = Arv::Collection.new(m_text.dup)
87     coll.normalize
88     assert_equal(m_text, coll.manifest_text)
89   end
90
91   def test_escaping_through_normalization
92     coll = Arv::Collection.new(MANY_ESCAPES_MANIFEST)
93     coll.normalize
94     # The result should simply duplicate the file spec.
95     # The source file spec has an unescaped backslash in it.
96     # It's OK for the Collection class to properly escape that.
97     expect_text = MANY_ESCAPES_MANIFEST.sub(/ \d+:\d+:\S+/) do |file_spec|
98       file_spec.gsub(/([^\\])(\\[^\\\d])/, '\1\\\\\2')
99     end
100     assert_equal(expect_text, coll.manifest_text)
101   end
102
103   def test_concatenation_with_locator_overlap(over_index=0)
104     blocks = random_blocks(4, 2)
105     blocks_s = blocks.join(" ")
106     coll = Arv::Collection.new(". %s 0:8:file\n. %s 0:4:file\n" %
107                                [blocks_s, blocks[over_index, 2].join(" ")])
108     coll.normalize
109     assert_equal(". #{blocks_s} 0:8:file #{over_index * 2}:4:file\n",
110                  coll.manifest_text)
111   end
112
113   def test_concatenation_with_middle_locator_overlap
114     test_concatenation_with_locator_overlap(1)
115   end
116
117   def test_concatenation_with_end_locator_overlap
118     test_concatenation_with_locator_overlap(2)
119   end
120
121   def test_concatenation_with_partial_locator_overlap
122     blocks = random_blocks(3, 3)
123     coll = Arv::Collection
124       .new(". %s 0:6:overlap\n. %s 0:6:overlap\n" %
125            [blocks[0, 2].join(" "), blocks[1, 2].join(" ")])
126     coll.normalize
127     assert_equal(". #{blocks.join(' ')} 0:6:overlap 3:6:overlap\n",
128                  coll.manifest_text)
129   end
130
131   def test_normalize
132     block = random_block
133     coll = Arv::Collection.new(". #{block} 0:0:f2 0:0:f1\n")
134     coll.normalize
135     assert_equal(". #{block} 0:0:f1 0:0:f2\n", coll.manifest_text)
136   end
137
138   def test_normalization_file_spans_two_whole_blocks(file_specs="0:10:f1",
139                                                      num_blocks=2)
140     blocks = random_blocks(num_blocks, 5)
141     m_text = ". #{blocks.join(' ')} #{file_specs}\n"
142     coll = Arv::Collection.new(m_text.dup)
143     coll.normalize
144     assert_equal(m_text, coll.manifest_text)
145   end
146
147   def test_normalization_file_fits_beginning_block
148     test_normalization_file_spans_two_whole_blocks("0:7:f1")
149   end
150
151   def test_normalization_file_fits_end_block
152     test_normalization_file_spans_two_whole_blocks("3:7:f1")
153   end
154
155   def test_normalization_file_spans_middle
156     test_normalization_file_spans_two_whole_blocks("3:5:f1")
157   end
158
159   def test_normalization_file_spans_three_whole_blocks
160     test_normalization_file_spans_two_whole_blocks("0:15:f1", 3)
161   end
162
163   def test_normalization_file_skips_bytes
164     test_normalization_file_spans_two_whole_blocks("0:3:f1 5:5:f1")
165   end
166
167   def test_normalization_file_inserts_bytes
168     test_normalization_file_spans_two_whole_blocks("0:3:f1 5:3:f1 3:2:f1")
169   end
170
171   def test_normalization_file_duplicates_bytes
172     test_normalization_file_spans_two_whole_blocks("2:3:f1 2:3:f1", 1)
173   end
174
175   def test_normalization_handles_duplicate_locator
176     blocks = random_blocks(2, 5)
177     coll = Arv::Collection.new(". %s %s 1:8:f1 11:8:f1\n" %
178                                [blocks.join(" "), blocks.reverse.join(" ")])
179     coll.normalize
180     assert_equal(". #{blocks.join(' ')} #{blocks[0]} 1:8:f1 6:8:f1\n",
181                  coll.manifest_text)
182   end
183
184   ### .cp_r
185
186   def test_simple_file_copy
187     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
188     coll.cp_r("./simple.txt", "./new")
189     assert_equal(SIMPLEST_MANIFEST.sub(" 0:9:", " 0:9:new 0:9:"),
190                  coll.manifest_text)
191   end
192
193   def test_copy_file_into_other_stream(target="./s1/f2", basename="f2")
194     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
195     coll.cp_r("./f2", target)
196     expected = "%s./s1 %s 0:5:f1 14:4:%s 5:4:f3\n" %
197       [TWO_BY_TWO_MANIFEST_A.first,
198        TWO_BY_TWO_BLOCKS.reverse.join(" "), basename]
199     assert_equal(expected, coll.manifest_text)
200   end
201
202   def test_implicit_copy_file_into_other_stream
203     test_copy_file_into_other_stream("./s1")
204   end
205
206   def test_copy_file_into_other_stream_with_new_name
207     test_copy_file_into_other_stream("./s1/f2a", "f2a")
208   end
209
210   def test_copy_file_over_in_other_stream(target="./s1/f1")
211     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
212     coll.cp_r("./f1", target)
213     expected = "%s./s1 %s 0:5:f1 14:4:f3\n" %
214       [TWO_BY_TWO_MANIFEST_A.first, TWO_BY_TWO_BLOCKS.join(" ")]
215     assert_equal(expected, coll.manifest_text)
216   end
217
218   def test_implicit_copy_file_over_in_other_stream
219     test_copy_file_over_in_other_stream("./s1")
220   end
221
222   def test_simple_stream_copy
223     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
224     coll.cp_r("./s1", "./sNew")
225     new_line = TWO_BY_TWO_MANIFEST_A.last.sub("./s1 ", "./sNew ")
226     assert_equal(TWO_BY_TWO_MANIFEST_S + new_line, coll.manifest_text)
227   end
228
229   def test_copy_stream_into_other_stream(target="./dir2/subdir",
230                                          basename="subdir")
231     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
232     coll.cp_r("./dir1/subdir", target)
233     new_line = MULTILEVEL_MANIFEST.lines[4].sub("./dir1/subdir ",
234                                                 "./dir2/#{basename} ")
235     assert_equal(MULTILEVEL_MANIFEST + new_line, coll.manifest_text)
236   end
237
238   def test_implicit_copy_stream_into_other_stream
239     test_copy_stream_into_other_stream("./dir2")
240   end
241
242   def test_copy_stream_into_other_stream_with_new_name
243     test_copy_stream_into_other_stream("./dir2/newsub", "newsub")
244   end
245
246   def test_copy_stream_over_empty_stream
247     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
248     (1..3).each do |file_num|
249       coll.rm("./dir0/subdir/file#{file_num}")
250     end
251     coll.cp_r("./dir1/subdir", "./dir0")
252     expected = MULTILEVEL_MANIFEST.lines
253     expected[2] = expected[4].sub("./dir1/", "./dir0/")
254     assert_equal(expected.join(""), coll.manifest_text)
255   end
256
257   def test_copy_stream_over_file_raises_ENOTDIR(source="./s1", target="./f2")
258     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
259     assert_raises(Errno::ENOTDIR) do
260       coll.cp_r(source, target)
261     end
262   end
263
264   def test_copy_file_under_file_raises_ENOTDIR
265     test_copy_stream_over_file_raises_ENOTDIR("./f1", "./f2/newfile")
266   end
267
268   def test_copy_stream_over_nonempty_stream_merges_and_overwrites
269     blocks = random_blocks(3, 9)
270     manifest_a =
271       ["./subdir #{blocks[0]} 0:1:s1 1:2:zero\n",
272        "./zdir #{blocks[1]} 0:9:zfile\n",
273        "./zdir/subdir #{blocks[2]} 0:1:s2 1:2:zero\n"]
274     coll = Arv::Collection.new(manifest_a.join(""))
275     coll.cp_r("./subdir", "./zdir")
276     manifest_a[2] = "./zdir/subdir %s %s 0:1:s1 9:1:s2 1:2:zero\n" %
277       [blocks[0], blocks[2]]
278     assert_equal(manifest_a.join(""), coll.manifest_text)
279   end
280
281   def test_copy_stream_into_substream(source="./dir1",
282                                       target="./dir1/subdir/dir1")
283     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
284     coll.cp_r(source, target)
285     expected = MULTILEVEL_MANIFEST.lines.flat_map do |line|
286       [line, line.gsub(/^#{Regexp.escape(source)}([\/ ])/, "#{target}\\1")].uniq
287     end
288     assert_equal(expected.sort.join(""), coll.manifest_text)
289   end
290
291   def test_copy_root
292     test_copy_stream_into_substream(".", "./root")
293   end
294
295   def test_adding_to_root_after_copy
296     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
297     coll.cp_r(".", "./root")
298     src_coll = Arv::Collection.new(COLON_FILENAME_MANIFEST)
299     coll.cp_r("./file:test.txt", ".", src_coll)
300     got_lines = coll.manifest_text.lines
301     assert_equal(2, got_lines.size)
302     assert_match(/^\. \S{33,} \S{33,} 0:9:file:test\.txt 9:9:simple\.txt\n/,
303                  got_lines.first)
304     assert_equal(SIMPLEST_MANIFEST.sub(". ", "./root "), got_lines.last)
305   end
306
307   def test_copy_chaining
308     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
309     coll.cp_r("./simple.txt", "./a").cp_r("./a", "./b")
310     assert_equal(SIMPLEST_MANIFEST.sub(" 0:9:", " 0:9:a 0:9:b 0:9:"),
311                  coll.manifest_text)
312   end
313
314   def prep_two_collections_for_copy(src_stream, dst_stream)
315     blocks = random_blocks(2, 8)
316     src_text = "#{src_stream} #{blocks.first} 0:8:f1\n"
317     dst_text = "#{dst_stream} #{blocks.last} 0:8:f2\n"
318     return [blocks, src_text, dst_text,
319             Arv::Collection.new(src_text.dup),
320             Arv::Collection.new(dst_text.dup)]
321   end
322
323   def test_copy_file_from_other_collection(src_stream=".", dst_stream="./s1")
324     blocks, src_text, dst_text, src_coll, dst_coll =
325       prep_two_collections_for_copy(src_stream, dst_stream)
326     dst_coll.cp_r("#{src_stream}/f1", dst_stream, src_coll)
327     assert_equal("#{dst_stream} #{blocks.join(' ')} 0:8:f1 8:8:f2\n",
328                  dst_coll.manifest_text)
329     assert_equal(src_text, src_coll.manifest_text)
330   end
331
332   def test_copy_file_from_other_collection_to_root
333     test_copy_file_from_other_collection("./s1", ".")
334   end
335
336   def test_copy_stream_from_other_collection
337     blocks, src_text, dst_text, src_coll, dst_coll =
338       prep_two_collections_for_copy("./s2", "./s1")
339     dst_coll.cp_r("./s2", "./s1", src_coll)
340     assert_equal(dst_text + src_text.sub("./s2 ", "./s1/s2 "),
341                  dst_coll.manifest_text)
342     assert_equal(src_text, src_coll.manifest_text)
343   end
344
345   def test_copy_stream_from_other_collection_to_root
346     blocks, src_text, dst_text, src_coll, dst_coll =
347       prep_two_collections_for_copy("./s1", ".")
348     dst_coll.cp_r("./s1", ".", src_coll)
349     assert_equal(dst_text + src_text, dst_coll.manifest_text)
350     assert_equal(src_text, src_coll.manifest_text)
351   end
352
353   def test_copy_stream_contents
354     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
355     coll.cp_r("./dir0/subdir/", "./dir1/subdir")
356     expect_lines = MULTILEVEL_MANIFEST.lines
357     expect_lines[4] = expect_lines[2].sub("./dir0/", "./dir1/")
358     assert_equal(expect_lines.join(""), coll.manifest_text)
359   end
360
361   def test_copy_file_into_new_stream_with_implicit_filename
362     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
363     coll.cp_r("./simple.txt", "./new/")
364     assert_equal(SIMPLEST_MANIFEST + SIMPLEST_MANIFEST.sub(". ", "./new "),
365                  coll.manifest_text)
366   end
367
368   def test_copy_file_into_new_stream_with_explicit_filename
369     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
370     coll.cp_r("./simple.txt", "./new/newfile.txt")
371     new_line = SIMPLEST_MANIFEST.sub(". ", "./new ").sub(":simple", ":newfile")
372     assert_equal(SIMPLEST_MANIFEST + new_line, coll.manifest_text)
373   end
374
375   def test_copy_stream_contents_into_root
376     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
377     coll.cp_r("./s1/", ".")
378     assert_equal(". %s 0:5:f1 14:4:f2 5:4:f3\n%s" %
379                  [TWO_BY_TWO_BLOCKS.reverse.join(" "),
380                   TWO_BY_TWO_MANIFEST_A.last],
381                  coll.manifest_text)
382   end
383
384   def test_copy_root_contents_into_stream
385     # This is especially fun, because we're copying a parent into its child.
386     # Make sure that happens depth-first.
387     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
388     coll.cp_r("./", "./s1")
389     assert_equal("%s./s1 %s 0:5:f1 5:4:f2 14:4:f3\n%s" %
390                  [TWO_BY_TWO_MANIFEST_A.first, TWO_BY_TWO_BLOCKS.join(" "),
391                   TWO_BY_TWO_MANIFEST_A.last.sub("./s1 ", "./s1/s1 ")],
392                  coll.manifest_text)
393   end
394
395   def test_copy_stream_contents_across_collections
396     block = random_block(8)
397     src_coll = Arv::Collection.new("./s1 #{block} 0:8:f1\n")
398     dst_coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
399     dst_coll.cp_r("./s1/", "./s1", src_coll)
400     assert_equal("%s./s1 %s %s 0:8:f1 13:4:f3\n" %
401                  [TWO_BY_TWO_MANIFEST_A.first, block, TWO_BY_TWO_BLOCKS.last],
402                  dst_coll.manifest_text)
403   end
404
405   def test_copy_root_contents_across_collections
406     block = random_block(8)
407     src_coll = Arv::Collection.new(". #{block} 0:8:f1\n")
408     dst_coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
409     dst_coll.cp_r("./", ".", src_coll)
410     assert_equal(". %s %s 0:8:f1 13:4:f2\n%s" %
411                  [block, TWO_BY_TWO_BLOCKS.first, TWO_BY_TWO_MANIFEST_A.last],
412                  dst_coll.manifest_text)
413   end
414
415   def test_copy_root_into_empty_collection
416     block = random_block(8)
417     src_coll = Arv::Collection.new(". #{block} 0:8:f1\n")
418     dst_coll = Arv::Collection.new()
419     dst_coll.cp_r("./", ".", src_coll)
420     assert_equal(". %s 0:8:f1\n" %
421                  [block],
422                  dst_coll.manifest_text)
423   end
424
425   def test_copy_with_repeated_blocks
426     blocks = abcde_blocks
427     src_coll = Arv::Collection.new(". #{blocks[0]} #{blocks[1]} #{blocks[2]} #{blocks[0]} #{blocks[1]} #{blocks[2]} #{blocks[3]} #{blocks[4]} 27:27:f1\n")
428     dst_coll = Arv::Collection.new()
429     dst_coll.cp_r("f1", "./", src_coll)
430     assert_equal(". #{blocks[0]} #{blocks[1]} #{blocks[2]} 0:27:f1\n", dst_coll.manifest_text, "mangled by cp_r")
431   end
432
433   def test_copy_with_repeated_split_blocks
434     blocks = abcde_blocks
435     src_coll = Arv::Collection.new(". #{blocks[0]} #{blocks[1]} #{blocks[2]} #{blocks[0]} #{blocks[1]} #{blocks[2]} #{blocks[3]} #{blocks[4]} 20:27:f1\n")
436     dst_coll = Arv::Collection.new()
437     src_coll.normalize
438     assert_equal(". #{blocks[2]} #{blocks[0]} #{blocks[1]} #{blocks[2]} 2:27:f1\n", src_coll.manifest_text, "mangled by normalize()")
439     dst_coll.cp_r("f1", "./", src_coll)
440     assert_equal(". #{blocks[2]} #{blocks[0]} #{blocks[1]} #{blocks[2]} 2:27:f1\n", dst_coll.manifest_text, "mangled by cp_r")
441   end
442
443   def test_copy_empty_source_path_raises_ArgumentError(src="", dst="./s1")
444     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
445     assert_raises(ArgumentError) do
446       coll.cp_r(src, dst)
447     end
448   end
449
450   def test_copy_empty_destination_path_raises_ArgumentError
451     test_copy_empty_source_path_raises_ArgumentError(".", "")
452   end
453
454   ### .each_file_path
455
456   def test_each_file_path
457     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
458     if block_given?
459       result = yield(coll)
460     else
461       result = []
462       coll.each_file_path { |path| result << path }
463     end
464     assert_equal(["./f1", "./f2", "./s1/f1", "./s1/f3"], result.sort)
465   end
466
467   def test_each_file_path_without_block
468     test_each_file_path { |coll| coll.each_file_path.to_a }
469   end
470
471   def test_each_file_path_empty_collection
472     assert_empty(Arv::Collection.new.each_file_path.to_a)
473   end
474
475   def test_each_file_path_after_collection_emptied
476     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
477     coll.rm("simple.txt")
478     assert_empty(coll.each_file_path.to_a)
479   end
480
481   def test_each_file_path_deduplicates_manifest_listings
482     coll = Arv::Collection.new(MULTIBLOCK_FILE_MANIFEST)
483     assert_equal(["./repfile", "./s1/repfile", "./s1/uniqfile",
484                   "./uniqfile", "./uniqfile2"],
485                  coll.each_file_path.to_a.sort)
486   end
487
488   ### .exist?
489
490   def test_exist(test_method=:assert, path="f2")
491     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
492     send(test_method, coll.exist?(path))
493   end
494
495   def test_file_not_exist
496     test_exist(:refute, "f3")
497   end
498
499   def test_stream_exist
500     test_exist(:assert, "s1")
501   end
502
503   def test_file_inside_stream_exist
504     test_exist(:assert, "s1/f1")
505   end
506
507   def test_path_inside_stream_not_exist
508     test_exist(:refute, "s1/f2")
509   end
510
511   def test_path_under_file_not_exist
512     test_exist(:refute, "f2/nonexistent")
513   end
514
515   def test_deep_substreams_not_exist
516     test_exist(:refute, "a/b/c/d/e/f/g")
517   end
518
519   ### .rename
520
521   def test_simple_file_rename
522     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
523     coll.rename("./simple.txt", "./new")
524     assert_equal(SIMPLEST_MANIFEST.sub(":simple.txt", ":new"),
525                  coll.manifest_text)
526   end
527
528   def test_rename_file_into_other_stream(target="./s1/f2", basename="f2")
529     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
530     coll.rename("./f2", target)
531     expected = ". %s 0:5:f1\n./s1 %s 0:5:f1 14:4:%s 5:4:f3\n" %
532       [TWO_BY_TWO_BLOCKS.first,
533        TWO_BY_TWO_BLOCKS.reverse.join(" "), basename]
534     assert_equal(expected, coll.manifest_text)
535   end
536
537   def test_implicit_rename_file_into_other_stream
538     test_rename_file_into_other_stream("./s1")
539   end
540
541   def test_rename_file_into_other_stream_with_new_name
542     test_rename_file_into_other_stream("./s1/f2a", "f2a")
543   end
544
545   def test_rename_file_over_in_other_stream(target="./s1/f1")
546     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
547     coll.rename("./f1", target)
548     expected = ". %s 5:4:f2\n./s1 %s 0:5:f1 14:4:f3\n" %
549       [TWO_BY_TWO_BLOCKS.first, TWO_BY_TWO_BLOCKS.join(" ")]
550     assert_equal(expected, coll.manifest_text)
551   end
552
553   def test_implicit_rename_file_over_in_other_stream
554     test_rename_file_over_in_other_stream("./s1")
555   end
556
557   def test_simple_stream_rename
558     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
559     coll.rename("./s1", "./newS")
560     assert_equal(TWO_BY_TWO_MANIFEST_S.sub("\n./s1 ", "\n./newS "),
561                  coll.manifest_text)
562   end
563
564   def test_rename_stream_into_other_stream(target="./dir2/subdir",
565                                            basename="subdir")
566     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
567     coll.rename("./dir1/subdir", target)
568     expected = MULTILEVEL_MANIFEST.lines
569     replaced_line = expected.delete_at(4)
570     expected << replaced_line.sub("./dir1/subdir ", "./dir2/#{basename} ")
571     assert_equal(expected.join(""), coll.manifest_text)
572   end
573
574   def test_implicit_rename_stream_into_other_stream
575     test_rename_stream_into_other_stream("./dir2")
576   end
577
578   def test_rename_stream_into_other_stream_with_new_name
579     test_rename_stream_into_other_stream("./dir2/newsub", "newsub")
580   end
581
582   def test_rename_stream_over_empty_stream
583     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
584     (1..3).each do |file_num|
585       coll.rm("./dir0/subdir/file#{file_num}")
586     end
587     coll.rename("./dir1/subdir", "./dir0")
588     expected = MULTILEVEL_MANIFEST.lines
589     expected[2] = expected.delete_at(4).sub("./dir1/", "./dir0/")
590     assert_equal(expected.sort.join(""), coll.manifest_text)
591   end
592
593   def test_rename_stream_over_file_raises_ENOTDIR
594     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
595     assert_raises(Errno::ENOTDIR) do
596       coll.rename("./s1", "./f2")
597     end
598   end
599
600   def test_rename_stream_over_nonempty_stream_raises_ENOTEMPTY
601     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
602     assert_raises(Errno::ENOTEMPTY) do
603       coll.rename("./dir1/subdir", "./dir0")
604     end
605   end
606
607   def test_rename_stream_into_substream(source="./dir1",
608                                         target="./dir1/subdir/dir1")
609     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
610     coll.rename(source, target)
611     assert_equal(MULTILEVEL_MANIFEST.gsub(/^#{Regexp.escape(source)}([\/ ])/m,
612                                           "#{target}\\1"),
613                  coll.manifest_text)
614   end
615
616   def test_rename_root
617     test_rename_stream_into_substream(".", "./root")
618   end
619
620   def test_adding_to_root_after_rename
621     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
622     coll.rename(".", "./root")
623     src_coll = Arv::Collection.new(SIMPLEST_MANIFEST)
624     coll.cp_r("./simple.txt", ".", src_coll)
625     assert_equal(SIMPLEST_MANIFEST + SIMPLEST_MANIFEST.sub(". ", "./root "),
626                  coll.manifest_text)
627   end
628
629   def test_rename_chaining
630     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
631     coll.rename("./simple.txt", "./x").rename("./x", "./simple.txt")
632     assert_equal(SIMPLEST_MANIFEST, coll.manifest_text)
633   end
634
635   ### .rm
636
637   def test_simple_remove
638     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S.dup)
639     coll.rm("./f2")
640     assert_equal(TWO_BY_TWO_MANIFEST_S.sub(" 5:4:f2", ""), coll.manifest_text)
641   end
642
643   def empty_stream_and_assert(expect_index=0)
644     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
645     yield coll
646     assert_equal(TWO_BY_TWO_MANIFEST_A[expect_index], coll.manifest_text)
647   end
648
649   def test_remove_all_files_in_substream
650     empty_stream_and_assert do |coll|
651       coll.rm("./s1/f1")
652       coll.rm("./s1/f3")
653     end
654   end
655
656   def test_remove_all_files_in_root_stream
657     empty_stream_and_assert(1) do |coll|
658       coll.rm("./f1")
659       coll.rm("./f2")
660     end
661   end
662
663   def test_chaining_removes
664     empty_stream_and_assert do |coll|
665       coll.rm("./s1/f1").rm("./s1/f3")
666     end
667   end
668
669   def test_remove_last_file
670     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
671     coll.rm("./simple.txt")
672     assert_equal("", coll.manifest_text)
673   end
674
675   def test_remove_nonexistent_file_raises_ENOENT(path="./NoSuchFile",
676                                                  method=:rm)
677     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
678     assert_raises(Errno::ENOENT) do
679       coll.send(method, path)
680     end
681   end
682
683   def test_remove_from_nonexistent_stream_raises_ENOENT
684     test_remove_nonexistent_file_raises_ENOENT("./NoSuchStream/simple.txt")
685   end
686
687   def test_remove_stream_raises_EISDIR(path="./s1")
688     coll = Arv::Collection.new(TWO_BY_TWO_MANIFEST_S)
689     assert_raises(Errno::EISDIR) do
690       coll.rm(path)
691     end
692   end
693
694   def test_remove_root_raises_EISDIR
695     test_remove_stream_raises_EISDIR(".")
696   end
697
698   def test_remove_empty_string_raises_ArgumentError
699     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
700     assert_raises(ArgumentError) do
701       coll.rm("")
702     end
703   end
704
705   ### rm_r
706
707   def test_recursive_remove
708     empty_stream_and_assert do |coll|
709       coll.rm_r("./s1")
710     end
711   end
712
713   def test_recursive_remove_on_files
714     empty_stream_and_assert do |coll|
715       coll.rm_r("./s1/f1")
716       coll.rm_r("./s1/f3")
717     end
718   end
719
720   def test_recursive_remove_root
721     coll = Arv::Collection.new(MULTILEVEL_MANIFEST)
722     coll.rm_r(".")
723     assert_equal("", coll.manifest_text)
724   end
725
726   def test_rm_r_nonexistent_file_raises_ENOENT(path="./NoSuchFile")
727     test_remove_nonexistent_file_raises_ENOENT("./NoSuchFile", :rm_r)
728   end
729
730   def test_rm_r_from_nonexistent_stream_raises_ENOENT
731     test_remove_nonexistent_file_raises_ENOENT("./NoSuchStream/file", :rm_r)
732   end
733
734   def test_rm_r_empty_string_raises_ArgumentError
735     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
736     assert_raises(ArgumentError) do
737       coll.rm_r("")
738     end
739   end
740
741   ### .modified?
742
743   def test_new_collection_unmodified(*args)
744     coll = Arv::Collection.new(*args)
745     yield coll if block_given?
746     refute(coll.modified?)
747   end
748
749   def test_collection_unmodified_after_instantiation
750     test_new_collection_unmodified(SIMPLEST_MANIFEST)
751   end
752
753   def test_collection_unmodified_after_mark
754     test_new_collection_unmodified(SIMPLEST_MANIFEST) do |coll|
755       coll.cp_r("./simple.txt", "./copy")
756       coll.unmodified
757     end
758   end
759
760   def check_collection_modified
761     coll = Arv::Collection.new(SIMPLEST_MANIFEST)
762     yield coll
763     assert(coll.modified?)
764   end
765
766   def test_collection_modified_after_copy
767     check_collection_modified do |coll|
768       coll.cp_r("./simple.txt", "./copy")
769     end
770   end
771
772   def test_collection_modified_after_remove
773     check_collection_modified do |coll|
774       coll.rm("./simple.txt")
775     end
776   end
777
778   def test_collection_modified_after_rename
779     check_collection_modified do |coll|
780       coll.rename("./simple.txt", "./newname")
781     end
782   end
783 end