crackerjack 0.20.1__tar.gz → 0.20.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {crackerjack-0.20.1 → crackerjack-0.20.3}/PKG-INFO +59 -3
  2. {crackerjack-0.20.1 → crackerjack-0.20.3}/README.md +58 -2
  3. crackerjack-0.20.3/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  4. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/crackerjack.py +26 -15
  5. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/pyproject.toml +1 -1
  6. {crackerjack-0.20.1 → crackerjack-0.20.3}/pyproject.toml +1 -1
  7. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_crackerjack.py +152 -49
  8. crackerjack-0.20.1/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  9. {crackerjack-0.20.1 → crackerjack-0.20.3}/LICENSE +0 -0
  10. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.gitignore +0 -0
  11. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.libcst.codemod.yaml +0 -0
  12. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pdm.toml +0 -0
  13. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pre-commit-config.yaml +0 -0
  14. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pytest_cache/.gitignore +0 -0
  15. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
  16. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pytest_cache/README.md +0 -0
  17. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
  18. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
  19. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/.gitignore +0 -0
  20. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  21. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  22. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  23. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  24. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  25. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  26. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.11/18187162184424859798 +0 -0
  27. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/16869036553936192448 +0 -0
  28. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/1867267426380906393 +0 -0
  29. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/4240757255861806333 +0 -0
  30. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/4441409093023629623 +0 -0
  31. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  32. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  33. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  34. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  35. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  36. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  37. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  38. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  39. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  40. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  41. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  42. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  43. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  44. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  45. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  46. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  47. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  48. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  49. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  50. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  51. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  52. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  53. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  54. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  55. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  56. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  57. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  58. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  59. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  60. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  61. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  62. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  63. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  64. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
  65. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/__init__.py +0 -0
  66. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/__main__.py +0 -0
  67. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/errors.py +0 -0
  68. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/interactive.py +0 -0
  69. {crackerjack-0.20.1 → crackerjack-0.20.3}/crackerjack/py313.py +0 -0
  70. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/TESTING.md +0 -0
  71. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/__init__.py +0 -0
  72. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/conftest.py +0 -0
  73. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/data/comments_sample.txt +0 -0
  74. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/data/docstrings_sample.txt +0 -0
  75. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/data/expected_comments_sample.txt +0 -0
  76. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/data/init.py +0 -0
  77. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_crackerjack_runner.py +0 -0
  78. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_errors.py +0 -0
  79. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_interactive.py +0 -0
  80. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_interactive_run.py +0 -0
  81. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_main.py +0 -0
  82. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_py313_advanced.py +0 -0
  83. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_py313_features.py +0 -0
  84. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_pytest_features.py +0 -0
  85. {crackerjack-0.20.1 → crackerjack-0.20.3}/tests/test_structured_errors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.20.1
3
+ Version: 0.20.3
4
4
  Summary: Crackerjack: code quality toolkit
5
5
  Keywords: bandit,black,creosote,mypy,pyright,pytest,refurb,ruff
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -192,9 +192,35 @@ Crackerjack provides advanced testing capabilities powered by pytest:
192
192
  ### Standard Testing
193
193
 
194
194
  - **Parallel Test Execution:** Tests run in parallel by default using pytest-xdist for faster execution
195
- - **Timeout Protection:** All tests have a default 60-second timeout to prevent hanging tests
195
+ - **Smart Parallelization:** Automatically adjusts the number of worker processes based on project size
196
+ - **Timeout Protection:** Tests have dynamic timeouts based on project size to prevent hanging tests
196
197
  - **Coverage Reports:** Automatically generates test coverage reports with configurable thresholds
197
198
 
199
+ ### Advanced Test Configuration
200
+
201
+ Crackerjack offers fine-grained control over test execution:
202
+
203
+ - **Worker Control:** Set the number of parallel workers with `--test-workers` (0 = auto-detect, 1 = disable parallelization)
204
+ - **Timeout Control:** Customize test timeouts with `--test-timeout` (in seconds)
205
+ - **Project Size Detection:** Automatically detects project size and adjusts timeout and parallelization settings
206
+ - **Deadlock Prevention:** Uses advanced threading techniques to prevent deadlocks in test output processing
207
+ - **Progress Tracking:** Shows periodic heartbeat messages for long-running tests
208
+
209
+ Example test execution options:
210
+ ```bash
211
+ # Run tests with a single worker (no parallelization)
212
+ python -m crackerjack -t --test-workers=1
213
+
214
+ # Run tests with a specific number of workers (e.g., 4)
215
+ python -m crackerjack -t --test-workers=4
216
+
217
+ # Run tests with a custom timeout (5 minutes per test)
218
+ python -m crackerjack -t --test-timeout=300
219
+
220
+ # Combine options for maximum control
221
+ python -m crackerjack -t --test-workers=2 --test-timeout=600
222
+ ```
223
+
198
224
  ### Benchmark Testing
