##// END OF EJS Templates
Merge pull request #8126 from quantopian/test-modified-date...
Min RK -
r20862:cea0c610 merge
parent child Browse files
Show More
@@ -1,497 +1,524
1 1 # coding: utf-8
2 2 """Tests for the notebook manager."""
3 3 from __future__ import print_function
4 4
5 5 import os
6 6 import sys
7 7 import time
8 8 from contextlib import contextmanager
9 9
10 10 from nose import SkipTest
11 11 from tornado.web import HTTPError
12 12 from unittest import TestCase
13 13 from tempfile import NamedTemporaryFile
14 14
15 15 from IPython.nbformat import v4 as nbformat
16 16
17 17 from IPython.utils.tempdir import TemporaryDirectory
18 18 from IPython.utils.traitlets import TraitError
19 19 from IPython.testing import decorators as dec
20 20
21 21 from ..filemanager import FileContentsManager
22 22
23 23
24 24 def _make_dir(contents_manager, api_path):
25 25 """
26 26 Make a directory.
27 27 """
28 28 os_path = contents_manager._get_os_path(api_path)
29 29 try:
30 30 os.makedirs(os_path)
31 31 except OSError:
32 32 print("Directory already exists: %r" % os_path)
33 33
34 34
35 35 class TestFileContentsManager(TestCase):
36 36
37 37 @contextmanager
38 38 def assertRaisesHTTPError(self, status, msg=None):
39 39 msg = msg or "Should have raised HTTPError(%i)" % status
40 40 try:
41 41 yield
42 42 except HTTPError as e:
43 43 self.assertEqual(e.status_code, status)
44 44 else:
45 45 self.fail(msg)
46 46
47 47 def symlink(self, contents_manager, src, dst):
48 48 """Make a symlink to src from dst
49 49
50 50 src and dst are api_paths
51 51 """
52 52 src_os_path = contents_manager._get_os_path(src)
53 53 dst_os_path = contents_manager._get_os_path(dst)
54 54 print(src_os_path, dst_os_path, os.path.isfile(src_os_path))
55 55 os.symlink(src_os_path, dst_os_path)
56 56
57 57 def test_root_dir(self):
58 58 with TemporaryDirectory() as td:
59 59 fm = FileContentsManager(root_dir=td)
60 60 self.assertEqual(fm.root_dir, td)
61 61
62 62 def test_missing_root_dir(self):
63 63 with TemporaryDirectory() as td:
64 64 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
65 65 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
66 66
67 67 def test_invalid_root_dir(self):
68 68 with NamedTemporaryFile() as tf:
69 69 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
70 70
71 71 def test_get_os_path(self):
72 72 # full filesystem path should be returned with correct operating system
73 73 # separators.
74 74 with TemporaryDirectory() as td:
75 75 root = td
76 76 fm = FileContentsManager(root_dir=root)
77 77 path = fm._get_os_path('/path/to/notebook/test.ipynb')
78 78 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
79 79 fs_path = os.path.join(fm.root_dir, *rel_path_list)
80 80 self.assertEqual(path, fs_path)
81 81
82 82 fm = FileContentsManager(root_dir=root)
83 83 path = fm._get_os_path('test.ipynb')
84 84 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
85 85 self.assertEqual(path, fs_path)
86 86
87 87 fm = FileContentsManager(root_dir=root)
88 88 path = fm._get_os_path('////test.ipynb')
89 89 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
90 90 self.assertEqual(path, fs_path)
91 91
92 92 def test_checkpoint_subdir(self):
93 93 subd = u'sub ∂ir'
94 94 cp_name = 'test-cp.ipynb'
95 95 with TemporaryDirectory() as td:
96 96 root = td
97 97 os.mkdir(os.path.join(td, subd))
98 98 fm = FileContentsManager(root_dir=root)
99 99 cpm = fm.checkpoints
100 100 cp_dir = cpm.checkpoint_path(
101 101 'cp', 'test.ipynb'
102 102 )
103 103 cp_subdir = cpm.checkpoint_path(
104 104 'cp', '/%s/test.ipynb' % subd
105 105 )
106 106 self.assertNotEqual(cp_dir, cp_subdir)
107 107 self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name))
108 108 self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name))
109 109
110 110 @dec.skip_win32
111 111 def test_bad_symlink(self):
112 112 with TemporaryDirectory() as td:
113 113 cm = FileContentsManager(root_dir=td)
114 114 path = 'test bad symlink'
115 115 _make_dir(cm, path)
116 116
117 117 file_model = cm.new_untitled(path=path, ext='.txt')
118 118
119 119 # create a broken symlink
120 120 self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink'))
121 121 model = cm.get(path)
122 122 self.assertEqual(model['content'], [file_model])
123 123
124 124 @dec.skip_win32
125 125 def test_good_symlink(self):
126 126 with TemporaryDirectory() as td:
127 127 cm = FileContentsManager(root_dir=td)
128 128 parent = 'test good symlink'
129 129 name = 'good symlink'
130 130 path = '{0}/{1}'.format(parent, name)
131 131 _make_dir(cm, parent)
132 132
133 133 file_model = cm.new(path=parent + '/zfoo.txt')
134 134
135 135 # create a good symlink
136 136 self.symlink(cm, file_model['path'], path)
137 137 symlink_model = cm.get(path, content=False)
138 138 dir_model = cm.get(parent)
139 139 self.assertEqual(
140 140 sorted(dir_model['content'], key=lambda x: x['name']),
141 141 [symlink_model, file_model],
142 142 )
143 143
144 144 def test_403(self):
145 145 if hasattr(os, 'getuid'):
146 146 if os.getuid() == 0:
147 147 raise SkipTest("Can't test permissions as root")
148 148 if sys.platform.startswith('win'):
149 149 raise SkipTest("Can't test permissions on Windows")
150 150
151 151 with TemporaryDirectory() as td:
152 152 cm = FileContentsManager(root_dir=td)
153 153 model = cm.new_untitled(type='file')
154 154 os_path = cm._get_os_path(model['path'])
155 155
156 156 os.chmod(os_path, 0o400)
157 157 try:
158 158 with cm.open(os_path, 'w') as f:
159 159 f.write(u"don't care")
160 160 except HTTPError as e:
161 161 self.assertEqual(e.status_code, 403)
162 162 else:
163 163 self.fail("Should have raised HTTPError(403)")
164 164
165 165 def test_escape_root(self):
166 166 with TemporaryDirectory() as td:
167 167 cm = FileContentsManager(root_dir=td)
168 168 # make foo, bar next to root
169 169 with open(os.path.join(cm.root_dir, '..', 'foo'), 'w') as f:
170 170 f.write('foo')
171 171 with open(os.path.join(cm.root_dir, '..', 'bar'), 'w') as f:
172 172 f.write('bar')
173 173
174 174 with self.assertRaisesHTTPError(404):
175 175 cm.get('..')
176 176 with self.assertRaisesHTTPError(404):
177 177 cm.get('foo/../../../bar')
178 178 with self.assertRaisesHTTPError(404):
179 179 cm.delete('../foo')
180 180 with self.assertRaisesHTTPError(404):
181 181 cm.rename('../foo', '../bar')
182 182 with self.assertRaisesHTTPError(404):
183 183 cm.save(model={
184 184 'type': 'file',
185 185 'content': u'',
186 186 'format': 'text',
187 187 }, path='../foo')
188 188
189 189
190 190 class TestContentsManager(TestCase):
191 191
192 192 def setUp(self):
193 193 self._temp_dir = TemporaryDirectory()
194 194 self.td = self._temp_dir.name
195 195 self.contents_manager = FileContentsManager(
196 196 root_dir=self.td,
197 197 )
198 198
199 199 def tearDown(self):
200 200 self._temp_dir.cleanup()
201 201
202 202 def make_dir(self, api_path):
203 203 """make a subdirectory at api_path
204 204
205 205 override in subclasses if contents are not on the filesystem.
206 206 """
207 207 _make_dir(self.contents_manager, api_path)
208 208
209 209 def add_code_cell(self, nb):
210 210 output = nbformat.new_output("display_data", {'application/javascript': "alert('hi');"})
211 211 cell = nbformat.new_code_cell("print('hi')", outputs=[output])
212 212 nb.cells.append(cell)
213 213
214 214 def new_notebook(self):
215 215 cm = self.contents_manager
216 216 model = cm.new_untitled(type='notebook')
217 217 name = model['name']
218 218 path = model['path']
219 219
220 220 full_model = cm.get(path)
221 221 nb = full_model['content']
222 222 nb['metadata']['counter'] = int(1e6 * time.time())
223 223 self.add_code_cell(nb)
224 224
225 225 cm.save(full_model, path)
226 226 return nb, name, path
227 227
228 228 def test_new_untitled(self):
229 229 cm = self.contents_manager
230 230 # Test in root directory
231 231 model = cm.new_untitled(type='notebook')
232 232 assert isinstance(model, dict)
233 233 self.assertIn('name', model)
234 234 self.assertIn('path', model)
235 235 self.assertIn('type', model)
236 236 self.assertEqual(model['type'], 'notebook')
237 237 self.assertEqual(model['name'], 'Untitled.ipynb')
238 238 self.assertEqual(model['path'], 'Untitled.ipynb')
239 239
240 240 # Test in sub-directory
241 241 model = cm.new_untitled(type='directory')
242 242 assert isinstance(model, dict)
243 243 self.assertIn('name', model)
244 244 self.assertIn('path', model)
245 245 self.assertIn('type', model)
246 246 self.assertEqual(model['type'], 'directory')
247 247 self.assertEqual(model['name'], 'Untitled Folder')
248 248 self.assertEqual(model['path'], 'Untitled Folder')
249 249 sub_dir = model['path']
250 250
251 251 model = cm.new_untitled(path=sub_dir)
252 252 assert isinstance(model, dict)
253 253 self.assertIn('name', model)
254 254 self.assertIn('path', model)
255 255 self.assertIn('type', model)
256 256 self.assertEqual(model['type'], 'file')
257 257 self.assertEqual(model['name'], 'untitled')
258 258 self.assertEqual(model['path'], '%s/untitled' % sub_dir)
259 259
260 def test_modified_date(self):
261
262 cm = self.contents_manager
263
264 # Create a new notebook.
265 nb, name, path = self.new_notebook()
266 model = cm.get(path)
267
268 # Add a cell and save.
269 self.add_code_cell(model['content'])
270 cm.save(model, path)
271
272 # Reload notebook and verify that last_modified incremented.
273 saved = cm.get(path)
274 self.assertGreater(saved['last_modified'], model['last_modified'])
275
276 # Move the notebook and verify that last_modified stayed the same.
277 # (The frontend fires a warning if last_modified increases on the
278 # renamed file.)
279 new_path = 'renamed.ipynb'
280 cm.rename(path, new_path)
281 renamed = cm.get(new_path)
282 self.assertGreaterEqual(
283 renamed['last_modified'],
284 saved['last_modified'],
285 )
286
260 287 def test_get(self):
261 288 cm = self.contents_manager
262 289 # Create a notebook
263 290 model = cm.new_untitled(type='notebook')
264 291 name = model['name']
265 292 path = model['path']
266 293
267 294 # Check that we 'get' on the notebook we just created
268 295 model2 = cm.get(path)
269 296 assert isinstance(model2, dict)
270 297 self.assertIn('name', model2)
271 298 self.assertIn('path', model2)
272 299 self.assertEqual(model['name'], name)
273 300 self.assertEqual(model['path'], path)
274 301
275 302 nb_as_file = cm.get(path, content=True, type='file')
276 303 self.assertEqual(nb_as_file['path'], path)
277 304 self.assertEqual(nb_as_file['type'], 'file')
278 305 self.assertEqual(nb_as_file['format'], 'text')
279 306 self.assertNotIsInstance(nb_as_file['content'], dict)
280 307
281 308 nb_as_bin_file = cm.get(path, content=True, type='file', format='base64')
282 309 self.assertEqual(nb_as_bin_file['format'], 'base64')
283 310
284 311 # Test in sub-directory
285 312 sub_dir = '/foo/'
286 313 self.make_dir('foo')
287 314 model = cm.new_untitled(path=sub_dir, ext='.ipynb')
288 315 model2 = cm.get(sub_dir + name)
289 316 assert isinstance(model2, dict)
290 317 self.assertIn('name', model2)
291 318 self.assertIn('path', model2)
292 319 self.assertIn('content', model2)
293 320 self.assertEqual(model2['name'], 'Untitled.ipynb')
294 321 self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name))
295 322
296 323 # Test with a regular file.
297 324 file_model_path = cm.new_untitled(path=sub_dir, ext='.txt')['path']
298 325 file_model = cm.get(file_model_path)
299 326 self.assertDictContainsSubset(
300 327 {
301 328 'content': u'',
302 329 'format': u'text',
303 330 'mimetype': u'text/plain',
304 331 'name': u'untitled.txt',
305 332 'path': u'foo/untitled.txt',
306 333 'type': u'file',
307 334 'writable': True,
308 335 },
309 336 file_model,
310 337 )
311 338 self.assertIn('created', file_model)
312 339 self.assertIn('last_modified', file_model)
313 340
314 341 # Test getting directory model
315 342
316 343 # Create a sub-sub directory to test getting directory contents with a
317 344 # subdir.
318 345 self.make_dir('foo/bar')
319 346 dirmodel = cm.get('foo')
320 347 self.assertEqual(dirmodel['type'], 'directory')
321 348 self.assertIsInstance(dirmodel['content'], list)
322 349 self.assertEqual(len(dirmodel['content']), 3)
323 350 self.assertEqual(dirmodel['path'], 'foo')
324 351 self.assertEqual(dirmodel['name'], 'foo')
325 352
326 353 # Directory contents should match the contents of each individual entry
327 354 # when requested with content=False.
328 355 model2_no_content = cm.get(sub_dir + name, content=False)
329 356 file_model_no_content = cm.get(u'foo/untitled.txt', content=False)
330 357 sub_sub_dir_no_content = cm.get('foo/bar', content=False)
331 358 self.assertEqual(sub_sub_dir_no_content['path'], 'foo/bar')
332 359 self.assertEqual(sub_sub_dir_no_content['name'], 'bar')
333 360
334 361 for entry in dirmodel['content']:
335 362 # Order isn't guaranteed by the spec, so this is a hacky way of
336 363 # verifying that all entries are matched.
337 364 if entry['path'] == sub_sub_dir_no_content['path']:
338 365 self.assertEqual(entry, sub_sub_dir_no_content)
339 366 elif entry['path'] == model2_no_content['path']:
340 367 self.assertEqual(entry, model2_no_content)
341 368 elif entry['path'] == file_model_no_content['path']:
342 369 self.assertEqual(entry, file_model_no_content)
343 370 else:
344 371 self.fail("Unexpected directory entry: %s" % entry())
345 372
346 373 with self.assertRaises(HTTPError):
347 374 cm.get('foo', type='file')
348 375
349 376 def test_update(self):
350 377 cm = self.contents_manager
351 378 # Create a notebook
352 379 model = cm.new_untitled(type='notebook')
353 380 name = model['name']
354 381 path = model['path']
355 382
356 383 # Change the name in the model for rename
357 384 model['path'] = 'test.ipynb'
358 385 model = cm.update(model, path)
359 386 assert isinstance(model, dict)
360 387 self.assertIn('name', model)
361 388 self.assertIn('path', model)
362 389 self.assertEqual(model['name'], 'test.ipynb')
363 390
364 391 # Make sure the old name is gone
365 392 self.assertRaises(HTTPError, cm.get, path)
366 393
367 394 # Test in sub-directory
368 395 # Create a directory and notebook in that directory
369 396 sub_dir = '/foo/'
370 397 self.make_dir('foo')
371 398 model = cm.new_untitled(path=sub_dir, type='notebook')
372 399 path = model['path']
373 400
374 401 # Change the name in the model for rename
375 402 d = path.rsplit('/', 1)[0]
376 403 new_path = model['path'] = d + '/test_in_sub.ipynb'
377 404 model = cm.update(model, path)
378 405 assert isinstance(model, dict)
379 406 self.assertIn('name', model)
380 407 self.assertIn('path', model)
381 408 self.assertEqual(model['name'], 'test_in_sub.ipynb')
382 409 self.assertEqual(model['path'], new_path)
383 410
384 411 # Make sure the old name is gone
385 412 self.assertRaises(HTTPError, cm.get, path)
386 413
387 414 def test_save(self):
388 415 cm = self.contents_manager
389 416 # Create a notebook
390 417 model = cm.new_untitled(type='notebook')
391 418 name = model['name']
392 419 path = model['path']
393 420
394 421 # Get the model with 'content'
395 422 full_model = cm.get(path)
396 423
397 424 # Save the notebook
398 425 model = cm.save(full_model, path)
399 426 assert isinstance(model, dict)
400 427 self.assertIn('name', model)
401 428 self.assertIn('path', model)
402 429 self.assertEqual(model['name'], name)
403 430 self.assertEqual(model['path'], path)
404 431
405 432 # Test in sub-directory
406 433 # Create a directory and notebook in that directory
407 434 sub_dir = '/foo/'
408 435 self.make_dir('foo')
409 436 model = cm.new_untitled(path=sub_dir, type='notebook')
410 437 name = model['name']
411 438 path = model['path']
412 439 model = cm.get(path)
413 440
414 441 # Change the name in the model for rename
415 442 model = cm.save(model, path)
416 443 assert isinstance(model, dict)
417 444 self.assertIn('name', model)
418 445 self.assertIn('path', model)
419 446 self.assertEqual(model['name'], 'Untitled.ipynb')
420 447 self.assertEqual(model['path'], 'foo/Untitled.ipynb')
421 448
422 449 def test_delete(self):
423 450 cm = self.contents_manager
424 451 # Create a notebook
425 452 nb, name, path = self.new_notebook()
426 453
427 454 # Delete the notebook
428 455 cm.delete(path)
429 456
430 457 # Check that deleting a non-existent path raises an error.
431 458 self.assertRaises(HTTPError, cm.delete, path)
432 459
433 460 # Check that a 'get' on the deleted notebook raises and error
434 461 self.assertRaises(HTTPError, cm.get, path)
435 462
436 463 def test_copy(self):
437 464 cm = self.contents_manager
438 465 parent = u'å b'
439 466 name = u'nb √.ipynb'
440 467 path = u'{0}/{1}'.format(parent, name)
441 468 self.make_dir(parent)
442 469
443 470 orig = cm.new(path=path)
444 471 # copy with unspecified name
445 472 copy = cm.copy(path)
446 473 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy1.ipynb'))
447 474
448 475 # copy with specified name
449 476 copy2 = cm.copy(path, u'å b/copy 2.ipynb')
450 477 self.assertEqual(copy2['name'], u'copy 2.ipynb')
451 478 self.assertEqual(copy2['path'], u'å b/copy 2.ipynb')
452 479 # copy with specified path
453 480 copy2 = cm.copy(path, u'/')
454 481 self.assertEqual(copy2['name'], name)
455 482 self.assertEqual(copy2['path'], name)
456 483
457 484 def test_trust_notebook(self):
458 485 cm = self.contents_manager
459 486 nb, name, path = self.new_notebook()
460 487
461 488 untrusted = cm.get(path)['content']
462 489 assert not cm.notary.check_cells(untrusted)
463 490
464 491 # print(untrusted)
465 492 cm.trust_notebook(path)
466 493 trusted = cm.get(path)['content']
467 494 # print(trusted)
468 495 assert cm.notary.check_cells(trusted)
469 496
470 497 def test_mark_trusted_cells(self):
471 498 cm = self.contents_manager
472 499 nb, name, path = self.new_notebook()
473 500
474 501 cm.mark_trusted_cells(nb, path)
475 502 for cell in nb.cells:
476 503 if cell.cell_type == 'code':
477 504 assert not cell.metadata.trusted
478 505
479 506 cm.trust_notebook(path)
480 507 nb = cm.get(path)['content']
481 508 for cell in nb.cells:
482 509 if cell.cell_type == 'code':
483 510 assert cell.metadata.trusted
484 511
485 512 def test_check_and_sign(self):
486 513 cm = self.contents_manager
487 514 nb, name, path = self.new_notebook()
488 515
489 516 cm.mark_trusted_cells(nb, path)
490 517 cm.check_and_sign(nb, path)
491 518 assert not cm.notary.check_signature(nb)
492 519
493 520 cm.trust_notebook(path)
494 521 nb = cm.get(path)['content']
495 522 cm.mark_trusted_cells(nb, path)
496 523 cm.check_and_sign(nb, path)
497 524 assert cm.notary.check_signature(nb)
General Comments 0
You need to be logged in to leave comments. Login now