2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 # You can obtain one at http://mozilla.org/MPL/2.0/.
13 # load modules from parent dir
14 sys
.path
.insert(1, os
.path
.dirname(sys
.path
[0]))
16 from mozharness
.base
.log
import WARNING
17 from mozharness
.base
.script
import BaseScript
, PreScriptAction
18 from mozharness
.mozilla
.automation
import TBPL_RETRY
19 from mozharness
.mozilla
.mozbase
import MozbaseMixin
20 from mozharness
.mozilla
.testing
.android
import AndroidMixin
21 from mozharness
.mozilla
.testing
.codecoverage
import CodeCoverageMixin
22 from mozharness
.mozilla
.testing
.testbase
import TestingMixin
, testing_config_options
24 SUITE_DEFAULT_E10S
= ["geckoview-junit", "mochitest", "reftest"]
25 SUITE_NO_E10S
= ["cppunittest", "gtest", "jittest"]
26 SUITE_REPEATABLE
= ["mochitest", "reftest", "xpcshell"]
29 class AndroidHardwareTest(
30 TestingMixin
, BaseScript
, MozbaseMixin
, CodeCoverageMixin
, AndroidMixin
33 [["--test-suite"], {"action": "store", "dest": "test_suite", "default": None}],
40 "help": "Path to adb",
47 "dest": "total_chunks",
49 "help": "Number of total chunks",
58 "help": "Number of this chunk",
65 "dest": "log_raw_level",
67 "help": "Set log level (debug|info|warning|error|critical|fatal)",
74 "dest": "log_tbpl_level",
76 "help": "Set log level (debug|info|warning|error|critical|fatal)",
82 "action": "store_false",
85 "help": "Run tests without multiple processes (e10s).",
89 ["--disable-fission"],
91 "action": "store_true",
92 "dest": "disable_fission",
94 "help": "Run with Fission disabled.",
104 "help": "Repeat the tests the given number of times. Supported "
105 "by mochitest, reftest, crashtest, ignored otherwise.",
114 "dest": "extra_prefs",
116 "help": "Extra user prefs.",
123 "dest": "jittest_flags",
125 "help": "Flags to run with jittest (all, debug, etc.).",
128 ] + copy
.deepcopy(testing_config_options
)
130 def __init__(self
, require_config_file
=False):
131 super(AndroidHardwareTest
, self
).__init
__(
132 config_options
=self
.config_options
,
135 "download-and-extract",
141 require_config_file
=require_config_file
,
143 "virtualenv_modules": [],
144 "virtualenv_requirements": [],
145 "require_test_zip": True,
146 # IP address of the host as seen from the device.
147 "remote_webserver": os
.environ
["HOST_IP"],
151 # these are necessary since self.config is read only
153 self
.installer_url
= c
.get("installer_url")
154 self
.installer_path
= c
.get("installer_path")
155 self
.test_url
= c
.get("test_url")
156 self
.test_packages_url
= c
.get("test_packages_url")
157 self
.test_manifest
= c
.get("test_manifest")
158 suite
= c
.get("test_suite")
159 self
.test_suite
= suite
160 self
.this_chunk
= c
.get("this_chunk")
161 self
.total_chunks
= c
.get("total_chunks")
163 self
.log_raw_level
= c
.get("log_raw_level")
164 self
.log_tbpl_level
= c
.get("log_tbpl_level")
165 self
.disable_e10s
= c
.get("disable_e10s")
166 self
.disable_fission
= c
.get("disable_fission")
167 self
.extra_prefs
= c
.get("extra_prefs")
168 self
.jittest_flags
= c
.get("jittest_flags")
170 def query_abs_dirs(self
):
173 abs_dirs
= super(AndroidHardwareTest
, self
).query_abs_dirs()
175 dirs
["abs_test_install_dir"] = os
.path
.join(abs_dirs
["abs_work_dir"], "tests")
176 dirs
["abs_test_bin_dir"] = os
.path
.join(
177 abs_dirs
["abs_work_dir"], "tests", "bin"
179 dirs
["abs_modules_dir"] = os
.path
.join(dirs
["abs_test_install_dir"], "modules")
180 dirs
["abs_blob_upload_dir"] = os
.path
.join(
181 abs_dirs
["abs_work_dir"], "blobber_upload_dir"
183 dirs
["abs_mochitest_dir"] = os
.path
.join(
184 dirs
["abs_test_install_dir"], "mochitest"
186 dirs
["abs_reftest_dir"] = os
.path
.join(dirs
["abs_test_install_dir"], "reftest")
187 dirs
["abs_xpcshell_dir"] = os
.path
.join(
188 dirs
["abs_test_install_dir"], "xpcshell"
190 work_dir
= os
.environ
.get("MOZ_FETCHES_DIR") or abs_dirs
["abs_work_dir"]
191 dirs
["abs_xre_dir"] = os
.path
.join(work_dir
, "hostutils")
193 for key
in dirs
.keys():
194 if key
not in abs_dirs
:
195 abs_dirs
[key
] = dirs
[key
]
196 self
.abs_dirs
= abs_dirs
199 def _query_tests_dir(self
):
200 dirs
= self
.query_abs_dirs()
202 test_dir
= self
.config
["suite_definitions"][self
.test_suite
]["testsdir"]
204 test_dir
= self
.test_suite
205 return os
.path
.join(dirs
["abs_test_install_dir"], test_dir
)
207 def _build_command(self
):
209 dirs
= self
.query_abs_dirs()
211 if self
.test_suite
not in self
.config
["suite_definitions"]:
212 self
.fatal("Key '%s' not defined in the config!" % self
.test_suite
)
215 self
.query_python_path("python"),
218 self
._query
_tests
_dir
(),
219 self
.config
["suite_definitions"][self
.test_suite
]["run_filename"],
223 raw_log_file
, error_summary_file
= self
.get_indexed_logs(
224 dirs
["abs_blob_upload_dir"], self
.test_suite
227 str_format_values
= {
228 "device_serial": self
.device_serial
,
229 "remote_webserver": c
["remote_webserver"],
230 "xre_path": self
.xre_path
,
231 "utility_path": self
.xre_path
,
232 "http_port": "8854", # starting http port to use for the mochitest server
233 "ssl_port": "4454", # starting ssl port to use for the server
234 "certs_path": os
.path
.join(dirs
["abs_work_dir"], "tests/certs"),
235 # TestingMixin._download_and_extract_symbols() will set
236 # self.symbols_path when downloading/extracting.
237 "symbols_path": self
.symbols_path
,
238 "modules_dir": dirs
["abs_modules_dir"],
239 "installer_path": self
.installer_path
,
240 "raw_log_file": raw_log_file
,
241 "log_tbpl_level": self
.log_tbpl_level
,
242 "log_raw_level": self
.log_raw_level
,
243 "error_summary_file": error_summary_file
,
244 "xpcshell_extra": c
.get("xpcshell_extra", ""),
245 "jittest_flags": self
.jittest_flags
,
248 user_paths
= json
.loads(os
.environ
.get("MOZHARNESS_TEST_PATHS", '""'))
249 confirm_paths
= json
.loads(os
.environ
.get("MOZHARNESS_CONFIRM_PATHS", '""'))
251 for option
in self
.config
["suite_definitions"][self
.test_suite
]["options"]:
252 opt
= option
.split("=")[0]
253 # override configured chunk options with script args, if specified
254 if opt
in ("--this-chunk", "--total-chunks"):
257 or getattr(self
, opt
.replace("-", "_").strip("_"), None) is not None
261 if "%(app)" in option
:
262 # only query package name if requested
263 cmd
.extend([option
% {"app": self
.query_package_name()}])
265 option
= option
% str_format_values
269 if not self
.verify_enabled
and not user_paths
:
270 if self
.this_chunk
is not None:
271 cmd
.extend(["--this-chunk", self
.this_chunk
])
272 if self
.total_chunks
is not None:
273 cmd
.extend(["--total-chunks", self
.total_chunks
])
275 if "mochitest" in self
.test_suite
:
276 category
= "mochitest"
277 elif "reftest" in self
.test_suite
or "crashtest" in self
.test_suite
:
280 category
= self
.test_suite
282 if category
in SUITE_REPEATABLE
:
283 cmd
.extend(["--repeat=%s" % c
.get("repeat")])
285 self
.log("--repeat not supported in {}".format(category
), level
=WARNING
)
287 if category
not in SUITE_NO_E10S
:
288 if category
in SUITE_DEFAULT_E10S
and not c
["e10s"]:
289 cmd
.append("--disable-e10s")
290 elif category
not in SUITE_DEFAULT_E10S
and c
["e10s"]:
293 if self
.disable_fission
and category
not in SUITE_NO_E10S
:
294 cmd
.append("--disable-fission")
296 cmd
.extend(["--setpref={}".format(p
) for p
in self
.extra_prefs
])
298 try_options
, try_tests
= self
.try_args(self
.test_suite
)
300 cmd
.extend(try_options
)
303 # reftest on android-hw uses a subset (reftest-qr) of tests,
304 # but scheduling only knows about 'reftest'
305 suite
= self
.test_suite
306 if suite
== "reftest-qr":
309 if user_paths
.get(suite
, []):
310 suite_test_paths
= user_paths
.get(suite
, [])
311 # NOTE: we do not want to prepend 'tests' if a single path
312 if confirm_paths
and confirm_paths
.get(suite
, []):
313 suite_test_paths
= confirm_paths
.get(suite
, [])
314 suite_test_paths
= [os
.path
.join("tests", p
) for p
in suite_test_paths
]
315 cmd
.extend(suite_test_paths
)
317 elif not self
.verify_enabled
and not self
.per_test_coverage
:
319 self
.query_tests_args(
320 self
.config
["suite_definitions"][self
.test_suite
].get("tests"),
326 if self
.config
.get("restartAfterFailure", False):
327 cmd
.append("--restartAfterFailure")
331 def _query_suites(self
):
333 return [(self
.test_suite
, self
.test_suite
)]
334 # per-test mode: determine test suites to run
339 "mochitest-plain": "mochitest-plain",
340 "mochitest-plain-gpu": "mochitest-plain-gpu",
343 ("reftest", {"reftest": "reftest", "crashtest": "crashtest"}),
344 ("xpcshell", {"xpcshell": "xpcshell"}),
347 for category
, all_suites
in all
:
348 cat_suites
= self
.query_per_test_category_suites(category
, all_suites
)
349 for k
in cat_suites
.keys():
350 suites
.append((k
, cat_suites
[k
]))
353 def _query_suite_categories(self
):
355 categories
= [self
.test_suite
]
358 categories
= ["mochitest", "reftest", "xpcshell"]
361 ##########################################
362 # Actions for AndroidHardwareTest #
363 ##########################################
365 def preflight_install(self
):
366 # in the base class, this checks for mozinstall, but we don't use it
369 @PreScriptAction("create-virtualenv")
370 def pre_create_virtualenv(self
, action
):
371 dirs
= self
.query_abs_dirs()
373 suites
= self
._query
_suites
()
374 if ("mochitest-media", "mochitest-media") in suites
:
375 # mochitest-media is the only thing that needs this
376 requirements
= os
.path
.join(
377 dirs
["abs_mochitest_dir"],
378 "websocketprocessbridge",
379 "websocketprocessbridge_requirements_3.txt",
382 self
.register_virtualenv_module(requirements
=[requirements
])
384 def download_and_extract(self
):
386 Download and extract product APK, tests.zip, and host utils.
388 super(AndroidHardwareTest
, self
).download_and_extract(
389 suite_categories
=self
._query
_suite
_categories
()
391 dirs
= self
.query_abs_dirs()
392 self
.xre_path
= dirs
["abs_xre_dir"]
396 Install APKs on the device.
398 install_needed
= (not self
.test_suite
) or self
.config
["suite_definitions"][
401 if install_needed
is False:
402 self
.info("Skipping apk installation for %s" % self
.test_suite
)
405 self
.installer_path
is not None
406 ), "Either add installer_path to the config or use --installer-path."
407 self
.uninstall_android_app()
408 self
.install_android_app(self
.installer_path
)
409 self
.info("Finished installing apps for %s" % self
.device_name
)
415 self
.start_time
= datetime
.datetime
.now()
416 max_per_test_time
= datetime
.timedelta(minutes
=60)
419 suites
= self
._query
_suites
()
420 minidump
= self
.query_minidump_stackwalk()
421 for per_test_suite
, suite
in suites
:
422 self
.test_suite
= suite
425 cwd
= self
._query
_tests
_dir
()
427 self
.fatal("Don't know how to run --test-suite '%s'!" % self
.test_suite
)
428 env
= self
.query_env()
430 env
["MINIDUMP_STACKWALK"] = minidump
431 env
["MOZ_UPLOAD_DIR"] = self
.query_abs_dirs()["abs_blob_upload_dir"]
432 env
["MINIDUMP_SAVE_PATH"] = self
.query_abs_dirs()["abs_blob_upload_dir"]
433 env
["RUST_BACKTRACE"] = "full"
436 for per_test_args
in self
.query_args(per_test_suite
):
437 if (datetime
.datetime
.now() - self
.start_time
) > max_per_test_time
:
438 # Running tests has run out of time. That is okay! Stop running
439 # them so that a task timeout is not triggered, and so that
440 # (partial) results are made available in a timely manner.
442 "TinderboxPrint: Running tests took too long: "
443 "Not all tests were executed.<br/>"
445 # Signal per-test time exceeded, to break out of suites and
446 # suite categories loops also.
449 cmd
= self
._build
_command
()
450 final_cmd
= copy
.copy(cmd
)
451 if len(per_test_args
) > 0:
452 # in per-test mode, remove any chunk arguments from command
453 for arg
in final_cmd
:
454 if "total-chunk" in arg
or "this-chunk" in arg
:
455 final_cmd
.remove(arg
)
456 final_cmd
.extend(per_test_args
)
459 "Running on %s the command %s"
460 % (self
.device_name
, subprocess
.list2cmdline(final_cmd
))
462 self
.info("##### %s log begins" % self
.test_suite
)
464 suite_category
= self
.test_suite
465 parser
= self
.get_test_output_parser(
468 log_obj
=self
.log_obj
,
471 self
.run_command(final_cmd
, cwd
=cwd
, env
=env
, output_parser
=parser
)
472 tbpl_status
, log_level
, summary
= parser
.evaluate_parser(0, summary
)
473 parser
.append_tinderboxprint_line(self
.test_suite
)
475 self
.info("##### %s log ends" % self
.test_suite
)
477 if len(per_test_args
) > 0:
478 self
.record_status(tbpl_status
, level
=log_level
)
479 self
.log_per_test_status(per_test_args
[-1], tbpl_status
, log_level
)
480 if tbpl_status
== TBPL_RETRY
:
481 self
.info("Per-test run abandoned due to RETRY status")
484 self
.record_status(tbpl_status
, level
=log_level
)
485 # report as INFO instead of log_level to avoid extra Treeherder lines
487 "The %s suite: %s ran with return status: %s"
488 % (suite_category
, suite
, tbpl_status
),
492 if __name__
== "__main__":
493 test
= AndroidHardwareTest()