1# Copyright (c) 2013 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 5import logging 6import unittest 7import re 8import csv 9import common 10import os 11 12from itertools import imap 13from autotest_lib.server.cros import resource_monitor 14from autotest_lib.server.hosts import abstract_ssh 15from autotest_lib.server import utils 16 17class HostMock(abstract_ssh.AbstractSSHHost): 18 """Mocks a host object.""" 19 20 TOP_PID = '1234' 21 22 def _initialize(self, test_env): 23 self.top_is_running = False 24 25 # Keep track of whether the top raw output file exists on the system, 26 # and if it does, where it is. 27 self.top_output_file_path = None 28 29 # Keep track of whether the raw top output file is currently being 30 # written to by top. 31 self.top_output_file_is_open = False 32 self.test_env = test_env 33 34 35 def get_file(self, src, dest): 36 pass 37 38 39 def called_unsupported_command(self, command): 40 """Raises assertion error when called. 41 42 @param command string the unsupported command called. 43 44 """ 45 raise AssertionError( 46 "ResourceMonitor called unsupported command %s" % command) 47 48 49 def _process_top(self, cmd_args, cmd_line): 50 """Process top command. 51 52 @param cmd_args string_list of command line args. 53 @param cmd_line string the command to run. 54 55 """ 56 self.test_env.assertFalse(self.top_is_running, 57 msg="Top must not already be running.") 58 self.test_env.assertFalse(self.top_output_file_is_open, 59 msg="The top output file should not be being written " 60 "to before top is started") 61 self.test_env.assertIsNone(self.top_output_file_path, 62 msg="The top output file should not exist" 63 "before top is started") 64 try: 65 self.redirect_index = cmd_args.index(">") 66 self.top_output_file_path = cmd_args[self.redirect_index + 1] 67 except ValueError, IndexError: 68 self.called_unsupported_command(cmd_line) 69 70 self.top_is_running = True 71 self.top_output_file_is_open = True 72 73 return HostMock.TOP_PID 74 75 76 def _process_kill(self, cmd_args, cmd_line): 77 """Process kill command. 78 79 @param cmd_args string_list of command line args. 80 @param cmd_line string the command to run. 81 82 """ 83 try: 84 if cmd_args[1].startswith('-'): 85 pid_to_kill = cmd_args[2] 86 else: 87 pid_to_kill = cmd_args[1] 88 except IndexError: 89 self.called_unsupported_command(cmd_line) 90 91 self.test_env.assertEqual(pid_to_kill, HostMock.TOP_PID, 92 msg="Wrong pid (%r) killed . Top pid is %r." % (pid_to_kill, 93 HostMock.TOP_PID)) 94 self.test_env.assertTrue(self.top_is_running, 95 msg="Top must be running before we try to kill it") 96 97 self.top_is_running = False 98 self.top_output_file_is_open = False 99 100 101 def _process_rm(self, cmd_args, cmd_line): 102 """Process rm command. 103 104 @param cmd_args string list list of command line args. 105 @param cmd_line string the command to run. 106 107 """ 108 try: 109 if cmd_args[1].startswith('-'): 110 file_to_rm = cmd_args[2] 111 else: 112 file_to_rm = cmd_args[1] 113 except IndexError: 114 self.called_unsupported_command(cmd_line) 115 116 self.test_env.assertEqual(file_to_rm, self.top_output_file_path, 117 msg="Tried to remove file that is not the top output file.") 118 self.test_env.assertFalse(self.top_output_file_is_open, 119 msg="Tried to remove top output file while top is still " 120 "writing to it.") 121 self.test_env.assertFalse(self.top_is_running, 122 msg="Top was still running when we tried to remove" 123 "the top output file.") 124 self.test_env.assertIsNotNone(self.top_output_file_path) 125 126 self.top_output_file_path = None 127 128 129 def _run_single_cmd(self, cmd_line, *args, **kwargs): 130 """Run a single command on host. 131 132 @param cmd_line command to run on the host. 133 134 """ 135 # Make the input a little nicer. 136 cmd_line = cmd_line.strip() 137 cmd_line = re.sub(">", " > ", cmd_line) 138 139 cmd_args = re.split("\s+", cmd_line) 140 self.test_env.assertTrue(len(cmd_args) >= 1) 141 command = cmd_args[0] 142 if (command == "top"): 143 return self._process_top(cmd_args, cmd_line) 144 elif (command == "kill"): 145 return self._process_kill(cmd_args, cmd_line) 146 elif(command == "rm"): 147 return self._process_rm(cmd_args, cmd_line) 148 else: 149 logging.warning("Called unemulated command %r", cmd_line) 150 return None 151 152 153 def run(self, cmd_line, *args, **kwargs): 154 """Run command(s) on host. 155 156 @param cmd_line command to run on the host. 157 @return CmdResult object. 158 159 """ 160 cmds = re.split("&&", cmd_line) 161 for cmd in cmds: 162 self._run_single_cmd(cmd) 163 return utils.CmdResult(exit_status=0) 164 165 166 def run_background(self, cmd_line, *args, **kwargs): 167 """Run command in background on host. 168 169 @param cmd_line command to run on the host. 170 171 """ 172 return self._run_single_cmd(cmd_line, args, kwargs) 173 174 175 def is_monitoring(self): 176 """Return true iff host is currently running top and writing output 177 to a file. 178 """ 179 return self.top_is_running and self.top_output_file_is_open and ( 180 self.top_output_file_path is not None) 181 182 183 def monitoring_stopped(self): 184 """Return true iff host is not running top and all top output files are 185 closed. 186 """ 187 return not self.is_monitoring() 188 189 190class ResourceMonitorTest(unittest.TestCase): 191 """Tests the non-trivial functionality of ResourceMonitor.""" 192 193 def setUp(self): 194 self.topoutfile = '/tmp/resourcemonitorunittest-1234' 195 self.monitor_period = 1 196 self.rm_conf = resource_monitor.ResourceMonitorConfig( 197 monitor_period=self.monitor_period, 198 rawresult_output_filename=self.topoutfile) 199 self.host = HostMock(self) 200 201 202 def test_normal_operation(self): 203 """Checks that normal (i.e. no exceptions, etc.) execution works.""" 204 self.assertFalse(self.host.is_monitoring()) 205 with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: 206 self.assertFalse(self.host.is_monitoring()) 207 for i in range(3): 208 rm.start() 209 self.assertTrue(self.host.is_monitoring()) 210 rm.stop() 211 self.assertTrue(self.host.monitoring_stopped()) 212 self.assertTrue(self.host.monitoring_stopped()) 213 214 215 def test_forgot_to_stop_monitor(self): 216 """Checks that resource monitor is cleaned up even if user forgets to 217 explicitly stop it. 218 """ 219 self.assertFalse(self.host.is_monitoring()) 220 with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: 221 self.assertFalse(self.host.is_monitoring()) 222 rm.start() 223 self.assertTrue(self.host.is_monitoring()) 224 self.assertTrue(self.host.monitoring_stopped()) 225 226 227 def test_unexpected_interruption_while_monitoring(self): 228 """Checks that monitor is cleaned up upon unexpected interrupt.""" 229 self.assertFalse(self.host.is_monitoring()) 230 231 with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: 232 self.assertFalse(self.host.is_monitoring()) 233 rm.start() 234 self.assertTrue(self.host.is_monitoring()) 235 raise KeyboardInterrupt 236 237 self.assertTrue(self.host.monitoring_stopped()) 238 239 240class ResourceMonitorResultTest(unittest.TestCase): 241 """Functional tests for ResourceMonitorParsedResult.""" 242 243 def setUp(self): 244 self._res_dir = os.path.join( 245 os.path.dirname(os.path.realpath(__file__)), 246 'res_resource_monitor') 247 248 249 def run_with_test_data(self, testdata_file, testans_file): 250 """Parses testdata_file with the parses, and checks that results 251 are the same as those in testans_file. 252 253 @param testdata_file string filename containing top output to test. 254 @param testans_file string filename containing answers to the test. 255 256 """ 257 parsed_results = resource_monitor.ResourceMonitorParsedResult( 258 testdata_file) 259 with open(testans_file, "rb") as testans: 260 csvreader = csv.reader(testans) 261 columns = csvreader.next() 262 self.assertEqual(list(columns), 263 resource_monitor.ResourceMonitorParsedResult._columns) 264 utils_over_time = [] 265 for util_val in imap( 266 resource_monitor. 267 ResourceMonitorParsedResult.UtilValues._make, 268 csvreader): 269 utils_over_time.append(util_val) 270 self.assertEqual(utils_over_time, parsed_results._utils_over_time) 271 272 273 def test_full_data(self): 274 """General test with many possible changes to input.""" 275 self.run_with_test_data( 276 os.path.join(self._res_dir, 'top_test_data.txt'), 277 os.path.join(self._res_dir, 'top_test_data_ans.csv')) 278 279 280 def test_whitespace_ridden(self): 281 """Tests resilience to arbitrary whitespace characters between fields""" 282 self.run_with_test_data( 283 os.path.join(self._res_dir, 'top_whitespace_ridden.txt'), 284 os.path.join(self._res_dir, 'top_whitespace_ridden_ans.csv')) 285 286 287 def test_field_order_changed(self): 288 """Tests resilience to changes in the order of fields 289 (for e.g, if the Mem free/used fields change orders in the input). 290 """ 291 self.run_with_test_data( 292 os.path.join(self._res_dir, 'top_field_order_changed.txt'), 293 os.path.join(self._res_dir, 'top_field_order_changed_ans.csv')) 294 295 296if __name__ == '__main__': 297 unittest.main() 298