095c7d684c3399703a789d669172a3a6e567f0aa
[barrelfish] / tools / harness / tests / common.py
1 ##########################################################################
2 # Copyright (c) 2009, 2010, ETH Zurich.
3 # All rights reserved.
4 #
5 # This file is distributed under the terms in the attached LICENSE file.
6 # If you do not find this file, copies can be found by writing to:
7 # ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8 ##########################################################################
9
10 import os, shutil, select, datetime, fdpexpect, pexpect, tempfile
11 import barrelfish, debug, results
12 from tests import Test
13 from results import PassFailResult
14
15 DEFAULT_TEST_TIMEOUT = datetime.timedelta(seconds=360)
16 DEFAULT_BOOT_TIMEOUT = datetime.timedelta(seconds=240)
17 TEST_TIMEOUT_LINE = '[Error: test timed out]\n'
18 BOOT_TIMEOUT_LINE_RETRY = '[Error: boot timed out, retrying...]\n'
19 BOOT_TIMEOUT_LINE_FAIL = '[Error: boot timed out, retry limit reached]\n'
20 MAX_BOOT_ATTEMPTS = 3 # maximum number of times to attempt booting before giving up
21
22 class TimeoutError(Exception):
23     def __init__(self, when=None):
24         self.when = when
25     def __str__(self):
26         return 'timeout occurred%s' % (': ' + self.when if self.when else '')
27
28 class TestCommon(Test):
29     name = None # should be overridden
30
31     def __init__(self, options):
32         super(TestCommon, self).__init__(options)
33         self.timeout = None # current timeout (as absolute datetime)
34         self.test_timeout_delta = DEFAULT_TEST_TIMEOUT # timeout delta for next test
35         self.boot_phase = True # are we waiting for machine boot or test output?
36         self.boot_attempts = 0 # how many times did we try to boot?
37
38     def _setup_harness_dir(self, build, machine):
39         dest_dir = machine.get_tftp_dir()
40         debug.verbose('installing to %s' % dest_dir)
41         if os.access(dest_dir, os.F_OK):
42             debug.verbose('clearing out %s' % dest_dir)
43             for e in os.listdir(dest_dir):
44                 p = os.path.join(dest_dir, e)
45                 if os.path.isdir(p):
46                     shutil.rmtree(p, ignore_errors=True)
47                 elif not e.startswith('.nfs'):
48                     os.unlink(p)
49         else:
50             debug.verbose('creating %s' % dest_dir)
51             os.makedirs(dest_dir)
52         return dest_dir
53
54     def setup(self, build, machine, testdir):
55         # build the default set of targets
56         targets = self.get_build_targets(build, machine)
57         # set custom test timeout if machine specifies one
58         test_timeout_secs = machine.get_test_timeout()
59         if not test_timeout_secs:
60             test_timeout_secs = DEFAULT_TEST_TIMEOUT
61         else:
62             test_timeout_secs = datetime.timedelta(seconds=test_timeout_secs)
63         self.test_timeout_delta = test_timeout_secs
64         build.build(targets)
65
66         # lock the machine
67         machine.lock()
68         machine.setup()
69
70         # setup the harness dir and install there
71         dest_dir = self._setup_harness_dir(build, machine)
72         build.install(targets, dest_dir)
73
74     def get_modules(self, build, machine):
75         return barrelfish.default_bootmodules(build, machine)
76
77     def get_build_targets(self, build, machine):
78         return self.get_modules(build, machine).get_build_targets()
79
80     def set_timeout(self, delta=DEFAULT_TEST_TIMEOUT):
81         self.test_timeout_delta = delta
82         if not self.boot_phase:
83             if delta:
84                 debug.verbose('setting timeout for %s' % delta)
85                 self.timeout = datetime.datetime.now() + delta
86             else:
87                 debug.verbose('cancelling timeout')
88                 self.timeout = None
89
90     def boot(self, machine, modules):
91         machine.set_bootmodules(modules)
92         self.boot_attempts = 0
93         self.reboot(machine)
94
95     def reboot(self, machine):
96         # retry a failed boot, without changing the modules
97         machine.reboot()
98         self.boot_phase = True
99         self.boot_attempts += 1
100         timeout_secs = machine.get_boot_timeout()
101         if timeout_secs:
102             self.timeout = (datetime.datetime.now()
103                                  + datetime.timedelta(seconds=timeout_secs))
104         else:
105             self.timeout = datetime.datetime.now() + DEFAULT_BOOT_TIMEOUT
106
107     def get_finish_string(self):
108         # default output from a test program when it completes
109         # should be overridden by subclasses
110         return "client done"
111
112     def is_finished(self, line):
113         return line.startswith(self.get_finish_string())
114
115     def is_booted(self, line):
116         # early boot output from Barrelfish kernel
117         return line.startswith("Barrelfish CPU driver starting")
118
119     def process_line(self, rawline):
120         """Can be used by subclasses to hook into the raw output stream."""
121         pass
122
123     def _readline(self, fh):
124         # standard blocking readline if no timeout is set
125         if not self.timeout:
126             return fh.readline()
127
128         line = ''
129         while not line.endswith('\n'):
130             # wait until there is something to read, with a timeout
131             (readlist, _, _) = select_timeout(self.timeout, [fh])
132             if not readlist:
133                 # if we have some partial data, return that first!
134                 # we'll be called again, and the next time can raise the error
135                 if line:
136                     return line
137                 elif self.boot_phase:
138                     raise TimeoutError('waiting for victim to boot')
139                 else:
140                     raise TimeoutError('waiting for test output from victim')
141             # read a single character, to avoid blocking
142             # FIXME: there must be a better way to do nonblocking IO!
143             c = fh.read(1)
144             if c == '': # should never see EOF
145                 raise Exception('read from sub-process returned EOF')
146             line += c
147         return line
148
149     def collect_data(self, machine):
150         fh = machine.get_output()
151         while True:
152             try:
153                 line = self._readline(fh)
154             except TimeoutError as e:
155                 if self.boot_phase:
156                     if self.boot_attempts < MAX_BOOT_ATTEMPTS:
157                         yield BOOT_TIMEOUT_LINE_RETRY
158                         self.reboot(machine)
159                         continue
160                     else:
161                         yield BOOT_TIMEOUT_LINE_FAIL
162                 else:
163                     yield TEST_TIMEOUT_LINE
164                 debug.verbose("timeout encountered in collect_data");
165                 raise e
166
167             yield line
168
169             if not self.boot_phase:
170                 self.process_line(line)
171                 if self.is_finished(line):
172                     debug.verbose("is_finished returned true for line %s" % line)
173                     break
174             elif self.is_booted(line):
175                 self.boot_phase = False
176                 self.set_timeout(self.test_timeout_delta)
177                 self.process_line(line)
178
179     def run(self, build, machine, testdir):
180         modules = self.get_modules(build, machine)
181         self.boot(machine, modules)
182         return self.collect_data(machine)
183
184     def cleanup(self, machine):
185         tftp_dir = machine.get_tftp_dir()
186         machine.shutdown()
187         machine.unlock()
188         debug.verbose('removing %s' % tftp_dir)
189         shutil.rmtree(tftp_dir, ignore_errors=True)
190
191
192 class InteractiveTest(TestCommon):
193     """
194     A interactive test class that allows a test-case to interact with
195     the fish shell. Sub-classes should implement the interact method.
196
197     As an example, have a look at coreboottest.py.
198     """
199
200     def get_modules(self, build, machine):
201         modules = super(InteractiveTest, self).get_modules(build, machine)
202         # Load the console
203         serialargs = []
204         if machine.get_machine_name() == "tomme1" or \
205            machine.get_machine_name() == "tomme2":
206             serialargs = ["portbase=0x2f8", "irq=0x3"]
207         modules.add_module("serial", args=serialargs)
208
209         modules.add_module("fish", args=["nospawn"])
210         modules.add_module("angler", args=['serial0.terminal xterm'])
211         return modules
212
213     def wait_for_prompt(self):
214         self.console.expect(">")
215
216     def wait_for_fish(self):
217         debug.verbose("Waiting for fish.")
218         self.console.expect("fish v0.2 -- pleased to meet you!",
219                 timeout=self.test_timeout)
220         self.wait_for_prompt()
221
222     def interact(self):
223         # Implement interaction with console
224         pass
225
226     def set_timeouts(self, machine):
227         self.boot_timeout = machine.get_boot_timeout()
228         if not self.boot_timeout:
229             self.boot_timeout = DEFAULT_BOOT_TIMEOUT.seconds
230         self.test_timeout = machine.get_test_timeout()
231         if not self.test_timeout:
232             self.test_timeout = DEFAULT_TEST_TIMEOUT.seconds
233
234     def collect_data(self, machine):
235         fh = machine.get_output()
236
237
238         self.console = fdpexpect.fdspawn(fh, timeout=self.test_timeout)
239         self.console.logfile = tempfile.NamedTemporaryFile()
240
241         while self.boot_attempts < MAX_BOOT_ATTEMPTS:
242             index = self.console.expect(["Barrelfish CPU driver starting", 
243                                  pexpect.TIMEOUT, pexpect.EOF],
244                                  timeout=self.boot_timeout)
245             if index == 0:
246                 self.boot_phase = False
247                 break
248             if index == 1:
249                 self.console.logfile.write(BOOT_TIMEOUT_LINE_RETRY)
250                 self.reboot(machine)
251             if index == 2:
252                 self.console.logfile.write(BOOT_TIMEOUT_LINE_FAIL)
253
254         if not self.boot_phase:
255             machine.force_write(self.console)
256             try:
257                 self.interact()
258             except pexpect.TIMEOUT, e:
259                 self.console.logfile.seek(0)
260                 print "Interaction timed out:"
261                 print self.console.logfile.readlines()
262                 raise e
263             except OSError, e:
264                 self.console.logfile.seek(0)
265                 print self.console.logfile.readlines()
266                 raise e
267
268         self.console.logfile.seek(0)
269         return self.console.logfile.readlines()
270
271     def run(self, build, machine, testdir):
272         modules = self.get_modules(build, machine)
273         self.set_timeouts(machine)
274         self.boot(machine, modules)
275         return self.collect_data(machine)
276
277     def process_data(self, testdir, rawiter):
278         passed = True
279         for line in rawiter:
280             if "user page fault in" in line:
281                 passed = False
282                 break
283             if "user trap #" in line:
284                 passed = False
285                 break
286             if "PANIC! kernel assertion" in line:
287                 passed = False
288         return PassFailResult(passed)
289
290
291 # utility function used by other tests
292 def select_timeout(timeout, rlist=[], wlist=[], xlist=[]):
293     """run select.select, with a timeout specified as a datetime object"""
294     delta = timeout - datetime.datetime.now()
295     if delta.days >= 0:
296         assert(delta.days == 0) # unimplemented, and insane!
297         secs = delta.seconds + delta.microseconds / 1000000.0
298         assert(secs > 0)
299         return select.select(rlist, wlist, xlist, secs)
300     else: # already timed out
301         return ([], [], [])