]> rtime.felk.cvut.cz Git - l4.git/blob - l4/pkg/plr/tools/timur/timur
update
[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 # (c) 2011-2013 Björn Döbel <doebel@os.inf.tu-dresden.de>,
6 #     economic rights: Technische Universität Dresden (Germany)
7 #
8 #  This file is part of TUD:OS and distributed under the terms of the
9 #  GNU General Public License 2.
10 #  Please see the COPYING-GPL-2 file for details.
11
12 import os, sys, sre
13 import subprocess       # launching qemu
14 import tempfile         # temporary output
15 import shlex            # proper splitting for qemu parameters
16 import time             # sleep
17 import filecmp          # comparing files
18 import argparse         # cmd line arguments
19
20 # XXX: config file
21 globalDirs = [ "/home/doebel/src/tudos/src/build/bin/x86_586/l4f",
22                "/home/doebel/src/tudos/src/build/bin/x86_586/",
23                "/home/doebel/src/tudos/src/kernel/fiasco/build",
24                "/home/doebel/src/tudos/src/l4/conf",
25              ]
26 globalQemu      = "qemu-system-i386"
27 globalFiascoOpt = "-serial_esc -jdb_cmd=JS"
28 globalModules   = ["sigma0"]
29
30
31 def usage():
32     print "  timur <path>"
33     print
34     print "    Runs all tests defined in subdirectories of <path>"
35
36
37 class FileFinder:
38     """
39     Maintain a set of directories and allow searching those dirs
40     for a given file name.
41     """
42     def __init__(self, directories):
43         self.dirs = directories
44
45     def find(self, name):
46         """
47         Find the given file name in the set of dirs.
48         """
49         for d in self.dirs:
50             try:
51                 f = file(d+"/"+name)
52                 f.close()
53                 return d + "/" + name
54             except:
55                 continue
56
57
58 class TempFile:
59     """
60     Wrapper for a temporary file
61     """
62     def __init__(self):
63         self.f = tempfile.NamedTemporaryFile()
64         self.name = self.f.name
65
66     def __repr__(self):
67         return "file:%s" % self.f.name
68
69     def readlines(self):
70         return self.f.readlines()
71
72
73 class StdioFile:
74     """
75     Class that behaves like a tempfile w.r.t. having a name property.
76     This class is passed to the command line generator when we want a
77     printable command line (e.g., one that you can copy & run from your shell)
78     """
79     def __init__(self):
80         self.name = "stdio"
81
82     def __repr__(self):
83         return "stdio"
84
85
86 class TestRunner:
87     def waitForTermination(self, test):
88         # monitor outfile for expected final line
89         while True:
90             f = test.tmpOut
91             l = f.readlines()
92             if len(l) > 1 and l[-1].startswith("Return reboots"):
93                 break
94             sys.stderr.write(".")
95             time.sleep(1)
96
97         sys.stderr.write(" ")
98
99
100     def adaptOutfile(self, test, filename=None):
101         """
102         Adapt the test output file to the expected format
103
104         Some output (e.g. bootstrap's) may differ between runs and
105         we want to ignore this. Therefore, we remove all output from
106         the test that comes before MOE's bootup message.
107
108         For simplicity, we generate a new file here that contains
109         the reduced output and is then used for comparison with the
110         expected output.
111         """
112
113         f1 = file(test.tmpOut.name)
114         if not filename:
115             f2 = tempfile.NamedTemporaryFile()
116         else:
117             f2 = file(filename, "w")
118
119         do_output = False  # tracks if a line should go to the new tmpfile
120
121         for l in f1.readlines():
122             if l.startswith("MOE: cmdline: "):
123                 do_output = True
124             if do_output:
125                 f2.write(l)
126
127         f1.close()
128         f2.flush() # makes sure that everything is written back before
129                    # we compare with the expected output
130         test.tmpOut = f2
131
132
133     def __init__(self, test):
134         # start the test in the background
135         pid = subprocess.Popen(shlex.split(test.commandLine()),
136                                stdout = file("/dev/null"),
137                                stderr = file("/dev/null"))
138
139         self.waitForTermination(test)
140
141         # SIGTERMinate the qemu process
142         pid.terminate()
143
144
145     def compare(self, test):
146         """
147         Evaluate test results by comparing output with expect file.
148         """
149         try:
150             expectFile = test.fullpath([f for f in test.caseFiles if sre.match(".*\.expect", f)][0])
151         except IndexError:
152             print "No .expect file found in %s. (Generate one with -e.)" % test.root
153             sys.exit(1)
154
155         if filecmp.cmp(expectFile, test.tmpOut.name):
156             print "OK"
157         else:
158             print "\033[31;1mFAIL\033[0m"
159             print "Output mismatch between %s and %s" % (expectFile, test.tmpOut.name)
160             subprocess.call(["diff", "-rub", expectFile, test.tmpOut.name])
161
162
163 class TestCase:
164     """
165     Representation of a single test case.
166     """
167     def get_modules(self):
168         """
169         Extract the list of modules for this setup from the .boot file
170         we stored in self.bootFile.
171         """
172         try:
173             f = file(self.bootFile)
174         except:
175             "Could not open %s for reading" % self.bootFile
176             sys.exit(1)
177
178         lines = [l.strip() for l in f.readlines()]
179         for l in lines:
180             v = l.split()
181             if v[0] == "roottask":
182                 self.roottask = v[1:]
183             elif v[0] == "module":
184                 self.modules += [v[1]]
185
186
187     def commandLine(self):
188         """
189         Generate the Qemu command line to launch this setup.
190         """
191         cmd = globalQemu + " -kernel " + self.finder.find("bootstrap")
192         cmd += " -append \"bootstrap -modaddr 0x01100000\" -initrd \""
193         cmd += self.fiasco + " " + globalFiascoOpt
194
195         for m in globalModules:
196             cmd += ","
197             cmd += self.finder.find(m)
198
199         cmd += "," + self.finder.find(self.roottask[0]) + " "
200         cmd += " ".join(self.roottask[1:])
201
202         for m in self.modules:
203             cmd += ","
204             cmd += self.finder.find(m)
205         cmd += "\""
206
207         cmd += " -serial %s" % self.tmpOut
208         cmd += " -no-reboot"
209
210         return cmd
211
212
213     def fullpath(self, filename):
214         return self.root + "/" + filename
215
216
217     def __init__(self, rootDir):
218         print "\033[32mTest case: \033[0m", os.path.basename(rootDir)
219         self.root      = rootDir
220         self.finder    = FileFinder([rootDir] + globalDirs)
221         self.caseFiles = os.listdir(rootDir)
222         self.modules   = []
223         self.fiasco    = self.finder.find("fiasco")
224         self.tmpOut    = TempFile()
225
226         try:
227             self.bootFile  = self.fullpath([f for f in self.caseFiles if sre.match(".*\.boot", f)][0])
228         except IndexError:
229             print "No .boot file found in %s." % rootDir
230             sys.exit(1)
231
232         self.get_modules()
233
234
235 def argsCheck(args):
236     try:
237         rootDir = args.rootDir
238         if not os.path.isdir(rootDir):
239             raise ValueError("Not a directory: %s" % rootDir)
240     except Exception as e:
241         print "[\033[31m%s\033[0m]\n" % e
242         sys.exit(1)
243
244
245 def run():
246     parser = argparse.ArgumentParser("./timur", epilog="Only one of the options {-c, -e, -t} can be run at a time.")
247     parser.add_argument("-d", "--directory", action="store", dest="rootDir",
248                         help="Directory containing test descriptions", required=True)
249     parser.add_argument("-c", "--cmdline", action="store_true", dest="cmdline",
250                         help="Print the command line that would be used to launch Qemu")
251     parser.add_argument("-e", "--expect",  action="store_true", dest="expect",
252                         help="Generate expected output")
253     parser.add_argument("-t", "--test",    action="store_true", dest="test",
254                         help="Run tests")
255
256     args = parser.parse_args()
257     argsCheck(args)
258
259     rootDir = args.rootDir # valid, because we'd have aborted otherwise
260
261     if args.cmdline:
262         for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
263             tc = TestCase(rootDir+"/"+f)
264             tc.tmpOut=StdioFile() # for the cmdline it is useful to print to stdio
265             print tc.commandLine()
266
267     elif args.expect:
268         for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
269             tc = TestCase(rootDir+"/"+f)
270             tr = TestRunner(tc)
271             fname = rootDir + "/" + f + "/"
272             fname += "%s.expect" % f
273             tr.adaptOutfile(tc, fname)
274             print
275
276     elif args.test:
277         for f in [d for d in os.listdir(rootDir) if os.path.isdir(rootDir+"/"+d) and d != ".svn" ]:
278             tc = TestCase(rootDir+"/"+f)
279             tr = TestRunner(tc)
280             tr.adaptOutfile(tc)
281             tr.compare(tc)
282
283
284 if __name__ == "__main__":
285     run()