199
225
 
200
226
  Crackerjack includes benchmark testing capabilities:
@@ -278,10 +304,16 @@ class MyOptions:
278
304
  # Process options
279
305
  self.clean = True # Clean code (remove docstrings, comments, etc.)
280
306
  self.test = True # Run tests using pytest
307
+ self.skip_hooks = False # Skip running pre-commit hooks
308
+
309
+ # Test execution options
310
+ self.test_workers = 2 # Number of parallel workers (0 = auto-detect, 1 = disable parallelization)
311
+ self.test_timeout = 120 # Timeout in seconds for individual tests (0 = use default based on project size)
312
+
313
+ # Benchmark options
281
314
  self.benchmark = False # Run tests in benchmark mode
282
315
  self.benchmark_regression = False # Fail tests if benchmarks regress beyond threshold
283
316
  self.benchmark_regression_threshold = 5.0 # Threshold percentage for benchmark regression
284
- self.skip_hooks = False # Skip running pre-commit hooks
285
317
 
286
318
  # Version and publishing options
287
319
  self.publish = None # Publish to PyPI (micro, minor, major)
@@ -321,6 +353,8 @@ runner.process(MyOptions())
321
353
  - `-s`, `--skip-hooks`: Skip running pre-commit hooks (useful with `-t`).
322
354
  - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
323
355
  - `-t`, `--test`: Run tests using `pytest`.
356
+ - `--test-workers`: Set the number of parallel workers for testing (0 = auto-detect, 1 = disable parallelization).
357
+ - `--test-timeout`: Set the timeout in seconds for individual tests (0 = use default based on project size).
324
358
  - `--benchmark`: Run tests in benchmark mode (disables parallel execution).
325
359
  - `--benchmark-regression`: Fail tests if benchmarks regress beyond threshold.
326
360
  - `--benchmark-regression-threshold`: Set threshold percentage for benchmark regression (default 5.0%).
@@ -356,6 +390,28 @@ runner.process(MyOptions())
356
390
  python -m crackerjack -t -s
