def test_infer_python_dependencies(self) -> None: options_bootstrapper = create_options_bootstrapper( args=["--source-root-patterns=src/python"] ) self.add_to_build_file( "3rdparty/python", dedent( """\ python_requirement_library( name='Django', requirements=[python_requirement('Django==1.21')], ) """ ), ) self.create_file("src/python/no_owner/f.py") self.add_to_build_file("src/python/no_owner", "python_library()") self.create_file("src/python/util/dep.py") self.add_to_build_file("src/python/util", "python_library()") self.create_file( "src/python/app.py", dedent( """\ import django from util.dep import Demo from util import dep """ ), ) self.create_file( "src/python/f2.py", dedent( """\ import typing # Import from another file in the same target. from app import main """ ), ) self.add_to_build_file("src/python", "python_library()") tgt = self.request_single_product(WrappedTarget, Address.parse("src/python")).target result = self.request_single_product( InferredDependencies, Params(InferPythonDependencies(tgt[PythonSources]), options_bootstrapper), ) assert result == InferredDependencies( [Address.parse("3rdparty/python:Django"), Address.parse("src/python/util")] )
def run_goal_rules( self, *, options_bootstrapper: OptionsBootstrapper, union_membership: UnionMembership, options: Options, goals: Iterable[str], specs: Specs, poll: bool = False, poll_delay: Optional[float] = None, ) -> int: """Runs @goal_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :returns: An exit code. """ workspace = Workspace(self.scheduler_session) interactive_runner = InteractiveRunner(self.scheduler_session) for goal in goals: goal_product = self.goal_map[goal] # NB: We no-op for goals that have no V2 implementation because no relevant backends are # registered. This allows us to safely set `--v1 --v2`, even if no V2 backends are registered. # Once V1 is removed, we might want to reconsider the behavior to instead warn or error when # trying to run something like `./pants run` without any backends registered. is_implemented = union_membership.has_members_for_all( goal_product.subsystem_cls.required_union_implementations) if not is_implemented: continue params = Params( specs.provided_specs, options_bootstrapper, self.console, workspace, interactive_runner, ) logger.debug( f"requesting {goal_product} to satisfy execution of `{goal}` goal" ) try: exit_code = self.scheduler_session.run_goal_rule( goal_product, params, poll=poll, poll_delay=poll_delay) finally: self.console.flush() if exit_code != PANTS_SUCCEEDED_EXIT_CODE: return exit_code return PANTS_SUCCEEDED_EXIT_CODE
def get_specified_source_files( self, adaptors_with_origins: Iterable[TargetAdaptorWithOrigin], *, strip_source_roots: bool = False, ) -> List[str]: request = LegacySpecifiedSourceFilesRequest( adaptors_with_origins, strip_source_roots=strip_source_roots, ) result = self.request_single_product( SourceFiles, Params(request, create_options_bootstrapper()) ) return sorted(result.snapshot.files)
def get_stripped_file_mapping( paths: List[str], args: Optional[List[str]] = None) -> Dict[str, str]: args = args or [] args.append("--source-root-patterns=src/python") args.append("--source-root-patterns=src/java") request = StripSnapshotRequest( self.make_snapshot_of_empty_files(paths)) result = self.request_single_product( SourceRootStrippedSources, Params(request, create_options_bootstrapper(args=args)), ) return dict(result.get_file_to_stripped_file_mapping())
def assert_error(self, addr: str, exc_cls: Type[Exception]): with pytest.raises(ExecutionError) as excinfo: self.request_single_product( SetupPyChroot, Params( SetupPyChrootRequest(ExportedTarget(self.tgt(addr)), py2=False), SourceRootConfig.global_instance(), ), ) ex = excinfo.value assert len(ex.wrapped_exceptions) == 1 assert type(ex.wrapped_exceptions[0]) == exc_cls
def assert_injected(deps_cls: Type[Dependencies], *, injected: List[str]) -> None: provided_addr = Address.parse("//:provided") deps_field = deps_cls([provided_addr], address=Address.parse("//:target")) result = self.request_single_product( Addresses, Params(DependenciesRequest(deps_field), create_options_bootstrapper())) assert result == Addresses( sorted([ provided_addr, *(Address.parse(addr) for addr in injected) ]))
def test_options_parse_scoped(self): options_bootstrapper = self._ob( args=['./pants', '-ldebug', 'binary', 'src/python::'], env=dict(PANTS_ENABLE_PANTSD='True', PANTS_BINARIES_BASEURLS='["https://bins.com"]'), ) global_options_params = Params(Scope(str(GLOBAL_SCOPE)), options_bootstrapper) python_setup_options_params = Params(Scope(str('python-setup')), options_bootstrapper) global_options, python_setup_options = self.scheduler.product_request( ScopedOptions, [global_options_params, python_setup_options_params], ) self.assertEqual(global_options.options.level, 'debug') self.assertEqual(global_options.options.enable_pantsd, True) self.assertEqual(global_options.options.binaries_baseurls, ['https://bins.com']) self.assertEqual(python_setup_options.options.platforms, ['current'])
def test_options_parse_scoped(self): options_bootstrapper = self._ob( args=["./pants", "-ldebug", "binary", "src/python::"], env=dict(PANTS_ENABLE_PANTSD="True", PANTS_BINARIES_BASEURLS='["https://bins.com"]'), ) global_options_params = Params(Scope(str(GLOBAL_SCOPE)), options_bootstrapper) python_setup_options_params = Params(Scope(str("python-setup")), options_bootstrapper) global_options, python_setup_options = self.scheduler.product_request( ScopedOptions, [global_options_params, python_setup_options_params], ) self.assertEqual(global_options.options.level, LogLevel.DEBUG) self.assertEqual(global_options.options.enable_pantsd, True) self.assertEqual(global_options.options.binaries_baseurls, ["https://bins.com"]) self.assertEqual(python_setup_options.options.platforms, ["current"])
def run_pytest(self, *, passthrough_args: Optional[str] = None) -> TestResult: args = [ "--backend-packages2=pants.backend.python", "--pytest-version=pytest>=4.6.6,<4.7", # so that we can run Python 2 tests "--pytest-pytest-plugins=zipp==1.0.0", # transitive dep of Pytest; we pin to ensure Python 2 support ] if passthrough_args: args.append(f"--pytest-args='{passthrough_args}'") options_bootstrapper = create_options_bootstrapper(args=args) target = PythonTestsAdaptor( address=BuildFileAddress(rel_path=f"{self.source_root}/BUILD", target_name="target"), ) test_result = self.request_single_product(TestResult, Params(target, options_bootstrapper)) debug_request = self.request_single_product( TestDebugRequest, Params(target, options_bootstrapper), ) debug_result = InteractiveRunner(self.scheduler).run_local_interactive_process(debug_request.ipr) if test_result.status == Status.SUCCESS: assert debug_result.process_exit_code == 0 else: assert debug_result.process_exit_code != 0 return test_result
def run_isort( self, source_files: List[FileContent], *, config: Optional[str] = None, passthrough_args: Optional[Sequence[str]] = None, skip: bool = False, ) -> Tuple[LintResult, FmtResult]: if config is not None: self.create_file(relpath=".isort.cfg", contents=config) input_snapshot = self.request_single_product(Snapshot, InputFilesContent(source_files)) target_adaptor = TargetAdaptor( sources=EagerFilesetWithSpec('test', {'globs': []}, snapshot=input_snapshot), address=Address.parse("test:target"), ) lint_target = IsortTarget(target_adaptor) fmt_target = IsortTarget(target_adaptor, prior_formatter_result_digest=input_snapshot.directory_digest) isort_subsystem = global_subsystem_instance( Isort, options={Isort.options_scope: { "config": [".isort.cfg"] if config else None, "args": passthrough_args or [], "skip": skip, }} ) python_subsystems = [ PythonNativeCode.global_instance(), PythonSetup.global_instance(), SubprocessEnvironment.global_instance(), ] isort_setup = self.request_single_product( IsortSetup, Params(isort_subsystem, *python_subsystems) ) lint_result = self.request_single_product( LintResult, Params(lint_target, isort_setup, *python_subsystems) ) fmt_result = self.request_single_product( FmtResult, Params(fmt_target, isort_setup, *python_subsystems) ) return lint_result, fmt_result
def get_specified_source_files( self, sources_fields_with_origins: Iterable[Tuple[SourcesField, OriginSpec]], *, strip_source_roots: bool = False, ) -> List[str]: request = SpecifiedSourceFilesRequest( sources_fields_with_origins, strip_source_roots=strip_source_roots, ) result = self.request_single_product( SourceFiles, Params(request, create_options_bootstrapper())) return sorted(result.snapshot.files)
def run_isort( self, targets: List[TargetWithOrigin], *, config: Optional[str] = None, passthrough_args: Optional[str] = None, skip: bool = False, ) -> Tuple[LintResults, FmtResult]: args = ["--backend-packages2=pants.backend.python.lint.isort"] if config is not None: self.create_file(relpath=".isort.cfg", contents=config) args.append("--isort-config=.isort.cfg") if passthrough_args: args.append(f"--isort-args='{passthrough_args}'") if skip: args.append("--isort-skip") options_bootstrapper = create_options_bootstrapper(args=args) field_sets = [IsortFieldSet.create(tgt) for tgt in targets] lint_results = self.request_single_product( LintResults, Params(IsortRequest(field_sets), options_bootstrapper)) input_sources = self.request_single_product( SourceFiles, Params( AllSourceFilesRequest(field_set.sources for field_set in field_sets), options_bootstrapper, ), ) fmt_result = self.request_single_product( FmtResult, Params( IsortRequest(field_sets, prior_formatter_result=input_sources.snapshot), options_bootstrapper, ), ) return lint_results, fmt_result
def assert_error(self, addr: str, exc_cls: Type[Exception]): with pytest.raises(ExecutionError) as excinfo: self.request_single_product( SetupPyChroot, Params( SetupPyChrootRequest(ExportedTarget(self.tgt(addr)), py2=False), create_options_bootstrapper( args=["--source-root-patterns=src/python"]), ), ) ex = excinfo.value assert len(ex.wrapped_exceptions) == 1 assert type(ex.wrapped_exceptions[0]) == exc_cls
def __init__( self, root_dir: str, options_bootstrapper: OptionsBootstrapper, options: Options, build_config: BuildConfiguration, run_tracker: RunTracker, reporting: Reporting, graph_session: LegacyGraphSession, specs: Specs, ) -> None: """ :param root_dir: The root directory of the pants workspace (aka the "build root"). :param options: The global, pre-initialized Options instance. :param build_config: A pre-initialized BuildConfiguration instance. :param run_tracker: The global, pre-initialized/running RunTracker instance. :param reporting: The global, pre-initialized Reporting instance. :param graph_session: The graph session for this run. :param specs: The specs for this run, i.e. either the address or filesystem specs. """ self._root_dir = root_dir self._options_bootstrapper = options_bootstrapper self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._graph_session = graph_session self._specs = specs self._global_options = options.for_global_scope() self._fail_fast = self._global_options.fail_fast self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns # V1 tasks do not understand FilesystemSpecs, so we eagerly convert them into AddressSpecs. if self._specs.filesystem_specs.dependencies: (owned_addresses,) = self._graph_session.scheduler_session.product_request( Addresses, [Params(self._specs.filesystem_specs, self._options_bootstrapper)] ) updated_address_specs = AddressSpecs( dependencies=tuple( SingleAddress(a.spec_path, a.target_name) for a in owned_addresses ), tags=self._specs.address_specs.matcher.tags, exclude_patterns=self._specs.address_specs.matcher.exclude_patterns, ) self._specs = Specs( address_specs=updated_address_specs, filesystem_specs=FilesystemSpecs([]), )
def get_stripped_files( self, request: Union[StripSnapshotRequest, StripSourcesFieldRequest, LegacyStripTargetRequest], *, args: Optional[List[str]] = None, ) -> List[str]: product = (SourceRootStrippedSources if not isinstance(request, LegacyStripTargetRequest) else LegacySourceRootStrippedSources) result = self.request_single_product( product, Params(request, create_options_bootstrapper(args=args)), ) return sorted(result.snapshot.files)
def get_stripped_files( self, request: Union[StripSnapshotRequest, StripSourcesFieldRequest], *, args: Optional[List[str]] = None, ) -> StrippedResponseData: args = args or [] args.append("--source-root-patterns=src/python") args.append("--source-root-patterns=src/java") args.append("--source-root-patterns=tests/python") result = self.request_single_product( SourceRootStrippedSources, Params(request, create_options_bootstrapper(args=args)), ) return list(result.snapshot.files), dict(result.root_to_relfiles)
def execute_rule( self, args: Optional[Iterable[str]] = None, global_args: Optional[Iterable[str]] = None, env: Optional[Dict[str, str]] = None, exit_code: int = 0, additional_params: Optional[Iterable[Any]] = None, ) -> str: """Executes the @goal_rule for this test class. :API: public Returns the text output of the task. """ # Create an OptionsBootstrapper for these args/env, and a captured Console instance. options_bootstrapper = create_options_bootstrapper( args=(*(global_args or []), self.goal_cls.name, *(args or [])), env=env, ) BuildConfigInitializer.get(options_bootstrapper) full_options = options_bootstrapper.get_full_options( [*GlobalOptions.known_scope_infos(), *self.goal_cls.subsystem_cls.known_scope_infos()] ) stdout, stderr = StringIO(), StringIO() console = Console(stdout=stdout, stderr=stderr) scheduler = self.scheduler workspace = Workspace(scheduler) # Run for the target specs parsed from the args. specs = SpecsCalculator.parse_specs(full_options.specs, self.build_root) params = Params( specs.provided_specs, console, options_bootstrapper, workspace, *(additional_params or []), ) actual_exit_code = self.scheduler.run_goal_rule(self.goal_cls, params) # Flush and capture console output. console.flush() stdout_val = stdout.getvalue() stderr_val = stderr.getvalue() assert ( exit_code == actual_exit_code ), f"Exited with {actual_exit_code} (expected {exit_code}):\nstdout:\n{stdout_val}\nstderr:\n{stderr_val}" return stdout_val
def find_valid_field_sets( superclass: Type, targets_with_origins: Iterable[TargetWithOrigin], *, error_if_no_valid_targets: bool = False, expect_single_config: bool = False, ) -> TargetsToValidFieldSets: request = TargetsToValidFieldSetsRequest( superclass, goal_description="fake", error_if_no_valid_targets=error_if_no_valid_targets, expect_single_field_set=expect_single_config, ) return self.request_single_product( TargetsToValidFieldSets, Params(request, TargetsWithOrigins(targets_with_origins),), )
def create_python_awslambda(self, addr: str) -> Tuple[str, bytes]: target = self.request_single_product(WrappedTarget, Address.parse(addr)).target created_awslambda = self.request_single_product( CreatedAWSLambda, Params( PythonAwsLambdaFieldSet.create(target), create_options_bootstrapper(args=[ "--backend-packages2=pants.backend.awslambda.python" ]), ), ) files_content = self.request_single_product(FilesContent, created_awslambda.digest) assert len(files_content) == 1 return created_awslambda.name, files_content[0].content
def run_black_and_isort( self, source_files: List[FileContent], *, name: str, extra_args: Optional[List[str]] = None ) -> LanguageFmtResults: for source_file in source_files: self.create_file(source_file.path, source_file.content.decode()) target = PythonLibrary({}, address=Address.parse(f"test:{name}")) origin = SingleAddress(directory="test", name=name) targets = PythonFmtTargets(TargetsWithOrigins([TargetWithOrigin(target, origin)])) args = [ "--backend-packages2=['pants.backend.python.lint.black', 'pants.backend.python.lint.isort']", *(extra_args or []), ] results = self.request_single_product( LanguageFmtResults, Params(targets, create_options_bootstrapper(args=args)), ) return results
def run_goal_rules( self, options_bootstrapper: OptionsBootstrapper, options: Options, goals: Iterable[str], specs: Specs, ): """Runs @goal_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :returns: An exit code. """ global_options = options.for_global_scope() console = Console( use_colors=global_options.colors, session=self.scheduler_session if global_options.get('v2_ui') else None, ) workspace = Workspace(self.scheduler_session) interactive_runner = InteractiveRunner(self.scheduler_session) for goal in goals: goal_product = self.goal_map[goal] params = Params( specs.provided_specs, options_bootstrapper, console, workspace, interactive_runner, ) logger.debug( f'requesting {goal_product} to satisfy execution of `{goal}` goal' ) try: exit_code = self.scheduler_session.run_goal_rule( goal_product, params) finally: console.flush() if exit_code != PANTS_SUCCEEDED_EXIT_CODE: return exit_code return PANTS_SUCCEEDED_EXIT_CODE
def create_pex_and_get_all_data( self, *, requirements=PexRequirements(), entry_point=None, interpreter_constraints=PexInterpreterConstraints(), platforms=PexPlatforms(), sources: Optional[Digest] = None, additional_inputs: Optional[Digest] = None, additional_pants_args: Tuple[str, ...] = (), additional_pex_args: Tuple[str, ...] = (), ) -> Dict: request = PexRequest( output_filename="test.pex", requirements=requirements, interpreter_constraints=interpreter_constraints, platforms=platforms, entry_point=entry_point, sources=sources, additional_inputs=additional_inputs, additional_args=additional_pex_args, ) pex = self.request_single_product( Pex, Params( request, create_options_bootstrapper(args=[ "--backend-packages2=pants.backend.python", *additional_pants_args ]), ), ) self.scheduler.materialize_directory(DirectoryToMaterialize( pex.digest)) pex_path = os.path.join(self.build_root, "test.pex") with zipfile.ZipFile(pex_path, "r") as zipfp: with zipfp.open("PEX-INFO", "r") as pex_info: pex_info_content = pex_info.readline().decode() pex_list = zipfp.namelist() return { "pex": pex, "local_path": pex_path, "info": json.loads(pex_info_content), "files": pex_list, }
def run_goal_rules( self, options_bootstrapper: OptionsBootstrapper, options: Options, goals: Iterable[str], target_roots: TargetRoots, ): """Runs @goal_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :param goals: The list of requested goal names as passed on the commandline. :param target_roots: The targets root of the request. :returns: An exit code. """ global_options = options.for_global_scope() address_specs = target_roots.specs console = Console( use_colors=global_options.colors, session=self.scheduler_session if global_options.v2_ui else None, ) workspace = Workspace(self.scheduler_session) interactive_runner = InteractiveRunner(self.scheduler_session) for goal in goals: goal_product = self.goal_map[goal] params = Params(address_specs, options_bootstrapper, console, workspace, interactive_runner) logger.debug( f'requesting {goal_product} to satisfy execution of `{goal}` goal' ) try: exit_code = self.scheduler_session.run_goal_rule( goal_product, params) finally: console.flush() if exit_code != PANTS_SUCCEEDED_EXIT_CODE: return exit_code return PANTS_SUCCEEDED_EXIT_CODE
def assert_stripped_source_file(self, *, original_path: str, expected_path: str, target_type_alias=None): init_subsystem(SourceRootConfig) adaptor = Mock() adaptor.sources = Mock() source_files = {original_path: "print('random python')"} adaptor.sources.snapshot = self.make_snapshot(source_files) adaptor.address = Mock() adaptor.address.spec_path = original_path if target_type_alias: adaptor.type_alias = target_type_alias target = HydratedTarget('some/target/address', adaptor, tuple()) stripped_sources = self.request_single_product( SourceRootStrippedSources, Params(target, SourceRootConfig.global_instance())) self.assertEqual(stripped_sources.snapshot.files, (expected_path, ))
def execute_rule(self, args=tuple(), env=tuple(), exit_code=0, additional_params: List[Any] = []): """Executes the @console_rule for this test class. :API: public Returns the text output of the task. """ # Create an OptionsBootstrapper for these args/env, and a captured Console instance. args = self._implicit_args + (self.goal_cls.name, ) + tuple(args) env = dict(env) options_bootstrapper = OptionsBootstrapper.create(args=args, env=env) BuildConfigInitializer.get(options_bootstrapper) full_options = options_bootstrapper.get_full_options( list(self.goal_cls.Options.known_scope_infos())) stdout, stderr = StringIO(), StringIO() console = Console(stdout=stdout, stderr=stderr) scheduler = self.scheduler workspace = Workspace(scheduler) # Run for the target specs parsed from the args. specs = TargetRootsCalculator.parse_specs(full_options.positional_args, self.build_root) params = Params(specs, console, options_bootstrapper, workspace, *additional_params) actual_exit_code = self.scheduler.run_console_rule( self.goal_cls, params) # Flush and capture console output. console.flush() stdout = stdout.getvalue() stderr = stderr.getvalue() self.assertEqual( exit_code, actual_exit_code, "Exited with {} (expected {}):\nstdout:\n{}\nstderr:\n{}".format( actual_exit_code, exit_code, stdout, stderr)) return stdout
def run_bandit( self, targets: List[PythonTargetAdaptorWithOrigin], *, config: Optional[str] = None, passthrough_args: Optional[str] = None, skip: bool = False, ) -> LintResult: args = ["--backend-packages2=pants.backend.python.lint.bandit"] if config: self.create_file(relpath=".bandit", contents=config) args.append("--bandit-config=.bandit") if passthrough_args: args.append(f"--bandit-args={passthrough_args}") if skip: args.append(f"--bandit-skip") return self.request_single_product( LintResult, Params(BanditLinter(tuple(targets)), create_options_bootstrapper(args=args)), )
def assert_stripped_source_file( self, *, original_path: str, expected_path: str, target_type_alias: Optional[str] = None, ) -> None: adaptor = Mock() adaptor.sources = Mock() source_files = {original_path: "print('random python')"} adaptor.sources.snapshot = self.make_snapshot(source_files) adaptor.address = Mock() adaptor.address.spec_path = original_path if target_type_alias: adaptor.type_alias = target_type_alias target = HydratedTarget('some/target/address', adaptor, tuple()) stripped_sources = self.request_single_product( SourceRootStrippedSources, Params(target, create_options_bootstrapper())) self.assertEqual(stripped_sources.snapshot.files, (expected_path, ))
def test_filters_out_irrelevant_targets(self) -> None: targets = [ self.create_target(parent_directory="src/python", files=["p.py"], target_cls=PythonTarget), self.create_target(parent_directory="src/python", files=["f.txt"], target_cls=Files), self.create_target(parent_directory="src/python", files=["r.txt"], target_cls=Resources), self.create_target(parent_directory="src/python", files=["j.java"], target_cls=NonPythonTarget), ] result = self.request_single_product( ImportablePythonSources, Params(Targets(targets), create_options_bootstrapper()), ) assert sorted(result.snapshot.files) == sorted( ["p.py", "src/python/f.txt", "r.txt"])
def run_console_rules(self, options_bootstrapper, goals, target_roots): """Runs @console_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :param list goals: The list of requested goal names as passed on the commandline. :param TargetRoots target_roots: The targets root of the request. """ # Reduce to only applicable goals - with validation happening by way of `validate_goals()`. goals = [goal for goal in goals if goal in self.goal_map] subjects = self._determine_subjects(target_roots) console = Console() # Console rule can only have one subject. assert len(subjects) == 1 for goal in goals: try: goal_product = self.goal_map[goal] params = Params(subjects[0], options_bootstrapper, console) logger.debug('requesting {} to satisfy execution of `{}` goal'.format(goal_product, goal)) self.scheduler_session.run_console_rule(goal_product, params) finally: console.flush()
def run_pytest( self, *, passthrough_args: Optional[str] = None, origin: Optional[OriginSpec] = None, ) -> TestResult: args = [ "--backend-packages2=pants.backend.python", # pin to lower versions so that we can run Python 2 tests "--pytest-version=pytest>=4.6.6,<4.7", "--pytest-pytest-plugins=['zipp==1.0.0']", ] if passthrough_args: args.append(f"--pytest-args='{passthrough_args}'") options_bootstrapper = create_options_bootstrapper(args=args) if origin is None: origin = SingleAddress(directory=self.source_root, name="target") # TODO: We must use the V1 target's `_sources_field.sources` field to set the TargetAdaptor's # sources attribute. The adaptor will not auto-populate this field. However, it will # auto-populate things like `dependencies` and this was not necessary before using # PythonTestsAdaptorWithOrigin. Why is this necessary in test code? v1_target = self.target(f"{self.source_root}:target") adaptor = PythonTestsAdaptor( address=v1_target.address.to_address(), sources=v1_target._sources_field.sources, ) params = Params( PytestRunner(PythonTestsAdaptorWithOrigin(adaptor, origin)), options_bootstrapper) test_result = self.request_single_product(TestResult, params) debug_request = self.request_single_product(TestDebugRequest, params) debug_result = InteractiveRunner( self.scheduler).run_local_interactive_process(debug_request.ipr) if test_result.status == Status.SUCCESS: assert debug_result.process_exit_code == 0 else: assert debug_result.process_exit_code != 0 return test_result