VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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