pyproteum 0.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyproteum-0.2/pyproteum.egg-info → pyproteum-0.2.4}/PKG-INFO +14 -1
- {pyproteum-0.2 → pyproteum-0.2.4}/README.md +13 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproject.toml +1 -1
- pyproteum-0.2.4/pyproteum/exemuta.py +598 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/cccr.py +2 -2
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/ccsr.py +6 -4
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/crcr.py +2 -2
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/myoperator.py +10 -12
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/oaaa.py +2 -2
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/oaan.py +2 -2
- pyproteum-0.2.4/pyproteum/moperators/oeap.py +107 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/oodl.py +2 -3
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/oplist.py +0 -1
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/orrn.py +2 -3
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/ssdl.py +10 -9
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/mutagen.py +15 -16
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/mutaview.py +18 -16
- pyproteum-0.2.4/pyproteum/runner_tests.py +106 -0
- pyproteum-0.2.4/pyproteum/runner_tests_myself.py +196 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/tcase.py +2 -5
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/testnew.py +6 -5
- {pyproteum-0.2 → pyproteum-0.2.4/pyproteum.egg-info}/PKG-INFO +14 -1
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum.egg-info/SOURCES.txt +2 -0
- pyproteum-0.2/pyproteum/exemuta.py +0 -406
- pyproteum-0.2/pyproteum/moperators/oeap.py +0 -107
- {pyproteum-0.2 → pyproteum-0.2.4}/LICENSE +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/__init__.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/__main__.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/execution_reg.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/models/models.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/sbrc.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/moperators/scrb.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/mutaviewgui.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/print_visit.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum/utiles.py +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum.egg-info/dependency_links.txt +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum.egg-info/entry_points.txt +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum.egg-info/requires.txt +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/pyproteum.egg-info/top_level.txt +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/setup.cfg +0 -0
- {pyproteum-0.2 → pyproteum-0.2.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyproteum
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A tool for mutation testing in Python. Implemented as a Python module.
|
|
5
5
|
Author: Delamaro
|
|
6
6
|
Author-email: Delamaro <delamaro@icmc.usp.br>
|
|
@@ -49,6 +49,14 @@ Dynamic: requires-python
|
|
|
49
49
|
|
|
50
50
|
> - Introduces the second argument for `mutagen` selection of mutant operatos.
|
|
51
51
|
|
|
52
|
+
> **V0.2.2**
|
|
53
|
+
|
|
54
|
+
> - Introduces argument `--assert` to enable mutation on assert statements.
|
|
55
|
+
|
|
56
|
+
> **V0.2.4**
|
|
57
|
+
|
|
58
|
+
> - Bug fixes.
|
|
59
|
+
|
|
52
60
|
---
|
|
53
61
|
|
|
54
62
|
## Features
|
|
@@ -170,6 +178,11 @@ Generate mutants for all operators:
|
|
|
170
178
|
python -m pyproteum mutagen --create --D /home/user/proteum foo_session
|
|
171
179
|
```
|
|
172
180
|
|
|
181
|
+
By default `assert` statements are not considered to generate mutants. This can be changed with `--assert` argument.
|
|
182
|
+
```bash
|
|
183
|
+
python -m pyproteum mutagen --create --D /home/user/proteum --assert foo_session
|
|
184
|
+
```
|
|
185
|
+
|
|
173
186
|
Generate only `orrn` mutants, with 50% random sampling:
|
|
174
187
|
```bash
|
|
175
188
|
python -m pyproteum mutagen --create --D /home/user/proteum --orrn 50 0 foo_session
|
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
|
|
35
35
|
> - Introduces the second argument for `mutagen` selection of mutant operatos.
|
|
36
36
|
|
|
37
|
+
> **V0.2.2**
|
|
38
|
+
|
|
39
|
+
> - Introduces argument `--assert` to enable mutation on assert statements.
|
|
40
|
+
|
|
41
|
+
> **V0.2.4**
|
|
42
|
+
|
|
43
|
+
> - Bug fixes.
|
|
44
|
+
|
|
37
45
|
---
|
|
38
46
|
|
|
39
47
|
## Features
|
|
@@ -155,6 +163,11 @@ Generate mutants for all operators:
|
|
|
155
163
|
python -m pyproteum mutagen --create --D /home/user/proteum foo_session
|
|
156
164
|
```
|
|
157
165
|
|
|
166
|
+
By default `assert` statements are not considered to generate mutants. This can be changed with `--assert` argument.
|
|
167
|
+
```bash
|
|
168
|
+
python -m pyproteum mutagen --create --D /home/user/proteum --assert foo_session
|
|
169
|
+
```
|
|
170
|
+
|
|
158
171
|
Generate only `orrn` mutants, with 50% random sampling:
|
|
159
172
|
```bash
|
|
160
173
|
python -m pyproteum mutagen --create --D /home/user/proteum --orrn 50 0 foo_session
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyproteum"
|
|
7
|
-
version = "0.2"
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "A tool for mutation testing in Python. Implemented as a Python module."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Delamaro", email = "delamaro@icmc.usp.br"}
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import types, sys, ast, multiprocessing
|
|
2
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
3
|
+
import sys, csv
|
|
4
|
+
import os,io,json
|
|
5
|
+
from pyproteum.models.models import *
|
|
6
|
+
import unittest
|
|
7
|
+
import pickle
|
|
8
|
+
import types
|
|
9
|
+
import platform
|
|
10
|
+
from pyproteum.moperators.myoperator import *
|
|
11
|
+
from pyproteum.tcase import change_dir_connect
|
|
12
|
+
from pyproteum.tcase import load_module, get_all_test_names, _ensure_module_hierarchy
|
|
13
|
+
from pyproteum.utiles import red,green
|
|
14
|
+
import timeout_decorator as tmtdecor
|
|
15
|
+
import subprocess as sb
|
|
16
|
+
from pyproteum.execution_reg import ExecutionReg
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MutantTimeout(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
class RegistrandoResultado(unittest.TextTestResult):
|
|
23
|
+
def __init__(self, stream=None, descriptions=None, verbosity=0, number=0, research=False):
|
|
24
|
+
super().__init__(stream, descriptions, verbosity)
|
|
25
|
+
self.my_successes = [] # armazena os testes que passaram
|
|
26
|
+
self.timeouts = []
|
|
27
|
+
self.all = [] # todos os testes executados
|
|
28
|
+
self.all_order = {}
|
|
29
|
+
self.order = number
|
|
30
|
+
self.count = 0
|
|
31
|
+
self.research = research
|
|
32
|
+
|
|
33
|
+
def startTest(self, test):
|
|
34
|
+
self.all.append(test)
|
|
35
|
+
self.count += 1
|
|
36
|
+
self.all_order[test.id()] = '{}:{}'.format(self.order, self.count)
|
|
37
|
+
super().startTest(test)
|
|
38
|
+
|
|
39
|
+
def addSuccess(self, test):
|
|
40
|
+
self.my_successes.append(test)
|
|
41
|
+
super().addSuccess(test)
|
|
42
|
+
|
|
43
|
+
def stopTest(self, test):
|
|
44
|
+
super().stopTest(test)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def addFailure(self, test, err):
|
|
48
|
+
etype, evalue, tb = err
|
|
49
|
+
# sys.__stderr__.write(
|
|
50
|
+
# f"[addFailure] test={test.id()}\n"
|
|
51
|
+
# f"[addFailure] etype={etype} (module={getattr(etype, '__module__', '?')}, name={getattr(etype, '__name__', '?')})\n"
|
|
52
|
+
# f"[addFailure] evalue={repr(evalue)}\n"
|
|
53
|
+
# )
|
|
54
|
+
# sys.__stderr__.write(f"addFailure etype id={id(etype)}\n")
|
|
55
|
+
# sys.__stderr__.write(f"addFailure MutantTimeout(ref) id={id(MutantTimeout)}\n")
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
if issubclass(etype, MutantTimeout):
|
|
59
|
+
self.timeouts.append(test)
|
|
60
|
+
except TypeError:
|
|
61
|
+
if isinstance(evalue, MutantTimeout):
|
|
62
|
+
self.timeouts.append(test)
|
|
63
|
+
super().addFailure(test, err)
|
|
64
|
+
if not self.research:
|
|
65
|
+
self.stop()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def addError(self, test, err):
|
|
69
|
+
etype, evalue, tb = err
|
|
70
|
+
# sys.__stderr__.write(
|
|
71
|
+
# f"[addError] test={test.id()}\n"
|
|
72
|
+
# f"[addError] etype={etype} (module={getattr(etype, '__module__', '?')}, name={getattr(etype, '__name__', '?')})\n"
|
|
73
|
+
# f"[addError] len(timeouts) antes={len(self.timeouts)} id(list)={id(self.timeouts)}\n"
|
|
74
|
+
# f"[addError] evalue={repr(evalue)}\n"
|
|
75
|
+
# )
|
|
76
|
+
# sys.__stderr__.write(f"etype id={id(etype)}\n")
|
|
77
|
+
# sys.__stderr__.write(f"MutantTimeout(ref) id={id(MutantTimeout)}\n")
|
|
78
|
+
# sys.__stderr__.write(f"[addError] self id={id(self)}\n")
|
|
79
|
+
# sys.__stderr__.flush()
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
if issubclass(etype, MutantTimeout):
|
|
83
|
+
self.timeouts.append(test)
|
|
84
|
+
# sys.__stderr__.write(f"[addError] APPEND OK len(timeouts) depois={len(self.timeouts)}\n")
|
|
85
|
+
# sys.__stderr__.flush()
|
|
86
|
+
except TypeError:
|
|
87
|
+
if isinstance(evalue, MutantTimeout):
|
|
88
|
+
self.timeouts.append(test)
|
|
89
|
+
super().addError(test, err)
|
|
90
|
+
# sys.__stderr__.write(f"[addError] len(timeouts) após super={len(self.timeouts)}\n")
|
|
91
|
+
# sys.__stderr__.flush()
|
|
92
|
+
|
|
93
|
+
if not self.research:
|
|
94
|
+
self.stop()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RegistrandoRunner(unittest.TextTestRunner):
|
|
98
|
+
|
|
99
|
+
def __init__(self, *args, number=0, research=False, **kwargs):
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
self.number = number
|
|
102
|
+
self.research = research
|
|
103
|
+
|
|
104
|
+
def _makeResult(self):
|
|
105
|
+
return RegistrandoResultado(self.stream, self.descriptions, self.verbosity, self.number,self.research)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _check_ast_executes(code_obj, path_filename, suppress_output):
|
|
110
|
+
module_name, parent, parts = _ensure_module_hierarchy(path_filename)
|
|
111
|
+
|
|
112
|
+
modulo = types.ModuleType(module_name)
|
|
113
|
+
if suppress_output:
|
|
114
|
+
with open(os.devnull, 'w') as fnull, redirect_stdout(fnull), redirect_stderr(fnull):
|
|
115
|
+
exec(code_obj, modulo.__dict__)
|
|
116
|
+
else:
|
|
117
|
+
exec(code_obj, modulo.__dict__)
|
|
118
|
+
|
|
119
|
+
sys.modules[module_name] = modulo
|
|
120
|
+
if parent is not None:
|
|
121
|
+
setattr(parent, parts[-1], modulo)
|
|
122
|
+
|
|
123
|
+
def _check_ast_executes_source(code_str, path_filename, suppress_output):
|
|
124
|
+
code = compile(code_str, filename="<ast>", mode="exec")
|
|
125
|
+
_check_ast_executes(code, path_filename, suppress_output)
|
|
126
|
+
|
|
127
|
+
def create_module_from_ast(path_filename, arvore_ast, timeout=3, suppress_output=True):
|
|
128
|
+
|
|
129
|
+
code = compile(arvore_ast, filename="<ast>", mode="exec")
|
|
130
|
+
code_src = ast.unparse(arvore_ast)
|
|
131
|
+
p = multiprocessing.Process(target=_check_ast_executes_source, args=(code_src, path_filename, suppress_output))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Testa em subprocesso com timeout
|
|
135
|
+
p.start()
|
|
136
|
+
p.join(timeout)
|
|
137
|
+
if p.is_alive():
|
|
138
|
+
p.terminate()
|
|
139
|
+
p.join()
|
|
140
|
+
raise TimeoutError(f"O módulo '{path_filename}' não respondeu em {timeout} segundos.")
|
|
141
|
+
p.close()
|
|
142
|
+
|
|
143
|
+
# Executa de verdade no processo principal
|
|
144
|
+
module_name, parent, parts = _ensure_module_hierarchy(path_filename)
|
|
145
|
+
|
|
146
|
+
modulo = types.ModuleType(module_name)
|
|
147
|
+
if suppress_output:
|
|
148
|
+
with open(os.devnull, 'w') as fnull, redirect_stdout(fnull), redirect_stderr(fnull):
|
|
149
|
+
exec(code, modulo.__dict__)
|
|
150
|
+
else:
|
|
151
|
+
exec(code, modulo.__dict__)
|
|
152
|
+
|
|
153
|
+
sys.modules[module_name] = modulo
|
|
154
|
+
if parent is not None:
|
|
155
|
+
setattr(parent, parts[-1], modulo)
|
|
156
|
+
|
|
157
|
+
return modulo
|
|
158
|
+
|
|
159
|
+
def simple_create_module_from_ast(path_filename, arvore_ast, suppress_output=True):
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
code = compile(arvore_ast, filename="<ast>", mode="exec")
|
|
163
|
+
|
|
164
|
+
# Executa de verdade no processo principal
|
|
165
|
+
module_name, parent, parts = _ensure_module_hierarchy(path_filename)
|
|
166
|
+
|
|
167
|
+
modulo = types.ModuleType(module_name)
|
|
168
|
+
sys.modules[module_name] = modulo
|
|
169
|
+
if suppress_output:
|
|
170
|
+
with open(os.devnull, 'w') as fnull, redirect_stdout(fnull), redirect_stderr(fnull):
|
|
171
|
+
exec(code, modulo.__dict__)
|
|
172
|
+
else:
|
|
173
|
+
exec(code, modulo.__dict__)
|
|
174
|
+
|
|
175
|
+
if parent is not None:
|
|
176
|
+
setattr(parent, parts[-1], modulo)
|
|
177
|
+
|
|
178
|
+
return modulo
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def parse_runner_json(s):
|
|
182
|
+
a = s.find("<<<PYPROTEUM_JSON>>>")
|
|
183
|
+
b = s.find("<<<END_PYPROTEUM_JSON>>>")
|
|
184
|
+
if a < 0 or b < 0 or b <= a:
|
|
185
|
+
raise ValueError("Runner did not output delimited JSON")
|
|
186
|
+
body = s[a + len("<<<PYPROTEUM_JSON>>>"):b].strip()
|
|
187
|
+
|
|
188
|
+
return json.loads(body)
|
|
189
|
+
|
|
190
|
+
def call_runner_windows(muta_name, muta_source, filename, research, number):
|
|
191
|
+
env = os.environ.copy()
|
|
192
|
+
|
|
193
|
+
cp = sb.run(
|
|
194
|
+
[sys.executable, "-m", "pyproteum.runner_tests_myself", filename, str(TIMEOUT_SEGUNDOS), str(research), str(number), muta_name],
|
|
195
|
+
env=env,
|
|
196
|
+
cwd=os.getcwd(),
|
|
197
|
+
input=muta_source,
|
|
198
|
+
check=False,
|
|
199
|
+
capture_output=True,
|
|
200
|
+
text=True
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
stdout = (cp.stdout or "").strip()
|
|
204
|
+
stderr = (cp.stderr or "").strip()
|
|
205
|
+
|
|
206
|
+
#print(stdout)
|
|
207
|
+
data = None
|
|
208
|
+
if stdout:
|
|
209
|
+
try:
|
|
210
|
+
data = parse_runner_json(stdout)
|
|
211
|
+
except json.JSONDecodeError as ex:
|
|
212
|
+
print(ex)
|
|
213
|
+
data = None
|
|
214
|
+
|
|
215
|
+
# runner quebrou ou não gerou JSON
|
|
216
|
+
if data is None:
|
|
217
|
+
fallback = {
|
|
218
|
+
"ok": False,
|
|
219
|
+
"testsRun": 0,
|
|
220
|
+
"my_successes": [],
|
|
221
|
+
"timeouts": [],
|
|
222
|
+
"all": [],
|
|
223
|
+
"all_order": {},
|
|
224
|
+
"failures": [],
|
|
225
|
+
"errors": [("runner", f"Runner failed. returncode={cp.returncode}. stderr={stderr}")],
|
|
226
|
+
"skipped": [],
|
|
227
|
+
"expectedFailures": [],
|
|
228
|
+
"unexpectedSuccesses": [],
|
|
229
|
+
"output": stdout,
|
|
230
|
+
"order": int(number),
|
|
231
|
+
"research": bool(research),
|
|
232
|
+
"runner_returncode": cp.returncode,
|
|
233
|
+
"runner_stderr": cp.stderr,
|
|
234
|
+
}
|
|
235
|
+
return resultado_from_runner_json(fallback)
|
|
236
|
+
|
|
237
|
+
# anexa metadados sempre
|
|
238
|
+
data["runner_returncode"] = cp.returncode
|
|
239
|
+
data["runner_stderr"] = cp.stderr
|
|
240
|
+
if "order" not in data:
|
|
241
|
+
data["order"] = int(number)
|
|
242
|
+
if "research" not in data:
|
|
243
|
+
data["research"] = bool(research)
|
|
244
|
+
|
|
245
|
+
return resultado_from_runner_json(data)
|
|
246
|
+
|
|
247
|
+
def resultado_from_runner_json(data):
|
|
248
|
+
|
|
249
|
+
class Fake_test:
|
|
250
|
+
def __init__(self, tid):
|
|
251
|
+
self._id = tid
|
|
252
|
+
|
|
253
|
+
def id(self):
|
|
254
|
+
return self._id
|
|
255
|
+
|
|
256
|
+
def __hash__(self):
|
|
257
|
+
return hash(self._id)
|
|
258
|
+
|
|
259
|
+
def __eq__(self, other):
|
|
260
|
+
return isinstance(other, Fake_test) and self._id == other._id
|
|
261
|
+
|
|
262
|
+
def _parse_bool(v):
|
|
263
|
+
if isinstance(v, bool):
|
|
264
|
+
return v
|
|
265
|
+
return str(v).strip().lower() in ("1", "true", "yes")
|
|
266
|
+
|
|
267
|
+
# cache pra garantir identidade consistente por id
|
|
268
|
+
cache = {}
|
|
269
|
+
def T(tid):
|
|
270
|
+
if tid not in cache:
|
|
271
|
+
cache[tid] = Fake_test(tid)
|
|
272
|
+
return cache[tid]
|
|
273
|
+
|
|
274
|
+
res = RegistrandoResultado(
|
|
275
|
+
number=int(data.get("order", 0)),
|
|
276
|
+
research=_parse_bool(data.get("research", False))
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
res.testsRun = int(data.get("testsRun", 0))
|
|
280
|
+
|
|
281
|
+
res.my_successes = [T(tid) for tid in data.get("my_successes", [])]
|
|
282
|
+
res.timeouts = [T(tid) for tid in data.get("timeouts", [])]
|
|
283
|
+
res.all = [T(tid) for tid in data.get("all", [])]
|
|
284
|
+
res.all_order = dict(data.get("all_order", {}))
|
|
285
|
+
|
|
286
|
+
# IMPORTANTÍSSIMO: failures/errors/skipped são listas de pares (id, texto)
|
|
287
|
+
res.failures = [(T(tid), tb) for (tid, tb) in data.get("failures", [])]
|
|
288
|
+
res.errors = [(T(tid), tb) for (tid, tb) in data.get("errors", [])]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
res.runner_returncode = data.get("runner_returncode", None)
|
|
292
|
+
|
|
293
|
+
# semântica: runner_ok (execução do runner OK)
|
|
294
|
+
res._ok = _parse_bool(data.get("runner_ok", False))
|
|
295
|
+
|
|
296
|
+
res.count = len(res.all)
|
|
297
|
+
|
|
298
|
+
return res
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def __exec():
|
|
303
|
+
session_name = sys.argv[-1]
|
|
304
|
+
directory = None
|
|
305
|
+
i = 2
|
|
306
|
+
keep = False
|
|
307
|
+
verbose = False
|
|
308
|
+
is_windows = False
|
|
309
|
+
while i < len(sys.argv[:-1]):
|
|
310
|
+
s = sys.argv[i]
|
|
311
|
+
match s:
|
|
312
|
+
case '--D':
|
|
313
|
+
i += 1
|
|
314
|
+
directory = sys.argv[i]
|
|
315
|
+
case '--keep':
|
|
316
|
+
keep = True
|
|
317
|
+
case '--v':
|
|
318
|
+
verbose = True
|
|
319
|
+
case '--win':
|
|
320
|
+
is_windows = True
|
|
321
|
+
case _:
|
|
322
|
+
usage()
|
|
323
|
+
i += 1
|
|
324
|
+
|
|
325
|
+
change_dir_connect(directory, session_name)
|
|
326
|
+
session = Session.get(Session.id==1)
|
|
327
|
+
rse = session.type == 'research'
|
|
328
|
+
|
|
329
|
+
fields = get_all_test_names()
|
|
330
|
+
|
|
331
|
+
cont_dead = 0
|
|
332
|
+
cont_live = 0
|
|
333
|
+
cont_equiv = 0
|
|
334
|
+
is_windows = is_windows or platform.system() == 'Windows'
|
|
335
|
+
|
|
336
|
+
with db.atomic():
|
|
337
|
+
for muta in Mutant.select():
|
|
338
|
+
|
|
339
|
+
reg_muta, created = Execution.get_or_create(
|
|
340
|
+
mutant=muta,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if created:
|
|
344
|
+
er = ExecutionReg()
|
|
345
|
+
else:
|
|
346
|
+
er = ExecutionReg(reg_muta.execucao)
|
|
347
|
+
er.update(fields)
|
|
348
|
+
|
|
349
|
+
reg_muta.execucao = str(er)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
if muta.status == 'equiv':
|
|
353
|
+
cont_equiv += 1
|
|
354
|
+
reg_muta.save()
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
if keep and muta.status != 'live' and not rse:
|
|
358
|
+
cont_dead += 1
|
|
359
|
+
reg_muta.save()
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
print('Mutant {} -- {}'.format(muta.id, muta.operator))
|
|
363
|
+
tree = pickle.loads(muta.ast)
|
|
364
|
+
muta_source_code = ast.unparse(tree)
|
|
365
|
+
simple_create_module_from_ast(muta.source.filename, tree)
|
|
366
|
+
|
|
367
|
+
#print(reg_muta)
|
|
368
|
+
dead = False
|
|
369
|
+
for testfile in TestCase:
|
|
370
|
+
try:
|
|
371
|
+
if is_windows:
|
|
372
|
+
resultado = call_runner_windows(muta.source.filename, muta_source_code, testfile.filename,rse, testfile.id)
|
|
373
|
+
if not resultado._ok :
|
|
374
|
+
#print(resultado.msg)
|
|
375
|
+
raise MutantTimeout('Can not start Windows runner.')
|
|
376
|
+
else:
|
|
377
|
+
with open(os.devnull, 'w') as fnull, redirect_stdout(fnull), redirect_stderr(fnull):
|
|
378
|
+
module_test = load_module(testfile.filename)
|
|
379
|
+
suite = unittest.TestLoader().loadTestsFromModule(module_test)
|
|
380
|
+
decor = tmtdecor.timeout(TIMEOUT_SEGUNDOS, use_signals=True,timeout_exception=MutantTimeout)
|
|
381
|
+
aplicar_timeout_em_suite(suite, decorador=decor)
|
|
382
|
+
resultado = RegistrandoRunner(stream=io.StringIO(), research=rse,verbosity=0,number=testfile.id).run(suite)
|
|
383
|
+
except Exception as ex:
|
|
384
|
+
print(red(f'Error. Can not run test file {testfile.filename}'))
|
|
385
|
+
print(red(ex))
|
|
386
|
+
sys.exit(1)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
for test in resultado.my_successes:
|
|
390
|
+
t = resultado.all_order[test.id()]
|
|
391
|
+
if verbose:
|
|
392
|
+
print(f'{t} passed.')
|
|
393
|
+
er.registro[test.id()] = 'live'
|
|
394
|
+
reg_muta.execucao = str(er)
|
|
395
|
+
reg_muta.save()
|
|
396
|
+
|
|
397
|
+
for test,tb in resultado.failures:
|
|
398
|
+
t = resultado.all_order[test.id()]
|
|
399
|
+
if test in resultado.timeouts:
|
|
400
|
+
s = 'timeout'
|
|
401
|
+
else:
|
|
402
|
+
s = 'fail'
|
|
403
|
+
if verbose:
|
|
404
|
+
print(f'{t} {s}.')
|
|
405
|
+
#print('failure', test.id())
|
|
406
|
+
#print(tb)
|
|
407
|
+
|
|
408
|
+
er.registro[test.id()] = s
|
|
409
|
+
reg_muta.execucao = str(er)
|
|
410
|
+
reg_muta.save()
|
|
411
|
+
dead = True
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
for test,_ in resultado.errors:
|
|
415
|
+
t = resultado.all_order[test.id()]
|
|
416
|
+
if test in resultado.timeouts:
|
|
417
|
+
s = 'timeout'
|
|
418
|
+
else:
|
|
419
|
+
s = 'error'
|
|
420
|
+
if verbose:
|
|
421
|
+
print(f'{t} {s}.')
|
|
422
|
+
# print('error', test.id())
|
|
423
|
+
#print(tb)
|
|
424
|
+
|
|
425
|
+
er.registro[test.id()] = s
|
|
426
|
+
reg_muta.execucao = str(er)
|
|
427
|
+
reg_muta.save()
|
|
428
|
+
dead = True
|
|
429
|
+
if dead and not rse:
|
|
430
|
+
break
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if dead:
|
|
434
|
+
muta.status = 'dead'
|
|
435
|
+
print('Dead')
|
|
436
|
+
cont_dead += 1
|
|
437
|
+
muta.save()
|
|
438
|
+
else:
|
|
439
|
+
cont_live += 1
|
|
440
|
+
muta.status = 'live'
|
|
441
|
+
muta.save()
|
|
442
|
+
|
|
443
|
+
print(green(f'Alive: {cont_live}'))
|
|
444
|
+
print(green(f'Dead: {cont_dead}'))
|
|
445
|
+
print(green(f'Equivalent: {cont_equiv}'))
|
|
446
|
+
if cont_live+cont_dead == 0:
|
|
447
|
+
print(green('Mutation score: {:.2f}'.format(0.0)))
|
|
448
|
+
else:
|
|
449
|
+
print(green('Mutation score: {:.3f}'.format(cont_dead/(cont_live+cont_dead))))
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
TIMEOUT_SEGUNDOS = 2
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def aplicar_timeout_em_suite(suite, timeout_segundos=None, use_signals=False, decorador=None):
|
|
457
|
+
if decorador is None:
|
|
458
|
+
if timeout_segundos is None:
|
|
459
|
+
timeout_segundos = TIMEOUT_SEGUNDOS
|
|
460
|
+
decorador = tmtdecor.timeout(timeout_segundos, use_signals=use_signals, timeout_exception=MutantTimeout)
|
|
461
|
+
for item in suite:
|
|
462
|
+
if isinstance(item, unittest.TestSuite):
|
|
463
|
+
aplicar_timeout_em_suite(item, timeout_segundos, use_signals, decorador)
|
|
464
|
+
elif isinstance(item, unittest.TestCase):
|
|
465
|
+
nome_metodo = item._testMethodName
|
|
466
|
+
metodo_original = getattr(item, nome_metodo)
|
|
467
|
+
|
|
468
|
+
func_original = getattr(metodo_original, "__func__", metodo_original)
|
|
469
|
+
func_decorada = decorador(func_original)
|
|
470
|
+
|
|
471
|
+
setattr(item, nome_metodo, func_decorada.__get__(item, item.__class__))
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def __csv():
|
|
475
|
+
session_name = sys.argv.pop()
|
|
476
|
+
directory = None
|
|
477
|
+
outname = None
|
|
478
|
+
i = 2
|
|
479
|
+
while i < len(sys.argv[:-1]):
|
|
480
|
+
s = sys.argv[i]
|
|
481
|
+
match s:
|
|
482
|
+
case '--D':
|
|
483
|
+
i += 1
|
|
484
|
+
directory = sys.argv[i]
|
|
485
|
+
case '--O':
|
|
486
|
+
i += 1
|
|
487
|
+
outname = sys.argv[i]
|
|
488
|
+
case _:
|
|
489
|
+
usage()
|
|
490
|
+
i += 1
|
|
491
|
+
|
|
492
|
+
change_dir_connect(directory, session_name)
|
|
493
|
+
|
|
494
|
+
if outname is None:
|
|
495
|
+
outname = session_name+'.csv'
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
try:
|
|
499
|
+
with open(outname, 'w') as out:
|
|
500
|
+
writer = csv.writer(out)
|
|
501
|
+
for registro in Execution.select():
|
|
502
|
+
execucao = json.loads(registro.execucao)
|
|
503
|
+
if registro.id == 1:
|
|
504
|
+
row = ['mutant_id']
|
|
505
|
+
for campo in execucao.keys():
|
|
506
|
+
row.append(campo)
|
|
507
|
+
writer.writerow(row)
|
|
508
|
+
row = [registro.mutant_id]
|
|
509
|
+
for campo,valor in execucao.items():
|
|
510
|
+
row.append(valor)
|
|
511
|
+
|
|
512
|
+
writer.writerow(row)
|
|
513
|
+
|
|
514
|
+
print(f'{outname} successfully generated')
|
|
515
|
+
except Exception as ex:
|
|
516
|
+
print(red(f'Could not generate {outname}'))
|
|
517
|
+
print(red(ex))
|
|
518
|
+
sys.exit(1)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def __equiv():
|
|
522
|
+
session_name = sys.argv[-1]
|
|
523
|
+
directory = None
|
|
524
|
+
list_number = None
|
|
525
|
+
i = 2
|
|
526
|
+
while i < len(sys.argv[:-1]):
|
|
527
|
+
s = sys.argv[i]
|
|
528
|
+
match s:
|
|
529
|
+
case '--D':
|
|
530
|
+
i += 1
|
|
531
|
+
directory = sys.argv[i]
|
|
532
|
+
case '--x':
|
|
533
|
+
i += 1
|
|
534
|
+
list_number = sys.argv[i]
|
|
535
|
+
case _:
|
|
536
|
+
usage()
|
|
537
|
+
i += 1
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
try:
|
|
541
|
+
change_dir_connect(directory, session_name)
|
|
542
|
+
except Exception as ex:
|
|
543
|
+
print(red(f'Can not access test session'))
|
|
544
|
+
print(red(ex))
|
|
545
|
+
sys.exit(1)
|
|
546
|
+
|
|
547
|
+
# se os número não foram fornecidos, marca todos os vivos
|
|
548
|
+
if list_number is None:
|
|
549
|
+
list_number = []
|
|
550
|
+
for numero in Mutant.select().where(Mutant.status == "live"):
|
|
551
|
+
list_number.append(numero.id)
|
|
552
|
+
else:
|
|
553
|
+
list_number = list_number.split()
|
|
554
|
+
|
|
555
|
+
with db.atomic():
|
|
556
|
+
for muta_number in list_number:
|
|
557
|
+
try:
|
|
558
|
+
muta_number = int(muta_number)
|
|
559
|
+
reg_muta = Mutant.get(Mutant.id==muta_number)
|
|
560
|
+
print('Mutant: ', reg_muta.id)
|
|
561
|
+
if not reg_muta.status in ['live','equiv']:
|
|
562
|
+
print(f'Warning: mutant {reg_muta.id} is not alive. It is {reg_muta.status}')
|
|
563
|
+
else:
|
|
564
|
+
reg_muta.status = 'equiv'
|
|
565
|
+
reg_muta.save()
|
|
566
|
+
except Exception as ex:
|
|
567
|
+
print(red(f'Can not access mutant number {muta_number}'))
|
|
568
|
+
print(red(ex))
|
|
569
|
+
sys.exit(1)
|
|
570
|
+
|
|
571
|
+
def main():
|
|
572
|
+
n = len(sys.argv)-2
|
|
573
|
+
if n < 1:
|
|
574
|
+
usage()
|
|
575
|
+
|
|
576
|
+
if sys.argv[1] == '--exec':
|
|
577
|
+
__exec()
|
|
578
|
+
elif sys.argv[1] == '--csv':
|
|
579
|
+
__csv()
|
|
580
|
+
elif sys.argv[1] == '--equiv':
|
|
581
|
+
__equiv()
|
|
582
|
+
else:
|
|
583
|
+
usage()
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def usage():
|
|
587
|
+
print('Usage:')
|
|
588
|
+
print('exemuta --exec [--keep] [--D <directory> ] <session name>')
|
|
589
|
+
print('\tExecute the mutants with the test cases in the session')
|
|
590
|
+
print('\t--keep: execute only the live mutants')
|
|
591
|
+
print('exemuta --csv [--D <directory> ] <session name>')
|
|
592
|
+
print('\tExports the last execution to a csv file')
|
|
593
|
+
print('exemuta --equiv --x <list of numbers> [--D <directory> ] <session name>')
|
|
594
|
+
print('\tMarks mutants as equivalents')
|
|
595
|
+
sys.exit(1)
|
|
596
|
+
|
|
597
|
+
if __name__ == '__main__' :
|
|
598
|
+
main()
|
|
@@ -4,8 +4,8 @@ from pyproteum.moperators.myoperator import *
|
|
|
4
4
|
|
|
5
5
|
class Cccr(MyOperator):
|
|
6
6
|
|
|
7
|
-
def __init__(self, original,filename,max):
|
|
8
|
-
super().__init__(original, filename, max)
|
|
7
|
+
def __init__(self, original,filename,max, **kwargs):
|
|
8
|
+
super().__init__(original, filename, max, **kwargs)
|
|
9
9
|
self.seq = 1
|
|
10
10
|
self.const_set = list()
|
|
11
11
|
|
|
@@ -5,8 +5,8 @@ import copy
|
|
|
5
5
|
|
|
6
6
|
class Ccsr(MyOperator):
|
|
7
7
|
|
|
8
|
-
def __init__(self, original,filename,max):
|
|
9
|
-
super().__init__(original, filename, max)
|
|
8
|
+
def __init__(self, original,filename,max, **kwargs):
|
|
9
|
+
super().__init__(original, filename, max, **kwargs)
|
|
10
10
|
self.seq = 1
|
|
11
11
|
self.scalar_set = set()
|
|
12
12
|
ParentSetter().visit(self.original)
|
|
@@ -21,13 +21,15 @@ class Ccsr(MyOperator):
|
|
|
21
21
|
|
|
22
22
|
def visit_Name(self, node):
|
|
23
23
|
if isinstance(node.ctx, ast.Store) :
|
|
24
|
-
self.scalar_set
|
|
24
|
+
if not node.id in self.scalar_set:
|
|
25
|
+
self.scalar_set.add(node.id)
|
|
25
26
|
self.generic_visit(node)
|
|
26
27
|
return node
|
|
27
28
|
|
|
28
29
|
def visit_arg(self, node):
|
|
29
30
|
if node.arg != 'self':
|
|
30
|
-
self.scalar_set
|
|
31
|
+
if not node.arg in self.scalar_set:
|
|
32
|
+
self.scalar_set.add(node.arg)
|
|
31
33
|
self.generic_visit(node)
|
|
32
34
|
return node
|
|
33
35
|
|