3 # Timur (*T*est*I*ng *M*achinari*U*m for *R*omain)
6 import subprocess # launching qemu
7 import tempfile # temporary output
8 import shlex # proper splitting for qemu parameters
10 import filecmp # comparing files
11 import argparse # cmd line arguments
14 globalDirs = [ "/home/doebel/src/tudos/src/build/bin/x86_586/l4f",
15 "/home/doebel/src/tudos/src/build/bin/x86_586/",
16 "/home/doebel/src/tudos/src/kernel/fiasco/build",
17 "/home/doebel/src/tudos/src/l4/conf",
19 globalQemu = "qemu-system-i386"
20 globalFiascoOpt = "-serial_esc -jdb_cmd=JS"
21 globalModules = ["sigma0"]
27 print " Runs all tests defined in subdirectories of <path>"
32 Maintain a set of directories and allow searching those dirs
33 for a given file name.
35 def __init__(self, directories):
36 self.dirs = directories
40 Find the given file name in the set of dirs.
53 Wrapper for a temporary file
56 self.f = tempfile.NamedTemporaryFile()
57 self.name = self.f.name
60 return "file:%s" % self.f.name
63 return self.f.readlines()
68 Class that behaves like a tempfile w.r.t. having a name property.
69 This class is passed to the command line generator when we want a
70 printable command line (e.g., one that you can copy & run from your shell)
80 def waitForTermination(self, test):
81 # monitor outfile for expected final line
85 if len(l) > 1 and l[-1].startswith("Return reboots"):
93 def adaptOutfile(self, test, filename=None):
95 Adapt the test output file to the expected format
97 Some output (e.g. bootstrap's) may differ between runs and
98 we want to ignore this. Therefore, we remove all output from
99 the test that comes before MOE's bootup message.
101 For simplicity, we generate a new file here that contains
102 the reduced output and is then used for comparison with the
106 f1 = file(test.tmpOut.name)
108 f2 = tempfile.NamedTemporaryFile()
110 f2 = file(filename, "w")
112 do_output = False # tracks if a line should go to the new tmpfile
114 for l in f1.readlines():
115 if l.startswith("MOE: cmdline: "):
121 f2.flush() # makes sure that everything is written back before
122 # we compare with the expected output
126 def __init__(self, test):
127 # start the test in the background
128 pid = subprocess.Popen(shlex.split(test.commandLine()),
129 stdout = file("/dev/null"),
130 stderr = file("/dev/null"))
132 self.waitForTermination(test)
134 # SIGTERMinate the qemu process
138 def compare(self, test):
140 Evaluate test results by comparing output with expect file.
143 expectFile = test.fullpath([f for f in test.caseFiles if sre.match(".*\.expect", f)][0])
145 print "No .expect file found in %s. (Generate one with -e.)" % test.root
148 if filecmp.cmp(expectFile, test.tmpOut.name):
151 print "\033[31;1mFAIL\033[0m"
152 print "Output mismatch between %s and %s" % (expectFile, test.tmpOut.name)
153 subprocess.call(["diff", "-rub", expectFile, test.tmpOut.name])
158 Representation of a single test case.
160 def get_modules(self):
162 Extract the list of modules for this setup from the .boot file
163 we stored in self.bootFile.
166 f = file(self.bootFile)
168 "Could not open %s for reading" % self.bootFile
171 lines = [l.strip() for l in f.readlines()]
174 if v[0] == "roottask":
175 self.roottask = v[1:]
176 elif v[0] == "module":
177 self.modules += [v[1]]
180 def commandLine(self):
182 Generate the Qemu command line to launch this setup.
184 cmd = globalQemu + " -kernel " + self.finder.find("bootstrap")
185 cmd += " -append \"bootstrap -modaddr 0x01100000\" -initrd \""
186 cmd += self.fiasco + " " + globalFiascoOpt
188 for m in globalModules:
190 cmd += self.finder.find(m)
192 cmd += "," + self.finder.find(self.roottask[0]) + " "
193 cmd += " ".join(self.roottask[1:])
195 for m in self.modules:
197 cmd += self.finder.find(m)
200 cmd += " -serial %s" % self.tmpOut
206 def fullpath(self, filename):
207 return self.root + "/" + filename
210 def __init__(self, rootDir):
211 print "\033[32mTest case: \033[0m", os.path.basename(rootDir)
213 self.finder = FileFinder([rootDir] + globalDirs)
214 self.caseFiles = os.listdir(rootDir)
216 self.fiasco = self.finder.find("fiasco")
217 self.tmpOut = TempFile()
220 self.bootFile = self.fullpath([f for f in self.caseFiles if sre.match(".*\.boot", f)][0])
222 print "No .boot file found in %s." % rootDir
230 rootDir = args.rootDir
231 if not os.path.isdir(rootDir):
232 raise ValueError("Not a directory: %s" % rootDir)
233 except Exception as e:
234 print "[\033[31m%s\033[0m]\n" % e
239 parser = argparse.ArgumentParser("./timur", epilog="Only one of the options {-c, -e, -t} can be run at a time.")
240 parser.add_argument("-d", "--directory", action="store", dest="rootDir",
241 help="Directory containing test descriptions", required=True)
242 parser.add_argument("-c", "--cmdline", action="store_true", dest="cmdline",
243 help="Print the command line that would be used to launch Qemu")
244 parser.add_argument("-e", "--expect", action="store_true", dest="expect",
245 help="Generate expected output")
246 parser.add_argument("-t", "--test", action="store_true", dest="test",
249 args = parser.parse_args()
252 rootDir = args.rootDir # valid, because we'd have aborted otherwise
255 for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
256 tc = TestCase(rootDir+"/"+f)
257 tc.tmpOut=StdioFile() # for the cmdline it is useful to print to stdio
258 print tc.commandLine()
261 for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
262 tc = TestCase(rootDir+"/"+f)
264 fname = rootDir + "/" + f + "/"
265 fname += "%s.expect" % f
266 tr.adaptOutfile(tc, fname)
270 for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
271 tc = TestCase(rootDir+"/"+f)
277 if __name__ == "__main__":