VirtualBox

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

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

vboxtestfileset.py: Generatlized and split up, leaving vboxtestfilesset.py containing the VBox VM specialization. bugref:9151 bugref:9320

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testfileset.py 79185 2019-06-17 14:19:41Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Test File Set
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2019 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: 79185 $"
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):
55 self.oParent = oParent # type: TestDir
56 self.sPath = sPath # type: str
57 self.sName = sPath # type: str
58 if oParent:
59 assert sPath.startswith(oParent.sPath);
60 self.sName = sPath[len(oParent.sPath) + 1:];
61 # Add to parent.
62 oParent.aoChildren.append(self);
63 oParent.dChildrenUpper[self.sName.upper()] = self;
64
65class TestFile(TestFsObj):
66 """ A file object in the guest. """
67 def __init__(self, oParent, sPath, abContent):
68 TestFsObj.__init__(self, oParent, sPath);
69 self.abContent = abContent # type: bytearray
70 self.cbContent = len(abContent);
71 self.off = 0;
72
73 def read(self, cbToRead):
74 """ read() emulation. """
75 assert self.off <= self.cbContent;
76 cbLeft = self.cbContent - self.off;
77 if cbLeft < cbToRead:
78 cbToRead = cbLeft;
79 abRet = self.abContent[self.off:(self.off + cbToRead)];
80 assert len(abRet) == cbToRead;
81 self.off += cbToRead;
82 if sys.version_info[0] < 3:
83 return bytes(abRet);
84 return abRet;
85
86class TestDir(TestFsObj):
87 """ A file object in the guest. """
88 def __init__(self, oParent, sPath):
89 TestFsObj.__init__(self, oParent, sPath);
90 self.aoChildren = [] # type: list(TestFsObj)
91 self.dChildrenUpper = {} # type: dict(str, TestFsObj)
92
93 def contains(self, sName):
94 """ Checks if the directory contains the given name. """
95 return sName.upper() in self.dChildrenUpper
96
97
98class TestFileSet(object):
99 """
100 A generated set of files and directories for use in a test.
101
102 Can be wrapped up into a tarball or written directly to the file system.
103 """
104
105 ksReservedWinOS2 = '/\\"*:<>?|\t\v\n\r\f\a\b';
106 ksReservedUnix = '/';
107 ksReservedTrailingWinOS2 = ' .';
108 ksReservedTrailingUnix = '';
109
110 ## @name Path style.
111 ## @{
112
113 ## @}
114
115 def __init__(self, fDosStyle, sBasePath, sSubDir, # pylint: disable=too-many-arguments
116 asCompatibleWith = None, # List of getHostOs values to the names must be compatible with.
117 oRngFileSizes = xrange(0, 16384),
118 oRngManyFiles = xrange(128, 512),
119 oRngTreeFiles = xrange(128, 384),
120 oRngTreeDepth = xrange(92, 256),
121 oRngTreeDirs = xrange(2, 16),
122 cchMaxPath = 230,
123 cchMaxName = 230,
124 uSeed = None):
125 ## @name Parameters
126 ## @{
127 self.fDosStyle = fDosStyle;
128 self.sMinStyle = 'win' if fDosStyle else 'linux';
129 if asCompatibleWith is not None:
130 for sOs in asCompatibleWith:
131 assert sOs in ('win', 'os2', 'darwin', 'linux', 'solaris',), sOs;
132 if 'os2' in asCompatibleWith:
133 self.sMinStyle = 'os2';
134 elif 'win' in asCompatibleWith:
135 self.sMinStyle = 'win';
136 self.sBasePath = sBasePath;
137 self.sSubDir = sSubDir;
138 self.oRngFileSizes = oRngFileSizes;
139 self.oRngManyFiles = oRngManyFiles;
140 self.oRngTreeFiles = oRngTreeFiles;
141 self.oRngTreeDepth = oRngTreeDepth;
142 self.oRngTreeDirs = oRngTreeDirs;
143 self.cchMaxPath = cchMaxPath;
144 self.cchMaxName = cchMaxName
145 ## @}
146
147 ## @name Charset stuff
148 ## @todo allow more chars for unix hosts + guests.
149 ## @todo include unicode stuff, except on OS/2 and DOS.
150 ## @{
151 ## The filename charset.
152 self.sFileCharset = string.printable;
153 ## Set of characters that should not trail a guest filename.
154 self.sReservedTrailing = self.ksReservedTrailingWinOS2;
155 if self.sMinStyle in ('win', 'os2'):
156 for ch in self.ksReservedWinOS2:
157 self.sFileCharset = self.sFileCharset.replace(ch, '');
158 else:
159 self.sReservedTrailing = self.ksReservedTrailingUnix;
160 for ch in self.ksReservedUnix:
161 self.sFileCharset = self.sFileCharset.replace(ch, '');
162 # More spaces and dot:
163 self.sFileCharset += ' ...';
164 ## @}
165
166 ## The root directory.
167 self.oRoot = None # type: TestDir;
168 ## An empty directory (under root).
169 self.oEmptyDir = None # type: TestDir;
170
171 ## A directory with a lot of files in it.
172 self.oManyDir = None # type: TestDir;
173
174 ## A directory with a mixed tree structure under it.
175 self.oTreeDir = None # type: TestDir;
176 ## Number of files in oTreeDir.
177 self.cTreeFiles = 0;
178 ## Number of directories under oTreeDir.
179 self.cTreeDirs = 0;
180 ## Number of other file types under oTreeDir.
181 self.cTreeOthers = 0;
182
183 ## All directories in creation order.
184 self.aoDirs = [] # type: list(TestDir);
185 ## All files in creation order.
186 self.aoFiles = [] # type: list(TestFile);
187 ## Path to object lookup.
188 self.dPaths = {} # type: dict(str, TestFsObj);
189
190 #
191 # Do the creating.
192 #
193 self.uSeed = uSeed if uSeed is not None else utils.timestampMilli();
194 self.oRandom = random.Random();
195 self.oRandom.seed(self.uSeed);
196 reporter.log('prepareGuestForTesting: random seed %s' % (self.uSeed,));
197
198 self.__createTestStuff();
199
200 def __createFilename(self, oParent, sCharset, sReservedTrailing):
201 """
202 Creates a filename contains random characters from sCharset and together
203 with oParent.sPath doesn't exceed the given max chars in length.
204 """
205 ## @todo Consider extending this to take UTF-8 and UTF-16 encoding so we
206 ## can safely use the full unicode range. Need to check how
207 ## RTZipTarCmd handles file name encoding in general...
208
209 if oParent:
210 cchMaxName = self.cchMaxPath - len(oParent.sPath) - 1;
211 else:
212 cchMaxName = self.cchMaxPath - 4;
213 if cchMaxName > self.cchMaxName:
214 cchMaxName = self.cchMaxName;
215 if cchMaxName <= 1:
216 cchMaxName = 2;
217
218 while True:
219 cchName = self.oRandom.randrange(1, cchMaxName);
220 sName = ''.join(self.oRandom.choice(sCharset) for _ in xrange(cchName));
221 if oParent is None or not oParent.contains(sName):
222 if sName[-1] not in sReservedTrailing:
223 if sName not in ('.', '..',):
224 return sName;
225 return ''; # never reached, but makes pylint happy.
226
227 def generateFilenameEx(self, cchMax = -1, cchMin = -1):
228 """
229 Generates a filename according to the given specs.
230
231 This is for external use, whereas __createFilename is for internal.
232
233 Returns generated filename.
234 """
235 assert cchMax == -1 or (cchMax >= 1 and cchMax > cchMin);
236 if cchMin <= 0:
237 cchMin = 1;
238 if cchMax < cchMin:
239 cchMax = self.cchMaxName;
240
241 while True:
242 cchName = self.oRandom.randrange(cchMin, cchMax + 1);
243 sName = ''.join(self.oRandom.choice(self.sFileCharset) for _ in xrange(cchName));
244 if sName[-1] not in self.sReservedTrailing:
245 if sName not in ('.', '..',):
246 return sName;
247 return ''; # never reached, but makes pylint happy.
248
249 def __createTestDir(self, oParent, sDir):
250 """
251 Creates a test directory.
252 """
253 oDir = TestDir(oParent, sDir);
254 self.aoDirs.append(oDir);
255 self.dPaths[sDir] = oDir;
256 return oDir;
257
258 def __createTestFile(self, oParent, sFile):
259 """
260 Creates a test file with random size up to cbMaxContent and random content.
261 """
262 cbFile = self.oRandom.choice(self.oRngFileSizes);
263 abContent = bytearray(self.oRandom.getrandbits(8) for _ in xrange(cbFile));
264
265 oFile = TestFile(oParent, sFile, abContent);
266 self.aoFiles.append(oFile);
267 self.dPaths[sFile] = oFile;
268 return oFile;
269
270 def __createTestStuff(self):
271 """
272 Create a random file set that we can work on in the tests.
273 Returns True/False.
274 """
275
276 #
277 # Create the root test dir.
278 #
279 sRoot = pathutils.joinEx(self.fDosStyle, self.sBasePath, self.sSubDir);
280 self.oRoot = self.__createTestDir(None, sRoot);
281 self.oEmptyDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'empty'));
282
283 #
284 # Create a directory with lots of files in it:
285 #
286 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'many'));
287 self.oManyDir = oDir;
288 cManyFiles = self.oRandom.choice(self.oRngManyFiles);
289 for _ in xrange(cManyFiles):
290 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
291 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
292
293 #
294 # Generate a tree of files and dirs.
295 #
296 oDir = self.__createTestDir(self.oRoot, pathutils.joinEx(self.fDosStyle, sRoot, 'tree'));
297 uMaxDepth = self.oRandom.choice(self.oRngTreeDepth);
298 cMaxFiles = self.oRandom.choice(self.oRngTreeFiles);
299 cMaxDirs = self.oRandom.choice(self.oRngTreeDirs);
300 self.oTreeDir = oDir;
301 self.cTreeFiles = 0;
302 self.cTreeDirs = 0;
303 uDepth = 0;
304 while self.cTreeFiles < cMaxFiles and self.cTreeDirs < cMaxDirs:
305 iAction = self.oRandom.randrange(0, 2+1);
306 # 0: Add a file:
307 if iAction == 0 and self.cTreeFiles < cMaxFiles and len(oDir.sPath) < 230 - 2:
308 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
309 self.__createTestFile(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
310 self.cTreeFiles += 1;
311 # 1: Add a subdirector and descend into it:
312 elif iAction == 1 and self.cTreeDirs < cMaxDirs and uDepth < uMaxDepth and len(oDir.sPath) < 220:
313 sName = self.__createFilename(oDir, self.sFileCharset, self.sReservedTrailing);
314 oDir = self.__createTestDir(oDir, pathutils.joinEx(self.fDosStyle, oDir.sPath, sName));
315 self.cTreeDirs += 1;
316 uDepth += 1;
317 # 2: Ascend to parent dir:
318 elif iAction == 2 and uDepth > 0:
319 oDir = oDir.oParent;
320 uDepth -= 1;
321
322 return True;
323
324 def createTarball(self, sTarFileHst):
325 """
326 Creates a tarball on the host.
327 Returns success indicator.
328 """
329 reporter.log('Creating tarball "%s" with test files for the guest...' % (sTarFileHst,));
330
331 cchSkip = len(self.sBasePath) + 1;
332
333 # Open the tarball:
334 try:
335 oTarFile = tarfile.open(sTarFileHst, 'w:gz');
336 except:
337 return reporter.errorXcpt('Failed to open new tar file: %s' % (sTarFileHst,));
338
339 # Directories:
340 for oDir in self.aoDirs:
341 sPath = oDir.sPath[cchSkip:];
342 if self.fDosStyle:
343 sPath = sPath.replace('\\', '/');
344 oTarInfo = tarfile.TarInfo(sPath + '/');
345 oTarInfo.mode = 0o777;
346 oTarInfo.type = tarfile.DIRTYPE;
347 try:
348 oTarFile.addfile(oTarInfo);
349 except:
350 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oDir.sPath,));
351
352 # Files:
353 for oFile in self.aoFiles:
354 sPath = oFile.sPath[cchSkip:];
355 if self.fDosStyle:
356 sPath = sPath.replace('\\', '/');
357 oTarInfo = tarfile.TarInfo(sPath);
358 oTarInfo.size = len(oFile.abContent);
359 oFile.off = 0;
360 try:
361 oTarFile.addfile(oTarInfo, oFile);
362 except:
363 return reporter.errorXcpt('Failed adding directory tarfile: %s' % (oFile.sPath,));
364
365 # Complete the tarball.
366 try:
367 oTarFile.close();
368 except:
369 return reporter.errorXcpt('Error closing new tar file: %s' % (sTarFileHst,));
370 return True;
371
372 def writeToDisk(self, sAltBase = None):
373 """
374 Writes out the files to disk.
375 Returns True on success, False + error logging on failure.
376 """
377
378 # The directories:
379 for oDir in self.aoDirs:
380 sPath = oDir.sPath;
381 if sAltBase:
382 sPath = sAltBase + sPath[len(self.sBasePath):];
383
384 try:
385 os.mkdir(sPath, 0o770);
386 except:
387 return reporter.errorXcpt('mkdir(%s) failed' % (sPath,));
388
389 # The files:
390 for oFile in self.aoFiles:
391 sPath = oFile.sPath;
392 if sAltBase:
393 sPath = sAltBase + sPath[len(self.sBasePath):];
394
395 try:
396 oFile = open(sPath, 'wb');
397 except:
398 return reporter.errorXcpt('open(%s, "wb") failed' % (sPath,));
399 try:
400 if sys.version_info[0] < 3:
401 oFile.write(bytes(oFile.abContent));
402 else:
403 oFile.write(oFile.abContent);
404 except:
405 try: oFile.close();
406 except: pass;
407 return reporter.errorXcpt('%s: write(%s bytes) failed' % (sPath, oFile.cbContent,));
408 try:
409 oFile.close();
410 except:
411 return reporter.errorXcpt('%s: close() failed' % (sPath,));
412
413 return True;
414
415
416 def chooseRandomFile(self):
417 """
418 Returns a random file.
419 """
420 return self.aoFiles[self.oRandom.choice(xrange(len(self.aoFiles)))];
421
422 def chooseRandomDirFromTree(self, fLeaf = False, fNonEmpty = False):
423 """
424 Returns a random directory from the tree (self.oTreeDir).
425 """
426 while True:
427 oDir = self.aoDirs[self.oRandom.choice(xrange(len(self.aoDirs)))];
428 # Check fNonEmpty requirement:
429 if not fNonEmpty or oDir.aoChildren:
430 # Check leaf requirement:
431 if not fLeaf:
432 for oChild in oDir.aoChildren:
433 if isinstance(oChild, TestDir):
434 continue; # skip it.
435
436 # Return if in the tree:
437 oParent = oDir.oParent;
438 while oParent is not None:
439 if oParent is self.oTreeDir:
440 return oDir;
441 oParent = oParent.oParent;
442
443#
444# Unit testing.
445#
446
447# pylint: disable=missing-docstring
448# pylint: disable=undefined-variable
449class TestFileSetUnitTests(unittest.TestCase):
450 def testGeneral(self):
451 oSet = TestFileSet(False, '/tmp', 'unittest');
452 self.assertTrue(isinstance(oSet.chooseRandomDirFromTree(), TestDir));
453 self.assertTrue(isinstance(oSet.chooseRandomFile(), TestFile));
454
455
456if __name__ == '__main__':
457 unittest.main();
458 # not reached.
459
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