comparison tests/test-manifest.py @ 47043:12450fbea288

manifests: push down expected node length into the parser This strictly enforces the node length in the manifest lines according to what the repository expects. One test case moves large hash testing into the non-treemanifest part as treemanifests don't provide an interface for overriding just the node length for now. Differential Revision: https://phab.mercurial-scm.org/D10533
author Joerg Sonnenberger <joerg@bec.de>
date Fri, 30 Apr 2021 02:11:58 +0200
parents 6266d19556ad
children 6000f5b25c9b
comparison
equal deleted inserted replaced
47042:c5e1cc0b4c77 47043:12450fbea288
79 class basemanifesttests(object): 79 class basemanifesttests(object):
80 def parsemanifest(self, text): 80 def parsemanifest(self, text):
81 raise NotImplementedError('parsemanifest not implemented by test case') 81 raise NotImplementedError('parsemanifest not implemented by test case')
82 82
83 def testEmptyManifest(self): 83 def testEmptyManifest(self):
84 m = self.parsemanifest(EMTPY_MANIFEST) 84 m = self.parsemanifest(20, EMTPY_MANIFEST)
85 self.assertEqual(0, len(m)) 85 self.assertEqual(0, len(m))
86 self.assertEqual([], list(m)) 86 self.assertEqual([], list(m))
87 87
88 def testManifest(self): 88 def testManifest(self):
89 m = self.parsemanifest(A_SHORT_MANIFEST) 89 m = self.parsemanifest(20, A_SHORT_MANIFEST)
90 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m)) 90 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m))
91 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py']) 91 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py'])
92 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py')) 92 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
93 self.assertEqual(BIN_HASH_1, m[b'foo']) 93 self.assertEqual(BIN_HASH_1, m[b'foo'])
94 self.assertEqual(b'', m.flags(b'foo')) 94 self.assertEqual(b'', m.flags(b'foo'))
95 with self.assertRaises(KeyError): 95 with self.assertRaises(KeyError):
96 m[b'wat'] 96 m[b'wat']
97 97
98 def testManifestLongHashes(self):
99 m = self.parsemanifest(b'a\0' + b'f' * 64 + b'\n')
100 self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a'])
101
102 def testSetItem(self): 98 def testSetItem(self):
103 want = BIN_HASH_1 99 want = BIN_HASH_1
104 100
105 m = self.parsemanifest(EMTPY_MANIFEST) 101 m = self.parsemanifest(20, EMTPY_MANIFEST)
106 m[b'a'] = want 102 m[b'a'] = want
107 self.assertIn(b'a', m) 103 self.assertIn(b'a', m)
108 self.assertEqual(want, m[b'a']) 104 self.assertEqual(want, m[b'a'])
109 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text()) 105 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text())
110 106
111 m = self.parsemanifest(A_SHORT_MANIFEST) 107 m = self.parsemanifest(20, A_SHORT_MANIFEST)
112 m[b'a'] = want 108 m[b'a'] = want
113 self.assertEqual(want, m[b'a']) 109 self.assertEqual(want, m[b'a'])
114 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text()) 110 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text())
115 111
116 def testSetFlag(self): 112 def testSetFlag(self):
117 want = b'x' 113 want = b'x'
118 114
119 m = self.parsemanifest(EMTPY_MANIFEST) 115 m = self.parsemanifest(20, EMTPY_MANIFEST)
120 # first add a file; a file-less flag makes no sense 116 # first add a file; a file-less flag makes no sense
121 m[b'a'] = BIN_HASH_1 117 m[b'a'] = BIN_HASH_1
122 m.setflag(b'a', want) 118 m.setflag(b'a', want)
123 self.assertEqual(want, m.flags(b'a')) 119 self.assertEqual(want, m.flags(b'a'))
124 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text()) 120 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text())
125 121
126 m = self.parsemanifest(A_SHORT_MANIFEST) 122 m = self.parsemanifest(20, A_SHORT_MANIFEST)
127 # first add a file; a file-less flag makes no sense 123 # first add a file; a file-less flag makes no sense
128 m[b'a'] = BIN_HASH_1 124 m[b'a'] = BIN_HASH_1
129 m.setflag(b'a', want) 125 m.setflag(b'a', want)
130 self.assertEqual(want, m.flags(b'a')) 126 self.assertEqual(want, m.flags(b'a'))
131 self.assertEqual( 127 self.assertEqual(
132 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text() 128 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text()
133 ) 129 )
134 130
135 def testCopy(self): 131 def testCopy(self):
136 m = self.parsemanifest(A_SHORT_MANIFEST) 132 m = self.parsemanifest(20, A_SHORT_MANIFEST)
137 m[b'a'] = BIN_HASH_1 133 m[b'a'] = BIN_HASH_1
138 m2 = m.copy() 134 m2 = m.copy()
139 del m 135 del m
140 del m2 # make sure we don't double free() anything 136 del m2 # make sure we don't double free() anything
141 137
142 def testCompaction(self): 138 def testCompaction(self):
143 unhex = binascii.unhexlify 139 unhex = binascii.unhexlify
144 h1, h2 = unhex(HASH_1), unhex(HASH_2) 140 h1, h2 = unhex(HASH_1), unhex(HASH_2)
145 m = self.parsemanifest(A_SHORT_MANIFEST) 141 m = self.parsemanifest(20, A_SHORT_MANIFEST)
146 m[b'alpha'] = h1 142 m[b'alpha'] = h1
147 m[b'beta'] = h2 143 m[b'beta'] = h2
148 del m[b'foo'] 144 del m[b'foo']
149 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % ( 145 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
150 HASH_1, 146 HASH_1,
162 self.assertEqual(b'', m.flags(b'beta')) 158 self.assertEqual(b'', m.flags(b'beta'))
163 with self.assertRaises(KeyError): 159 with self.assertRaises(KeyError):
164 m[b'foo'] 160 m[b'foo']
165 161
166 def testMatchException(self): 162 def testMatchException(self):
167 m = self.parsemanifest(A_SHORT_MANIFEST) 163 m = self.parsemanifest(20, A_SHORT_MANIFEST)
168 match = matchmod.match(util.localpath(b'/repo'), b'', [b're:.*']) 164 match = matchmod.match(util.localpath(b'/repo'), b'', [b're:.*'])
169 165
170 def filt(path): 166 def filt(path):
171 if path == b'foo': 167 if path == b'foo':
172 assert False 168 assert False
175 match.matchfn = filt 171 match.matchfn = filt
176 with self.assertRaises(AssertionError): 172 with self.assertRaises(AssertionError):
177 m._matches(match) 173 m._matches(match)
178 174
179 def testRemoveItem(self): 175 def testRemoveItem(self):
180 m = self.parsemanifest(A_SHORT_MANIFEST) 176 m = self.parsemanifest(20, A_SHORT_MANIFEST)
181 del m[b'foo'] 177 del m[b'foo']
182 with self.assertRaises(KeyError): 178 with self.assertRaises(KeyError):
183 m[b'foo'] 179 m[b'foo']
184 self.assertEqual(1, len(m)) 180 self.assertEqual(1, len(m))
185 self.assertEqual(1, len(list(m))) 181 self.assertEqual(1, len(list(m)))
191 def testManifestDiff(self): 187 def testManifestDiff(self):
192 MISSING = (None, b'') 188 MISSING = (None, b'')
193 addl = b'z-only-in-left\0' + HASH_1 + b'\n' 189 addl = b'z-only-in-left\0' + HASH_1 + b'\n'
194 addr = b'z-only-in-right\0' + HASH_2 + b'x\n' 190 addr = b'z-only-in-right\0' + HASH_2 + b'x\n'
195 left = self.parsemanifest( 191 left = self.parsemanifest(
196 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl 192 20, A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl
197 ) 193 )
198 right = self.parsemanifest(A_SHORT_MANIFEST + addr) 194 right = self.parsemanifest(20, A_SHORT_MANIFEST + addr)
199 want = { 195 want = {
200 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')), 196 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')),
201 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), 197 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
202 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')), 198 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')),
203 } 199 }
206 want = { 202 want = {
207 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')), 203 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')),
208 b'foo': (MISSING, (BIN_HASH_3, b'x')), 204 b'foo': (MISSING, (BIN_HASH_3, b'x')),
209 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')), 205 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')),
210 } 206 }
211 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left)) 207 self.assertEqual(
208 want, self.parsemanifest(20, EMTPY_MANIFEST).diff(left)
209 )
212 210
213 want = { 211 want = {
214 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING), 212 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING),
215 b'foo': ((BIN_HASH_3, b'x'), MISSING), 213 b'foo': ((BIN_HASH_3, b'x'), MISSING),
216 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), 214 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
217 } 215 }
218 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST))) 216 self.assertEqual(
217 want, left.diff(self.parsemanifest(20, EMTPY_MANIFEST))
218 )
219 copy = right.copy() 219 copy = right.copy()
220 del copy[b'z-only-in-right'] 220 del copy[b'z-only-in-right']
221 del right[b'foo'] 221 del right[b'foo']
222 want = { 222 want = {
223 b'foo': (MISSING, (BIN_HASH_1, b'')), 223 b'foo': (MISSING, (BIN_HASH_1, b'')),
224 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING), 224 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING),
225 } 225 }
226 self.assertEqual(want, right.diff(copy)) 226 self.assertEqual(want, right.diff(copy))
227 227
228 short = self.parsemanifest(A_SHORT_MANIFEST) 228 short = self.parsemanifest(20, A_SHORT_MANIFEST)
229 pruned = short.copy() 229 pruned = short.copy()
230 del pruned[b'foo'] 230 del pruned[b'foo']
231 want = { 231 want = {
232 b'foo': ((BIN_HASH_1, b''), MISSING), 232 b'foo': ((BIN_HASH_1, b''), MISSING),
233 } 233 }
245 def testReversedLines(self): 245 def testReversedLines(self):
246 backwards = b''.join( 246 backwards = b''.join(
247 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l 247 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l
248 ) 248 )
249 try: 249 try:
250 self.parsemanifest(backwards) 250 self.parsemanifest(20, backwards)
251 self.fail('Should have raised ValueError') 251 self.fail('Should have raised ValueError')
252 except ValueError as v: 252 except ValueError as v:
253 self.assertIn('Manifest lines not in sorted order.', str(v)) 253 self.assertIn('Manifest lines not in sorted order.', str(v))
254 254
255 def testNoTerminalNewline(self): 255 def testNoTerminalNewline(self):
256 try: 256 try:
257 self.parsemanifest(A_SHORT_MANIFEST + b'wat') 257 self.parsemanifest(20, A_SHORT_MANIFEST + b'wat')
258 self.fail('Should have raised ValueError') 258 self.fail('Should have raised ValueError')
259 except ValueError as v: 259 except ValueError as v:
260 self.assertIn('Manifest did not end in a newline.', str(v)) 260 self.assertIn('Manifest did not end in a newline.', str(v))
261 261
262 def testNoNewLineAtAll(self): 262 def testNoNewLineAtAll(self):
263 try: 263 try:
264 self.parsemanifest(b'wat') 264 self.parsemanifest(20, b'wat')
265 self.fail('Should have raised ValueError') 265 self.fail('Should have raised ValueError')
266 except ValueError as v: 266 except ValueError as v:
267 self.assertIn('Manifest did not end in a newline.', str(v)) 267 self.assertIn('Manifest did not end in a newline.', str(v))
268 268
269 def testHugeManifest(self): 269 def testHugeManifest(self):
270 m = self.parsemanifest(A_HUGE_MANIFEST) 270 m = self.parsemanifest(20, A_HUGE_MANIFEST)
271 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m)) 271 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
272 self.assertEqual(len(m), len(list(m))) 272 self.assertEqual(len(m), len(list(m)))
273 273
274 def testMatchesMetadata(self): 274 def testMatchesMetadata(self):
275 """Tests matches() for a few specific files to make sure that both 275 """Tests matches() for a few specific files to make sure that both
276 the set of files as well as their flags and nodeids are correct in 276 the set of files as well as their flags and nodeids are correct in
277 the resulting manifest.""" 277 the resulting manifest."""
278 m = self.parsemanifest(A_HUGE_MANIFEST) 278 m = self.parsemanifest(20, A_HUGE_MANIFEST)
279 279
280 match = matchmod.exact([b'file1', b'file200', b'file300']) 280 match = matchmod.exact([b'file1', b'file200', b'file300'])
281 m2 = m._matches(match) 281 m2 = m._matches(match)
282 282
283 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % ( 283 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % (
289 289
290 def testMatchesNonexistentFile(self): 290 def testMatchesNonexistentFile(self):
291 """Tests matches() for a small set of specific files, including one 291 """Tests matches() for a small set of specific files, including one
292 nonexistent file to make sure in only matches against existing files. 292 nonexistent file to make sure in only matches against existing files.
293 """ 293 """
294 m = self.parsemanifest(A_DEEPER_MANIFEST) 294 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
295 295
296 match = matchmod.exact( 296 match = matchmod.exact(
297 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt', b'nonexistent'] 297 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt', b'nonexistent']
298 ) 298 )
299 m2 = m._matches(match) 299 m2 = m._matches(match)
303 ) 303 )
304 304
305 def testMatchesNonexistentDirectory(self): 305 def testMatchesNonexistentDirectory(self):
306 """Tests matches() for a relpath match on a directory that doesn't 306 """Tests matches() for a relpath match on a directory that doesn't
307 actually exist.""" 307 actually exist."""
308 m = self.parsemanifest(A_DEEPER_MANIFEST) 308 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
309 309
310 match = matchmod.match( 310 match = matchmod.match(
311 util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath' 311 util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath'
312 ) 312 )
313 m2 = m._matches(match) 313 m2 = m._matches(match)
314 314
315 self.assertEqual([], m2.keys()) 315 self.assertEqual([], m2.keys())
316 316
317 def testMatchesExactLarge(self): 317 def testMatchesExactLarge(self):
318 """Tests matches() for files matching a large list of exact files.""" 318 """Tests matches() for files matching a large list of exact files."""
319 m = self.parsemanifest(A_HUGE_MANIFEST) 319 m = self.parsemanifest(20, A_HUGE_MANIFEST)
320 320
321 flist = m.keys()[80:300] 321 flist = m.keys()[80:300]
322 match = matchmod.exact(flist) 322 match = matchmod.exact(flist)
323 m2 = m._matches(match) 323 m2 = m._matches(match)
324 324
325 self.assertEqual(flist, m2.keys()) 325 self.assertEqual(flist, m2.keys())
326 326
327 def testMatchesFull(self): 327 def testMatchesFull(self):
328 '''Tests matches() for what should be a full match.''' 328 '''Tests matches() for what should be a full match.'''
329 m = self.parsemanifest(A_DEEPER_MANIFEST) 329 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
330 330
331 match = matchmod.match(util.localpath(b'/repo'), b'', [b'']) 331 match = matchmod.match(util.localpath(b'/repo'), b'', [b''])
332 m2 = m._matches(match) 332 m2 = m._matches(match)
333 333
334 self.assertEqual(m.keys(), m2.keys()) 334 self.assertEqual(m.keys(), m2.keys())
335 335
336 def testMatchesDirectory(self): 336 def testMatchesDirectory(self):
337 """Tests matches() on a relpath match on a directory, which should 337 """Tests matches() on a relpath match on a directory, which should
338 match against all files within said directory.""" 338 match against all files within said directory."""
339 m = self.parsemanifest(A_DEEPER_MANIFEST) 339 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
340 340
341 match = matchmod.match( 341 match = matchmod.match(
342 util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath' 342 util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath'
343 ) 343 )
344 m2 = m._matches(match) 344 m2 = m._matches(match)
360 360
361 def testMatchesExactPath(self): 361 def testMatchesExactPath(self):
362 """Tests matches() on an exact match on a directory, which should 362 """Tests matches() on an exact match on a directory, which should
363 result in an empty manifest because you can't perform an exact match 363 result in an empty manifest because you can't perform an exact match
364 against a directory.""" 364 against a directory."""
365 m = self.parsemanifest(A_DEEPER_MANIFEST) 365 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
366 366
367 match = matchmod.exact([b'a/b']) 367 match = matchmod.exact([b'a/b'])
368 m2 = m._matches(match) 368 m2 = m._matches(match)
369 369
370 self.assertEqual([], m2.keys()) 370 self.assertEqual([], m2.keys())
371 371
372 def testMatchesCwd(self): 372 def testMatchesCwd(self):
373 """Tests matches() on a relpath match with the current directory ('.') 373 """Tests matches() on a relpath match with the current directory ('.')
374 when not in the root directory.""" 374 when not in the root directory."""
375 m = self.parsemanifest(A_DEEPER_MANIFEST) 375 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
376 376
377 match = matchmod.match( 377 match = matchmod.match(
378 util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath' 378 util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath'
379 ) 379 )
380 m2 = m._matches(match) 380 m2 = m._matches(match)
395 ) 395 )
396 396
397 def testMatchesWithPattern(self): 397 def testMatchesWithPattern(self):
398 """Tests matches() for files matching a pattern that reside 398 """Tests matches() for files matching a pattern that reside
399 deeper than the specified directory.""" 399 deeper than the specified directory."""
400 m = self.parsemanifest(A_DEEPER_MANIFEST) 400 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
401 401
402 match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt']) 402 match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt'])
403 m2 = m._matches(match) 403 m2 = m._matches(match)
404 404
405 self.assertEqual( 405 self.assertEqual(
406 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys() 406 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys()
407 ) 407 )
408 408
409 409
410 class testmanifestdict(unittest.TestCase, basemanifesttests): 410 class testmanifestdict(unittest.TestCase, basemanifesttests):
411 def parsemanifest(self, text): 411 def parsemanifest(self, nodelen, text):
412 return manifestmod.manifestdict(text) 412 return manifestmod.manifestdict(nodelen, text)
413
414 def testManifestLongHashes(self):
415 m = self.parsemanifest(32, b'a\0' + b'f' * 64 + b'\n')
416 self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a'])
413 417
414 def testObviouslyBogusManifest(self): 418 def testObviouslyBogusManifest(self):
415 # This is a 163k manifest that came from oss-fuzz. It was a 419 # This is a 163k manifest that came from oss-fuzz. It was a
416 # timeout there, but when run normally it doesn't seem to 420 # timeout there, but when run normally it doesn't seem to
417 # present any particular slowness. 421 # present any particular slowness.
431 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 435 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
432 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17' 436 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17'
433 b'\xac\xbe' 437 b'\xac\xbe'
434 ) 438 )
435 with self.assertRaises(ValueError): 439 with self.assertRaises(ValueError):
436 self.parsemanifest(data) 440 self.parsemanifest(20, data)
437 441
438 442
439 class testtreemanifest(unittest.TestCase, basemanifesttests): 443 class testtreemanifest(unittest.TestCase, basemanifesttests):
440 def parsemanifest(self, text): 444 def parsemanifest(self, nodelen, text):
441 return manifestmod.treemanifest(sha1nodeconstants, b'', text) 445 return manifestmod.treemanifest(sha1nodeconstants, b'', text)
442 446
443 def testWalkSubtrees(self): 447 def testWalkSubtrees(self):
444 m = self.parsemanifest(A_DEEPER_MANIFEST) 448 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
445 449
446 dirs = [s._dir for s in m.walksubtrees()] 450 dirs = [s._dir for s in m.walksubtrees()]
447 self.assertEqual( 451 self.assertEqual(
448 sorted( 452 sorted(
449 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/'] 453 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/']