Skip to content
This repository was archived by the owner on Feb 12, 2025. It is now read-only.

Commit dddb34a

Browse files
committed
Added function error predicate
1 parent 9faac89 commit dddb34a

File tree

4 files changed

+82
-14
lines changed

4 files changed

+82
-14
lines changed

src/sflkit/analysis/predicate.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import enum
22
from abc import ABC
3-
from typing import Tuple, Callable, Optional
3+
from typing import Tuple, Callable, Optional, List, Type
44

55
from sflkitlib.events import EventType
6-
from sflkitlib.events.event import BranchEvent, FunctionExitEvent, DefEvent
6+
from sflkitlib.events.event import (
7+
BranchEvent,
8+
FunctionExitEvent,
9+
DefEvent,
10+
Event,
11+
ConditionEvent,
12+
)
713

814
from sflkit.analysis.analysis_type import AnalysisType, EvaluationResult
915
from sflkit.analysis.spectra import Spectrum
@@ -51,12 +57,14 @@ def finalize(self, passed: list, failed: list):
5157
else:
5258
self.false_relevant_observed()
5359

54-
def hit(self, id_, event, scope_: scope.Scope = None):
55-
super(Predicate, self).hit(id_, event, scope_)
60+
def hit(self, id_, event: Event, scope_: scope.Scope = None):
5661
if id_ not in self.true_hits:
5762
self.true_hits[id_] = 0
58-
if self._evaluate_predicate(scope_):
63+
if id_ not in self.hits:
64+
self.hits[id_] = 0
65+
if self._evaluate_predicate(event, scope_):
5966
self.true_hits[id_] += 1
67+
self.hits[id_] += 1
6068
self.last_evaluation = EvaluationResult.TRUE
6169
else:
6270
self.last_evaluation = EvaluationResult.FALSE
@@ -66,7 +74,7 @@ def get_metric(self, metric: Callable = None):
6674
metric = Predicate.IncreaseTrue
6775
return super().get_metric(metric)
6876

69-
def _evaluate_predicate(self, scope_: scope.Scope):
77+
def _evaluate_predicate(self, event: Event, scope_: scope.Scope):
7078
return False
7179

7280
def true_relevant_observed(self):
@@ -139,7 +147,7 @@ def analysis_type() -> AnalysisType:
139147
def events():
140148
return [EventType.BRANCH]
141149

142-
def hit(self, id_, event, scope_: scope.Scope = None):
150+
def hit(self, id_, event: BranchEvent, scope_: scope.Scope = None):
143151
if id_ not in self.true_hits:
144152
self.true_hits[id_] = 0
145153
if event.then_id == self.then_id:
@@ -206,7 +214,7 @@ def events():
206214
EventType.FUNCTION_ERROR,
207215
]
208216

209-
def _evaluate_predicate(self, scope_: scope.Scope) -> bool:
217+
def _evaluate_predicate(self, event: Event, scope_: scope.Scope) -> bool:
210218
return self._compare(self._get_first(scope_), self._get_second(scope_))
211219

212220
def _compare(self, first, second) -> bool:
@@ -220,7 +228,7 @@ def _get_second(self, scope_: scope.Scope):
220228

221229

222230
class ScalarPair(Comparison):
223-
def __init__(self, event, op: Comp, var: str):
231+
def __init__(self, event: DefEvent, op: Comp, var: str):
224232
super().__init__(event.file, event.line, op)
225233
self.var1 = event.var
226234
self.var2 = var
@@ -240,7 +248,7 @@ def __str__(self):
240248

241249

242250
class VariablePredicate(Comparison):
243-
def __init__(self, event, op: Comp):
251+
def __init__(self, event: DefEvent, op: Comp):
244252
super().__init__(event.file, event.line, op)
245253
self.var = event.var
246254

@@ -376,7 +384,7 @@ def events():
376384
EventType.DEF,
377385
]
378386

379-
def _evaluate_predicate(self, scope_: scope.Scope):
387+
def _evaluate_predicate(self, event: Event, scope_: scope.Scope):
380388
value = scope_.value(self.var)
381389
return isinstance(value, str) and self.predicate(scope_.value(self.var))
382390

@@ -438,7 +446,7 @@ def analysis_type():
438446
def events():
439447
return [EventType.CONDITION]
440448

