serenecode 0.1.0__py3-none-any.whl

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 (39) hide show
  1. serenecode/__init__.py +281 -0
  2. serenecode/adapters/__init__.py +6 -0
  3. serenecode/adapters/coverage_adapter.py +1173 -0
  4. serenecode/adapters/crosshair_adapter.py +1069 -0
  5. serenecode/adapters/hypothesis_adapter.py +1824 -0
  6. serenecode/adapters/local_fs.py +169 -0
  7. serenecode/adapters/module_loader.py +492 -0
  8. serenecode/adapters/mypy_adapter.py +161 -0
  9. serenecode/checker/__init__.py +6 -0
  10. serenecode/checker/compositional.py +2216 -0
  11. serenecode/checker/coverage.py +186 -0
  12. serenecode/checker/properties.py +154 -0
  13. serenecode/checker/structural.py +1504 -0
  14. serenecode/checker/symbolic.py +178 -0
  15. serenecode/checker/types.py +148 -0
  16. serenecode/cli.py +478 -0
  17. serenecode/config.py +711 -0
  18. serenecode/contracts/__init__.py +6 -0
  19. serenecode/contracts/predicates.py +176 -0
  20. serenecode/core/__init__.py +6 -0
  21. serenecode/core/exceptions.py +38 -0
  22. serenecode/core/pipeline.py +807 -0
  23. serenecode/init.py +307 -0
  24. serenecode/models.py +308 -0
  25. serenecode/ports/__init__.py +6 -0
  26. serenecode/ports/coverage_analyzer.py +124 -0
  27. serenecode/ports/file_system.py +95 -0
  28. serenecode/ports/property_tester.py +69 -0
  29. serenecode/ports/symbolic_checker.py +70 -0
  30. serenecode/ports/type_checker.py +66 -0
  31. serenecode/reporter.py +346 -0
  32. serenecode/source_discovery.py +319 -0
  33. serenecode/templates/__init__.py +5 -0
  34. serenecode/templates/content.py +337 -0
  35. serenecode-0.1.0.dist-info/METADATA +298 -0
  36. serenecode-0.1.0.dist-info/RECORD +39 -0
  37. serenecode-0.1.0.dist-info/WHEEL +4 -0
  38. serenecode-0.1.0.dist-info/entry_points.txt +2 -0
  39. serenecode-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,124 @@
