<gh_stars>10-100
import os
from pathlib import Path
import re
import shutil
import tempfile
from typing import Any, Optional
import unittest

from src.compiler import Compiler
from src.preprocess import preprocess
from src import main


class TestPermMacros(unittest.TestCase):
    def go(
        self,
        intro: str,
        outro: str,
        base: str,
        target: str,
        *,
        fn_name: Optional[str] = None,
        **kwargs: Any
    ) -> int:
        base = intro + "\n" + base + "\n" + outro
        target = intro + "\n" + target + "\n" + outro
        compiler = Compiler("test/compile.sh", show_errors=True)

        # For debugging, to avoid the auto-deleted directory:
        # target_dir = tempfile.mkdtemp()
        with tempfile.TemporaryDirectory() as target_dir:
            with open(os.path.join(target_dir, "base.c"), "w") as f:
                f.write(base)

            target_o = compiler.compile(target, show_errors=True)
            assert target_o is not None
            shutil.move(target_o, os.path.join(target_dir, "target.o"))

            shutil.copy2("test/compile.sh", os.path.join(target_dir, "compile.sh"))

            if fn_name:
                with open(os.path.join(target_dir, "function.txt"), "w") as f:
                    f.write(fn_name)

            opts = main.Options(directories=[target_dir], stop_on_zero=True, **kwargs)
            return main.run(opts)[0]

    def test_general(self) -> None:
        score = self.go(
            "int test() {",
            "}",
            "return PERM_GENERAL(32,64);",
            "return 64;",
        )
        self.assertEqual(score, 0)

    def test_not_found(self) -> None:
        score = self.go(
            "int test() {",
            "}",
            "return PERM_GENERAL(32,64);",
            "return 92;",
        )
        self.assertNotEqual(score, 0)

    def test_multiple_functions(self) -> None:
        score = self.go(
            "",
            "",
            """
            int ignoreme() {}
            int foo() { return PERM_GENERAL(32,64); }
            int ignoreme2() {}
            """,
            "int foo() { return 64; }",
            fn_name="foo",
        )
        self.assertEqual(score, 0)

    def test_general_multiple(self) -> None:
        score = self.go(
            "int test() {",
            "}",
            "return PERM_GENERAL(1,2,3) + PERM_GENERAL(3,6,9);",
            "return 9;",
        )
        self.assertEqual(score, 0)

    def test_general_nested(self) -> None:
        score = self.go(
            "int test() {",
            "}",
            "return PERM_GENERAL(1,PERM_GENERAL(100,101),3) + PERM_GENERAL(3,6,9);",
            "return 110;",
        )
        self.assertEqual(score, 0)

    def test_cast(self) -> None:
        score = self.go(
            "int test(int a, int b) {",
            "}",
            "return a / PERM_GENERAL(,(unsigned int),(float)) b;",
            "return a / (float) b;",
        )
        self.assertEqual(score, 0)

    def test_cast_threaded(self) -> None:
        score = self.go(
            "int test(int a, int b) {",
            "}",
            "return a / PERM_GENERAL(,(unsigned int),(float)) b;",
            "return a / (float) b;",
            threads=2,
        )
        self.assertEqual(score, 0)

    def test_ignore(self) -> None:
        score = self.go(
            "int test(int a, int b) {",
            "}",
            "PERM_IGNORE( return a / PERM_GENERAL(a, b); )",
            "return a / b;",
        )
        self.assertEqual(score, 0)

    def test_pretend(self) -> None:
        score = self.go(
            "int global;",
            "",
            """
            PERM_IGNORE( inline void foo() { )
            PERM_PRETEND( void foo(); void bar() { )
                PERM_RANDOMIZE(
                    global = 1;
                )
            PERM_IGNORE( } void bar() { )
                PERM_RANDOMIZE(
                    global = 2; foo();
                )
            }
            """,
            """
            inline void foo() { global = 1; }
            void bar() { foo(); global = 2; }
            """,
            fn_name="bar",
        )
        self.assertEqual(score, 0)

    def test_once1(self) -> None:
        score = self.go(
            "volatile int A, B, C; void test() {",
            "}",
            """
                PERM_ONCE(B = 2;)
                A = 1;
                PERM_ONCE(B = 2;)
                C = 3;
                PERM_ONCE(B = 2;)
            """,
            "A = 1; B = 2; C = 3;",
        )
        self.assertEqual(score, 0)

    def test_once2(self) -> None:
        score = self.go(
            "volatile int A, B, C; void test() {",
            "}",
            """
                PERM_VAR(emit,)
                PERM_VAR(bademit,)
                PERM_ONCE(1, PERM_VAR(bademit, A = 7;) A = 2;)
                PERM_ONCE(1, PERM_VAR(emit, A = 1;))
                PERM_VAR(emit)
                PERM_VAR(bademit)
                PERM_ONCE(2, B = 2;)
                PERM_ONCE(2, B = 1;)
                PERM_ONCE(2,)
                PERM_ONCE(3, PERM_VAR(bademit, A = 9))
                PERM_ONCE(3, PERM_VAR(bademit, A = 9))
                C = 3;
            """,
            "A = 1; B = 2; C = 3;",
        )
        self.assertEqual(score, 0)

    def test_lineswap(self) -> None:
        score = self.go(
            "void a(); void b(); void c(); void test(void) {",
            "}",
            """
            PERM_LINESWAP(
                a();
                b();
                c();
            )
            """,
            "b(); a(); c();",
        )
        self.assertEqual(score, 0)

    def test_lineswap_text(self) -> None:
        score = self.go(
            "void a(); void b(); void c(); void test(void) {",
            "}",
            """
            PERM_LINESWAP_TEXT(
                a();
                b();
                c();
            )
            """,
            "b(); a(); c();",
        )
        self.assertEqual(score, 0)

    def test_randomizer(self) -> None:
        score = self.go(
            "void foo(); void bar(); void test(void) {",
            "}",
            "PERM_RANDOMIZE(bar(); foo();)",
            "foo(); bar();",
        )
        self.assertEqual(score, 0)

    def test_auto_randomizer(self) -> None:
        score = self.go(
            "void foo(); void bar(); void test(void) {",
            "}",
            "bar(); foo();",
            "foo(); bar();",
        )
        self.assertEqual(score, 0)

    def test_randomizer_threaded(self) -> None:
        score = self.go(
            "void foo(); void bar(); void test(void) {",
            "}",
            "PERM_RANDOMIZE(bar(); foo();)",
            "foo(); bar();",
            threads=2,
        )
        self.assertEqual(score, 0)


if __name__ == "__main__":
    unittest.main()