441-
def hit(self, id_, event, scope_: scope.Scope = None):
449+
def hit(self, id_, event: ConditionEvent, scope_: scope.Scope = None):
442450
super(Predicate, self).hit(id_, event, scope_)
443451
if id_ not in self.true_hits:
444452
self.true_hits[id_] = 0
@@ -450,3 +458,34 @@ def hit(self, id_, event, scope_: scope.Scope = None):
450458

451459
def __str__(self):
452460
return f"{self.analysis_type()}:{self.file}:{self.line}:{self.condition}"
461+
462+
463+
class FunctionErrorPredicate(Predicate):
464+
def __init__(self, file, line, function):
465+
super().__init__(file, line)
466+
self.function = function
467+
468+
@staticmethod
469+
def analysis_type() -> AnalysisType:
470+
return AnalysisType.FUNCTION_ERROR
471+
472+
@staticmethod
473+
def events() -> List[Type]:
474+
return [
475+
EventType.FUNCTION_ENTER,
476+
EventType.FUNCTION_ERROR,
477+
EventType.FUNCTION_EXIT,
478+
]
479+
480+
def _evaluate_predicate(self, event: Event, scope_: scope.Scope):
481+
return event.event_type == EventType.FUNCTION_ERROR
482+
483+
def get_suggestion(self, metric: Callable = None, base_dir: str = ""):
484+
finder = self.function_finder(self.file, self.line, self.function)
485+
return Suggestion(
486+
[Location(self.file, line) for line in finder.get_locations(base_dir)],
487+
self.get_metric(metric),
488+
)
489+
490+
def __str__(self):
491+
return f"{self.analysis_type()}:{self.file}:{self.function}:{self.line}"

tests/test_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ def tearDown(self) -> None:
3434
pass
3535

3636
def test_instrument_analyze(self):
37-
main("-c", self.config_path, "instrument")
37+
main("instrument", "-c", self.config_path)
3838
self.execute_subject([], 0)
39-
main("-c", self.config_path, "analyze", "-o", self.results_path)
39+
main("analyze", "-c", self.config_path, "-o", self.results_path)
4040
with open(self.results_path, "r") as fp:
4141
results = json.load(fp)
4242
self.assertEqual(1, len(results))

tests/test_suggestions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,31 @@ def test_loop_suggestions(self):
307307
self.assertAlmostEqual(0.5, suggestions[0].suspiciousness, delta=self.DELTA)
308308
self.assertEqual(1, len(suggestions[0].lines))
309309
self.assertIn(Location("main.py", 7), suggestions[0].lines)
310+
311+
312+
class SuggestionsFromPredicatesFunctionErrorTest(BaseTest):
313+
@classmethod
314+
def setUpClass(cls):
315+
cls.analyzer = cls.run_analysis(
316+
cls.TEST_ERROR,
317+
"",
318+
"function_error",
319+
relevant=[["-1"]],
320+
irrelevant=[["1"]],
321+
)
322+
cls.original_dir = os.path.join(cls.TEST_RESOURCES, cls.TEST_ERROR)
323+
324+
def test_function_error_suggestions(self):
325+
suggestions = self.analyzer.get_sorted_suggestions(
326+
base_dir=self.original_dir,
327+
type_=AnalysisType.FUNCTION_ERROR,
328+
metric=Spectrum.Tarantula,
329+
)
330+
self.assertAlmostEqual(1, suggestions[0].suspiciousness, delta=self.DELTA)
331+
self.assertEqual(5, len(suggestions[0].lines))
332+
for i in range(4, 9):
333+
self.assertIn(Location("main.py", i), suggestions[0].lines)
334+
self.assertAlmostEqual(0, suggestions[1].suspiciousness, delta=self.DELTA)
335+
self.assertEqual(5, len(suggestions[1].lines))
336+
for i in range(11, 16):
337+
self.assertIn(Location("main.py", i), suggestions[1].lines)

tests/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class BaseTest(unittest.TestCase):
1818
TEST_PATH = "EVENTS_PATH"
1919
PYTHON = "python3.10"
2020
ACCESS = "main.py"
21+
TEST_ERROR = "test_error"
2122
TEST_LINES = "test_lines"
2223
TEST_LEN = "test_len"
2324
TEST_BRANCHES = "test_branches"

0 commit comments

Comments
 (0)