Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright 2018, The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # |
| 17 | |
| 18 | """ |
| 19 | Unit tests for the app_startup_runner.py script. |
| 20 | |
| 21 | Install: |
| 22 | $> sudo apt-get install python3-pytest ## OR |
| 23 | $> pip install -U pytest |
| 24 | See also https://docs.pytest.org/en/latest/getting-started.html |
| 25 | |
| 26 | Usage: |
| 27 | $> ./app_startup_runner_test.py |
| 28 | $> pytest app_startup_runner_test.py |
| 29 | $> python -m pytest app_startup_runner_test.py |
| 30 | |
| 31 | See also https://docs.pytest.org/en/latest/usage.html |
| 32 | """ |
| 33 | |
| 34 | # global imports |
| 35 | from contextlib import contextmanager |
| 36 | import io |
| 37 | import shlex |
| 38 | import sys |
| 39 | import typing |
| 40 | |
| 41 | # pip imports |
| 42 | import pytest |
| 43 | |
| 44 | # local imports |
| 45 | import app_startup_runner as asr |
| 46 | |
| 47 | # |
| 48 | # Argument Parsing Helpers |
| 49 | # |
| 50 | |
| 51 | @contextmanager |
| 52 | def ignore_stdout_stderr(): |
| 53 | """Ignore stdout/stderr output for duration of this context.""" |
| 54 | old_stdout = sys.stdout |
| 55 | old_stderr = sys.stderr |
| 56 | sys.stdout = io.StringIO() |
| 57 | sys.stderr = io.StringIO() |
| 58 | try: |
| 59 | yield |
| 60 | finally: |
| 61 | sys.stdout = old_stdout |
| 62 | sys.stderr = old_stderr |
| 63 | |
| 64 | @contextmanager |
| 65 | def argparse_bad_argument(msg): |
| 66 | """ |
| 67 | Assert that a SystemExit is raised when executing this context. |
| 68 | If the assertion fails, print the message 'msg'. |
| 69 | """ |
| 70 | with pytest.raises(SystemExit, message=msg): |
| 71 | with ignore_stdout_stderr(): |
| 72 | yield |
| 73 | |
| 74 | def assert_bad_argument(args, msg): |
| 75 | """ |
| 76 | Assert that the command line arguments in 'args' are malformed. |
| 77 | Prints 'msg' if the assertion fails. |
| 78 | """ |
| 79 | with argparse_bad_argument(msg): |
| 80 | parse_args(args) |
| 81 | |
| 82 | def parse_args(args): |
| 83 | """ |
| 84 | :param args: command-line like arguments as a single string |
| 85 | :return: dictionary of parsed key/values |
| 86 | """ |
| 87 | # "-a b -c d" => ['-a', 'b', '-c', 'd'] |
| 88 | return vars(asr.parse_options(shlex.split(args))) |
| 89 | |
| 90 | def default_dict_for_parsed_args(**kwargs): |
| 91 | """ |
| 92 | # Combine it with all of the "optional" parameters' default values. |
| 93 | """ |
| 94 | d = {'compiler_filters': None, 'simulate': False, 'debug': False, 'output': None, 'timeout': None, 'loop_count': 1, 'inodes': None} |
| 95 | d.update(kwargs) |
| 96 | return d |
| 97 | |
| 98 | def default_mock_dict_for_parsed_args(include_optional=True, **kwargs): |
| 99 | """ |
| 100 | Combine default dict with all optional parameters with some mock required parameters. |
| 101 | """ |
| 102 | d = {'packages': ['com.fake.package'], 'readaheads': ['warm']} |
| 103 | if include_optional: |
| 104 | d.update(default_dict_for_parsed_args()) |
| 105 | d.update(kwargs) |
| 106 | return d |
| 107 | |
| 108 | def parse_optional_args(str): |
| 109 | """ |
| 110 | Parse an argument string which already includes all the required arguments |
| 111 | in default_mock_dict_for_parsed_args. |
| 112 | """ |
| 113 | req = "--package com.fake.package --readahead warm" |
| 114 | return parse_args("%s %s" %(req, str)) |
| 115 | |
| 116 | def test_argparse(): |
| 117 | # missing arguments |
| 118 | assert_bad_argument("", "-p and -r are required") |
| 119 | assert_bad_argument("-r warm", "-p is required") |
| 120 | assert_bad_argument("--readahead warm", "-p is required") |
| 121 | assert_bad_argument("-p com.fake.package", "-r is required") |
| 122 | assert_bad_argument("--package com.fake.package", "-r is required") |
| 123 | |
| 124 | # required arguments are parsed correctly |
| 125 | ad = default_dict_for_parsed_args # assert dict |
| 126 | |
| 127 | assert parse_args("--package xyz --readahead warm") == ad(packages=['xyz'], readaheads=['warm']) |
| 128 | assert parse_args("-p xyz -r warm") == ad(packages=['xyz'], readaheads=['warm']) |
| 129 | |
| 130 | assert parse_args("-p xyz -r warm -s") == ad(packages=['xyz'], readaheads=['warm'], simulate=True) |
| 131 | assert parse_args("-p xyz -r warm --simulate") == ad(packages=['xyz'], readaheads=['warm'], simulate=True) |
| 132 | |
| 133 | # optional arguments are parsed correctly. |
| 134 | mad = default_mock_dict_for_parsed_args # mock assert dict |
| 135 | assert parse_optional_args("--output filename.csv") == mad(output='filename.csv') |
| 136 | assert parse_optional_args("-o filename.csv") == mad(output='filename.csv') |
| 137 | |
| 138 | assert parse_optional_args("--timeout 123") == mad(timeout=123) |
| 139 | assert parse_optional_args("-t 456") == mad(timeout=456) |
| 140 | |
| 141 | assert parse_optional_args("--loop-count 123") == mad(loop_count=123) |
| 142 | assert parse_optional_args("-lc 456") == mad(loop_count=456) |
| 143 | |
| 144 | assert parse_optional_args("--inodes bar") == mad(inodes="bar") |
| 145 | assert parse_optional_args("-in baz") == mad(inodes="baz") |
| 146 | |
| 147 | |
| 148 | def generate_run_combinations(*args): |
| 149 | # expand out the generator values so that assert x == y works properly. |
| 150 | return [i for i in asr.generate_run_combinations(*args)] |
| 151 | |
| 152 | def test_generate_run_combinations(): |
| 153 | blank_nd = typing.NamedTuple('Blank') |
| 154 | assert generate_run_combinations(blank_nd, {}) == [()], "empty" |
| 155 | assert generate_run_combinations(blank_nd, {'a' : ['a1', 'a2']}) == [()], "empty filter" |
| 156 | a_nd = typing.NamedTuple('A', [('a', str)]) |
| 157 | assert generate_run_combinations(a_nd, {'a': None}) == [(None,)], "None" |
| 158 | assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}) == [('a1',), ('a2',)], "one item" |
| 159 | assert generate_run_combinations(a_nd, |
| 160 | {'a' : ['a1', 'a2'], 'b': ['b1', 'b2']}) == [('a1',), ('a2',)],\ |
| 161 | "one item filter" |
| 162 | ab_nd = typing.NamedTuple('AB', [('a', str), ('b', str)]) |
| 163 | assert generate_run_combinations(ab_nd, |
| 164 | {'a': ['a1', 'a2'], |
| 165 | 'b': ['b1', 'b2']}) == [ab_nd('a1', 'b1'), |
| 166 | ab_nd('a1', 'b2'), |
| 167 | ab_nd('a2', 'b1'), |
| 168 | ab_nd('a2', 'b2')],\ |
| 169 | "two items" |
| 170 | |
| 171 | assert generate_run_combinations(ab_nd, |
| 172 | {'as': ['a1', 'a2'], |
| 173 | 'bs': ['b1', 'b2']}) == [ab_nd('a1', 'b1'), |
| 174 | ab_nd('a1', 'b2'), |
| 175 | ab_nd('a2', 'b1'), |
| 176 | ab_nd('a2', 'b2')],\ |
| 177 | "two items plural" |
| 178 | |
| 179 | def test_key_to_cmdline_flag(): |
| 180 | assert asr.key_to_cmdline_flag("abc") == "--abc" |
| 181 | assert asr.key_to_cmdline_flag("foos") == "--foo" |
| 182 | assert asr.key_to_cmdline_flag("ba_r") == "--ba-r" |
| 183 | assert asr.key_to_cmdline_flag("ba_zs") == "--ba-z" |
| 184 | |
| 185 | |
| 186 | def test_make_script_command_with_temp_output(): |
| 187 | cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script", args=[], count=1) |
| 188 | with tmp_file: |
| 189 | assert cmd_str == ["fake_script", "--count", "1", "--output", tmp_file.name] |
| 190 | |
| 191 | cmd_str, tmp_file = asr.make_script_command_with_temp_output("fake_script", args=['a', 'b'], count=2) |
| 192 | with tmp_file: |
| 193 | assert cmd_str == ["fake_script", "a", "b", "--count", "2", "--output", tmp_file.name] |
| 194 | |
| 195 | def test_parse_run_script_csv_file(): |
| 196 | # empty file -> empty list |
| 197 | f = io.StringIO("") |
| 198 | assert asr.parse_run_script_csv_file(f) == [] |
| 199 | |
| 200 | # common case |
| 201 | f = io.StringIO("1,2,3") |
| 202 | assert asr.parse_run_script_csv_file(f) == [1,2,3] |
| 203 | |
| 204 | # ignore trailing comma |
| 205 | f = io.StringIO("1,2,3,4,5,") |
| 206 | assert asr.parse_run_script_csv_file(f) == [1,2,3,4,5] |
| 207 | |
| 208 | |
| 209 | if __name__ == '__main__': |
| 210 | pytest.main() |