2 # ***** BEGIN LICENSE BLOCK *****
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 # You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ***** END LICENSE BLOCK *****
15 # load modules from parent dir
16 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
17 sys
.path
.insert(1, os
.path
.dirname(here
))
19 from mozharness
.base
.log
import WARNING
20 from mozharness
.base
.script
import BaseScript
, PreScriptAction
21 from mozharness
.mozilla
.automation
import TBPL_RETRY
22 from mozharness
.mozilla
.mozbase
import MozbaseMixin
23 from mozharness
.mozilla
.testing
.android
import AndroidMixin
24 from mozharness
.mozilla
.testing
.codecoverage
import (
26 code_coverage_config_options
,
28 from mozharness
.mozilla
.testing
.testbase
import TestingMixin
, testing_config_options
30 SUITE_DEFAULT_E10S
= ["geckoview-junit", "mochitest", "reftest"]
31 SUITE_NO_E10S
= ["cppunittest", "gtest", "jittest", "xpcshell"]
32 SUITE_REPEATABLE
= ["mochitest", "reftest", "xpcshell"]
35 class AndroidEmulatorTest(
36 TestingMixin
, BaseScript
, MozbaseMixin
, CodeCoverageMixin
, AndroidMixin
39 A mozharness script for Android functional tests (like mochitests and reftests)
40 run on an Android emulator. This script starts and manages an Android emulator
41 for the duration of the required tests. This is like desktop_unittest.py, but
42 for Android emulator test platforms.
49 {"action": "store", "dest": "test_suite", "default": None},
55 "dest": "total_chunks",
57 "help": "Number of total chunks",
66 "help": "Number of this chunk",
70 ["--enable-xorigin-tests"],
72 "action": "store_true",
73 "dest": "enable_xorigin_tests",
75 "help": "Run tests in a cross origin iframe.",
81 "action": "store_true",
82 "dest": "gpu_required",
84 "help": "Run additional verification on modified tests using gpu instances.",
91 "dest": "log_raw_level",
93 "help": "Set log level (debug|info|warning|error|critical|fatal)",
100 "dest": "log_tbpl_level",
102 "help": "Set log level (debug|info|warning|error|critical|fatal)",
108 "action": "store_false",
111 "help": "Run tests without multiple processes (e10s).",
115 ["--disable-fission"],
117 "action": "store_true",
118 "dest": "disable_fission",
120 "help": "Run without Fission enabled.",
124 ["--web-content-isolation-strategy"],
128 "dest": "web_content_isolation_strategy",
129 "help": "Strategy used to determine whether or not a particular site should"
130 "load into a webIsolated content process, see "
131 "fission.webContentIsolationStrategy.",
141 "help": "Repeat the tests the given number of times. Supported "
142 "by mochitest, reftest, crashtest, ignored otherwise.",
149 "metavar": "PREF=VALUE",
150 "dest": "extra_prefs",
152 "help": "Extra user prefs.",
156 + copy
.deepcopy(testing_config_options
)
157 + copy
.deepcopy(code_coverage_config_options
)
160 def __init__(self
, require_config_file
=False):
161 super(AndroidEmulatorTest
, self
).__init
__(
162 config_options
=self
.config_options
,
165 "download-and-extract",
172 require_config_file
=require_config_file
,
174 "virtualenv_modules": [],
175 "virtualenv_requirements": [],
176 "require_test_zip": True,
180 # these are necessary since self.config is read only
182 self
.installer_url
= c
.get("installer_url")
183 self
.installer_path
= c
.get("installer_path")
184 self
.test_url
= c
.get("test_url")
185 self
.test_packages_url
= c
.get("test_packages_url")
186 self
.test_manifest
= c
.get("test_manifest")
187 suite
= c
.get("test_suite")
188 self
.test_suite
= suite
189 self
.this_chunk
= c
.get("this_chunk")
190 self
.total_chunks
= c
.get("total_chunks")
192 self
.device_serial
= "emulator-5554"
193 self
.log_raw_level
= c
.get("log_raw_level")
194 self
.log_tbpl_level
= c
.get("log_tbpl_level")
195 # AndroidMixin uses this when launching the emulator. We only want
196 # GLES3 if we're running WebRender (default)
197 self
.use_gles3
= True
198 self
.disable_e10s
= c
.get("disable_e10s")
199 self
.disable_fission
= c
.get("disable_fission")
200 self
.web_content_isolation_strategy
= c
.get("web_content_isolation_strategy")
201 self
.extra_prefs
= c
.get("extra_prefs")
203 def query_abs_dirs(self
):
206 abs_dirs
= super(AndroidEmulatorTest
, self
).query_abs_dirs()
208 dirs
["abs_test_install_dir"] = os
.path
.join(abs_dirs
["abs_work_dir"], "tests")
209 dirs
["abs_test_bin_dir"] = os
.path
.join(
210 abs_dirs
["abs_work_dir"], "tests", "bin"
212 dirs
["abs_xre_dir"] = os
.path
.join(abs_dirs
["abs_work_dir"], "hostutils")
213 dirs
["abs_modules_dir"] = os
.path
.join(dirs
["abs_test_install_dir"], "modules")
214 dirs
["abs_blob_upload_dir"] = os
.path
.join(
215 abs_dirs
["abs_work_dir"], "blobber_upload_dir"
217 dirs
["abs_mochitest_dir"] = os
.path
.join(
218 dirs
["abs_test_install_dir"], "mochitest"
220 dirs
["abs_reftest_dir"] = os
.path
.join(dirs
["abs_test_install_dir"], "reftest")
221 dirs
["abs_xpcshell_dir"] = os
.path
.join(
222 dirs
["abs_test_install_dir"], "xpcshell"
224 work_dir
= os
.environ
.get("MOZ_FETCHES_DIR") or abs_dirs
["abs_work_dir"]
225 dirs
["abs_sdk_dir"] = os
.path
.join(work_dir
, "android-sdk-linux")
226 dirs
["abs_avds_dir"] = os
.path
.join(work_dir
, "android-device")
227 dirs
["abs_bundletool_path"] = os
.path
.join(work_dir
, "bundletool.jar")
229 for key
in dirs
.keys():
230 if key
not in abs_dirs
:
231 abs_dirs
[key
] = dirs
[key
]
232 self
.abs_dirs
= abs_dirs
235 def _query_tests_dir(self
, test_suite
):
236 dirs
= self
.query_abs_dirs()
238 test_dir
= self
.config
["suite_definitions"][test_suite
]["testsdir"]
240 test_dir
= test_suite
241 return os
.path
.join(dirs
["abs_test_install_dir"], test_dir
)
243 def _get_mozharness_test_paths(self
, suite
):
244 test_paths
= os
.environ
.get("MOZHARNESS_TEST_PATHS")
248 return json
.loads(test_paths
).get(suite
)
250 def _build_command(self
):
252 dirs
= self
.query_abs_dirs()
254 if self
.test_suite
not in self
.config
["suite_definitions"]:
255 self
.fatal("Key '%s' not defined in the config!" % self
.test_suite
)
258 self
.query_python_path("python"),
261 self
._query
_tests
_dir
(self
.test_suite
),
262 self
.config
["suite_definitions"][self
.test_suite
]["run_filename"],
266 raw_log_file
, error_summary_file
= self
.get_indexed_logs(
267 dirs
["abs_blob_upload_dir"], self
.test_suite
270 str_format_values
= {
271 "device_serial": self
.device_serial
,
272 # IP address of the host as seen from the emulator
273 "remote_webserver": "10.0.2.2",
274 "xre_path": self
.xre_path
,
275 "utility_path": self
.xre_path
,
276 "http_port": "8854", # starting http port to use for the mochitest server
277 "ssl_port": "4454", # starting ssl port to use for the server
278 "certs_path": os
.path
.join(dirs
["abs_work_dir"], "tests/certs"),
279 # TestingMixin._download_and_extract_symbols() will set
280 # self.symbols_path when downloading/extracting.
281 "symbols_path": self
.symbols_path
,
282 "modules_dir": dirs
["abs_modules_dir"],
283 "installer_path": self
.installer_path
,
284 "raw_log_file": raw_log_file
,
285 "log_tbpl_level": self
.log_tbpl_level
,
286 "log_raw_level": self
.log_raw_level
,
287 "error_summary_file": error_summary_file
,
288 "xpcshell_extra": c
.get("xpcshell_extra", ""),
289 "gtest_dir": os
.path
.join(dirs
["abs_test_install_dir"], "gtest"),
292 user_paths
= self
._get
_mozharness
_test
_paths
(self
.test_suite
)
294 for option
in self
.config
["suite_definitions"][self
.test_suite
]["options"]:
295 opt
= option
.split("=")[0]
296 # override configured chunk options with script args, if specified
297 if opt
in ("--this-chunk", "--total-chunks"):
300 or getattr(self
, opt
.replace("-", "_").strip("_"), None) is not None
304 if "%(app)" in option
:
305 # only query package name if requested
306 cmd
.extend([option
% {"app": self
.query_package_name()}])
308 option
= option
% str_format_values
312 if "mochitest" in self
.test_suite
:
313 category
= "mochitest"
314 elif "reftest" in self
.test_suite
or "crashtest" in self
.test_suite
:
317 category
= self
.test_suite
319 if category
in SUITE_REPEATABLE
:
320 cmd
.extend(["--repeat=%s" % c
.get("repeat")])
322 self
.log("--repeat not supported in {}".format(category
), level
=WARNING
)
324 # do not add --disable fission if we don't have --disable-e10s
325 if c
["disable_fission"] and category
not in ["gtest", "cppunittest"]:
326 cmd
.append("--disable-fission")
328 if "web_content_isolation_strategy" in c
:
330 "--web-content-isolation-strategy=%s"
331 % c
["web_content_isolation_strategy"]
333 cmd
.extend(["--setpref={}".format(p
) for p
in self
.extra_prefs
])
335 if not (self
.verify_enabled
or self
.per_test_coverage
):
337 cmd
.extend(user_paths
)
338 elif not (self
.verify_enabled
or self
.per_test_coverage
):
339 if self
.this_chunk
is not None:
340 cmd
.extend(["--this-chunk", self
.this_chunk
])
341 if self
.total_chunks
is not None:
342 cmd
.extend(["--total-chunks", self
.total_chunks
])
344 if category
not in SUITE_NO_E10S
:
345 if category
in SUITE_DEFAULT_E10S
and not c
["e10s"]:
346 cmd
.append("--disable-e10s")
347 elif category
not in SUITE_DEFAULT_E10S
and c
["e10s"]:
350 if c
.get("enable_xorigin_tests"):
351 cmd
.extend(["--enable-xorigin-tests"])
353 try_options
, try_tests
= self
.try_args(self
.test_suite
)
354 cmd
.extend(try_options
)
355 if not self
.verify_enabled
and not self
.per_test_coverage
:
357 self
.query_tests_args(
358 self
.config
["suite_definitions"][self
.test_suite
].get("tests"),
364 if self
.java_code_coverage_enabled
:
368 "--coverage-output-dir",
369 self
.java_coverage_output_dir
,
375 def _query_suites(self
):
377 return [(self
.test_suite
, self
.test_suite
)]
378 # per-test mode: determine test suites to run
380 # For each test category, provide a list of supported sub-suites and a mapping
381 # between the per_test_base suite name and the android suite name.
386 "mochitest-plain": "mochitest-plain",
387 "mochitest-media": "mochitest-media",
388 "mochitest-plain-gpu": "mochitest-plain-gpu",
394 "reftest": "reftest",
395 "crashtest": "crashtest",
396 "jsreftest": "jsreftest",
399 ("xpcshell", {"xpcshell": "xpcshell"}),
402 for category
, all_suites
in all
:
403 cat_suites
= self
.query_per_test_category_suites(category
, all_suites
)
404 for k
in cat_suites
.keys():
405 suites
.append((k
, cat_suites
[k
]))
408 def _query_suite_categories(self
):
410 categories
= [self
.test_suite
]
413 categories
= ["mochitest", "reftest", "xpcshell"]
416 ##########################################
417 # Actions for AndroidEmulatorTest #
418 ##########################################
420 def preflight_install(self
):
421 # in the base class, this checks for mozinstall, but we don't use it
424 @PreScriptAction("create-virtualenv")
425 def pre_create_virtualenv(self
, action
):
426 dirs
= self
.query_abs_dirs()
428 suites
= self
._query
_suites
()
429 if ("mochitest-media", "mochitest-media") in suites
:
430 # mochitest-media is the only thing that needs this
431 requirements
= os
.path
.join(
432 dirs
["abs_mochitest_dir"],
433 "websocketprocessbridge",
434 "websocketprocessbridge_requirements_3.txt",
437 self
.register_virtualenv_module(requirements
=[requirements
], two_pass
=True)
439 def download_and_extract(self
):
441 Download and extract product APK, tests.zip, and host utils.
443 super(AndroidEmulatorTest
, self
).download_and_extract(
444 suite_categories
=self
._query
_suite
_categories
()
446 dirs
= self
.query_abs_dirs()
447 self
.xre_path
= self
.download_hostutils(dirs
["abs_xre_dir"])
451 Install APKs on the device.
453 install_needed
= (not self
.test_suite
) or self
.config
["suite_definitions"][
456 if install_needed
is False:
457 self
.info("Skipping apk installation for %s" % self
.test_suite
)
460 self
.installer_path
is not None
461 ), "Either add installer_path to the config or use --installer-path."
462 self
.install_android_app(self
.installer_path
)
463 self
.info("Finished installing apps for %s" % self
.device_serial
)
469 self
.start_time
= datetime
.datetime
.now()
470 max_per_test_time
= datetime
.timedelta(minutes
=60)
473 suites
= self
._query
_suites
()
474 minidump
= self
.query_minidump_stackwalk()
475 for per_test_suite
, suite
in suites
:
476 self
.test_suite
= suite
479 cwd
= self
._query
_tests
_dir
(self
.test_suite
)
481 self
.fatal("Don't know how to run --test-suite '%s'!" % self
.test_suite
)
483 env
= self
.query_env()
485 env
["MINIDUMP_STACKWALK"] = minidump
486 env
["MOZ_UPLOAD_DIR"] = self
.query_abs_dirs()["abs_blob_upload_dir"]
487 env
["MINIDUMP_SAVE_PATH"] = self
.query_abs_dirs()["abs_blob_upload_dir"]
488 env
["RUST_BACKTRACE"] = "full"
489 if self
.config
["nodejs_path"]:
490 env
["MOZ_NODE_PATH"] = self
.config
["nodejs_path"]
493 for per_test_args
in self
.query_args(per_test_suite
):
494 if (datetime
.datetime
.now() - self
.start_time
) > max_per_test_time
:
495 # Running tests has run out of time. That is okay! Stop running
496 # them so that a task timeout is not triggered, and so that
497 # (partial) results are made available in a timely manner.
499 "TinderboxPrint: Running tests took too long: "
500 "Not all tests were executed.<br/>"
502 # Signal per-test time exceeded, to break out of suites and
503 # suite categories loops also.
506 cmd
= self
._build
_command
()
507 final_cmd
= copy
.copy(cmd
)
508 if len(per_test_args
) > 0:
509 # in per-test mode, remove any chunk arguments from command
510 for arg
in final_cmd
:
511 if "total-chunk" in arg
or "this-chunk" in arg
:
512 final_cmd
.remove(arg
)
513 final_cmd
.extend(per_test_args
)
515 self
.info("Running the command %s" % subprocess
.list2cmdline(final_cmd
))
516 self
.info("##### %s log begins" % self
.test_suite
)
518 suite_category
= self
.test_suite
519 parser
= self
.get_test_output_parser(
522 log_obj
=self
.log_obj
,
525 self
.run_command(final_cmd
, cwd
=cwd
, env
=env
, output_parser
=parser
)
526 tbpl_status
, log_level
, summary
= parser
.evaluate_parser(
527 0, previous_summary
=summary
529 parser
.append_tinderboxprint_line(self
.test_suite
)
531 self
.info("##### %s log ends" % self
.test_suite
)
533 if len(per_test_args
) > 0:
534 self
.record_status(tbpl_status
, level
=log_level
)
535 self
.log_per_test_status(per_test_args
[-1], tbpl_status
, log_level
)
536 if tbpl_status
== TBPL_RETRY
:
537 self
.info("Per-test run abandoned due to RETRY status")
540 self
.record_status(tbpl_status
, level
=log_level
)
541 # report as INFO instead of log_level to avoid extra Treeherder lines
543 "The %s suite: %s ran with return status: %s"
544 % (suite_category
, suite
, tbpl_status
),
548 if __name__
== "__main__":
549 test
= AndroidEmulatorTest()