• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""
16Robolectric test runner class.
17
18This test runner will be short lived, once robolectric support v2 is in, then
19robolectric tests will be invoked through AtestTFTestRunner.
20"""
21
22import json
23import logging
24import os
25import re
26import tempfile
27import time
28
29from functools import partial
30
31# pylint: disable=import-error
32import atest_utils
33import constants
34
35from event_handler import EventHandler
36from test_runners import test_runner_base
37
38POLL_FREQ_SECS = 0.1
39# A pattern to match event like below
40#TEST_FAILED {'className':'SomeClass', 'testName':'SomeTestName',
41#            'trace':'{"trace":"AssertionError: <true> is equal to <false>\n
42#               at FailureStrategy.fail(FailureStrategy.java:24)\n
43#               at FailureStrategy.fail(FailureStrategy.java:20)\n"}\n\n
44EVENT_RE = re.compile(r'^(?P<event_name>[A-Z_]+) (?P<json_data>{(.\r*|\n)*})(?:\n|$)')
45
46
47class RobolectricTestRunner(test_runner_base.TestRunnerBase):
48    """Robolectric Test Runner class."""
49    NAME = 'RobolectricTestRunner'
50    # We don't actually use EXECUTABLE because we're going to use
51    # atest_utils.build to kick off the test but if we don't set it, the base
52    # class will raise an exception.
53    EXECUTABLE = 'make'
54
55    # pylint: disable=useless-super-delegation
56    def __init__(self, results_dir, **kwargs):
57        """Init stuff for robolectric runner class."""
58        super(RobolectricTestRunner, self).__init__(results_dir, **kwargs)
59        self.is_verbose = logging.getLogger().isEnabledFor(logging.DEBUG)
60
61    def run_tests(self, test_infos, extra_args, reporter):
62        """Run the list of test_infos. See base class for more.
63
64        Args:
65            test_infos: A list of TestInfos.
66            extra_args: Dict of extra args to add to test run.
67            reporter: An instance of result_report.ResultReporter.
68
69        Returns:
70            0 if tests succeed, non-zero otherwise.
71        """
72        if os.getenv(test_runner_base.OLD_OUTPUT_ENV_VAR):
73            return self.run_tests_raw(test_infos, extra_args, reporter)
74        return self.run_tests_pretty(test_infos, extra_args, reporter)
75
76    def run_tests_raw(self, test_infos, extra_args, reporter):
77        """Run the list of test_infos with raw output.
78
79        Args:
80            test_infos: List of TestInfo.
81            extra_args: Dict of extra args to add to test run.
82            reporter: A ResultReporter Instance.
83
84        Returns:
85            0 if tests succeed, non-zero otherwise.
86        """
87        reporter.register_unsupported_runner(self.NAME)
88        ret_code = constants.EXIT_CODE_SUCCESS
89        for test_info in test_infos:
90            full_env_vars = self._get_full_build_environ(test_info,
91                                                         extra_args)
92            run_cmd = self.generate_run_commands([test_info], extra_args)[0]
93            subproc = self.run(run_cmd,
94                               output_to_stdout=self.is_verbose,
95                               env_vars=full_env_vars)
96            ret_code |= self.wait_for_subprocess(subproc)
97        return ret_code
98
99    def run_tests_pretty(self, test_infos, extra_args, reporter):
100        """Run the list of test_infos with pretty output mode.
101
102        Args:
103            test_infos: List of TestInfo.
104            extra_args: Dict of extra args to add to test run.
105            reporter: A ResultReporter Instance.
106
107        Returns:
108            0 if tests succeed, non-zero otherwise.
109        """
110        ret_code = constants.EXIT_CODE_SUCCESS
111        for test_info in test_infos:
112            # Create a temp communication file.
113            with tempfile.NamedTemporaryFile(mode='w+r',
114                                             dir=self.results_dir) as event_file:
115                # Prepare build environment parameter.
116                full_env_vars = self._get_full_build_environ(test_info,
117                                                             extra_args,
118                                                             event_file)
119                run_cmd = self.generate_run_commands([test_info], extra_args)[0]
120                subproc = self.run(run_cmd,
121                                   output_to_stdout=self.is_verbose,
122                                   env_vars=full_env_vars)
123                event_handler = EventHandler(reporter, self.NAME)
124                # Start polling.
125                self.handle_subprocess(subproc, partial(self._exec_with_robo_polling,
126                                                        event_file,
127                                                        subproc,
128                                                        event_handler))
129                ret_code |= self.wait_for_subprocess(subproc)
130        return ret_code
131
132    def _get_full_build_environ(self, test_info=None, extra_args=None, event_file=None):
133        """Helper to get full build environment.
134
135       Args:
136           test_info: TestInfo object.
137           extra_args: Dict of extra args to add to test run.
138           event_file: A file-like object that can be used as a temporary storage area.
139       """
140        full_env_vars = os.environ.copy()
141        env_vars = self.generate_env_vars(test_info,
142                                          extra_args,
143                                          event_file)
144        full_env_vars.update(env_vars)
145        return full_env_vars
146
147    def _exec_with_robo_polling(self, communication_file, robo_proc, event_handler):
148        """Polling data from communication file
149
150        Polling data from communication file. Exit when communication file
151        is empty and subprocess ended.
152
153        Args:
154            communication_file: A monitored communication file.
155            robo_proc: The build process.
156            event_handler: A file-like object storing the events of robolectric tests.
157        """
158        buf = ''
159        while True:
160            data = communication_file.read()
161            buf += data
162            reg = re.compile(r'(.|\n)*}\n\n')
163            if not reg.match(buf) or data == '':
164                if robo_proc.poll() is not None:
165                    logging.debug('Build process exited early')
166                    return
167                time.sleep(POLL_FREQ_SECS)
168            else:
169                # Read all new data and handle it at one time.
170                for event in re.split(r'\n\n', buf):
171                    match = EVENT_RE.match(event)
172                    if match:
173                        try:
174                            event_data = json.loads(match.group('json_data'),
175                                                    strict=False)
176                        except ValueError:
177                            # Parse event fail, continue to parse next one.
178                            logging.debug('"%s" is not valid json format.',
179                                          match.group('json_data'))
180                            continue
181                        event_name = match.group('event_name')
182                        event_handler.process_event(event_name, event_data)
183                buf = ''
184
185    @staticmethod
186    def generate_env_vars(test_info, extra_args, event_file=None):
187        """Turn the args into env vars.
188
189        Robolectric tests specify args through env vars, so look for class
190        filters and debug args to apply to the env.
191
192        Args:
193            test_info: TestInfo class that holds the class filter info.
194            extra_args: Dict of extra args to apply for test run.
195            event_file: A file-like object storing the events of robolectric tests.
196
197        Returns:
198            Dict of env vars to pass into invocation.
199        """
200        env_var = {}
201        for arg in extra_args:
202            if constants.WAIT_FOR_DEBUGGER == arg:
203                env_var['DEBUG_ROBOLECTRIC'] = 'true'
204                continue
205        filters = test_info.data.get(constants.TI_FILTER)
206        if filters:
207            robo_filter = next(iter(filters))
208            env_var['ROBOTEST_FILTER'] = robo_filter.class_name
209            if robo_filter.methods:
210                logging.debug('method filtering not supported for robolectric '
211                              'tests yet.')
212        if event_file:
213            env_var['EVENT_FILE_ROBOLECTRIC'] = event_file.name
214        return env_var
215
216    def host_env_check(self):
217        """Check that host env has everything we need.
218
219        We actually can assume the host env is fine because we have the same
220        requirements that atest has. Update this to check for android env vars
221        if that changes.
222        """
223        pass
224
225    def get_test_runner_build_reqs(self):
226        """Return the build requirements.
227
228        Returns:
229            Set of build targets.
230        """
231        return set()
232
233    # pylint: disable=unused-argument
234    def generate_run_commands(self, test_infos, extra_args, port=None):
235        """Generate a list of run commands from TestInfos.
236
237        Args:
238            test_infos: A set of TestInfo instances.
239            extra_args: A Dict of extra args to append.
240            port: Optional. An int of the port number to send events to.
241                  Subprocess reporter in TF won't try to connect if it's None.
242
243        Returns:
244            A list of run commands to run the tests.
245        """
246        run_cmds = []
247        for test_info in test_infos:
248            robo_command = atest_utils.BUILD_CMD + [str(test_info.test_name)]
249            run_cmd = ' '.join(x for x in robo_command)
250            if constants.DRY_RUN in extra_args:
251                run_cmd = run_cmd.replace(
252                    os.environ.get(constants.ANDROID_BUILD_TOP) + os.sep, '')
253            run_cmds.append(run_cmd)
254        return run_cmds
255