"""Tests for the requirements.txt and pyproject.toml parsers."""

from __future__ import annotations

import logging
from pathlib import Path

import pytest

from depwhy.parsers.pyproject import parse_pyproject
from depwhy.parsers.requirements import parse_requirements

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def write_req(tmp_path: Path, name: str, content: str) -> Path:
    """Write *content* to a file named *name* inside *tmp_path* and return its Path."""
    p = tmp_path / name
    p.write_text(content, encoding="utf-8")
    return p


# ---------------------------------------------------------------------------
# Basic parsing
# ---------------------------------------------------------------------------


class TestBasicParsing:
    """Core happy-path behaviour."""

    def test_pinned_versions(self, tmp_path: Path) -> None:
        """Pinned requirements are returned verbatim (normalised by packaging)."""
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "django==4.2.0\ncelery==5.3.6\n",
        )
        result = parse_requirements(req_file)
        assert result == ["django==4.2.0", "celery==5.3.6"]

    def test_version_specifiers(self, tmp_path: Path) -> None:
        """Various version specifiers are preserved."""
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "requests>=2.28\nhttpx<1.0\npackaging!=23.0\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 3
        assert any("requests" in r for r in result)
        assert any("httpx" in r for r in result)
        assert any("packaging" in r for r in result)

    def test_no_version(self, tmp_path: Path) -> None:
        """Requirements without version specifiers are accepted."""
        req_file = write_req(tmp_path, "requirements.txt", "requests\n")
        result = parse_requirements(req_file)
        assert result == ["requests"]

    def test_returns_list(self, tmp_path: Path) -> None:
        """Return type is always a list."""
        req_file = write_req(tmp_path, "requirements.txt", "")
        assert isinstance(parse_requirements(req_file), list)

    def test_empty_file(self, tmp_path: Path) -> None:
        """An empty file returns an empty list."""
        req_file = write_req(tmp_path, "requirements.txt", "")
        assert parse_requirements(req_file) == []


# ---------------------------------------------------------------------------
# Comments and blank lines
# ---------------------------------------------------------------------------


