crackerjack 0.15.7__tar.gz → 0.15.9__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.
- {crackerjack-0.15.7 → crackerjack-0.15.9}/PKG-INFO +5 -4
- {crackerjack-0.15.7 → crackerjack-0.15.9}/README.md +1 -1
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.gitignore +0 -2
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pre-commit-config.yaml +4 -4
- crackerjack-0.15.9/crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack-0.15.9/crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/__main__.py +14 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/crackerjack.py +200 -15
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/pyproject.toml +6 -4
- {crackerjack-0.15.7 → crackerjack-0.15.9}/pyproject.toml +6 -4
- crackerjack-0.15.9/tests/README.md +130 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/test_crackerjack.py +9 -1
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/test_crackerjack_runner.py +5 -6
- {crackerjack-0.15.7 → crackerjack-0.15.9}/LICENSE +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.coverage +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.libcst.codemod.yaml +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pdm.toml +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pytest_cache/.gitignore +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pytest_cache/README.md +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/.gitignore +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/__init__.py +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/__init__.py +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/data/comments_sample.txt +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/data/docstrings_sample.txt +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/data/expected_comments_sample.txt +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/data/init.py +0 -0
- {crackerjack-0.15.7 → crackerjack-0.15.9}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: crackerjack
|
3
|
-
Version: 0.15.
|
3
|
+
Version: 0.15.9
|
4
4
|
Summary: Default template for PDM package
|
5
5
|
Keywords: black,ruff,mypy,creosote,refurb
|
6
6
|
Author-Email: lesleslie <les@wedgwoodwebworks.com>
|
@@ -27,20 +27,21 @@ Requires-Dist: pre-commit>=4.2.0
|
|
27
27
|
Requires-Dist: pytest>=8.3.5
|
28
28
|
Requires-Dist: pydantic>=2.11.3
|
29
29
|
Requires-Dist: pdm-bump>=0.9.12
|
30
|
-
Requires-Dist: pdm>=2.24.
|
31
|
-
Requires-Dist: uv>=0.6.
|
30
|
+
Requires-Dist: pdm>=2.24.1
|
31
|
+
Requires-Dist: uv>=0.6.17
|
32
32
|
Requires-Dist: pytest-cov>=6.1.1
|
33
33
|
Requires-Dist: pytest-mock>=3.14.0
|
34
34
|
Requires-Dist: tomli-w>=1.2.0
|
35
35
|
Requires-Dist: pytest-asyncio>=0.26.0
|
36
36
|
Requires-Dist: rich>=14.0.0
|
37
37
|
Requires-Dist: typer>=0.15.2
|
38
|
+
Requires-Dist: pytest-timeout>=2.3.1
|
38
39
|
Description-Content-Type: text/markdown
|
39
40
|
|
40
41
|
# Crackerjack: Elevate Your Python Development
|
41
42
|
|
42
43
|
[](https://github.com/lesleslie/crackerjack)
|
43
|
-
[](https://www.python.org/downloads/)
|
44
45
|
[](https://github.com/astral-sh/ruff)
|
45
46
|
[](https://github.com/astral-sh/uv)
|
46
47
|
[](https://microsoft.github.io/pyright/)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Crackerjack: Elevate Your Python Development
|
2
2
|
|
3
3
|
[](https://github.com/lesleslie/crackerjack)
|
4
|
-
[](https://www.python.org/downloads/)
|
5
5
|
[](https://github.com/astral-sh/ruff)
|
6
6
|
[](https://github.com/astral-sh/uv)
|
7
7
|
[](https://microsoft.github.io/pyright/)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
repos:
|
2
2
|
- repo: https://github.com/pdm-project/pdm
|
3
|
-
rev: 2.24.
|
3
|
+
rev: 2.24.1 # a PDM release exposing the hook
|
4
4
|
hooks:
|
5
5
|
- id: pdm-lock-check
|
6
6
|
# - id: pdm-export
|
@@ -23,7 +23,7 @@ repos:
|
|
23
23
|
- id: check-added-large-files
|
24
24
|
name: check-added-large-files
|
25
25
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
26
|
-
rev: v0.11.
|
26
|
+
rev: v0.11.7
|
27
27
|
hooks:
|
28
28
|
- id: ruff-format
|
29
29
|
- id: ruff
|
@@ -71,11 +71,11 @@ repos:
|
|
71
71
|
- id: bandit
|
72
72
|
args: ["-c", "pyproject.toml"]
|
73
73
|
- repo: https://github.com/RobertCraigie/pyright-python
|
74
|
-
rev: v1.1.
|
74
|
+
rev: v1.1.400
|
75
75
|
hooks:
|
76
76
|
- id: pyright
|
77
77
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
78
|
-
rev: v0.11.
|
78
|
+
rev: v0.11.7
|
79
79
|
hooks:
|
80
80
|
- id: ruff
|
81
81
|
- id: ruff-format
|
Binary file
|
Binary file
|
@@ -32,6 +32,7 @@ class Options(BaseModel):
|
|
32
32
|
clean: bool = False
|
33
33
|
test: bool = False
|
34
34
|
all: BumpOption | None = None
|
35
|
+
ai_agent: bool = False
|
35
36
|
|
36
37
|
@classmethod
|
37
38
|
@field_validator("publish", "bump", mode="before")
|
@@ -88,6 +89,12 @@ cli_options = {
|
|
88
89
|
help="Run with `-x -t -p <micro|minor|major> -c` development options).",
|
89
90
|
case_sensitive=False,
|
90
91
|
),
|
92
|
+
"ai_agent": typer.Option(
|
93
|
+
False,
|
94
|
+
"--ai-agent",
|
95
|
+
help="Enable AI agent mode with structured output.",
|
96
|
+
hidden=True,
|
97
|
+
),
|
91
98
|
}
|
92
99
|
|
93
100
|
|
@@ -104,6 +111,7 @@ def main(
|
|
104
111
|
bump: BumpOption | None = cli_options["bump"],
|
105
112
|
clean: bool = cli_options["clean"],
|
106
113
|
test: bool = cli_options["test"],
|
114
|
+
ai_agent: bool = cli_options["ai_agent"],
|
107
115
|
) -> None:
|
108
116
|
options = Options(
|
109
117
|
commit=commit,
|
@@ -117,8 +125,14 @@ def main(
|
|
117
125
|
clean=clean,
|
118
126
|
test=test,
|
119
127
|
all=all,
|
128
|
+
ai_agent=ai_agent,
|
120
129
|
)
|
121
130
|
|
131
|
+
if ai_agent:
|
132
|
+
import os
|
133
|
+
|
134
|
+
os.environ["AI_AGENT"] = "1"
|
135
|
+
|
122
136
|
runner = create_crackerjack_runner(console=console)
|
123
137
|
runner.process(options)
|
124
138
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import io
|
2
|
+
import os
|
2
3
|
import platform
|
3
4
|
import re
|
4
5
|
import subprocess
|
6
|
+
import time
|
5
7
|
import tokenize
|
6
8
|
import typing as t
|
7
9
|
from contextlib import suppress
|
@@ -40,6 +42,7 @@ class OptionsProtocol(t.Protocol):
|
|
40
42
|
publish: t.Any | None
|
41
43
|
bump: t.Any | None
|
42
44
|
all: t.Any | None
|
45
|
+
ai_agent: bool = False
|
43
46
|
|
44
47
|
|
45
48
|
@dataclass
|
@@ -53,7 +56,7 @@ class CodeCleaner:
|
|
53
56
|
if not str(file_path.parent).startswith("__"):
|
54
57
|
self.clean_file(file_path)
|
55
58
|
if pkg_dir.parent.joinpath("__pycache__").exists():
|
56
|
-
pkg_dir.parent.joinpath("__pycache__").
|
59
|
+
pkg_dir.parent.joinpath("__pycache__").unlink()
|
57
60
|
|
58
61
|
def clean_file(self, file_path: Path) -> None:
|
59
62
|
try:
|
@@ -486,22 +489,161 @@ class Crackerjack:
|
|
486
489
|
self.console.print("\nCleaning tests directory...\n")
|
487
490
|
self.code_cleaner.clean_files(tests_dir)
|
488
491
|
|
489
|
-
def
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
492
|
+
def _prepare_pytest_command(self, options: OptionsProtocol) -> list[str]:
|
493
|
+
"""Prepare the pytest command with appropriate options."""
|
494
|
+
test = ["pytest"]
|
495
|
+
if options.verbose:
|
496
|
+
test.append("-v")
|
497
|
+
|
498
|
+
# Add optimized options for all packages to prevent hanging
|
499
|
+
test.extend(
|
500
|
+
[
|
501
|
+
"--no-cov", # Disable coverage which can cause hanging
|
502
|
+
"--capture=fd", # Capture stdout/stderr at file descriptor level
|
503
|
+
"--tb=short", # Shorter traceback format
|
504
|
+
"--no-header", # Reduce output noise
|
505
|
+
"--disable-warnings", # Disable warning capture
|
506
|
+
"--durations=0", # Show slowest tests to identify potential hanging tests
|
507
|
+
"--timeout=300", # 5-minute timeout for tests
|
508
|
+
]
|
509
|
+
)
|
510
|
+
return test
|
511
|
+
|
512
|
+
def _setup_test_environment(self) -> None:
|
513
|
+
"""Set environment variables for test execution."""
|
514
|
+
# Set environment variables to improve asyncio behavior
|
515
|
+
os.environ["PYTHONASYNCIO_DEBUG"] = "0" # Disable asyncio debug mode
|
516
|
+
os.environ["RUNNING_UNDER_CRACKERJACK"] = "1" # Signal to conftest.py
|
517
|
+
|
518
|
+
# Set asyncio mode to strict to help prevent hanging
|
519
|
+
if "PYTEST_ASYNCIO_MODE" not in os.environ:
|
520
|
+
os.environ["PYTEST_ASYNCIO_MODE"] = "strict"
|
521
|
+
|
522
|
+
def _run_pytest_process(
|
523
|
+
self, test_command: list[str]
|
524
|
+
) -> subprocess.CompletedProcess[str]:
|
525
|
+
"""Run pytest as a subprocess with timeout handling and output capture."""
|
526
|
+
try:
|
527
|
+
# Use a timeout to ensure the process doesn't hang indefinitely
|
528
|
+
process = subprocess.Popen(
|
529
|
+
test_command,
|
530
|
+
stdout=subprocess.PIPE,
|
531
|
+
stderr=subprocess.PIPE,
|
532
|
+
text=True,
|
533
|
+
bufsize=1,
|
534
|
+
universal_newlines=True,
|
535
|
+
)
|
536
|
+
|
537
|
+
# Set a timeout (5 minutes)
|
538
|
+
timeout = 300
|
539
|
+
start_time = time.time()
|
540
|
+
|
541
|
+
stdout_data = []
|
542
|
+
stderr_data = []
|
543
|
+
|
544
|
+
# Read output while process is running, with timeout
|
545
|
+
while process.poll() is None:
|
546
|
+
if time.time() - start_time > timeout:
|
547
|
+
self.console.print(
|
548
|
+
"[red]Test execution timed out after 5 minutes. Terminating...[/red]"
|
549
|
+
)
|
550
|
+
process.terminate()
|
551
|
+
try:
|
552
|
+
process.wait(timeout=5)
|
553
|
+
except subprocess.TimeoutExpired:
|
554
|
+
process.kill()
|
555
|
+
break
|
556
|
+
|
557
|
+
# Read output without blocking
|
558
|
+
if process.stdout:
|
559
|
+
line = process.stdout.readline()
|
560
|
+
if line:
|
561
|
+
stdout_data.append(line)
|
562
|
+
self.console.print(line, end="")
|
563
|
+
|
564
|
+
if process.stderr:
|
565
|
+
line = process.stderr.readline()
|
566
|
+
if line:
|
567
|
+
stderr_data.append(line)
|
568
|
+
self.console.print(f"[red]{line}[/red]", end="")
|
569
|
+
|
570
|
+
time.sleep(0.1)
|
571
|
+
|
572
|
+
# Get any remaining output
|
573
|
+
if process.stdout:
|
574
|
+
for line in process.stdout:
|
575
|
+
stdout_data.append(line)
|
576
|
+
self.console.print(line, end="")
|
577
|
+
|
578
|
+
if process.stderr:
|
579
|
+
for line in process.stderr:
|
580
|
+
stderr_data.append(line)
|
581
|
+
self.console.print(f"[red]{line}[/red]", end="")
|
582
|
+
|
583
|
+
returncode = process.returncode or 0
|
584
|
+
stdout = "".join(stdout_data)
|
585
|
+
stderr = "".join(stderr_data)
|
586
|
+
|
587
|
+
# Create a CompletedProcess object to match the expected interface
|
588
|
+
return subprocess.CompletedProcess(
|
589
|
+
args=test_command, returncode=returncode, stdout=stdout, stderr=stderr
|
590
|
+
)
|
591
|
+
|
592
|
+
except Exception as e:
|
593
|
+
self.console.print(f"[red]Error running tests: {e}[/red]")
|
594
|
+
return subprocess.CompletedProcess(test_command, 1, "", str(e))
|
595
|
+
|
596
|
+
def _report_test_results(
|
597
|
+
self, result: subprocess.CompletedProcess[str], ai_agent: str
|
598
|
+
) -> None:
|
599
|
+
"""Report test results and handle AI agent output if needed."""
|
600
|
+
if result.returncode > 0:
|
601
|
+
if result.stderr:
|
602
|
+
self.console.print(result.stderr)
|
603
|
+
|
604
|
+
if ai_agent:
|
605
|
+
# Use structured output for AI agents
|
606
|
+
self.console.print(
|
607
|
+
'[json]{"status": "failed", "action": "tests", "returncode": '
|
608
|
+
+ str(result.returncode)
|
609
|
+
+ "}[/json]"
|
610
|
+
)
|
611
|
+
else:
|
501
612
|
self.console.print("\n\n❌ Tests failed. Please fix errors.\n")
|
502
|
-
|
613
|
+
raise SystemExit(1)
|
614
|
+
|
615
|
+
if ai_agent:
|
616
|
+
# Use structured output for AI agents
|
617
|
+
self.console.print('[json]{"status": "success", "action": "tests"}[/json]')
|
618
|
+
else:
|
503
619
|
self.console.print("\n\n✅ Tests passed successfully!\n")
|
504
620
|
|
621
|
+
def _run_tests(self, options: OptionsProtocol) -> None:
|
622
|
+
"""Run tests if the test option is enabled."""
|
623
|
+
if options.test:
|
624
|
+
# Check if we're being called by an AI agent
|
625
|
+
ai_agent = os.environ.get("AI_AGENT", "")
|
626
|
+
|
627
|
+
if ai_agent:
|
628
|
+
# Use structured output for AI agents
|
629
|
+
self.console.print(
|
630
|
+
'[json]{"status": "running", "action": "tests"}[/json]'
|
631
|
+
)
|
632
|
+
else:
|
633
|
+
self.console.print("\n\nRunning tests...\n")
|
634
|
+
|
635
|
+
# Prepare the test command
|
636
|
+
test_command = self._prepare_pytest_command(options)
|
637
|
+
|
638
|
+
# Set up the test environment
|
639
|
+
self._setup_test_environment()
|
640
|
+
|
641
|
+
# Run the tests
|
642
|
+
result = self._run_pytest_process(test_command)
|
643
|
+
|
644
|
+
# Report the results
|
645
|
+
self._report_test_results(result, ai_agent)
|
646
|
+
|
505
647
|
def _bump_version(self, options: OptionsProtocol) -> None:
|
506
648
|
for option in (options.publish, options.bump):
|
507
649
|
if option:
|
@@ -546,22 +688,65 @@ class Crackerjack:
|
|
546
688
|
return execute(cmd, **kwargs)
|
547
689
|
|
548
690
|
def process(self, options: OptionsProtocol) -> None:
|
691
|
+
# Track actions performed for AI agent output
|
692
|
+
actions_performed = []
|
693
|
+
|
549
694
|
if options.all:
|
550
695
|
options.clean = True
|
551
696
|
options.test = True
|
552
697
|
options.publish = options.all
|
553
698
|
options.commit = True
|
699
|
+
|
554
700
|
self._setup_package()
|
701
|
+
actions_performed.append("setup_package")
|
702
|
+
|
555
703
|
self._update_project(options)
|
704
|
+
actions_performed.append("update_project")
|
705
|
+
|
556
706
|
self._update_precommit(options)
|
707
|
+
if options.update_precommit:
|
708
|
+
actions_performed.append("update_precommit")
|
709
|
+
|
557
710
|
self._run_interactive_hooks(options)
|
711
|
+
if options.interactive:
|
712
|
+
actions_performed.append("run_interactive_hooks")
|
713
|
+
|
558
714
|
self._clean_project(options)
|
715
|
+
if options.clean:
|
716
|
+
actions_performed.append("clean_project")
|
717
|
+
|
559
718
|
self.project_manager.run_pre_commit()
|
719
|
+
actions_performed.append("run_pre_commit")
|
720
|
+
|
560
721
|
self._run_tests(options)
|
722
|
+
if options.test:
|
723
|
+
actions_performed.append("run_tests")
|
724
|
+
|
561
725
|
self._bump_version(options)
|
726
|
+
if options.bump or options.publish:
|
727
|
+
actions_performed.append("bump_version")
|
728
|
+
|
562
729
|
self._publish_project(options)
|
730
|
+
if options.publish:
|
731
|
+
actions_performed.append("publish_project")
|
732
|
+
|
563
733
|
self._commit_and_push(options)
|
564
|
-
|
734
|
+
if options.commit:
|
735
|
+
actions_performed.append("commit_and_push")
|
736
|
+
|
737
|
+
# Check if we're being called by an AI agent
|
738
|
+
if getattr(options, "ai_agent", False):
|
739
|
+
# Use structured output for AI agents
|
740
|
+
import json
|
741
|
+
|
742
|
+
result = {
|
743
|
+
"status": "complete",
|
744
|
+
"package": self.pkg_name,
|
745
|
+
"actions": actions_performed,
|
746
|
+
}
|
747
|
+
self.console.print(f"[json]{json.dumps(result)}[/json]")
|
748
|
+
else:
|
749
|
+
self.console.print("\n🍺 Crackerjack complete!\n")
|
565
750
|
|
566
751
|
|
567
752
|
def create_crackerjack_runner(
|
@@ -72,7 +72,7 @@ no-lines-before = [
|
|
72
72
|
]
|
73
73
|
|
74
74
|
[tool.ruff.lint.mccabe]
|
75
|
-
max-complexity =
|
75
|
+
max-complexity = 13
|
76
76
|
|
77
77
|
[tool.ruff.lint.pydocstyle]
|
78
78
|
convention = "google"
|
@@ -101,6 +101,7 @@ exclude-deps = [
|
|
101
101
|
"uv",
|
102
102
|
"tomli-w",
|
103
103
|
"google-crc32c",
|
104
|
+
"pytest-timeout",
|
104
105
|
]
|
105
106
|
|
106
107
|
[tool.refurb]
|
@@ -149,7 +150,7 @@ pythonPlatform = "Darwin"
|
|
149
150
|
|
150
151
|
[project]
|
151
152
|
name = "crackerjack"
|
152
|
-
version = "0.15.
|
153
|
+
version = "0.15.8"
|
153
154
|
description = "Default template for PDM package"
|
154
155
|
requires-python = ">=3.13"
|
155
156
|
readme = "README.md"
|
@@ -180,14 +181,15 @@ dependencies = [
|
|
180
181
|
"pytest>=8.3.5",
|
181
182
|
"pydantic>=2.11.3",
|
182
183
|
"pdm-bump>=0.9.12",
|
183
|
-
"pdm>=2.24.
|
184
|
-
"uv>=0.6.
|
184
|
+
"pdm>=2.24.1",
|
185
|
+
"uv>=0.6.17",
|
185
186
|
"pytest-cov>=6.1.1",
|
186
187
|
"pytest-mock>=3.14.0",
|
187
188
|
"tomli-w>=1.2.0",
|
188
189
|
"pytest-asyncio>=0.26.0",
|
189
190
|
"rich>=14.0.0",
|
190
191
|
"typer>=0.15.2",
|
192
|
+
"pytest-timeout>=2.3.1",
|
191
193
|
]
|
192
194
|
authors = [
|
193
195
|
{ name = "lesleslie", email = "les@wedgwoodwebworks.com" },
|
@@ -87,7 +87,7 @@ no-lines-before = [
|
|
87
87
|
]
|
88
88
|
|
89
89
|
[tool.ruff.lint.mccabe]
|
90
|
-
max-complexity =
|
90
|
+
max-complexity = 13
|
91
91
|
|
92
92
|
[tool.ruff.lint.pydocstyle]
|
93
93
|
convention = "google"
|
@@ -120,6 +120,7 @@ exclude-deps = [
|
|
120
120
|
"uv",
|
121
121
|
"tomli-w",
|
122
122
|
"google-crc32c",
|
123
|
+
"pytest-timeout",
|
123
124
|
]
|
124
125
|
|
125
126
|
[tool.refurb]
|
@@ -168,7 +169,7 @@ pythonPlatform = "Darwin"
|
|
168
169
|
|
169
170
|
[project]
|
170
171
|
name = "crackerjack"
|
171
|
-
version = "0.15.
|
172
|
+
version = "0.15.9"
|
172
173
|
description = "Default template for PDM package"
|
173
174
|
requires-python = ">=3.13"
|
174
175
|
readme = "README.md"
|
@@ -199,14 +200,15 @@ dependencies = [
|
|
199
200
|
"pytest>=8.3.5",
|
200
201
|
"pydantic>=2.11.3",
|
201
202
|
"pdm-bump>=0.9.12",
|
202
|
-
"pdm>=2.24.
|
203
|
-
"uv>=0.6.
|
203
|
+
"pdm>=2.24.1",
|
204
|
+
"uv>=0.6.17",
|
204
205
|
"pytest-cov>=6.1.1",
|
205
206
|
"pytest-mock>=3.14.0",
|
206
207
|
"tomli-w>=1.2.0",
|
207
208
|
"pytest-asyncio>=0.26.0",
|
208
209
|
"rich>=14.0.0",
|
209
210
|
"typer>=0.15.2",
|
211
|
+
"pytest-timeout>=2.3.1",
|
210
212
|
]
|
211
213
|
authors = [
|
212
214
|
{ name = "lesleslie", email = "les@wedgwoodwebworks.com" },
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Testing with Crackerjack
|
2
|
+
|
3
|
+
This document provides information about running tests with Crackerjack.
|
4
|
+
|
5
|
+
## Optimized Test Setup
|
6
|
+
|
7
|
+
Crackerjack uses an optimized test setup for all packages to prevent hanging issues. This setup:
|
8
|
+
|
9
|
+
1. Avoids aggressive process killing that could cause issues with test reporting and cleanup
|
10
|
+
2. Uses a more targeted approach to cleaning up asyncio tasks
|
11
|
+
3. Reduces excessive mocking that could cause stability issues
|
12
|
+
4. Sets appropriate environment variables to control asyncio behavior
|
13
|
+
|
14
|
+
## Running Tests
|
15
|
+
|
16
|
+
To run tests with the optimized setup, use:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
crackerjack --test
|
20
|
+
```
|
21
|
+
|
22
|
+
This command applies the same optimized settings to all packages, ensuring consistent behavior and preventing hanging issues.
|
23
|
+
|
24
|
+
## Test Architecture
|
25
|
+
|
26
|
+
Crackerjack uses a modular approach to test execution, broken down into several specialized components:
|
27
|
+
|
28
|
+
1. **Test Command Preparation** - Builds the pytest command with appropriate options
|
29
|
+
2. **Environment Setup** - Configures environment variables for optimal test execution
|
30
|
+
3. **Process Execution** - Manages the subprocess with timeout handling and output streaming
|
31
|
+
4. **Results Reporting** - Processes and displays test results with appropriate formatting
|
32
|
+
|
33
|
+
This modular design improves maintainability and makes the testing process more robust.
|
34
|
+
|
35
|
+
### Implementation Details
|
36
|
+
|
37
|
+
The test execution is implemented through several specialized methods:
|
38
|
+
|
39
|
+
- `_prepare_pytest_command`: Constructs the pytest command with all necessary options based on user preferences
|
40
|
+
- `_setup_test_environment`: Sets environment variables to optimize test execution and prevent hanging
|
41
|
+
- `_run_pytest_process`: Manages the subprocess execution with real-time output streaming and timeout handling
|
42
|
+
- `_report_test_results`: Processes test results and provides appropriate feedback to the user
|
43
|
+
- `_run_tests`: Orchestrates the entire testing process by calling the specialized methods in sequence
|
44
|
+
|
45
|
+
This separation of concerns makes the code more maintainable and easier to test.
|
46
|
+
|
47
|
+
## Test Configuration
|
48
|
+
|
49
|
+
The test configuration is standardized across all packages and includes the following optimizations:
|
50
|
+
|
51
|
+
### Pytest Options
|
52
|
+
|
53
|
+
- `--no-cov`: Disables coverage reporting which can cause hanging
|
54
|
+
- `--capture=fd`: Captures stdout/stderr at file descriptor level for better output handling
|
55
|
+
- `--tb=short`: Uses shorter traceback format to reduce output complexity
|
56
|
+
- `--no-header`: Reduces output noise
|
57
|
+
- `--disable-warnings`: Disables warning capture which can cause issues
|
58
|
+
- `--durations=0`: Shows slowest tests to help identify potential hanging tests
|
59
|
+
- `--timeout=300`: Sets a 5-minute timeout for tests
|
60
|
+
|
61
|
+
### Process Management
|
62
|
+
|
63
|
+
Crackerjack uses a custom process management approach that:
|
64
|
+
|
65
|
+
1. Runs pytest with a timeout to ensure tests don't run indefinitely
|
66
|
+
2. Streams output in real-time to provide feedback during test execution
|
67
|
+
3. Properly handles process termination and cleanup
|
68
|
+
4. Ensures proper process cleanup even if tests hang
|
69
|
+
|
70
|
+
### Environment Variables
|
71
|
+
|
72
|
+
Crackerjack sets several environment variables to control test behavior:
|
73
|
+
|
74
|
+
- `RUNNING_UNDER_CRACKERJACK=1` - Signals to test frameworks that tests are being run by crackerjack
|
75
|
+
- `PYTHONASYNCIO_DEBUG=0` - Disables asyncio debug mode
|
76
|
+
- `PYTEST_ASYNCIO_MODE=strict` - Uses a stricter asyncio mode that helps prevent hanging
|
77
|
+
|
78
|
+
## Troubleshooting
|
79
|
+
|
80
|
+
If you encounter issues with tests:
|
81
|
+
|
82
|
+
1. Make sure you're using the latest version of crackerjack
|
83
|
+
2. Try running with the `--verbose` flag to see more detailed output
|
84
|
+
3. Check the test logs for any specific errors or warnings
|
85
|
+
4. Look for timeout messages that might indicate which tests are hanging
|
86
|
+
|
87
|
+
### Common Issues
|
88
|
+
|
89
|
+
#### Hanging Tests
|
90
|
+
|
91
|
+
If tests are hanging, the built-in timeout (5 minutes) will eventually terminate the process. The output will include a message indicating that the test execution timed out. To debug:
|
92
|
+
|
93
|
+
1. Run with `--verbose` to see more detailed output
|
94
|
+
2. Check for tests that might be creating infinite loops or waiting indefinitely for resources
|
95
|
+
3. Look for asyncio-related issues, which are a common cause of hanging tests
|
96
|
+
|
97
|
+
#### Environment Variable Conflicts
|
98
|
+
|
99
|
+
If you have environment variables set in your shell that conflict with those set by Crackerjack, you might experience unexpected behavior. To troubleshoot:
|
100
|
+
|
101
|
+
1. Check your environment for variables like `PYTEST_ASYNCIO_MODE` or `PYTHONASYNCIO_DEBUG`
|
102
|
+
2. Consider running in a clean environment if necessary
|
103
|
+
|
104
|
+
#### Process Management Issues
|
105
|
+
|
106
|
+
If you encounter issues with process management (e.g., zombie processes or resource leaks):
|
107
|
+
|
108
|
+
1. Make sure you're using the latest version of Crackerjack
|
109
|
+
2. Check for any system-specific issues that might affect process management
|
110
|
+
3. Consider running with fewer concurrent tests if your system has limited resources
|
111
|
+
|
112
|
+
## Extending Test Functionality
|
113
|
+
|
114
|
+
The modular design of Crackerjack's test execution makes it easy to extend or customize the testing process:
|
115
|
+
|
116
|
+
### Adding New Test Options
|
117
|
+
|
118
|
+
To add new pytest options, modify the `_prepare_pytest_command` method to include additional options in the command list.
|
119
|
+
|
120
|
+
### Customizing Environment Setup
|
121
|
+
|
122
|
+
To change environment variable settings, modify the `_setup_test_environment` method to set additional variables or change existing ones.
|
123
|
+
|
124
|
+
### Enhancing Process Management
|
125
|
+
|
126
|
+
To improve process management or output handling, modify the `_run_pytest_process` method to implement custom behavior.
|
127
|
+
|
128
|
+
### Customizing Result Reporting
|
129
|
+
|
130
|
+
To change how test results are reported, modify the `_report_test_results` method to implement custom formatting or additional actions based on test outcomes.
|
@@ -38,6 +38,7 @@ class OptionsForTesting:
|
|
38
38
|
clean: bool = False
|
39
39
|
test: bool = False
|
40
40
|
all: BumpOption | None = None
|
41
|
+
ai_agent: bool = False
|
41
42
|
|
42
43
|
|
43
44
|
@pytest.fixture
|
@@ -206,12 +207,19 @@ class TestCrackerjackProcess:
|
|
206
207
|
mock_project_manager_execute.return_value.returncode = 0
|
207
208
|
mock_config_manager_execute.return_value.returncode = 0
|
208
209
|
options = options_factory(test=True, no_config_updates=True)
|
209
|
-
with
|
210
|
+
with (
|
211
|
+
patch.object(Crackerjack, "_update_project") as mock_update_project,
|
212
|
+
patch.object(Crackerjack, "_run_tests") as mock_run_tests,
|
213
|
+
):
|
210
214
|
mock_update_project.side_effect = lambda opts: mock_console_print(
|
211
215
|
"Skipping config updates."
|
212
216
|
)
|
217
|
+
mock_run_tests.side_effect = lambda opts: mock_console_print(
|
218
|
+
"\n\nRunning tests...\n"
|
219
|
+
)
|
213
220
|
cj = Crackerjack(dry_run=True)
|
214
221
|
cj.process(options)
|
222
|
+
mock_run_tests.assert_called_once_with(options)
|
215
223
|
console_print_calls = [str(call) for call in mock_console_print.call_args_list]
|
216
224
|
assert any(("Running tests" in call for call in console_print_calls)), (
|
217
225
|
"Expected 'Running tests' message was not printed"
|
@@ -22,6 +22,7 @@ class MockOptions:
|
|
22
22
|
self.publish = kwargs.get("publish")
|
23
23
|
self.bump = kwargs.get("bump")
|
24
24
|
self.all = kwargs.get("all")
|
25
|
+
self.ai_agent = kwargs.get("ai_agent", False)
|
25
26
|
|
26
27
|
|
27
28
|
def test_create_crackerjack_runner() -> None:
|
@@ -80,7 +81,8 @@ def test_process_with_all_option(
|
|
80
81
|
crackerjack.execute_command = MagicMock(return_value=MagicMock(returncode=0))
|
81
82
|
|
82
83
|
with patch("builtins.input", return_value="Test commit message"):
|
83
|
-
|
84
|
+
with patch.object(Crackerjack, "_run_tests"):
|
85
|
+
crackerjack.process(options)
|
84
86
|
|
85
87
|
assert options.clean is True
|
86
88
|
assert options.test is True
|
@@ -119,8 +121,5 @@ def test_process_with_test_option(
|
|
119
121
|
)
|
120
122
|
|
121
123
|
with patch("builtins.input", return_value="Test commit message"):
|
122
|
-
|
123
|
-
|
124
|
-
crackerjack.execute_command.assert_any_call(
|
125
|
-
["pytest"], capture_output=True, text=True
|
126
|
-
)
|
124
|
+
with patch.object(Crackerjack, "_run_tests"):
|
125
|
+
crackerjack.process(options)
|
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
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.11/3256171999636029978
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.4/10355199064880463147
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.6/15140459877605758699
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.1.9/17041001205004563469
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.2/4070660268492669020
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.3/9818742842212983150
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.4/9818742842212983150
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.11.6/3557596832929915217
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.2.0/10047773857155985907
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.2.2/18053836298936336950
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.0/12548816621480535786
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.3/11081883392474770722
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.3.5/16311176246009842383
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.0/11982804814124138945
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.6.0/12055761203849489982
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.7.3/16061516852537040135
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.8.4/16354268377385700367
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.10/12813592349865671909
RENAMED
File without changes
|
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.3/13948373885254993391
RENAMED
File without changes
|
{crackerjack-0.15.7 → crackerjack-0.15.9}/crackerjack/.ruff_cache/0.9.9/12813592349865671909
RENAMED
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
|