sflkit 0.2.16__tar.gz → 0.2.18__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.
Files changed (61) hide show
  1. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18}/PKG-INFO +1 -1
  2. {sflkit-0.2.16 → sflkit-0.2.18}/pyproject.toml +1 -1
  3. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/__init__.py +1 -1
  4. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/analysis_type.py +2 -0
  5. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/analyzer.py +27 -4
  6. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/factory.py +25 -0
  7. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/mapping.py +2 -0
  8. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/predicate.py +51 -12
  9. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/spectra.py +0 -1
  10. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/cli.py +45 -6
  11. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/config.py +25 -6
  12. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/instrumentation/__init__.py +4 -6
  13. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/instrumentation/dir_instrumentation.py +5 -4
  14. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/instrumentation/file_instrumentation.py +5 -4
  15. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/meta.py +1 -1
  16. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/python/factory.py +1 -1
  17. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/mapping.py +19 -7
  18. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/model/event_file.py +7 -1
  19. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/model/model.py +3 -2
  20. {sflkit-0.2.16 → sflkit-0.2.18/src/sflkit.egg-info}/PKG-INFO +1 -1
  21. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18/src/sflkit.egg-info}/SOURCES.txt +1 -5
  22. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_cli.py +2 -2
  23. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_config.py +1 -0
  24. sflkit-0.2.18/tests/test_coverage.py +19 -0
  25. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_instrumentation.py +15 -0
  26. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_suggestions.py +28 -0
  27. {sflkit-0.2.16 → sflkit-0.2.18}/LICENSE +0 -0
  28. {sflkit-0.2.16 → sflkit-0.2.18}/README.md +0 -0
  29. {sflkit-0.2.16 → sflkit-0.2.18}/setup.cfg +0 -0
  30. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/__init__.py +0 -0
  31. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/similarity.py +0 -0
  32. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/analysis/suggestion.py +0 -0
  33. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/color.py +0 -0
  34. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/__init__.py +0 -0
  35. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/extract.py +0 -0
  36. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/finder.py +0 -0
  37. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/language.py +0 -0
  38. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/python/__init__.py +0 -0
  39. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/python/extract.py +0 -0
  40. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/python/finder.py +0 -0
  41. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/python/visitor.py +0 -0
  42. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/language/visitor.py +0 -0
  43. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/logger.py +0 -0
  44. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/model/__init__.py +0 -0
  45. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/model/scope.py +0 -0
  46. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/runners/__init__.py +0 -0
  47. {sflkit-0.2.16 → sflkit-0.2.18}/src/sflkit/runners/run.py +0 -0
  48. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18/src/sflkit.egg-info}/dependency_links.txt +0 -0
  49. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18/src/sflkit.egg-info}/entry_points.txt +0 -0
  50. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18/src/sflkit.egg-info}/requires.txt +0 -0
  51. {sflkit-0.2.16/src/SFLKit.egg-info → sflkit-0.2.18/src/sflkit.egg-info}/top_level.txt +0 -0
  52. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_analysis_objects.py +0 -0
  53. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_color.py +0 -0
  54. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_events.py +0 -0
  55. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_execution.py +0 -0
  56. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_language.py +0 -0
  57. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_predicates.py +0 -0
  58. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_runner.py +0 -0
  59. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_scope.py +0 -0
  60. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_spectra.py +0 -0
  61. {sflkit-0.2.16 → sflkit-0.2.18}/tests/test_visitors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sflkit
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: SFLKit: : A Workbench for Statistical Fault Localization
5
5
  Author-email: Marius Smytzek <marius.smytzek@cispa.de>
6
6
  Project-URL: Homepage, https://github.com/uds-se/sflkit/
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "sflkit"
10
- version = "0.2.16"
10
+ version = "0.2.18"
11
11
  authors = [
12
12
  { name = "Marius Smytzek", email = "marius.smytzek@cispa.de" },
13
13
  ]
@@ -8,7 +8,7 @@ from sflkit.instrumentation.dir_instrumentation import DirInstrumentation
8
8
 
9
9
 
10
10
  def instrument_config(conf: Config):
