# Lint as: python2, python3 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import absolute_import from __future__ import division from __future__ import print_function import logging import unittest import re import csv import common import os from autotest_lib.server.cros import resource_monitor from autotest_lib.server.hosts import abstract_ssh from autotest_lib.server import utils import six from six.moves import map from six.moves import range class HostMock(abstract_ssh.AbstractSSHHost): """Mocks a host object.""" TOP_PID = '1234' def _initialize(self, test_env): self.top_is_running = False # Keep track of whether the top raw output file exists on the system, # and if it does, where it is. self.top_output_file_path = None # Keep track of whether the raw top output file is currently being # written to by top. self.top_output_file_is_open = False self.test_env = test_env def get_file(self, src, dest): pass def called_unsupported_command(self, command): """Raises assertion error when called. @param command string the unsupported command called. """ raise AssertionError( "ResourceMonitor called unsupported command %s" % command) def _process_top(self, cmd_args, cmd_line): """Process top command. @param cmd_args string_list of command line args. @param cmd_line string the command to run. """ self.test_env.assertFalse(self.top_is_running, msg="Top must not already be running.") self.test_env.assertFalse(self.top_output_file_is_open, msg="The top output file should not be being written " "to before top is started") self.test_env.assertIsNone(self.top_output_file_path, msg="The top output file should not exist" "before top is started") try: self.redirect_index = cmd_args.index(">") self.top_output_file_path = cmd_args[self.redirect_index + 1] except (ValueError, IndexError): self.called_unsupported_command(cmd_line) self.top_is_running = True self.top_output_file_is_open = True return HostMock.TOP_PID def _process_kill(self, cmd_args, cmd_line): """Process kill command. @param cmd_args string_list of command line args. @param cmd_line string the command to run. """ try: if cmd_args[1].startswith('-'): pid_to_kill = cmd_args[2] else: pid_to_kill = cmd_args[1] except IndexError: self.called_unsupported_command(cmd_line) self.test_env.assertEqual(pid_to_kill, HostMock.TOP_PID, msg="Wrong pid (%r) killed . Top pid is %r." % (pid_to_kill, HostMock.TOP_PID)) self.test_env.assertTrue(self.top_is_running, msg="Top must be running before we try to kill it") self.top_is_running = False self.top_output_file_is_open = False def _process_rm(self, cmd_args, cmd_line): """Process rm command. @param cmd_args string list list of command line args. @param cmd_line string the command to run. """ try: if cmd_args[1].startswith('-'): file_to_rm = cmd_args[2] else: file_to_rm = cmd_args[1] except IndexError: self.called_unsupported_command(cmd_line) self.test_env.assertEqual(file_to_rm, self.top_output_file_path, msg="Tried to remove file that is not the top output file.") self.test_env.assertFalse(self.top_output_file_is_open, msg="Tried to remove top output file while top is still " "writing to it.") self.test_env.assertFalse(self.top_is_running, msg="Top was still running when we tried to remove" "the top output file.") self.test_env.assertIsNotNone(self.top_output_file_path) self.top_output_file_path = None def _run_single_cmd(self, cmd_line, *args, **kwargs): """Run a single command on host. @param cmd_line command to run on the host. """ # Make the input a little nicer. cmd_line = cmd_line.strip() cmd_line = re.sub(">", " > ", cmd_line) cmd_args = re.split("\s+", cmd_line) self.test_env.assertTrue(len(cmd_args) >= 1) command = cmd_args[0] if (command == "top"): return self._process_top(cmd_args, cmd_line) elif (command == "kill"): return self._process_kill(cmd_args, cmd_line) elif(command == "rm"): return self._process_rm(cmd_args, cmd_line) else: logging.warning("Called unemulated command %r", cmd_line) return None def run(self, cmd_line, *args, **kwargs): """Run command(s) on host. @param cmd_line command to run on the host. @return CmdResult object. """ cmds = re.split("&&", cmd_line) for cmd in cmds: self._run_single_cmd(cmd) return utils.CmdResult(exit_status=0) def run_background(self, cmd_line, *args, **kwargs): """Run command in background on host. @param cmd_line command to run on the host. """ return self._run_single_cmd(cmd_line, args, kwargs) def is_monitoring(self): """Return true iff host is currently running top and writing output to a file. """ return self.top_is_running and self.top_output_file_is_open and ( self.top_output_file_path is not None) def monitoring_stopped(self): """Return true iff host is not running top and all top output files are closed. """ return not self.is_monitoring() class ResourceMonitorTest(unittest.TestCase): """Tests the non-trivial functionality of ResourceMonitor.""" def setUp(self): self.topoutfile = '/tmp/resourcemonitorunittest-1234' self.monitor_period = 1 self.rm_conf = resource_monitor.ResourceMonitorConfig( monitor_period=self.monitor_period, rawresult_output_filename=self.topoutfile) self.host = HostMock(self) def test_normal_operation(self): """Checks that normal (i.e. no exceptions, etc.) execution works.""" self.assertFalse(self.host.is_monitoring()) with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: self.assertFalse(self.host.is_monitoring()) for i in range(3): rm.start() self.assertTrue(self.host.is_monitoring()) rm.stop() self.assertTrue(self.host.monitoring_stopped()) self.assertTrue(self.host.monitoring_stopped()) def test_forgot_to_stop_monitor(self): """Checks that resource monitor is cleaned up even if user forgets to explicitly stop it. """ self.assertFalse(self.host.is_monitoring()) with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: self.assertFalse(self.host.is_monitoring()) rm.start() self.assertTrue(self.host.is_monitoring()) self.assertTrue(self.host.monitoring_stopped()) def test_unexpected_interruption_while_monitoring(self): """Checks that monitor is cleaned up upon unexpected interrupt.""" self.assertFalse(self.host.is_monitoring()) with resource_monitor.ResourceMonitor(self.host, self.rm_conf) as rm: self.assertFalse(self.host.is_monitoring()) rm.start() self.assertTrue(self.host.is_monitoring()) raise KeyboardInterrupt self.assertTrue(self.host.monitoring_stopped()) class ResourceMonitorResultTest(unittest.TestCase): """Functional tests for ResourceMonitorParsedResult.""" def setUp(self): self._res_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'res_resource_monitor') def run_with_test_data(self, testdata_file, testans_file): """Parses testdata_file with the parses, and checks that results are the same as those in testans_file. @param testdata_file string filename containing top output to test. @param testans_file string filename containing answers to the test. """ parsed_results = resource_monitor.ResourceMonitorParsedResult( testdata_file) with open(testans_file, "rb") as testans: csvreader = csv.reader(testans) columns = next(csvreader) self.assertEqual(list(columns), resource_monitor.ResourceMonitorParsedResult._columns) utils_over_time = [] for util_val in map( resource_monitor. ResourceMonitorParsedResult.UtilValues._make, csvreader): utils_over_time.append(util_val) self.assertEqual(utils_over_time, parsed_results._utils_over_time) def test_full_data(self): """General test with many possible changes to input.""" self.run_with_test_data( os.path.join(self._res_dir, 'top_test_data.txt'), os.path.join(self._res_dir, 'top_test_data_ans.csv')) def test_whitespace_ridden(self): """Tests resilience to arbitrary whitespace characters between fields""" self.run_with_test_data( os.path.join(self._res_dir, 'top_whitespace_ridden.txt'), os.path.join(self._res_dir, 'top_whitespace_ridden_ans.csv')) def test_field_order_changed(self): """Tests resilience to changes in the order of fields (for e.g, if the Mem free/used fields change orders in the input). """ self.run_with_test_data( os.path.join(self._res_dir, 'top_field_order_changed.txt'), os.path.join(self._res_dir, 'top_field_order_changed_ans.csv')) if __name__ == '__main__': unittest.main()