Skip to content

Commit 9f80487

Browse files
SCANPY-237 Improved documentation and output, especially regarding error reporting. Minor refactoring.
1 parent 347b012 commit 9f80487

4 files changed

Lines changed: 59 additions & 101 deletions

File tree

DRY_RUN_MODE.md

Lines changed: 38 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,32 @@ The Pysonar scanner supports a **dry-run mode** that helps you troubleshoot conf
77
- Validating configuration properties
88
- Debugging analysis failures related to configuration
99

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+
1036
## Enabling Dry Run Mode
1137

1238
To run the scanner in dry-run mode, add the `--dry-run` flag:
@@ -79,101 +105,44 @@ DRY RUN MODE - Validation Results
79105

80106
## Coverage Report Validation
81107

82-
The scanner validates coverage reports by checking:
108+
The scanner performs basic validation of coverage reports by checking:
83109

84110
1. **File existence** - Verifies that the file exists at the specified path
85111
2. **File readability** - Ensures the file is readable and accessible
86-
3. **File format** - Validates that coverage reports are in valid Cobertura XML format
112+
3. **XML format** - Validates that the file can be parsed as valid XML
87113
4. **Root element** - Checks that XML root element is `<coverage>` (expected Cobertura format)
88114

115+
Note: This is a basic sanity check and does not perform full schema validation of the Cobertura format.
116+
89117
### Example: Coverage Report Validation Output
90118

91119
Successful validation:
92120

93121
```
94122
Coverage Report Paths: coverage.xml
95123
96-
✓ Coverage report is valid Cobertura XML: coverage.xml
124+
✓ Coverage report check passed: coverage.xml
97125
```
98126

99127
Missing file error:
100128

101129
```
102130
✗ Configuration validation FAILED with the following issues:
103-
Coverage report not found: coverage.xml (resolved to /project/coverage.xml)
131+
Coverage report not found: coverage.xml (resolved to /project/coverage.xml)
104132
```
105133

106134
Invalid format error:
107135

108136
```
109137
✗ 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)
138+
✗ Coverage report is not valid XML (Cobertura format): coverage.xml (Parse error: XML not well-formed (invalid token))
112139
```
113140

114141
## Exit Codes
115142

116143
- **0**: Configuration validation passed, no errors found
117144
- **1**: Configuration validation failed, errors were found
118145

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-
177146
## Common Issues and Solutions
178147

179148
### Issue: Coverage report not found
@@ -199,20 +168,17 @@ Coverage report is not readable (permission denied): coverage.xml
199168
**Solution:**
200169
- Check file permissions: `ls -l coverage.xml`
201170
- Make the file readable: `chmod 644 coverage.xml`
202-
- Ensure the process running the scanner has read access
203171

204172
### Issue: Invalid XML format
205173

206174
**Error message:**
207175
```
208-
Coverage report is not valid XML (Cobertura format): coverage.xml
209-
Parse error: XML not well-formed (invalid token)
176+
Coverage report is not valid XML (Cobertura format): coverage.xml (Parse error: XML not well-formed (invalid token))
210177
```
211178

212179
**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
180+
- Verify the coverage report was generated correctly with your coverage tool
181+
- Check the coverage tool documentation for the proper output format
216182

217183
### Issue: Wrong root element
218184

@@ -223,8 +189,8 @@ Coverage report root element is 'report', expected 'coverage' (Cobertura format)
223189

