blob: 4bc317c9930f987d77b84ad3e6b4bad00ff1ad9b [file] [log] [blame]
Charlie Mooney3cca6ba2014-11-19 16:15:28 -08001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Charlie Mooney08661912015-04-16 09:20:34 -07005import os
6import sys
Charlie Mooneyc68f9c32015-04-16 15:23:22 -07007import webbrowser
Charlie Mooney08661912015-04-16 09:20:34 -07008
Charlie Mooney3cca6ba2014-11-19 16:15:28 -08009import colorama as color
10
Charlie Mooney08661912015-04-16 09:20:34 -070011import gesture_interpreter
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080012import tests
Charlie Mooneyc68f9c32015-04-16 15:23:22 -070013from webplot import Webplot
Charlie Mooney985cf402015-01-08 15:15:59 -080014from report import Report
Charlie Mooney08661912015-04-16 09:20:34 -070015from touchbot import Touchbot, DeviceSpec
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080016
17
18class TestSuite:
19 """ This class represents a collection of tests and is used to run them
20
21 A TestSuite object will set up a connection to the DUT, robot, etc, and
22 determine which tests can be run. Once the object is instantiated,
23 RunNextTestAndVariation() can be run repeatedly to work your way through
24 the entire suite.
25 """
Charlie Mooney1144c9b2015-04-16 13:59:56 -070026 FIRST_SNAPSHOT_TIMEOUT_S = 60
Charlie Mooney3b43cd72016-02-04 13:08:46 -080027 MAX_EMPTY_SNAPSHOTS = 20
Charlie Mooneyc68f9c32015-04-16 15:23:22 -070028 WEBPLOT_ADDR = '127.0.0.1'
29 WEBPLOT_PORT = 8080
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080030
Charlie Mooneyd5712b82015-02-23 12:06:59 -080031 def __init__(self, touch_dev, options, args):
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080032 color.init(autoreset=True)
33
34 self.options = options
Charlie Mooneyd5712b82015-02-23 12:06:59 -080035 self.touch_dev = touch_dev
Charlie Mooneyaf9d5122014-12-04 15:15:52 -080036 tests.validator.BaseValidator._device = self.touch_dev
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080037
Charlie Mooneydaaaa242015-02-03 14:20:23 -080038 # Connect to the function generator if the operator says they have one
39 self.fn_generator = None
40 if options.has_fn_gen:
41 self.fn_generator = tests.noise.HP33120A()
42 if not self.fn_generator.IsValid():
43 self.fn_generator = None
44 options.has_fn_gen = False
Charlie Mooney08661912015-04-16 09:20:34 -070045 print color.Fore.RED + 'Error: Unable to connect to function generator!'
46 sys.exit(1)
47
48 # Connect to the robot if the operator says they have it
49 self.robot = self.device_spec = None
50 if options.has_robot:
51 self.robot = Touchbot()
52 if not self.robot.comm:
53 print color.Fore.RED + 'Error: Unable to connect to robot!'
54 sys.exit(1)
55 # Load a copy of the device spec if it already exists or calibrate now
56 device_spec_filename = './%s.p' % options.name
57 if os.path.isfile(device_spec_filename):
58 self.device_spec = DeviceSpec.LoadFromDisk(device_spec_filename)
59 else:
60 print color.Fore.YELLOW + 'No spec found (%s)' % device_spec_filename
61 print (color.Fore.YELLOW + 'Please follow these instructions to '
62 'calibrate for your "%s" device' % options.name)
63 self.device_spec = self.robot.CalibrateDevice(device_spec_filename)
Charlie Mooneydaaaa242015-02-03 14:20:23 -080064
Charlie Mooneyc68f9c32015-04-16 15:23:22 -070065 # Start up the plotter
66 self.plotter = Webplot(TestSuite.WEBPLOT_ADDR, TestSuite.WEBPLOT_PORT,
67 self.touch_dev)
68 self.plotter.start()
69 print color.Fore.YELLOW + 'Plots visible at "%s"' % self.plotter.Url()
70 webbrowser.open(self.plotter.Url())
71
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080072 # Compute the list of tests to run
73 self.tests = tests.generate_test_list(options)
Charlie Mooney73051762015-02-06 11:52:38 -080074 if not self.tests:
75 print color.Fore.RED + 'Warning: No tests selected!'
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080076 self.curr_test = 0
77 self.curr_variation = 0
Charlie Mooneyaf9d5122014-12-04 15:15:52 -080078 self.curr_iteration = 1
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080079
Charlie Mooney985cf402015-01-08 15:15:59 -080080 # Create a new Report that will store all the test Results
Charlie Mooney4291c4e2015-06-15 10:15:30 -070081 self.report = Report(title=self.options.title,
82 test_version=self.options.test_version)
Charlie Mooney8f07d832015-06-15 15:29:47 -070083 self.report.warnings = self.touch_dev.warnings
Charlie Mooney985cf402015-01-08 15:15:59 -080084
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080085 def RunNextTestAndVariation(self):
86 """ Run the next test.
87
88 This function runs the next test/variation combination in the test suite
89 and advances the internal state to the next one automatically. When
90 finished, this function return True if there are more tests to run, and
91 False if the whole test suite is done.
92
93 After a TestSuite is instantiated, this function should be called
94 repeatedly until it returns False to go through all tests, variations,
95 and iterations.
96 """
Charlie Mooney73051762015-02-06 11:52:38 -080097 if self.curr_test >= len(self.tests):
98 return False
Charlie Mooney3cca6ba2014-11-19 16:15:28 -080099 test = self.tests[self.curr_test]
100
101 # Print the header for this new test and variation
102 prompt = test.PromptForVariation(self.curr_variation)
103 print color.Fore.WHITE + '-' * 80
104 print color.Fore.BLUE + test.name
105 print color.Fore.GREEN + prompt
106
Charlie Mooneydaaaa242015-02-03 14:20:23 -0800107 # Start the function generator (if applicable)
108 if self.fn_generator:
109 waveform = test.WaveformForVariation(self.curr_variation)
110 if waveform:
111 self.fn_generator.GenerateFunction(*waveform)
112 else:
113 self.fn_generator.Off()
114
Charlie Mooney08661912015-04-16 09:20:34 -0700115 # Start a new thread to perform the robot gesture (if applicable)
116 robot_thread = None
117 if self.robot:
118 robot_thread = gesture_interpreter.PerformCorrespondingGesture(
119 test, self.curr_variation,
120 self.robot, self.device_spec)
121 if robot_thread:
122 robot_thread.start()
Charlie Mooneyca3fc202015-05-21 11:16:53 -0700123 else:
124 next_test, next_var = self._Advance()
125 return (next_test is not None)
Charlie Mooney08661912015-04-16 09:20:34 -0700126
Charlie Mooney389e1f32015-03-06 13:33:20 -0800127 # Consume any stray events that happened since the last gesture
128 print 'Waiting for all contacts to leave before recording the gesture...'
129 self.touch_dev.FlushSnapshotBuffer()
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700130 self.plotter.Clear()
Charlie Mooney3cca6ba2014-11-19 16:15:28 -0800131
132 # Wait a long time for the first event, then have a much shorter
133 # timeout on subsequent incoming events
Charlie Mooney389e1f32015-03-06 13:33:20 -0800134 snapshots = []
Charlie Mooney389e1f32015-03-06 13:33:20 -0800135 print 'Waiting for 1st snapshot...',
Charlie Mooney1144c9b2015-04-16 13:59:56 -0700136 snapshot = None
Charlie Mooney9b7f41b2015-12-09 09:45:36 -0800137 finger_count = 0
Charlie Mooney26722492016-01-05 10:12:29 -0800138 log = open('debug.log', 'w')
Charlie Mooney3b43cd72016-02-04 13:08:46 -0800139 num_empty_snapshots = 0
Charlie Mooney1144c9b2015-04-16 13:59:56 -0700140 if not robot_thread or not robot_thread.isAlive():
141 snapshot = self.touch_dev.NextSnapshot(TestSuite.FIRST_SNAPSHOT_TIMEOUT_S)
Charlie Mooney26722492016-01-05 10:12:29 -0800142 snapshots.append(snapshot)
143 log.write('\n'.join([str(r) for r in snapshot.raw_events]) + '\n')
144 log.flush()
145 finger_count = len(snapshot.fingers)
Charlie Mooney1144c9b2015-04-16 13:59:56 -0700146 if not snapshot:
147 print '\rNo MT snapshots collected before timeout!'
Charlie Mooney3b43cd72016-02-04 13:08:46 -0800148 while ((snapshot and num_empty_snapshots < TestSuite.MAX_EMPTY_SNAPSHOTS) or
149 (robot_thread and robot_thread.isAlive()) or
Charlie Mooney9b7f41b2015-12-09 09:45:36 -0800150 finger_count > 0):
Charlie Mooney08661912015-04-16 09:20:34 -0700151 if snapshot:
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700152 self.plotter.AddSnapshot(snapshot)
Charlie Mooney08661912015-04-16 09:20:34 -0700153 snapshots.append(snapshot)
Charlie Mooney26722492016-01-05 10:12:29 -0800154 log.write('\n'.join([str(r) for r in snapshot.raw_events]) + '\n')
155 log.flush()
Charlie Mooney9b7f41b2015-12-09 09:45:36 -0800156 finger_count = len(snapshot.fingers)
Charlie Mooney3b43cd72016-02-04 13:08:46 -0800157 print ('\rCollected %d MT snapshots (%d fingers on pad)' %
158 (len(snapshots), finger_count)),
159
160 if len(snapshot.fingers) == 0:
161 num_empty_snapshots += 1
162 else:
163 num_empty_snapshots = 0
Charlie Mooney389e1f32015-03-06 13:33:20 -0800164 snapshot = self.touch_dev.NextSnapshot(test.timeout)
Charlie Mooney26722492016-01-05 10:12:29 -0800165 log.close()
Charlie Mooney3cca6ba2014-11-19 16:15:28 -0800166 print
Charlie Mooney173396c2015-03-19 15:25:41 -0700167
Charlie Mooney08661912015-04-16 09:20:34 -0700168 # Cleanup the robot gesture thread, if it existed
169 if robot_thread:
170 robot_thread.join()
171
Charlie Mooney76c871f2015-05-21 12:01:52 -0700172 # Run the validators on these events
173 results = test.RunAllValidators(snapshots)
174 for result in results:
175 print result
176
Charlie Mooney173396c2015-03-19 15:25:41 -0700177 # Prompt to see if the gesture was performed acceptibly by the user before
178 # continuing. If the user is unsatisfied abort before advancing on to the
179 # next test/variation, allowing the test suite to retry this variation.
180 if not self.options.has_robot:
181 CONFIRMATION_PROMPT = 'Accept Gesture? ([y]/n) '
182 yorn = raw_input(CONFIRMATION_PROMPT).lower()
183 while yorn not in ['y', 'n', 'yes', 'no', '']:
184 print color.Fore.RED + 'Error: please enter "y" or "n" only'
185 yorn = raw_input(CONFIRMATION_PROMPT).lower()
Charlie Mooney173396c2015-03-19 15:25:41 -0700186 if yorn in ['n', 'no']:
187 print color.Fore.RED + 'Operator rejected the gesture. Please retry...'
188 return True
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700189
Charlie Mooney76c871f2015-05-21 12:01:52 -0700190 # Save the image displayed by the plotter
Charlie Mooney990d0ea2015-07-28 09:57:59 -0700191 plot_image_png = self.plotter.Save()
Charlie Mooney3cca6ba2014-11-19 16:15:28 -0800192
Charlie Mooney5e9a10f2015-01-13 14:54:27 -0800193 # Bundle the Validator results with some details of which gesture was used
194 # during the test for easier debugging.
Charlie Mooney2c270d12015-04-16 13:38:03 -0700195 test_result = tests.TestResult(test.variations[self.curr_variation],
196 results, prompt, plot_image_png)
Charlie Mooney5e9a10f2015-01-13 14:54:27 -0800197
Charlie Mooney985cf402015-01-08 15:15:59 -0800198 # Add the results into our report (And have it print them to stdout, too)
Charlie Mooney76c871f2015-05-21 12:01:52 -0700199 self.report.AddTestResult(test_result)
Charlie Mooney3cca6ba2014-11-19 16:15:28 -0800200
201 # Advance the test suite to the next test and variation and return an
202 # indicator as to whether this was the last thing to do or not.
203 next_test, next_var = self._Advance()
204 return (next_test is not None)
205
Charlie Mooneyc68f9c32015-04-16 15:23:22 -0700206 def StopPlotter(self):
207 self.plotter.Quit()
208
Charlie Mooney3cca6ba2014-11-19 16:15:28 -0800209 def _Advance(self):
210 """ Move on to the next test/variation pair
211
212 This function increments all the interal counters, according to the
213 number of tests, their variations, and the selected number of iterations
214 and returns the test object and the variation number that should be
215 done next.
216
217 When the TestSuite is complete, this function will return None, None
218 otherwise it will return the next Test object and the variation number
219 the test suite is on.
220 """
221 if self.curr_test >= len(self.tests):
222 return None, None
223 test = self.tests[self.curr_test]
224
225 if self.curr_variation >= len(test.variations):
226 self.curr_test += 1
227 self.curr_variation = 0
228 self.curr_iteration = 0
229 return self._Advance()
230
231 if self.curr_iteration >= self.options.num_iterations:
232 self.curr_variation += 1
233 self.curr_iteration = 0
234 return self._Advance()
235
236 self.curr_iteration += 1
237 return test, self.curr_variation