sflkit 0.2.18__tar.gz → 0.2.20__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.
- {sflkit-0.2.18/src/sflkit.egg-info → sflkit-0.2.20}/PKG-INFO +1 -1
- {sflkit-0.2.18 → sflkit-0.2.20}/pyproject.toml +1 -1
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/python/factory.py +9 -6
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/runners/run.py +150 -12
- {sflkit-0.2.18 → sflkit-0.2.20/src/sflkit.egg-info}/PKG-INFO +1 -1
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit.egg-info/SOURCES.txt +1 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_instrumentation.py +56 -6
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_runner.py +1 -1
- sflkit-0.2.20/tests/test_runner_pytest_versions.py +121 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/LICENSE +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/README.md +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/setup.cfg +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/analysis_type.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/analyzer.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/factory.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/mapping.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/predicate.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/similarity.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/spectra.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/analysis/suggestion.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/cli.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/color.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/config.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/instrumentation/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/instrumentation/dir_instrumentation.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/instrumentation/file_instrumentation.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/extract.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/finder.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/language.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/meta.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/python/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/python/extract.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/python/finder.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/python/visitor.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/language/visitor.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/logger.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/mapping.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/model/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/model/event_file.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/model/model.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/model/scope.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit/runners/__init__.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit.egg-info/dependency_links.txt +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit.egg-info/entry_points.txt +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit.egg-info/requires.txt +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/src/sflkit.egg-info/top_level.txt +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_analysis_objects.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_cli.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_color.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_config.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_coverage.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_events.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_execution.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_language.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_predicates.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_scope.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_spectra.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_suggestions.py +0 -0
- {sflkit-0.2.18 → sflkit-0.2.20}/tests/test_visitors.py +0 -0
|
@@ -849,14 +849,17 @@ class LenEventFactory(DefEventFactory):
|
|
|
849
849
|
keywords=[],
|
|
850
850
|
)
|
|
851
851
|
)
|
|
852
|
-
return
|
|
853
|
-
test=Call(
|
|
854
|
-
func=Name(id="hasattr"),
|
|
855
|
-
args=[Name(id=event.var), Constant(value="__len__")],
|
|
856
|
-
keywords=[],
|
|
857
|
-
),
|
|
852
|
+
return Try(
|
|
858
853
|
body=[call],
|
|
854
|
+
handlers=[
|
|
855
|
+
ExceptHandler(
|
|
856
|
+
type=Name(id="TypeError"),
|
|
857
|
+
name=None,
|
|
858
|
+
body=[Pass()],
|
|
859
|
+
)
|
|
860
|
+
],
|
|
859
861
|
orelse=[],
|
|
862
|
+
finalbody=[],
|
|
860
863
|
)
|
|
861
864
|
|
|
862
865
|
def get_event_call(self, event: LenEvent):
|
|
@@ -9,6 +9,8 @@ import subprocess
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import List, Dict, Optional, Tuple
|
|
11
11
|
|
|
12
|
+
from sflkit.logger import LOGGER
|
|
13
|
+
|
|
12
14
|
Environment = Dict[str, str]
|
|
13
15
|
|
|
14
16
|
PYTEST_RESULT_PATTERN = re.compile(
|
|
@@ -51,6 +53,7 @@ class Runner(abc.ABC):
|
|
|
51
53
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
52
54
|
base: Optional[os.PathLike] = None,
|
|
53
55
|
environ: Environment = None,
|
|
56
|
+
k: str = None,
|
|
54
57
|
) -> List[str]:
|
|
55
58
|
return []
|
|
56
59
|
|
|
@@ -99,6 +102,7 @@ class Runner(abc.ABC):
|
|
|
99
102
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
100
103
|
base: Optional[os.PathLike] = None,
|
|
101
104
|
environ: Environment = None,
|
|
105
|
+
k: str = None,
|
|
102
106
|
):
|
|
103
107
|
self.passing_tests.clear()
|
|
104
108
|
self.failing_tests.clear()
|
|
@@ -107,7 +111,7 @@ class Runner(abc.ABC):
|
|
|
107
111
|
directory,
|
|
108
112
|
output,
|
|
109
113
|
self.filter_tests(
|
|
110
|
-
self.get_tests(directory, files=files, base=base, environ=environ)
|
|
114
|
+
self.get_tests(directory, files=files, base=base, environ=environ, k=k)
|
|
111
115
|
),
|
|
112
116
|
environ=environ,
|
|
113
117
|
)
|
|
@@ -117,7 +121,130 @@ class VoidRunner(Runner):
|
|
|
117
121
|
pass
|
|
118
122
|
|
|
119
123
|
|
|
124
|
+
class PytestStructure:
|
|
125
|
+
def __init__(self, name: str, parent: Optional["PytestStructure"] = None):
|
|
126
|
+
self.name = name
|
|
127
|
+
self.children = []
|
|
128
|
+
self.parent = parent
|
|
129
|
+
self.depth = 0
|
|
130
|
+
|
|
131
|
+
def add_child(self, child):
|
|
132
|
+
self.children.append(child)
|
|
133
|
+
|
|
134
|
+
def set_parent(self, parent):
|
|
135
|
+
self.parent = parent
|
|
136
|
+
|
|
137
|
+
def get_seperator(self):
|
|
138
|
+
return ""
|
|
139
|
+
|
|
140
|
+
def get_tests(self) -> List[str]:
|
|
141
|
+
if self.children:
|
|
142
|
+
return [
|
|
143
|
+
self.name + self.get_seperator() + test
|
|
144
|
+
for child in self.children
|
|
145
|
+
for test in child.get_tests()
|
|
146
|
+
]
|
|
147
|
+
else:
|
|
148
|
+
return [self.name]
|
|
149
|
+
|
|
150
|
+
DIR = "Dir"
|
|
151
|
+
PACKAGE = "Package"
|
|
152
|
+
MODULE = "Module"
|
|
153
|
+
CLASS = "Class"
|
|
154
|
+
FUNCTION = "Function"
|
|
155
|
+
UNIT_TEST_CASE = "UnitTestCase"
|
|
156
|
+
TEST_CASE_FUNCTION = "TestCaseFunction"
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def get_pattern(obj):
|
|
160
|
+
return re.compile(rf"<{obj} ['\"]?(?P<name>[^>'\"]*)['\"]?>")
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def parse_tests(output: str) -> List[str]:
|
|
164
|
+
trees = []
|
|
165
|
+
current = None
|
|
166
|
+
for original_line in output.split("\n"):
|
|
167
|
+
line = original_line.lstrip()
|
|
168
|
+
level = len(original_line) - len(line)
|
|
169
|
+
if f"<{PytestStructure.DIR}" in line:
|
|
170
|
+
structure = Directory
|
|
171
|
+
pattern = PytestStructure.get_pattern(PytestStructure.DIR)
|
|
172
|
+
elif f"<{PytestStructure.PACKAGE}" in line:
|
|
173
|
+
structure = Directory
|
|
174
|
+
pattern = PytestStructure.get_pattern(PytestStructure.PACKAGE)
|
|
175
|
+
elif f"<{PytestStructure.MODULE}" in line:
|
|
176
|
+
structure = Module
|
|
177
|
+
pattern = PytestStructure.get_pattern(PytestStructure.MODULE)
|
|
178
|
+
elif f"<{PytestStructure.CLASS}" in line:
|
|
179
|
+
structure = Class
|
|
180
|
+
pattern = PytestStructure.get_pattern(PytestStructure.CLASS)
|
|
181
|
+
elif f"<{PytestStructure.FUNCTION}" in line:
|
|
182
|
+
structure = Function
|
|
183
|
+
pattern = PytestStructure.get_pattern(PytestStructure.FUNCTION)
|
|
184
|
+
elif f"<{PytestStructure.UNIT_TEST_CASE}" in line:
|
|
185
|
+
structure = Class
|
|
186
|
+
pattern = PytestStructure.get_pattern(PytestStructure.UNIT_TEST_CASE)
|
|
187
|
+
elif f"<{PytestStructure.TEST_CASE_FUNCTION}" in line:
|
|
188
|
+
structure = Function
|
|
189
|
+
pattern = PytestStructure.get_pattern(
|
|
190
|
+
PytestStructure.TEST_CASE_FUNCTION
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
continue
|
|
194
|
+
match = pattern.search(line)
|
|
195
|
+
if match:
|
|
196
|
+
obj = structure(match.group("name"))
|
|
197
|
+
obj.depth = level
|
|
198
|
+
if current is None:
|
|
199
|
+
trees.append(obj)
|
|
200
|
+
elif current.depth < obj.depth:
|
|
201
|
+
obj.set_parent(current)
|
|
202
|
+
current.add_child(obj)
|
|
203
|
+
elif current.depth >= obj.depth:
|
|
204
|
+
while current.depth >= obj.depth and current:
|
|
205
|
+
current = current.parent
|
|
206
|
+
if current is None:
|
|
207
|
+
trees.append(obj)
|
|
208
|
+
else:
|
|
209
|
+
obj.set_parent(current)
|
|
210
|
+
current.add_child(obj)
|
|
211
|
+
current = obj
|
|
212
|
+
return [test for tree in trees for test in tree.get_tests()]
|
|
213
|
+
|
|
214
|
+
def __repr__(self):
|
|
215
|
+
return f"<{self.__class__.__name__} {self.name}>"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class Directory(PytestStructure):
|
|
219
|
+
def get_seperator(self):
|
|
220
|
+
return os.sep
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class Module(PytestStructure):
|
|
224
|
+
def get_seperator(self):
|
|
225
|
+
return "::"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class Class(PytestStructure):
|
|
229
|
+
def get_seperator(self):
|
|
230
|
+
return "::"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class Function(PytestStructure):
|
|
234
|
+
def get_seperator(self):
|
|
235
|
+
return "::"
|
|
236
|
+
|
|
237
|
+
|
|
120
238
|
class PytestRunner(Runner):
|
|
239
|
+
def __init__(
|
|
240
|
+
self,
|
|
241
|
+
re_filter: str = r".*",
|
|
242
|
+
timeout=DEFAULT_TIMEOUT,
|
|
243
|
+
set_python_path: bool = False,
|
|
244
|
+
):
|
|
245
|
+
super().__init__(re_filter, timeout)
|
|
246
|
+
self.set_python_path = set_python_path
|
|
247
|
+
|
|
121
248
|
@staticmethod
|
|
122
249
|
def _common_base(directory: Path, tests: List[str]) -> Path:
|
|
123
250
|
parts = directory.parts
|
|
@@ -160,29 +287,39 @@ class PytestRunner(Runner):
|
|
|
160
287
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
161
288
|
base: Optional[os.PathLike] = None,
|
|
162
289
|
environ: Environment = None,
|
|
290
|
+
k: str = None,
|
|
163
291
|
) -> List[str]:
|
|
164
292
|
c = []
|
|
165
293
|
directory = directory.absolute()
|
|
294
|
+
if k:
|
|
295
|
+
c.append("-k")
|
|
296
|
+
c.append(k)
|
|
166
297
|
if files:
|
|
167
298
|
if isinstance(files, (str, os.PathLike)):
|
|
168
|
-
c.append(files)
|
|
299
|
+
c.append(str(files))
|
|
169
300
|
else:
|
|
170
|
-
c += files
|
|
301
|
+
c += [str(f) for f in files]
|
|
171
302
|
if base:
|
|
172
303
|
if not files:
|
|
173
|
-
c.append(base)
|
|
304
|
+
c.append(str(base))
|
|
174
305
|
root_dir = directory / base
|
|
175
306
|
else:
|
|
176
307
|
root_dir = directory
|
|
177
|
-
|
|
178
|
-
[
|
|
308
|
+
process = subprocess.run(
|
|
309
|
+
[
|
|
310
|
+
"python3",
|
|
311
|
+
"-m",
|
|
312
|
+
"pytest",
|
|
313
|
+
"--collect-only",
|
|
314
|
+
]
|
|
315
|
+
+ c,
|
|
179
316
|
stdout=subprocess.PIPE,
|
|
317
|
+
stderr=subprocess.PIPE,
|
|
180
318
|
env=environ,
|
|
181
319
|
cwd=directory,
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
tests = tests[:-1]
|
|
320
|
+
)
|
|
321
|
+
LOGGER.info(f"pytest collection finished with {process.returncode}")
|
|
322
|
+
tests = PytestStructure.parse_tests(process.stdout.decode("utf8"))
|
|
186
323
|
return self._normalize_paths(tests, directory, root_dir)
|
|
187
324
|
|
|
188
325
|
@staticmethod
|
|
@@ -207,7 +344,7 @@ class PytestRunner(Runner):
|
|
|
207
344
|
) -> TestResult:
|
|
208
345
|
try:
|
|
209
346
|
output = subprocess.run(
|
|
210
|
-
["
|
|
347
|
+
["python3", "-m", "pytest", test],
|
|
211
348
|
stdout=subprocess.PIPE,
|
|
212
349
|
env=environ,
|
|
213
350
|
cwd=directory,
|
|
@@ -274,6 +411,7 @@ class InputRunner(Runner):
|
|
|
274
411
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
275
412
|
base: Optional[os.PathLike] = None,
|
|
276
413
|
environ: Environment = None,
|
|
414
|
+
k: str = None,
|
|
277
415
|
) -> List[str]:
|
|
278
416
|
return list(self.passing.keys()) + list(self.failing.keys())
|
|
279
417
|
|
|
@@ -288,7 +426,7 @@ class InputRunner(Runner):
|
|
|
288
426
|
result = TestResult.FAILING
|
|
289
427
|
try:
|
|
290
428
|
process = subprocess.run(
|
|
291
|
-
["
|
|
429
|
+
["python3", self.access] + test,
|
|
292
430
|
stdout=subprocess.PIPE,
|
|
293
431
|
stderr=subprocess.PIPE,
|
|
294
432
|
env=environ,
|
|
@@ -2,8 +2,7 @@ import atexit
|
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from sflkitlib.events import event, EventType
|
|
5
|
+
from sflkitlib.events import EventType
|
|
7
6
|
|
|
8
7
|
from sflkit import instrument_config, Config
|
|
9
8
|
from utils import BaseTest
|
|
@@ -121,6 +120,61 @@ class TestInstrumentation(BaseTest):
|
|
|
121
120
|
self.assertTrue(mapping.exists())
|
|
122
121
|
self.assertTrue(mapping.is_file())
|
|
123
122
|
|
|
123
|
+
def test_instrument_exclude(self):
|
|
124
|
+
src = Path(BaseTest.TEST_RESOURCES, "test_exclude")
|
|
125
|
+
dst = Path(BaseTest.TEST_DIR)
|
|
126
|
+
instrument_config(
|
|
127
|
+
Config.create(
|
|
128
|
+
path=str(src),
|
|
129
|
+
language="python",
|
|
130
|
+
events="line",
|
|
131
|
+
predicates="line",
|
|
132
|
+
working=BaseTest.TEST_DIR,
|
|
133
|
+
include="included",
|
|
134
|
+
exclude=os.path.join("included", "excluded"),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
included = dst / "included"
|
|
138
|
+
excluded = included / "excluded"
|
|
139
|
+
a = included / "a.py"
|
|
140
|
+
b = excluded / "b.py"
|
|
141
|
+
c = dst / "c.py"
|
|
142
|
+
|
|
143
|
+
included_src = src / "included"
|
|
144
|
+
excluded_src = included_src / "excluded"
|
|
145
|
+
a_src = included_src / "a.py"
|
|
146
|
+
b_src = excluded_src / "b.py"
|
|
147
|
+
c_src = src / "c.py"
|
|
148
|
+
|
|
149
|
+
self.assertTrue(included.exists())
|
|
150
|
+
self.assertTrue(included.is_dir())
|
|
151
|
+
self.assertTrue(excluded.exists())
|
|
152
|
+
self.assertTrue(excluded.is_dir())
|
|
153
|
+
self.assertTrue(a.exists())
|
|
154
|
+
self.assertTrue(a.is_file())
|
|
155
|
+
self.assertTrue(b.exists())
|
|
156
|
+
self.assertTrue(b.is_file())
|
|
157
|
+
self.assertTrue(c.exists())
|
|
158
|
+
self.assertTrue(c.is_file())
|
|
159
|
+
|
|
160
|
+
with open(a, "r") as fp:
|
|
161
|
+
a_content = fp.read()
|
|
162
|
+
with open(a_src, "r") as fp:
|
|
163
|
+
a_src_content = fp.read()
|
|
164
|
+
self.assertNotEqual(a_src_content, a_content)
|
|
165
|
+
|
|
166
|
+
with open(b, "r") as fp:
|
|
167
|
+
b_content = fp.read()
|
|
168
|
+
with open(b_src, "r") as fp:
|
|
169
|
+
b_src_content = fp.read()
|
|
170
|
+
self.assertEqual(b_src_content, b_content)
|
|
171
|
+
|
|
172
|
+
with open(c, "r") as fp:
|
|
173
|
+
c_content = fp.read()
|
|
174
|
+
with open(c_src, "r") as fp:
|
|
175
|
+
c_src_content = fp.read()
|
|
176
|
+
self.assertEqual(c_src_content, c_content)
|
|
177
|
+
|
|
124
178
|
|
|
125
179
|
class TestLib(BaseTest):
|
|
126
180
|
@classmethod
|
|
@@ -146,10 +200,6 @@ class TestLib(BaseTest):
|
|
|
146
200
|
def setUp(self) -> None:
|
|
147
201
|
self.events = None
|
|
148
202
|
|
|
149
|
-
def load(self) -> None:
|
|
150
|
-
self.lib.dump_events()
|
|
151
|
-
self.events = event.load(self.TEST_PATH)
|
|
152
|
-
|
|
153
203
|
def tearDown(self) -> None:
|
|
154
204
|
# noinspection PyBroadException
|
|
155
205
|
try:
|
|
@@ -37,7 +37,7 @@ class RunnerTests(BaseTest):
|
|
|
37
37
|
exclude="tests",
|
|
38
38
|
)
|
|
39
39
|
instrument_config(config)
|
|
40
|
-
runner = PytestRunner()
|
|
40
|
+
runner = PytestRunner(set_python_path=True)
|
|
41
41
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
42
42
|
runner.run(
|
|
43
43
|
Path(BaseTest.TEST_DIR), output, files=[Path("tests", "test_middle.py")]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from sflkit import Config, instrument_config, Analyzer
|
|
9
|
+
from sflkit.analysis.analysis_type import AnalysisType
|
|
10
|
+
from sflkit.analysis.suggestion import Location
|
|
11
|
+
from sflkit.mapping import EventMapping
|
|
12
|
+
from sflkit.model import EventFile
|
|
13
|
+
from sflkit.runners.run import (
|
|
14
|
+
PytestRunner,
|
|
15
|
+
)
|
|
16
|
+
from tests.utils import BaseTest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@unittest.skip
|
|
20
|
+
class RunnerTests(BaseTest):
|
|
21
|
+
VENV = Path("test_venv")
|
|
22
|
+
PYTHON_BASE = os.path.join(
|
|
23
|
+
os.path.expanduser("~"), ".pyenv", "versions", "3.8.4", "bin", "python3"
|
|
24
|
+
)
|
|
25
|
+
PYTHON = "python3"
|
|
26
|
+
|
|
27
|
+
def assertPathExists(self, path: os.PathLike):
|
|
28
|
+
self.assertTrue(os.path.exists(path), f"{path} does not exists.")
|
|
29
|
+
|
|
30
|
+
def create_venv(self, environ):
|
|
31
|
+
shutil.rmtree(self.VENV, ignore_errors=True)
|
|
32
|
+
subprocess.check_call([self.PYTHON_BASE, "-m", "venv", self.VENV], env=environ)
|
|
33
|
+
|
|
34
|
+
def activate_venv(self, environ):
|
|
35
|
+
environ["VIRTUAL_ENV"] = str(self.VENV.absolute())
|
|
36
|
+
environ["_OLD_VIRTUAL_PATH"] = environ["PATH"]
|
|
37
|
+
if sys.platform.startswith("win"):
|
|
38
|
+
environ["PATH"] = f'{(self.VENV / "Scripts").absolute()};{environ["PATH"]}'
|
|
39
|
+
else:
|
|
40
|
+
environ["PATH"] = f'{(self.VENV / "bin").absolute()}:{environ["PATH"]}'
|
|
41
|
+
if "PYTHONHOME" in environ:
|
|
42
|
+
environ["_OLD_VIRTUAL_PYTHONHOME"] = environ["PYTHONHOME"]
|
|
43
|
+
del environ["PYTHONHOME"]
|
|
44
|
+
if "VIRTUAL_ENV_DISABLE_PROMPT" not in environ:
|
|
45
|
+
environ["_OLD_VIRTUAL_PS1"] = environ["PS1"] if "PS1" in environ else ""
|
|
46
|
+
environ[
|
|
47
|
+
"PS1"
|
|
48
|
+
] = f'({self.VENV}) {environ["PS1"] if "PS1" in environ else ""}'
|
|
49
|
+
environ["VIRTUAL_ENV_PROMPT"] = f"({self.VENV})"
|
|
50
|
+
return environ
|
|
51
|
+
|
|
52
|
+
def install_pytest(self, environ, pytest_version):
|
|
53
|
+
subprocess.check_call(
|
|
54
|
+
[self.PYTHON, "-m", "pip", "install", f"pytest=={pytest_version}"],
|
|
55
|
+
env=environ,
|
|
56
|
+
)
|
|
57
|
+
subprocess.check_call(
|
|
58
|
+
[self.PYTHON, "-m", "pip", "install", f"sflkitlib"],
|
|
59
|
+
env=environ,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def tearDown(self):
|
|
63
|
+
shutil.rmtree(self.VENV, ignore_errors=True)
|
|
64
|
+
|
|
65
|
+
def _runner_test(self, pytest_version):
|
|
66
|
+
environ = os.environ.copy()
|
|
67
|
+
self.create_venv(environ)
|
|
68
|
+
environ = self.activate_venv(environ)
|
|
69
|
+
self.install_pytest(environ, pytest_version)
|
|
70
|
+
config = Config.create(
|
|
71
|
+
path=os.path.join(self.TEST_RESOURCES, BaseTest.TEST_RUNNER),
|
|
72
|
+
language="python",
|
|
73
|
+
events="line",
|
|
74
|
+
predicates="line",
|
|
75
|
+
working=BaseTest.TEST_DIR,
|
|
76
|
+
exclude="tests",
|
|
77
|
+
)
|
|
78
|
+
instrument_config(config)
|
|
79
|
+
runner = PytestRunner(set_python_path=True)
|
|
80
|
+
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
81
|
+
runner.run(
|
|
82
|
+
Path(BaseTest.TEST_DIR),
|
|
83
|
+
output,
|
|
84
|
+
files=[Path("tests", "test_middle.py")],
|
|
85
|
+
environ=environ,
|
|
86
|
+
)
|
|
87
|
+
self.assertPathExists(output)
|
|
88
|
+
self.assertPathExists(output / "passing")
|
|
89
|
+
self.assertPathExists(output / "failing")
|
|
90
|
+
self.assertPathExists(output / "undefined")
|
|
91
|
+
mapping = EventMapping.load(config)
|
|
92
|
+
analyzer = Analyzer(
|
|
93
|
+
[
|
|
94
|
+
EventFile(
|
|
95
|
+
output / "failing" / os.listdir(output / "failing")[0],
|
|
96
|
+
0,
|
|
97
|
+
mapping,
|
|
98
|
+
failing=True,
|
|
99
|
+
)
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
EventFile(output / "passing" / path, run_id, mapping)
|
|
103
|
+
for run_id, path in enumerate(os.listdir(output / "passing"), start=1)
|
|
104
|
+
],
|
|
105
|
+
config.factory,
|
|
106
|
+
)
|
|
107
|
+
analyzer.analyze()
|
|
108
|
+
predicates = analyzer.get_analysis_by_type(AnalysisType.LINE)
|
|
109
|
+
suggestions = sorted(map(lambda p: p.get_suggestion(), predicates))
|
|
110
|
+
self.assertEqual(1, suggestions[-1].suspiciousness)
|
|
111
|
+
self.assertEqual(1, len(suggestions[-1].lines))
|
|
112
|
+
self.assertEqual(Location("middle.py", 7), suggestions[-1].lines[0])
|
|
113
|
+
|
|
114
|
+
def test_pytest_3_10_1(self):
|
|
115
|
+
self._runner_test("3.10.1")
|
|
116
|
+
|
|
117
|
+
def test_pytest_5_4_3(self):
|
|
118
|
+
self._runner_test("5.4.3")
|
|
119
|
+
|
|
120
|
+
def test_pytest_7_0_1(self):
|
|
121
|
+
self._runner_test("7.0.1")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|