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