diff options
author | Jonathan Abrahams <jonathan@mongodb.com> | 2019-04-12 14:16:14 -0400 |
---|---|---|
committer | Jonathan Abrahams <jonathan@mongodb.com> | 2019-04-15 11:50:35 -0400 |
commit | eb05fd56aadd4acd4197fdee209dc3ad2b77d495 (patch) | |
tree | a0146439e57a7d664b52e0b2521a9a62d9910892 /buildscripts | |
parent | 688948927bedd25c4c5c50bc7e6a253feeec4e25 (diff) | |
download | mongo-eb05fd56aadd4acd4197fdee209dc3ad2b77d495.tar.gz |
SERVER-40418 Refactor test_adb_monitor; add python2 for systrace.py for adb_monitor
Diffstat (limited to 'buildscripts')
-rw-r--r-- | buildscripts/mobile/adb_monitor.py | 290 | ||||
-rw-r--r-- | buildscripts/tests/mobile/test_adb_monitor.py | 945 |
2 files changed, 990 insertions, 245 deletions
diff --git a/buildscripts/mobile/adb_monitor.py b/buildscripts/mobile/adb_monitor.py index ac6b1042e22..7f535c7c768 100644 --- a/buildscripts/mobile/adb_monitor.py +++ b/buildscripts/mobile/adb_monitor.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 """ADB utilities to collect adb samples from a locally connected Android device.""" +import argparse import distutils.spawn # pylint: disable=no-name-in-module import logging -import optparse import os import pipes import re @@ -22,34 +22,138 @@ if __name__ == "__main__" and __package__ is None: from buildscripts.util import fileops from buildscripts.util import runcommand +# Default program options. +DEFAULT_ADB_BINARY = "adb" +DEFAULT_BATTERY_FILE = "battery.csv" +DEFAULT_CPU_FILE = "cpu.json" +DEFAULT_NUM_SAMPLES = 0 +DEFAULT_LOG_LEVEL = "info" +DEFAULT_MEMORY_FILE = "memory.csv" +DEFAULT_PYTHON27 = "python2" +DEFAULT_SAMPLE_INTERVAL_MS = 500 + +LOG_LEVELS = ["debug", "error", "info", "warning"] + # Initialize the global logger. LOGGER = logging.getLogger(__name__) +def parse_command_line(): + """Parse command line options. + + :return: Argparser object. + """ + parser = argparse.ArgumentParser() + + program_options = parser.add_argument_group("Program Options") + battery_options = parser.add_argument_group("Battery Options") + memory_options = parser.add_argument_group("Memory Options") + systrace_options = parser.add_argument_group("Systrace Options") + + program_options.add_argument( + "--adbBinary", dest="adb_binary", + help="The path for adb. Defaults to '%(default)s', which is in $PATH.", + default=DEFAULT_ADB_BINARY) + + program_options.add_argument( + "--python27", dest="python27", + help="The path for python2.7, required by systrace. Defaults to '%(default)s', which is in" + " $PATH.", default=DEFAULT_PYTHON27) + + program_options.add_argument( + "--samples", dest="num_samples", + help="Number of samples to collect, 0 indicates infinite. [Default: %(default)d]", type=int, + default=DEFAULT_NUM_SAMPLES) + + program_options.add_argument( + "--collectionTime", dest="collection_time_secs", + help="Time in seconds to collect samples, if specifed overrides '--samples'.", type=int, + default=None) + + program_options.add_argument( + "--sampleIntervalMs", dest="sample_interval_ms", + help="Time in milliseconds between collecting a sample. [Default: %(default)d]", type=int, + default=DEFAULT_SAMPLE_INTERVAL_MS) + + program_options.add_argument( + "--logLevel", dest="log_level", choices=LOG_LEVELS, + help=f"The log level. Accepted values are: {LOG_LEVELS}. [default: '%(default)s'].", + default=DEFAULT_LOG_LEVEL) + + battery_options.add_argument( + "--batteryFile", dest="battery_file", + help="The destination file for battery stats (CSV format). [Default: %(default)s].", + default=DEFAULT_BATTERY_FILE) + + battery_options.add_argument("--noBattery", dest="battery_file", + help="Disable collection of battery samples.", + action="store_const", const=None) + + memory_options.add_argument( + "--memoryFile", dest="memory_file", + help="The destination file for memory stats (CSV format). [Default: %(default)s].", + default=DEFAULT_MEMORY_FILE) + + memory_options.add_argument("--noMemory", dest="memory_file", + help="Disable collection of memory samples.", action="store_const", + const=None) + + systrace_options.add_argument( + "--cpuFile", dest="cpu_file", + help="The destination file for CPU stats (JSON format). [Default: %(default)s].", + default=DEFAULT_CPU_FILE) + + systrace_options.add_argument("--noCpu", dest="cpu_file", + help="Disable collection of CPU samples.", action="store_const", + const=None) + + return parser + + +def create_files_mtime(files): + """Create dict of file names and it's modified time. + + param files: List file names. + return: Dict of file names with value of the file's modified time. + """ + return {file_name: fileops.getmtime(file_name) for file_name in files if file_name} + + +def find_executable(binary_file): + """Find if binary_file exists in $PATH. Raise exception if it cannot be found. + + param binary_file: Name of binary to find. + return: Full path of binary_file. + """ + binary_path = distutils.spawn.find_executable(binary_file) + if not binary_path: + raise EnvironmentError(f"Executable '{binary_file}' does not exist or is not in the PATH.") + return binary_path + + class Adb(object): """Class to abstract calls to adb.""" - def __init__(self, adb_binary="adb", logger=LOGGER): + def __init__(self, adb_binary=DEFAULT_ADB_BINARY, logger=LOGGER, python27=DEFAULT_PYTHON27): """Initialize the Adb object.""" self._cmd = None + self._tempfile = None self.logger = logger - self.adb_path = distutils.spawn.find_executable(adb_binary) - if not self.adb_path: - raise EnvironmentError( - "Executable '{}' does not exist or is not in the PATH.".format(adb_binary)) + self.python27 = find_executable(python27) + adb_path = find_executable(adb_binary) # We support specifying a path the adb binary to use; however, systrace.py only # knows how to find it using the PATH environment variable. It is possible that # 'adb_binary' is an absolute path specified by the user, so we add its parent # directory to the PATH manually. - adb_dir = os.path.dirname(self.adb_path) - os.environ["PATH"] = "{}{}{}".format(os.environ["PATH"], os.path.pathsep, adb_dir) + adb_dir = os.path.dirname(adb_path) + if adb_dir: + os.environ["PATH"] = "{}{}{}".format(os.environ["PATH"], os.path.pathsep, adb_dir) # systrace.py should be in <adb_dir>/systrace/systrace.py self.systrace_script = os.path.join(adb_dir, "systrace", "systrace.py") if not os.path.isfile(self.systrace_script): raise EnvironmentError("Script '{}' cannot be found.".format(self.systrace_script)) - self._tempfile = None @staticmethod def adb_cmd(adb_command, output_file=None, append_file=False, output_string=False): @@ -108,13 +212,13 @@ class Adb(object): def _battery_cmd(self, option, output_file=None, append_file=False): self.adb_cmd("shell dumpsys batterystats {}".format(option), output_file, append_file) - def battery(self, reset=False, output_file=None, append_file=False): + def battery(self, output_file, append_file=False, reset=False): """Collect the battery stats and save to the output_file.""" if reset: self._battery_cmd("--reset") self._battery_cmd("--checkin", output_file, append_file) - def memory(self, output_file=None, append_file=False): + def memory(self, output_file, append_file=False): """Collect the memory stats and save to the output_file.""" self.adb_cmd("shell dumpsys meminfo -c -d", output_file, append_file) @@ -122,7 +226,8 @@ class Adb(object): """Start the systrace.py script to collect CPU usage.""" self._tempfile = tempfile.NamedTemporaryFile(delete=False).name self._cmd = runcommand.RunCommand(output_file=self._tempfile, propagate_signals=False) - self._cmd.add_file(sys.executable) + # systrace.py currently only supports python 2.7. + self._cmd.add_file(self.python27) self._cmd.add_file(self.systrace_script) self._cmd.add("--json") self._cmd.add("-o") @@ -150,15 +255,16 @@ class AdbControl(object): # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments self, adb, logger=LOGGER, battery_file=None, memory_file=None, cpu_file=None, - append_file=False, num_samples=0, collection_time_secs=0, sample_interval_ms=0): + append_file=False, num_samples=DEFAULT_NUM_SAMPLES, collection_time_secs=None, + sample_interval_ms=DEFAULT_SAMPLE_INTERVAL_MS): """Initialize AdbControl object.""" self.adb = adb self.logger = logger - output_files = [battery_file, memory_file, cpu_file] - if not any(output_files): + output_files = [fn for fn in [battery_file, memory_file, cpu_file] if fn] + if not output_files: raise ValueError("There are no collection sample files selected.") self.battery_file = battery_file self.memory_file = memory_file @@ -171,44 +277,43 @@ class AdbControl(object): # pylint: disable=too-many-instance-attributes if not append_file: fileops.create_empty(output_file) - self.append_file = append_file # collection_time_secs overrides num_samples - self.num_samples = num_samples if collection_time_secs == 0 else 0 + self.num_samples = num_samples if not collection_time_secs else 0 self.collection_time_secs = collection_time_secs self.sample_interval_ms = sample_interval_ms - self.should_stop = threading.Event() - self.should_stop.clear() - self.sample_based_threads = [] - self.all_threads = [] + self._should_stop = threading.Event() + self._should_stop.clear() + self._sample_based_threads = [] + self._all_threads = [] def start(self): """Start adb sample collection.""" if self.cpu_file: - monitor = AdbContinuousResourceMonitor(self.cpu_file, self.should_stop, + monitor = AdbContinuousResourceMonitor(self.cpu_file, self._should_stop, self.adb.systrace_start, self.adb.systrace_stop) - self.all_threads.append(monitor) + self._all_threads.append(monitor) monitor.start() if self.battery_file: - monitor = AdbSampleBasedResourceMonitor(self.battery_file, self.should_stop, + monitor = AdbSampleBasedResourceMonitor(self.battery_file, self._should_stop, self.adb.battery, self.num_samples, self.sample_interval_ms) - self.sample_based_threads.append(monitor) - self.all_threads.append(monitor) + self._sample_based_threads.append(monitor) + self._all_threads.append(monitor) monitor.start() if self.memory_file: - monitor = AdbSampleBasedResourceMonitor(self.memory_file, self.should_stop, + monitor = AdbSampleBasedResourceMonitor(self.memory_file, self._should_stop, self.adb.memory, self.num_samples, self.sample_interval_ms) - self.sample_based_threads.append(monitor) - self.all_threads.append(monitor) + self._sample_based_threads.append(monitor) + self._all_threads.append(monitor) monitor.start() def stop(self): """Stop adb sample collection.""" - self.should_stop.set() + self._should_stop.set() self.wait() def wait(self): @@ -216,10 +321,10 @@ class AdbControl(object): # pylint: disable=too-many-instance-attributes try: # We either wait for the specified amount of time or for the sample-based monitors # to have collected the specified number of samples. - if self.collection_time_secs > 0: - self.should_stop.wait(self.collection_time_secs) + if self.collection_time_secs: + self._should_stop.wait(self.collection_time_secs) else: - for thread in self.sample_based_threads: + for thread in self._sample_based_threads: # We must specify a timeout to threading.Thread.join() to ensure that the # wait is interruptible. The main thread would otherwise never be able to # receive a KeyboardInterrupt. @@ -229,17 +334,17 @@ class AdbControl(object): # pylint: disable=too-many-instance-attributes # that they should exit as quickly as they can. pass finally: - self.should_stop.set() + self._should_stop.set() # Wait for all of the monitor threads to exit, by specifying a timeout to # threading.Thread.join() in case the user tries to interrupt the script again. - for thread in self.all_threads: + for thread in self._all_threads: thread.join(self._JOIN_TIMEOUT) self.logger.info("Collections stopped.") # If any of the monitor threads encountered an error, then reraise the exception in the # main thread. - for thread in self.all_threads: + for thread in self._all_threads: if thread.exception is not None: raise thread.exception @@ -322,99 +427,42 @@ class AdbContinuousResourceMonitor(AdbResourceMonitor): self._adb_stop_cmd(output_file=self._output_file) -def main(): #pylint: disable=too-many-statements +def monitor_device(adb_control, files_mtime): + """Run monitoring on device and collect results. + param adb_control: AdbControl object. + param files_mtime: Dict of files with their modified time. + """ + adb_control.start() + try: + adb_control.wait() + finally: + files_saved = [ + path for path in files_mtime + if fileops.getmtime(path) > files_mtime[path] and not fileops.is_empty(path) + ] + LOGGER.info("Files saved: %s", files_saved) + + +def main(): """Execute Main program.""" logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=logging.INFO) logging.Formatter.converter = time.gmtime - parser = optparse.OptionParser() - - program_options = optparse.OptionGroup(parser, "Program Options") - battery_options = optparse.OptionGroup(parser, "Battery Options") - memory_options = optparse.OptionGroup(parser, "Memory Options") - systrace_options = optparse.OptionGroup(parser, "Systrace Options") - - program_options.add_option("--adbBinary", dest="adb_binary", - help="The path for adb. Defaults to '%default', which is in $PATH.", - default="adb") - - program_options.add_option( - "--samples", dest="num_samples", - help="Number of samples to collect, 0 indicates infinite. [Default: %default]", type=int, - default=0) - - program_options.add_option( - "--collectionTime", dest="collection_time_secs", - help="Time in seconds to collect samples, if specifed overrides '--samples'.", type=int, - default=0) - - program_options.add_option( - "--sampleIntervalMs", dest="sample_interval_ms", - help="Time in milliseconds between collecting a sample. [Default: %default]", type=int, - default=500) - - log_levels = ["debug", "error", "info", "warning"] - program_options.add_option( - "--logLevel", dest="log_level", choices=log_levels, - help="The log level. Accepted values are: {}. [default: '%default'].".format(log_levels), - default="info") + parser = parse_command_line() + options = parser.parse_args() - battery_options.add_option( - "--batteryFile", dest="battery_file", - help="The destination file for battery stats (CSV format). [Default: %default].", - default="battery.csv") - - battery_options.add_option("--noBattery", dest="no_battery", - help="Disable collection of battery samples.", action="store_true", - default=False) - - memory_options.add_option( - "--memoryFile", dest="memory_file", - help="The destination file for memory stats (CSV format). [Default: %default].", - default="memory.csv") - - memory_options.add_option("--noMemory", dest="no_memory", - help="Disable collection of memory samples.", action="store_true", - default=False) - - systrace_options.add_option( - "--cpuFile", dest="cpu_file", - help="The destination file for CPU stats (JSON format). [Default: %default].", - default="cpu.json") - - systrace_options.add_option("--noCpu", dest="no_cpu", help="Disable collection of CPU samples.", - action="store_true", default=False) - - parser.add_option_group(program_options) - parser.add_option_group(battery_options) - parser.add_option_group(memory_options) - parser.add_option_group(systrace_options) + files_mtime = create_files_mtime([options.battery_file, options.memory_file, options.cpu_file]) - options, _ = parser.parse_args() - - output_files = {} - if options.no_battery: - options.battery_file = None - else: - output_files[options.battery_file] = fileops.getmtime(options.battery_file) - - if options.no_memory: - options.memory_file = None - else: - output_files[options.memory_file] = fileops.getmtime(options.memory_file) - - if options.no_cpu: - options.cpu_file = None - else: - output_files[options.cpu_file] = fileops.getmtime(options.cpu_file) + if not files_mtime: + parser.error("Must specify one ouptut file") LOGGER.setLevel(options.log_level.upper()) LOGGER.info( "This program can be cleanly terminated by issuing the following command:" "\n\t\t'kill -INT %d'", os.getpid()) - adb = Adb(options.adb_binary) + adb = Adb(adb_binary=options.adb_binary, python27=options.python27) LOGGER.info("Detected devices by adb:\n%s%s", adb.devices(), adb.device_available()) adb_control = AdbControl(adb=adb, battery_file=options.battery_file, @@ -423,15 +471,7 @@ def main(): #pylint: disable=too-many-statements collection_time_secs=options.collection_time_secs, sample_interval_ms=options.sample_interval_ms) - adb_control.start() - try: - adb_control.wait() - finally: - files_saved = [] - for path in output_files: - if fileops.getmtime(path) > output_files[path] and not fileops.is_empty(path): - files_saved.append(path) - LOGGER.info("Files saved: %s", files_saved) + monitor_device(adb_control, files_mtime) if __name__ == "__main__": diff --git a/buildscripts/tests/mobile/test_adb_monitor.py b/buildscripts/tests/mobile/test_adb_monitor.py index 610e3e10aae..286593fbc89 100644 --- a/buildscripts/tests/mobile/test_adb_monitor.py +++ b/buildscripts/tests/mobile/test_adb_monitor.py @@ -1,134 +1,839 @@ """ Unit tests for adb_monitor. """ -import distutils.spawn # pylint: disable=no-name-in-module import os -import shutil -import sys -import tempfile import unittest +from unittest.mock import MagicMock, mock_open, patch + import buildscripts.mobile.adb_monitor as adb_monitor -_IS_WINDOWS = sys.platform == "win32" or sys.platform == "cygwin" - -if _IS_WINDOWS: - import win32file - -# pylint: disable=missing-docstring,protected-access - - -def mock_adb_and_systrace(directory): - """Mock adb and systrace.py.""" - # Create mock 'adb', which is really 'echo'. - adb_binary = os.path.join(directory, "adb") - echo_binary = distutils.spawn.find_executable("echo") - if _IS_WINDOWS: - adb_binary = "{}.exe".format(adb_binary) - shutil.copyfile(echo_binary, adb_binary) - else: - os.symlink(echo_binary, adb_binary) - os.environ["PATH"] = "{}{}{}".format(directory, os.path.pathsep, os.environ["PATH"]) - - # Create mock 'systrace.py'. - systrace_dir = os.path.join(directory, "systrace") - os.mkdir(systrace_dir) - systrace = os.path.join(systrace_dir, "systrace.py") - with open(systrace, "w") as fh: - fh.write("import optparse\n") - fh.write("input('waiting...')\n") - fh.write("print('Wrote trace')\n") - fh.write("parser = optparse.OptionParser()\n") - fh.write("parser.add_option('-o', dest='output_file')\n") - fh.write("parser.add_option('--json', dest='json_opts', action='store_true')\n") - fh.write("options, args = parser.parse_args()\n") - fh.write("with open(options.output_file, 'w') as fh:\n") - fh.write("\tfh.write('{hello:1}')\n") - - -def file_contents(path): - with open(path, "r") as fh: - return fh.read() - - -class AdbTestCase(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.temp_dir = tempfile.mkdtemp() - mock_adb_and_systrace(cls.temp_dir) - cls.adb = adb_monitor.Adb() - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.temp_dir) - - -class AdbTest(AdbTestCase): - def test_bad_adb(self): - self.assertRaises(EnvironmentError, lambda: adb_monitor.Adb("bad_adb")) - - def test_devices(self): - self.adb.devices() - - def test_battery(self): - temp_file = os.path.join(self.temp_dir, "battery_output") - self.adb.battery(output_file=temp_file) - self.assertTrue(os.path.isfile(temp_file)) - - def test_memory(self): - temp_file = os.path.join(self.temp_dir, "memory_output") - self.adb.memory(output_file=temp_file) - self.assertTrue(os.path.isfile(temp_file)) - - def test_systrace(self): - temp_file = os.path.join(self.temp_dir, "systrace_output") - self.adb.systrace_start(output_file=temp_file) - self.adb.systrace_stop() - self.assertTrue(os.path.isfile(temp_file)) - - -class AdbControlTestCase(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.temp_dir = tempfile.mkdtemp() - mock_adb_and_systrace(cls.temp_dir) - cls.adb = adb_monitor.Adb() - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.temp_dir) - - -class AdbControlTest(AdbControlTestCase): - def _test_files(self, num_samples=1, collection_time_secs=0, sample_interval_ms=500, - arg_list=None): - args = {} - arg_file_list = [] - for arg_name in arg_list: - arg_test_file = tempfile.NamedTemporaryFile(dir=self.temp_dir, delete=False).name - args[arg_name] = arg_test_file - arg_file_list.append(arg_test_file) - adb_control = adb_monitor.AdbControl(self.adb, collection_time_secs=collection_time_secs, - num_samples=num_samples, - sample_interval_ms=sample_interval_ms, **args) +ADB_MONITOR = "buildscripts.mobile.adb_monitor" + + +def ns(module): + return f"{ADB_MONITOR}.{module}" + + +class TestParseCommandLine(unittest.TestCase): + def test_parse_command_line(self): + options = adb_monitor.parse_command_line().parse_args([]) + self.assertEqual(options.adb_binary, adb_monitor.DEFAULT_ADB_BINARY) + self.assertEqual(options.python27, adb_monitor.DEFAULT_PYTHON27) + self.assertEqual(options.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + self.assertEqual(options.log_level, adb_monitor.DEFAULT_LOG_LEVEL) + self.assertEqual(options.battery_file, adb_monitor.DEFAULT_BATTERY_FILE) + self.assertEqual(options.memory_file, adb_monitor.DEFAULT_MEMORY_FILE) + self.assertEqual(options.cpu_file, adb_monitor.DEFAULT_CPU_FILE) + self.assertEqual(options.num_samples, adb_monitor.DEFAULT_NUM_SAMPLES) + self.assertEqual(options.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + self.assertIsNone(options.collection_time_secs) + + def test_parse_command_line_partial_args(self): + adb_binary = "myadb" + python27 = "mypython2" + collection_time_secs = 35 + battery_file = "mybattery" + arg_list = [ + "--adbBinary", adb_binary, "--python27", python27, "--collectionTime", + str(collection_time_secs), "--batteryFile", battery_file, "--noMemory" + ] + options = adb_monitor.parse_command_line().parse_args(arg_list) + self.assertEqual(options.adb_binary, adb_binary) + self.assertEqual(options.python27, python27) + self.assertEqual(options.collection_time_secs, collection_time_secs) + self.assertEqual(options.battery_file, battery_file) + + self.assertEqual(options.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + self.assertEqual(options.log_level, adb_monitor.DEFAULT_LOG_LEVEL) + self.assertIsNone(options.memory_file) + self.assertEqual(options.cpu_file, adb_monitor.DEFAULT_CPU_FILE) + self.assertEqual(options.num_samples, adb_monitor.DEFAULT_NUM_SAMPLES) + self.assertEqual(options.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + + def test_parse_command_line_no_files(self): + arg_list = ["--noBattery", "--noCpu", "--noMemory"] + options = adb_monitor.parse_command_line().parse_args(arg_list) + self.assertIsNone(options.battery_file) + self.assertIsNone(options.cpu_file) + self.assertIsNone(options.memory_file) + + def test_parse_command_line_no_battery_first(self): + battery_file = "mybattery" + arg_list = ["--noBattery", "--batteryFile", battery_file] + options = adb_monitor.parse_command_line().parse_args(arg_list) + self.assertEqual(options.battery_file, battery_file) + + def test_parse_command_line_no_battery_second(self): + battery_file = "mybattery" + arg_list = ["--batteryFile", battery_file, "--noBattery"] + options = adb_monitor.parse_command_line().parse_args(arg_list) + self.assertIsNone(options.battery_file) + + +class TestMonitorDevice(unittest.TestCase): + @patch(ns("fileops"), return_value=False) + def test_monitor_device(self, mock_fileops): + files_mtime = {"file1": 0, "file2": 0} + mock_fileops.getmtime.return_value = 10 + mock_adb_control = MagicMock() + adb_monitor.monitor_device(mock_adb_control, files_mtime) + mock_adb_control.start.assert_called_once() + mock_adb_control.wait.assert_called_once() + self.assertEqual(mock_fileops.getmtime.call_count, len(files_mtime)) + self.assertEqual(mock_fileops.is_empty.call_count, len(files_mtime)) + + @patch(ns("fileops"), return_value=True) + def test_monitor_device_empty_file(self, mock_fileops): + files_mtime = {"file1": 0, "file2": 0} + mock_fileops.getmtime.return_value = 10 + mock_adb_control = MagicMock() + adb_monitor.monitor_device(mock_adb_control, files_mtime) + mock_adb_control.start.assert_called_once() + mock_adb_control.wait.assert_called_once() + self.assertEqual(mock_fileops.getmtime.call_count, len(files_mtime)) + self.assertEqual(mock_fileops.is_empty.call_count, len(files_mtime)) + + @patch(ns("fileops"), return_value=False) + def test_monitor_device_earlier_mtime(self, mock_fileops): + files_mtime = {"file1": 10, "file2": 10} + mock_fileops.getmtime.return_value = 6 + mock_adb_control = MagicMock() + adb_monitor.monitor_device(mock_adb_control, files_mtime) + mock_adb_control.start.assert_called_once() + mock_adb_control.wait.assert_called_once() + self.assertEqual(mock_fileops.getmtime.call_count, len(files_mtime)) + mock_fileops.is_empty.assert_not_called() + + @patch(ns("fileops"), return_value=False) + def test_monitor_device_no_files(self, mock_fileops): + files_mtime = {} + mock_fileops.getmtime.return_value = 10 + mock_adb_control = MagicMock() + adb_monitor.monitor_device(mock_adb_control, files_mtime) + mock_adb_control.start.assert_called_once() + mock_adb_control.wait.assert_called_once() + mock_fileops.getmtime.assert_not_called() + mock_fileops.is_empty.assert_not_called() + + +class TestOutputFilesMtime(unittest.TestCase): + @patch(ns("fileops.getmtime")) + def test_output_files_mtime(self, mock_getmtime): + mtime = 11 + mock_getmtime.return_value = mtime + files = ["file1", "file2"] + m_files = adb_monitor.create_files_mtime(files) + self.assertEqual(len(m_files), len(files)) + for fn in files: + self.assertEqual(m_files[fn], mtime) + + @patch(ns("fileops.getmtime")) + def test_output_files_mtime_no_files(self, mock_getmtime): + m_files = adb_monitor.create_files_mtime([]) + self.assertEqual(len(m_files), 0) + + +class TestFindExecutable(unittest.TestCase): + def test_find_executable(self): + my_binary = "mybinary" + with patch(ns("distutils.spawn.find_executable"), side_effect=lambda x: x): + adb_binary = adb_monitor.find_executable(my_binary) + self.assertEqual(adb_binary, my_binary) + + def test_find_executable_not_found(self): + with patch(ns("distutils.spawn.find_executable"), side_effect=lambda x: x),\ + self.assertRaises(EnvironmentError): + adb_monitor.find_executable(None) + + +class TestAdb(unittest.TestCase): + @patch(ns("os.path.isfile"), return_value=True) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test___init__(self, mock_find_executable, mock_isfile): + os_path = os.environ["PATH"] + adb = adb_monitor.Adb() + self.assertTrue(adb.systrace_script.startswith(os.path.join("systrace", "systrace.py"))) + self.assertEqual(adb.logger, adb_monitor.LOGGER) + self.assertEqual(adb.python27, adb_monitor.DEFAULT_PYTHON27) + self.assertEqual(adb.logger, adb_monitor.LOGGER) + self.assertEqual(os.environ["PATH"], os_path) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test___init__adb_binary(self, mock_find_executable, mock_os): + adb_dir = os.path.join("/root", "adb_dir") + adb_path = os.path.join(adb_dir, "adb") + mock_os.environ = {"PATH": os.environ["PATH"]} + mock_os.path.pathsep = os.path.pathsep + mock_os.path.dirname = lambda x: x + adb = adb_monitor.Adb(adb_binary=adb_path) + self.assertTrue(adb.systrace_script.startswith(adb_dir)) + self.assertIn(os.path.pathsep + adb_dir, mock_os.environ["PATH"]) + + @patch(ns("os.environ")) + @patch(ns("os.path.isfile"), return_value=True) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test___init__python27_binary(self, mock_find_executable, mock_isfile, mock_os_environ): + python_dir = os.path.join("/root", "python27_dir") + python27_path = os.path.join(python_dir, "python2") + adb = adb_monitor.Adb(python27=python27_path) + self.assertEqual(adb.python27, python27_path) + + @patch(ns("os.environ")) + @patch(ns("os.path.isfile"), return_value=False) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test___init__bad_systrace(self, mock_find_executable, mock_isfile, mock_os_environ): + adb_dir = os.path.join("/root", "adb_dir") + adb_path = os.path.join(adb_dir, "adb") + with self.assertRaises(EnvironmentError): + adb_monitor.Adb() + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_adb_cmd_output(self, mock_runcmd, mock_os): + adb_result = adb_monitor.Adb.adb_cmd("mycmd") + self.assertEqual(adb_result, mock_runcmd.RunCommand().execute_with_output()) + self.assertNotEqual(adb_result, mock_runcmd.RunCommand().execute_save_output()) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_adb_cmd_output_string(self, mock_runcmd, mock_os): + adb_result = adb_monitor.Adb.adb_cmd("mycmd", output_string=True) + self.assertEqual(adb_result, mock_runcmd.RunCommand().execute_with_output()) + self.assertNotEqual(adb_result, mock_runcmd.RunCommand().execute_save_output()) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_adb_cmd_save_output(self, mock_runcmd, mock_os): + adb_result = adb_monitor.Adb.adb_cmd("mycmd", output_file="myfile") + self.assertEqual(adb_result, mock_runcmd.RunCommand().execute_save_output()) + self.assertNotEqual(adb_result, mock_runcmd.RunCommand().execute_with_output()) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_adb_cmd_all_params(self, mock_runcmd, mock_os): + adb_result = adb_monitor.Adb.adb_cmd("mycmd", output_file="myfile", append_file=True, + output_string=True) + self.assertNotEqual(adb_result, mock_runcmd.RunCommand().execute_save_output()) + self.assertEqual(adb_result, mock_runcmd.RunCommand().execute_with_output()) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_shell(self, mock_runcmd, mock_os): + cmd_output = adb_monitor.Adb.shell("mycmd") + self.assertEqual(cmd_output, mock_runcmd.RunCommand().execute_with_output()) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_shell_stripped(self, mock_runcmd, mock_os): + output = "output from shell" + mock_runcmd.RunCommand().execute_with_output.return_value = output + "__EXIT__:0\n" + cmd_output = adb_monitor.Adb.shell("mycmd") + self.assertEqual(cmd_output, output) + + @patch(ns("os")) + @patch(ns("runcommand")) + def test_shell_error(self, mock_runcmd, mock_os): + output = "output from shell" + mock_runcmd.RunCommand().execute_with_output.return_value = output + "__EXIT__:1\n" + with self.assertRaises(RuntimeError): + adb_monitor.Adb.shell("mycmd") + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_devices(self, mock_runcmd, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.devices() + mock_runcmd.RunCommand.assert_called_once_with("adb devices -l", unittest.mock.ANY, + unittest.mock.ANY) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_device_available(self, mock_runcmd, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.device_available() + mock_runcmd.RunCommand.assert_called_once_with("adb shell uptime", unittest.mock.ANY, + unittest.mock.ANY) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_push(self, mock_runcmd, mock_find_executable, mock_os): + files = "myfile" + remote_dir = "/remotedir" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.push(files, remote_dir) + push_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + self.assertIn(files, push_cmd) + self.assertIn(remote_dir, push_cmd) + self.assertNotIn("--sync", push_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_push_list(self, mock_runcmd, mock_find_executable, mock_os): + files = ["myfile", "file2"] + remote_dir = "/remotedir" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.push(files, remote_dir) + push_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + self.assertIn(" ".join(files), push_cmd) + self.assertIn(remote_dir, push_cmd) + self.assertNotIn("--sync", push_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_push_sync(self, mock_runcmd, mock_find_executable, mock_os): + files = ["myfile", "file2"] + remote_dir = "/remotedir" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.push(files, remote_dir, sync=True) + push_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + for file_name in files: + self.assertIn(file_name, push_cmd) + self.assertIn(remote_dir, push_cmd) + self.assertIn("--sync", push_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_pull(self, mock_runcmd, mock_find_executable, mock_os): + files = "myfile" + local_dir = "/localdir" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.pull(files, local_dir) + pull_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + self.assertIn(files, pull_cmd) + self.assertIn(local_dir, pull_cmd) + self.assertNotIn("--sync", pull_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_pull_files(self, mock_runcmd, mock_find_executable, mock_os): + files = ["myfile", "file2"] + local_dir = "/localdir" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.pull(files, local_dir) + pull_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + for file_name in files: + self.assertIn(file_name, pull_cmd) + self.assertIn(local_dir, pull_cmd) + self.assertNotIn("--sync", pull_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test__battery_cmd(self, mock_runcmd, mock_find_executable, mock_os): + option = "myopt" + battery_cmd = "shell dumpsys batterystats " + option + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb._battery_cmd(option) + mock_runcmd.RunCommand.assert_called_once_with("adb " + battery_cmd, unittest.mock.ANY, + unittest.mock.ANY) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test__battery_cmd_save_output(self, mock_runcmd, mock_find_executable, mock_os): + option = "myopt" + battery_cmd = "shell dumpsys batterystats " + option + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb._battery_cmd(option, "myfile") + mock_runcmd.RunCommand.assert_called_once_with("adb " + battery_cmd, unittest.mock.ANY, + unittest.mock.ANY) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_battery(self, mock_runcmd, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.battery("myfile") + battery_cmd = mock_runcmd.RunCommand.call_args_list[0][0][0] + self.assertIn("--checkin", battery_cmd) + self.assertNotIn("--reset", battery_cmd) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_battery_reset(self, mock_runcmd, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.battery("myfile", reset=True, append_file=True) + battery_cmd1 = mock_runcmd.RunCommand.call_args_list[0][0][0] + battery_cmd2 = mock_runcmd.RunCommand.call_args_list[1][0][0] + self.assertIn("--reset", battery_cmd1) + self.assertIn("--checkin", battery_cmd2) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_memory(self, mock_runcmd, mock_find_executable, mock_os): + memory_cmd = "shell dumpsys meminfo -c -d" + mock_os.path.dirname.return_value = "adb_dir" + adb = adb_monitor.Adb() + adb.memory("myfile") + mock_runcmd.RunCommand.assert_called_once_with("adb " + memory_cmd, unittest.mock.ANY, + unittest.mock.ANY) + + @patch(ns("tempfile.NamedTemporaryFile")) + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_systrace_start(self, mock_runcmd, mock_find_executable, mock_os, mock_tempfile): + mock_os.path.dirname.return_value = "adb_dir" + systrace_script = "systrace.py" + mock_os.path.join.return_value = systrace_script + adb = adb_monitor.Adb() + adb.systrace_start() + call_args = mock_runcmd.RunCommand.call_args_list[0] + mock_runcmd.RunCommand.assert_called_once_with(output_file=mock_tempfile().name, + propagate_signals=False) + self.assertEqual(adb.systrace_script, systrace_script) + self.assertEqual(mock_runcmd.RunCommand().add_file.call_count, 3) + mock_runcmd.RunCommand().add_file.assert_any_call(adb_monitor.DEFAULT_PYTHON27) + mock_runcmd.RunCommand().add_file.assert_any_call(systrace_script) + self.assertEqual(mock_runcmd.RunCommand().add.call_count, 3) + mock_runcmd.RunCommand().add.assert_any_call("--json") + mock_runcmd.RunCommand().add.assert_any_call("-o") + mock_runcmd.RunCommand().add.assert_any_call("dalvik sched freq idle load") + mock_runcmd.RunCommand().start_process.assert_called_once() + + @patch(ns("tempfile.NamedTemporaryFile")) + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + @patch(ns("runcommand")) + def test_systrace_start_output_file(self, mock_runcmd, mock_find_executable, mock_os, + mock_tempfile): + output_file = "myfile" + mock_os.path.dirname.return_value = "adb_dir" + systrace_script = "systrace.py" + mock_os.path.join.return_value = systrace_script + adb = adb_monitor.Adb() + adb.systrace_start(output_file) + call_args = mock_runcmd.RunCommand.call_args_list[0] + mock_runcmd.RunCommand.assert_called_once_with(output_file=mock_tempfile().name, + propagate_signals=False) + self.assertEqual(adb.systrace_script, systrace_script) + self.assertEqual(mock_runcmd.RunCommand().add_file.call_count, 3) + mock_runcmd.RunCommand().add_file.assert_any_call(adb_monitor.DEFAULT_PYTHON27) + mock_runcmd.RunCommand().add_file.assert_any_call(systrace_script) + mock_runcmd.RunCommand().add_file.assert_any_call(output_file) + self.assertEqual(mock_runcmd.RunCommand().add.call_count, 3) + mock_runcmd.RunCommand().add.assert_any_call("--json") + mock_runcmd.RunCommand().add.assert_any_call("-o") + mock_runcmd.RunCommand().add.assert_any_call("dalvik sched freq idle load") + mock_runcmd.RunCommand().start_process.assert_called_once() + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test_systrace_stop(self, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + systrace_script = "systrace.py" + systrace_output = "Systrace: Wrote trace" + mock_os.path.join.return_value = systrace_script + adb = adb_monitor.Adb() + adb._cmd = MagicMock() + with patch(ADB_MONITOR + ".open", mock_open(read_data=systrace_output)): + adb.systrace_stop() + adb._cmd.send_to_process.assert_called_once_with(b"bye") + mock_os.remove.assert_called_once_with(adb._tempfile) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test_systrace_stop_output_file(self, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + systrace_script = "systrace.py" + systrace_output = "Systrace: Wrote trace" + output_file = "myfile" + mock_os.path.join.return_value = systrace_script + adb = adb_monitor.Adb() + adb._cmd = MagicMock() + with patch(ADB_MONITOR + ".open", mock_open(read_data=systrace_output)): + adb.systrace_stop(output_file=output_file) + adb._cmd.send_to_process.assert_called_once_with(b"bye") + mock_os.remove.assert_called_once_with(adb._tempfile) + + @patch(ns("os")) + @patch(ns("find_executable"), side_effect=lambda x: x) + def test_systrace_stop_no_trace(self, mock_find_executable, mock_os): + mock_os.path.dirname.return_value = "adb_dir" + systrace_script = "systrace.py" + systrace_output = "Systrace: did not Write trace" + output_file = "myfile" + mock_os.path.join.return_value = systrace_script + adb = adb_monitor.Adb() + adb._cmd = MagicMock() + with patch(ADB_MONITOR + ".open", mock_open(read_data=systrace_output)): + adb.systrace_stop(output_file=output_file) + adb._cmd.send_to_process.assert_called_once_with(b"bye") + self.assertEqual(mock_os.remove.call_count, 2) + mock_os.remove.assert_any_call(adb._tempfile) + mock_os.remove.assert_any_call(output_file) + + +class TestAdbControl(unittest.TestCase): + def test___init___no_files(self): + mock_adb = MagicMock() + with self.assertRaises(ValueError): + adb_monitor.AdbControl(mock_adb) + + @patch(ns("fileops.create_empty")) + def test___init___all_files(self, mock_create_empty): + mock_adb = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + adb_control = adb_monitor.AdbControl(mock_adb, battery_file=battery_file, + memory_file=memory_file, cpu_file=cpu_file) + self.assertEqual(adb_control.adb, mock_adb) + self.assertEqual(adb_control.battery_file, battery_file) + self.assertEqual(adb_control.memory_file, memory_file) + self.assertEqual(adb_control.cpu_file, cpu_file) + self.assertEqual(adb_control.num_samples, 0) + self.assertIsNone(adb_control.collection_time_secs) + self.assertEqual(adb_control.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + self.assertEqual(mock_create_empty.call_count, 3) + mock_create_empty.assert_any_call(battery_file) + mock_create_empty.assert_any_call(memory_file) + mock_create_empty.assert_any_call(cpu_file) + + @patch(ns("fileops.create_empty")) + def test___init___battery_file(self, mock_create_empty): + mock_adb = MagicMock() + battery_file = "mybattery" + adb_control = adb_monitor.AdbControl(mock_adb, battery_file=battery_file) + self.assertEqual(adb_control.adb, mock_adb) + self.assertEqual(adb_control.battery_file, battery_file) + self.assertIsNone(adb_control.memory_file) + self.assertIsNone(adb_control.cpu_file) + self.assertEqual(adb_control.num_samples, 0) + self.assertIsNone(adb_control.collection_time_secs) + self.assertEqual(adb_control.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + mock_create_empty.assert_called_once_with(battery_file) + + @patch(ns("fileops.create_empty")) + def test___init___all_params(self, mock_create_empty): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + num_samples = 5 + collection_time_secs = 10 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + cpu_file=cpu_file, append_file=True, num_samples=num_samples, collection_time_secs=10, + sample_interval_ms=sample_interval_ms) + self.assertEqual(adb_control.adb, mock_adb) + self.assertEqual(adb_control.battery_file, battery_file) + self.assertEqual(adb_control.memory_file, memory_file) + self.assertEqual(adb_control.cpu_file, cpu_file) + self.assertEqual(adb_control.num_samples, 0) + self.assertEqual(adb_control.collection_time_secs, collection_time_secs) + self.assertEqual(adb_control.sample_interval_ms, sample_interval_ms) + mock_create_empty.assert_not_called() + + @patch(ns("fileops.create_empty")) + def test___init___num_samples(self, mock_create_empty): + mock_adb = MagicMock() + battery_file = "mybattery" + num_samples = 5 + adb_control = adb_monitor.AdbControl(mock_adb, battery_file=battery_file, + num_samples=num_samples) + self.assertEqual(adb_control.adb, mock_adb) + self.assertEqual(adb_control.battery_file, battery_file) + self.assertIsNone(adb_control.memory_file) + self.assertIsNone(adb_control.cpu_file) + self.assertEqual(adb_control.num_samples, num_samples) + self.assertIsNone(adb_control.collection_time_secs) + self.assertEqual(adb_control.sample_interval_ms, adb_monitor.DEFAULT_SAMPLE_INTERVAL_MS) + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_start_all(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + num_samples = 5 + collection_time_secs = 10 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + cpu_file=cpu_file, append_file=True, num_samples=num_samples, collection_time_secs=10, + sample_interval_ms=sample_interval_ms) + adb_control.start() + self.assertEqual(len(adb_control._all_threads), 3) + self.assertEqual(len(adb_control._sample_based_threads), 2) + mock_continuous_monitor.assert_called_once_with( + cpu_file, adb_control._should_stop, mock_adb.systrace_start, mock_adb.systrace_stop) + mock_continuous_monitor().start.assert_called_once() + self.assertEqual(mock_sample_monitor.call_count, 2) + mock_sample_monitor.assert_any_call(battery_file, adb_control._should_stop, + mock_adb.battery, 0, sample_interval_ms) + mock_sample_monitor.assert_any_call(memory_file, adb_control._should_stop, mock_adb.memory, + 0, sample_interval_ms) + self.assertEqual(mock_sample_monitor().start.call_count, 2) + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_start_cpu(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + cpu_file = "mycpu" + collection_time_secs = 10 + adb_control = adb_monitor.AdbControl(mock_adb, logger=mock_logger, cpu_file=cpu_file, + append_file=True, collection_time_secs=10) + adb_control.start() + self.assertEqual(len(adb_control._all_threads), 1) + self.assertEqual(len(adb_control._sample_based_threads), 0) + mock_continuous_monitor.assert_called_once_with( + cpu_file, adb_control._should_stop, mock_adb.systrace_start, mock_adb.systrace_stop) + mock_continuous_monitor().start.assert_called_once() + mock_sample_monitor.assert_not_called() + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_start_battery(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + num_samples = 5 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, append_file=True, + num_samples=num_samples, sample_interval_ms=sample_interval_ms) + adb_control.start() + self.assertEqual(len(adb_control._all_threads), 1) + self.assertEqual(len(adb_control._sample_based_threads), 1) + mock_continuous_monitor.assert_not_called() + mock_sample_monitor.assert_called_once_with(battery_file, adb_control._should_stop, + mock_adb.battery, num_samples, + sample_interval_ms) + mock_sample_monitor().start.assert_called_once() + + def test_stop(self): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + num_samples = 5 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, append_file=True, + num_samples=num_samples, sample_interval_ms=sample_interval_ms) + adb_control._should_stop = MagicMock() + adb_control.wait = MagicMock() + adb_control.stop() + adb_control._should_stop.set.assert_called_once() + adb_control.wait.assert_called_once() + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_wait(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + num_samples = 5 + collection_time_secs = 10 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + cpu_file=cpu_file, append_file=True, num_samples=num_samples, collection_time_secs=10, + sample_interval_ms=sample_interval_ms) + mock_sample_monitor().exception = None + mock_continuous_monitor().exception = None + adb_control._should_stop = MagicMock() adb_control.start() adb_control.wait() - for arg_file in arg_file_list: - self.assertGreater(os.stat(arg_file).st_size, 0) - os.remove(arg_file) + adb_control._should_stop.wait.assert_called_once_with(collection_time_secs) + adb_control._should_stop.set.assert_called_once() + mock_continuous_monitor().join.assert_called_once_with(adb_control._JOIN_TIMEOUT) + self.assertEqual(mock_sample_monitor().join.call_count, 2) + mock_sample_monitor().join.assert_called_with(adb_control._JOIN_TIMEOUT) + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_wait_keyboard_interrupt(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + num_samples = 5 + collection_time_secs = 10 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + cpu_file=cpu_file, append_file=True, num_samples=num_samples, collection_time_secs=10, + sample_interval_ms=sample_interval_ms) + mock_sample_monitor().exception = None + mock_continuous_monitor().exception = None + adb_control._should_stop = MagicMock() + adb_control._should_stop.wait.side_effect = KeyboardInterrupt() + adb_control.start() + adb_control.wait() + adb_control._should_stop.wait.assert_called_once_with(collection_time_secs) + adb_control._should_stop.set.assert_called_once() + mock_continuous_monitor().join.assert_called_once_with(adb_control._JOIN_TIMEOUT) + self.assertEqual(mock_sample_monitor().join.call_count, 2) + mock_sample_monitor().join.assert_called_with(adb_control._JOIN_TIMEOUT) + + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_wait_no_collection_time(self, mock_sample_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + num_samples = 5 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + append_file=True, num_samples=num_samples, sample_interval_ms=sample_interval_ms) + mock_sample_monitor().exception = None + adb_control._should_stop = MagicMock() + adb_control.start() + adb_control.wait() + adb_control._should_stop.wait.assert_not_called() + adb_control._should_stop.set.assert_called_once() + self.assertEqual(mock_sample_monitor().join.call_count, 4) + mock_sample_monitor().join.assert_called_with(adb_control._JOIN_TIMEOUT) + + @patch(ns("AdbContinuousResourceMonitor")) + @patch(ns("AdbSampleBasedResourceMonitor")) + def test_wait_thread_exception(self, mock_sample_monitor, mock_continuous_monitor): + mock_adb = MagicMock() + mock_logger = MagicMock() + battery_file = "mybattery" + memory_file = "mymemory" + cpu_file = "mycpu" + num_samples = 5 + collection_time_secs = 10 + sample_interval_ms = 25 + adb_control = adb_monitor.AdbControl( + mock_adb, logger=mock_logger, battery_file=battery_file, memory_file=memory_file, + cpu_file=cpu_file, append_file=True, num_samples=num_samples, collection_time_secs=10, + sample_interval_ms=sample_interval_ms) + mock_sample_monitor().exception = RuntimeError("my run time exception") + mock_continuous_monitor().exception = None + adb_control._should_stop = MagicMock() + adb_control.start() + with self.assertRaises(RuntimeError): + adb_control.wait() + adb_control._should_stop.wait.assert_called_once_with(collection_time_secs) + adb_control._should_stop.set.assert_called_once() + mock_continuous_monitor().join.assert_called_once_with(adb_control._JOIN_TIMEOUT) + self.assertEqual(mock_sample_monitor().join.call_count, 2) + mock_sample_monitor().join.assert_called_with(adb_control._JOIN_TIMEOUT) + + +class TestAdbResourceMonitor(unittest.TestCase): + @patch(ns("threading")) + def test_run(self, mock_threading): + output_file = "myfile" + should_stop = MagicMock() + arm = adb_monitor.AdbResourceMonitor(output_file, should_stop) + arm._do_monitoring = MagicMock() + arm.run() + self.assertEqual(arm._output_file, output_file) + arm._do_monitoring.assert_called_once() + self.assertIsNone(arm.exception) + should_stop.set.assert_not_called() + + @patch(ns("threading")) + def test_run_exception(self, mock_threading): + output_file = "myfile" + should_stop = MagicMock() + exception = RuntimeError() + arm = adb_monitor.AdbResourceMonitor(output_file, should_stop) + arm._do_monitoring = MagicMock(side_effect=exception) + arm.run() + self.assertEqual(arm._output_file, output_file) + arm._do_monitoring.assert_called_once() + self.assertEqual(arm.exception, exception) + should_stop.set.assert_called_once() + + +class TestAdbSampleBasedResourceMonitor(unittest.TestCase): + @patch(ns("threading")) + def test__do_monitoring(self, mock_threading): + output_file = "myfile" + mock_should_stop = MagicMock() + mock_should_stop.is_set.return_value = False + mock_adb_cmd = MagicMock() + num_samples = 5 + sample_interval_ms = 50 + arm = adb_monitor.AdbSampleBasedResourceMonitor(output_file, mock_should_stop, mock_adb_cmd, + num_samples, sample_interval_ms) + arm._take_sample = MagicMock() + arm._do_monitoring() + self.assertEqual(mock_should_stop.is_set.call_count, num_samples + 1) + self.assertEqual(arm._take_sample.call_count, num_samples) + self.assertEqual(mock_should_stop.wait.call_count, num_samples - 1) + mock_should_stop.wait.assert_called_with(sample_interval_ms / 1000.0) - def test_all_files_num_samples(self): - self._test_files(num_samples=5, arg_list=["battery_file", "cpu_file", "memory_file"]) + def _is_set(self): + self.num_collected += 1 + return self.num_collected == self.num_samples - def test_all_files_collection_time_secs(self): - self._test_files(collection_time_secs=3, - arg_list=["battery_file", "cpu_file", "memory_file"]) + @patch(ns("threading")) + def test__do_monitoring_no_samples(self, mock_threading): + output_file = "myfile" + mock_should_stop = MagicMock() + self.num_collected = 0 + self.num_samples = 5 + mock_should_stop.is_set = self._is_set + mock_adb_cmd = MagicMock() + num_samples = 0 + sample_interval_ms = 50 + arm = adb_monitor.AdbSampleBasedResourceMonitor(output_file, mock_should_stop, mock_adb_cmd, + num_samples, sample_interval_ms) + arm._take_sample = MagicMock() + arm._do_monitoring() + self.assertEqual(arm._take_sample.call_count, self.num_samples - 1) + self.assertEqual(mock_should_stop.wait.call_count, self.num_samples - 2) - def test_all_files_collection_and_samples(self): - self._test_files(collection_time_secs=3, num_samples=5, - arg_list=["battery_file", "cpu_file", "memory_file"]) + @patch(ns("threading")) + def test__take_sample(self, mock_threading): + output_file = "myfile" + mock_should_stop = MagicMock() + self.num_collected = 0 + self.num_samples = 5 + mock_should_stop.is_set = self._is_set + mock_adb_cmd = MagicMock() + arm = adb_monitor.AdbSampleBasedResourceMonitor(output_file, mock_should_stop, mock_adb_cmd, + 10, 20) + arm._take_sample(2) + mock_adb_cmd.assert_called_once_with(output_file=output_file, append_file=True) - def test_no_file_arg(self): - self.assertRaises(ValueError, lambda: adb_monitor.AdbControl(self.adb)) - def test_bad_file_arg(self): - self.assertRaises(TypeError, lambda: self._test_files(arg_list=["bad_file_arg"])) +class TestAdbContinuousResourceMonitor(unittest.TestCase): + @patch(ns("threading")) + def test__do_monitoring(self, mock_threading): + output_file = "myfile" + mock_should_stop = MagicMock() + mock_adb_start_cmd = MagicMock() + mock_adb_stop_cmd = MagicMock() + acrm = adb_monitor.AdbContinuousResourceMonitor(output_file, mock_should_stop, + mock_adb_start_cmd, mock_adb_stop_cmd) + acrm._do_monitoring() + mock_should_stop.wait.assert_called_once() + mock_adb_start_cmd.assert_called_with(output_file=output_file) + mock_adb_stop_cmd.assert_called_with(output_file=output_file) |