1 """
2 Testing infrastructure - enforce runtime restrictions
3
4 Computer Systems Architecture Course
5 Assignment 1
6 March 2016
7 """
8
9 import os
10 import random
11 import sys
12 import time
13 import threading
14
15 from collections import namedtuple
16 from device import Device
17 from random import shuffle, uniform
18 from threading import current_thread, Event, Semaphore, Thread
19 from time import sleep
20 from traceback import print_stack
24 """
25 Class used to globally check accesses from device threads and verify result
26 correctness.
27 """
28
29
30
31 - def __init__(self, testcase, die_on_error=True):
32 """
33 !!! This is not part of the assignment API, do not call it !!!
34
35 Create a new supervisor for the test case.
36
37 @type testcase: testcase.TestCase
38 @param testcase: the test to supervise
39
40 @type die_on_error: Boolean
41 @param die_on_error: true for the test to be killed on first error
42 """
43 self.testcase = testcase
44 self.setup_event = Event()
45 self.start_event = Event()
46 self.devices = {}
47 self.threads = {}
48 self.waits = {}
49 self.die_on_error = die_on_error
50 self.banned_threads = set()
51 self.messages = []
52 self.scripts = {i : {j : [] for j in range(len(self.testcase.devices))} for i in range(self.testcase.duration + self.testcase.extra_duration)}
53 for script_td in self.testcase.scripts:
54 script = Script(self.testcase.script_sleep)
55 script._Script__set_supervisor(self)
56 self.scripts[script_td.time_point][script_td.device].append(ScriptRunData(script=script, location=script_td.location))
57
59 """
60 !!! This is not part of the assignment API, do not call it !!!
61
62 Registers a tester thread. This thread must not be used by devices for
63 any method execution.
64
65 @type thread: Thread
66 @param thread: the thread
67 """
68 if thread is None:
69 thread = current_thread()
70 self.banned_threads.add(thread)
71
73 """
74 !!! This is not part of the assignment API, do not call it !!!
75
76 Check device execution.
77
78 @type method: String
79 @param method: the name of the checked method
80 @type device: device.Device
81 @param device: the device which is checked
82 """
83 thread = current_thread()
84 if thread in self.banned_threads:
85
86 self.report("device '%s' is trying to execute %s on \
87 tester thread '%s'" % (str(device), method, thread.name))
88 return
89
91 """
92 !!! This is not part of the assignment API, do not call it !!!
93
94 Checks for correct device shutdown. There must not be any active
95 device threads.
96 """
97 for thrd in threading.enumerate():
98 if thrd in self.banned_threads:
99 continue
100 self.report("thread '%s' did not terminate"
101 % str(thrd.name), die_on_error=False)
102
104 """
105 !!! This is not part of the assignment API, do not call it !!!
106
107 Validates the current state of the data.
108 """
109 data = {}
110 for device_testdata in self.testcase.devices:
111 device_id = device_testdata.id
112 sensor_data = {loc : data for (loc, data) in device_testdata.locations}
113 data[device_id] = sensor_data
114
115 for tpt in range(crt_timepoint + 1):
116
117
118
119
120 for run_tpt in range(tpt + 1):
121 for (dev, scripts) in self.scripts[run_tpt].items():
122 for script_rd in scripts:
123 scrpt = script_rd.script
124 location = script_rd.location
125 neighbour_ids = self.__compute_neighbour_ids(dev, tpt)
126
127
128
129 script_data = []
130
131 for neigh in neighbour_ids:
132 if location in data[neigh]:
133 script_data.append(data[neigh][location])
134
135 if location in data[dev]:
136 script_data.append(data[dev][location])
137
138
139 if script_data != []:
140 result = scrpt._Script__update(script_data)
141
142
143 for neigh in neighbour_ids:
144 if location in data[neigh]:
145 data[neigh][location] = result
146
147 if location in data[dev]:
148 data[dev][location] = result
149
150 for (dev_id, sens_data) in data.items():
151 for (loc, ref_data) in sens_data.items():
152 calc_data = self.devices[dev_id].device.get_data(loc)
153 if ref_data != calc_data:
154 self.report("after timepoint %d, data for location %d on device %d differs: expected %f, found %f\n" % (crt_timepoint, loc, dev_id, ref_data, calc_data))
155
156 - def report(self, message, die_on_error=None):
157 """
158 !!! This is not part of the assignment API, do not call it !!!
159
160 Reports an error message. All messages are stored in a list for
161 retrieval at the end of the test.
162
163 @type message: String
164 @param message: the error message to log
165 """
166 if die_on_error is None:
167 die_on_error = self.die_on_error
168
169 if die_on_error:
170 print >> sys.stderr, message + "\n",
171 print_stack()
172 os.abort()
173
174 self.messages.append(message)
175
177 """
178 !!! This is not part of the assignment API, do not call it !!!
179
180 Returns the list of logged error messages.
181
182 @rtype: List of String
183 @return: the list of encountered errors
184 """
185 return self.messages
186
187 @staticmethod
191
192 @staticmethod
194 time.sleep(delay)
195 for script_rd in scripts:
196 if script_rd is scripts[-1]:
197 time.sleep(delay)
198 device.assign_script(script_rd.script, script_rd.location)
199 wait.release()
200
201 @staticmethod
206
215
217 """
218 !!! This is not part of the assignment API, do not call it !!!
219
220 Returns the list of neighbours for device_id for the current timepoint, and increments the
221 timepoint for the next invocation. This method is wrapped by Runtime.
222 WARNING: this method is not thread-safe and must not be called concurrently with
223 the same device_id.
224
225 @type device_id: Integer
226 @param device_id: the id of the device for which neighbours must be returned
227
228 @rtype: List of device.Device
229 @return: the list of neighbours for the current timepoint
230 """
231 self.start_event.wait()
232
233 device = self.devices[device_id].device
234 crt_timepoint = self.devices[device_id].crt_timepoint
235
236 self.check_execution("get_neighbours", device)
237
238 for dev_rd in self.devices.values():
239 if dev_rd.crt_timepoint < crt_timepoint or dev_rd.crt_timepoint > crt_timepoint + 1:
240 self.report("device %d called 'get_neighbours' without waiting for other devices\n" % device_id, True)
241
242 for thrd in self.threads[device_id]:
243 thrd.join()
244
245 if crt_timepoint == self.testcase.duration + self.testcase.extra_duration:
246 return None
247
248 if crt_timepoint > self.testcase.duration + self.testcase.extra_duration:
249 self.report("called 'get_neighbours' from device %d, on timepoint %d, after simulation end at %d\n" % (device_id, crt_timepoint, self.testcase.duration + self.testcase.extra_duration), True)
250
251 neighbour_ids = self.__compute_neighbour_ids(device_id, crt_timepoint)
252
253 neighbours = [self.devices[neigh_id].device for neigh_id in neighbour_ids]
254
255 scripts = self.scripts[crt_timepoint][device_id]
256
257 for scrpt in scripts:
258 scrpt.script._Script__set_device(device)
259
260 if self.testcase.parallel_script:
261 scripts = [[script] for script in scripts]
262 else:
263 scripts = [scripts]
264
265 self.waits[device_id] = Semaphore(0)
266
267 self.threads[device_id] = []
268 for scrpt in scripts:
269 delay_min = self.testcase.script_delay[0]
270 delay_max = self.testcase.script_delay[1]
271 thread = Thread(name="Sender",
272 target=Supervisor.__send_scripts,
273 args=(device, scrpt, random.uniform(delay_min, delay_max), self.waits[device_id]))
274 self.register_banned_thread(thread)
275 self.threads[device_id].append(thread)
276 thread.start()
277
278 thread = Thread(name="Ender",
279 target=Supervisor.__send_end,
280 args=(device, self.waits[device_id], len(scripts)))
281 self.threads[device_id].append(thread)
282 thread.start()
283
284 self.devices[device_id].crt_timepoint = crt_timepoint + 1
285
286 return neighbours
287
289 """
290 !!! This is not part of the assignment API, do not call it !!!
291
292 Runs the test case by creating the devices, unblocking the script assignment and waiting
293 for device termination.
294
295 @rtype: Integer
296 @return: the number of errors
297 """
298 for device_testdata in self.testcase.devices:
299 device_id = device_testdata.id
300 sensor_data = {loc : data for (loc, data) in device_testdata.locations}
301 supervisor = Runtime(self, device_id)
302 device = Device(device_id, sensor_data, supervisor)
303 self.devices[device_id] = DeviceRunData(device=device, crt_timepoint=0)
304 self.threads[device_id] = []
305
306 devices = [device_rd.device for device_rd in self.devices.values()]
307 setup_threads = []
308 for dev in devices:
309 neighbours = devices[:]
310 shuffle(neighbours)
311 setup_threads.append(Thread(name = "Setup",
312 target = Supervisor.__setup_devices,
313 args = (self.setup_event, dev, neighbours)))
314 setup_threads[-1].start()
315
316 self.setup_event.set()
317
318 for thread in setup_threads:
319 thread.join()
320
321 self.start_event.set()
322
323 for dev in self.devices.values():
324 dev.device.shutdown()
325
326 self.check_termination()
327
328 self.validate(self.testcase.duration + self.testcase.extra_duration - 1)
329
330 for msg in self.status():
331 print >> sys.stderr, msg
332
333 return len(self.status())
334
337 """
338 Object called by a device to get its neighbours at each timepoint. Each device will get a
339 different instance of this type which wraps the Supervisor object.
340 """
341 - def __init__(self, supervisor, device_id):
342 """
343 !!! This is not part of the assignment API, do not call it !!!
344
345 Creates a new Runtime object.
346
347 @type supervisor: Supervisor
348 @param supervisor: the supervisor object to wrap
349 @type device_id: Integer
350 @param device_id: the id of the device which uses this Runtime instance
351 """
352 self.supervisor = supervisor
353 self.device_id = device_id
354
356 """
357 Returns the list of neighbours for the current timepoint and increments the timepoint for
358 the next invocation.
359 WARNING: this method is not thread-safe, do not call it concurrently
360
361 @rtype: List of Device
362 @return: the list of current neighbours
363 """
364 return self.supervisor.get_neighbours(self.device_id)
365
368 - def __init__(self, device, crt_timepoint):
369 self.device = device
370 self.crt_timepoint = crt_timepoint
371
372
373 ScriptRunData = namedtuple("ScriptRunData", ['script', 'location'])
377 """
378 Encapsulates the algoritm for improving noisy measurement data.
379 """
380 - def __init__(self, delay=None, threshold=30):
381 """
382 !!! This is not part of the assignment API, do not call it !!!
383
384 Creates a new script.
385 """
386 self.__delay = delay
387 self.__threshold = threshold
388 self.__supervisor = None
389 self.__device = None
390
391 - def run(self, data):
392 """
393 Executes this script.
394
395 @type data: List of Integer
396 @param data: list containing data relevant for location, from one or multiple devices
397
398 @rtype: Integer
399 @return: improved measurement for the location
400 """
401 self.__supervisor.check_execution("run", self.__device)
402
403 if self.__delay is not None:
404 sleep(uniform(self.__delay[0], self.__delay[1]))
405
406 return self.__update(data)
407
411
414
417