pdd-cli 0.0.90__py3-none-any.whl → 0.0.121__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.
- pdd/__init__.py +38 -6
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +506 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +537 -0
- pdd/agentic_common.py +533 -770
- pdd/agentic_crash.py +2 -1
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +582 -0
- pdd/agentic_fix.py +118 -3
- pdd/agentic_update.py +27 -9
- pdd/agentic_verify.py +3 -2
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +63 -53
- pdd/auto_include.py +236 -3
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +195 -23
- pdd/cmd_test_main.py +345 -197
- pdd/code_generator.py +4 -2
- pdd/code_generator_main.py +118 -32
- pdd/commands/__init__.py +6 -0
- pdd/commands/analysis.py +113 -48
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +358 -0
- pdd/commands/fix.py +155 -114
- pdd/commands/generate.py +5 -0
- pdd/commands/maintenance.py +3 -2
- pdd/commands/misc.py +8 -0
- pdd/commands/modify.py +225 -163
- pdd/commands/sessions.py +284 -0
- pdd/commands/utility.py +12 -7
- pdd/construct_paths.py +334 -32
- pdd/context_generator_main.py +167 -170
- pdd/continue_generation.py +6 -3
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +44 -7
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +68 -20
- pdd/core/errors.py +4 -0
- pdd/core/remote_session.py +61 -0
- pdd/crash_main.py +219 -23
- pdd/data/llm_model.csv +4 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +208 -34
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +291 -38
- pdd/fix_main.py +208 -6
- pdd/fix_verification_errors_loop.py +235 -26
- pdd/fix_verification_main.py +269 -83
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-CUWd8al1.js +450 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +46 -5
- pdd/generate_test.py +212 -151
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +309 -20
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +7 -5
- pdd/insert_includes.py +2 -1
- pdd/llm_invoke.py +531 -97
- pdd/load_prompt_template.py +15 -34
- pdd/operation_log.py +342 -0
- pdd/path_resolution.py +140 -0
- pdd/postprocess.py +122 -97
- pdd/preprocess.py +68 -12
- pdd/preprocess_main.py +33 -1
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +140 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +2 -2
- pdd/prompts/agentic_update_LLM.prompt +192 -338
- pdd/prompts/auto_include_LLM.prompt +22 -0
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +571 -14
- pdd/prompts/fix_code_module_errors_LLM.prompt +8 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +1 -0
- pdd/prompts/generate_test_LLM.prompt +19 -1
- pdd/prompts/generate_test_from_example_LLM.prompt +366 -0
- pdd/prompts/insert_includes_LLM.prompt +262 -252
- pdd/prompts/prompt_code_diff_LLM.prompt +123 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/remote_session.py +876 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1347 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +217 -0
- pdd/server/token_counter.py +222 -0
- pdd/summarize_directory.py +236 -237
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +329 -47
- pdd/sync_main.py +272 -28
- pdd/sync_orchestration.py +289 -211
- pdd/sync_order.py +304 -0
- pdd/template_expander.py +161 -0
- pdd/templates/architecture/architecture_json.prompt +41 -46
- pdd/trace.py +1 -1
- pdd/track_cost.py +0 -13
- pdd/unfinished_prompt.py +2 -1
- pdd/update_main.py +68 -26
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/METADATA +15 -10
- pdd_cli-0.0.121.dist-info/RECORD +229 -0
- pdd_cli-0.0.90.dist-info/RECORD +0 -153
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.90.dist-info → pdd_cli-0.0.121.dist-info}/top_level.txt +0 -0
pdd/cmd_test_main.py
CHANGED
|
@@ -2,234 +2,382 @@
|
|
|
2
2
|
Main entry point for the 'test' command.
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
6
8
|
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import requests
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
9
14
|
|
|
10
15
|
from .config_resolution import resolve_effective_config
|
|
11
16
|
from .construct_paths import construct_paths
|
|
17
|
+
from .core.cloud import CloudConfig
|
|
12
18
|
from .generate_test import generate_test
|
|
13
19
|
from .increase_tests import increase_tests
|
|
14
20
|
|
|
21
|
+
# Cloud request timeout
|
|
22
|
+
CLOUD_REQUEST_TIMEOUT = 400 # seconds
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
15
26
|
|
|
16
|
-
# pylint: disable=too-many-arguments, too-many-locals, too-many-return-statements, too-many-branches, too-many-statements, broad-except
|
|
17
27
|
def cmd_test_main(
|
|
18
28
|
ctx: click.Context,
|
|
19
29
|
prompt_file: str,
|
|
20
30
|
code_file: str,
|
|
21
|
-
output: str | None,
|
|
22
|
-
language: str | None,
|
|
23
|
-
coverage_report: str | None,
|
|
24
|
-
existing_tests: list[str] | None,
|
|
25
|
-
target_coverage: float | None,
|
|
26
|
-
merge: bool
|
|
31
|
+
output: str | None = None,
|
|
32
|
+
language: str | None = None,
|
|
33
|
+
coverage_report: str | None = None,
|
|
34
|
+
existing_tests: list[str] | None = None,
|
|
35
|
+
target_coverage: float | None = None,
|
|
36
|
+
merge: bool = False,
|
|
27
37
|
strength: float | None = None,
|
|
28
38
|
temperature: float | None = None,
|
|
29
39
|
) -> tuple[str, float, str]:
|
|
30
40
|
"""
|
|
31
41
|
CLI wrapper for generating or enhancing unit tests.
|
|
32
42
|
|
|
33
|
-
Reads a prompt file and a code file, generates unit tests using the `generate_test` function,
|
|
34
|
-
and handles the output location.
|
|
35
|
-
|
|
36
43
|
Args:
|
|
37
|
-
ctx
|
|
38
|
-
prompt_file
|
|
39
|
-
code_file
|
|
40
|
-
output
|
|
41
|
-
language
|
|
42
|
-
coverage_report
|
|
43
|
-
existing_tests
|
|
44
|
-
target_coverage (
|
|
45
|
-
merge
|
|
44
|
+
ctx: Click context object containing global options.
|
|
45
|
+
prompt_file: Path to the prompt file.
|
|
46
|
+
code_file: Path to the code file.
|
|
47
|
+
output: Optional path for the output test file.
|
|
48
|
+
language: Optional programming language.
|
|
49
|
+
coverage_report: Optional path to a coverage report (triggers enhancement mode).
|
|
50
|
+
existing_tests: List of paths to existing test files (required if coverage_report is used).
|
|
51
|
+
target_coverage: Desired coverage percentage (not currently used by logic but accepted).
|
|
52
|
+
merge: If True, merge output into the first existing test file.
|
|
53
|
+
strength: Optional override for LLM strength.
|
|
54
|
+
temperature: Optional override for LLM temperature.
|
|
46
55
|
|
|
47
56
|
Returns:
|
|
48
|
-
tuple
|
|
57
|
+
tuple: (generated_test_code, total_cost, model_name)
|
|
49
58
|
"""
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
print(f"[bold blue]Language:[/bold blue] {language}")
|
|
69
|
-
|
|
70
|
-
# Construct input strings, output file paths, and determine language
|
|
71
|
-
try:
|
|
72
|
-
input_file_paths = {
|
|
73
|
-
"prompt_file": prompt_file,
|
|
74
|
-
"code_file": code_file,
|
|
75
|
-
}
|
|
76
|
-
if coverage_report:
|
|
77
|
-
input_file_paths["coverage_report"] = coverage_report
|
|
59
|
+
# 1. Prepare inputs for path construction
|
|
60
|
+
input_file_paths = {
|
|
61
|
+
"prompt_file": prompt_file,
|
|
62
|
+
"code_file": code_file,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# If coverage report is provided, we need existing tests
|
|
66
|
+
if coverage_report:
|
|
67
|
+
if not existing_tests:
|
|
68
|
+
console.print(
|
|
69
|
+
"[bold red]Error: 'existing_tests' is required when "
|
|
70
|
+
"'coverage_report' is provided.[/bold red]"
|
|
71
|
+
)
|
|
72
|
+
return "", 0.0, "Error: Missing existing_tests"
|
|
73
|
+
|
|
74
|
+
input_file_paths["coverage_report"] = coverage_report
|
|
75
|
+
# We pass the first existing test to help construct_paths resolve context if needed,
|
|
76
|
+
# though construct_paths primarily uses prompt/code files for language detection.
|
|
78
77
|
if existing_tests:
|
|
79
|
-
input_file_paths["
|
|
78
|
+
input_file_paths["existing_test_0"] = existing_tests[0]
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
command_options = {
|
|
81
|
+
"output": output,
|
|
82
|
+
"language": language,
|
|
83
|
+
"merge": merge,
|
|
84
|
+
"target_coverage": target_coverage,
|
|
85
|
+
}
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
# 2. Construct paths and read inputs
|
|
88
|
+
try:
|
|
89
|
+
resolved_config, input_strings, output_file_paths, detected_language = construct_paths(
|
|
89
90
|
input_file_paths=input_file_paths,
|
|
90
|
-
force=ctx.obj["force"],
|
|
91
|
-
quiet=ctx.obj["quiet"],
|
|
92
|
-
command="test",
|
|
93
91
|
command_options=command_options,
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
force=ctx.obj.get("force", False),
|
|
93
|
+
quiet=ctx.obj.get("quiet", False),
|
|
94
|
+
command="test",
|
|
95
|
+
context_override=ctx.obj.get("context"),
|
|
96
|
+
confirm_callback=ctx.obj.get("confirm_callback"),
|
|
96
97
|
)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
console.print(f"[bold red]Error constructing paths: {e}[/bold red]")
|
|
100
|
+
return "", 0.0, f"Error: {e}"
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
|
|
102
|
+
# 3. Resolve effective configuration (strength, temperature, time)
|
|
103
|
+
# Priority: Function Arg > CLI Context > Config File > Default
|
|
104
|
+
eff_config = resolve_effective_config(
|
|
105
|
+
ctx,
|
|
106
|
+
resolved_config,
|
|
107
|
+
param_overrides={"strength": strength, "temperature": temperature}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
eff_strength = eff_config["strength"]
|
|
111
|
+
eff_temperature = eff_config["temperature"]
|
|
112
|
+
eff_time = eff_config["time"]
|
|
113
|
+
verbose = ctx.obj.get("verbose", False)
|
|
114
|
+
is_local = ctx.obj.get("local", False)
|
|
115
|
+
|
|
116
|
+
# 4. Prepare content variables
|
|
117
|
+
prompt_content = input_strings.get("prompt_file", "")
|
|
118
|
+
code_content = input_strings.get("code_file", "")
|
|
119
|
+
|
|
120
|
+
# Handle existing tests concatenation
|
|
121
|
+
concatenated_tests = None
|
|
122
|
+
if existing_tests:
|
|
123
|
+
test_contents = []
|
|
124
|
+
for et_path in existing_tests:
|
|
125
|
+
try:
|
|
126
|
+
# We read these manually because construct_paths only reads
|
|
127
|
+
# what's in input_file_paths keys. While we added
|
|
128
|
+
# existing_test_0, we might have multiple. To be safe and
|
|
129
|
+
# consistent with the requirement "read all files", we read
|
|
130
|
+
# them here. Note: construct_paths might have read
|
|
131
|
+
# 'existing_test_0', but we need all of them.
|
|
132
|
+
p = Path(et_path).expanduser().resolve()
|
|
133
|
+
if p.exists():
|
|
134
|
+
test_contents.append(p.read_text(encoding="utf-8"))
|
|
135
|
+
except Exception as e:
|
|
136
|
+
console.print(
|
|
137
|
+
f"[yellow]Warning: Could not read existing test file {et_path}: {e}[/yellow]"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if test_contents:
|
|
141
|
+
concatenated_tests = "\n".join(test_contents)
|
|
142
|
+
# Update input_strings for consistency if needed downstream
|
|
143
|
+
input_strings["existing_tests"] = concatenated_tests
|
|
144
|
+
|
|
145
|
+
# 5. Determine Execution Mode (Generate vs Increase)
|
|
146
|
+
mode = "increase" if coverage_report else "generate"
|
|
147
|
+
|
|
148
|
+
# Prepare metadata for generation
|
|
149
|
+
source_file_path = str(Path(code_file).expanduser().resolve())
|
|
150
|
+
# output_file_paths['output_file'] is set by construct_paths based on
|
|
151
|
+
# --output or defaults
|
|
152
|
+
test_file_path = str(
|
|
153
|
+
Path(output_file_paths.get("output_file", "test_output.py"))
|
|
154
|
+
.expanduser().resolve()
|
|
155
|
+
)
|
|
156
|
+
module_name = Path(source_file_path).stem
|
|
157
|
+
|
|
158
|
+
# Determine if code file is an example (for TDD style generation)
|
|
159
|
+
is_example = Path(code_file).stem.endswith("_example")
|
|
160
|
+
|
|
161
|
+
# Check for cloud-only mode
|
|
162
|
+
cloud_only = os.environ.get("PDD_CLOUD_ONLY", "").lower() in ("1", "true", "yes")
|
|
163
|
+
|
|
164
|
+
# 6. Execution Logic (Cloud vs Local)
|
|
165
|
+
generated_content = ""
|
|
166
|
+
total_cost = 0.0
|
|
167
|
+
model_name = "unknown"
|
|
168
|
+
|
|
169
|
+
# --- Cloud Execution ---
|
|
170
|
+
if not is_local:
|
|
148
171
|
try:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"
|
|
172
|
-
"
|
|
172
|
+
if verbose:
|
|
173
|
+
console.print(
|
|
174
|
+
Panel(
|
|
175
|
+
f"Attempting Cloud Execution (Mode: {mode})",
|
|
176
|
+
title="Cloud Status",
|
|
177
|
+
style="blue"
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Get JWT Token using CloudConfig
|
|
182
|
+
jwt_token = CloudConfig.get_jwt_token(verbose=verbose)
|
|
183
|
+
|
|
184
|
+
if not jwt_token:
|
|
185
|
+
raise Exception("Failed to obtain JWT token.")
|
|
186
|
+
|
|
187
|
+
# Prepare Payload
|
|
188
|
+
payload = {
|
|
189
|
+
"promptContent": prompt_content,
|
|
190
|
+
"language": detected_language,
|
|
191
|
+
"strength": eff_strength,
|
|
192
|
+
"temperature": eff_temperature,
|
|
193
|
+
"time": eff_time,
|
|
194
|
+
"verbose": verbose,
|
|
195
|
+
"sourceFilePath": source_file_path,
|
|
196
|
+
"testFilePath": test_file_path,
|
|
197
|
+
"moduleName": module_name,
|
|
198
|
+
"mode": mode,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if mode == "generate":
|
|
202
|
+
if is_example:
|
|
203
|
+
payload["example"] = code_content
|
|
204
|
+
payload["codeContent"] = None
|
|
205
|
+
else:
|
|
206
|
+
payload["codeContent"] = code_content
|
|
207
|
+
payload["example"] = None
|
|
208
|
+
|
|
209
|
+
if concatenated_tests:
|
|
210
|
+
payload["existingTests"] = concatenated_tests
|
|
211
|
+
|
|
212
|
+
elif mode == "increase":
|
|
213
|
+
payload["codeContent"] = code_content
|
|
214
|
+
payload["existingTests"] = concatenated_tests
|
|
215
|
+
payload["coverageReport"] = input_strings.get("coverage_report", "")
|
|
216
|
+
|
|
217
|
+
# Make Request
|
|
218
|
+
cloud_url = CloudConfig.get_endpoint_url("generateTest")
|
|
219
|
+
headers = {"Authorization": f"Bearer {jwt_token}"}
|
|
220
|
+
response = requests.post(
|
|
221
|
+
cloud_url,
|
|
222
|
+
json=payload,
|
|
223
|
+
headers=headers,
|
|
224
|
+
timeout=CLOUD_REQUEST_TIMEOUT
|
|
173
225
|
)
|
|
174
|
-
|
|
175
|
-
|
|
226
|
+
|
|
227
|
+
# Check for HTTP errors explicitly
|
|
228
|
+
response.raise_for_status()
|
|
229
|
+
|
|
230
|
+
# Parse response
|
|
231
|
+
try:
|
|
232
|
+
data = response.json()
|
|
233
|
+
except json.JSONDecodeError as json_err:
|
|
234
|
+
if cloud_only:
|
|
235
|
+
raise click.UsageError(f"Cloud returned invalid JSON: {json_err}")
|
|
236
|
+
console.print("[yellow]Cloud returned invalid JSON, falling back to local.[/yellow]")
|
|
237
|
+
is_local = True
|
|
238
|
+
raise # Re-raise to exit try block
|
|
239
|
+
|
|
240
|
+
generated_content = data.get("generatedTest", "")
|
|
241
|
+
total_cost = float(data.get("totalCost", 0.0))
|
|
242
|
+
model_name = data.get("modelName", "cloud-model")
|
|
243
|
+
|
|
244
|
+
# Check for empty response
|
|
245
|
+
if not generated_content or not generated_content.strip():
|
|
246
|
+
if cloud_only:
|
|
247
|
+
raise click.UsageError("Cloud returned empty test content")
|
|
248
|
+
console.print("[yellow]Cloud returned empty test content, falling back to local.[/yellow]")
|
|
249
|
+
is_local = True
|
|
250
|
+
else:
|
|
251
|
+
# Success!
|
|
252
|
+
console.print("[green]Cloud Success[/green]")
|
|
253
|
+
|
|
254
|
+
except click.UsageError:
|
|
255
|
+
# Re-raise UsageError without wrapping
|
|
256
|
+
raise
|
|
257
|
+
except requests.exceptions.Timeout as timeout_err:
|
|
258
|
+
if cloud_only:
|
|
259
|
+
raise click.UsageError(f"Cloud request timed out: {timeout_err}")
|
|
260
|
+
console.print("[yellow]Cloud request timed out, falling back to local.[/yellow]")
|
|
261
|
+
is_local = True
|
|
262
|
+
except requests.exceptions.HTTPError as http_err:
|
|
263
|
+
# Handle HTTP errors from raise_for_status()
|
|
264
|
+
# HTTPError from requests always has a response attribute
|
|
265
|
+
response_obj = getattr(http_err, 'response', None)
|
|
266
|
+
status_code = response_obj.status_code if response_obj is not None else None
|
|
267
|
+
error_text = response_obj.text if response_obj is not None else str(http_err)
|
|
268
|
+
|
|
269
|
+
# Non-recoverable errors - raise UsageError
|
|
270
|
+
if status_code == 402:
|
|
271
|
+
# Display balance info
|
|
272
|
+
try:
|
|
273
|
+
error_data = http_err.response.json()
|
|
274
|
+
balance = error_data.get("currentBalance", "unknown")
|
|
275
|
+
cost = error_data.get("estimatedCost", "unknown")
|
|
276
|
+
console.print(f"[red]Current balance: {balance}, Estimated cost: {cost}[/red]")
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
raise click.UsageError(f"Insufficient credits: {error_text}")
|
|
280
|
+
elif status_code == 401:
|
|
281
|
+
raise click.UsageError(f"Cloud authentication failed: {error_text}")
|
|
282
|
+
elif status_code == 403:
|
|
283
|
+
raise click.UsageError(f"Access denied: {error_text}")
|
|
284
|
+
elif status_code == 400:
|
|
285
|
+
raise click.UsageError(f"Invalid request: {error_text}")
|
|
286
|
+
|
|
287
|
+
# 5xx and other errors - fall back if allowed
|
|
288
|
+
if cloud_only:
|
|
289
|
+
raise click.UsageError(f"Cloud execution failed (HTTP {status_code}): {error_text}")
|
|
290
|
+
console.print(f"[yellow]Cloud execution failed (HTTP {status_code}), falling back to local.[/yellow]")
|
|
291
|
+
is_local = True
|
|
292
|
+
except json.JSONDecodeError:
|
|
293
|
+
# Already handled above, just ensure we fall through to local
|
|
294
|
+
pass
|
|
295
|
+
except Exception as e:
|
|
296
|
+
if cloud_only:
|
|
297
|
+
raise click.UsageError(f"Cloud execution failed: {e}")
|
|
298
|
+
console.print(f"[yellow]Cloud execution failed: {e}. Falling back to local.[/yellow]")
|
|
299
|
+
is_local = True
|
|
300
|
+
|
|
301
|
+
# --- Local Execution ---
|
|
302
|
+
if is_local:
|
|
176
303
|
try:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
304
|
+
if verbose:
|
|
305
|
+
console.print(
|
|
306
|
+
Panel(
|
|
307
|
+
f"Running Local Execution (Mode: {mode})",
|
|
308
|
+
title="Local Status",
|
|
309
|
+
style="green"
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if mode == "generate":
|
|
314
|
+
# Determine args based on is_example
|
|
315
|
+
code_arg = None if is_example else code_content
|
|
316
|
+
example_arg = code_content if is_example else None
|
|
317
|
+
|
|
318
|
+
generated_content, total_cost, model_name = generate_test(
|
|
319
|
+
prompt=prompt_content,
|
|
320
|
+
code=code_arg,
|
|
321
|
+
example=example_arg,
|
|
322
|
+
strength=eff_strength,
|
|
323
|
+
temperature=eff_temperature,
|
|
324
|
+
time=eff_time,
|
|
325
|
+
language=detected_language,
|
|
326
|
+
verbose=verbose,
|
|
327
|
+
source_file_path=source_file_path,
|
|
328
|
+
test_file_path=test_file_path,
|
|
329
|
+
module_name=module_name,
|
|
330
|
+
existing_tests=concatenated_tests
|
|
331
|
+
)
|
|
332
|
+
else: # mode == "increase"
|
|
333
|
+
generated_content, total_cost, model_name = increase_tests(
|
|
334
|
+
existing_unit_tests=concatenated_tests if concatenated_tests else "",
|
|
335
|
+
coverage_report=input_strings.get("coverage_report", ""),
|
|
336
|
+
code=code_content,
|
|
337
|
+
prompt_that_generated_code=prompt_content,
|
|
338
|
+
language=detected_language,
|
|
339
|
+
strength=eff_strength,
|
|
340
|
+
temperature=eff_temperature,
|
|
341
|
+
time=eff_time,
|
|
342
|
+
verbose=verbose
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
console.print(f"[bold red]Error during local execution: {e}[/bold red]")
|
|
347
|
+
return "", 0.0, f"Error: {e}"
|
|
348
|
+
|
|
349
|
+
# 7. Validate Output
|
|
350
|
+
if not generated_content or not generated_content.strip():
|
|
351
|
+
console.print("[bold red]Error: Generated test content is empty.[/bold red]")
|
|
352
|
+
return "", 0.0, "Error: Empty output"
|
|
353
|
+
|
|
354
|
+
# 8. Write Output
|
|
215
355
|
try:
|
|
356
|
+
final_output_path = Path(output_file_paths["output"])
|
|
357
|
+
|
|
216
358
|
# Ensure parent directory exists
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
359
|
+
final_output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
360
|
+
|
|
361
|
+
write_mode = "w"
|
|
362
|
+
content_to_write = generated_content
|
|
363
|
+
|
|
364
|
+
# Handle merge logic
|
|
365
|
+
if merge and existing_tests:
|
|
366
|
+
# If merging, we write to the first existing test file
|
|
367
|
+
final_output_path = Path(existing_tests[0])
|
|
368
|
+
write_mode = "a"
|
|
369
|
+
content_to_write = "\n\n" + generated_content
|
|
370
|
+
if verbose:
|
|
371
|
+
console.print(f"Merging new tests into existing file: {final_output_path}")
|
|
372
|
+
|
|
373
|
+
with open(str(final_output_path), write_mode, encoding="utf-8") as f:
|
|
374
|
+
f.write(content_to_write)
|
|
375
|
+
|
|
376
|
+
if not ctx.obj.get("quiet", False):
|
|
377
|
+
console.print(f"[green]Successfully wrote tests to {final_output_path}[/green]")
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
console.print(f"[bold red]Error writing output file: {e}[/bold red]")
|
|
381
|
+
return "", 0.0, f"Error: {e}"
|
|
382
|
+
|
|
383
|
+
return generated_content, total_cost, model_name
|
pdd/code_generator.py
CHANGED
|
@@ -86,7 +86,8 @@ def code_generator(
|
|
|
86
86
|
temperature=temperature,
|
|
87
87
|
time=time,
|
|
88
88
|
verbose=verbose,
|
|
89
|
-
output_schema=output_schema
|
|
89
|
+
output_schema=output_schema,
|
|
90
|
+
language=language,
|
|
90
91
|
)
|
|
91
92
|
else:
|
|
92
93
|
response = llm_invoke(
|
|
@@ -96,7 +97,8 @@ def code_generator(
|
|
|
96
97
|
temperature=temperature,
|
|
97
98
|
time=time,
|
|
98
99
|
verbose=verbose,
|
|
99
|
-
output_schema=output_schema
|
|
100
|
+
output_schema=output_schema,
|
|
101
|
+
language=language,
|
|
100
102
|
)
|
|
101
103
|
initial_output = response['result']
|
|
102
104
|
total_cost += response['cost']
|