"""Pruebas del laboratorio. LAB_DIR dice dónde están los deques: lab o solution.

Cada prueba corre en su propio proceso, con sanitizers: una fuga o un acceso inválido
la hace fallar aunque el resultado parezca correcto. Las dos representaciones pasan
por las mismas pruebas comunes, escritas como templates en lab/tests/deque_suite.h.
"""
import os
import pathlib
import re
import subprocess
import unittest

HERE = pathlib.Path(__file__).parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
BINARY = HERE / "build" / LAB_DIR / "deque_tests"
COMMON = [
    "new_is_empty", "push_back_keeps_order", "push_front_reverses", "as_stack", "as_queue",
    "growth_while_wrapped", "index_reads_and_writes", "range_for", "iterator_operators",
    "iterator_after_wrap", "copy_is_independent", "self_assignment",
    "move_leaves_empty_and_usable", "no_objects_left_behind", "rvalue_push_does_not_copy",
    "lvalue_push_copies_once", "push_own_element", "pop_releases_value", "random_ints",
    "random_strings",
]
ONLY_ARRAY = ["few_allocations", "capacity_doubles", "ring_reuses_space", "move_is_noexcept"]
ONLY_LINKED = ["one_allocation_per_push", "elements_stay_in_place", "move_assign_is_noexcept",
               "shuffle_nodes"]


def short_report(text):
    """Deja lo útil de un informe: el tipo de error y las líneas de sus archivos."""
    if "ERROR:" not in text and "runtime error" not in text:
        return text.strip()
    useful = [line.strip() for line in text.splitlines()
              if "ERROR:" in line or "SUMMARY:" in line or "runtime error" in line
              or re.search(r"(array_deque|linked_deque|deque_suite)\.h:\d+", line)]
    return "\n".join(useful[:12])


def build():
    done = subprocess.run(
        ["make", "--no-print-directory", f"LAB_DIR={LAB_DIR}", f"build/{LAB_DIR}/deque_tests"],
        cwd=HERE, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
    )
    return done.returncode, done.stdout


def run_case(testcase, kind, name):
    done = subprocess.run(
        [str(BINARY), kind, name], cwd=HERE, text=True,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120,
    )
    testcase.assertEqual(done.returncode, 0, short_report(done.stdout))


def add_cases(cls, kind, names):
    for name in names:
        def case(self, name=name):
            run_case(self, kind, name)
        setattr(cls, f"test_{name}", case)
    return cls


@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe la carpeta {LAB_DIR}")
class Builds(unittest.TestCase):
    def test_builds_without_warnings(self):
        BINARY.unlink(missing_ok=True)
        code, output = build()
        self.assertEqual(code, 0, output)
        self.assertNotIn("warning", output)


@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe la carpeta {LAB_DIR}")
class TestArrayDeque(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        code, output = build()
        if code != 0:
            raise AssertionError(output)


@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe la carpeta {LAB_DIR}")
class TestLinkedDeque(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        code, output = build()
        if code != 0:
            raise AssertionError(output)


add_cases(TestArrayDeque, "array", COMMON + ONLY_ARRAY)
add_cases(TestLinkedDeque, "linked", COMMON + ONLY_LINKED)


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