def do_build(self, test_filters): workqueue = ndk.test.builder.LoadRestrictingWorkQueue() try: for suite, tests in self.tests.items(): # Each test configuration was expanded when each test was # discovered, so the current order has all the largest tests # right next to each other. Spread them out to try to avoid # having too many heavy builds happening simultaneously. random.shuffle(tests) for test in tests: if not test_filters.filter(test.name): continue if test.name == 'libc++': workqueue.add_load_restricted_task( _run_test, suite, test, self.obj_dir, self.dist_dir, test_filters) else: workqueue.add_task(_run_test, suite, test, self.obj_dir, self.dist_dir, test_filters) report = ndk.test.report.Report() self.wait_for_results(report, workqueue, test_filters) return report finally: workqueue.terminate() workqueue.join()
def test_subprocess_exception(self): """Tests that exceptions raised in the task are re-raised.""" workqueue = ndk.workqueue.DummyWorkQueue() try: workqueue.add_task(raise_error) with self.assertRaises(ndk.workqueue.TaskError): workqueue.get_result() finally: workqueue.terminate() workqueue.join()
def test_finished(self): """Tests that finished() returns the correct result.""" workqueue = ndk.workqueue.WorkQueue() self.assertTrue(workqueue.finished()) workqueue.add_task(put, 1) self.assertFalse(workqueue.finished()) workqueue.get_result() self.assertTrue(workqueue.finished()) workqueue.terminate() workqueue.join() self.assertTrue(workqueue.finished())
def test_put_func(self): """Test that we can pass a function to the queue and get results.""" workqueue = ndk.workqueue.WorkQueue(4) workqueue.add_task(put, 1) workqueue.add_task(put, 2) expected_results = [1, 2] while expected_results: i = workqueue.get_result() self.assertIn(i, expected_results) expected_results.remove(i) workqueue.terminate() workqueue.join()
def test_put_functor(self): """Test that we can pass a functor to the queue and get results.""" workqueue = ndk.workqueue.DummyWorkQueue() workqueue.add_task(Functor(1)) workqueue.add_task(Functor(2)) expected_results = [1, 2] while expected_results: i = workqueue.get_result() self.assertIn(i, expected_results) expected_results.remove(i) workqueue.terminate() workqueue.join()
def test_finished(self): """Tests that finished() returns the correct result.""" workqueue = ndk.workqueue.WorkQueue(4) self.assertTrue(workqueue.finished()) manager = multiprocessing.Manager() event = manager.Event() workqueue.add_task(block_on_event, event) self.assertFalse(workqueue.finished()) event.set() workqueue.get_result() self.assertTrue(workqueue.finished()) workqueue.terminate() workqueue.join() self.assertTrue(workqueue.finished())
def test_status(self): """Tests that worker status can be accessed from the parent.""" workqueue = ndk.workqueue.WorkQueue(1) manager = multiprocessing.Manager() ready_event = manager.Event() finish_event = manager.Event() self.assertEqual(ndk.workqueue.Worker.IDLE_STATUS, workqueue.workers[0].status) workqueue.add_task(update_status, ready_event, finish_event, 'working') ready_event.wait() self.assertEqual('working', workqueue.workers[0].status) finish_event.set() workqueue.get_result() self.assertEqual(ndk.workqueue.Worker.IDLE_STATUS, workqueue.workers[0].status) workqueue.terminate() workqueue.join()
def test_subprocesses_killed(self): """Tests that terminate() kills descendents of worker processes.""" workqueue = ndk.workqueue.WorkQueue(4) manager = multiprocessing.Manager() queue = manager.Queue() workqueue.add_task(spawn_child, queue) pids = [] pids.append(queue.get()) pids.append(queue.get()) workqueue.terminate() workqueue.join() killed_pid = queue.get() self.assertIn(killed_pid, pids) pids.remove(killed_pid) killed_pid = queue.get() self.assertIn(killed_pid, pids) pids.remove(killed_pid)
def run_tests(args): results = Results() if not os.path.exists(args.test_dir): if args.rebuild or args.build_only: os.makedirs(args.test_dir) else: sys.exit('Test output directory does not exist: {}'.format( args.test_dir)) test_config = get_config_dict(args.config, args.abi, args.toolchain, args.pie) printer = printers.StdoutPrinter(show_all=args.show_all) if args.test_src is None: args.test_src = os.path.realpath('tests') if not os.path.exists(args.test_src): sys.exit('Test source directory does not exist: {}'.format( args.test_src)) if args.build_only or args.rebuild: build_timer = ndk.timer.Timer() with build_timer: report = build_tests(args.test_src, args.ndk, args.test_dir, args.clean, printer, test_config, args.filter) results.add_timing_report('Build', build_timer) if report.num_tests == 0: results.failed('Found no tests for filter {}.'.format(args.filter)) return results printer.print_summary(report) if not report.successful: results.failed() return results if args.build_only: results.passed() return results test_dist_dir = os.path.join(args.test_dir, 'dist') test_filter = filters.TestFilter.from_string(args.filter) # dict of {BuildConfiguration: [Test]} config_filter = ConfigFilter(test_config) test_discovery_timer = ndk.timer.Timer() with test_discovery_timer: test_groups = enumerate_tests(test_dist_dir, args.test_src, test_filter, config_filter) results.add_timing_report('Test discovery', test_discovery_timer) if sum([len(tests) for tests in test_groups.values()]) == 0: # As long as we *built* some tests, not having anything to run isn't a # failure. if args.rebuild: results.passed() else: results.failed('Found no tests in {} for filter {}.'.format( test_dist_dir, args.filter)) return results if args.show_test_stats: print_test_stats(test_groups) # For finding devices, we have a list of devices we want to run on in our # config file. If we did away with this list, we could instead run every # test on every compatible device, but in the event of multiple similar # devices, that's a lot of duplication. The list keeps us from running # tests on android-24 and android-25, which don't have meaningful # differences. # # The list also makes sure we don't miss any devices that we expect to run # on. # # The other thing we need to verify is that each test we find is run at # least once. # # Get the list of all devices. Prune this by the requested device # configuration. For each requested configuration that was not found, print # a warning. Then compare that list of devices against all our tests and # make sure each test is claimed by at least one device. For each # configuration that is unclaimed, print a warning. workqueue = ndk.workqueue.WorkQueue() try: device_discovery_timer = ndk.timer.Timer() with device_discovery_timer: fleet = ndk.test.devices.find_devices(test_config['devices'], workqueue) results.add_timing_report('Device discovery', device_discovery_timer) have_all_devices = verify_have_all_requested_devices(fleet) if args.require_all_devices and not have_all_devices: results.failed('Some requested devices were not available.') return results groups_for_config = match_configs_to_device_groups( fleet, test_groups.keys()) for config in find_configs_with_no_device(groups_for_config): logger().warning('No device found for %s.', config) report = ndk.test.report.Report() clean_device_timer = ndk.timer.Timer() if args.clean_device: with clean_device_timer: clear_test_directories(workqueue, fleet) results.add_timing_report('Clean device', clean_device_timer) can_use_sync = adb_has_feature('push_sync') push_timer = ndk.timer.Timer() with push_timer: push_tests_to_devices(workqueue, test_dist_dir, groups_for_config, can_use_sync) results.add_timing_report('Push', push_timer) asan_setup_timer = ndk.timer.Timer() with asan_setup_timer: perform_asan_setup(workqueue, args.ndk, groups_for_config) results.add_timing_report('ASAN setup', asan_setup_timer) finally: workqueue.terminate() workqueue.join() shard_queue = ShardingWorkQueue(fleet.get_unique_device_groups(), 4) try: # Need an input queue per device group, a single result queue, and a # pool of threads per device. # Shuffle the test runs to distribute the load more evenly. These are # ordered by (build config, device, test), so most of the tests running # at any given point in time are all running on the same device. test_runs = pair_test_runs(test_groups, groups_for_config) random.shuffle(test_runs) test_run_timer = ndk.timer.Timer() with test_run_timer: for test_run in test_runs: shard_queue.add_task(test_run.device_group, run_test, test_run) wait_for_results(report, shard_queue, printer) restart_flaky_tests(report, shard_queue) wait_for_results(report, shard_queue, printer) results.add_timing_report('Run', test_run_timer) printer.print_summary(report) finally: shard_queue.terminate() shard_queue.join() if report.successful: results.passed() else: results.failed() return results
def main(): logging.basicConfig() total_timer = build_support.Timer() total_timer.start() # It seems the build servers run us in our own session, in which case we # get EPERM from `setpgrp`. No need to call this in that case because we # will already be the process group leader. if os.getpid() != os.getsid(os.getpid()): os.setpgrp() args = parse_args() if args.modules is None: modules = get_all_module_names() else: modules = args.modules if args.host_only: modules = [ 'clang', 'gcc', 'host-tools', 'ndk-build', 'python-packages', 'renderscript-toolchain', 'shader-tools', 'simpleperf', ] required_package_modules = set(get_all_module_names()) have_required_modules = required_package_modules <= set(modules) if (args.package and have_required_modules) or args.force_package: do_package = True else: do_package = False # TODO(danalbert): wine? # We're building the Windows packages from Linux, so we can't actually run # any of the tests from here. if args.system.startswith('windows') or not do_package: args.test = False # Disable buffering on stdout so the build output doesn't hide all of our # "Building..." messages. sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) os.chdir(os.path.dirname(os.path.realpath(__file__))) # Set ANDROID_BUILD_TOP. if 'ANDROID_BUILD_TOP' in os.environ: sys.exit(textwrap.dedent("""\ Error: ANDROID_BUILD_TOP is already set in your environment. This typically means you are running in a shell that has lunched a target in a platform build. The platform environment interferes with the NDK build environment, so the build cannot continue. Launch a new shell before building the NDK.""")) os.environ['ANDROID_BUILD_TOP'] = os.path.realpath('..') out_dir = build_support.get_out_dir() dist_dir = build_support.get_dist_dir(out_dir) tmp_dir = os.path.join(out_dir, 'build') if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) os.mkdir(tmp_dir) os.environ['TMPDIR'] = tmp_dir print('Cleaning up...') invoke_build('dev-cleanup.sh') print('Building modules: {}'.format(' '.join(modules))) print('Machine has {} CPUs'.format(multiprocessing.cpu_count())) log_dir = os.path.join(dist_dir, 'logs') if not os.path.exists(log_dir): os.makedirs(log_dir) build_timer = build_support.Timer() with build_timer: workqueue = ndk.workqueue.WorkQueue(args.jobs) try: for module in ALL_MODULES: if module.name in modules: workqueue.add_task( launch_build, module, out_dir, dist_dir, args, log_dir) while not workqueue.finished(): build_name, result, log_path = workqueue.get_result() if result: print('BUILD SUCCESSFUL: ' + build_name) else: # Kill all the children so the error we print appears last. workqueue.terminate() workqueue.join() print('BUILD FAILED: ' + build_name) with open(log_path, 'r') as log_file: contents = log_file.read() print(contents) # The build server has a build_error.log file that is # supposed to be the short log of the failure that # stopped the build. Append our failing log to that. build_error_log = os.path.join( dist_dir, 'logs/build_error.log') with open(build_error_log, 'a') as error_log: error_log.write('\n') error_log.write(contents) sys.exit(1) finally: workqueue.terminate() workqueue.join() ndk_dir = ndk.paths.get_install_path(out_dir) install_timer = build_support.Timer() with install_timer: if not os.path.exists(ndk_dir): os.makedirs(ndk_dir) for module in ALL_MODULES: if module.name in modules: module.install(out_dir, dist_dir, args) package_timer = build_support.Timer() with package_timer: if do_package: host_tag = build_support.host_to_tag(args.system) package_ndk(ndk_dir, dist_dir, host_tag, args.build_number) good = True test_timer = build_support.Timer() with test_timer: if args.test: good = test_ndk(out_dir, dist_dir, args) print() # Blank line between test results and timing data. total_timer.finish() print('Finished {}'.format('successfully' if good else 'unsuccessfully')) print('Build: {}'.format(build_timer.duration)) print('Install: {}'.format(install_timer.duration)) print('Packaging: {}'.format(package_timer.duration)) print('Testing: {}'.format(test_timer.duration)) print('Total: {}'.format(total_timer.duration)) subject = 'NDK Build {}!'.format('Passed' if good else 'Failed') body = 'Build finished in {}'.format(total_timer.duration) ndk.notify.toast(subject, body) sys.exit(not good)