2 # -*- coding: utf-8 -*-
14 import arvados.commands.put as arv_put
15 from arvados_testutil import ArvadosBaseTestCase, ArvadosKeepLocalStoreTestCase
17 class ArvadosPutResumeCacheTest(ArvadosBaseTestCase):
21 ['/dev/null', '--filename', 'empty'],
23 ['/tmp', '--max-manifest-depth', '0'],
24 ['/tmp', '--max-manifest-depth', '1']
28 super(ArvadosPutResumeCacheTest, self).tearDown()
30 self.last_cache.destroy()
31 except AttributeError:
34 def cache_path_from_arglist(self, arglist):
35 return arv_put.ResumeCache.make_path(arv_put.parse_arguments(arglist))
37 def test_cache_names_stable(self):
38 for argset in self.CACHE_ARGSET:
39 self.assertEquals(self.cache_path_from_arglist(argset),
40 self.cache_path_from_arglist(argset),
41 "cache name changed for {}".format(argset))
43 def test_cache_names_unique(self):
45 for argset in self.CACHE_ARGSET:
46 path = self.cache_path_from_arglist(argset)
47 self.assertNotIn(path, results)
50 def test_cache_names_simple(self):
51 # The goal here is to make sure the filename doesn't use characters
52 # reserved by the filesystem. Feel free to adjust this regexp as
53 # long as it still does that.
54 bad_chars = re.compile(r'[^-\.\w]')
55 for argset in self.CACHE_ARGSET:
56 path = self.cache_path_from_arglist(argset)
57 self.assertFalse(bad_chars.search(os.path.basename(path)),
58 "path too exotic: {}".format(path))
60 def test_cache_names_ignore_argument_order(self):
62 self.cache_path_from_arglist(['a', 'b', 'c']),
63 self.cache_path_from_arglist(['c', 'a', 'b']))
65 self.cache_path_from_arglist(['-', '--filename', 'stdin']),
66 self.cache_path_from_arglist(['--filename', 'stdin', '-']))
68 def test_cache_names_ignore_irrelevant_arguments(self):
69 # Workaround: parse_arguments bails on --filename with a directory.
70 path1 = self.cache_path_from_arglist(['/tmp'])
71 args = arv_put.parse_arguments(['/tmp'])
73 path2 = arv_put.ResumeCache.make_path(args)
74 self.assertEquals(path1, path2,
75 "cache path considered --filename for directory")
77 self.cache_path_from_arglist(['-']),
78 self.cache_path_from_arglist(['-', '--max-manifest-depth', '1']),
79 "cache path considered --max-manifest-depth for file")
81 def test_cache_names_treat_negative_manifest_depths_identically(self):
82 base_args = ['/tmp', '--max-manifest-depth']
84 self.cache_path_from_arglist(base_args + ['-1']),
85 self.cache_path_from_arglist(base_args + ['-2']))
87 def test_cache_names_treat_stdin_consistently(self):
89 self.cache_path_from_arglist(['-', '--filename', 'test']),
90 self.cache_path_from_arglist(['/dev/stdin', '--filename', 'test']))
92 def test_cache_names_identical_for_synonymous_names(self):
94 self.cache_path_from_arglist(['.']),
95 self.cache_path_from_arglist([os.path.realpath('.')]))
96 testdir = self.make_tmpdir()
97 looplink = os.path.join(testdir, 'loop')
98 os.symlink(testdir, looplink)
100 self.cache_path_from_arglist([testdir]),
101 self.cache_path_from_arglist([looplink]))
103 def test_cache_names_different_by_api_host(self):
104 config = arvados.config.settings()
105 orig_host = config.get('ARVADOS_API_HOST')
107 name1 = self.cache_path_from_arglist(['.'])
108 config['ARVADOS_API_HOST'] = 'x' + (orig_host or 'localhost')
109 self.assertNotEqual(name1, self.cache_path_from_arglist(['.']))
111 if orig_host is None:
112 del config['ARVADOS_API_HOST']
114 config['ARVADOS_API_HOST'] = orig_host
116 def test_basic_cache_storage(self):
117 thing = ['test', 'list']
118 with tempfile.NamedTemporaryFile() as cachefile:
119 self.last_cache = arv_put.ResumeCache(cachefile.name)
120 self.last_cache.save(thing)
121 self.assertEquals(thing, self.last_cache.load())
123 def test_empty_cache(self):
124 with tempfile.NamedTemporaryFile() as cachefile:
125 cache = arv_put.ResumeCache(cachefile.name)
126 self.assertRaises(ValueError, cache.load)
128 def test_cache_persistent(self):
129 thing = ['test', 'list']
130 path = os.path.join(self.make_tmpdir(), 'cache')
131 cache = arv_put.ResumeCache(path)
134 self.last_cache = arv_put.ResumeCache(path)
135 self.assertEquals(thing, self.last_cache.load())
137 def test_multiple_cache_writes(self):
138 thing = ['short', 'list']
139 with tempfile.NamedTemporaryFile() as cachefile:
140 self.last_cache = arv_put.ResumeCache(cachefile.name)
141 # Start writing an object longer than the one we test, to make
142 # sure the cache file gets truncated.
143 self.last_cache.save(['long', 'long', 'list'])
144 self.last_cache.save(thing)
145 self.assertEquals(thing, self.last_cache.load())
147 def test_cache_is_locked(self):
148 with tempfile.NamedTemporaryFile() as cachefile:
149 cache = arv_put.ResumeCache(cachefile.name)
150 self.assertRaises(arv_put.ResumeCacheConflict,
151 arv_put.ResumeCache, cachefile.name)
153 def test_cache_stays_locked(self):
154 with tempfile.NamedTemporaryFile() as cachefile:
155 self.last_cache = arv_put.ResumeCache(cachefile.name)
156 path = cachefile.name
157 self.last_cache.save('test')
158 self.assertRaises(arv_put.ResumeCacheConflict,
159 arv_put.ResumeCache, path)
161 def test_destroy_cache(self):
162 cachefile = tempfile.NamedTemporaryFile(delete=False)
164 cache = arv_put.ResumeCache(cachefile.name)
168 arv_put.ResumeCache(cachefile.name)
169 except arv_put.ResumeCacheConflict:
170 self.fail("could not load cache after destroying it")
171 self.assertRaises(ValueError, cache.load)
173 if os.path.exists(cachefile.name):
174 os.unlink(cachefile.name)
176 def test_restart_cache(self):
177 path = os.path.join(self.make_tmpdir(), 'cache')
178 cache = arv_put.ResumeCache(path)
181 self.assertRaises(ValueError, cache.load)
182 self.assertRaises(arv_put.ResumeCacheConflict,
183 arv_put.ResumeCache, path)
186 class ArvadosPutCollectionWriterTest(ArvadosKeepLocalStoreTestCase):
188 super(ArvadosPutCollectionWriterTest, self).setUp()
189 with tempfile.NamedTemporaryFile(delete=False) as cachefile:
190 self.cache = arv_put.ResumeCache(cachefile.name)
191 self.cache_filename = cachefile.name
194 super(ArvadosPutCollectionWriterTest, self).tearDown()
195 if os.path.exists(self.cache_filename):
199 def test_writer_caches(self):
200 cwriter = arv_put.ArvPutCollectionWriter(self.cache)
201 cwriter.write_file('/dev/null')
202 cwriter.cache_state()
203 self.assertTrue(self.cache.load())
204 self.assertEquals(". 0:0:null\n", cwriter.manifest_text())
206 def test_writer_works_without_cache(self):
207 cwriter = arv_put.ArvPutCollectionWriter()
208 cwriter.write_file('/dev/null')
209 self.assertEquals(". 0:0:null\n", cwriter.manifest_text())
211 def test_writer_resumes_from_cache(self):
212 cwriter = arv_put.ArvPutCollectionWriter(self.cache)
213 with self.make_test_file() as testfile:
214 cwriter.write_file(testfile.name, 'test')
215 cwriter.cache_state()
216 new_writer = arv_put.ArvPutCollectionWriter.from_cache(
219 ". 098f6bcd4621d373cade4e832627b4f6+4 0:4:test\n",
220 new_writer.manifest_text())
222 def test_new_writer_from_stale_cache(self):
223 cwriter = arv_put.ArvPutCollectionWriter(self.cache)
224 with self.make_test_file() as testfile:
225 cwriter.write_file(testfile.name, 'test')
226 new_writer = arv_put.ArvPutCollectionWriter.from_cache(self.cache)
227 new_writer.write_file('/dev/null')
228 self.assertEquals(". 0:0:null\n", new_writer.manifest_text())
230 def test_new_writer_from_empty_cache(self):
231 cwriter = arv_put.ArvPutCollectionWriter.from_cache(self.cache)
232 cwriter.write_file('/dev/null')
233 self.assertEquals(". 0:0:null\n", cwriter.manifest_text())
235 def test_writer_resumable_after_arbitrary_bytes(self):
236 cwriter = arv_put.ArvPutCollectionWriter(self.cache)
237 # These bytes are intentionally not valid UTF-8.
238 with self.make_test_file('\x00\x07\xe2') as testfile:
239 cwriter.write_file(testfile.name, 'test')
240 cwriter.cache_state()
241 new_writer = arv_put.ArvPutCollectionWriter.from_cache(
243 self.assertEquals(cwriter.manifest_text(), new_writer.manifest_text())
245 def make_progress_tester(self):
247 def record_func(written, expected):
248 progression.append((written, expected))
249 return progression, record_func
251 def test_progress_reporting(self):
252 for expect_count in (None, 8):
253 progression, reporter = self.make_progress_tester()
254 cwriter = arv_put.ArvPutCollectionWriter(
255 reporter=reporter, bytes_expected=expect_count)
256 with self.make_test_file() as testfile:
257 cwriter.write_file(testfile.name, 'test')
258 cwriter.finish_current_stream()
259 self.assertIn((4, expect_count), progression)
261 def test_resume_progress(self):
262 cwriter = arv_put.ArvPutCollectionWriter(self.cache, bytes_expected=4)
263 with self.make_test_file() as testfile:
264 # Set up a writer with some flushed bytes.
265 cwriter.write_file(testfile.name, 'test')
266 cwriter.finish_current_stream()
267 cwriter.cache_state()
268 new_writer = arv_put.ArvPutCollectionWriter.from_cache(self.cache)
269 self.assertEqual(new_writer.bytes_written, 4)
272 class ArvadosExpectedBytesTest(ArvadosBaseTestCase):
273 TEST_SIZE = os.path.getsize(__file__)
275 def test_expected_bytes_for_file(self):
276 self.assertEquals(self.TEST_SIZE,
277 arv_put.expected_bytes_for([__file__]))
279 def test_expected_bytes_for_tree(self):
280 tree = self.make_tmpdir()
281 shutil.copyfile(__file__, os.path.join(tree, 'one'))
282 shutil.copyfile(__file__, os.path.join(tree, 'two'))
283 self.assertEquals(self.TEST_SIZE * 2,
284 arv_put.expected_bytes_for([tree]))
285 self.assertEquals(self.TEST_SIZE * 3,
286 arv_put.expected_bytes_for([tree, __file__]))
288 def test_expected_bytes_for_device(self):
289 self.assertIsNone(arv_put.expected_bytes_for(['/dev/null']))
290 self.assertIsNone(arv_put.expected_bytes_for([__file__, '/dev/null']))
293 class ArvadosPutReportTest(ArvadosBaseTestCase):
294 def test_machine_progress(self):
295 for count, total in [(0, 1), (0, None), (1, None), (235, 9283)]:
296 expect = ": {} written {} total\n".format(
297 count, -1 if (total is None) else total)
299 arv_put.machine_progress(count, total).endswith(expect))
301 def test_known_human_progress(self):
302 for count, total in [(0, 1), (2, 4), (45, 60)]:
303 expect = '{:.1%}'.format(float(count) / total)
304 actual = arv_put.human_progress(count, total)
305 self.assertTrue(actual.startswith('\r'))
306 self.assertIn(expect, actual)
308 def test_unknown_human_progress(self):
309 for count in [1, 20, 300, 4000, 50000]:
310 self.assertTrue(re.search(r'\b{}\b'.format(count),
311 arv_put.human_progress(count, None)))
314 class ArvadosPutTest(ArvadosKeepLocalStoreTestCase):
315 def test_simple_file_put(self):
316 with self.make_test_file() as testfile:
318 arv_put.main(['--stream', '--no-progress', path])
320 os.path.exists(os.path.join(os.environ['KEEP_LOCAL_STORE'],
321 '098f6bcd4621d373cade4e832627b4f6')),
322 "did not find file stream in Keep store")
324 def test_short_put_from_stdin(self):
325 # Have to run this separately since arv-put can't read from the
327 # arv-put usually can't stat(os.path.realpath('/dev/stdin')) in this
328 # case, because the /proc entry is already gone by the time it tries.
329 pipe = subprocess.Popen(
330 [sys.executable, arv_put.__file__, '--stream'],
331 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
332 stderr=open('/dev/null', 'w'))
333 pipe.stdin.write('stdin test\n')
335 deadline = time.time() + 5
336 while (pipe.poll() is None) and (time.time() < deadline):
338 if pipe.returncode is None:
340 self.fail("arv-put did not PUT from stdin within 5 seconds")
341 self.assertEquals(pipe.returncode, 0)
342 self.assertIn('4a9c8b735dce4b5fa3acf221a0b13628+11', pipe.stdout.read())
345 if __name__ == '__main__':