def run_one_test(self, prog, env, symbols_path=None): """ Run a single C++ unit test program remotely. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) remote_bin = posixpath.join(self.remote_bin_dir, basename) log.info("Running test %s", basename) buf = StringIO.StringIO() returncode = self.device.shell( [remote_bin], buf, env=env, cwd=self.remote_home_dir, timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT) print >> sys.stdout, buf.getvalue() with mozfile.TemporaryDirectory() as tempdir: self.device.getDirectory(self.remote_home_dir, tempdir) if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): log.testFail("%s | test crashed", basename) return False result = returncode == 0 if not result: log.testFail("%s | test failed with return code %s", basename, returncode) return result
def checkForCrashes(self, dump_directory, symbols_path, test_name=None): with mozfile.TemporaryDirectory() as dumpDir: self.device.pull(self.remoteMinidumpDir, dumpDir) crashed = mozcrash.log_crashes( self.log, dumpDir, symbols_path, test=test_name ) return crashed
def create_dmg(source_directory, output_dmg, volume_name, extra_files): ''' Create a DMG disk image at the path output_dmg from source_directory. Use volume_name as the disk image volume name, and use extra_files as a list of tuples of (filename, relative path) to copy into the disk image. ''' with mozfile.TemporaryDirectory() as tmpdir: stagedir = os.path.join(tmpdir, 'stage') os.mkdir(stagedir) # Copy the app bundle over using rsync rsync(source_directory, stagedir) # Copy extra files for source, target in extra_files: full_target = os.path.join(stagedir, target) mkdir(os.path.dirname(full_target)) shutil.copyfile(source, full_target) # Make a symlink to /Applications. The symlink name is a space # so we don't have to localize it. The Applications folder icon # will be shown in Finder, which should be clear enough for users. os.symlink('/Applications', os.path.join(stagedir, ' ')) # Set the folder attributes to use a custom icon set_folder_icon(stagedir) chmod(stagedir) create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
def checkForCrashes(self, dump_directory, symbols_path, test_name=None): with mozfile.TemporaryDirectory() as dumpDir: self.device.pull(self.remoteMinidumpDir, dumpDir) crashed = xpcshell.XPCShellTestThread.checkForCrashes( self, dumpDir, symbols_path, test_name) self.initDir(self.remoteMinidumpDir) return crashed
def run_one_test(self, prog, env, symbols_path=None): """ Run a single C++ unit test program. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) log.info("Running test %s", basename) with mozfile.TemporaryDirectory() as tempdir: proc = mozprocess.ProcessHandler([prog], cwd=tempdir, env=env) #TODO: After bug 811320 is fixed, don't let .run() kill the process, # instead use a timeout in .wait() and then kill to get a stack. proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT, outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT) proc.wait() if proc.timedOut: log.testFail("%s | timed out after %d seconds", basename, CPPUnitTests.TEST_PROC_TIMEOUT) return False if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): log.testFail("%s | test crashed", basename) return False result = proc.proc.returncode == 0 if not result: log.testFail("%s | test failed with return code %d", basename, proc.proc.returncode) return result
def run_one_test(self, prog, env, symbols_path=None, interactive=False, timeout_factor=1): """ Run a single C++ unit test program. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) self.log.test_start(basename) with mozfile.TemporaryDirectory() as tempdir: if interactive: # For tests run locally, via mach, print output directly proc = mozprocess.ProcessHandler([prog], cwd=tempdir, env=env, storeOutput=False) else: proc = mozprocess.ProcessHandler([prog], cwd=tempdir, env=env, storeOutput=True, processOutputLine=lambda _: None) # TODO: After bug 811320 is fixed, don't let .run() kill the process, # instead use a timeout in .wait() and then kill to get a stack. test_timeout = CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor proc.run(timeout=test_timeout, outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT) proc.wait() if proc.output: if self.fix_stack: procOutput = [self.fix_stack(l) for l in proc.output] else: procOutput = proc.output output = "\n%s" % "\n".join(procOutput) self.log.process_output(proc.pid, output, command=[prog]) if proc.timedOut: message = "timed out after %d seconds" % CPPUnitTests.TEST_PROC_TIMEOUT self.log.test_end(basename, status='TIMEOUT', expected='PASS', message=message) return False if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): self.log.test_end(basename, status='CRASH', expected='PASS') return False result = proc.proc.returncode == 0 if not result: self.log.test_end(basename, status='FAIL', expected='PASS', message=("test failed with return code %d" % proc.proc.returncode)) else: self.log.test_end(basename, status='PASS', expected='PASS') return result
def create_dmg(source_directory, output_dmg, volume_name, extra_files): ''' Create a DMG disk image at the path output_dmg from source_directory. Use volume_name as the disk image volume name, and use extra_files as a list of tuples of (filename, relative path) to copy into the disk image. ''' if platform.system() not in ('Darwin', 'Linux'): raise Exception("Don't know how to build a DMG on '%s'" % platform.system()) if is_linux: check_tools('DMG_TOOL', 'MKFSHFS', 'HFS_TOOL') with mozfile.TemporaryDirectory() as tmpdir: stagedir = os.path.join(tmpdir, 'stage') os.mkdir(stagedir) # Copy the app bundle over using rsync rsync(source_directory, stagedir) # Copy extra files for source, target in extra_files: full_target = os.path.join(stagedir, target) mkdir(os.path.dirname(full_target)) shutil.copyfile(source, full_target) generate_hfs_file(stagedir, tmpdir, volume_name) create_app_symlink(stagedir, tmpdir) # Set the folder attributes to use a custom icon set_folder_icon(stagedir, tmpdir) chmod(stagedir) create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)
def extract_dmg(dmgfile, output, dsstore=None, icon=None, background=None): if platform.system() not in ("Darwin", "Linux"): raise Exception("Don't know how to extract a DMG on '%s'" % platform.system()) if is_linux: check_tools("DMG_TOOL", "MKFSHFS", "HFS_TOOL") with mozfile.TemporaryDirectory() as tmpdir: extract_dmg_contents(dmgfile, tmpdir) if os.path.islink(os.path.join(tmpdir, " ")): # Rsync will fail on the presence of this symlink os.remove(os.path.join(tmpdir, " ")) rsync(tmpdir, output) if dsstore: mkdir(os.path.dirname(dsstore)) rsync(os.path.join(tmpdir, ".DS_Store"), dsstore) if background: mkdir(os.path.dirname(background)) rsync( os.path.join(tmpdir, ".background", os.path.basename(background)), background, ) if icon: mkdir(os.path.dirname(icon)) rsync(os.path.join(tmpdir, ".VolumeIcon.icns"), icon)
def run_one_test(self, prog, env, symbols_path=None, interactive=False, timeout_factor=1): """ Run a single C++ unit test program remotely. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) remote_bin = posixpath.join(self.remote_bin_dir, basename) self.log.test_start(basename) test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor try: output = self.device.shell_output(remote_bin, env=env, cwd=self.remote_home_dir, timeout=test_timeout) returncode = 0 except ADBTimeoutError: raise except ADBProcessError as e: output = e.adb_process.stdout returncode = e.adb_process.exitcode self.log.process_output(basename, "\n%s" % output, command=[remote_bin]) with mozfile.TemporaryDirectory() as tempdir: self.device.pull(self.remote_home_dir, tempdir) if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): self.log.test_end(basename, status="CRASH", expected="PASS") return False result = returncode == 0 if not result: self.log.test_end( basename, status="FAIL", expected="PASS", message=("test failed with return code %s" % returncode), ) else: self.log.test_end(basename, status="PASS", expected="PASS") return result
def checkForCrashes(self, dump_directory, symbols_path, test_name=None): if not self.device.dirExists(self.remoteMinidumpDir): # The minidumps directory is automatically created when Fennec # (first) starts, so its lack of presence is a hint that # something went wrong. print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir # Whilst no crash was found, the run should still display as a failure return True with mozfile.TemporaryDirectory() as dumpDir: self.device.getDirectory(self.remoteMinidumpDir, dumpDir) crashed = xpcshell.XPCShellTestThread.checkForCrashes( self, dumpDir, symbols_path, test_name) self.clearRemoteDir(self.remoteMinidumpDir) return crashed
def run_one_test(self, prog, env, symbols_path=None, interactive=False, timeout_factor=1): """ Run a single C++ unit test program remotely. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) remote_bin = posixpath.join(self.remote_bin_dir, basename) self.log.test_start(basename) buf = StringIO.StringIO() test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * \ timeout_factor returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir, timeout=test_timeout) self.log.process_output(basename, "\n%s" % buf.getvalue(), command=[remote_bin]) with mozfile.TemporaryDirectory() as tempdir: self.device.getDirectory(self.remote_home_dir, tempdir) if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): self.log.test_end(basename, status='CRASH', expected='PASS') return False result = returncode == 0 if not result: self.log.test_end(basename, status='FAIL', expected='PASS', message=("test failed with return code %s" % returncode)) else: self.log.test_end(basename, status='PASS', expected='PASS') return result
def push_libs(self): if self.options.local_apk: with mozfile.TemporaryDirectory() as tmpdir: apk_contents = ZipFile(self.options.local_apk) szip = os.path.join(self.options.local_bin, '..', 'host', 'bin', 'szip') if not os.path.exists(szip): # Tinderbox builds must run szip from the test package szip = os.path.join(self.options.local_bin, 'host', 'szip') if not os.path.exists(szip): # If the test package doesn't contain szip, it means files # are not szipped in the test package. szip = None for info in apk_contents.infolist(): if info.filename.endswith(".so"): print >> sys.stderr, "Pushing %s.." % info.filename remote_file = posixpath.join( self.remote_bin_dir, os.path.basename(info.filename)) apk_contents.extract(info, tmpdir) file = os.path.join(tmpdir, info.filename) if szip: out = subprocess.check_output( [szip, '-d', file], stderr=subprocess.STDOUT) self.device.pushFile( os.path.join(tmpdir, info.filename), remote_file) return elif self.options.local_lib: for file in os.listdir(self.options.local_lib): if file.endswith(".so"): print >> sys.stderr, "Pushing %s.." % file remote_file = posixpath.join(self.remote_bin_dir, file) self.device.pushFile( os.path.join(self.options.local_lib, file), remote_file) # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" local_arm_lib = os.path.join(self.options.local_lib, "lib") if os.path.isdir(local_arm_lib): for root, dirs, files in os.walk(local_arm_lib): for file in files: if (file.endswith(".so")): remote_file = posixpath.join( self.remote_bin_dir, file) self.device.pushFile(os.path.join(root, file), remote_file)
def push_libs(self): if self.options.local_apk: with mozfile.TemporaryDirectory() as tmpdir: apk_contents = ZipFile(self.options.local_apk) for info in apk_contents.infolist(): if info.filename.endswith(".so"): print("Pushing %s.." % info.filename, file=sys.stderr) remote_file = posixpath.join( self.remote_bin_dir, os.path.basename(info.filename)) apk_contents.extract(info, tmpdir) local_file = os.path.join(tmpdir, info.filename) with open(local_file) as f: # Decompress xz-compressed file. if f.read(5)[1:] == "7zXZ": cmd = [ "xz", "-df", "--suffix", ".so", local_file ] subprocess.check_output(cmd) # xz strips the ".so" file suffix. os.rename(local_file[:-3], local_file) self.device.push(local_file, remote_file) elif self.options.local_lib: for path in os.listdir(self.options.local_lib): if path.endswith(".so"): print("Pushing {}..".format(path), file=sys.stderr) remote_file = posixpath.join(self.remote_bin_dir, path) local_file = os.path.join(self.options.local_lib, path) self.device.push(local_file, remote_file) # Additional libraries may be found in a sub-directory such as # "lib/armeabi-v7a" for subdir in ["assets", "lib"]: local_arm_lib = os.path.join(self.options.local_lib, subdir) if os.path.isdir(local_arm_lib): for root, dirs, paths in os.walk(local_arm_lib): for path in paths: if path.endswith(".so"): print("Pushing {}..".format(path), file=sys.stderr) remote_file = posixpath.join( self.remote_bin_dir, path) local_file = os.path.join(root, path) self.device.push(local_file, remote_file)
def push_libs(self): if self.options.local_apk: with mozfile.TemporaryDirectory() as tmpdir: apk_contents = ZipFile(self.options.local_apk) for info in apk_contents.infolist(): if info.filename.endswith(".so"): print >> sys.stderr, "Pushing %s.." % info.filename remote_file = posixpath.join( self.remote_bin_dir, os.path.basename(info.filename)) apk_contents.extract(info, tmpdir) local_file = os.path.join(tmpdir, info.filename) with open(local_file) as f: # Decompress xz-compressed file. if f.read(5)[1:] == '7zXZ': cmd = [ 'xz', '-df', '--suffix', '.so', local_file ] subprocess.check_output(cmd) # xz strips the ".so" file suffix. os.rename(local_file[:-3], local_file) self.device.pushFile(local_file, remote_file) elif self.options.local_lib: for file in os.listdir(self.options.local_lib): if file.endswith(".so"): print >> sys.stderr, "Pushing %s.." % file remote_file = posixpath.join(self.remote_bin_dir, file) local_file = os.path.join(self.options.local_lib, file) self.device.pushFile(local_file, remote_file) # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" for subdir in ["assets", "lib"]: local_arm_lib = os.path.join(self.options.local_lib, subdir) if os.path.isdir(local_arm_lib): for root, dirs, files in os.walk(local_arm_lib): for file in files: if (file.endswith(".so")): print >> sys.stderr, "Pushing %s.." % file remote_file = posixpath.join( self.remote_bin_dir, file) local_file = os.path.join(root, file) self.device.pushFile(local_file, remote_file)
def extract_dmg_contents(dmgfile, destdir): import buildconfig if is_linux: with mozfile.TemporaryDirectory() as tmpdir: hfs_file = os.path.join(tmpdir, 'firefox.hfs') subprocess.check_call([ buildconfig.substs['DMG_TOOL'], 'extract', dmgfile, hfs_file ], # dmg is seriously chatty stdout=open(os.devnull, 'wb')) subprocess.check_call([ buildconfig.substs['HFS_TOOL'], hfs_file, 'extractall', '/', destdir]) else: unpack_diskimage = os.path.join(buildconfig.topsrcdir, 'build', 'package', 'mac_osx', 'unpack-diskimage') unpack_mountpoint = os.path.join( '/tmp', '{}-unpack'.format(buildconfig.substs['MOZ_APP_NAME'])) subprocess.check_call([unpack_diskimage, dmgfile, unpack_mountpoint, destdir])
def extract_dmg_contents(dmgfile, destdir): import buildconfig if is_linux: with mozfile.TemporaryDirectory() as tmpdir: hfs_file = os.path.join(tmpdir, "firefox.hfs") subprocess.check_call( [buildconfig.substs["DMG_TOOL"], "extract", dmgfile, hfs_file], # dmg is seriously chatty stdout=open(os.devnull, "wb"), ) subprocess.check_call([ buildconfig.substs["HFS_TOOL"], hfs_file, "extractall", "/", destdir ]) else: unpack_diskimage = os.path.join(buildconfig.topsrcdir, "build", "package", "mac_osx", "unpack-diskimage") unpack_mountpoint = os.path.join( "/tmp", "{}-unpack".format(buildconfig.substs["MOZ_APP_NAME"])) subprocess.check_call( [unpack_diskimage, dmgfile, unpack_mountpoint, destdir])
def run(self): if self.options.remote_webserver: httpd_host = self.options.remote_webserver.split(':')[0] else: httpd_host = self.httpd.host httpd_port = self.httpd.httpd.server_port locations = ServerLocations() locations.add_host(host=httpd_host, port=httpd_port, options='primary,privileged') #TODO: use Preferences.read when prefs_general.js has been updated prefpath = self.options.prefs prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = { "server": "%s:%d" % (httpd_host, httpd_port), "OOP": "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) prefs[ "steeplechase.signalling_server"] = self.options.signalling_server prefs["steeplechase.signalling_room"] = str(uuid.uuid4()) if self.options.timeout is None: prefs["steeplechase.timeout"] = 30000 else: prefs["steeplechase.timeout"] = self.options.timeout prefs["media.navigator.permission.disabled"] = True prefs["media.navigator.streams.fake"] = True specialpowers_path = self.options.specialpowers threads = [] results = [] cond = threading.Condition() for info in self.remote_info: with mozfile.TemporaryDirectory() as profile_path: # Create and push profile print "Writing profile for %s..." % info['name'] prefs["steeplechase.is_initiator"] = info['is_initiator'] profile = FirefoxProfile(profile=profile_path, preferences=prefs, addons=[specialpowers_path], locations=locations) print "Pushing profile to %s..." % info['name'] remote_profile_path = posixpath.join(info['test_root'], "profile") info['dm'].mkDir(remote_profile_path) info['dm'].pushDir(profile_path, remote_profile_path) info['remote_profile_path'] = remote_profile_path env = {} env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" env["DISPLAY"] = self.options.remote_xdisplay cmd = [ info['remote_app_path'], "-no-remote", "-profile", info['remote_profile_path'], 'http://%s:%d/index.html' % (httpd_host, httpd_port) ] print "cmd: %s" % (cmd, ) t = RunThread(name=info['name'], args=(info['dm'], cmd, env, cond, results)) threads.append(t) for t in threads: t.start() self.log.info("Waiting for results...") pass_count, fail_count = 0, 0 outputs = {} while threads: cond.acquire() while not results: cond.wait() thread, result, output = results.pop(0) cond.release() outputs[thread.name] = output passes, failures = result #XXX: double-counting tests from both clients. Ok? pass_count += passes fail_count += failures if failures: self.log.error("Error in %s" % thread.name) threads.remove(thread) self.log.info("All clients finished") for info in self.remote_info: if self.options.log_dest: with open( os.path.join(self.options.log_dest, "%s.log" % info['name']), "wb") as f: f.write(outputs[info['name']]) if fail_count: self.log.info("Log output for %s:", info["name"]) self.log.info(">>>>>>>") for line in outputs[info['name']].splitlines(): #TODO: make structured log messages human-readable self.log.info(line) self.log.info("<<<<<<<") return pass_count, fail_count