VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 25.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testfileset.py 106061 2024-09-16 14:03:52Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test File Set
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2024 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: 106061 $"
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', 'cross'), sOs;
323 if 'os2' in asCompatibleWith:
324 self.sMinStyle = 'os2';
325 elif 'win' in asCompatibleWith:
326 self.sMinStyle = 'win';
327 # 'cross' marks a lowest common denominator for all supported platforms.
328 # Used for Guest Control testing.
329 elif 'cross' in asCompatibleWith:
330 self.sMinStyle = 'cross';
331 self.sBasePath = sBasePath;
332 self.sSubDir = sSubDir;
333 self.oRngFileSizes = oRngFileSizes;
334 self.oRngManyFiles = oRngManyFiles;
335 self.oRngTreeFiles = oRngTreeFiles;
336 self.oRngTreeDepth = oRngTreeDepth;
337 self.oRngTreeDirs = oRngTreeDirs;
338 self.cchMaxPath = cchMaxPath;
339 self.cchMaxName = cchMaxName
340 ## @}
341
342 ## @name Charset stuff
343 ## @todo allow more chars for unix hosts + guests.
344 ## @todo include unicode stuff, except on OS/2 and DOS.
345 ## @{
346 ## The filename charset.
347 self.sFileCharset = string.printable;
348 ## Set of characters that should not trail a guest filename.
349 self.sReservedTrailing = self.ksReservedTrailingWinOS2;
350 if self.sMinStyle in ('win', 'os2'):
351 for ch in self.ksReservedWinOS2:
352 self.sFileCharset = self.sFileCharset.replace(ch, '');
353 elif self.sMinStyle in ('darwin', 'linux', 'solaris'):
354 self.sReservedTrailing = self.ksReservedTrailingUnix;
355 for ch in self.ksReservedUnix:
356 self.sFileCharset = self.sFileCharset.replace(ch, '');
357 else: # 'cross'
358 # Filter out all reserved charsets from all platforms.
359 for ch in self.ksReservedWinOS2:
360 self.sFileCharset = self.sFileCharset.replace(ch, '');
361 for ch in self.ksReservedUnix:
362 self.sFileCharset = self.sFileCharset.replace(ch, '');
363 self.sReservedTrailing = self.ksReservedTrailingWinOS2 \
364 + self.ksReservedTrailingUnix;
365 # More spaces and dot:
366 self.sFileCharset += ' ...';
367 ## @}
368
369 ## The root directory.
370 self.oRoot = None # type: TestDir;
371 ## An empty directory (under root).
372 self.oEmptyDir = None # type: TestDir;
373
374 ## A directory with a lot of files in it.
375 self.oManyDir = None # type: TestDir;
376
377 ## A directory with a mixed tree structure under it.
378 self.oTreeDir = None # type: TestDir;
379 ## Number of files in oTreeDir.
380 self.cTreeFiles = 0;
381 ## Number of directories under oTreeDir.
382 self.cTreeDirs = 0;
383 ## Number of other file types under oTreeDir.
384 self.cTreeOthers = 0;
385
386 ## All directories in creation order.
387 self.aoDirs = [] # type: list(TestDir);
388 ## All files in creation order.
389 self.aoFiles = [] # type: list(TestFile);
390 ## Path to object lookup.
391 self.dPaths = {} # type: dict(str, TestFsObj);
392
393 #
394 # Do the creating.
395 #
396 self.uSeed = uSeed if uSeed is not None else utils.timestampMilli();
397 self.oRandom = random.Random();
398 self.oRandom.seed(self.uSeed);
399 reporter.log('prepareGuestForTesting: random seed %s' % (self.uSeed,));
400
401 self.__createTestStuff();
402
403 def __createFilename(self, oParent, sCharset, sReservedTrailing):
404 """
405 Creates a filename contains random characters from sCharset and together
406 with oParent.sPath doesn't exceed the given max chars in length.
407 """
408 ## @todo Consider extending this to take UTF-8 and UTF-16 encoding so we
409 ## can safely use the full unicode range. Need to check how
410 ## RTZipTarCmd handles file name encoding in general...
411
412 if oParent:
413 cchMaxName = self.cchMaxPath - len(oParent.sPath) - 1;
414 else:
415 cchMaxName = self.cchMaxPath - 4;
416 if cchMaxName > self.cchMaxName:
417 cchMaxName = self.cchMaxName;
418 if cchMaxName <= 1:
419 cchMaxName = 2;
420
421 while True:
422 cchName = self.oRandom.randrange(1, cchMaxName);
423 sName = ''.join(self.oRandom.choice(sCharset) for _ in xrange(cchName));
424 if oParent is None or not oParent.contains(sName):
425 if sName[-1] not in sReservedTrailing:
426 if sName not in ('.', '..',):
427 return sName;
428 return ''; # never reached, but makes pylint happy.
429
430 def generateFilenameEx(self, cchMax = -1, cchMin = -1):
431 """
432 Generates a filename according to the given specs.
433
434 This is for external use, whereas __createFilename is for internal.
435
436 Returns generated filename.
437 """
438 assert cchMax == -1 or (cchMax >= 1 and cchMax > cchMin);
439 if cchMin <= 0:
440 cchMin = 1;
441 if cchMax < cchMin:
442 cchMax = self.cchMaxName;
443
444 while True:
445 cchName = self.oRandom.randrange(cchMin, cchMax + 1);
446 sName = ''.join(self.oRandom.choice(self.sFileCharset) for _ in xrange(cchName));
447 if sName[-1] not in self.sReservedTrailing:
448 if sName not in ('.', '..',):
449 return sName;
450 return ''; # never reached, but makes pylint happy.
451
452 def __createTestDir(self, oParent, sDir, sName = None):
453 """
454 Creates a test directory.
455 """
456 oDir = TestDir(oParent, sDir, sName);
457 self.aoDirs.append(oDir);
458 self.dPaths[sDir] = oDir;
459 return oDir;
460
461 def __createTestFile(self, oParent, sFile):
462 """
463 Creates a test file with random size up to cbMaxContent and random content.
464 """
465 cbFile = self.oRandom.choice(self.oRngFileSizes);
466 abContent = bytearray(self.oRandom.getrandbits(8) for _ in xrange(cbFile));
467
468 oFile = TestFile(oParent, sFile, abContent);
469 self.aoFiles.append(oFile);
470 self.dPaths[sFile] = oFile;
471 return oFile;
472
473 def __createTestStuff(self):
474 """
475 Create a random file set that we can work on in the tests.
476 Returns True/False.
477 """
478
479 #
480 # Create the root test dir.
481 #
482 sRoot = pathutils.joinEx(self.fDosStyle, self.sBasePath, self.sSubDir);
483 self.oRoot = self.__createTestDir(None, sRoot, self.sSubDir);
484 self.oEmptyDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'empty'));
485
486 #
487 # Create a directory with lots of files in it:
488 #
489 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'many'));
490 self.oManyDir = oDir;
491 cManyFiles = self.oRandom.choice(self.oRngManyFiles);
492 for _ in xrange(cManyFiles):
493 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
494 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
495
496 #
497 # Generate a tree of files and dirs.
498 #
499 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'tree'));
500 uMaxDepth = self.oRandom.choice(self.oRngTreeDepth);
501 cMaxFiles = self.oRandom.choice(self.oRngTreeFiles);
502 cMaxDirs = self.oRandom.choice(self.oRngTreeDirs);
503 self.oTreeDir = oDir;
504 self.cTreeFiles = 0;
505 self.cTreeDirs = 0;
506 uDepth = 0;
507 while self.cTreeFiles < cMaxFiles and self.cTreeDirs < cMaxDirs:
508 iAction = self.oRandom.randrange(0, 2+1);
509 # 0: Add a file:
510 if iAction == 0 and self.cTreeFiles < cMaxFiles and len(oDir.sPath) < 230 - 2:
511 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
512 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
513 self.cTreeFiles += 1;
514 # 1: Add a subdirector and descend into it:
515 elif iAction == 1 and self.cTreeDirs < cMaxDirs and uDepth < uMaxDepth and len(oDir.sPath) < 220:
516 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
517 oDir = self.__createTestDir(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
518 self.cTreeDirs += 1;
519 uDepth += 1;
520 # 2: Ascend to parent dir:
521 elif iAction == 2 and uDepth > 0:
522 oDir = oDir.oParent;
523 uDepth -= 1;
524
525 return True;
526
527 def createTarball(self, sTarFileHst):
528 """
529 Creates a tarball on the host.
530 Returns success indicator.
531 """
532 reporter.log('Creating tarball "%s" with test files for the guest...' % (sTarFileHst,));
533
534 cchSkip = len(self.sBasePath) + 1;
535
536 # Open the tarball:
537 try:
538 # Make sure to explicitly set GNU_FORMAT here, as with Python 3.8 the default format (tarfile.DEFAULT_FORMAT)
539 # has been changed to tarfile.PAX_FORMAT, which our extraction code (vts_tar) currently can't handle.
540 ## @todo Remove tarfile.GNU_FORMAT and use tarfile.PAX_FORMAT as soon as we have PAX support.
541 oTarFile = tarfile.open(sTarFileHst, 'w:gz', format = tarfile.GNU_FORMAT); # pylint: disable=consider-using-with
542 except:
543 return reporter.errorXcpt('Failed to open new tar file: %s' % (sTarFileHst,));
544
545 # Directories:
546 for oDir in self.aoDirs:
547 sPath = oDir.sPath[cchSkip:];
548 if self.fDosStyle:
549 sPath = sPath.replace('\\', '/');
550 oTarInfo = tarfile.TarInfo(sPath + '/');
551 oTarInfo.mode = 0o777;
552 oTarInfo.type = tarfile.DIRTYPE;
553 try:
554 oTarFile.addfile(oTarInfo);
555 except:
556 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oDir.sPath,));
557
558 # Files:
559 for oFile in self.aoFiles:
560 sPath = oFile.sPath[cchSkip:];
561 if self.fDosStyle:
562 sPath = sPath.replace('\\', '/');
563 oTarInfo = tarfile.TarInfo(sPath);
564 oTarInfo.mode = 0o666;
565 oTarInfo.size = len(oFile.abContent);
566 oFile.off = 0;
567 try:
568 oTarFile.addfile(oTarInfo, oFile);
569 except:
570 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oFile.sPath,));
571
572 # Complete the tarball.
573 try:
574 oTarFile.close();
575 except:
576 return reporter.errorXcpt('Error closing new tar file: %s' % (sTarFileHst,));
577 return True;
578
579 def writeToDisk(self, sAltBase = None):
580 """
581 Writes out the files to disk.
582 Returns True on success, False + error logging on failure.
583 """
584
585 # We only need to flip DOS slashes to unix ones, since windows & OS/2 can handle unix slashes.
586 fDosToUnix = self.fDosStyle and os.path.sep != '\\';
587
588 # The directories:
589 for oDir in self.aoDirs:
590 sPath = oDir.sPath;
591 if sAltBase:
592 if fDosToUnix:
593 sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep);
594 else:
595 sPath = sAltBase + sPath[len(self.sBasePath):];
596 elif fDosToUnix:
597 sPath = sPath.replace('\\', os.path.sep);
598
599 try:
600 os.mkdir(sPath, 0o770);
601 except:
602 return reporter.errorXcpt('mkdir(%s) failed' % (sPath,));
603
604 # The files:
605 for oFile in self.aoFiles:
606 sPath = oFile.sPath;
607 if sAltBase:
608 if fDosToUnix:
609 sPath = sAltBase + sPath[len(self.sBasePath):].replace('\\', os.path.sep);
610 else:
611 sPath = sAltBase + sPath[len(self.sBasePath):];
612 elif fDosToUnix:
613 sPath = sPath.replace('\\', os.path.sep);
614
615 try:
616 oOutFile = open(sPath, 'wb'); # pylint: disable=consider-using-with
617 except:
618 return reporter.errorXcpt('open(%s, "wb") failed' % (sPath,));
619 try:
620 if sys.version_info[0] < 3:
621 oOutFile.write(bytes(oFile.abContent));
622 else:
623 oOutFile.write(oFile.abContent);
624 except:
625 try: oOutFile.close();
626 except: pass;
627 return reporter.errorXcpt('%s: write(%s bytes) failed' % (sPath, oFile.cbContent,));
628 try:
629 oOutFile.close();
630 except:
631 return reporter.errorXcpt('%s: close() failed' % (sPath,));
632
633 return True;
634
635
636 def chooseRandomFile(self):
637 """
638 Returns a random file.
639 """
640 return self.aoFiles[self.oRandom.choice(xrange(len(self.aoFiles)))];
641
642 def chooseRandomDirFromTree(self, fLeaf = False, fNonEmpty = False, cMaxRetries = 1024):
643 """
644 Returns a random directory from the tree (self.oTreeDir).
645 Will return None if no directory with given parameters was found.
646 """
647 cRetries = 0;
648 while cRetries < cMaxRetries:
649 oDir = self.aoDirs[self.oRandom.choice(xrange(len(self.aoDirs)))];
650 # Check fNonEmpty requirement:
651 if not fNonEmpty or oDir.aoChildren:
652 # Check leaf requirement:
653 if not fLeaf:
654 for oChild in oDir.aoChildren:
655 if isinstance(oChild, TestDir):
656 continue; # skip it.
657
658 # Return if in the tree:
659 oParent = oDir.oParent;
660 while oParent is not None:
661 if oParent is self.oTreeDir:
662 return oDir;
663 oParent = oParent.oParent;
664 cRetries += 1;
665
666 return None; # make pylint happy
667
668#
669# Unit testing.
670#
671
672# pylint: disable=missing-docstring
673# pylint: disable=undefined-variable
674class TestFileSetUnitTests(unittest.TestCase):
675 def testGeneral(self):
676 oSet = TestFileSet(False, '/tmp', 'unittest');
677 self.assertTrue(isinstance(oSet.chooseRandomDirFromTree(), TestDir));
678 self.assertTrue(isinstance(oSet.chooseRandomFile(), TestFile));
679
680 def testHexFormatBytes(self):
681 self.assertEqual(TestFile.hexFormatBytes(bytearray([0,1,2,3,4,5,6,7,8,9])),
682 '00 01 02 03 04 05 06 07-08 09');
683 self.assertEqual(TestFile.hexFormatBytes(memoryview(bytearray([0,1,2,3,4,5,6,7,8,9,10, 16]))),
684 '00 01 02 03 04 05 06 07-08 09 0a 10');
685
686
687if __name__ == '__main__':
688 unittest.main();
689 # not reached.
690
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