##// END OF EJS Templates
TEST: Test separate roots for Contents and Checkpoints.
Scott Sanderson -
Show More
@@ -1,542 +1,568 b''
1 1 # coding: utf-8
2 2 """Test the contents webservice API."""
3 3
4 4 import base64
5 from contextlib import contextmanager
5 6 import io
6 7 import json
7 8 import os
8 9 import shutil
9 10 from unicodedata import normalize
10 11
11 12 pjoin = os.path.join
12 13
13 14 import requests
14 15
15 16 from IPython.html.utils import url_path_join, url_escape, to_os_path
16 17 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
17 18 from IPython.nbformat import read, write, from_dict
18 19 from IPython.nbformat.v4 import (
19 20 new_notebook, new_markdown_cell,
20 21 )
21 22 from IPython.nbformat import v2
22 23 from IPython.utils import py3compat
23 24 from IPython.utils.data import uniq_stable
25 from IPython.utils.tempdir import TemporaryDirectory
24 26
25 27
26 28 def notebooks_only(dir_model):
27 29 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
28 30
29 31 def dirs_only(dir_model):
30 32 return [x for x in dir_model['content'] if x['type']=='directory']
31 33
32 34
33 35 class API(object):
34 36 """Wrapper for contents API calls."""
35 37 def __init__(self, base_url):
36 38 self.base_url = base_url
37 39
38 40 def _req(self, verb, path, body=None, params=None):
39 41 response = requests.request(verb,
40 42 url_path_join(self.base_url, 'api/contents', path),
41 43 data=body, params=params,
42 44 )
43 45 response.raise_for_status()
44 46 return response
45 47
46 48 def list(self, path='/'):
47 49 return self._req('GET', path)
48 50
49 51 def read(self, path, type=None, format=None):
50 52 params = {}
51 53 if type is not None:
52 54 params['type'] = type
53 55 if format is not None:
54 56 params['format'] = format
55 57 return self._req('GET', path, params=params)
56 58
57 59 def create_untitled(self, path='/', ext='.ipynb'):
58 60 body = None
59 61 if ext:
60 62 body = json.dumps({'ext': ext})
61 63 return self._req('POST', path, body)
62 64
63 65 def mkdir_untitled(self, path='/'):
64 66 return self._req('POST', path, json.dumps({'type': 'directory'}))
65 67
66 68 def copy(self, copy_from, path='/'):
67 69 body = json.dumps({'copy_from':copy_from})
68 70 return self._req('POST', path, body)
69 71
70 72 def create(self, path='/'):
71 73 return self._req('PUT', path)
72 74
73 75 def upload(self, path, body):
74 76 return self._req('PUT', path, body)
75 77
76 78 def mkdir(self, path='/'):
77 79 return self._req('PUT', path, json.dumps({'type': 'directory'}))
78 80
79 81 def copy_put(self, copy_from, path='/'):
80 82 body = json.dumps({'copy_from':copy_from})
81 83 return self._req('PUT', path, body)
82 84
83 85 def save(self, path, body):
84 86 return self._req('PUT', path, body)
85 87
86 88 def delete(self, path='/'):
87 89 return self._req('DELETE', path)
88 90
89 91 def rename(self, path, new_path):
90 92 body = json.dumps({'path': new_path})
91 93 return self._req('PATCH', path, body)
92 94
93 95 def get_checkpoints(self, path):
94 96 return self._req('GET', url_path_join(path, 'checkpoints'))
95 97
96 98 def new_checkpoint(self, path):
97 99 return self._req('POST', url_path_join(path, 'checkpoints'))
98 100
99 101 def restore_checkpoint(self, path, checkpoint_id):
100 102 return self._req('POST', url_path_join(path, 'checkpoints', checkpoint_id))
101 103
102 104 def delete_checkpoint(self, path, checkpoint_id):
103 105 return self._req('DELETE', url_path_join(path, 'checkpoints', checkpoint_id))
104 106
105 107 class APITest(NotebookTestBase):
106 108 """Test the kernels web service API"""
107 109 dirs_nbs = [('', 'inroot'),
108 110 ('Directory with spaces in', 'inspace'),
109 111 (u'unicodΓ©', 'innonascii'),
110 112 ('foo', 'a'),
111 113 ('foo', 'b'),
112 114 ('foo', 'name with spaces'),
113 115 ('foo', u'unicodΓ©'),
114 116 ('foo/bar', 'baz'),
115 117 ('ordering', 'A'),
116 118 ('ordering', 'b'),
117 119 ('ordering', 'C'),
118 120 (u'Γ₯ b', u'Γ§ d'),
119 121 ]
120 122 hidden_dirs = ['.hidden', '__pycache__']
121 123
122 124 # Don't include root dir.
123 125 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs[1:]])
124 126 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
125 127
126 128 @staticmethod
127 129 def _blob_for_name(name):
128 130 return name.encode('utf-8') + b'\xFF'
129 131
130 132 @staticmethod
131 133 def _txt_for_name(name):
132 134 return u'%s text file' % name
133 135
134 136 def to_os_path(self, api_path):
135 137 return to_os_path(api_path, root=self.notebook_dir.name)
136 138
137 139 def make_dir(self, api_path):
138 140 """Create a directory at api_path"""
139 141 os_path = self.to_os_path(api_path)
140 142 try:
141 143 os.makedirs(os_path)
142 144 except OSError:
143 145 print("Directory already exists: %r" % os_path)
144 146
145 147 def make_txt(self, api_path, txt):
146 148 """Make a text file at a given api_path"""
147 149 os_path = self.to_os_path(api_path)
148 150 with io.open(os_path, 'w', encoding='utf-8') as f:
149 151 f.write(txt)
150 152
151 153 def make_blob(self, api_path, blob):
152 154 """Make a binary file at a given api_path"""
153 155 os_path = self.to_os_path(api_path)
154 156 with io.open(os_path, 'wb') as f:
155 157 f.write(blob)
156 158
157 159 def make_nb(self, api_path, nb):
158 160 """Make a notebook file at a given api_path"""
159 161 os_path = self.to_os_path(api_path)
160 162
161 163 with io.open(os_path, 'w', encoding='utf-8') as f:
162 164 write(nb, f, version=4)
163 165
164 166 def delete_dir(self, api_path):
165 167 """Delete a directory at api_path, removing any contents."""
166 168 os_path = self.to_os_path(api_path)
167 169 shutil.rmtree(os_path, ignore_errors=True)
168 170
169 171 def delete_file(self, api_path):
170 172 """Delete a file at the given path if it exists."""
171 173 if self.isfile(api_path):
172 174 os.unlink(self.to_os_path(api_path))
173 175
174 176 def isfile(self, api_path):
175 177 return os.path.isfile(self.to_os_path(api_path))
176 178
177 179 def isdir(self, api_path):
178 180 return os.path.isdir(self.to_os_path(api_path))
179 181
180 182 def setUp(self):
181 183
182 184 for d in (self.dirs + self.hidden_dirs):
183 185 self.make_dir(d)
184 186
185 187 for d, name in self.dirs_nbs:
186 188 # create a notebook
187 189 nb = new_notebook()
188 190 self.make_nb(u'{}/{}.ipynb'.format(d, name), nb)
189 191
190 192 # create a text file
191 193 txt = self._txt_for_name(name)
192 194 self.make_txt(u'{}/{}.txt'.format(d, name), txt)
193 195
194 196 # create a binary file
195 197 blob = self._blob_for_name(name)
196 198 self.make_blob(u'{}/{}.blob'.format(d, name), blob)
197 199
198 200 self.api = API(self.base_url())
199 201
200 202 def tearDown(self):
201 203 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
202 204 self.delete_dir(dname)
203 205 self.delete_file('inroot.ipynb')
204 206
205 207 def test_list_notebooks(self):
206 208 nbs = notebooks_only(self.api.list().json())
207 209 self.assertEqual(len(nbs), 1)
208 210 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
209 211
210 212 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
211 213 self.assertEqual(len(nbs), 1)
212 214 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
213 215
214 216 nbs = notebooks_only(self.api.list(u'/unicodΓ©/').json())
215 217 self.assertEqual(len(nbs), 1)
216 218 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
217 219 self.assertEqual(nbs[0]['path'], u'unicodΓ©/innonascii.ipynb')
218 220
219 221 nbs = notebooks_only(self.api.list('/foo/bar/').json())
220 222 self.assertEqual(len(nbs), 1)
221 223 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
222 224 self.assertEqual(nbs[0]['path'], 'foo/bar/baz.ipynb')
223 225
224 226 nbs = notebooks_only(self.api.list('foo').json())
225 227 self.assertEqual(len(nbs), 4)
226 228 nbnames = { normalize('NFC', n['name']) for n in nbs }
227 229 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
228 230 expected = { normalize('NFC', name) for name in expected }
229 231 self.assertEqual(nbnames, expected)
230 232
231 233 nbs = notebooks_only(self.api.list('ordering').json())
232 234 nbnames = [n['name'] for n in nbs]
233 235 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
234 236 self.assertEqual(nbnames, expected)
235 237
236 238 def test_list_dirs(self):
237 239 dirs = dirs_only(self.api.list().json())
238 240 dir_names = {normalize('NFC', d['name']) for d in dirs}
239 241 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
240 242
241 243 def test_list_nonexistant_dir(self):
242 244 with assert_http_error(404):
243 245 self.api.list('nonexistant')
244 246
245 247 def test_get_nb_contents(self):
246 248 for d, name in self.dirs_nbs:
247 249 path = url_path_join(d, name + '.ipynb')
248 250 nb = self.api.read(path).json()
249 251 self.assertEqual(nb['name'], u'%s.ipynb' % name)
250 252 self.assertEqual(nb['path'], path)
251 253 self.assertEqual(nb['type'], 'notebook')
252 254 self.assertIn('content', nb)
253 255 self.assertEqual(nb['format'], 'json')
254 256 self.assertIn('content', nb)
255 257 self.assertIn('metadata', nb['content'])
256 258 self.assertIsInstance(nb['content']['metadata'], dict)
257 259
258 260 def test_get_contents_no_such_file(self):
259 261 # Name that doesn't exist - should be a 404
260 262 with assert_http_error(404):
261 263 self.api.read('foo/q.ipynb')
262 264
263 265 def test_get_text_file_contents(self):
264 266 for d, name in self.dirs_nbs:
265 267 path = url_path_join(d, name + '.txt')
266 268 model = self.api.read(path).json()
267 269 self.assertEqual(model['name'], u'%s.txt' % name)
268 270 self.assertEqual(model['path'], path)
269 271 self.assertIn('content', model)
270 272 self.assertEqual(model['format'], 'text')
271 273 self.assertEqual(model['type'], 'file')
272 274 self.assertEqual(model['content'], self._txt_for_name(name))
273 275
274 276 # Name that doesn't exist - should be a 404
275 277 with assert_http_error(404):
276 278 self.api.read('foo/q.txt')
277 279
278 280 # Specifying format=text should fail on a non-UTF-8 file
279 281 with assert_http_error(400):
280 282 self.api.read('foo/bar/baz.blob', type='file', format='text')
281 283
282 284 def test_get_binary_file_contents(self):
283 285 for d, name in self.dirs_nbs:
284 286 path = url_path_join(d, name + '.blob')
285 287 model = self.api.read(path).json()
286 288 self.assertEqual(model['name'], u'%s.blob' % name)
287 289 self.assertEqual(model['path'], path)
288 290 self.assertIn('content', model)
289 291 self.assertEqual(model['format'], 'base64')
290 292 self.assertEqual(model['type'], 'file')
291 293 self.assertEqual(
292 294 base64.decodestring(model['content'].encode('ascii')),
293 295 self._blob_for_name(name),
294 296 )
295 297
296 298 # Name that doesn't exist - should be a 404
297 299 with assert_http_error(404):
298 300 self.api.read('foo/q.txt')
299 301
300 302 def test_get_bad_type(self):
301 303 with assert_http_error(400):
302 304 self.api.read(u'unicodΓ©', type='file') # this is a directory
303 305
304 306 with assert_http_error(400):
305 307 self.api.read(u'unicodΓ©/innonascii.ipynb', type='directory')
306 308
307 309 def _check_created(self, resp, path, type='notebook'):
308 310 self.assertEqual(resp.status_code, 201)
309 311 location_header = py3compat.str_to_unicode(resp.headers['Location'])
310 312 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path)))
311 313 rjson = resp.json()
312 314 self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1])
313 315 self.assertEqual(rjson['path'], path)
314 316 self.assertEqual(rjson['type'], type)
315 317 isright = self.isdir if type == 'directory' else self.isfile
316 318 assert isright(path)
317 319
318 320 def test_create_untitled(self):
319 321 resp = self.api.create_untitled(path=u'Γ₯ b')
320 322 self._check_created(resp, u'Γ₯ b/Untitled.ipynb')
321 323
322 324 # Second time
323 325 resp = self.api.create_untitled(path=u'Γ₯ b')
324 326 self._check_created(resp, u'Γ₯ b/Untitled1.ipynb')
325 327
326 328 # And two directories down
327 329 resp = self.api.create_untitled(path='foo/bar')
328 330 self._check_created(resp, 'foo/bar/Untitled.ipynb')
329 331
330 332 def test_create_untitled_txt(self):
331 333 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
332 334 self._check_created(resp, 'foo/bar/untitled.txt', type='file')
333 335
334 336 resp = self.api.read(path='foo/bar/untitled.txt')
335 337 model = resp.json()
336 338 self.assertEqual(model['type'], 'file')
337 339 self.assertEqual(model['format'], 'text')
338 340 self.assertEqual(model['content'], '')
339 341
340 342 def test_upload(self):
341 343 nb = new_notebook()
342 344 nbmodel = {'content': nb, 'type': 'notebook'}
343 345 path = u'Γ₯ b/Upload tΓ©st.ipynb'
344 346 resp = self.api.upload(path, body=json.dumps(nbmodel))
345 347 self._check_created(resp, path)
346 348
347 349 def test_mkdir_untitled(self):
348 350 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
349 351 self._check_created(resp, u'Γ₯ b/Untitled Folder', type='directory')
350 352
351 353 # Second time
352 354 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
353 355 self._check_created(resp, u'Γ₯ b/Untitled Folder 1', type='directory')
354 356
355 357 # And two directories down
356 358 resp = self.api.mkdir_untitled(path='foo/bar')
357 359 self._check_created(resp, 'foo/bar/Untitled Folder', type='directory')
358 360
359 361 def test_mkdir(self):
360 362 path = u'Γ₯ b/New βˆ‚ir'
361 363 resp = self.api.mkdir(path)
362 364 self._check_created(resp, path, type='directory')
363 365
364 366 def test_mkdir_hidden_400(self):
365 367 with assert_http_error(400):
366 368 resp = self.api.mkdir(u'Γ₯ b/.hidden')
367 369
368 370 def test_upload_txt(self):
369 371 body = u'ΓΌnicode tΓ©xt'
370 372 model = {
371 373 'content' : body,
372 374 'format' : 'text',
373 375 'type' : 'file',
374 376 }
375 377 path = u'Γ₯ b/Upload tΓ©st.txt'
376 378 resp = self.api.upload(path, body=json.dumps(model))
377 379
378 380 # check roundtrip
379 381 resp = self.api.read(path)
380 382 model = resp.json()
381 383 self.assertEqual(model['type'], 'file')
382 384 self.assertEqual(model['format'], 'text')
383 385 self.assertEqual(model['content'], body)
384 386
385 387 def test_upload_b64(self):
386 388 body = b'\xFFblob'
387 389 b64body = base64.encodestring(body).decode('ascii')
388 390 model = {
389 391 'content' : b64body,
390 392 'format' : 'base64',
391 393 'type' : 'file',
392 394 }
393 395 path = u'Γ₯ b/Upload tΓ©st.blob'
394 396 resp = self.api.upload(path, body=json.dumps(model))
395 397
396 398 # check roundtrip
397 399 resp = self.api.read(path)
398 400 model = resp.json()
399 401 self.assertEqual(model['type'], 'file')
400 402 self.assertEqual(model['path'], path)
401 403 self.assertEqual(model['format'], 'base64')
402 404 decoded = base64.decodestring(model['content'].encode('ascii'))
403 405 self.assertEqual(decoded, body)
404 406
405 407 def test_upload_v2(self):
406 408 nb = v2.new_notebook()
407 409 ws = v2.new_worksheet()
408 410 nb.worksheets.append(ws)
409 411 ws.cells.append(v2.new_code_cell(input='print("hi")'))
410 412 nbmodel = {'content': nb, 'type': 'notebook'}
411 413 path = u'Γ₯ b/Upload tΓ©st.ipynb'
412 414 resp = self.api.upload(path, body=json.dumps(nbmodel))
413 415 self._check_created(resp, path)
414 416 resp = self.api.read(path)
415 417 data = resp.json()
416 418 self.assertEqual(data['content']['nbformat'], 4)
417 419
418 420 def test_copy(self):
419 421 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
420 422 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
421 423
422 424 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
423 425 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
424 426
425 427 def test_copy_copy(self):
426 428 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
427 429 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
428 430
429 431 resp = self.api.copy(u'Γ₯ b/Γ§ d-Copy1.ipynb', u'Γ₯ b')
430 432 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
431 433
432 434 def test_copy_path(self):
433 435 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
434 436 self._check_created(resp, u'Γ₯ b/a.ipynb')
435 437
436 438 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
437 439 self._check_created(resp, u'Γ₯ b/a-Copy1.ipynb')
438 440
439 441 def test_copy_put_400(self):
440 442 with assert_http_error(400):
441 443 resp = self.api.copy_put(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b/cΓΈpy.ipynb')
442 444
443 445 def test_copy_dir_400(self):
444 446 # can't copy directories
445 447 with assert_http_error(400):
446 448 resp = self.api.copy(u'Γ₯ b', u'foo')
447 449
448 450 def test_delete(self):
449 451 for d, name in self.dirs_nbs:
450 452 print('%r, %r' % (d, name))
451 453 resp = self.api.delete(url_path_join(d, name + '.ipynb'))
452 454 self.assertEqual(resp.status_code, 204)
453 455
454 456 for d in self.dirs + ['/']:
455 457 nbs = notebooks_only(self.api.list(d).json())
456 458 print('------')
457 459 print(d)
458 460 print(nbs)
459 461 self.assertEqual(nbs, [])
460 462
461 463 def test_delete_dirs(self):
462 464 # depth-first delete everything, so we don't try to delete empty directories
463 465 for name in sorted(self.dirs + ['/'], key=len, reverse=True):
464 466 listing = self.api.list(name).json()['content']
465 467 for model in listing:
466 468 self.api.delete(model['path'])
467 469 listing = self.api.list('/').json()['content']
468 470 self.assertEqual(listing, [])
469 471
470 472 def test_delete_non_empty_dir(self):
471 473 """delete non-empty dir raises 400"""
472 474 with assert_http_error(400):
473 475 self.api.delete(u'Γ₯ b')
474 476
475 477 def test_rename(self):
476 478 resp = self.api.rename('foo/a.ipynb', 'foo/z.ipynb')
477 479 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
478 480 self.assertEqual(resp.json()['name'], 'z.ipynb')
479 481 self.assertEqual(resp.json()['path'], 'foo/z.ipynb')
480 482 assert self.isfile('foo/z.ipynb')
481 483
482 484 nbs = notebooks_only(self.api.list('foo').json())
483 485 nbnames = set(n['name'] for n in nbs)
484 486 self.assertIn('z.ipynb', nbnames)
485 487 self.assertNotIn('a.ipynb', nbnames)
486 488
487 489 def test_rename_existing(self):
488 490 with assert_http_error(409):
489 491 self.api.rename('foo/a.ipynb', 'foo/b.ipynb')
490 492
491 493 def test_save(self):
492 494 resp = self.api.read('foo/a.ipynb')
493 495 nbcontent = json.loads(resp.text)['content']
494 496 nb = from_dict(nbcontent)
495 497 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
496 498
497 499 nbmodel= {'content': nb, 'type': 'notebook'}
498 500 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
499 501
500 502 nbcontent = self.api.read('foo/a.ipynb').json()['content']
501 503 newnb = from_dict(nbcontent)
502 504 self.assertEqual(newnb.cells[0].source,
503 505 u'Created by test Β³')
504 506
505
506 507 def test_checkpoints(self):
507 508 resp = self.api.read('foo/a.ipynb')
508 509 r = self.api.new_checkpoint('foo/a.ipynb')
509 510 self.assertEqual(r.status_code, 201)
510 511 cp1 = r.json()
511 512 self.assertEqual(set(cp1), {'id', 'last_modified'})
512 513 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
513 514
514 515 # Modify it
515 516 nbcontent = json.loads(resp.text)['content']
516 517 nb = from_dict(nbcontent)
517 518 hcell = new_markdown_cell('Created by test')
518 519 nb.cells.append(hcell)
519 520 # Save
520 521 nbmodel= {'content': nb, 'type': 'notebook'}
521 522 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
522 523
523 524 # List checkpoints
524 525 cps = self.api.get_checkpoints('foo/a.ipynb').json()
525 526 self.assertEqual(cps, [cp1])
526 527
527 528 nbcontent = self.api.read('foo/a.ipynb').json()['content']
528 529 nb = from_dict(nbcontent)
529 530 self.assertEqual(nb.cells[0].source, 'Created by test')
530 531
531 532 # Restore cp1
532 533 r = self.api.restore_checkpoint('foo/a.ipynb', cp1['id'])
533 534 self.assertEqual(r.status_code, 204)
534 535 nbcontent = self.api.read('foo/a.ipynb').json()['content']
535 536 nb = from_dict(nbcontent)
536 537 self.assertEqual(nb.cells, [])
537 538
538 539 # Delete cp1
539 540 r = self.api.delete_checkpoint('foo/a.ipynb', cp1['id'])
540 541 self.assertEqual(r.status_code, 204)
541 542 cps = self.api.get_checkpoints('foo/a.ipynb').json()
542 543 self.assertEqual(cps, [])
544
545 @contextmanager
546 def patch_cp_root(self, dirname):
547 """
548 Temporarily patch the root dir of our checkpoint manager.
549 """
550 cpm = self.notebook.contents_manager.checkpoint_manager
551 old_dirname = cpm.root_dir
552 cpm.root_dir = dirname
553 try:
554 yield
555 finally:
556 cpm.root_dir = old_dirname
557
558 def test_checkpoints_separate_root(self):
559 """
560 Test that FileCheckpointManager functions correctly even when it's
561 using a different root dir from FileContentsManager. This also keeps
562 the implementation honest for use with ContentsManagers that don't map
563 models to the filesystem
564 """
565
566 with TemporaryDirectory() as td:
567 with self.patch_cp_root(td):
568 self.test_checkpoints()
General Comments 0
You need to be logged in to leave comments. Login now