1
+ """Port definition for coverage analysis (Level 3).
2
+
3
+ This module defines the Protocol interface for coverage analysis
4
+ backends using coverage.py. The checker module depends on this
5
+ protocol rather than concrete implementations.
6
+
7
+ This is a ports module — no implementations, only abstract contracts.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Protocol
14
+
15
+ import icontract
16
+
17
+ from serenecode.contracts.predicates import is_non_empty_string
18
+
19
+
20
+ @icontract.invariant(
21
+ lambda self: is_non_empty_string(self.name),
22
+ "dependency name must be non-empty",
23
+ )
24
+ @dataclass(frozen=True)
25
+ class MockDependency:
26
+ """A dependency that must be mocked for a test to reach uncovered code.
27
+
28
+ Represents a call target found via AST analysis in an uncovered path.
29
+ """
30
+
31
+ name: str
32
+ import_module: str
33
+ is_external: bool
34
+ mock_necessary: bool
35
+ reason: str
36
+
37
+
38
+ @icontract.invariant(
39
+ lambda self: is_non_empty_string(self.description),
40
+ "test suggestion description must be non-empty",
41
+ )
42
+ @dataclass(frozen=True)
43
+ class TestSuggestion:
44
+ """A suggested test to cover an uncovered code path.
45
+
46
+ Includes the mock setup code needed and whether each mock
47
+ could be replaced with a real implementation.
48
+ """
49
+
50
+ description: str
51
+ target_lines: tuple[int, ...]
52
+ suggested_test_code: str
53
+ required_mocks: tuple[MockDependency, ...]
54
+ all_mocks_necessary: bool
55
+
56
+
57
+ @icontract.invariant(
58
+ lambda self: is_non_empty_string(self.function_name)
59
+ and is_non_empty_string(self.module_path),
60
+ "finding names must be non-empty",
61
+ )
62
+ @icontract.invariant(
63
+ lambda self: 0.0 <= self.line_coverage_percent <= 100.0,
64
+ "line coverage must be a valid percentage",
65
+ )
66
+ @icontract.invariant(
67
+ lambda self: 0.0 <= self.branch_coverage_percent <= 100.0,
68
+ "branch coverage must be a valid percentage",
69
+ )
70
+ @dataclass(frozen=True)
71
+ class CoverageFinding:
72
+ """A coverage analysis finding for a single function.
73
+
74
+ Contains coverage metrics, uncovered locations, test suggestions
75
+ with mock assessments, and the threshold pass/fail determination.
76
+ """
77
+
78
+ function_name: str
79
+ module_path: str
80
+ line_start: int
81
+ line_end: int
82
+ line_coverage_percent: float
83
+ branch_coverage_percent: float
84
+ uncovered_lines: tuple[int, ...]
85
+ uncovered_branches: tuple[tuple[int, int], ...]
86
+ suggestions: tuple[TestSuggestion, ...]
87
+ meets_threshold: bool
88
+ message: str
89
+
90
+
91
+ # Protocol classes are exempt from @icontract.invariant — see ports/file_system.py.
92
+ class CoverageAnalyzer(Protocol):
93
+ """Port for coverage analysis.
94
+
95
+ Implementations use coverage.py and AST analysis to measure
96
+ test coverage per function and generate test suggestions for
97
+ uncovered paths.
98
+ """
99
+
100
+ @icontract.require(
101
+ lambda module_path: is_non_empty_string(module_path),
102
+ "module_path must be a non-empty string",
103
+ )
104
+ @icontract.ensure(
105
+ lambda result: isinstance(result, list),
106
+ "result must be a list",
107
+ )
108
+ def analyze_module(
109
+ self,
110
+ module_path: str,
111
+ search_paths: tuple[str, ...] = (),
112
+ coverage_threshold: float = 80.0,
113
+ ) -> list[CoverageFinding]:
114
+ """Run coverage analysis on all functions in a module.
115
+
116
+ Args:
117
+ module_path: Importable Python module path to analyze.
118
+ search_paths: sys.path roots needed to import the module.
119
+ coverage_threshold: Minimum coverage percentage to pass.
120
+
121
+ Returns:
122
+ List of coverage findings per function.
123
+ """
124
+ ...
@@ -0,0 +1,95 @@
1
+ """Port definitions for file system operations.
2
+
3
+ This module defines the Protocol interfaces for reading and writing
4
+ files. Core modules depend on these protocols rather than concrete
5
+ file system implementations, enabling dependency injection and
6
+ testability.
7
+
8
+ This is a ports module — no implementations, only abstract contracts.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol
14
+
15
+ import icontract
16
+
17
+
18
+ # Protocol classes are exempt from @icontract.invariant because invariants
19
+ # are not inherited by Protocol implementors — they would only fire on the
20
+ # Protocol itself, which is never instantiated directly.
21
+ class FileReader(Protocol):
22
+ """Port for reading file contents.
23
+
24
+ Implementations must handle encoding and raise domain exceptions
25
+ for file system errors.
26
+ """
27
+
28
+ @icontract.require(lambda path: isinstance(path, str), "path must be a string")
29
+ @icontract.ensure(lambda result: isinstance(result, str), "result must be a string")
30
+ def read_file(self, path: str) -> str:
31
+ """Read a file and return its contents as a UTF-8 string.
32
+
33
+ Args:
34
+ path: Path to the file to read.
35
+
36
+ Returns:
37
+ The full file contents as a string.
38
+ """
39
+ ...
40
+
41
+ @icontract.require(lambda path: isinstance(path, str), "path must be a string")
42
+ @icontract.ensure(lambda result: isinstance(result, bool), "result must be a bool")
43
+ def file_exists(self, path: str) -> bool:
44
+ """Check whether a file exists at the given path.
45
+
46
+ Args:
47
+ path: Path to check.
48
+
49
+ Returns:
50
+ True if a file exists at path.
51
+ """
52
+ ...
53
+
54
+ @icontract.require(lambda directory: isinstance(directory, str), "directory must be a string")
55
+ @icontract.ensure(lambda result: isinstance(result, list), "result must be a list")
56
+ def list_python_files(self, directory: str) -> list[str]:
57
+ """List all Python (.py) files in a directory recursively.
58
+
59
+ Args:
60
+ directory: Root directory to search.
61
+
62
+ Returns:
63
+ List of absolute or relative paths to .py files.
64
+ """
65
+ ...
66
+
67
+
68
+ class FileWriter(Protocol):
69
+ """Port for writing file contents.
70
+
71
+ Implementations must handle encoding and raise domain exceptions
72
+ for file system errors.
73
+ """
74
+
75
+ @icontract.require(lambda path: isinstance(path, str), "path must be a string")
76
+ @icontract.require(lambda content: isinstance(content, str), "content must be a string")
77
+ @icontract.ensure(lambda result: result is None, "result must be None")
78
+ def write_file(self, path: str, content: str) -> None:
79
+ """Write content to a file, creating it if it doesn't exist.
80
+
81
+ Args:
82
+ path: Path to the file to write.
83
+ content: Content to write as UTF-8.
84
+ """
85
+ ...
86
+
87
+ @icontract.require(lambda path: isinstance(path, str), "path must be a string")
88
+ @icontract.ensure(lambda result: result is None, "result must be None")
89
+ def ensure_directory(self, path: str) -> None:
90
+ """Ensure a directory exists, creating it if necessary.
91
+
92
+ Args:
93
+ path: Path to the directory.
94
+ """
95
+ ...
@@ -0,0 +1,69 @@
1
+ """Port definition for property-based testing (Level 3).
2
+
3
+ This module defines the Protocol interface for property-based testing
4
+ backends such as Hypothesis. The checker module depends on this
5
+ protocol rather than concrete implementations.
6
+
7
+ This is a ports module — no implementations, only abstract contracts.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Protocol
14
+
15
+ import icontract
16
+
17
+ from serenecode.contracts.predicates import is_non_empty_string
18
+
19
+
20
+
21
+ @icontract.invariant(
22
+ lambda self: is_non_empty_string(self.function_name) and is_non_empty_string(self.module_path),
23
+ "finding names must be non-empty",
24
+ )
25
+ @dataclass(frozen=True)
26
+ class PropertyFinding:
27
+ """A single finding from property-based testing.
28
+
29
+ Represents either a successful verification or a counterexample
30
+ that violates a postcondition.
31
+ """
32
+
33
+ function_name: str
34
+ module_path: str
35
+ passed: bool
36
+ finding_type: str # "postcondition_violated", "precondition_error", "crash", "verified"
37
+ message: str
38
+ counterexample: dict[str, object] | None = None
39
+ exception_type: str | None = None
40
+ exception_message: str | None = None
41
+
42
+
43
+ # Protocol classes are exempt from @icontract.invariant — see ports/file_system.py.
44
+ class PropertyTester(Protocol):
45
+ """Port for property-based testing.
46
+
47
+ Implementations use Hypothesis (or similar) to generate test inputs
48
+ from function contracts and verify postconditions hold.
49
+ """
50
+
51
+ @icontract.require(lambda module_path: is_non_empty_string(module_path), "module_path must be a non-empty string")
52
+ @icontract.ensure(lambda result: isinstance(result, list), "result must be a list")
53
+ def test_module(
54
+ self,
55
+ module_path: str,
56
+ max_examples: int | None = None,
57
+ search_paths: tuple[str, ...] = (),
58
+ ) -> list[PropertyFinding]:
59
+ """Run property-based tests on all contracted functions in a module.
60
+
61
+ Args:
62
+ module_path: Importable Python module path to test.
63
+ max_examples: Maximum number of test examples per function.
64
+ search_paths: sys.path roots needed to import the module.
65
+
66
+ Returns:
67
+ List of property findings.
68
+ """
69
+ ...
@@ -0,0 +1,70 @@
1
+ """Port definition for symbolic verification (Level 4).
2
+
3
+ This module defines the Protocol interface for symbolic execution
4
+ backends such as CrossHair. The checker module depends on this
5
+ protocol rather than concrete implementations.
6
+
7
+ This is a ports module — no implementations, only abstract contracts.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Protocol
14
+
15
+ import icontract
16
+
17
+ from serenecode.contracts.predicates import is_non_empty_string
18
+
19
+
20
+
21
+ @icontract.invariant(
22
+ lambda self: is_non_empty_string(self.function_name) and is_non_empty_string(self.module_path),
23
+ "finding names must be non-empty",
24
+ )
25
+ @dataclass(frozen=True)
26
+ class SymbolicFinding:
27
+ """A single finding from symbolic verification.
28
+
29
+ Represents one of three outcomes: verified (proven correct),
30
+ counterexample (violation found), or unknown (timeout/unsupported).
31
+ """
32
+
33
+ function_name: str
34
+ module_path: str
35
+ outcome: str # "verified", "counterexample", "timeout", "unsupported", "error"
36
+ message: str
37
+ counterexample: dict[str, object] | None = None
38
+ condition: str | None = None # which postcondition was violated
39
+ duration_seconds: float = 0.0
40
+
41
+
42
+ # Protocol classes are exempt from @icontract.invariant — see ports/file_system.py.
43
+ class SymbolicChecker(Protocol):
44
+ """Port for symbolic verification.
45
+
46
+ Implementations use CrossHair/Z3 (or similar) to symbolically
47
+ execute functions and prove contracts hold for all valid inputs.
48
+ """
49
+
50
+ @icontract.require(lambda module_path: is_non_empty_string(module_path), "module_path must be a non-empty string")
51
+ @icontract.ensure(lambda result: isinstance(result, list), "result must be a list")
52
+ def verify_module(
53
+ self,
54
+ module_path: str,
55
+ per_condition_timeout: int | None = None,
56
+ per_path_timeout: int | None = None,
57
+ search_paths: tuple[str, ...] = (),
58
+ ) -> list[SymbolicFinding]:
59
+ """Run symbolic verification on all contracted functions in a module.
60
+
61
+ Args:
62
+ module_path: Importable Python module path to verify.
63
+ per_condition_timeout: Max seconds per postcondition.
64
+ per_path_timeout: Max seconds per execution path.
65
+ search_paths: sys.path roots needed to import the module.
66
+
67
+ Returns:
68
+ List of symbolic findings.
69
+ """
70
+ ...
@@ -0,0 +1,66 @@
1
+ """Port definition for type checking (Level 2).
2
+
3
+ This module defines the Protocol interface for static type analysis
4
+ backends such as mypy. The checker module depends on this protocol
5
+ rather than concrete implementations.
6
+
7
+ This is a ports module — no implementations, only abstract contracts.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Protocol
14
+
15
+ import icontract
16
+
17
+ from serenecode.contracts.predicates import is_non_empty_string
18
+
19
+
20
+
21
+ @icontract.invariant(
22
+ lambda self: is_non_empty_string(self.file) and self.line >= 0 and self.column >= 0,
23
+ "type issues must reference a file and non-negative position",
24
+ )
25
+ @dataclass(frozen=True)
26
+ class TypeIssue:
27
+ """A single type checking issue reported by a backend like mypy.
28
+
29
+ Represents one error, warning, or note from the type checker.
30
+ """
31
+
32
+ file: str
33
+ line: int
34
+ column: int
35
+ severity: str # "error", "warning", "note"
36
+ message: str
37
+ code: str | None = None # e.g. "arg-type", "return-value"
38
+
39
+
40
+ # Protocol classes are exempt from @icontract.invariant — see ports/file_system.py.
41
+ class TypeChecker(Protocol):
42
+ """Port for static type checking.
43
+
44
+ Implementations run a type checker (e.g. mypy) on source files
45
+ and return structured issue reports.
46
+ """
47
+
48
+ @icontract.require(lambda file_paths: isinstance(file_paths, list), "file_paths must be a list")
49
+ @icontract.ensure(lambda result: isinstance(result, list), "result must be a list")
50
+ def check(
51
+ self,
52
+ file_paths: list[str],
53
+ strict: bool = True,
54
+ search_paths: tuple[str, ...] = (),
55
+ ) -> list[TypeIssue]:
56
+ """Run type checking on the given files.
57
+
58
+ Args:
59
+ file_paths: Paths to Python files to check.
60
+ strict: Whether to use strict mode.
61
+ search_paths: Import roots needed to resolve local modules.
62
+
63
+ Returns:
64
+ List of type issues found.
65
+ """
66
+ ...