"""
=============================================================================
CS525 · 第 9 讲模块：一个迷你"代码仓库"和它的测试（toy_repo.py）
=============================================================================

对应讲义：lectures/lecture09_posttraining_data_ii.md §5

-----------------------------------------------------------------------------
【这是什么】
-----------------------------------------------------------------------------
SWE-smith 的输入是一个真实的 GitHub 仓库和它的测试。这里用一个 8 个小函数的"仓库"代替：
    SOURCE         仓库的源代码（一个字符串——代码 03 会解析它、往里面注入 bug、再执行）
    STRONG_TESTS   一套比较完整的测试（每个函数 4–6 个用例，包括边界情况）
    WEAK_TESTS     一套很弱的测试（每个函数 1 个"典型"用例）——很多真实项目的测试就是这样
每个测试用例是 (函数名, 参数, 期望的结果)。

【运行自检】
    python3 toy_repo.py        # 在原始代码上跑两套测试，应该全部通过
"""

SOURCE = '''
def clamp(x, lo, hi):
    if x < lo:
        return lo
    if x > hi:
        return hi
    return x


def mean(xs):
    if len(xs) == 0:
        return 0.0
    return sum(xs) / len(xs)


def median(xs):
    s = sorted(xs)
    n = len(s)
    if n % 2 == 1:
        return s[n // 2]
    return (s[n // 2 - 1] + s[n // 2]) / 2


def is_palindrome(text):
    t = [c.lower() for c in text if c.isalnum()]
    return t == t[::-1]


def dedupe(xs):
    seen = set()
    out = []
    for x in xs:
        if x not in seen:
            seen.add(x)
            out.append(x)
    return out


def word_count(text):
    counts = {}
    for w in text.lower().split():
        counts[w] = counts.get(w, 0) + 1
    return counts


def fizzbuzz(n):
    if n % 15 == 0:
        return "FizzBuzz"
    if n % 3 == 0:
        return "Fizz"
    if n % 5 == 0:
        return "Buzz"
    return str(n)


def running_max(xs):
    out = []
    best = None
    for x in xs:
        if best is None or x > best:
            best = x
        out.append(best)
    return out
'''

STRONG_TESTS = [
    ("clamp", (5, 0, 10), 5), ("clamp", (-3, 0, 10), 0), ("clamp", (12, 0, 10), 10),
    ("clamp", (0, 0, 10), 0), ("clamp", (10, 0, 10), 10),
    ("mean", ([1, 2, 3, 4],), 2.5), ("mean", ([],), 0.0), ("mean", ([5],), 5.0), ("mean", ([-1, 1],), 0.0),
    ("median", ([3, 1, 2],), 2), ("median", ([4, 1, 3, 2],), 2.5), ("median", ([7],), 7), ("median", ([1, 1, 2, 9],), 1.5),
    ("is_palindrome", ("A man, a plan, a canal: Panama",), True), ("is_palindrome", ("hello",), False),
    ("is_palindrome", ("",), True), ("is_palindrome", ("ab",), False), ("is_palindrome", ("aa",), True),
    ("dedupe", ([3, 1, 3, 2, 1],), [3, 1, 2]), ("dedupe", ([],), []), ("dedupe", ([1, 1, 1],), [1]),
    ("dedupe", (["b", "a", "b"],), ["b", "a"]),
    ("word_count", ("the cat the dog",), {"the": 2, "cat": 1, "dog": 1}), ("word_count", ("",), {}),
    ("word_count", ("Hi hi HI",), {"hi": 3}), ("word_count", ("one",), {"one": 1}),
    ("fizzbuzz", (15,), "FizzBuzz"), ("fizzbuzz", (9,), "Fizz"), ("fizzbuzz", (10,), "Buzz"), ("fizzbuzz", (7,), "7"),
    ("fizzbuzz", (30,), "FizzBuzz"), ("fizzbuzz", (1,), "1"),
    ("running_max", ([1, 3, 2, 5, 4],), [1, 3, 3, 5, 5]), ("running_max", ([],), []),
    ("running_max", ([-2, -5, -1],), [-2, -2, -1]), ("running_max", ([2, 2],), [2, 2]),
]

WEAK_TESTS = [
    ("clamp", (5, 0, 10), 5), ("mean", ([1, 2, 3, 4],), 2.5), ("median", ([3, 1, 2],), 2),
    ("is_palindrome", ("A man, a plan, a canal: Panama",), True), ("dedupe", ([3, 1, 3, 2, 1],), [3, 1, 2]),
    ("word_count", ("the cat the dog",), {"the": 2, "cat": 1, "dog": 1}), ("fizzbuzz", (15,), "FizzBuzz"),
    ("running_max", ([1, 3, 2, 5, 4],), [1, 3, 3, 5, 5]),
]

FUNCTIONS = ["clamp", "mean", "median", "is_palindrome", "dedupe", "word_count", "fizzbuzz", "running_max"]


def run_tests(source, tests, only=None):
    """
    执行一份源代码，跑一套测试，返回没通过的测试的下标列表。

    【参数】source：源代码字符串；tests：测试用例列表；only：只跑这个函数的测试（None = 全部）
    【返回】失败的用例下标（空列表 = 全部通过）；源代码本身无法执行时，返回所有相关用例
    【思路】exec 把源代码执行在一个全新的字典里，相当于"导入"这个模块；每个用例调用对应的函数、比较结果。
            抛出任何异常都算失败。
    【边界】这里的代码没有 while 循环和递归，变异之后也不会死循环；真实的 SWE-smith 在 Docker 容器里跑测试、设超时。
    【Python 小知识】exec(code, namespace) 在给定的字典里执行代码；执行完，代码里定义的函数就是字典里的条目。
    """
    idx = [i for i, t in enumerate(tests) if only is None or t[0] == only]
    ns = {}
    try:
        exec(compile(source, "<toy_repo>", "exec"), ns)
    except Exception:                                   # noqa: BLE001 —— 变异可能让代码根本跑不起来
        return idx
    failed = []
    for i in idx:
        name, args, expected = tests[i]
        try:
            ok = ns[name](*[a.copy() if isinstance(a, list) else a for a in args]) == expected
        except Exception:                               # noqa: BLE001
            ok = False
        if not ok:
            failed.append(i)
    return failed


if __name__ == "__main__":
    print("强测试失败：", run_tests(SOURCE, STRONG_TESTS), "；弱测试失败：", run_tests(SOURCE, WEAK_TESTS))
    print("强测试", len(STRONG_TESTS), "个用例；弱测试", len(WEAK_TESTS), "个用例")
