Home | History | Annotate | Download | only in cros
      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 
      5 import logging
      6 import unittest
      7 import re
      8 import csv
      9 import common
     10 import os
     11 
     12 from itertools import imap
     13 from autotest_lib.server.cros import resource_monitor
     14 from autotest_lib.server.hosts import abstract_ssh
     15 from autotest_lib.server import utils
     16 
     17 class 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 
    190 class 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 
    240 class 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 
    296 if __name__ == '__main__':
    297     unittest.main()
    298