mirror of
https://github.com/unclebob/mutator.git
synced 2026-11-06 18:23:50 +00:00
Add a multi-language mutation tester that writes .metrics/mutate for uml-viewer.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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) == []
|
||||
Reference in New Issue
Block a user