224190
**Solution:**
225191
- 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`
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
228194

229195
## Integration with CI/CD
230196

src/pysonar_scanner/__main__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,8 @@ def run_dry_run(config: dict[str, Any]) -> int:
140140

141141
DryRunReporter.report_configuration(config)
142142

143-
validation_result = ValidationResult()
144-
145143
coverage_paths = config.get(SONAR_PYTHON_COVERAGE_REPORT_PATHS)
146144
project_base_dir = config.get(SONAR_PROJECT_BASE_DIR, ".")
147-
CoverageReportValidator.validate_coverage_reports(coverage_paths, project_base_dir, validation_result)
145+
validation_result = CoverageReportValidator.validate_coverage_reports(coverage_paths, project_base_dir)
148146

149147
return DryRunReporter.report_validation_results(validation_result)

src/pysonar_scanner/dry_run_reporter.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def report_configuration(config: dict[str, Any]) -> None:
4747
{
4848
SONAR_PROJECT_KEY: config.get(SONAR_PROJECT_KEY),
4949
SONAR_PROJECT_NAME: config.get(SONAR_PROJECT_NAME),
50-
SONAR_ORGANIZATION: config.get(SONAR_ORGANIZATION, "N/A (likely SonarQube Server)"),
50+
SONAR_ORGANIZATION: config.get(SONAR_ORGANIZATION, "N/A"),
5151
},
5252
)
5353

@@ -90,9 +90,9 @@ def report_validation_results(validation_result: "ValidationResult") -> int:
9090
else:
9191
logging.warning("✗ Configuration validation FAILED with the following issues:")
9292
for error in validation_result.errors:
93-
logging.error(f" {error}")
93+
logging.error(f" {error}")
9494
for warning in validation_result.warnings:
95-
logging.warning(f" {warning}")
95+
logging.warning(f" {warning}")
9696
logging.info("=" * 80)
9797
return 1
9898

@@ -136,18 +136,20 @@ class CoverageReportValidator:
136136
def validate_coverage_reports(
137137
coverage_paths: Optional[str],
138138
project_base_dir: str,
139-
validation_result: ValidationResult,
140-
) -> None:
139+
) -> ValidationResult:
140+
validation_result = ValidationResult()
141141
if not coverage_paths:
142142
validation_result.add_warning("No coverage report paths specified")
143-
return
143+
return validation_result
144144

145145
base_path = Path(project_base_dir)
146146
report_paths = [p.strip() for p in coverage_paths.split(",")]
147147

148148
for report_path in report_paths:
149149
CoverageReportValidator._validate_single_report(report_path, base_path, validation_result)
150150

151+
return validation_result
152+
151153
@staticmethod
152154
def _validate_single_report(report_path: str, base_path: Path, validation_result: ValidationResult) -> None:
153155
# Resolve relative path
@@ -170,7 +172,7 @@ def _validate_single_report(report_path: str, base_path: Path, validation_result
170172
f"Coverage report root element is '{root.tag}', expected 'coverage' (Cobertura format)"
171173
)
172174
else:
173-
validation_result.add_info(f"Coverage report is valid Cobertura XML: {report_path}")
175+
validation_result.add_info(f"Coverage report check passed: {report_path}")
174176
except PermissionError:
175177
validation_result.add_error(f"Coverage report is not readable (permission denied): {report_path}")
176178
except UnicodeDecodeError:
@@ -179,7 +181,7 @@ def _validate_single_report(report_path: str, base_path: Path, validation_result
179181
)
180182
except ET.ParseError as e:
181183
validation_result.add_error(
182-
f"Coverage report is not valid XML (Cobertura format): {report_path}\n Parse error: {str(e)}"
184+
f"Coverage report is not valid XML (Cobertura format): {report_path} (Parse error: {str(e)})"
183185
)
184186
except Exception as e:
185-
validation_result.add_error(f"Error validating coverage report format: {report_path}\n Error: {str(e)}")
187+
validation_result.add_error(f"Error validating coverage report format: {report_path} (Error: {str(e)})")

tests/unit/test_dry_run.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,16 @@ def setUp(self):
145145
self.setUpPyfakefs()
146146

147147
def test_validate_coverage_reports_no_paths(self):
148-
result = ValidationResult()
149-
CoverageReportValidator.validate_coverage_reports(None, ".", result)
148+
result = CoverageReportValidator.validate_coverage_reports(None, ".")
150149

151150
assert result.is_valid()
152151
assert len(result.warnings) == 1
153152
assert "No coverage report paths specified" in result.warnings[0]
154153

155154
def test_validate_single_report_file_not_found(self):
156155
self.fs.create_dir("/project")
157-
result = ValidationResult()
158156

159-
CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project", result)
157+
result = CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project")
160158

161159
assert not result.is_valid()
162160
assert len(result.errors) == 1
@@ -166,51 +164,46 @@ def test_validate_single_report_file_not_found(self):
166164
def test_validate_single_report_valid_cobertura(self, mock_logging):
167165
self.fs.create_dir("/project")
168166
self.fs.create_file("/project/coverage.xml", contents='<?xml version="1.0"?>\n<coverage></coverage>')
169-
result = ValidationResult()
170167

171-
CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project", result)
168+
result = CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project")
172169

173170
assert result.is_valid()
174171
assert len(result.warnings) == 0
175172
assert len(result.infos) == 1
176-
assert "valid Cobertura XML" in result.infos[0]
173+
assert "Coverage report check passed" in result.infos[0]
177174

178175
def test_validate_multiple_coverage_reports(self):
179176
self.fs.create_dir("/project")
180177
self.fs.create_file("/project/coverage1.xml", contents='<?xml version="1.0"?>\n<coverage></coverage>')
181178
self.fs.create_file("/project/coverage2.xml", contents='<?xml version="1.0"?>\n<coverage></coverage>')
182-
result = ValidationResult()
183179

184-
CoverageReportValidator.validate_coverage_reports("coverage1.xml, coverage2.xml", "/project", result)
180+
result = CoverageReportValidator.validate_coverage_reports("coverage1.xml, coverage2.xml", "/project")
185181

186182
assert result.is_valid()
187183

188184
def test_validate_report_not_a_file(self):
189185
self.fs.create_dir("/project")
190186
self.fs.create_dir("/project/coverage.xml")
191-
result = ValidationResult()
192187

193-
CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project", result)
188+
result = CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project")
194189

195190
assert not result.is_valid()
196191
assert "not a file" in result.errors[0]
197192

198193
def test_validate_report_invalid_xml(self):
199194
self.fs.create_dir("/project")
200195
self.fs.create_file("/project/coverage.xml", contents="not valid xml")
201-
result = ValidationResult()
202196

203-
CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project", result)
197+
result = CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project")
204198

205199
assert not result.is_valid()
206200
assert "not valid XML" in result.errors[0]
207201

208202
def test_validate_report_wrong_root_element(self):
209203
self.fs.create_dir("/project")
210204
self.fs.create_file("/project/coverage.xml", contents='<?xml version="1.0"?>\n<report></report>')
211-
result = ValidationResult()
212205

213-
CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project", result)
206+
result = CoverageReportValidator.validate_coverage_reports("coverage.xml", "/project")
214207

215208
assert result.is_valid()
216209
assert len(result.warnings) == 1
@@ -220,9 +213,8 @@ def test_validate_report_wrong_root_element(self):
220213
def test_validate_mixed_valid_and_missing_reports(self):
221214
self.fs.create_dir("/project")
222215
self.fs.create_file("/project/exists.xml", contents='<?xml version="1.0"?>\n<coverage></coverage>')
223-
result = ValidationResult()
224216

225-
CoverageReportValidator.validate_coverage_reports("exists.xml, missing.xml", "/project", result)
217+
result = CoverageReportValidator.validate_coverage_reports("exists.xml, missing.xml", "/project")
226218

227219
assert not result.is_valid()
228220
assert len(result.errors) == 1

0 commit comments

Comments
 (0)