357
391
  ```
358
392
 
393
+ #### Test Execution Options
394
+
395
+ - **Single-Process Testing** - Run tests sequentially (no parallelization):
396
+ ```bash
397
+ python -m crackerjack -t --test-workers=1
398
+ ```
399
+
400
+ - **Customized Parallel Testing** - Run tests with a specific number of workers:
401
+ ```bash
402
+ python -m crackerjack -t --test-workers=4
403
+ ```
404
+
405
+ - **Long-Running Tests** - Increase test timeout for complex tests:
406
+ ```bash
407
+ python -m crackerjack -t --test-timeout=600
408
+ ```
409
+
410
+ - **Optimized for Large Projects** - Reduce workers and increase timeout for large codebases:
411
+ ```bash
412
+ python -m crackerjack -t --test-workers=2 --test-timeout=300
413
+ ```
414
+
359
415
  #### Version Management
360
416
 
361
417
  - **Bump and Publish** - Bump version and publish to PyPI:
@@ -150,9 +150,35 @@ Crackerjack provides advanced testing capabilities powered by pytest:
150
150
  ### Standard Testing
151
151
 
152
152
  - **Parallel Test Execution:** Tests run in parallel by default using pytest-xdist for faster execution
153
- - **Timeout Protection:** All tests have a default 60-second timeout to prevent hanging tests
153
+ - **Smart Parallelization:** Automatically adjusts the number of worker processes based on project size
154
+ - **Timeout Protection:** Tests have dynamic timeouts based on project size to prevent hanging tests
154
155
  - **Coverage Reports:** Automatically generates test coverage reports with configurable thresholds
155
156
 
157
+ ### Advanced Test Configuration
158
+
159
+ Crackerjack offers fine-grained control over test execution:
160
+
161
+ - **Worker Control:** Set the number of parallel workers with `--test-workers` (0 = auto-detect, 1 = disable parallelization)
162
+ - **Timeout Control:** Customize test timeouts with `--test-timeout` (in seconds)
163
+ - **Project Size Detection:** Automatically detects project size and adjusts timeout and parallelization settings
164
+ - **Deadlock Prevention:** Uses advanced threading techniques to prevent deadlocks in test output processing
165
+ - **Progress Tracking:** Shows periodic heartbeat messages for long-running tests
166
+
167
+ Example test execution options:
168
+ ```bash
169
+ # Run tests with a single worker (no parallelization)
170
+ python -m crackerjack -t --test-workers=1
171
+
172
+ # Run tests with a specific number of workers (e.g., 4)
173
+ python -m crackerjack -t --test-workers=4
174
+
175
+ # Run tests with a custom timeout (5 minutes per test)
176
+ python -m crackerjack -t --test-timeout=300
177
+
178
+ # Combine options for maximum control
179
+ python -m crackerjack -t --test-workers=2 --test-timeout=600
180
+ ```
181
+
156
182
  ### Benchmark Testing
157
183
 
158
184
  Crackerjack includes benchmark testing capabilities:
@@ -236,10 +262,16 @@ class MyOptions:
236
262
  # Process options
237
263
  self.clean = True # Clean code (remove docstrings, comments, etc.)
238
264
  self.test = True # Run tests using pytest
265
+ self.skip_hooks = False # Skip running pre-commit hooks
266
+
267
+ # Test execution options
268
+ self.test_workers = 2 # Number of parallel workers (0 = auto-detect, 1 = disable parallelization)
269
+ self.test_timeout = 120 # Timeout in seconds for individual tests (0 = use default based on project size)
270
+
271
+ # Benchmark options
239
272
  self.benchmark = False # Run tests in benchmark mode
240
273
  self.benchmark_regression = False # Fail tests if benchmarks regress beyond threshold
241
274
  self.benchmark_regression_threshold = 5.0 # Threshold percentage for benchmark regression
242
- self.skip_hooks = False # Skip running pre-commit hooks
243
275
 
244
276
  # Version and publishing options
245
277
  self.publish = None # Publish to PyPI (micro, minor, major)
@@ -279,6 +311,8 @@ runner.process(MyOptions())
279
311
  - `-s`, `--skip-hooks`: Skip running pre-commit hooks (useful with `-t`).
280
312
  - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
281
313
  - `-t`, `--test`: Run tests using `pytest`.
314
+ - `--test-workers`: Set the number of parallel workers for testing (0 = auto-detect, 1 = disable parallelization).
315
+ - `--test-timeout`: Set the timeout in seconds for individual tests (0 = use default based on project size).
282
316
  - `--benchmark`: Run tests in benchmark mode (disables parallel execution).
283
317
  - `--benchmark-regression`: Fail tests if benchmarks regress beyond threshold.
284
318
  - `--benchmark-regression-threshold`: Set threshold percentage for benchmark regression (default 5.0%).
@@ -314,6 +348,28 @@ runner.process(MyOptions())
314
348
  python -m crackerjack -t -s
315
349
  ```
316
350
 
351
+ #### Test Execution Options
352
+
353
+ - **Single-Process Testing** - Run tests sequentially (no parallelization):
354
+ ```bash
355
+ python -m crackerjack -t --test-workers=1
356
+ ```
357
+
358
+ - **Customized Parallel Testing** - Run tests with a specific number of workers:
359
+ ```bash
360
+ python -m crackerjack -t --test-workers=4
361
+ ```
362
+
363
+ - **Long-Running Tests** - Increase test timeout for complex tests:
364
+ ```bash
365
+ python -m crackerjack -t --test-timeout=600
366
+ ```
367
+
368
+ - **Optimized for Large Projects** - Reduce workers and increase timeout for large codebases:
369
+ ```bash
370
+ python -m crackerjack -t --test-workers=2 --test-timeout=300
371
+ ```
372
+
317
373
  #### Version Management
318
374
 
319
375
  - **Bump and Publish** - Bump version and publish to PyPI:
@@ -1022,23 +1022,34 @@ class Crackerjack:
1022
1022
 
1023
1023
  if options.publish:
1024
1024
  if platform.system() == "Darwin":
