Skip to content

Commit 20229e9

Browse files
SCANPY-237 Added dry run mode.
1 parent e98a8f8 commit 20229e9

File tree

8 files changed

+850
-0
lines changed

8 files changed

+850
-0
lines changed

CLI_ARGS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
| `--sonar-scanner-arch`, `-Dsonar.scanner.arch` | Architecture on which the scanner will be running |
7474
| `--sonar-scanner-cloud-url`, `-Dsonar.scanner.cloudUrl` | SonarQube Cloud base URL, https://sonarcloud.io for example |
7575
| `--sonar-scanner-connect-timeout`, `-Dsonar.scanner.connectTimeout` | Time period to establish connections with the server (in seconds) |
76+
| `--dry-run`, `--no-dry-run` | Enable dry-run mode to validate configuration without connecting to SonarQube server or submitting analysis. See [Dry Run Mode](DRY_RUN_MODE.md) for details. Can also be set via `-Dsonar.scanner.dryRun=true` or `SONAR_SCANNER_DRY_RUN=true` |
7677
| `--sonar-scanner-internal-dump-to-file`, `-Dsonar.scanner.internal.dumpToFile` | Filename where the input to the scanner engine will be dumped. Useful for debugging |
7778
| `--sonar-scanner-internal-sq-version`, `-Dsonar.scanner.internal.sqVersion` | Emulate the result of the call to get SQ server version. Useful for debugging with --sonar-scanner-internal-dump-to-file |
7879
| `--sonar-scanner-java-exe-path`, `-Dsonar.scanner.javaExePath` | If defined, the scanner engine will be run with this JRE |

DRY_RUN_MODE.md

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

src/pysonar_scanner/__main__.py

Lines changed: 26 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,22 @@ 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+
validation_result = ValidationResult()
144+
145+
coverage_paths = config.get(SONAR_PYTHON_COVERAGE_REPORT_PATHS)
146+
project_base_dir = config.get(SONAR_PROJECT_BASE_DIR, ".")
147+
CoverageReportValidator.validate_coverage_reports(coverage_paths, project_base_dir, validation_result)
148+
149+
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",
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)