Skip to content

Commit 4d8f247

Browse files
SCANPY-237 Add dry run mode to Pysonar scanner (#301)
1 parent b49e705 commit 4d8f247

File tree

9 files changed

+802
-3
lines changed

9 files changed

+802
-3
lines changed

CLI_ARGS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
| Option | Description |
5050
| ------ | ----------- |
51+
| `--dry-run`, `--no-dry-run` | Enable dry-run mode to validate configuration without connecting to SonarQube server or submitting analysis. See DRY_RUN_MODE.md for details |
5152
| `--skip-jre-provisioning`, `-Dsonar.scanner.skipJreProvisioning` | If provided, the provisioning of the JRE will be skipped |
5253
| `--sonar-branch-name`, `-Dsonar.branch.name` | Name of the branch being analyzed |
5354
| `--sonar-build-string`, `-Dsonar.buildString` | The string passed with this property will be stored with the analysis and available in the results of api/project_analyses/search, thus allowing you to later identify a specific analysis and obtain its key for use with api/new_code_periods/set on the SPECIFIC_ANALYSIS type |

DRY_RUN_MODE.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Dry Run / Debug Mode for Pysonar Scanner
2+
3+
The Pysonar scanner supports a **dry-run mode** that helps you troubleshoot configuration issues without connecting to a SonarQube server or submitting analysis results. This is particularly useful when:
4+
5+
- Setting up new projects
6+
- Adjusting coverage report paths
7+
- Validating configuration properties
8+
- Debugging analysis failures related to configuration
9+
10+
## Use Cases
11+
12+
### 1. Validating Configuration Before First Analysis
13+
14+
Use dry-run mode to verify your configuration is correct before running a full analysis. This is especially useful when setting up new projects or making configuration changes:
15+
16+
```bash
17+
pysonar \
18+
--token "myToken" \
19+
--project-key "my:project" \
20+
--sonar-python-coverage-report-paths "coverage/cobertura.xml" \
21+
--dry-run
22+
```
23+
24+
Or with minimal configuration:
25+
26+
```bash
27+
pysonar --dry-run -v
28+
```
29+
30+
This validates all configuration sources (pyproject.toml, environment variables, CLI arguments) and checks that coverage report paths are valid before submitting any analysis. This also helps ensure that environment variables, CLI arguments, and configuration files are being read correctly.
31+
32+
### 2. Troubleshooting Failed Analysis
33+
34+
If an analysis fails, use dry-run mode to quickly identify configuration issues without waiting for a full analysis. This is especially useful for catching problems with configuration files, environment variables, or required properties before attempting a full analysis.
35+
36+
## Enabling Dry Run Mode
37+
38+
To run the scanner in dry-run mode, add the `--dry-run` flag:
39+
40+
```bash
41+
pysonar --token "myToken" --project-key "my:project" --dry-run
42+
```
43+
44+
Alternatively, use the property format:
45+
46+
```bash
47+
pysonar -Dsonar.scanner.dryRun=true
48+
```
49+
50+
Or set it as an environment variable:
51+
52+
```bash
53+
export SONAR_SCANNER_DRY_RUN=true
54+
pysonar
55+
```
56+
57+
## What Dry Run Mode Does
58+
59+
When dry-run mode is enabled, the scanner:
60+
61+
1. **Skips SonarQube server validation** - No connection attempt to the SonarQube server is made
62+
2. **Skips analysis submission** - No data is sent to or modified on the server
63+
3. **Resolves configuration** - Loads configuration from all sources (CLI, environment variables, pyproject.toml, etc.)
64+
4. **Reports resolved configuration** - Displays the detected settings including:
65+
- Project key and name
66+
- Organization (if applicable)
67+
- Detected main source directories
68+
- Detected test source directories
69+
- Configured coverage report paths
70+
- Server URL (if configured)
71+
5. **Validates coverage reports** - Checks coverage report paths and formats with clear error reporting
72+
73+
## Configuration Report Output
74+
75+
In dry-run mode, the scanner outputs a configuration summary. Example:
76+
77+
```
78+
================================================================================
79+
DRY RUN MODE - Configuration Report
80+
================================================================================
81+
82+
Project Configuration:
83+
Project Key: my:project
84+
Project Name: My Project
85+
Organization: my-org
86+
87+
Server Configuration:
88+
Host Url: https://sonarcloud.io
89+
90+
Source Configuration:
91+
Sources: src
92+
Tests: tests
93+
94+
Coverage Configuration:
95+
Coverage Report Paths: coverage/cobertura.xml
96+
97+
================================================================================
98+
DRY RUN MODE - Validation Results
99+
================================================================================
100+
101+
✓ Configuration validation PASSED
102+
103+
================================================================================
104+
```
105+
106+
## Coverage Report Validation
107+
108+
The scanner performs basic validation of coverage reports by checking:
109+
110+
1. **File existence** - Verifies that the file exists at the specified path
111+
2. **File readability** - Ensures the file is readable and accessible
112+
3. **XML format** - Validates that the file can be parsed as valid XML
113+
4. **Root element** - Checks that XML root element is `<coverage>` (expected Cobertura format)
114+
115+
Note: This is a basic sanity check and does not perform full schema validation of the Cobertura format.
116+
117+
### Example: Coverage Report Validation Output
118+
119+
Successful validation:
120+
121+
```
122+
Coverage Report Paths: coverage.xml
123+
124+
✓ Coverage report check passed: coverage.xml
125+
```
126+
127+
Missing file error:
128+
129+
```
130+
✗ Configuration validation FAILED with the following issues:
131+
✗ Coverage report not found: coverage.xml (resolved to /project/coverage.xml)
132+
```
133+
134+
Invalid format error:
135+
136+
```
137+
✗ Configuration validation FAILED with the following issues:
138+
✗ Coverage report is not valid XML (Cobertura format): coverage.xml (Parse error: XML not well-formed (invalid token))
139+
```
140+
141+
## Exit Codes
142+
143+
- **0**: Configuration validation passed, no errors found
144+
- **1**: Configuration validation failed, errors were found
145+
146+
## Common Issues and Solutions
147+
148+
### Issue: Coverage report not found
149+
150+
**Error message:**
151+
```
152+
Coverage report not found: coverage.xml (resolved to /project/coverage.xml)
153+
```
154+
155+
**Solution:**
156+
- Verify the file path is correct relative to the project base directory
157+
- Check that the file actually exists: `ls -la /project/coverage.xml`
158+
- Use absolute paths if relative paths are not working
159+
- Ensure the scanner is run from the correct directory
160+
161+
### Issue: Coverage report is not readable
162+
163+
**Error message:**
164+
```
165+
Coverage report is not readable (permission denied): coverage.xml
166+
```
167+
168+
**Solution:**
169+
- Check file permissions: `ls -l coverage.xml`
170+
- Make the file readable: `chmod 644 coverage.xml`
171+
172+
### Issue: Invalid XML format
173+
174+
**Error message:**
175+
```
176+
Coverage report is not valid XML (Cobertura format): coverage.xml (Parse error: XML not well-formed (invalid token))
177+
```
178+
179+
**Solution:**
180+
- Verify the coverage report was generated correctly with your coverage tool
181+
- Check the coverage tool documentation for the proper output format
182+
183+
### Issue: Wrong root element
184+
185+
**Warning message:**
186+
```
187+
Coverage report root element is 'report', expected 'coverage' (Cobertura format)
188+
```
189+
190+
**Solution:**
191+
- The coverage report may not be in Cobertura XML format
192+
- For Python projects using coverage.py, generate the report with: `coverage xml`
193+
- Check that your coverage tool is configured to output Cobertura XML format
194+
195+
## Integration with CI/CD
196+
197+
Dry-run mode is particularly useful in CI/CD pipelines to fail fast on configuration issues:
198+
199+
### GitHub Actions Example
200+
201+
```yaml
202+
- name: Validate configuration
203+
run: |
204+
pysonar \
205+
--token ${{ secrets.SONAR_TOKEN }} \
206+
--project-key ${{ env.SONAR_PROJECT_KEY }} \
207+
--sonar-python-coverage-report-paths "coverage/cobertura.xml" \
208+
--dry-run
209+
210+
- name: Run analysis
211+
run: |
212+
pysonar \
213+
--token ${{ secrets.SONAR_TOKEN }} \
214+
--project-key ${{ env.SONAR_PROJECT_KEY }} \
215+
--sonar-python-coverage-report-paths "coverage/cobertura.xml"
216+
```
217+
218+
### GitLab CI Example
219+
220+
```yaml
221+
validate_config:
222+
script:
223+
- pysonar
224+
--token $SONAR_TOKEN
225+
--project-key $SONAR_PROJECT_KEY
226+
--sonar-python-coverage-report-paths "coverage/cobertura.xml"
227+
--dry-run
228+
229+
analyze:
230+
script:
231+
- pysonar
232+
--token $SONAR_TOKEN
233+
--project-key $SONAR_PROJECT_KEY
234+
--sonar-python-coverage-report-paths "coverage/cobertura.xml"
235+
```
236+
237+
## Additional Resources
238+
239+
- [CLI Arguments Reference](CLI_ARGS.md)
240+
- [SonarQube Analysis Parameters](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/analysis-parameters/)
241+
- [Cobertura XML Format](https://cobertura.github.io/cobertura/)

poetry.lock

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pysonar_scanner/__main__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@
3535
SONAR_SCANNER_JAVA_EXE_PATH,
3636
SONAR_SCANNER_OS,
3737
SONAR_SCANNER_ARCH,
38+
SONAR_SCANNER_DRY_RUN,
39+
SONAR_PROJECT_BASE_DIR,
40+
SONAR_PYTHON_COVERAGE_REPORT_PATHS,
3841
)
3942
from pysonar_scanner.exceptions import SQTooOldException
4043
from pysonar_scanner.jre import JREResolvedPath, JREProvisioner, JREResolver, JREResolverConfiguration
4144
from pysonar_scanner.scannerengine import ScannerEngine, ScannerEngineProvisioner
45+
from pysonar_scanner.dry_run_reporter import DryRunReporter, CoverageReportValidator, ValidationResult
4246

4347

4448
def main():
@@ -61,6 +65,9 @@ def do_scan():
6165
config = ConfigurationLoader.load()
6266
set_logging_options(config)
6367

68+
if config.get(SONAR_SCANNER_DRY_RUN, False):
69+
return run_dry_run(config)
70+
6471
ConfigurationLoader.check_configuration(config)
6572

6673
api = build_api(config)
@@ -121,3 +128,20 @@ def create_jre(api, cache, config: dict[str, Any]) -> JREResolvedPath:
121128
jre_provisioner = JREProvisioner(api, cache, config[SONAR_SCANNER_OS], config[SONAR_SCANNER_ARCH])
122129
jre_resolver = JREResolver(JREResolverConfiguration.from_dict(config), jre_provisioner)
123130
return jre_resolver.resolve_jre()
131+
132+
133+
def run_dry_run(config: dict[str, Any]) -> int:
134+
"""
135+
Run in dry-run mode without connecting to SonarQube server.
136+
Validates configuration and coverage reports.
137+
"""
138+
logging.info("Running in DRY RUN mode")
139+
logging.info("No server connection will be made and no analysis will be submitted")
140+
141+
DryRunReporter.report_configuration(config)
142+
143+
coverage_paths = config.get(SONAR_PYTHON_COVERAGE_REPORT_PATHS)
144+
project_base_dir = config.get(SONAR_PROJECT_BASE_DIR, ".")
145+
validation_result = CoverageReportValidator.validate_coverage_reports(coverage_paths, project_base_dir)
146+
147+
return DryRunReporter.report_validation_results(validation_result)

src/pysonar_scanner/configuration/cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,12 @@ def __create_parser(cls):
363363
action=argparse.BooleanOptionalAction,
364364
help="Override the SonarQube configuration of skipping or not the analysis of unchanged Python files",
365365
)
366+
scanner_behavior_group.add_argument(
367+
"--dry-run",
368+
action=argparse.BooleanOptionalAction,
369+
default=None,
370+
help="Enable dry-run mode to validate configuration without connecting to SonarQube server or submitting analysis. See DRY_RUN_MODE.md for details",
371+
)
366372

367373
jvm_group = parser.add_argument_group("JVM Settings")
368374
jvm_group.add_argument(

src/pysonar_scanner/configuration/properties.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
SONAR_PYTHON_BANDIT_REPORT_PATHS: Key = "sonar.python.bandit.reportPaths"
103103
SONAR_PYTHON_FLAKE8_REPORT_PATHS: Key = "sonar.python.flake8.reportPaths"
104104
SONAR_PYTHON_RUFF_REPORT_PATHS: Key = "sonar.python.ruff.reportPaths"
105+
SONAR_SCANNER_DRY_RUN: Key = "sonar.scanner.dryRun"
105106
TOML_PATH: Key = "toml-path"
106107

107108
# ============ DEPRECATED ==============
@@ -554,5 +555,10 @@ def env_variable_name(self) -> str:
554555
default_value=None,
555556
cli_getter=lambda args: args.sonar_python_analysis_threads
556557
),
558+
Property(
559+
name=SONAR_SCANNER_DRY_RUN,
560+
default_value=False,
561+
cli_getter=lambda args: args.dry_run
562+
),
557563
]
558564
# fmt: on

0 commit comments

Comments
 (0)