mirror of
https://github.com/unclebob/mutator.git
synced 2026-11-06 18:23:50 +00:00
237 lines
8.5 KiB
Python
237 lines
8.5 KiB
Python
from pathlib import Path
|
|
|
|
from mutator.functions import form_id, is_private, project_functions, sites_in_file
|
|
from mutator.sites import discover_raw
|
|
|
|
|
|
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) == []
|
|
|
|
|
|
def test_typescript_mutates_nullish_and_optional_chaining(tmp_path):
|
|
source = """
|
|
export function place(a, b) {
|
|
return a ?? b?.c ?? b?.[0] ?? b?.();
|
|
}
|
|
"""
|
|
sites = _sites(tmp_path, "src/demo/app.ts", source)
|
|
assert sum(site.original == "??" and site.mutant == "||" for site in sites) == 3
|
|
assert {site.mutant for site in sites if site.original == "?."} == {".", ""}
|
|
assert {site.namespace for site in sites} == {"demo.app"}
|
|
|
|
|
|
def test_javascript_express_handler_owns_its_nullish_site(tmp_path):
|
|
source = """
|
|
export function mount(app) {
|
|
app.get("/users", (req, res) => {
|
|
return req.body ?? false;
|
|
});
|
|
}
|
|
"""
|
|
sites = _sites(tmp_path, "src/demo/routes.js", source)
|
|
assert {site.namespace for site in sites} == {"demo.routes"}
|
|
assert ("GET /users", "??", "||") in {(site.name, site.original, site.mutant) for site in sites}
|
|
assert all(site.name != "mount" for site in sites)
|
|
|
|
|
|
def test_a_quoted_nullish_operator_is_not_a_site(tmp_path):
|
|
source = 'export function place() {\n return "a ?? b?.c";\n}\n'
|
|
assert _sites(tmp_path, "src/demo/app.ts", source) == []
|
|
|
|
|
|
def test_a_deep_expression_is_walked(tmp_path):
|
|
expr = " + ".join(["1"] * 1200)
|
|
python = f"def place():\n return {expr}\n"
|
|
assert discover_raw(python, "python", "app.py")
|
|
go = f"package p\nfunc place() int {{ return {expr} }}\n"
|
|
assert discover_raw(go, "go", "app.go")
|
|
|
|
|
|
def test_typescript_types_are_not_mutation_sites(tmp_path):
|
|
source = """type Flag = { on: true };
|
|
interface Box { open: false }
|
|
export function check(n: number): { ok: true } | { ok: false; used?: true } {
|
|
const seen: Promise<{ done: false }> | null = null;
|
|
return { ok: n > 0 };
|
|
}
|
|
"""
|
|
sites = _sites(tmp_path, "src/demo/check.ts", source)
|
|
described = {(site.line, site.original, site.mutant) for site in sites}
|
|
assert described == {(5, ">", ">="), (5, "0", "1")}
|
|
|
|
|
|
def test_a_site_remembers_where_its_statement_starts(tmp_path):
|
|
source = """export function save(db: Db, now: number) {
|
|
db
|
|
.prepare("x")
|
|
.bind(now + 1)
|
|
.run();
|
|
return {
|
|
ok: true,
|
|
};
|
|
}
|
|
"""
|
|
sites = _sites(tmp_path, "src/demo/save.ts", source)
|
|
anchors = {(site.line, site.original, site.anchor_line) for site in sites}
|
|
assert (4, "+", 2) in anchors
|
|
assert (4, "1", 2) in anchors
|
|
assert (7, "true", 6) in anchors
|