1025
- authorize = self.execute_command(
1026
- ["pdm", "self", "add", "keyring"], capture_output=True, text=True
1025
+ # First check if keyring is already installed in PDM
1026
+ check_keyring = self.execute_command(
1027
+ ["pdm", "self", "list"], capture_output=True, text=True
1027
1028
  )
1028
- if authorize.returncode > 0:
1029
- error = PublishError(
1030
- message="Authentication setup failed",
1031
- error_code=ErrorCode.AUTHENTICATION_ERROR,
1032
- details=f"Failed to add keyring support to PDM.\nCommand output:\n{authorize.stderr}",
1033
- recovery="Please manually add your keyring credentials to PDM. Run `pdm self add keyring` and try again.",
1034
- exit_code=1,
1035
- )
1036
- handle_error(
1037
- error=error,
1038
- console=self.console,
1039
- verbose=options.verbose,
1040
- ai_agent=options.ai_agent,
1029
+ keyring_installed = "keyring" in check_keyring.stdout
1030
+
1031
+ if not keyring_installed:
1032
+ # Only attempt to install keyring if it's not already installed
1033
+ self.console.print("Installing keyring for PDM...")
1034
+ authorize = self.execute_command(
1035
+ ["pdm", "self", "add", "keyring"],
1036
+ capture_output=True,
1037
+ text=True,
1041
1038
  )
1039
+ if authorize.returncode > 0:
1040
+ error = PublishError(
1041
+ message="Authentication setup failed",
1042
+ error_code=ErrorCode.AUTHENTICATION_ERROR,
1043
+ details=f"Failed to add keyring support to PDM.\nCommand output:\n{authorize.stderr}",
1044
+ recovery="Please manually add your keyring credentials to PDM. Run `pdm self add keyring` and try again.",
1045
+ exit_code=1,
1046
+ )
1047
+ handle_error(
1048
+ error=error,
1049
+ console=self.console,
1050
+ verbose=options.verbose,
1051
+ ai_agent=options.ai_agent,
1052
+ )
1042
1053
 
1043
1054
  build = self.execute_command(
1044
1055
  ["pdm", "build"], capture_output=True, text=True
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.20.0"
7
+ version = "0.20.2"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "crackerjack"
9
- version = "0.20.1"
9
+ version = "0.20.3"
10
10
  description = "Crackerjack: code quality toolkit"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import subprocess
2
3
  import typing as t
3
4
  from contextlib import suppress
4
5
  from dataclasses import dataclass
@@ -12,6 +13,7 @@ from crackerjack.crackerjack import (
12
13
  CodeCleaner,
13
14
  ConfigManager,
14
15
  Crackerjack,
16
+ OptionsProtocol,
15
17
  ProjectManager,
16
18
  )
17
19
 
@@ -180,7 +182,7 @@ class TestCrackerjackProcess:
180
182
  options = options_factory(commit=True, no_config_updates=True)
181
183
  with patch.object(Crackerjack, "_update_project") as mock_update_project:
182
184
 
183
- def side_effect(opts: t.Any) -> None:
185
+ def side_effect(opts: OptionsProtocol) -> None:
184
186
  if opts.no_config_updates:
185
187
  mock_console_print("Skipping config updates.")
186
188
 
@@ -580,7 +582,9 @@ class TestCrackerjackProcess:
580
582
  with patch("platform.system", return_value="Linux"):
581
583
  with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
582
584
 
583
- def mock_execute_side_effect(*args: t.Any, **kwargs: t.Any):
585
+ def mock_execute_side_effect(
586
+ *args: t.Any, **kwargs: t.Any
587
+ ) -> subprocess.CompletedProcess[str]:
584
588
  cmd = args[0][0]
585
589
  if cmd == "pdm" and "build" in args[0]:
586
590
  return MagicMock(
@@ -596,55 +600,154 @@ class TestCrackerjackProcess:
596
600
 
597
601
  mock_handle_error.assert_called()
598
602
 
599
- def test_process_with_darwin_platform(
600
- self,
601
- mock_execute: MagicMock,
602
- mock_console_print: MagicMock,
603
- tmp_path: Path,
604
- tmp_path_package: Path,
605
- create_package_dir: None,
606
- options_factory: t.Callable[..., OptionsForTesting],
607
- ) -> None:
608
- options = options_factory(publish="micro", no_config_updates=True)
609
- with patch("platform.system", return_value="Darwin"):
610
- with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
611
- mock_cj_execute.side_effect = [
612
- MagicMock(returncode=0, stdout="Success"),
613
- MagicMock(returncode=0, stdout="Success"),
614
- MagicMock(returncode=0, stdout="Success"),
615
- MagicMock(returncode=0, stdout="Success"),
616
- ]
617
- with patch.object(Crackerjack, "_update_project"):
618
- cj = Crackerjack(dry_run=True)
619
- cj.process(options)
620
- mock_cj_execute.assert_any_call(
621
- ["pdm", "self", "add", "keyring"],
622
- capture_output=True,
623
- text=True,
624
- )
603
+ def test_keyring_check_install_darwin(self) -> None:
604
+ """Test checking for keyring on Darwin (macOS) and installing it."""
605
+ # Create a minimal options object
606
+ options = OptionsForTesting(publish=BumpOption.micro)
625
607
 
626
- def test_process_with_darwin_platform_keyring_failure(
627
- self,
628
- mock_execute: MagicMock,
629
- mock_console_print: MagicMock,
630
- tmp_path: Path,
631
- tmp_path_package: Path,
632
- create_package_dir: None,
633
- options_factory: t.Callable[..., OptionsForTesting],
634
- ) -> None:
635
- with patch("crackerjack.errors.handle_error") as mock_handle_error:
636
- options = options_factory(publish="micro", no_config_updates=True)
637
- with patch("platform.system", return_value="Darwin"):
638
- with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
639
- mock_cj_execute.return_value = MagicMock(
640
- returncode=1, stdout="", stderr="Authorization failed"
641
- )
642
- with patch.object(Crackerjack, "_update_project"):
643
- with suppress(SystemExit):
644
- cj = Crackerjack(dry_run=True)
645
- cj.process(options)
608
+ # Use a list to track actual calls
609
+ actual_calls = []
646
610
 
647
- mock_handle_error.assert_called()
611
+ # Create a Crackerjack instance for testing
612
+ crackerjack = Crackerjack(dry_run=False)
613
+
614
+ # Mock platform.system to return 'Darwin'
615
+ with patch("platform.system", return_value="Darwin"):
616
+ # Create a custom execute_command function that tracks calls
617
+ def mock_execute_side_effect(
618
+ *args: t.Any, **kwargs: t.Any
619
+ ) -> subprocess.CompletedProcess[str]:
620
+ cmd = args[0]
621
+ actual_calls.append(cmd) # Track each command
622
+
623
+ if cmd == ["pdm", "self", "list"]:
624
+ # Important: This MUST NOT contain the string "keyring" to trigger installation
625
+ return MagicMock(
626
+ returncode=0, stdout="packages installed: pytest, black"
627
+ )
628
+ elif cmd == ["pdm", "self", "add", "keyring"]:
629
+ return MagicMock(returncode=0, stdout="installed keyring")
630
+ elif cmd == ["pdm", "build"]:
631
+ return MagicMock(returncode=0, stdout="build output")
632
+ elif cmd == ["pdm", "publish", "--no-build"]:
633
+ return MagicMock(returncode=0, stdout="publish output")
634
+ return MagicMock(returncode=0, stdout="")
635
+
636
+ # Mock execute_command with our custom side_effect
637
+ with patch.object(
638
+ crackerjack, "execute_command", side_effect=mock_execute_side_effect
639
+ ):
640
+ # Mock console.print
641
+ with patch.object(crackerjack.console, "print"):
642
+ # Call the method we're testing
643
+ crackerjack._publish_project(options)
644
+
645
+ # Check that all the expected commands were called
646
+ assert ["pdm", "self", "list"] in actual_calls
647
+ assert ["pdm", "self", "add", "keyring"] in actual_calls
648
+ assert ["pdm", "build"] in actual_calls
649
+ assert ["pdm", "publish", "--no-build"] in actual_calls
650
+
651
+ def test_keyring_already_installed_darwin(self) -> None:
652
+ """Test behavior when keyring is already installed on Darwin (macOS)."""
653
+ # Create a minimal options object
654
+ options = OptionsForTesting(publish=BumpOption.micro)
655
+
656
+ # Use a list to track actual calls
657
+ actual_calls = []
658
+
659
+ # Create a Crackerjack instance for testing
660
+ crackerjack = Crackerjack(dry_run=False)
661
+
662
+ # Mock platform.system to return 'Darwin'
663
+ with patch("platform.system", return_value="Darwin"):
664
+ # Create a custom execute_command function that tracks calls
665
+ def mock_execute_side_effect(
666
+ *args: t.Any, **kwargs: t.Any
667
+ ) -> subprocess.CompletedProcess[str]:
668
+ cmd = args[0]
669
+ actual_calls.append(cmd) # Track each command
670
+
671
+ if cmd == ["pdm", "self", "list"]:
672
+ return MagicMock(returncode=0, stdout="keyring 25.6.0")
673
+ elif cmd == ["pdm", "build"]:
674
+ return MagicMock(returncode=0, stdout="build output")
675
+ elif cmd == ["pdm", "publish", "--no-build"]:
676
+ return MagicMock(returncode=0, stdout="publish output")
677
+ return MagicMock(returncode=0, stdout="")
678
+
679
+ # Mock execute_command with our custom side_effect
680
+ with patch.object(
681
+ crackerjack, "execute_command", side_effect=mock_execute_side_effect
682
+ ):
683
+ # Mock console.print
684
+ with patch.object(crackerjack.console, "print"):
685
+ # Call the method we're testing
686
+ crackerjack._publish_project(options)
687
+
688
+ # Check that keyring installation was skipped
689
+ assert ["pdm", "self", "list"] in actual_calls
690
+ assert ["pdm", "self", "add", "keyring"] not in actual_calls
691
+ assert ["pdm", "build"] in actual_calls
692
+ assert ["pdm", "publish", "--no-build"] in actual_calls
693
+
694
+ def test_keyring_install_failure_darwin(self) -> None:
695
+ """Test handling of keyring installation failure on Darwin (macOS)."""
696
+ # Create a minimal options object with ai_agent False to avoid extra mocking
697
+ options = OptionsForTesting(publish=BumpOption.micro, ai_agent=False)
698
+
699
+ # Create a Crackerjack instance for testing
700
+ crackerjack = Crackerjack(dry_run=False)
701
+
702
+ # Mock the PublishError class to capture its creation
703
+ with patch("crackerjack.errors.PublishError") as mock_publish_error:
704
+ # Set up the mock to return a MagicMock that we can track
705
+ error_instance = MagicMock()
706
+ mock_publish_error.return_value = error_instance
707
+
708
+ # Mock handle_error to avoid actual exit
709
+ with patch("crackerjack.errors.handle_error") as mock_handle_error:
710
+ # Mock platform.system to return 'Darwin'
711
+ with patch("platform.system", return_value="Darwin"):
712
+ # Create a custom execute_command function with failure scenario
713
+ def mock_execute_side_effect(
714
+ *args: t.Any, **kwargs: t.Any
715
+ ) -> subprocess.CompletedProcess[str]:
716
+ cmd = args[0]
717
+
718
+ if cmd == ["pdm", "self", "list"]:
719
+ # This must NOT contain the string "keyring" to trigger installation
720
+ return MagicMock(
721
+ returncode=0, stdout="packages installed: pytest, black"
722
+ )
723
+ elif cmd == ["pdm", "self", "add", "keyring"]:
724
+ # Return a failure for keyring installation
725
+ mock_result = MagicMock()
726
+ mock_result.returncode = 1
727
+ mock_result.stdout = ""
728
+ mock_result.stderr = "Installation failed"
729
+ return mock_result
730
+ return MagicMock(returncode=0, stdout="")
731
+
732
+ # Mock execute_command with our custom side_effect
733
+ with patch.object(
734
+ crackerjack,
735
+ "execute_command",
736
+ side_effect=mock_execute_side_effect,
737
+ ):
738
+ # Mock console.print to avoid terminal output
739
+ with patch.object(crackerjack.console, "print"):
740
+ # Call the method we're testing with error suppression
741
+ with suppress(SystemExit):
742
+ crackerjack._publish_project(options)
743
+
744
+ # Check that the error handler was called with the right error
745
+ mock_handle_error.assert_called_once_with(
746
+ error=error_instance,
747
+ console=crackerjack.console,
748
+ verbose=options.verbose,
749
+ ai_agent=options.ai_agent,
750
+ )
648
751
 
649
752
  def test_process_with_commit_input(
650
753
  self,
File without changes