VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testdriver/testfileset.py@ 83623

Last change on this file since 83623 was 83623, checked in by vboxsync, 5 years ago

Validation Kit/testdriver: Explicitly set tarfile.GNU_FORMAT when creating tarballs, as with Python 3.8 the default format (tarfile.DEFAULT_FORMAT) has been changed to tarfile.PAX_FORMAT, which our extraction code (vts_tar) currently can't handle.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 24.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testfileset.py 83623 2020-04-08 16:26:17Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test File Set
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2020 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 83623 $"
31
32
33# Standard Python imports.
34import os;
35import random;
36import string;
37import sys;
38import tarfile;
39import unittest;
40
41# Validation Kit imports.
42from common import utils;
43from common import pathutils;
44from testdriver import reporter;
45
46# Python 3 hacks:
47if sys.version_info[0] >= 3:
48 xrange = range; # pylint: disable=redefined-builtin,invalid-name
49
50
51
52class TestFsObj(object):
53 """ A file system object we created in for test purposes. """
54 def __init__(self, oParent, sPath, sName = None):
55 self.oParent = oParent # type: TestDir
56 self.sPath = sPath # type: str
57 self.sName = sName # type: str
58 if oParent:
59 assert sPath.startswith(oParent.sPath);
60 assert sName is None;
61 self.sName = sPath[len(oParent.sPath) + 1:];
62 # Add to parent.
63 oParent.aoChildren.append(self);
64 oParent.dChildrenUpper[self.sName.upper()] = self;
65
66 def buildPath(self, sRoot, sSep):
67 """
68 Build the path from sRoot using sSep.
69
70 This is handy for getting the path to an object in a different context
71 (OS, path) than what it was generated for.
72 """
73 if self.oParent:
74 return self.oParent.buildPath(sRoot, sSep) + sSep + self.sName;
75 return sRoot + sSep + self.sName;
76
77
78class TestFile(TestFsObj):
79 """ A file object in the guest. """
80 def __init__(self, oParent, sPath, abContent):
81 TestFsObj.__init__(self, oParent, sPath);
82 self.abContent = abContent # type: bytearray
83 self.cbContent = len(abContent);
84 self.off = 0;
85
86 def read(self, cbToRead):
87 """ read() emulation. """
88 assert self.off <= self.cbContent;
89 cbLeft = self.cbContent - self.off;
90 if cbLeft < cbToRead:
91 cbToRead = cbLeft;
92 abRet = self.abContent[self.off:(self.off + cbToRead)];
93 assert len(abRet) == cbToRead;
94 self.off += cbToRead;
95 if sys.version_info[0] < 3:
96 return bytes(abRet);
97 return abRet;
98
99 def equalFile(self, oFile):
100 """ Compares the content of oFile with self.abContent. """
101
102 # Check the size first.
103 try:
104 cbFile = os.fstat(oFile.fileno()).st_size;
105 except:
106 return reporter.errorXcpt();
107 if cbFile != self.cbContent:
108 return reporter.error('file size differs: %s, cbContent=%s' % (cbFile, self.cbContent));
109
110 # Compare the bytes next.
111 offFile = 0;
112 try:
113 oFile.seek(offFile);
114 except:
115 return reporter.error('seek error');
116 while offFile < self.cbContent:
117 cbToRead = self.cbContent - offFile;
118 if cbToRead > 256*1024:
119 cbToRead = 256*1024;
120 try:
121 abRead = oFile.read(cbToRead);
122 except:
123 return reporter.error('read error at offset %s' % (offFile,));
124 cbRead = len(abRead);
125 if cbRead == 0:
126 return reporter.error('premature end of file at offset %s' % (offFile,));
127 if not utils.areBytesEqual(abRead, self.abContent[offFile:(offFile + cbRead)]):
128 return reporter.error('%s byte block at offset %s differs' % (cbRead, offFile,));
129 # Advance:
130 offFile += cbRead;
131
132 return True;
133
134 @staticmethod
135 def hexFormatBytes(abBuf):
136 """ Formats a buffer/string/whatever as a string of hex bytes """
137 if sys.version_info[0] >= 3:
138 if utils.isString(abBuf):
139 try: abBuf = bytes(abBuf, 'utf-8');
140 except: pass;
141 else:
142 if utils.isString(abBuf):
143 try: abBuf = bytearray(abBuf, 'utf-8'); # pylint: disable=redefined-variable-type
144 except: pass;
145 sRet = '';
146 off = 0;
147 for off, bByte in enumerate(abBuf):
148 if off > 0:
149 sRet += ' ' if off & 7 else '-';
150 if isinstance(bByte, int):
151 sRet += '%02x' % (bByte,);
152 else:
153 sRet += '%02x' % (ord(bByte),);
154 return sRet;
155
156 def checkRange(self, cbRange, offFile = 0):
157 """ Check if the specified range is entirely within the file or not. """
158 if offFile >= self.cbContent:
159 return reporter.error('buffer @ %s LB %s is beyond the end of the file (%s bytes)!'
160 % (offFile, cbRange, self.cbContent,));
161 if offFile + cbRange > self.cbContent:
162 return reporter.error('buffer @ %s LB %s is partially beyond the end of the file (%s bytes)!'
163 % (offFile, cbRange, self.cbContent,));
164 return True;
165
166 def equalMemory(self, abBuf, offFile = 0):
167 """
168 Compares the content of the given buffer with the file content at that
169 file offset.
170
171 Returns True if it matches, False + error logging if it does not match.
172 """
173 if not abBuf:
174 return True;
175
176 if not self.checkRange(len(abBuf), offFile):
177 return False;
178
179 if sys.version_info[0] >= 3:
180 if utils.areBytesEqual(abBuf, self.abContent[offFile:(offFile + len(abBuf))]):
181 return True;
182 else:
183 if utils.areBytesEqual(abBuf, buffer(self.abContent, offFile, len(abBuf))): # pylint: disable=undefined-variable
184 return True;
185
186 reporter.error('mismatch with buffer @ %s LB %s (cbContent=%s)!' % (offFile, len(abBuf), self.cbContent,));
187 reporter.error(' type(abBuf): %s' % (type(abBuf),));
188 #if isinstance(abBuf, memoryview):
189 # reporter.error(' nbytes=%s len=%s itemsize=%s type(obj)=%s'
190 # % (abBuf.nbytes, len(abBuf), abBuf.itemsize, type(abBuf.obj),));
191 reporter.error('type(abContent): %s' % (type(self.abContent),));
192
193 offBuf = 0;
194 cbLeft = len(abBuf);
195 while cbLeft > 0:
196 cbLine = min(16, cbLeft);
197 abBuf1 = abBuf[offBuf:(offBuf + cbLine)];
198 abBuf2 = self.abContent[offFile:(offFile + cbLine)];
199 if not utils.areBytesEqual(abBuf1, abBuf2):
200 try: sStr1 = self.hexFormatBytes(abBuf1);
201 except: sStr1 = 'oops';
202 try: sStr2 = self.hexFormatBytes(abBuf2);
203 except: sStr2 = 'oops';
204 reporter.log('%#10x: %s' % (offBuf, sStr1,));
205 reporter.log('%#10x: %s' % (offFile, sStr2,));
206
207 # Advance.
208 offBuf += 16;
209 offFile += 16;
210 cbLeft -= 16;
211
212 return False;
213
214
215class TestFileZeroFilled(TestFile):
216 """
217 Zero filled test file.
218 """
219
220 def __init__(self, oParent, sPath, cbContent):
221 TestFile.__init__(self, oParent, sPath, bytearray(1));
222 self.cbContent = cbContent;
223
224 def read(self, cbToRead):
225 """ read() emulation. """
226 assert self.off <= self.cbContent;
227 cbLeft = self.cbContent - self.off;
228 if cbLeft < cbToRead:
229 cbToRead = cbLeft;
230 abRet = bytearray(cbToRead);
231 assert len(abRet) == cbToRead;
232 self.off += cbToRead;
233 if sys.version_info[0] < 3:
234 return bytes(abRet);
235 return abRet;
236
237 def equalFile(self, oFile):
238 _ = oFile;
239 assert False, "not implemented";
240 return False;
241
242 def equalMemory(self, abBuf, offFile = 0):
243 if not abBuf:
244 return True;
245
246 if not self.checkRange(len(abBuf), offFile):
247 return False;
248
249 if utils.areBytesEqual(abBuf, bytearray(len(abBuf))):
250 return True;
251
252 cErrors = 0;
253 offBuf = 0
254 while offBuf < len(abBuf):
255 bByte = abBuf[offBuf];
256 if not isinstance(bByte, int):
257 bByte = ord(bByte);
258 if bByte != 0:
259 reporter.error('Mismatch @ %s/%s: %#x, expected 0!' % (offFile, offBuf, bByte,));
260 cErrors += 1;
261 if cErrors > 32:
262 return False;
263 offBuf += 1;
264 return cErrors == 0;
265
266
267class TestDir(TestFsObj):
268 """ A file object in the guest. """
269 def __init__(self, oParent, sPath, sName = None):
270 TestFsObj.__init__(self, oParent, sPath, sName);
271 self.aoChildren = [] # type: list(TestFsObj)
272 self.dChildrenUpper = {} # type: dict(str, TestFsObj)
273
274 def contains(self, sName):
275 """ Checks if the directory contains the given name. """
276 return sName.upper() in self.dChildrenUpper
277
278
279class TestFileSet(object):
280 """
281 A generated set of files and directories for use in a test.
282
283 Can be wrapped up into a tarball or written directly to the file system.
284 """
285
286 ksReservedWinOS2 = '/\\"*:<>?|\t\v\n\r\f\a\b';
287 ksReservedUnix = '/';
288 ksReservedTrailingWinOS2 = ' .';
289 ksReservedTrailingUnix = '';
290
291 ## @name Path style.
292 ## @{
293
294 ## @}
295
296 def __init__(self, fDosStyle, sBasePath, sSubDir, # pylint: disable=too-many-arguments
297 asCompatibleWith = None, # List of getHostOs values to the names must be compatible with.
298 oRngFileSizes = xrange(0, 16384),
299 oRngManyFiles = xrange(128, 512),
300 oRngTreeFiles = xrange(128, 384),
301 oRngTreeDepth = xrange(92, 256),
302 oRngTreeDirs = xrange(2, 16),
303 cchMaxPath = 230,
304 cchMaxName = 230,
305 uSeed = None):
306 ## @name Parameters
307 ## @{
308 self.fDosStyle = fDosStyle;
309 self.sMinStyle = 'win' if fDosStyle else 'linux';
310 if asCompatibleWith is not None:
311 for sOs in asCompatibleWith:
312 assert sOs in ('win', 'os2', 'darwin', 'linux', 'solaris',), sOs;
313 if 'os2' in asCompatibleWith:
314 self.sMinStyle = 'os2';
315 elif 'win' in asCompatibleWith:
316 self.sMinStyle = 'win';
317 self.sBasePath = sBasePath;
318 self.sSubDir = sSubDir;
319 self.oRngFileSizes = oRngFileSizes;
320 self.oRngManyFiles = oRngManyFiles;
321 self.oRngTreeFiles = oRngTreeFiles;
322 self.oRngTreeDepth = oRngTreeDepth;
323 self.oRngTreeDirs = oRngTreeDirs;
324 self.cchMaxPath = cchMaxPath;
325 self.cchMaxName = cchMaxName
326 ## @}
327
328 ## @name Charset stuff
329 ## @todo allow more chars for unix hosts + guests.
330 ## @todo include unicode stuff, except on OS/2 and DOS.
331 ## @{
332 ## The filename charset.
333 self.sFileCharset = string.printable;
334 ## Set of characters that should not trail a guest filename.
335 self.sReservedTrailing = self.ksReservedTrailingWinOS2;
336 if self.sMinStyle in ('win', 'os2'):
337 for ch in self.ksReservedWinOS2:
338 self.sFileCharset = self.sFileCharset.replace(ch, '');
339 else:
340 self.sReservedTrailing = self.ksReservedTrailingUnix;
341 for ch in self.ksReservedUnix:
342 self.sFileCharset = self.sFileCharset.replace(ch, '');
343 # More spaces and dot:
344 self.sFileCharset += ' ...';
345 ## @}
346
347 ## The root directory.
348 self.oRoot = None # type: TestDir;
349 ## An empty directory (under root).
350 self.oEmptyDir = None # type: TestDir;
351
352 ## A directory with a lot of files in it.
353 self.oManyDir = None # type: TestDir;
354
355 ## A directory with a mixed tree structure under it.
356 self.oTreeDir = None # type: TestDir;
357 ## Number of files in oTreeDir.
358 self.cTreeFiles = 0;
359 ## Number of directories under oTreeDir.
360 self.cTreeDirs = 0;
361 ## Number of other file types under oTreeDir.
362 self.cTreeOthers = 0;
363
364 ## All directories in creation order.
365 self.aoDirs = [] # type: list(TestDir);
366 ## All files in creation order.
367 self.aoFiles = [] # type: list(TestFile);
368 ## Path to object lookup.
369 self.dPaths = {} # type: dict(str, TestFsObj);
370
371 #
372 # Do the creating.
373 #
374 self.uSeed = uSeed if uSeed is not None else utils.timestampMilli();
375 self.oRandom = random.Random();
376 self.oRandom.seed(self.uSeed);
377 reporter.log('prepareGuestForTesting: random seed %s' % (self.uSeed,));
378
379 self.__createTestStuff();
380
381 def __createFilename(self, oParent, sCharset, sReservedTrailing):
382 """
383 Creates a filename contains random characters from sCharset and together
384 with oParent.sPath doesn't exceed the given max chars in length.
385 """
386 ## @todo Consider extending this to take UTF-8 and UTF-16 encoding so we
387 ## can safely use the full unicode range. Need to check how
388 ## RTZipTarCmd handles file name encoding in general...
389
390 if oParent:
391 cchMaxName = self.cchMaxPath - len(oParent.sPath) - 1;
392 else:
393 cchMaxName = self.cchMaxPath - 4;
394 if cchMaxName > self.cchMaxName:
395 cchMaxName = self.cchMaxName;
396 if cchMaxName <= 1:
397 cchMaxName = 2;
398
399 while True:
400 cchName = self.oRandom.randrange(1, cchMaxName);
401 sName = ''.join(self.oRandom.choice(sCharset) for _ in xrange(cchName));
402 if oParent is None or not oParent.contains(sName):
403 if sName[-1] not in sReservedTrailing:
404 if sName not in ('.', '..',):
405 return sName;
406 return ''; # never reached, but makes pylint happy.
407
408 def generateFilenameEx(self, cchMax = -1, cchMin = -1):
409 """
410 Generates a filename according to the given specs.
411
412 This is for external use, whereas __createFilename is for internal.
413
414 Returns generated filename.
415 """
416 assert cchMax == -1 or (cchMax >= 1 and cchMax > cchMin);
417 if cchMin <= 0:
418 cchMin = 1;
419 if cchMax < cchMin:
420 cchMax = self.cchMaxName;
421
422 while True:
423 cchName = self.oRandom.randrange(cchMin, cchMax + 1);
424 sName = ''.join(self.oRandom.choice(self.sFileCharset) for _ in xrange(cchName));
425 if sName[-1] not in self.sReservedTrailing:
426 if sName not in ('.', '..',):
427 return sName;
428 return ''; # never reached, but makes pylint happy.
429
430 def __createTestDir(self, oParent, sDir, sName = None):
431 """
432 Creates a test directory.
433 """
434 oDir = TestDir(oParent, sDir, sName);
435 self.aoDirs.append(oDir);
436 self.dPaths[sDir] = oDir;
437 return oDir;
438
439 def __createTestFile(self, oParent, sFile):
440 """
441 Creates a test file with random size up to cbMaxContent and random content.
442 """
443 cbFile = self.oRandom.choice(self.oRngFileSizes);
444 abContent = bytearray(self.oRandom.getrandbits(8) for _ in xrange(cbFile));
445
446 oFile = TestFile(oParent, sFile, abContent);
447 self.aoFiles.append(oFile);
448 self.dPaths[sFile] = oFile;
449 return oFile;
450
451 def __createTestStuff(self):
452 """
453 Create a random file set that we can work on in the tests.
454 Returns True/False.
455 """
456
457 #
458 # Create the root test dir.
459 #
460 sRoot = pathutils.joinEx(self.fDosStyle, self.sBasePath, self.sSubDir);
461 self.oRoot = self.__createTestDir(None, sRoot, self.sSubDir);
462 self.oEmptyDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'empty'));
463
464 #
465 # Create a directory with lots of files in it:
466 #
467 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'many'));
468 self.oManyDir = oDir;
469 cManyFiles = self.oRandom.choice(self.oRngManyFiles);
470 for _ in xrange(cManyFiles):
471 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
472 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
473
474 #
475 # Generate a tree of files and dirs.
476 #
477 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'tree'));
478 uMaxDepth = self.oRandom.choice(self.oRngTreeDepth);
479 cMaxFiles = self.oRandom.choice(self.oRngTreeFiles);
480 cMaxDirs = self.oRandom.choice(self.oRngTreeDirs);
481 self.oTreeDir = oDir;
482 self.cTreeFiles = 0;
483 self.cTreeDirs = 0;
484 uDepth = 0;
485 while self.cTreeFiles < cMaxFiles and self.cTreeDirs < cMaxDirs:
486 iAction = self.oRandom.randrange(0, 2+1);
487 # 0: Add a file:
488 if iAction == 0 and self.cTreeFiles < cMaxFiles and len(oDir.sPath) < 230 - 2:
489 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
490 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
491 self.cTreeFiles += 1;
492 # 1: Add a subdirector and descend into it:
493 elif iAction == 1 and self.cTreeDirs < cMaxDirs and uDepth < uMaxDepth and len(oDir.sPath) < 220:
494 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
495 oDir = self.__createTestDir(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
496 self.cTreeDirs += 1;
497 uDepth += 1;
498 # 2: Ascend to parent dir:
499 elif iAction == 2 and uDepth > 0:
500 oDir = oDir.oParent;
501 uDepth -= 1;
502
503 return True;
504
505 def createTarball(self, sTarFileHst):
506 """
507 Creates a tarball on the host.
508 Returns success indicator.
509 """
510 reporter.log('Creating tarball "%s" with test files for the guest...' % (sTarFileHst,));
511
512 cchSkip = len(self.sBasePath) + 1;
513
514 # Open the tarball:
515 try:
516 # Make sure to explicitly set GNU_FORMAT here, as with Python 3.8 the default format (tarfile.DEFAULT_FORMAT)
517 # has been changed to tarfile.PAX_FORMAT, which our extraction code (vts_tar) currently can't handle.
518 ## @todo Remove tarfile.GNU_FORMAT and use tarfile.PAX_FORMAT as soon as we have PAX support.
519 oTarFile = tarfile.open(sTarFileHst, 'w:gz', format = tarfile.GNU_FORMAT);
520 except:
521 return reporter.errorXcpt('Failed to open new tar file: %s' % (sTarFileHst,));
522
523 # Directories:
524 for oDir in self.aoDirs:
525 sPath = oDir.sPath[cchSkip:];
526 if self.fDosStyle:
527 sPath = sPath.replace('\\', '/');
528 oTarInfo = tarfile.TarInfo(sPath + '/');
529 oTarInfo.mode = 0o777;
530 oTarInfo.type = tarfile.DIRTYPE;
531 try:
532 oTarFile.addfile(oTarInfo);
533 except:
534 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oDir.sPath,));
535
536 # Files:
537 for oFile in self.aoFiles:
538 sPath = oFile.sPath[cchSkip:];
539 if self.fDosStyle:
540 sPath = sPath.replace('\\', '/');
541 oTarInfo = tarfile.TarInfo(sPath);
542 oTarInfo.mode = 0o666;
543 oTarInfo.size = len(oFile.abContent);
544 oFile.off = 0;
545 try:
546 oTarFile.addfile(oTarInfo, oFile);
547 except:
548 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oFile.sPath,));
549
550 # Complete the tarball.
551 try:
552 oTarFile.close();
553 except:
554 return reporter.errorXcpt('Error closing new tar file: %s' % (sTarFileHst,));
555 return True;
556
557 def writeToDisk(self, sAltBase = None):
558 """
559 Writes out the files to disk.
560 Returns True on success, False + error logging on failure.
561 """
562
563 # We only need to flip DOS slashes to unix ones, since windows & OS/2 can handle unix slashes.
564 fDosToUnix = self.fDosStyle and os.path.sep != '\\';
565
566 # The directories:
567 for oDir in self.aoDirs:
568 sPath = oDir.sPath;
569 if sAltBase:
570 if fDosToUnix:
571 sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep);
572 else:
573 sPath = sAltBase + sPath[len(self.sBasePath):];
574 elif fDosToUnix:
575 sPath = sPath.replace('\\', os.path.sep);
576
577 try:
578 os.mkdir(sPath, 0o770);
579 except:
580 return reporter.errorXcpt('mkdir(%s) failed' % (sPath,));
581
582 # The files:
583 for oFile in self.aoFiles:
584 sPath = oFile.sPath;
585 if sAltBase:
586 if fDosToUnix:
587 sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep);
588 else:
589 sPath = sAltBase + sPath[len(self.sBasePath):];
590 elif fDosToUnix:
591 sPath = sPath.replace('\\', os.path.sep);
592
593 try:
594 oOutFile = open(sPath, 'wb');
595 except:
596 return reporter.errorXcpt('open(%s, "wb") failed' % (sPath,));
597 try:
598 if sys.version_info[0] < 3:
599 oOutFile.write(bytes(oFile.abContent));
600 else:
601 oOutFile.write(oFile.abContent);
602 except:
603 try: oOutFile.close();
604 except: pass;
605 return reporter.errorXcpt('%s: write(%s bytes) failed' % (sPath, oFile.cbContent,));
606 try:
607 oOutFile.close();
608 except:
609 return reporter.errorXcpt('%s: close() failed' % (sPath,));
610
611 return True;
612
613
614 def chooseRandomFile(self):
615 """
616 Returns a random file.
617 """
618 return self.aoFiles[self.oRandom.choice(xrange(len(self.aoFiles)))];
619
620 def chooseRandomDirFromTree(self, fLeaf = False, fNonEmpty = False):
621 """
622 Returns a random directory from the tree (self.oTreeDir).
623 """
624 while True:
625 oDir = self.aoDirs[self.oRandom.choice(xrange(len(self.aoDirs)))];
626 # Check fNonEmpty requirement:
627 if not fNonEmpty or oDir.aoChildren:
628 # Check leaf requirement:
629 if not fLeaf:
630 for oChild in oDir.aoChildren:
631 if isinstance(oChild, TestDir):
632 continue; # skip it.
633
634 # Return if in the tree:
635 oParent = oDir.oParent;
636 while oParent is not None:
637 if oParent is self.oTreeDir:
638 return oDir;
639 oParent = oParent.oParent;
640 return None; # make pylint happy
641
642#
643# Unit testing.
644#
645
646# pylint: disable=missing-docstring
647# pylint: disable=undefined-variable
648class TestFileSetUnitTests(unittest.TestCase):
649 def testGeneral(self):
650 oSet = TestFileSet(False, '/tmp', 'unittest');
651 self.assertTrue(isinstance(oSet.chooseRandomDirFromTree(), TestDir));
652 self.assertTrue(isinstance(oSet.chooseRandomFile(), TestFile));
653
654 def testHexFormatBytes(self):
655 self.assertEqual(TestFile.hexFormatBytes(bytearray([0,1,2,3,4,5,6,7,8,9])),
656 '00 01 02 03 04 05 06 07-08 09');
657 self.assertEqual(TestFile.hexFormatBytes(memoryview(bytearray([0,1,2,3,4,5,6,7,8,9,10, 16]))),
658 '00 01 02 03 04 05 06 07-08 09 0a 10');
659
660
661if __name__ == '__main__':
662 unittest.main();
663 # not reached.
664
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette