from pathlib import Path from mutator.functions import form_id, is_private, project_functions, sites_in_file def _sites(tmp_path: Path, relative: str, source: str): path = tmp_path / relative path.parent.mkdir(parents=True, exist_ok=True) path.write_text(source, encoding="utf-8") return sites_in_file(source, path, tmp_path, relative) def test_clojure_rules_follow_clj_mutate_and_mark_private_defns(tmp_path): source = """(ns demo.core) (defn place [x] (if (> x 0) (+ x 1) 0)) (defn- hide [] false) """ sites = _sites(tmp_path, "src/demo/core.clj", source) described = {(site.form_id, site.original, site.mutant) for site in sites} assert ("defn/place", "if", "if-not") in described assert ("defn/place", ">", ">=") in described assert ("defn/place", "+", "-") in described assert ("defn/place", "0", "1") in described assert ("defn/place", "1", "0") in described assert ("defn-/hide", "false", "true") in described assert {site.namespace for site in sites} == {"demo.core"} functions = project_functions(source, tmp_path / "src/demo/core.clj", tmp_path) assert form_id("hide", True) == "defn-/hide" assert is_private("clojure", "hide", source, 6, 7) assert {fn.namespace for fn in functions} == {"demo.core"} def test_clojure_ignores_strings_comments_and_quotes(tmp_path): source = """(ns demo.core) (defn place [] (comment (+ 1 0)) (quote (+ 1 0)) (str "true + 1")) """ sites = _sites(tmp_path, "src/demo/core.clj", source) assert sites == [] def test_java_rules_follow_mutate4java(tmp_path): source = """package demo; class Board { int place(int x) { if (x > 0 && ready) return x + 1; return !ok ? -x : 0; } private int hide() { return 1; } } """ sites = _sites(tmp_path, "src/demo/Board.java", source) public = [site for site in sites if site.form_id == "defn/place"] private = [site for site in sites if site.form_id == "defn-/hide"] assert ("+", "-") in {(site.original, site.mutant) for site in public} assert ("/", "*") not in {(site.original, site.mutant) for site in public} assert ("*", "/") not in {(site.original, site.mutant) for site in public} assert (">", ">=") in {(site.original, site.mutant) for site in public} assert ("&&", "||") in {(site.original, site.mutant) for site in public} assert ("!", "") in {(site.original, site.mutant) for site in public} assert ("-", "") in {(site.original, site.mutant) for site in public} assert ("0", "1") in {(site.original, site.mutant) for site in public} assert private == [] or {site.original for site in private} == {"1"} assert {site.namespace for site in sites} == {"demo.Board"} def test_java_swaps_division_both_ways(tmp_path): source = """package demo; class Math { int place(int a, int b) { return a * b + a / b; } } """ sites = _sites(tmp_path, "src/demo/Math.java", source) pairs = {(site.original, site.mutant) for site in sites} assert ("*", "/") in pairs assert ("/", "*") in pairs assert ("+", "-") in pairs def test_go_matches_mutate4go_and_splits_receivers(tmp_path): source = """package demo func Run(a int, b int) int { if a > 0 && b == 1 { return a * b } return a / b } type Widget struct{} func (w Widget) Run() bool { return true } """ sites = _sites(tmp_path, "demo.go", source) free = [site for site in sites if site.namespace == "demo"] method = [site for site in sites if site.namespace == "demo.Widget"] pairs = {(site.original, site.mutant) for site in free} assert ("*", "/") in pairs assert ("/", "*") not in pairs assert ("&&", "||") in pairs assert ("==", "!=") in pairs assert (">", ">=") in pairs assert ("0", "1") in pairs assert ("1", "0") in pairs assert {site.form_id for site in method} == {"defn/Run"} assert ("true", "false") in {(site.original, site.mutant) for site in method} assert {site.form_id for site in free} == {"defn/Run"} def test_go_unexported_function_is_private(tmp_path): source = """package demo func hide() bool { return false } """ sites = _sites(tmp_path, "demo.go", source) assert sites[0].form_id == "defn-/hide" def test_python_typescript_and_rust_use_the_java_decisions(tmp_path): python = _sites( tmp_path, "src/demo/app.py", "def place(x):\n if x > 0 and ready:\n return x + 1\n if not ok:\n return 0\n", ) typescript = _sites( tmp_path, "src/demo/app.ts", "export function place(x: number) {\n if (x > 0 && ready) return x + 1;\n return true === false;\n}\n", ) rust = _sites( tmp_path, "src/lib.rs", "fn place(x: i32) -> i32 {\n if x > 0 && ready { return x + 1; }\n if !ok { return -x; }\n 0\n}\n", ) assert ("and", "or") in {(site.original, site.mutant) for site in python} assert ("not", "") in {(site.original, site.mutant) for site in python} assert ("True", "False") not in {(site.original, site.mutant) for site in python} assert {site.namespace for site in python} == {"demo.app"} assert ("===", "!==") in {(site.original, site.mutant) for site in typescript} assert ("&&", "||") in {(site.original, site.mutant) for site in typescript} assert {site.namespace for site in typescript} == {"demo.app"} assert ("&&", "||") in {(site.original, site.mutant) for site in rust} assert ("!", "") in {(site.original, site.mutant) for site in rust} assert ("-", "") in {(site.original, site.mutant) for site in rust} def test_python_private_and_class_namespace(tmp_path): source = """class Board: def place(self, x): return x > 0 def _hide(self): return False """ sites = _sites(tmp_path, "src/demo/board.py", source) assert {site.form_id for site in sites if site.name == "place"} == {"defn/place"} assert {site.form_id for site in sites if site.name == "_hide"} == {"defn-/_hide"} assert {site.namespace for site in sites} == {"demo.board.Board"} def test_string_operators_are_not_sites(tmp_path): source = 'def place():\n return "x > 0 and true"\n' assert _sites(tmp_path, "src/demo/app.py", source) == []