import ast

import pytest

from trio._tools.gen_exports import create_passthrough_args, get_public_methods, process

SOURCE = '''from _run import _public

class Test:
    @_public
    def public_func(self):
        """With doc string"""

    @ignore_this
    @_public
    @another_decorator
    async def public_async_func(self):
        pass  # no doc string

    def not_public(self):
        pass

    async def not_public_async(self):
        pass
'''


def test_get_public_methods():
    methods = list(get_public_methods(ast.parse(SOURCE)))
    assert {m.name for m in methods} == {"public_func", "public_async_func"}


def test_create_pass_through_args():
    testcases = [
        ("def f()", "()"),
        ("def f(one)", "(one)"),
        ("def f(one, two)", "(one, two)"),
        ("def f(one, *args)", "(one, *args)"),
        (
            "def f(one, *args, kw1, kw2=None, **kwargs)",
            "(one, *args, kw1=kw1, kw2=kw2, **kwargs)",
        ),
    ]

    for funcdef, expected in testcases:
        func_node = ast.parse(funcdef + ":\n  pass").body[0]
        assert isinstance(func_node, ast.FunctionDef)
        assert create_passthrough_args(func_node) == expected


def test_process(tmp_path):
    modpath = tmp_path / "_module.py"
    genpath = tmp_path / "_generated_module.py"
    modpath.write_text(SOURCE, encoding="utf-8")
    assert not genpath.exists()
    with pytest.raises(SystemExit) as excinfo:
        process([(str(modpath), "runner")], do_test=True)
    assert excinfo.value.code == 1
    process([(str(modpath), "runner")], do_test=False)
    assert genpath.exists()
    process([(str(modpath), "runner")], do_test=True)
    # But if we change the lookup path it notices
    with pytest.raises(SystemExit) as excinfo:
        process([(str(modpath), "runner.io_manager")], do_test=True)
    assert excinfo.value.code == 1
