]> rtime.felk.cvut.cz Git - l4.git/blob - l4/pkg/plr/tools/timur/timur
a9041204ff62c5af7254b8f6731edf854d5c8904
[l4.git] / l4 / pkg / plr / tools / timur / timur
1 #!/usr/bin/python
2
3 # Timur (*T*est*I*ng *M*achinari*U*m for *R*omain)
4
5 import os, sys, sre
6 import subprocess       # launching qemu
7 import tempfile         # temporary output
8 import shlex            # proper splitting for qemu parameters
9 import time             # sleep
10 import filecmp          # comparing files
11 import argparse         # cmd line arguments
12
13 # XXX: config file
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",
18              ]
19 globalQemu      = "qemu-system-i386"
20 globalFiascoOpt = "-serial_esc -jdb_cmd=JS"
21 globalModules   = ["sigma0"]
22
23
24 def usage():
25     print "  timur <path>"
26     print
27     print "    Runs all tests defined in subdirectories of <path>"
28
29
30 class FileFinder:
31     """
32     Maintain a set of directories and allow searching those dirs
33     for a given file name.
34     """
35     def __init__(self, directories):
36         self.dirs = directories
37
38     def find(self, name):
39         """
40         Find the given file name in the set of dirs.
41         """
42         for d in self.dirs:
43             try:
44                 f = file(d+"/"+name)
45                 f.close()
46                 return d + "/" + name
47             except:
48                 continue
49
50
51 class TempFile:
52     """
53     Wrapper for a temporary file
54     """
55     def __init__(self):
56         self.f = tempfile.NamedTemporaryFile()
57         self.name = self.f.name
58
59     def __repr__(self):
60         return "file:%s" % self.f.name
61
62     def readlines(self):
63         return self.f.readlines()
64
65
66 class StdioFile:
67     """
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)
71     """
72     def __init__(self):
73         self.name = "stdio"
74
75     def __repr__(self):
76         return "stdio"
77
78
79 class TestRunner:
80     def waitForTermination(self, test):
81         # monitor outfile for expected final line
82         while True:
83             f = test.tmpOut
84             l = f.readlines()
85             if len(l) > 1 and l[-1].startswith("Return reboots"):
86                 break
87             sys.stderr.write(".")
88             time.sleep(1)
89
90         sys.stderr.write(" ")
91
92
93     def adaptOutfile(self, test, filename=None):
94         """
95         Adapt the test output file to the expected format
96
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.
100
101         For simplicity, we generate a new file here that contains
102         the reduced output and is then used for comparison with the
103         expected output.
104         """
105
106         f1 = file(test.tmpOut.name)
107         if not filename:
108             f2 = tempfile.NamedTemporaryFile()
109         else:
110             f2 = file(filename, "w")
111
112         do_output = False  # tracks if a line should go to the new tmpfile
113
114         for l in f1.readlines():
115             if l.startswith("MOE: cmdline: "):
116                 do_output = True
117             if do_output:
118                 f2.write(l)
119
120         f1.close()
121         f2.flush() # makes sure that everything is written back before
122                    # we compare with the expected output
123         test.tmpOut = f2
124
125
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"))
131
132         self.waitForTermination(test)
133
134         # SIGTERMinate the qemu process
135         pid.terminate()
136
137
138     def compare(self, test):
139         """
140         Evaluate test results by comparing output with expect file.
141         """
142         try:
143             expectFile = test.fullpath([f for f in test.caseFiles if sre.match(".*\.expect", f)][0])
144         except IndexError:
145             print "No .expect file found in %s. (Generate one with -e.)" % test.root
146             sys.exit(1)
147
148         if filecmp.cmp(expectFile, test.tmpOut.name):
149             print "OK"
150         else:
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])
154
155
156 class TestCase:
157     """
158     Representation of a single test case.
159     """
160     def get_modules(self):
161         """
162         Extract the list of modules for this setup from the .boot file
163         we stored in self.bootFile.
164         """
165         try:
166             f = file(self.bootFile)
167         except:
168             "Could not open %s for reading" % self.bootFile
169             sys.exit(1)
170
171         lines = [l.strip() for l in f.readlines()]
172         for l in lines:
173             v = l.split()
174             if v[0] == "roottask":
175                 self.roottask = v[1:]
176             elif v[0] == "module":
177                 self.modules += [v[1]]
178
179
180     def commandLine(self):
181         """
182         Generate the Qemu command line to launch this setup.
183         """
184         cmd = globalQemu + " -kernel " + self.finder.find("bootstrap")
185         cmd += " -append \"bootstrap -modaddr 0x01100000\" -initrd \""
186         cmd += self.fiasco + " " + globalFiascoOpt
187
188         for m in globalModules:
189             cmd += ","
190             cmd += self.finder.find(m)
191
192         cmd += "," + self.finder.find(self.roottask[0]) + " "
193         cmd += " ".join(self.roottask[1:])
194
195         for m in self.modules:
196             cmd += ","
197             cmd += self.finder.find(m)
198         cmd += "\""
199
200         cmd += " -serial %s" % self.tmpOut
201         cmd += " -no-reboot"
202
203         return cmd
204
205
206     def fullpath(self, filename):
207         return self.root + "/" + filename
208
209
210     def __init__(self, rootDir):
211         print "\033[32mTest case: \033[0m", os.path.basename(rootDir)
212         self.root      = rootDir
213         self.finder    = FileFinder([rootDir] + globalDirs)
214         self.caseFiles = os.listdir(rootDir)
215         self.modules   = []
216         self.fiasco    = self.finder.find("fiasco")
217         self.tmpOut    = TempFile()
218
219         try:
220             self.bootFile  = self.fullpath([f for f in self.caseFiles if sre.match(".*\.boot", f)][0])
221         except IndexError:
222             print "No .boot file found in %s." % rootDir
223             sys.exit(1)
224
225         self.get_modules()
226
227
228 def argsCheck(args):
229     try:
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
235         sys.exit(1)
236
237
238 def run():
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",
247                         help="Run tests")
248
249     args = parser.parse_args()
250     argsCheck(args)
251
252     rootDir = args.rootDir # valid, because we'd have aborted otherwise
253
254     if args.cmdline:
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()
259
260     elif args.expect:
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)
263             tr = TestRunner(tc)
264             fname = rootDir + "/" + f + "/"
265             fname += "%s.expect" % f
266             tr.adaptOutfile(tc, fname)
267             print
268
269     elif args.test:
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)
272             tr = TestRunner(tc)
273             tr.adaptOutfile(tc)
274             tr.compare(tc)
275
276
277 if __name__ == "__main__":
278     run()