blob: c6cf9d671c721f70895f5465c0884c057da93bf9 [file] [log] [blame]
Satya Tangiralabbcc1c92017-07-18 11:46:46 -07001# 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()