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