def test_coverage_global() -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage(tmpdir, "--coverage-py-global-report") assert ( dedent( f"""\ Name Stmts Miss Cover --------------------------------------------------------------------------------- {tmpdir}/foo/bar.py 1 1 0% {tmpdir}/src/python/core/__init__.py 0 0 100% {tmpdir}/src/python/core/untested.py 1 1 0% {tmpdir}/src/python/project/__init__.py 0 0 100% {tmpdir}/src/python/project/lib.py 6 0 100% {tmpdir}/src/python/project/lib_test.py 3 0 100% {tmpdir}/src/python/project/random.py 2 2 0% {tmpdir}/tests/python/project_test/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/BUILD.py 1 1 0% {tmpdir}/tests/python/project_test/no_src/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/test_no_src.py 2 0 100% {tmpdir}/tests/python/project_test/test_arithmetic.py 3 0 100% {tmpdir}/tests/python/project_test/test_multiply.py 3 0 100% --------------------------------------------------------------------------------- TOTAL 22 5 77% """ ) in result.stderr ), result.stderr
def test_sibling_addresses() -> None: """Semantics: * project introspection: include all targets that are "resident" to the directory, i.e. defined there or generated and reside there. * "build" goals: match all targets that are resident to the directory, but replace any target generators with their generated targets, even if those generated targets are resident to another directory! """ with setup_tmpdir(SOURCES) as tmpdir: assert run(["list", f"{tmpdir}/py/utils:"]).stdout.splitlines() == [ f"{tmpdir}/py/utils/strutil.py:../lib", f"{tmpdir}/py/utils/strutil_test.py:../tests", ] assert run(["list", f"{tmpdir}/py:"]).stdout.splitlines() == [ f"{tmpdir}/py:bin", f"{tmpdir}/py:lib", f"{tmpdir}/py:tests", f"{tmpdir}/py/app.py:lib", ] # Even though no `python_test` targets are explicitly defined in `util/`, a generated # target is resident there. test_result = run(["test", f"{tmpdir}/py/utils:"]).stderr assert f"{tmpdir}/py/utils/strutil_test.py:../tests succeeded." in test_result assert f"{tmpdir}/py/base/common_test.py:../tests" not in test_result assert f"{tmpdir}/py:tests" not in test_result # Even though no `_test.py` files live in this dir, we match the `python_tests` target # and replace it with its generated targets. test_result = run(["test", f"{tmpdir}/py:"]).stderr assert f"{tmpdir}/py/utils/strutil_test.py:../tests succeeded." in test_result assert f"{tmpdir}/py/base/common_test.py:../tests" in test_result assert f"{tmpdir}/py:tests" not in test_result
def test_coverage(major_minor_interpreter: str) -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage( tmpdir, f"--coverage-py-interpreter-constraints=['=={major_minor_interpreter}.*']" ) assert ( dedent( f"""\ Name Stmts Miss Cover --------------------------------------------------------------------------------- {tmpdir}/src/python/project/__init__.py 0 0 100% {tmpdir}/src/python/project/lib.py 6 0 100% {tmpdir}/src/python/project/lib_test.py 3 0 100% {tmpdir}/src/python/project/random.py 2 2 0% {tmpdir}/tests/python/project_test/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/test_no_src.py 2 0 100% {tmpdir}/tests/python/project_test/test_arithmetic.py 3 0 100% {tmpdir}/tests/python/project_test/test_multiply.py 3 0 100% --------------------------------------------------------------------------------- TOTAL 19 2 89% """ ) in result.stderr )
def test_no_strip_pex_env_issues_12057() -> None: sources = { "src/app.py": dedent("""\ import os import sys if __name__ == "__main__": exit_code = os.environ.get("PANTS_ISSUES_12057") if exit_code is None: os.environ["PANTS_ISSUES_12057"] = "42" os.execv(sys.executable, [sys.executable, *sys.argv]) sys.exit(int(exit_code)) """), "src/BUILD": dedent("""\ python_sources(name="lib") pex_binary(entry_point="app.py") """), } with setup_tmpdir(sources) as tmpdir: args = [ "--backend-packages=pants.backend.python", f"--source-root-patterns=['/{tmpdir}/src']", "run", f"{tmpdir}/src/app.py", ] result = run_pants(args) assert result.exit_code == 42, result.stderr
def package_determinism(expected_artifact_count: int, files: dict[str, str]) -> None: """Tests that the given sources can be `package`d reproducibly.""" def digest(path: str) -> tuple[str, str]: d = hashlib.sha256(Path(path).read_bytes()).hexdigest() return path, d def run_and_digest(address: str) -> dict[str, str]: safe_rmtree("dist") pants_run = run_pants([ "--backend-packages=pants.backend.python", "--no-pantsd", "package", address, ], ) pants_run.assert_success() return dict( digest(os.path.join("dist", f)) for f in os.listdir("dist")) with setup_tmpdir(files) as source_dir: one = run_and_digest(f"{source_dir}:dist") two = run_and_digest(f"{source_dir}:dist") assert len(one) == expected_artifact_count assert one == two
def test_export() -> None: with setup_tmpdir(SOURCES) as tmpdir: run_pants(["generate-lockfiles", "export", f"{tmpdir}/::"], config=build_config(tmpdir)).assert_success() export_prefix = os.path.join("dist", "export", "python", "virtualenvs") py_minor_version = f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}" for resolve, ansicolors_version in [("a", "1.1.8"), ("b", "1.0.2")]: export_dir = os.path.join(export_prefix, resolve, platform.python_version()) assert os.path.isdir( export_dir), f"expected export dir '{export_dir}' does not exist" lib_dir = os.path.join(export_dir, "lib", f"python{py_minor_version}", "site-packages") expected_ansicolors_dir = os.path.join( lib_dir, f"ansicolors-{ansicolors_version}.dist-info") assert os.path.isdir( expected_ansicolors_dir ), f"expected dist-info for ansicolors '{expected_ansicolors_dir}' does not exist" for tool_config in EXPORTED_TOOLS: export_dir = os.path.join(export_prefix, "tools", tool_config.name) assert os.path.isdir( export_dir), f"expected export dir '{export_dir}' does not exist" # NOTE: Not every tool implements --version so this is the best we can do. lib_dir = os.path.join(export_dir, "lib", f"python{py_minor_version}", "site-packages") expected_tool_dir = os.path.join( lib_dir, f"{tool_config.name}-{tool_config.version}.dist-info") assert os.path.isdir( expected_tool_dir ), f"expected dist-info for {tool_config.name} '{expected_tool_dir}' does not exist"
def test_no_leak_pex_root_issues_12055() -> None: read_config_result = run_pants(["help-all"]) read_config_result.assert_success() config_data = json.loads(read_config_result.stdout) global_advanced_options = { option["config_key"]: [ ranked_value["value"] for ranked_value in option["value_history"]["ranked_values"] ][-1] for option in config_data["scope_to_help_info"][""]["advanced"] } named_caches_dir = global_advanced_options["named_caches_dir"] sources = { "src/app.py": "import os; print(os.environ['PEX_ROOT'])", "src/BUILD": dedent("""\ python_sources(name="lib") pex_binary(entry_point="app.py") """), } with setup_tmpdir(sources) as tmpdir: args = [ "--backend-packages=pants.backend.python", f"--source-root-patterns=['/{tmpdir}/src']", "run", f"{tmpdir}/src/app.py", ] result = run_pants(args) result.assert_success() assert os.path.join(named_caches_dir, "pex_root") == result.stdout.strip()
def test_coverage_fail_under() -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage(tmpdir, "--coverage-py-fail-under=89") result.assert_success() result = run_coverage_that_may_fail(tmpdir, "--coverage-py-fail-under=90") result.assert_failure()
def test_pantsd_run(self): with self.pantsd_successful_run_context(log_level="debug") as ctx: with setup_tmpdir({"foo/BUILD": "target()"}) as tmpdir: ctx.runner(["list", f"{tmpdir}/foo::"]) ctx.checker.assert_started() ctx.runner(["list", f"{tmpdir}/foo::"]) ctx.checker.assert_running()
def test_build_ignore_list() -> None: with setup_tmpdir({"dir/BUILD": "files(sources=[])"}) as tmpdir: ignore_result = run_pants([f"--build-ignore={tmpdir}/dir", "list", f"{tmpdir}/dir"]) no_ignore_result = run_pants(["list", f"{tmpdir}/dir"]) ignore_result.assert_failure() assert f"{tmpdir}/dir" in ignore_result.stderr no_ignore_result.assert_success() assert f"{tmpdir}/dir" in no_ignore_result.stdout
def run_pants_with_sources(sources: dict[str, str], *args: str) -> PantsResult: with setup_tmpdir(sources) as tmpdir: return run_pants( [ "--backend-packages=pants.backend.experimental.docker", "--pants-ignore=__pycache__", ] + [arg.format(tmpdir=tmpdir) for arg in args] )
def test_deprecation_and_ignore_warnings(use_pantsd: bool) -> None: plugin = dedent("""\ from pants.option.subsystem import Subsystem from pants.engine.rules import SubsystemRule class Options(Subsystem): help = "Options just for a test." options_scope = "mock-options" @classmethod def register_options(cls, register): super().register_options(register) register( "--deprecated", removal_version="999.99.9.dev0", removal_hint="blah", ) def rules(): return [SubsystemRule(Options)] """) with setup_tmpdir({ "plugins/mock_options/register.py": plugin, "BUILD": "files(name='t', sources=['fake'])", }) as tmpdir: config = { "GLOBAL": { "pythonpath": [f"%(buildroot)s/{tmpdir}/plugins"], "backend_packages": ["mock_options"], }, "mock-options": { "deprecated": "foo" }, } unmatched_glob_warning = f"Unmatched glob from {tmpdir}:t's `sources` field" result = run_pants(["filedeps", f"{tmpdir}:t"], config=config, use_pantsd=use_pantsd) result.assert_success() assert unmatched_glob_warning in result.stderr assert ( "DEPRECATED: option 'deprecated' in scope 'mock-options' will be removed in version " "999.99.9.dev0.") in result.stderr config["GLOBAL"]["ignore_warnings"] = [ # type: ignore[index] unmatched_glob_warning, "$regex$DeprecationWarning: DEPRECATED: option 'de.+ted'", ] ignore_result = run_pants(["filedeps", f"{tmpdir}:t"], config=config, use_pantsd=use_pantsd) ignore_result.assert_success() assert unmatched_glob_warning not in ignore_result.stderr assert "DEPRECATED: option 'another_deprecated'" not in ignore_result.stderr
def test_unimplemented_goals_noop() -> None: # Running on a `files` target should usually fail, but it should no-op if no `run` # implementations are activated. with setup_tmpdir({ "bad.txt": "", "BUILD": "files(sources=['f.txt')" }) as tmpdir: run_pants(["run", tmpdir]).assert_success() run_pants( ["--backend-packages=['pants.backend.python']", "run", tmpdir]).assert_failure()
def test_workunits_logger() -> None: with setup_tmpdir({}) as tmpdir: dest = os.path.join(tmpdir, "dest.log") pants_run = run_pants([ "--backend-packages=+['workunit_logger','pants.backend.python']", f"--workunit-logger-dest={dest}", "list", "3rdparty::", ]) pants_run.assert_success() # Assert that the file was created and non-empty. assert maybe_read_file(dest)
def run(*extra_args: str, **extra_env: str) -> PantsResult: with setup_tmpdir(sources) as tmpdir: args = [ "--backend-packages=pants.backend.python", f"--source-root-patterns=['/{tmpdir}/src_root1', '/{tmpdir}/src_root2']", "--pants-ignore=__pycache__", "--pants-ignore=/src/python", "run", f"{tmpdir}/src_root1/project/app.py", *extra_args, ] return run_pants(args, extra_env=extra_env)
def test_default_coverage_issues_12390() -> None: # N.B.: This ~replicates the repo used to reproduce this issue at # https://github.com/alexey-tereshenkov-oxb/monorepo-coverage-pants. files = { "requirements.txt": "PySide2==5.15.2", "BUILD": dedent( """\ python_requirements( module_mapping={{ "PySide2": ["PySide2"], }}, ) """ ), "minimalcov/minimalcov/src/foo.py": 'print("In the foo module!")', "minimalcov/minimalcov/src/BUILD": "python_library()", "minimalcov/minimalcov/tests/test_foo.py": dedent( """\ import minimalcov.src.foo from PySide2.QtWidgets import QApplication def test_1(): assert True """ ), "minimalcov/minimalcov/tests/BUILD": "python_tests()", } with setup_tmpdir(files) as tmpdir: command = [ "--backend-packages=pants.backend.python", "test", "--use-coverage", "::", f"--source-root-patterns=['/{tmpdir}/minimalcov']", "--coverage-py-report=raw", ] result = run_pants(command) result.assert_success() assert ( dedent( f"""\ Name Stmts Miss Cover ------------------------------------------------------------------------- {tmpdir}/minimalcov/minimalcov/src/foo.py 1 0 100% {tmpdir}/minimalcov/minimalcov/tests/test_foo.py 4 0 100% ------------------------------------------------------------------------- TOTAL 5 0 100% """ ) in result.stderr ), result.stderr
def test_pantsd_file_logging(self) -> None: with self.pantsd_successful_run_context("debug") as ctx: with setup_tmpdir({"foo/BUILD": "files(sources=[])"}) as tmpdir: daemon_run = ctx.runner( ["--backend-packages=pants.backend.python", "list", f"{tmpdir}/foo::"] ) ctx.checker.assert_started() assert "[DEBUG] Connecting to pantsd on port" in daemon_run.stderr assert "[DEBUG] Connected to pantsd" in daemon_run.stderr pants_log = "\n".join(read_pants_log(ctx.workdir)) assert "[INFO] handling request" in pants_log
def test_unicode_containing_exception(use_pantsd: bool) -> None: with setup_tmpdir(dir_layout) as tmpdir: pants_run = run_pants( [ "--backend-packages=pants.backend.python", "run", os.path.join(tmpdir, "exiter_integration_test_harness", "main.py"), ], use_pantsd=use_pantsd, ) pants_run.assert_failure() assert "import sys¡" in pants_run.stderr
def test_build_ignore_dependency() -> None: sources = { "dir1/BUILD": "files(sources=[])", "dir2/BUILD": "files(sources=[], dependencies=['{tmpdir}/dir1'])", } with setup_tmpdir(sources) as tmpdir: ignore_result = run_pants( [f"--build-ignore={tmpdir}/dir1", "dependencies", f"{tmpdir}/dir2"] ) no_ignore_result = run_pants(["dependencies", f"{tmpdir}/dir2"]) ignore_result.assert_failure() assert f"{tmpdir}/dir1" in ignore_result.stderr no_ignore_result.assert_success() assert f"{tmpdir}/dir1" in no_ignore_result.stdout
def test_address_literal() -> None: """Semantics: * project introspection: do not replace target generators with generated. * "build" goals: replace target generators with generated. """ with setup_tmpdir(SOURCES) as tmpdir: list_specs = [f"{tmpdir}/py:bin", f"{tmpdir}/py:tests", f"{tmpdir}/py/app.py:lib"] assert run(["list", *list_specs]).stdout.splitlines() == list_specs test_result = run(["test", f"{tmpdir}/py:tests"]).stderr assert f"{tmpdir}/py/utils/strutil_test.py:../tests succeeded." in test_result assert f"{tmpdir}/py/base/common_test.py:../tests succeeded." in test_result assert f"{tmpdir}/py:tests" not in test_result
def test_coverage_with_filter() -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage( tmpdir, "--coverage-py-filter=['project.lib', 'project_test.no_src']") assert (dedent(f"""\ Name Stmts Miss Cover --------------------------------------------------------------------------------- {tmpdir}/src/python/project/lib.py 6 0 100% {tmpdir}/tests/python/project_test/no_src/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/test_no_src.py 2 0 100% --------------------------------------------------------------------------------- TOTAL 8 0 100% """) in result.stderr)
def test_file_arg() -> None: """Semantics: find the 'owning' target, using generated target rather than target generator when possible (regardless of project introspection vs. "build" goal). Also, check that we support 'secondary ownership', e.g. a `pex_binary` being associated with the the file `app.py` even though it does not have a `sources` field. """ with setup_tmpdir(SOURCES) as tmpdir: assert run( ["list", f"{tmpdir}/py/app.py", f"{tmpdir}/py/utils/strutil_test.py"] ).stdout.splitlines() == [ f"{tmpdir}/py:bin", f"{tmpdir}/py/app.py:lib", f"{tmpdir}/py/utils/strutil_test.py:../tests", ]
def run(args: list[str], success: bool = True, *, files: dict[str, str] | None = None) -> tuple[PantsResult, str | None]: with setup_tmpdir(files or {}) as tmpdir: dest = os.path.join(tmpdir, "dest.log") normalized_args = [arg.format(tmpdir=tmpdir) for arg in args] pants_run = run_pants(normalized_args, config=workunit_logger_config(dest)) if success: pants_run.assert_success() confirm_eventual_success(dest) else: pants_run.assert_failure() return pants_run, maybe_read_file(dest)
def test_native_logging() -> None: expected_msg = r"\[DEBUG\] Launching \d+ root" with setup_tmpdir({"foo/BUILD": "files(sources=[])"}) as tmpdir: pants_run = run_pants( ["-linfo", "--backend-packages=pants.backend.python", "list", f"{tmpdir}/foo::"] ) pants_run.assert_success() assert not bool(re.search(expected_msg, pants_run.stderr)) pants_run = run_pants( ["-ldebug", "--backend-packages=pants.backend.python", "list", f"{tmpdir}/foo::"] ) pants_run.assert_success() assert bool(re.search(expected_msg, pants_run.stderr))
def test_run_sample_script(tgt_content: str) -> None: """Test that we properly run a `pex_binary` target. This checks a few things: - We can handle source roots. - We properly load third party requirements. - We propagate the error code. """ sources = { "src_root1/project/app.py": dedent( """\ import sys from utils.strutil import upper_case def main(): print(upper_case("Hello world.")) print("Hola, mundo.", file=sys.stderr) sys.exit(23) if __name__ == "__main__": main() """ ), "src_root1/project/BUILD": tgt_content, "src_root2/utils/strutil.py": dedent( """\ def upper_case(s): return s.upper() """ ), "src_root2/utils/BUILD": "python_library()", } with setup_tmpdir(sources) as tmpdir: result = run_pants( [ "--backend-packages=pants.backend.python", f"--source-root-patterns=['/{tmpdir}/src_root1', '/{tmpdir}/src_root2']", "--pants-ignore=__pycache__", "--pants-ignore=/src/python", "run", f"{tmpdir}/src_root1/project/app.py", ] ) assert "Hola, mundo.\n" in result.stderr assert result.stdout == "HELLO WORLD.\n" assert result.exit_code == 23
def test_pantsd_pantsd_runner_doesnt_die_after_failed_run(self): with self.pantsd_test_context() as (workdir, pantsd_config, checker): # Run target that throws an exception in pants. with setup_tmpdir(compilation_failure_dir_layout) as tmpdir: self.run_pants_with_workdir( ["lint", os.path.join(tmpdir, "compilation_failure", "main.py")], workdir=workdir, config=pantsd_config, ).assert_failure() checker.assert_started() # Assert pantsd is in a good functional state. self.run_pants_with_workdir( ["help"], workdir=workdir, config=pantsd_config ).assert_success() checker.assert_running()
def test_deprecation_and_ignore_pants_warnings(use_pantsd: bool) -> None: plugin = dedent("""\ from pants.option.subsystem import Subsystem from pants.engine.rules import SubsystemRule class Options(Subsystem): '''Options just for a test.''' options_scope = "mock-options" @classmethod def register_options(cls, register): super().register_options(register) register( "--deprecated", removal_version="999.99.9.dev0", removal_hint="blah", ) def rules(): return [SubsystemRule(Options)] """) with setup_tmpdir({"plugins/mock_options/register.py": plugin}) as tmpdir: config = { "GLOBAL": { "pythonpath": [f"%(buildroot)s/{tmpdir}/plugins"], "backend_packages": ["mock_options"], }, "mock-options": { "deprecated": "foo" }, } result = run_pants(["help"], config=config, use_pantsd=use_pantsd) result.assert_success() assert ( "DEPRECATED: option 'deprecated' in scope 'mock-options' will be removed in version " "999.99.9.dev0.") in result.stderr # Now use `ignore_pants_warnings`. config["GLOBAL"]["ignore_pants_warnings"] = [ "DEPRECATED: option 'deprecated'" ] # type: ignore[index] ignore_result = run_pants(["help"], config=config, use_pantsd=use_pantsd) ignore_result.assert_success() assert "DEPRECATED: option 'deprecated'" not in ignore_result.stderr
def test_coverage() -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage(tmpdir) assert (dedent(f"""\ Name Stmts Miss Cover --------------------------------------------------------------------------------- {tmpdir}/src/python/project/__init__.py 0 0 100% {tmpdir}/src/python/project/lib.py 6 0 100% {tmpdir}/src/python/project/lib_test.py 3 0 100% {tmpdir}/tests/python/project_test/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/__init__.py 0 0 100% {tmpdir}/tests/python/project_test/no_src/test_no_src.py 2 0 100% {tmpdir}/tests/python/project_test/test_arithmetic.py 3 0 100% {tmpdir}/tests/python/project_test/test_multiply.py 3 0 100% --------------------------------------------------------------------------------- TOTAL 17 0 100% """) in result.stderr)
def test_coverage_html_xml_json() -> None: with setup_tmpdir(SOURCES) as tmpdir: result = run_coverage(tmpdir, "--coverage-py-report=['xml', 'html', 'json']") coverage_path = Path(get_buildroot(), "dist", "coverage", "python") assert coverage_path.exists() is True assert "Wrote xml coverage report to `dist/coverage/python`" in result.stderr xml_coverage = coverage_path / "coverage.xml" assert xml_coverage.exists() is True assert "Wrote html coverage report to `dist/coverage/python`" in result.stderr html_cov_dir = coverage_path / "htmlcov" assert html_cov_dir.exists() is True assert (html_cov_dir / "index.html").exists() is True assert "Wrote json coverage report to `dist/coverage/python`" in result.stderr json_coverage = coverage_path / "coverage.json" assert json_coverage.exists() is True
def test_log_filtering_by_target() -> None: sources = { "src/python/project/__init__.py": "", "src/python/project/BUILD": "python_library()", } with setup_tmpdir(sources) as tmpdir: result = run_pants([ "--no-pantsd", "--backend-packages=['pants.backend.python']", "--no-dynamic-ui", "--level=info", "--show-log-target", '--log-levels-by-target={"workunit_store": "debug"}', "list", f"{tmpdir}/src/python/project", ]) assert "[DEBUG] (workunit_store) Starting: `list` goal" in result.stderr