11
- instrumentation = DirInstrumentation(conf.visitor)
11
+ instrumentation = DirInstrumentation(conf.visitor, conf.mapping.path)
12
12
  instrumentation.instrument(
13
13
  conf.target_path,
14
14
  conf.instrument_working,
@@ -28,6 +28,7 @@ class AnalysisType(enum.Enum):
28
28
  SPECIAL_STRING = 13
29
29
  EMPTY_BYTES = 14
30
30
  LENGTH = 15
31
+ FUNCTION_ERROR = 16
31
32
 
32
33
  def __str__(self):
33
34
  return self.name
@@ -56,6 +57,7 @@ class AnalysisObject(object):
56
57
  def __init__(self, event):
57
58
  self.suspiciousness: float = 0
58
59
  self.last_evaluation: EvaluationResult = EvaluationResult.UNOBSERVED
60
+ self.hits = dict()
59
61
 
60
62
  def get_last_evaluation(self, id_: int) -> EvaluationResult:
61
63
  return self.last_evaluation
@@ -1,6 +1,7 @@
1
- from typing import List, Callable
1
+ import os
2
+ from typing import List, Callable, Set, Dict
2
3
 
3
- from sflkit.analysis.analysis_type import AnalysisType
4
+ from sflkit.analysis.analysis_type import AnalysisType, AnalysisObject
4
5
  from sflkit.analysis.factory import AnalysisFactory
5
6
  from sflkit.analysis.suggestion import Suggestion
6
7
  from sflkit.model.event_file import EventFile
@@ -17,6 +18,7 @@ class Analyzer(object):
17
18
  self.relevant_event_files = relevant_event_files
18
19
  self.irrelevant_event_files = irrelevant_event_files
19
20
  self.model = Model(factory)
21
+ self.paths: Dict[int, os.PathLike] = dict()
20
22
 
21
23
  def _analyze(self, event_file):
22
24
  self.model.prepare(event_file)
@@ -29,16 +31,17 @@ class Analyzer(object):
29
31
 
30
32
  def analyze(self):
31
33
  for event_file in self.relevant_event_files + self.irrelevant_event_files:
34
+ self.paths[event_file.run_id] = event_file.path
32
35
  self._analyze(event_file)
33
36
  self._finalize()
34
37
 
35
38
  def dump(self, path):
36
39
  pass
37
40
 
38
- def get_analysis(self):
41
+ def get_analysis(self) -> Set[AnalysisObject]:
39
42
  return list(self.model.get_analysis())
40
43
 
41
- def get_analysis_by_type(self, type_: AnalysisType):
44
+ def get_analysis_by_type(self, type_: AnalysisType) -> Set[AnalysisObject]:
42
45
  return list(
43
46
  filter(lambda p: p.analysis_type() == type_, self.model.get_analysis())
44
47
  )
@@ -66,3 +69,23 @@ class Analyzer(object):
66
69
  ],
67
70
  reverse=True,
68
71
  )[:]
72
+
73
+ def get_coverage_per_run(
74
+ self, type_: AnalysisType = None
75
+ ) -> Dict[EventFile, Set[AnalysisObject]]:
76
+ if type_:
77
+ objects = self.get_analysis_by_type(type_)
78
+ else:
79
+ objects = self.get_analysis()
80
+ coverage = dict()
81
+ for obj in objects:
82
+ for run_id in obj.hits:
83
+ if run_id not in coverage:
84
+ coverage[run_id] = {obj}
85
+ else:
86
+ coverage[run_id].add(obj)
87
+ return coverage
88
+
89
+ def get_coverage(self, type_: AnalysisType = None) -> Set[AnalysisObject]:
90
+ coverage = self.get_coverage_per_run(type_)
91
+ return set.union(*coverage.values())
@@ -18,6 +18,7 @@ from sflkit.analysis.predicate import (
18
18
  ContainsDigitPredicate,
19
19
  ContainsSpecialPredicate,
20
20
  EmptyBytesPredicate,
21
+ FunctionErrorPredicate,
21
22
  )
22
23
  from sflkit.analysis.spectra import Line, Function, Loop, DefUse, Length
23
24
  from sflkit.model.scope import Scope
@@ -429,6 +430,29 @@ class LengthFactory(AnalysisFactory):
429
430
  return self.objects[key][:]
430
431
 
431
432
 
