def wrapped(*args, **params): if args: raise Exception( 'Invoking %s with positional arguments is not allowed.' % func.__name__) log_params = params.copy() log_params['command'] = func.__module__.split('.')[-1] try: try: send_start(log_params) func(**params) send_success(log_params) except BaseException as e: send_failure(e, traceback.format_exc(), log_params) raise except KeyboardInterrupt as e: print logger.info(common.colorize('%s', common.BASH_YELLOW_MARKER), e.__class__.__name__) sys.exit(1) except error.ExpectedException as e: print logger.info(common.colorize('%s: %s', common.BASH_YELLOW_MARKER), e.__class__.__name__, e.message) sys.exit(e.exit_code) finally: print('\nDetailed log of this run can be found in: %s' % local_logging.LOG_FILE_PATH)
def warn_unreproducible_if_needed(current_testcase): """Print warning if the testcase is unreproducible.""" if current_testcase.gestures: print logger.info(common.colorize( 'WARNING: the testcase is using gestures and inherently flaky. ' "Therefore, we cannot guarantee that it'll reproduce correctly.", common.BASH_YELLOW_MARKER)) if not current_testcase.reproducible: print logger.info(common.colorize( 'WARNING: the testcase is marked as unreproducible. Therefore, it ' 'might not be reproduced.', common.BASH_YELLOW_MARKER))
def test_empty_default(self): """Tests functionality when default is explicitly None.""" self.mock.raw_input.side_effect = ['y', 'n', '', 'n'] self.assertTrue(common.confirm('A question', default=None)) self.assertFalse(common.confirm('A question', default=None)) self.assertFalse(common.confirm('A question', default=None)) msg = common.colorize('A question [y/n]: ', common.BASH_MAGENTA_MARKER) another_msg = common.colorize('Please type either "y" or "n": ', common.BASH_MAGENTA_MARKER) self.mock.raw_input.assert_has_calls([mock.call(msg)] * 3 + [mock.call(another_msg)]) self.assert_n_calls(4, [self.mock.raw_input])
def gn_gen(self): """Finalize args.gn and run `gn gen`.""" args_gn_path = os.path.join(self.get_build_dir_path(), ARGS_GN_FILENAME) common.ensure_dir(self.get_build_dir_path()) common.delete_if_exists(args_gn_path) # Let users edit the current args. content = serialize_gn_args(self.get_gn_args()) content = common.edit_if_needed( content, prefix='edit-args-gn-', comment='Edit %s before building.' % ARGS_GN_FILENAME, should_edit=self.options.edit_mode) # Write args to file and store. with open(args_gn_path, 'w') as f: f.write(content) logger.info( common.colorize('\nGenerating %s:\n%s\n', common.BASH_GREEN_MARKER), args_gn_path, content) common.execute( 'gn', 'gen %s' % (self.get_build_dir_path()), self.get_source_dir_path())
def test_returns_when_correct(self): """Tests that the method only returns when the answer fits validation.""" question = 'Initial Question' error_message = 'Please answer correctly' validate_fn = lambda x: x == 'correct' result = common.ask(question, error_message, validate_fn) self.assert_n_calls(4, [self.mock.raw_input]) self.mock.raw_input.assert_has_calls([ mock.call( common.emphasize( common.colorize('Initial Question: ', common.BASH_MAGENTA_MARKER))), mock.call( common.emphasize( common.colorize('Please answer correctly: ', common.BASH_MAGENTA_MARKER))) ]) self.assertEqual(result, 'correct')
def test_no_default(self): """Tests functionality when no is the default.""" self.mock.raw_input.side_effect = ['y', 'n', ''] self.assertTrue(common.confirm('A question', default='n')) self.assertFalse(common.confirm('A question', default='n')) self.assertFalse(common.confirm('A question', default='n')) msg = common.colorize('A question [y/N]: ', common.BASH_MAGENTA_MARKER) self.mock.raw_input.assert_has_calls([mock.call(msg)] * 3) self.assert_n_calls(3, [self.mock.raw_input])
def write_content(path, content): """Write content to path on Android.""" with tempfile.NamedTemporaryFile(delete=False) as tmp_file: tmp_file.write(content) logger.info( common.colorize('\nWriting %s:\n%s\n', common.BASH_GREEN_MARKER), path, content) adb('push %s %s' % (tmp_file.name, path)) adb_shell('chmod 0644 %s' % path) common.delete_if_exists(tmp_file.name)
def reproduce_normal(self, iteration_max): """Reproduce normally.""" iterations = 1 signatures = set() has_signature = False while iterations <= iteration_max: _, output = self.reproduce_crash() new_signature = get_crash_signature(self.job_type, output) new_signature.output = output signatures.add(new_signature) has_signature = (bool(new_signature.crash_type) or bool(new_signature.crash_state_lines)) logger.info( 'New crash type: %s\n' 'New crash state:\n %s\n\n' 'Original crash type: %s\n' 'Original crash state:\n %s\n', new_signature.crash_type, '\n '.join(new_signature.crash_state_lines), self.get_crash_signature().crash_type, '\n '.join(self.get_crash_signature().crash_state_lines)) # The crash signature validation is intentionally forgiving. if is_similar(new_signature, self.get_crash_signature()): logger.info( common.colorize( 'The stacktrace seems similar to the original stacktrace.\n' "Since you've reproduced the crash correctly, there are some " 'tricks that might help you move faster:\n' '- In case of fixing the crash, you can use `--current` to run ' 'on tip-of-tree (or, in other words, avoid git-checkout).\n' '- You can save time by using `--skip-deps` to avoid ' '`gclient sync`, `gclient runhooks`, and other dependency ' 'installations in subsequential runs.\n' '- You can debug with gdb using `--enable-debug`.\n' '- You can modify args.gn and arguments using `--edit-mode`.', common.BASH_GREEN_MARKER)) return True else: logger.info( "The stacktrace doesn't match the original stacktrace.") logger.info( 'Try again (%d times). Press Ctrl+C to stop trying to ' 'reproduce.', iterations) iterations += 1 time.sleep(3) if has_signature: raise error.DifferentStacktraceError(iteration_max, signatures) else: raise error.UnreproducibleError(iteration_max, signatures)
def test_yes_default(self): """Tests functionality with yes as default.""" self.mock.raw_input.side_effect = ['y', 'n', ''] self.assertTrue(common.confirm('A question')) self.assertFalse(common.confirm('A question')) self.assertTrue(common.confirm('A question')) msg = common.emphasize( common.colorize('A question [Y/n]: ', common.BASH_MAGENTA_MARKER)) self.mock.raw_input.assert_has_calls([mock.call(msg)] * 3) self.assert_n_calls(3, [self.mock.raw_input])
def get_gn_args(self): """Ensures that args.gn is set up properly.""" args = super(ClankiumBuilder, self).get_gn_args() # android_asan_chrome_x86 is on a VM. Therefore, it's not possible to # deploy it on a device. Therefore, we change its target CPU to arm, so that # it can be deployed on an android device. if 'arm' not in args.get('target_cpu'): logger.info(common.colorize( 'WARNING: this crash is NOT on `target_cpu = arm`. ' '`target_cpu` is modified to `arm` to make it work on an android ' 'device.', common.BASH_YELLOW_MARKER)) args['target_cpu'] = '"arm"' return args
def setup_gn_args(self): """Ensures that args.gn is set up properly.""" # Remove existing gn file from build directory. args_gn_path = os.path.join(self.build_directory, 'args.gn') if os.path.isfile(args_gn_path): os.remove(args_gn_path) # Create build directory if it does not already exist. if not os.path.exists(self.build_directory): os.makedirs(self.build_directory) # If no args.gn file is found, get it from downloaded build. if self.gn_args: gn_args = self.gn_args else: args_gn_downloaded_build_path = os.path.join( self.build_dir_name(), 'args.gn') with open(args_gn_downloaded_build_path, 'r') as f: gn_args = f.read() # Add additional options to existing gn args. args_hash = self.deserialize_gn_args(gn_args) args_hash = self.setup_gn_goma_params(args_hash) args_hash = setup_debug_symbol_if_needed(args_hash, self.options.enable_debug) if self.gn_args_options: for k, v in self.gn_args_options.iteritems(): args_hash[k] = v # Let users edit the current args. content = self.serialize_gn_args(args_hash) content = edit_if_needed(content, self.options.edit_mode) # Write args to file and store. with open(args_gn_path, 'w') as f: f.write(content) self.gn_args = content logger.info( common.colorize('\nGenerating %s:\n%s\n', common.BASH_GREEN_MARKER), args_gn_path, self.gn_args) common.execute('gn', 'gen %s %s' % (self.gn_flags, self.build_directory), self.source_directory)
def reproduce(self, iteration_max): """Reproduces the crash and prints the stacktrace.""" logger.info('Reproducing...') self.pre_build_steps() iterations = 1 signatures = set() while iterations <= iteration_max: _, output = self.reproduce_crash() new_signature = self.get_stacktrace_info(output) new_signature.output = output signatures.add(new_signature) logger.info( 'New crash type: %s\n' 'New crash state:\n %s\n\n' 'Original crash type: %s\n' 'Original crash state:\n %s\n', new_signature.crash_type, '\n '.join(new_signature.crash_state_lines), self.crash_signature.crash_type, '\n '.join(self.crash_signature.crash_state_lines)) # The crash signature validation is intentionally forgiving. if is_similar(new_signature, self.crash_signature): logger.info( common.colorize( 'The stacktrace seems similar to the original stacktrace.', common.BASH_GREEN_MARKER)) return True else: logger.info( "The stacktrace doesn't match the original stacktrace.") logger.info( 'Try again (%d times). Press Ctrl+C to stop trying to ' 'reproduce.', iterations) iterations += 1 time.sleep(3) raise common.UnreproducibleError(iteration_max, signatures)
def test_colorize(self): """Test colorize.""" self.assertEqual('style', common.colorize('s', common.BASH_BLUE_MARKER)) self.mock.style.assert_called_once_with('s', common.BASH_BLUE_MARKER, common.BASH_RESET_COLOR_MARKER)
def test_not_posix(self): """Test not posix.""" self.mock.get_os_name.return_value = 'windows' self.assertEqual('test', common.colorize('test', common.BASH_BLUE_MARKER))
def test_posix(self): """Test posix.""" self.mock.get_os_name.return_value = 'posix' self.assertEqual( common.BASH_BLUE_MARKER + 'test' + common.BASH_RESET_MARKER, common.colorize('test', common.BASH_BLUE_MARKER))
def execute(testcase_id, current, build, disable_goma, goma_threads, iterations, disable_xvfb, target_args, edit_mode, disable_gclient, enable_debug, goma_dir=None): """Execute the reproduce command.""" options = common.Options(testcase_id=testcase_id, current=current, build=build, disable_goma=disable_goma, goma_threads=goma_threads, iterations=iterations, disable_xvfb=disable_xvfb, target_args=target_args, edit_mode=edit_mode, disable_gclient=disable_gclient, enable_debug=enable_debug, goma_dir=goma_dir) logger.info('Reproducing testcase %s', testcase_id) logger.debug('%s', str(options)) logger.info('Downloading testcase information...') response = get_testcase_info(testcase_id) current_testcase = testcase.Testcase(response) if 'gestures' in response['testcase']: logger.info( common.colorize( 'Warning: the testcase is using gestures and inherently flaky. ' "Therefore, we cannot guaranteed that it'll reproduce correctly.", common.BASH_YELLOW_MARKER)) definition = get_definition(current_testcase.job_type, build) maybe_warn_unreproducible(current_testcase) if build == 'download': if definition.binary_name: binary_name = definition.binary_name else: binary_name = common.get_binary_name( current_testcase.stacktrace_lines) binary_provider = binary_providers.DownloadedBinary( testcase_id=current_testcase.id, build_url=current_testcase.build_url, binary_name=binary_name) else: options.goma_dir = None if options.disable_goma else ensure_goma() binary_provider = definition.builder(testcase=current_testcase, definition=definition, options=options) reproducer = definition.reproducer(definition=definition, binary_provider=binary_provider, testcase=current_testcase, sanitizer=definition.sanitizer, options=options) try: reproducer.reproduce(iterations) finally: maybe_warn_unreproducible(current_testcase)