unit-tests-generator 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.

Potentially problematic release.


This version of unit-tests-generator might be problematic. Click here for more details.

@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: unit-tests-generator
3
+ Version: 0.1.0
4
+ Summary: Open source project to build a unit tests generator in Python.
5
+ Author: Arnau Fabregat
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: >=3.13
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: crewai[google-genai]>=1.10.1
17
+ Requires-Dist: litellm>=1.82.1
18
+ Requires-Dist: loguru>=0.7.3
19
+ Requires-Dist: networkx>=3.6.1
20
+ Requires-Dist: pytest>=9.0.2
21
+ Requires-Dist: pytest-cov>=7.0.0
22
+ Requires-Dist: typer>=0.23.1
23
+ Dynamic: license-file
24
+
25
+ # 🛡️ Unit-Tests-Generator
26
+ *Autonomous, RAG-Driven Test Engineering for Python.*
27
+
28
+ **Unit-Tests-Generator** is an intelligent agentic tool that automates the entire `pytest` lifecycle. Unlike static template generators, it leverages an in-memory knowledge graph of your codebase and RAG to produce context-aware, executable, and validated test suites.
29
+
30
+ The system operates through a multi-stage pipeline:
31
+ 1. **Codebase Graphing**: Maps your repository into an in-memory graph to understand cross-file dependencies and type definitions.
32
+
33
+ 2. **Contextual RAG**: Retrieves relevant context for the LLM, ensuring the generated tests understand your project's unique patterns.
34
+
35
+ 3. **Inference & Guardrails**: Generates test cases via LLM API, filtered through strict structural and security guardrails.
36
+
37
+ 4. **The "Sandbox" Validation**: Automatically executes the generated tests.
38
+
39
+ 5. **Persistence**: Only tests that pass execution and meet coverage criteria are committed to your `/tests` directory.
40
+
41
+ ## Quick Start
42
+ ```bash
43
+ # Install the package
44
+ pip install unit-tests-generator
45
+
46
+ # Generate tests using source and output paths
47
+ utgen -s src/ -t tests/
48
+ ```
49
+
50
+ ### Setting up your LLM
51
+ Required environment variables:
52
+ ```bash
53
+ # .env file
54
+ LLM_OPENROUTER_MODEL = ""
55
+ LLM_OPENROUTER_API_KEY = ""
56
+ ```
57
+ - Powered by CrewAI and OpenRouter: https://docs.crewai.com/en/concepts/llms#open-router.
58
+ - OpenRouter models: https://openrouter.ai/models.
59
+
60
+ ## Table of Contents
61
+ 1. [Usage](#usage)
62
+ 2. [Examples](#examples)
63
+ 3. [Graph structure](#graph-structure)
64
+ 4. [Code Quality & Documentation](#code-quality--documentation)
65
+ - [Pre-commit Hooks](#pre-commit-hooks)
66
+ - [Unit Testing](#unit-testing)
67
+ 5. [Virtual Environment](#virtual-environment)
68
+ - [Create a new virtualenv with the project's dependencies](#create-a-new-virtualenv-with-the-projects-dependencies)
69
+ - [Checking if the project's virtual environment is active](#checking-if-the-projects-virtual-environment-is-active)
70
+ - [Updating the project's dependencies](#updating-the-projects-dependencies)
71
+ 6. [LICENSE](#license)
72
+ 7. [TODO](#todo)
73
+
74
+ ## Usage
75
+ `utgen` is designed to be simple and terminal-first. Once installed, you can generate tests by pointing the tool to your source code and specifying an output directory.
76
+
77
+ ### CLI Arguments
78
+ | Flag | Shortcut | Description | Required | Default |
79
+ | :--- | :--- | :--- | :--- | :--- |
80
+ | `--src_path` | `-s` | Path to the directory containing your source code. | **Yes** | — |
81
+ | `--test_path` | `-t` | Path to the directory where generated tests will be saved. | **Yes** | — |
82
+ | `--graph_path` | `-g` | Path to the RAG-Graph data (enables advanced context). | No | `None` |
83
+ | `--help` | — | Show the help message and exit. | No | — |
84
+
85
+ ### Basic Command
86
+ Run a standard generation without a RAG-Graph:
87
+ ```bash
88
+ utgen -s src/ -t tests/
89
+ ```
90
+ ### Full Command
91
+ Enable RAG-Graph context for more accurate, context-aware test generation:
92
+ ```bash
93
+ utgen --src_path src/ --test_path tests/ --graph_path data/
94
+ ```
95
+
96
+ ## Examples
97
+ Upload `repo.graphml` to https://lite.gephi.org/.
98
+
99
+ - This repository:
100
+ - Code coverage:
101
+ - Example for repository https://github.com/ArnauFabregat/probability_estimation
102
+ ![Diagram](https://raw.githubusercontent.com/ArnauFabregat/unit-tests-generator/main/docs/probability-estimation-repo-graph.png)
103
+ - Code coverage:
104
+ - Example for repository
105
+ - Code coverage
106
+
107
+ ## Graph structure
108
+ To extract relevant context, the graph needs:
109
+
110
+ ### 🟢 Nodes for:
111
+ - Files
112
+ - Classes
113
+ - Functions
114
+ - Methods
115
+ - Nested functions
116
+
117
+ Fields:
118
+ - **id**: canonical ID (e.g., file::...::class::Calibrator / ...::method::Calibrator.fit)
119
+ - **type**: "file" | "class" | "method" | "function" | "nested_function"
120
+ - **name**: symbol name ("Calibrator", "fit", etc.)
121
+ - **file**: file path
122
+ - **signature**: normalized function/method signature "def fit(self, probs, y) -> None"
123
+ - **docstring**: (trimmed) docstring
124
+ - **source**: actual source code of the node
125
+
126
+ ### ➡️ Edges for:
127
+ - **defines** (file → symbol, function → nested function)
128
+ - **has_method** (class → method)
129
+ - **calls** (function/method → called symbol)
130
+ - **references** (function/method → referenced symbol)
131
+
132
+ Fields:
133
+ - **src**: source node id
134
+ - **dst**: destination node id
135
+ - **rel**: "defines" | "has_method" | "references" | "calls"
136
+
137
+ ### 🧠 Prompt template
138
+ TBD
139
+
140
+ ## Code Quality & Documentation
141
+ ### Pre-commit Hooks
142
+ ---
143
+ This project uses [pre-commit](https://pre-commit.com/) hooks to enforce code quality standards automatically before each commit. The following hooks are configured:
144
+
145
+ - **Formatting & File Integrity**: `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-toml`
146
+ - **Code Linting & Formatting**: `ruff-check`, `ruff-format`
147
+ - **Type Checking**: `mypy`
148
+
149
+ Pre-commit hooks are automatically installed during virtual environment setup (`uv sync`).
150
+ - To run them for modified (staged) files:
151
+ ```bash
152
+ uv run pre-commit run
153
+ ```
154
+ - To run them for the entire repository:
155
+ ```bash
156
+ uv run pre-commit run --all-files
157
+ ```
158
+
159
+ ### Unit Testing
160
+ ---
161
+ Unit tests ensure code reliability and prevent regressions. Tests are written using pytest and should cover critical functionality.
162
+
163
+ To run all tests:
164
+ ```bash
165
+ uv run pytest
166
+ ```
167
+
168
+ To run tests with coverage:
169
+ ```bash
170
+ uv run pytest --cov
171
+ ```
172
+
173
+ ## Virtual Environment
174
+ ### Create a new virtualenv with the project's dependencies
175
+ ---
176
+ Install the project's virtual environment and set it as your project's Python interpreter.
177
+ This will also install the project's current dependencies.
178
+
179
+ Open a terminal in VSCode, then execute the following commands:
180
+
181
+ 1. Install [UV: Python package and project manager](https://docs.astral.sh/uv/getting-started/installation/):
182
+ * On Mac OSX / Linux: `curl -LsSf https://astral.sh/uv/install.sh | sh`
183
+ * On Windows [In Powershell]: `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"`
184
+
185
+ 2. [optional] To create virtual environment from scratch with `uv`: [Working on projects](https://docs.astral.sh/uv/guides/projects/)
186
+
187
+ 3. If the environment already exists, install the virtual environment. It will be installed in the project's root, under a new directory named `.venv`:
188
+ * `uv sync`
189
+ * `uv sync --group dev --group dashboard --group test` to install all the dependency groups
190
+
191
+ 4. Activate the new virtual environment:
192
+ * On Mac OSX / Linux: `source .venv/bin/activate`
193
+ * On Windows [In Powershell]: `.venv\Scripts\activate`
194
+
195
+ 5. Configure / install pre-commit hooks:
196
+ * [Pre-commit](https://pre-commit.com/) is a tool that helps us keep the repository complying with certain formatting and style standards, using the `hooks` configured in the `.pre-commit-config.yaml` file.
197
+ * Previously installed with `uv sync`.
198
+
199
+ ### Checking if the project's virtual environment is active
200
+ ---
201
+ All commands listed here assume the project's virtual env is active.
202
+
203
+ To ensure so, execute the following command, and ensure it points to: `{project_root}/.venv/bin/python`:
204
+ * On Mac OSX / Linux: `which python`
205
+ * On Windows / Mac OSX / Linux: `python -c "import sys; import os; print(os.path.abspath(sys.executable))"`
206
+
207
+ If not active, execute the following to activate:
208
+ * On Mac OSX / Linux: `source .venv/bin/activate`
209
+ * On Windows [In Powershell]: `.venv\Scripts\activate`
210
+
211
+ Alternatively, you can also run any command using the prefix `uv run` and `uv` will make sure that it uses the virtual env's Python executable.
212
+
213
+ ### Updating the project's dependencies
214
+ ---
215
+ #### Adding new dependencies
216
+ ---
217
+ In order to avoid potential version conflicts, we should use uv's dependency manager to add new libraries additional to the project's current dependenies.
218
+ Open a terminal in VSCode and execute the following commands:
219
+
220
+ * `uv add {dependency}` e.g. `uv add pandas`
221
+
222
+ This command will update the project's files `pyproject.toml` and `uv.lock` automatically, which are the ones ensuring all developers and environments have the exact same dependencies.
223
+
224
+ #### Updating your virtual env with dependencies recently added or removed from the project
225
+ ---
226
+ Open a terminal in VSCode and execute the following command:
227
+ * `uv sync`
228
+
229
+ ## License
230
+ MIT. See [LICENSE](LICENSE) for more information.
231
+
232
+ ## TODO
233
+ - Study the possibility to run the tests in docker isolated environment for safety
234
+ - Add the remaining guardrails: not allow to write files from test_*.py
235
+ - Replace crewai by langgraph
236
+ - Publish to PyPI
@@ -0,0 +1,23 @@
1
+ unit_tests_generator-0.1.0.dist-info/licenses/LICENSE,sha256=cglefN9tjSeVCrm5HTQ-5lczPVR1D3r_IKT_NJ2R2vY,1070
2
+ utgen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ utgen/constants.py,sha256=qz-bCgeXV3ArpW9QvOCVf2k5zuCONBNeHyk7pjQ5Ogw,26
4
+ utgen/logger.py,sha256=Xh1ZxJnwZUbqwcWYqD2gsf6bEaQcaHqq5jbZuQnQmXc,2605
5
+ utgen/main.py,sha256=uYr5Ae9aCEJUPth7ue_EFHKsq3UCzpMA28P3gvoPPos,1518
6
+ utgen/pipeline.py,sha256=3c8nHfQMKWxjBcpoawBGija8Yy47aFVXWUODF4vhkR4,3858
7
+ utgen/validation.py,sha256=pMLcCv8I1U25pIXo_JS1m_X4Ph0zI0KlcCg9EZ4iru8,2828
8
+ utgen/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ utgen/config/params.py,sha256=EacHophIxiT2KzEyAiWynsbecrDjqf3_8-wdoks5C6w,19
10
+ utgen/raggraph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ utgen/raggraph/parser.py,sha256=eCczESMEevtzTJs2Kqev3az6SKhsjszyf2Cc7NJBuY8,8329
12
+ utgen/raggraph/utils.py,sha256=VwXqXMvC7EtZEdOwwJn0wHeQRDPICPX7wWgG2OM8Sjo,5854
13
+ utgen/raggraph/walker.py,sha256=a90EsYdGV8dFiKdpHp7T3cvVlbo7EarQ1X18xExgG8Y,2591
14
+ utgen/test_generation_crew/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ utgen/test_generation_crew/crew.py,sha256=YPtZIplumHMylc4KptBC0ZBUgzW-LZWAfwSJhQwxjqk,2992
16
+ utgen/test_generation_crew/guardrails.py,sha256=D94ZL3ocfJvEYxejCd8LYJOZmQB8wndWUd6EQj8Ube0,3805
17
+ utgen/test_generation_crew/llm_config.py,sha256=rsetwm8PCVljM4sQSF1e1s_KmFssrMSclwn6nMi4odk,749
18
+ utgen/test_generation_crew/schemas.py,sha256=oBK9sIdrPLTMPTMdTRy3HFzKsjlFHf2csFUhcclNGkQ,855
19
+ unit_tests_generator-0.1.0.dist-info/METADATA,sha256=lFMX3jZHMmZ5L9F9XKjXtKhQx7X4krMcATDiKT-EB6c,9196
20
+ unit_tests_generator-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
21
+ unit_tests_generator-0.1.0.dist-info/entry_points.txt,sha256=j0RKTVAFIdePsOTKG8oQF4Mit_bGqa0UySKvaiaK2L0,41
22
+ unit_tests_generator-0.1.0.dist-info/top_level.txt,sha256=WUMK5MkWCv5d6k8BpwWCgiF02sKTDBpb-JeHvFD-Wiw,6
23
+ unit_tests_generator-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ utgen = utgen.main:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ArnauFabregat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ utgen
utgen/__init__.py ADDED
File without changes
File without changes
utgen/config/params.py ADDED
@@ -0,0 +1 @@
1
+ DEBUG_LOGS = False
utgen/constants.py ADDED
@@ -0,0 +1 @@
1
+ GUARDRAIL_MAX_RETRIES = 3
utgen/logger.py ADDED
@@ -0,0 +1,87 @@
1
+ """
2
+ Central logger configuration for the application.
3
+
4
+ This module sets up Loguru-based logging, featuring split streams for
5
+ standard output and errors, log rotation, and the ability to silence
6
+ noisy third-party library logs.
7
+ """
8
+
9
+ import logging
10
+ import sys
11
+
12
+ from loguru import logger
13
+
14
+ from utgen.config.params import DEBUG_LOGS
15
+
16
+ # List of library names (e.g., 'chromadb', 'httpx') to silence
17
+ DEPENDENCIES_WITH_LOGGING: list[str] = []
18
+
19
+ LOG_FORMAT = (
20
+ "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
21
+ "<level>{level: <8}</level> | "
22
+ "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
23
+ "<level>{message}</level>"
24
+ )
25
+
26
+
27
+ def disable_dependency_loggers(dependencies: list[str]) -> None:
28
+ """
29
+ Disables logging for specific third-party dependencies.
30
+
31
+ Prevents external library logs from cluttering the application logs
32
+ by setting their 'disabled' flag to True and stopping propagation.
33
+
34
+ Parameters
35
+ ----------
36
+ dependencies : list[str]
37
+ A list of library names as strings to be disabled.
38
+ """
39
+ for name in dependencies:
40
+ logging.getLogger(name).disabled = True
41
+ logging.getLogger(name).propagate = False
42
+
43
+
44
+ def setup_logger(debug: bool | None = None) -> None:
45
+ """
46
+ Configures and initializes the Loguru logger handlers.
47
+
48
+ Sets up a three-tier logging strategy:
49
+ 1. Standard Output: Handles DEBUG/INFO levels (Severity < 30).
50
+ 2. Standard Error: Handles WARNING and higher (Severity >= 30).
51
+ 3. File: Persistent storage with rotation and compression.
52
+
53
+ Parameters
54
+ ----------
55
+ debug : bool, optional
56
+ If True, the base log level is set to 'DEBUG'. If False, it
57
+ defaults to 'INFO'. If None, it uses the logic defined
58
+ within the function.
59
+ """
60
+ # Remove default Loguru handler
61
+ logger.remove()
62
+
63
+ # Determine the "Floor" level (DEBUG or INFO)
64
+ base_level = "DEBUG" if debug else "INFO"
65
+
66
+ # 1. Console: Standard Output (DEBUG/INFO)
67
+ # Uses a filter to ensure higher severity levels don't duplicate to stdout
68
+ logger.add(
69
+ sys.stdout,
70
+ format=LOG_FORMAT,
71
+ level=base_level,
72
+ filter=lambda r: r["level"].no < 30, # Stop before WARNING
73
+ )
74
+
75
+ # 2. Console: Standard Error (WARNING+)
76
+ # This ensures critical issues are highlighted in the error stream
77
+ logger.add(
78
+ sys.stderr,
79
+ format="\n" + LOG_FORMAT,
80
+ level="WARNING",
81
+ )
82
+
83
+
84
+ # --- INITIALIZATION ---
85
+ # Run once when module is imported to apply settings globally
86
+ disable_dependency_loggers(DEPENDENCIES_WITH_LOGGING)
87
+ setup_logger(debug=DEBUG_LOGS)
utgen/main.py ADDED
@@ -0,0 +1,43 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from utgen.logger import logger
8
+ from utgen.pipeline import pipeline
9
+
10
+ app = typer.Typer(help="UTGen: Unit test generation with RAG-Graph and LLMs")
11
+
12
+
13
+ @app.callback(invoke_without_command=True)
14
+ def run(
15
+ src_path: Annotated[Path, typer.Option("--src_path", "-s", help="Path to source")],
16
+ test_path: Annotated[Path, typer.Option("--test_path", "-t", help="Path to tests")],
17
+ graph_path: Annotated[Path | None, typer.Option("--graph_path", "-g", help="Path to Graph")] = None,
18
+ ) -> None:
19
+ """
20
+ Generate unit tests based on your source code and RAG-Graph.
21
+ """
22
+ logger.info("Initializing utgen...")
23
+ logger.info(f"Source: {src_path} | Tests: {test_path} | Graph: {graph_path}")
24
+
25
+ pipeline(
26
+ source_code_dir=str(src_path),
27
+ tests_output_dir=str(test_path),
28
+ save_graph_path=str(graph_path) if graph_path else "",
29
+ )
30
+
31
+ logger.info("Running coverage report...")
32
+ # We run pytest-cov on the newly generated test directory
33
+ # --cov points to the source code we want to measure
34
+ try:
35
+ subprocess.run(["pytest", str(test_path), f"--cov={src_path}", "--cov-report=term-missing"], check=True)
36
+ except subprocess.CalledProcessError:
37
+ logger.warning("Some tests failed or coverage couldn't be calculated.")
38
+ except FileNotFoundError:
39
+ logger.error("`pytest` not found. Make sure it's installed in your environment.")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ app()
utgen/pipeline.py ADDED
@@ -0,0 +1,84 @@
1
+ import json
2
+ from collections import defaultdict
3
+ from pathlib import Path
4
+
5
+ from utgen.logger import logger
6
+ from utgen.raggraph.utils import get_node_context
7
+ from utgen.raggraph.walker import build_graph_from_directory
8
+ from utgen.test_generation_crew.crew import TestGenerationCrew
9
+ from utgen.validation import save_and_clean_tests, validate_individual_test
10
+
11
+
12
+ def pipeline(source_code_dir: str, tests_output_dir: str, save_graph_path: str = "") -> None:
13
+ """
14
+ Main function to orchestrate the test generation process.
15
+ Args:
16
+ source_code_dir (str): Directory containing the source code to analyze.
17
+ tests_output_dir (str): Directory where generated tests will be saved.
18
+ save_graph_path (str): Path to save the graph representation.
19
+ """
20
+ logger.info("Creating graph from source code...")
21
+ g = build_graph_from_directory(code_path=source_code_dir, save_graph_path=save_graph_path)
22
+ logger.info(f"Graph built with {g.number_of_nodes()} nodes and {g.number_of_edges()} edges.")
23
+
24
+ logger.info("Started test generation process...")
25
+ # Define defaultdict of dicts
26
+ tests_results: defaultdict[str, dict[str, dict]] = defaultdict(dict)
27
+
28
+ # TODO: afegir guardrails que falten
29
+ test_generator = TestGenerationCrew(guardrail_max_retries=5, verbose=False)
30
+
31
+ for node_id, data in list(g.nodes(data=True))[20:30]:
32
+ if data["type"] in ["function", "method"]:
33
+ logger.info(f"Generating tests for node: {node_id}")
34
+ try:
35
+ # Get context
36
+ context = get_node_context(g=g, node_id=node_id)
37
+ inputs = {"graph_context": context}
38
+
39
+ # Generate tests
40
+ response = test_generator.crew().kickoff(inputs=inputs)
41
+
42
+ # Convert string to dictionary
43
+ response_dict = json.loads(response.raw)
44
+
45
+ # Store results
46
+ p = Path(data["file"])
47
+ new_filename = f"test_{p.stem}{p.suffix}"
48
+ save_path = (p.parent / new_filename).as_posix()
49
+ tests_results[save_path][node_id] = response_dict["tests"]
50
+
51
+ except Exception:
52
+ # This catches guardrail retries exceeded, JSON parsing errors, etc.
53
+ logger.error(f"Failed to generate tests for {node_id} after max retries.")
54
+ # Use 'continue' to skip the rest of this iteration and move to the next node
55
+ continue
56
+ logger.info("Test generation process completed.")
57
+
58
+ logger.info("Validating and saving generated tests...")
59
+ for save_path, nodes in tests_results.items():
60
+ accepted_tests: list[tuple[str, str]] = []
61
+ for node_id, tests in nodes.items():
62
+ base_import = (
63
+ "from "
64
+ + node_id.split("::")[0][:-3].replace("/", ".")
65
+ + " import "
66
+ + node_id.split("::")[-1].split(".")[0]
67
+ )
68
+ for name, values in tests.items():
69
+ imports, code = values["imports"], values["code"]
70
+ if base_import not in imports:
71
+ logger.debug(f"Added missing import `{base_import}` for test `{name}`.")
72
+ imports.append(base_import)
73
+ imports = "\n".join(imports)
74
+
75
+ if validate_individual_test(import_code=imports, test_code=code):
76
+ logger.debug(f"Test `{name}` accepted during validation.")
77
+ accepted_tests.append((imports, code))
78
+ else:
79
+ logger.debug(f"Test `{name}` rejected during validation.")
80
+
81
+ logger.debug(f"Saving cleaned tests for `{save_path}`...")
82
+ save_and_clean_tests(valid_tests=accepted_tests, destination=f"{tests_output_dir}/{save_path}")
83
+ logger.info("All tests validated and saved successfully.")
84
+ # TODO: run pytest coverage
File without changes