433
+ class FunctionErrorFactory(AnalysisFactory):
434
+ def __init__(self):
435
+ super().__init__()
436
+ self.function_mapping = dict()
437
+
438
+ def get_analysis(self, event, scope: Scope = None) -> List[AnalysisObject]:
439
+ if event.event_type == EventType.FUNCTION_ENTER:
440
+ self.function_mapping[event.function_id] = event.line
441
+ if event.event_type in (EventType.FUNCTION_ERROR, EventType.FUNCTION_EXIT):
442
+ line = self.function_mapping[event.function_id]
443
+ key = (
444
+ FunctionErrorPredicate.analysis_type(),
445
+ event.file,
446
+ line,
447
+ event.function_id,
448
+ )
449
+ if key not in self.objects:
450
+ self.objects[key] = FunctionErrorPredicate(
451
+ event.file, line, event.function
452
+ )
453
+ return [self.objects[key]]
454
+
455
+
432
456
  analysis_factory_mapping = {
433
457
  AnalysisType.LINE: LineFactory,
434
458
  AnalysisType.BRANCH: BranchFactory,
@@ -446,4 +470,5 @@ analysis_factory_mapping = {
446
470
  AnalysisType.VARIABLE: VariableFactory,
447
471
  AnalysisType.SCALAR_PAIR: ScalarPairFactory,
448
472
  AnalysisType.FUNCTION: FunctionFactory,
473
+ AnalysisType.FUNCTION_ERROR: FunctionErrorFactory,
449
474
  }
@@ -6,6 +6,7 @@ from sflkit.analysis.predicate import (
6
6
  VariablePredicate,
7
7
  ReturnPredicate,
8
8
  NonePredicate,
9
+ FunctionErrorPredicate,
9
10
  )
10
11
  from sflkit.analysis.predicate import (
11
12
  EmptyStringPredicate,
@@ -39,6 +40,7 @@ analysis_mapping[AnalysisType.ASCII_STRING] = IsAsciiPredicate
39
40
  analysis_mapping[AnalysisType.DIGIT_STRING] = ContainsDigitPredicate
40
41
  analysis_mapping[AnalysisType.SPECIAL_STRING] = ContainsSpecialPredicate
41
42
  analysis_mapping[AnalysisType.CONDITION] = Condition
43
+ analysis_mapping[AnalysisType.FUNCTION_ERROR] = FunctionErrorPredicate
42
44
 
43
45
  """
44
46
  If you want to add new spectra or predicates, please register them here and in sdtools/analysis/analysis_type.py
@@ -1,9 +1,15 @@
1
1
  import enum
2
2
  from abc import ABC
3
- from typing import Tuple, Callable, Optional
3
+ from typing import Tuple, Callable, Optional, List, Type
4
4
 
5
5
  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
+ )
7
13
 
8
14
  from sflkit.analysis.analysis_type import AnalysisType, EvaluationResult
9
15
  from sflkit.analysis.spectra import Spectrum
@@ -51,12 +57,14 @@ class Predicate(Spectrum, ABC):
51
57
  else:
52
58
  self.false_relevant_observed()
53
59
 
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):
56
61
  if id_ not in self.true_hits:
57
62
  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_):
59
66
  self.true_hits[id_] += 1
67
+ self.hits[id_] += 1
60
68
  self.last_evaluation = EvaluationResult.TRUE
61
69
  else:
62
70
  self.last_evaluation = EvaluationResult.FALSE
@@ -66,7 +74,7 @@ class Predicate(Spectrum, ABC):
66
74
  metric = Predicate.IncreaseTrue
67
75
  return super().get_metric(metric)
68
76
 
69
- def _evaluate_predicate(self, scope_: scope.Scope):
77
+ def _evaluate_predicate(self, event: Event, scope_: scope.Scope):
70
78
  return False
71
79
 
72
80
  def true_relevant_observed(self):
@@ -139,7 +147,7 @@ class Branch(Predicate):
139
147
  def events():
140
148
  return [EventType.BRANCH]
141
149
 
142
- def hit(self, id_, event, scope_: scope.Scope = None):
150
+ def hit(self, id_, event: BranchEvent, scope_: scope.Scope = None):
143
151
  if id_ not in self.true_hits:
144
152
  self.true_hits[id_] = 0
145
153
  if event.then_id == self.then_id:
@@ -206,7 +214,7 @@ class Comparison(Predicate, ABC):
206
214
  EventType.FUNCTION_ERROR,
207
215
  ]
208
216
 
209
- def _evaluate_predicate(self, scope_: scope.Scope) -> bool:
217
+ def _evaluate_predicate(self, event: Event, scope_: scope.Scope) -> bool:
210
218
  return self._compare(self._get_first(scope_), self._get_second(scope_))
211
219
 
212
220
  def _compare(self, first, second) -> bool:
@@ -220,7 +228,7 @@ class Comparison(Predicate, ABC):
220
228
 
221
229
 
222
230
  class ScalarPair(Comparison):
223
- def __init__(self, event, op: Comp, var: str):
231
+ def __init__(self, event: DefEvent, op: Comp, var: str):
224
232
  super().__init__(event.file, event.line, op)
225
233
  self.var1 = event.var
226
234
  self.var2 = var
@@ -240,7 +248,7 @@ class ScalarPair(Comparison):
240
248
 
241
249
 
242
250
  class VariablePredicate(Comparison):
243
- def __init__(self, event, op: Comp):
251
+ def __init__(self, event: DefEvent, op: Comp):
244
252
  super().__init__(event.file, event.line, op)
245
253
  self.var = event.var
246
254
 
@@ -376,7 +384,7 @@ class FunctionPredicate(Predicate, ABC):
376
384
  EventType.DEF,
377
385
  ]
378
386
 
379
- def _evaluate_predicate(self, scope_: scope.Scope):
387
+ def _evaluate_predicate(self, event: Event, scope_: scope.Scope):
380
388
  value = scope_.value(self.var)
381
389
  return isinstance(value, str) and self.predicate(scope_.value(self.var))
382
390
 
@@ -438,7 +446,7 @@ class Condition(Predicate):
438
446
  def events():
439
447
  return [EventType.CONDITION]
440
448
 
441
- def hit(self, id_, event, scope_: scope.Scope = None):
449
+ def hit(self, id_, event: ConditionEvent, scope_: scope.Scope = None):
442
450
  super(Predicate, self).hit(id_, event, scope_)
443
451
  if id_ not in self.true_hits:
444
452
  self.true_hits[id_] = 0
@@ -450,3 +458,34 @@ class Condition(Predicate):
450
458
 
451
459
  def __str__(self):
452
460
  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}"
@@ -39,7 +39,6 @@ class Spectrum(AnalysisObject, ABC):
39
39
  self.failed = failed_observed + failed_not_observed
40
40
  self.failed_observed = failed_observed
41
41
  self.failed_not_observed = failed_not_observed
42
- self.hits = dict()
43
42
  self.last_evaluation: EvaluationResult = EvaluationResult.FALSE
44
43
 
45
44
  def __str__(self):
@@ -7,11 +7,15 @@ from typing import Any
7
7
 
8
8
  import sflkit
9
9
  from sflkit.analysis.suggestion import Suggestion, Location
10
+ from sflkit.config import hash_identifier
10
11
  from sflkit.logger import LOGGER
12
+ from sflkit.mapping import EventMapping
13
+ from sflkit.model import EventFile
11
14
 
12
15
  INSTRUMENT = "instrument"
13
16
  RUN = "run"
14
17
  ANALYZE = "analyze"
18
+ READ = "read"
15
19
 
16
20
 
17
21
  class ResultEncoder(json.JSONEncoder):
@@ -51,6 +55,14 @@ def main(*args: str, stdout=sys.stdout, stderr=sys.stderr):
51
55
  results = sflkit.analyze(args.config, args.analysis)
52
56
  with open(args.out, "w") as output:
53
57
  json.dump(results, output, cls=ResultEncoder, indent=4)
58
+ elif args.command == READ:
59
+ mapping = EventMapping.load_from_file(hash_identifier(args.target))
60
+ with EventFile(args.event_file, 0, mapping) as event_file:
61
+ for i, event in enumerate(event_file.load()):
62
+ print(f"{i:>5}: {event}")
63
+ if (i + 1) % 10 == 0:
64
+ input("Press Enter to continue... ")
65
+ print("\033[F\033[K", end="")
54
66
 
55
67
 
56
68
  def parse_args(args=None, namespace=None):
@@ -66,9 +78,6 @@ def parse_args(args=None, namespace=None):
66
78
  default=False,
67
79
  help="the debug flag to activate debug information",
68
80
  )
69
- arg_parser.add_argument(
70
- "-c", "--config", dest="config", required=True, help="path to the config file"
71
- )
72
81
  commands = arg_parser.add_subparsers(
73
82
  title="command",
74
83
  description="The framework allows for the execution of various commands.",
@@ -77,12 +86,15 @@ def parse_args(args=None, namespace=None):
77
86
  required=True,
78
87
  )
79
88
 
80
- commands.add_parser(
89
+ instrument_parser = commands.add_parser(
81
90
  INSTRUMENT,
82
91
  description="The instrumentation command instruments a subject to collect "
83
92
  "various predicates during its execution.",
84
93
  help="execute the instrumentation of subject",
85
94
  )
95
+ instrument_parser.add_argument(
96
+ "-c", "--config", dest="config", required=True, help="path to the config file"
97
+ )
86
98
 
87
99
  analyze_parser = commands.add_parser(
88
100
  ANALYZE,
@@ -90,6 +102,9 @@ def parse_args(args=None, namespace=None):
90
102
  "subject to detect the statistically correlating ones.",
91
103
  help="execute the analysis of the collected predicates",
92
104
  )
105
+ analyze_parser.add_argument(
106
+ "-c", "--config", dest="config", required=True, help="path to the config file"
107
+ )
93
108
  analyze_parser.add_argument(
94
109
  "-a",
95
110
  "--analysis",
@@ -106,18 +121,42 @@ def parse_args(args=None, namespace=None):
106
121
  help="The report of the final results, i.e. the suggestions sorted by analysis",
107
122
  )
108
123
 
109
- analyze_parser = commands.add_parser(
124
+ run_parser = commands.add_parser(
110
125
  RUN,
111
126
  description="The run command executes the tests of the subject and creates the event files.",
112
127
  help="execute the tests and collect predicates",
113
128
  )
114
- analyze_parser.add_argument(
129
+ run_parser.add_argument(
130
+ "-c", "--config", dest="config", required=True, help="path to the config file"
131
+ )
132
+ run_parser.add_argument(
115
133
  "-o",
116
134
  "--out",
117
135
  dest="out",
118
136
  default=None,
119
137
  help="The output path of the event files.",
120
138
  )
139
+
140
+ read_parser = commands.add_parser(
141
+ READ,
142
+ description="The read command reads an event file and prints the events.",
143
+ help="read an event file",
144
+ )
145
+ read_parser.add_argument(
146
+ "-e",
147
+ "--events",
148
+ dest="event_file",
149
+ required=True,
150
+ help="The input path of the event file.",
151
+ )
152
+ read_parser.add_argument(
153
+ "-t",
154
+ "--target",
155
+ dest="target",
156
+ required=True,
157
+ help="The path of the target to investigate.",
158
+ )
159
+
121
160
  return arg_parser.parse_args(args=args, namespace=namespace)
122
161
 
123
162
 
@@ -77,6 +77,7 @@ class Config:
77
77
  self.instrument_working = None
78
78
  self.runner = None
79
79
  self.mapping = None
80
+ self.mapping_path = None
80
81
  if path:
81
82
  if isinstance(path, configparser.ConfigParser):
82
83
  config = path
@@ -86,9 +87,11 @@ class Config:
86
87
  try:
87
88
  # target section
88
89
  target = config["target"]
89
- self.target_path = Path(target["path"])
90
- self.language = Language[target["language"].upper()]
91
- self.language.setup()
90
+ if "path" in target:
91
+ self.target_path = Path(target["path"])
92
+ if "language" in target:
93
+ self.language = Language[target["language"].upper()]
94
+ self.language.setup()
92
95
 
93
96
  # events section
94
97
  events = config["events"]
@@ -111,7 +114,7 @@ class Config:
111
114
  self.events = {
112
115
  e for p in self.predicates for e in analysis_mapping[p].events()
113
116
  }
114
- else:
117
+ elif "events" in events:
115
118
  self.factory = CombinationFactory(list())
116
119
  # get the events
117
120
  self.events = list(
@@ -141,10 +144,12 @@ class Config:
141
144
  self.metrics = [Spectrum.Ochiai]
142
145
 
143
146
  run_id_generator = IDGenerator()
147
+ if "mapping" in events:
148
+ self.mapping_path = Path(events["mapping"])
144
149
  try:
145
150
  self.mapping = EventMapping.load(self)
146
151
  except InstrumentationError:
147
- self.mapping = EventMapping()
152
+ self.mapping = EventMapping(path=self.mapping_path)
148
153
  if "passing" in events:
149
154
  self.passing = self.get_event_files(
150
155
  list(csv.reader([events["passing"]]))[0],
@@ -196,6 +201,7 @@ class Config:
196
201
  visitor: ASTVisitor = None,
197
202
  passing: List[EventFile] = None,
198
203
  failing: List[EventFile] = None,
204
+ mapping: EventMapping = None,
199
205
  instrument_include: List[str] = None,
200
206
  instrument_exclude: List[str] = None,
201
207
  instrument_working: str = None,
@@ -218,6 +224,10 @@ class Config:
218
224
  conf.instrument_exclude = instrument_exclude if instrument_exclude else list()
219
225
  conf.instrument_working = instrument_working
220
226
  conf.runner = runner
227
+ if mapping:
228
+ conf.mapping = mapping
229
+ if mapping.path:
230
+ conf.mapping_path = mapping.path
221
231
  return conf
222
232
 
223
233
  @staticmethod
@@ -261,6 +271,7 @@ class Config:
261
271
  metrics=None,
262
272
  passing=None,
263
273
  failing=None,
274
+ mapping_path=None,
264
275
  working=None,
265
276
  include=None,
266
277
  exclude=None,
@@ -286,6 +297,8 @@ class Config:
286
297
  conf["events"]["passing"] = passing
287
298
  if failing:
288
299
  conf["events"]["failing"] = failing
300
+ if mapping_path:
301
+ conf["events"]["mapping"] = mapping_path
289
302
  if working:
290
303
  conf["instrumentation"]["path"] = working
291
304
  if include:
@@ -318,6 +331,8 @@ class Config:
318
331
  conf["events"]["passing"] = ",".join(e.path for e in self.passing)
319
332
  if self.failing:
320
333
  conf["events"]["failing"] = ",".join(e.path for e in self.failing)
334
+ if self.mapping_path:
335
+ conf["events"]["mapping"] = str(self.mapping_path)
321
336
  if self.instrument_working:
322
337
  conf["instrumentation"]["path"] = str(self.instrument_working)
323
338
  if self.instrument_include:
@@ -331,7 +346,11 @@ class Config:
331
346
  conf.write(fp)
332
347
 
333
348
  def identifier(self):
334
- return hashlib.md5(str(self.target_path).encode("utf-8")).hexdigest()
349
+ return hash_identifier(self.target_path)
350
+
351
+
352
+ def hash_identifier(path: os.PathLike):
353
+ return hashlib.md5(str(path).encode("utf-8")).hexdigest()
335
354
 
336
355
 
337
356
  def parse_config(path: str) -> Config:
@@ -1,8 +1,6 @@
1
- import json
2
1
  from abc import abstractmethod
3
- from typing import List
4
-
5
- from sflkitlib.events import event
2
+ from pathlib import Path
3
+ from typing import List, Optional
6
4
 
7
5
  from sflkit import Config
8
6
  from sflkit.language.visitor import ASTVisitor
@@ -10,9 +8,9 @@ from sflkit.mapping import EventMapping
10
8
 
11
9
 
12
10
  class Instrumentation:
13
- def __init__(self, visitor: ASTVisitor):
11
+ def __init__(self, visitor: ASTVisitor, mapping_path: Optional[Path] = None):
14
12
  self.visitor = visitor
15
- self.events = EventMapping()
13
+ self.events = EventMapping(path=mapping_path)
16
14
 
17
15
  @abstractmethod
18
16
  def instrument(
@@ -2,6 +2,7 @@ import os
2
2
  import queue
3
3
  import re
4
4
  import shutil
5
+ from pathlib import Path
5
6
  from typing import List, Optional, Iterable, Tuple
6
7
 
7
8
  from sflkit.instrumentation import Instrumentation
@@ -11,9 +12,9 @@ from sflkit.logger import LOGGER
11
12
 
12
13
 
13
14
  class DirInstrumentation(Instrumentation):
14
- def __init__(self, visitor: ASTVisitor):
15
- super().__init__(visitor)
16
- self.file_instrumentation = FileInstrumentation(visitor)
15
+ def __init__(self, visitor: ASTVisitor, mapping_path: Optional[Path] = None):
16
+ super().__init__(visitor, mapping_path)
17
+ self.file_instrumentation = FileInstrumentation(visitor, mapping_path)
17
18
 
18
19
  @staticmethod
19
20
  def check_included(element: str, includes: Optional[Iterable[str]]):
@@ -103,5 +104,5 @@ class DirInstrumentation(Instrumentation):
103
104
  continue
104
105
  else:
105
106
  self.handle_element(element, file_queue, src, dst, suffixes, check)
106
- self.events = self.file_instrumentation.events
107
+ self.events = self.file_instrumentation.events
107
108
  LOGGER.info(f"I found {len(self.events)} events in {src}.")
@@ -1,4 +1,5 @@
1
- from typing import List
1
+ from pathlib import Path
2
+ from typing import List, Optional
2
3
 
3
4
  from sflkit.instrumentation import Instrumentation
4
5
  from sflkit.language.visitor import ASTVisitor
@@ -6,13 +7,13 @@ from sflkit.mapping import EventMapping
6
7
 
7
8
 
8
9
  class FileInstrumentation(Instrumentation):
9
- def __init__(self, visitor: ASTVisitor):
10
- super().__init__(visitor)
10
+ def __init__(self, visitor: ASTVisitor, mapping_path: Optional[Path] = None):
11
+ super().__init__(visitor, mapping_path)
11
12
 
12
13
  def instrument(
13
14
  self, src: str, dst: str, suffixes: List[str] = None, file: str = ""
14
15
  ):
15
16
  self.visitor.instrument(src, dst, file)
16
17
  self.events = EventMapping(
17
- {event.event_id: event for event in self.visitor.events}
18
+ {event.event_id: event for event in self.visitor.events}, self.events.path
18
19
  )
@@ -61,7 +61,7 @@ class TmpGenerator:
61
61
  self.random = random.randbytes(4).hex()
62
62
 
63
63
  def get_var_name(self):
64
- var = f"sd_tmp_{self.random}_{self._tmp_count}"
64
+ var = f"sk_tmp_{self.random}_{self._tmp_count}"
65
65
  self._tmp_count += 1
66
66
  return var
67
67
 
@@ -521,7 +521,7 @@ class FunctionErrorEventFactory(FunctionEventFactory):
521
521
  function_error_event = FunctionErrorEvent(
522
522
  self.file,
523
523
  node.lineno,
524
- self.get_function_event_id(node, self.event_id_generator),
524
+ self.event_id_generator.get_next_id(),
525
525
  node.name,
526
526
  self.get_function_id(node),
527
527
  )
@@ -13,33 +13,45 @@ class InstrumentationError(RuntimeError):
13
13
 
14
14
 
15
15
  class EventMapping:
16
- def __init__(self, mapping: Dict[int, Event] = None):
16
+ def __init__(
17
+ self, mapping: Dict[int, Event] = None, path: Optional[os.PathLike] = None
18
+ ):
17
19
  self.mapping = mapping or dict()
20
+ self.path = path
18
21
 
19
22
  def get(self, event_id) -> Optional[Event]:
20
23
  return self.mapping.get(event_id, None)
21
24
 
22
25
  @staticmethod
23
- def get_path(identifier: str):
26
+ def get_path(identifier: str) -> Path:
24
27
  return SFLKIT_PATH / f"{identifier}.json"
25
28
 
26
29
  @staticmethod
27
30
  def load(config: Any):
28
31
  if not hasattr(config, "identifier"):
29
32
  raise InstrumentationError(f"Argument does not have an identifier")
30
- file = EventMapping.get_path(config.identifier())
33
+ return EventMapping.load_from_file(
34
+ config.mapping_path or EventMapping.get_path(config.identifier()),
35
+ config.target_path,
36
+ )
37
+
38
+ @staticmethod
39
+ def load_from_file(file: Path, path: os.PathLike):
31
40
  if file.exists():
32
- return EventMapping(load_json(file))
41
+ return EventMapping(load_json(file), file)
33
42
  else:
34
43
  raise InstrumentationError(
35
- f"Cannot find information about instrumentation of {config.identifier()}"
44
+ f"Cannot find information about instrumentation of {path or file}"
36
45
  )
37
46
 
38
47
  def write(self, config):
39
48
  if not hasattr(config, "identifier"):
40
49
  raise InstrumentationError(f"Argument does not have an identifier")
41
- SFLKIT_PATH.mkdir(parents=True, exist_ok=True)
42
- file = self.get_path(config.identifier())
50
+ if self.path:
51
+ file = self.path
52
+ else:
53
+ SFLKIT_PATH.mkdir(parents=True, exist_ok=True)
54
+ file = self.get_path(config.identifier())
43
55
  with file.open("w") as fp:
44
56
  json.dump(list(self.mapping.values()), fp, cls=EventEncoder)
45
57
 
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from pickle import PickleError
2
3
 
3
4
  from sflkitlib.events import event
@@ -7,7 +8,11 @@ from sflkit.mapping import EventMapping
7
8
 
8
9
  class EventFile(object):
9
10
  def __init__(
10
- self, path: str, run_id: int, mapping: EventMapping, failing: bool = False
11
+ self,
12
+ path: os.PathLike,
13
+ run_id: int,
14
+ mapping: EventMapping,
15
+ failing: bool = False,
11
16
  ):
12
17
  self.path = path
13
18
  self.run_id = run_id
@@ -18,6 +23,7 @@ class EventFile(object):
18
23
 
19
24
  def __enter__(self):
20
25
  self._file_pointer = open(self.path, "rb")
26
+ return self
21
27
 
22
28
  def __exit__(self, exc_type, exc_val, exc_tb):
23
29
  self._file_pointer.close()
@@ -1,4 +1,4 @@
1
- from typing import List
1
+ from typing import Set
2
2
 
3
3
  from sflkit.model.scope import Scope
4
4
 
@@ -68,7 +68,8 @@ class Model(object):
68
68
  def exit_scope(self):
69
69
  self.variables = self.variables.exit()
70
70
 
71
- def get_analysis(self) -> List:
71
+ # noinspection PyUnresolvedReferences
72
+ def get_analysis(self) -> Set["AnalysisObject"]:
72
73
  return self.factory.get_all()
73
74
 
74
75
  def finalize(self, passed, failed):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sflkit
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: SFLKit: : A Workbench for Statistical Fault Localization
5
5
  Author-email: Marius Smytzek <marius.smytzek@cispa.de>
6
6
  Project-URL: Homepage, https://github.com/uds-se/sflkit/
@@ -1,11 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- src/SFLKit.egg-info/PKG-INFO
5
- src/SFLKit.egg-info/SOURCES.txt
6
- src/SFLKit.egg-info/dependency_links.txt
7
- src/SFLKit.egg-info/entry_points.txt
8
- src/SFLKit.egg-info/top_level.txt
9
4
  src/sflkit/__init__.py
10
5
  src/sflkit/cli.py
11
6
  src/sflkit/color.py
@@ -51,6 +46,7 @@ tests/test_analysis_objects.py
51
46
  tests/test_cli.py
52
47
  tests/test_color.py
53
48
  tests/test_config.py
49
+ tests/test_coverage.py
54
50
  tests/test_events.py
55
51
  tests/test_execution.py
56
52
  tests/test_instrumentation.py
@@ -34,9 +34,9 @@ class TestCli(BaseTest):
34
34
  pass
35
35
 
36
36
  def test_instrument_analyze(self):
37
- main("-c", self.config_path, "instrument")
37
+ main("instrument", "-c", self.config_path)
38
38
  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)
40
40
  with open(self.results_path, "r") as fp:
41
41
  results = json.load(fp)
42
42
  self.assertEqual(1, len(results))
@@ -93,6 +93,7 @@ class ConfigTests(unittest.TestCase):
93
93
  config.visitor,
94
94
  config.passing,
95
95
  config.failing,
96
+ config.mapping,
96
97
  config.instrument_include,
97
98
  config.instrument_exclude,
98
99
  config.instrument_working,
@@ -0,0 +1,19 @@
1
+ from typing import List
2
+
3
+ from sflkit.analysis.analysis_type import AnalysisType
4
+ from sflkit.analysis.spectra import Line
5
+ from utils import BaseTest
6
+
7
+
8
+ class TestCoverage(BaseTest):
9
+ def test_coverage(self):
10
+ analyzer = self.run_analysis(
11
+ self.TEST_SUGGESTIONS,
12
+ "line",
13
+ "line",
14
+ relevant=[["2", "1", "3"]],
15
+ irrelevant=[["3", "2", "1"], ["3", "1", "2"]],
16
+ )
17
+ coverage: List[Line] = analyzer.get_coverage(AnalysisType.LINE)
18
+ coverage = {line.line for line in coverage}
19
+ self.assertEqual(coverage, {1, 5, 6, 7, 9, 10, 12, 13, 16, 19, 20})
@@ -106,6 +106,21 @@ class TestInstrumentation(BaseTest):
106
106
  )
107
107
  )
108
108
 
109
+ def test_mapping_output(self):
110
+ instrument_config(
111
+ Config.create(
112
+ path=os.path.join(BaseTest.TEST_RESOURCES, "test_instrumentation"),
113
+ language="python",
114
+ events="line",
115
+ predicates="line",
116
+ working=BaseTest.TEST_DIR,
117
+ mapping_path="mapping.json",
118
+ )
119
+ )
120
+ mapping = Path("mapping.json")
121
+ self.assertTrue(mapping.exists())
122
+ self.assertTrue(mapping.is_file())
123
+
109
124
 
110
125
  class TestLib(BaseTest):
111
126
  @classmethod
@@ -307,3 +307,31 @@ class SuggestionsFromConditionsLoopTest(BaseTest):
307
307
  self.assertAlmostEqual(0.5, suggestions[0].suspiciousness, delta=self.DELTA)
308
308
  self.assertEqual(1, len(suggestions[0].lines))
309
309
  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)
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