class TestCommentsAndBlankLines:
    """Comment stripping and blank-line skipping."""

    def test_full_line_comment_skipped(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "# This is a comment\ndjango==4.2.0\n",
        )
        result = parse_requirements(req_file)
        assert result == ["django==4.2.0"]

    def test_inline_comment_stripped(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "requests>=2.28  # HTTP library\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert result[0].startswith("requests")
        assert "#" not in result[0]

    def test_blank_lines_skipped(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "\n\ndjango==4.2.0\n\ncelery==5.3.6\n\n",
        )
        result = parse_requirements(req_file)
        assert result == ["django==4.2.0", "celery==5.3.6"]

    def test_mixed_comments_blanks_and_reqs(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "# header\n\ndjango==4.2.0  # pinned\n\n# footer\n",
        )
        result = parse_requirements(req_file)
        assert result == ["django==4.2.0"]


# ---------------------------------------------------------------------------
# Line continuations
# ---------------------------------------------------------------------------


class TestLineContinuations:
    """Backslash line-continuation handling."""

    def test_simple_continuation(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "django\\\n==4.2.0\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "django" in result[0]

    def test_multi_line_continuation(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "package[\\\nextra1,\\\nextra2\\\n]>=1.0\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "package" in result[0]
        assert "extra1" in result[0]
        assert "extra2" in result[0]


# ---------------------------------------------------------------------------
# -r / --requirement includes
# ---------------------------------------------------------------------------


class TestRecursiveIncludes:
    """-r and --requirement include directives."""

    def test_r_include_resolved(self, tmp_path: Path) -> None:
        base = write_req(tmp_path, "requirements.txt", "-r base.txt\ndjango==4.2.0\n")
        write_req(tmp_path, "base.txt", "requests>=2.28\n")
        result = parse_requirements(base)
        assert any("requests" in r for r in result)
        assert any("django" in r for r in result)

    def test_requirement_long_form_include(self, tmp_path: Path) -> None:
        base = write_req(
            tmp_path,
            "requirements.txt",
            "--requirement base.txt\ndjango==4.2.0\n",
        )
        write_req(tmp_path, "base.txt", "requests>=2.28\n")
        result = parse_requirements(base)
        assert any("requests" in r for r in result)

    def test_nested_r_include(self, tmp_path: Path) -> None:
        """Three-level recursive include."""
        top = write_req(tmp_path, "top.txt", "-r mid.txt\ntop-pkg==1.0\n")
        write_req(tmp_path, "mid.txt", "-r leaf.txt\nmid-pkg==2.0\n")
        write_req(tmp_path, "leaf.txt", "leaf-pkg==3.0\n")
        result = parse_requirements(top)
        assert any("top-pkg" in r for r in result)
        assert any("mid-pkg" in r for r in result)
        assert any("leaf-pkg" in r for r in result)

    def test_r_include_preserves_order(self, tmp_path: Path) -> None:
        """Requirements from included files appear before those in the parent."""
        base = write_req(tmp_path, "requirements.txt", "-r base.txt\nz-pkg==1.0\n")
        write_req(tmp_path, "base.txt", "a-pkg==1.0\n")
        result = parse_requirements(base)
        assert result.index(next(r for r in result if "a-pkg" in r)) < result.index(
            next(r for r in result if "z-pkg" in r)
        )


# ---------------------------------------------------------------------------
# Circular include detection
# ---------------------------------------------------------------------------


class TestCircularIncludes:
    """Circular -r includes must raise ValueError."""

    def test_direct_circular(self, tmp_path: Path) -> None:
        a = tmp_path / "a.txt"
        b = tmp_path / "b.txt"
        a.write_text("-r b.txt\n", encoding="utf-8")
        b.write_text("-r a.txt\n", encoding="utf-8")
        with pytest.raises(ValueError, match="[Cc]ircular"):
            parse_requirements(a)

    def test_self_include(self, tmp_path: Path) -> None:
        a = write_req(tmp_path, "a.txt", "-r a.txt\n")
        with pytest.raises(ValueError, match="[Cc]ircular"):
            parse_requirements(a)


# ---------------------------------------------------------------------------
# Options lines
# ---------------------------------------------------------------------------


class TestOptionsLines:
    """Option flags like -i, --index-url should be silently skipped."""

    @pytest.mark.parametrize(
        "option_line",
        [
            "-i https://pypi.org/simple\n",
            "--index-url https://pypi.org/simple\n",
            "--extra-index-url https://my.registry.example/simple\n",
            "--find-links /path/to/wheels\n",
            "-f /path/to/wheels\n",
            "--trusted-host my.registry.example\n",
            "--no-binary :all:\n",
            "--only-binary :none:\n",
            "--prefer-binary\n",
            "--require-hashes\n",
            "--pre\n",
            "--index-url=https://pypi.org/simple\n",  # = form
        ],
    )
    def test_option_line_skipped(self, tmp_path: Path, option_line: str) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            option_line + "requests>=2.28\n",
        )
        result = parse_requirements(req_file)
        assert result == ["requests>=2.28"]

    def test_only_options_returns_empty(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "--index-url https://pypi.org/simple\n--pre\n",
        )
        assert parse_requirements(req_file) == []


# ---------------------------------------------------------------------------
# Editable installs
# ---------------------------------------------------------------------------


class TestEditableInstalls:
    """-e / --editable installs are skipped with a warning."""

    def test_editable_skipped(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "-e ./mypackage\nrequests>=2.28\n",
        )
        result = parse_requirements(req_file)
        assert result == ["requests>=2.28"]

    def test_editable_long_form_skipped(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "--editable ./mypackage\nrequests>=2.28\n",
        )
        result = parse_requirements(req_file)
        assert result == ["requests>=2.28"]

    def test_editable_emits_warning(self, tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "-e ./mypackage\n",
        )
        with caplog.at_level(logging.WARNING, logger="depwhy.parsers.requirements"):
            parse_requirements(req_file)
        assert any("editable" in record.message.lower() for record in caplog.records)


# ---------------------------------------------------------------------------
# Invalid requirements
# ---------------------------------------------------------------------------


class TestInvalidRequirements:
    """Malformed requirement strings must raise a clear error."""

    def test_invalid_requirement_raises(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "not a valid requirement!!!\n",
        )
        with pytest.raises(ValueError):
            parse_requirements(req_file)

    def test_error_message_contains_line(self, tmp_path: Path) -> None:
        bad_line = "!!!bad!!!"
        req_file = write_req(tmp_path, "requirements.txt", bad_line + "\n")
        with pytest.raises(ValueError, match=bad_line):
            parse_requirements(req_file)


# ---------------------------------------------------------------------------
# File not found
# ---------------------------------------------------------------------------


class TestFileNotFound:
    """Non-existent files raise FileNotFoundError."""

    def test_missing_file_raises(self, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            parse_requirements(tmp_path / "does_not_exist.txt")

    def test_missing_include_raises(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "-r missing.txt\n",
        )
        with pytest.raises(FileNotFoundError):
            parse_requirements(req_file)


# ---------------------------------------------------------------------------
# Environment markers
# ---------------------------------------------------------------------------


class TestEnvironmentMarkers:
    """Environment markers must be preserved in the output."""

    def test_sys_platform_marker(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "pywin32; sys_platform == 'win32'\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "sys_platform" in result[0]
        assert "win32" in result[0]

    def test_python_version_marker(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "tomli>=2.0; python_version < '3.11'\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "python_version" in result[0]

    def test_marker_with_inline_comment(self, tmp_path: Path) -> None:
        """Inline comments after markers must not corrupt the marker."""
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "pywin32; sys_platform == 'win32'  # Windows only\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        # The marker should still be intact
        assert "sys_platform" in result[0]


# ---------------------------------------------------------------------------
# Extras
# ---------------------------------------------------------------------------


class TestExtras:
    """Package extras must be preserved."""

    def test_single_extra(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "uvicorn[standard]>=0.20\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "standard" in result[0]

    def test_multiple_extras(self, tmp_path: Path) -> None:
        req_file = write_req(
            tmp_path,
            "requirements.txt",
            "package[extra1,extra2]>=1.0\n",
        )
        result = parse_requirements(req_file)
        assert len(result) == 1
        assert "extra1" in result[0]
        assert "extra2" in result[0]


# ---------------------------------------------------------------------------
# Encoding edge cases
# ---------------------------------------------------------------------------


class TestEncodingEdgeCases:
    """UTF-8 BOM and Windows line endings."""

    def test_utf8_bom_handled(self, tmp_path: Path) -> None:
        """A UTF-8 BOM at the start of the file must not corrupt parsing."""
        req_file = tmp_path / "requirements.txt"
        # Write raw bytes with BOM
        req_file.write_bytes(b"\xef\xbb\xbfrequests>=2.28\ndjango==4.2.0\n")
        result = parse_requirements(req_file)
        assert any("requests" in r for r in result)
        assert any("django" in r for r in result)

    def test_windows_line_endings(self, tmp_path: Path) -> None:
        """CRLF line endings are handled correctly."""
        req_file = tmp_path / "requirements.txt"
        req_file.write_bytes(b"requests>=2.28\r\ndjango==4.2.0\r\n")
        result = parse_requirements(req_file)
        assert result == ["requests>=2.28", "django==4.2.0"]

    def test_utf8_bom_with_windows_endings(self, tmp_path: Path) -> None:
        """BOM + CRLF combination is handled."""
        req_file = tmp_path / "requirements.txt"
        req_file.write_bytes(b"\xef\xbb\xbfrequests>=2.28\r\ndjango==4.2.0\r\n")
        result = parse_requirements(req_file)
        assert any("requests" in r for r in result)
        assert any("django" in r for r in result)


# ---------------------------------------------------------------------------
# accepts str and Path-like
# ---------------------------------------------------------------------------


class TestSourceTypes:
    """parse_requirements accepts both str and Path-like objects."""

    def test_accepts_str(self, tmp_path: Path) -> None:
        req_file = write_req(tmp_path, "requirements.txt", "requests>=2.28\n")
        result = parse_requirements(str(req_file))
        assert result == ["requests>=2.28"]

    def test_accepts_path(self, tmp_path: Path) -> None:
        req_file = write_req(tmp_path, "requirements.txt", "requests>=2.28\n")
        result = parse_requirements(req_file)
        assert result == ["requests>=2.28"]


# ===========================================================================
# pyproject.toml parser tests
# ===========================================================================


def write_toml(tmp_path: Path, name: str, content: str) -> Path:
    """Write *content* to a file named *name* inside *tmp_path* and return its Path."""
    p = tmp_path / name
    p.write_text(content, encoding="utf-8")
    return p


# ---------------------------------------------------------------------------
# Basic happy-path parsing
# ---------------------------------------------------------------------------


class TestPyprojectBasicParsing:
    """Core happy-path behaviour for parse_pyproject."""

    def test_standard_dependencies(self, tmp_path: Path) -> None:
        """Standard [project.dependencies] entries are returned."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            "dependencies = [\n"
            '  "requests>=2.28",\n'
            '  "django==4.2.0",\n'
            "]\n",
        )
        result = parse_pyproject(toml_file)
        assert len(result) == 2
        assert any("requests" in r for r in result)
        assert any("django" in r for r in result)

    def test_returns_list(self, tmp_path: Path) -> None:
        """Return type is always a list."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\ndependencies = []\n',
        )
        assert isinstance(parse_pyproject(toml_file), list)

    def test_accepts_str_source(self, tmp_path: Path) -> None:
        """parse_pyproject accepts a str path."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\ndependencies = ["requests>=2.28"]\n',
        )
        result = parse_pyproject(str(toml_file))
        assert any("requests" in r for r in result)

    def test_accepts_path_source(self, tmp_path: Path) -> None:
        """parse_pyproject accepts a Path object."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\ndependencies = ["requests>=2.28"]\n',
        )
        result = parse_pyproject(toml_file)
        assert any("requests" in r for r in result)


# ---------------------------------------------------------------------------
# Optional dependencies
# ---------------------------------------------------------------------------


class TestPyprojectOptionalDependencies:
    """Optional dependency groups are included in the output."""

    def test_optional_dependencies_included(self, tmp_path: Path) -> None:
        """All optional-dependency group entries appear in the result."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            'dependencies = ["requests>=2.28"]\n'
            "\n"
            "[project.optional-dependencies]\n"
            'dev = ["pytest>=8.0", "ruff>=0.4.0"]\n'
            'docs = ["sphinx>=7.0"]\n',
        )
        result = parse_pyproject(toml_file)
        assert any("requests" in r for r in result)
        assert any("pytest" in r for r in result)
        assert any("ruff" in r for r in result)
        assert any("sphinx" in r for r in result)

    def test_no_optional_dependencies_section(self, tmp_path: Path) -> None:
        """A file with no [project.optional-dependencies] is handled gracefully."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\ndependencies = ["requests>=2.28"]\n',
        )
        result = parse_pyproject(toml_file)
        assert result == ["requests>=2.28"]

    def test_multiple_optional_groups_all_included(self, tmp_path: Path) -> None:
        """Dependencies from every optional group are present."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            'dependencies = ["httpx>=0.27"]\n'
            "\n"
            "[project.optional-dependencies]\n"
            'extra1 = ["pkg-a==1.0"]\n'
            'extra2 = ["pkg-b==2.0"]\n'
            'extra3 = ["pkg-c==3.0"]\n',
        )
        result = parse_pyproject(toml_file)
        assert any("httpx" in r for r in result)
        assert any("pkg-a" in r for r in result)
        assert any("pkg-b" in r for r in result)
        assert any("pkg-c" in r for r in result)


# ---------------------------------------------------------------------------
# Empty dependencies
# ---------------------------------------------------------------------------


class TestPyprojectEmptyDependencies:
    """Empty dependency lists return an empty list."""

    def test_empty_dependencies_returns_empty_list(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\ndependencies = []\n',
        )
        result = parse_pyproject(toml_file)
        assert result == []

    def test_empty_optional_group_included_without_error(self, tmp_path: Path) -> None:
        """An optional group that is an empty list should not cause errors."""
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            'dependencies = ["requests>=2.28"]\n'
            "\n"
            "[project.optional-dependencies]\n"
            "extras = []\n",
        )
        result = parse_pyproject(toml_file)
        assert result == ["requests>=2.28"]


# ---------------------------------------------------------------------------
# Environment markers
# ---------------------------------------------------------------------------


class TestPyprojectEnvironmentMarkers:
    """Environment markers must be preserved in the output."""

    def test_sys_platform_marker_preserved(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            "dependencies = [\"pywin32; sys_platform == 'win32'\"]\n",
        )
        result = parse_pyproject(toml_file)
        assert len(result) == 1
        assert "sys_platform" in result[0]
        assert "win32" in result[0]

    def test_python_version_marker_preserved(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            "dependencies = [\"tomli>=2.0; python_version < '3.11'\"]\n",
        )
        result = parse_pyproject(toml_file)
        assert len(result) == 1
        assert "python_version" in result[0]
        assert "3.11" in result[0]

    def test_marker_in_optional_dep_preserved(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project]\n"
            'name = "myproject"\n'
            'version = "1.0.0"\n'
            "dependencies = []\n"
            "\n"
            "[project.optional-dependencies]\n"
            "extras = [\"colorama; sys_platform == 'win32'\"]\n",
        )
        result = parse_pyproject(toml_file)
        assert len(result) == 1
        assert "sys_platform" in result[0]


# ---------------------------------------------------------------------------
# Missing [project] section
# ---------------------------------------------------------------------------


class TestPyprojectMissingProjectSection:
    """Missing [project] table must raise a clear ValueError."""

    def test_missing_project_raises_value_error(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[build-system]\nrequires = ["hatchling"]\nbuild-backend = "hatchling.build"\n',
        )
        with pytest.raises(ValueError, match=r"\[project\]"):
            parse_pyproject(toml_file)

    def test_missing_project_error_mentions_file(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[tool.something]\nfoo = 'bar'\n",
        )
        with pytest.raises(ValueError):
            parse_pyproject(toml_file)


# ---------------------------------------------------------------------------
# Missing [project.dependencies]
# ---------------------------------------------------------------------------


class TestPyprojectMissingDependencies:
    """Missing [project.dependencies] key must raise a clear ValueError."""

    def test_missing_dependencies_raises_value_error(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\n',
        )
        with pytest.raises(ValueError, match=r"dependencies"):
            parse_pyproject(toml_file)

    def test_missing_dependencies_error_mentions_file(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            '[project]\nname = "myproject"\nversion = "1.0.0"\n',
        )
        with pytest.raises(ValueError):
            parse_pyproject(toml_file)


# ---------------------------------------------------------------------------
# TOML syntax errors
# ---------------------------------------------------------------------------


class TestPyprojectTomlSyntaxErrors:
    """Syntactically invalid TOML must raise a clear ValueError."""

    def test_invalid_toml_raises_value_error(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "[project\nname = 'broken'\n",  # missing closing bracket
        )
        with pytest.raises(ValueError, match=r"[Ss]yntax|TOML|parse"):
            parse_pyproject(toml_file)

    def test_toml_syntax_error_mentions_file(self, tmp_path: Path) -> None:
        toml_file = write_toml(
            tmp_path,
            "pyproject.toml",
            "this is not toml at all !!!\n",
        )
        with pytest.raises(ValueError):
            parse_pyproject(toml_file)


# ---------------------------------------------------------------------------
# File not found
# ---------------------------------------------------------------------------


class TestPyprojectFileNotFound:
    """Non-existent files raise FileNotFoundError."""

    def test_missing_file_raises_file_not_found(self, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            parse_pyproject(tmp_path / "does_not_exist.toml")

    def test_missing_file_str_path_raises_file_not_found(self, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            parse_pyproject(str(tmp_path / "no_such_file.toml"))
