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