[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pythonnative/pythonnative/main/tests/test_codegen.py [Back]  [Original]

"""Generated native contracts match a clean Python interpreter."""

import os
import re
import runpy
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Literal

import pytest

from pythonnative.sdk.native_types import NativeTypes, quoted


def test_checked_native_contracts_match_python_definitions(tmp_path: Path) -> None:
    subprocess.run(
        [
            sys.executable,
            "-c",
            "import pythonnative; from pythonnative.sdk.codegen import generate; " "import sys; generate(sys.argv[1])",
            str(tmp_path),
        ],
        check=True,
    )
    root = Path(__file__).parents[1] / "src/pythonnative/native"
    for extension, folder in (
        ("swift", root / "ios/Sources/PythonNativeKit/Generated"),
        ("kt", root / "android/src/main/java/com/pythonnative/generated"),
    ):
        for name in ("PNContracts", "NativeProps", "NativeModules", "NativeValues"):
            assert (tmp_path / f"{name}.{extension}").read_bytes() == (folder / f"{name}.{extension}").read_bytes()
    for name in ("components.py", "modules.py"):
        compile((tmp_path / name).read_text(), str(tmp_path / name), "exec")


def test_extension_manifests_keep_builtin_type_names(tmp_path: Path) -> None:
    # A plugin build loads extension manifests from JSON, which turns tuples
    # into lists. An extension component that redeclares View's props must
    # not rename the built-in types that hand-written native code spells.
    script = (
        "import json, sys; import pythonnative; from pythonnative.sdk import schema; "
        "from pythonnative.sdk.codegen import generate; "
        "badge = json.loads(json.dumps(schema.manifest()['components']['View'], default=str)); "
        "badge['name'] = 'AaaBadge'; "
        "schema.load_manifest({'protocol': 4, 'yoga': '3.2.1', 'components': {'AaaBadge': badge}}); "
        "generate(sys.argv[1])"
    )
    subprocess.run([sys.executable, "-c", script, str(tmp_path)], check=True)
    root = Path(__file__).parents[1] / "src/pythonnative/native"
    declaration = re.compile(r"^(?:sealed class|data class|enum class|public enum|public struct) (PN\w+)", re.M)
    for extension, folder in (
        ("swift", root / "ios/Sources/PythonNativeKit/Generated"),
        ("kt", root / "android/src/main/java/com/pythonnative/generated"),
    ):
        checked = set(declaration.findall((folder / f"NativeValues.{extension}").read_text()))
        assert checked  str:
            return text.split("open class PNViewProps", 1)[1].split("\n}\n", 1)[0]

        assert base((tmp_path / f"NativeProps.{extension}").read_text()) == base(
            (folder / f"NativeProps.{extension}").read_text()
        )


def test_example_extension_contracts_match_the_builtins(tmp_path: Path) -> None:
    # The inbox extension's manifest copies View's props; regenerate it in a
    # scratch copy so a built-in change can't leave it stale unnoticed.
    source = Path(__file__).parents[1] / "examples/inbox-extension"
    copy = tmp_path / "inbox-extension"
    shutil.copytree(source / "src", copy / "src")
    shutil.copy2(source / "generate_contracts.py", copy / "generate_contracts.py")
    subprocess.run(
        [sys.executable, str(copy / "generate_contracts.py")],
        check=True,
        env={**os.environ, "PYTHONPATH": str(copy / "src")},
    )
    for relative in ("inbox_extension/native/schema.json", "inbox_extension/api.py"):
        assert (copy / "src" / relative).read_text() == (source / "src" / relative).read_text(), relative


def test_generated_literals_preserve_interpolation_and_control_characters() -> None:
    value = '$total "quoted" \\path\n\x00\b'
    assert quoted(value) == r'"$total \"quoted\" \\path\n\u{0}\u{8}"'
    assert quoted(value, kotlin=True) == r'"\$total \"quoted\" \\path\n\u0000\u0008"'
    types = NativeTypes()
    types.types({"enum": ["$total", "\x00"]}, "EscapedLiteral")
    assert r'"\$total"' in "\n".join(types.kotlin)
    assert r'"\u{0}"' in "\n".join(types.swift)


def test_checked_native_record_fixtures_match_python_annotations() -> None:
    fixtures = runpy.run_path("scripts/generate-native-contracts.py")["record_fixtures"]()
    for path, expected in fixtures.items():
        assert path.read_text() == expected


def test_codegen_escapes_kotlin_literals_once(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    from pythonnative.sdk.codegen import generate
    from pythonnative.sdk.schema import COMPONENTS, ComponentSchema
    from pythonnative.sdk.types import type_schema

    monkeypatch.setitem(
        COMPONENTS, "LiteralFixture", ComponentSchema("LiteralFixture", {"choice": type_schema(Literal["$total"])})
    )
    generate(tmp_path)
    kotlin = (tmp_path / "NativeValues.kt").read_text()
    assert r'"\$total"' in kotlin
    assert r'"\\$total"' not in kotlin
    assert (
        (tmp_path / "modules.py")
        .read_text()
        .startswith('"""Generated Python interfaces. Regenerate from the declarations."""\n\n')
    )

Web Proxy Viewer  |  New URL  |  Original Page