1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: testfileset.py 83623 2020-04-08 16:26:17Z vboxsync $
|
---|
3 | # pylint: disable=too-many-lines
|
---|
4 |
|
---|
5 | """
|
---|
6 | Test File Set
|
---|
7 | """
|
---|
8 |
|
---|
9 | __copyright__ = \
|
---|
10 | """
|
---|
11 | Copyright (C) 2010-2020 Oracle Corporation
|
---|
12 |
|
---|
13 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
14 | available from http://www.virtualbox.org. This file is free software;
|
---|
15 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
16 | General Public License (GPL) as published by the Free Software
|
---|
17 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
18 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
19 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
20 |
|
---|
21 | The contents of this file may alternatively be used under the terms
|
---|
22 | of the Common Development and Distribution License Version 1.0
|
---|
23 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
24 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
25 | CDDL are applicable instead of those of the GPL.
|
---|
26 |
|
---|
27 | You may elect to license modified versions of this file under the
|
---|
28 | terms and conditions of either the GPL or the CDDL or both.
|
---|
29 | """
|
---|
30 | __version__ = "$Revision: 83623 $"
|
---|
31 |
|
---|
32 |
|
---|
33 | # Standard Python imports.
|
---|
34 | import os;
|
---|
35 | import random;
|
---|
36 | import string;
|
---|
37 | import sys;
|
---|
38 | import tarfile;
|
---|
39 | import unittest;
|
---|
40 |
|
---|
41 | # Validation Kit imports.
|
---|
42 | from common import utils;
|
---|
43 | from common import pathutils;
|
---|
44 | from testdriver import reporter;
|
---|
45 |
|
---|
46 | # Python 3 hacks:
|
---|
47 | if sys.version_info[0] >= 3:
|
---|
48 | xrange = range; # pylint: disable=redefined-builtin,invalid-name
|
---|
49 |
|
---|
50 |
|
---|
51 |
|
---|
52 | class 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 |
|
---|
78 | class 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 |
|
---|
215 | class 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 |
|
---|
267 | class 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 |
|
---|
279 | class 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
|
---|
648 | class 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 |
|
---|
661 | if __name__ == '__main__':
|
---|
662 | unittest.main();
|
---|
663 | # not reached.
|
---|
664 |
|
---|