0
# Jenkins Compatibility Tools
1
2
Transformation utilities for ensuring XML report compatibility with various versions of Jenkins xUnit plugins. Addresses schema validation differences and provides tools for adapting reports to specific CI/CD requirements.
3
4
## Capabilities
5
6
### XML Report Transformation
7
8
Transform XML reports to be compatible with specific versions of Jenkins xUnit plugin that have stricter schema validation requirements.
9
10
```python { .api }
11
def transform(xml_data):
12
"""
13
Transform XML report for Jenkins xUnit plugin compatibility.
14
15
Removes attributes that cause validation failures in Jenkins xUnit plugin
16
version 1.104+ which uses stricter XSD validation.
17
18
Parameters:
19
- xml_data: bytes, input XML report data
20
21
Returns:
22
- bytes: transformed XML report data
23
24
Dependencies:
25
- lxml: required for XML transformation
26
"""
27
```
28
29
## Jenkins Plugin Compatibility
30
31
### Jenkins JUnit Plugin
32
33
The standard JUnit plugin (https://plugins.jenkins.io/junit/) has relaxed validation:
34
35
- **Schema Validation**: None (at time of writing)
36
- **Compatibility**: Standard unittest-xml-reporting output works without modification
37
- **Attributes**: Accepts all attributes including file, line, timestamp
38
39
```python
40
import unittest
41
import xmlrunner
42
43
# Standard output works with Jenkins JUnit plugin
44
unittest.main(
45
testRunner=xmlrunner.XMLTestRunner(output='test-reports'),
46
failfast=False, buffer=False, catchbreak=False
47
)
48
```
49
50
### Jenkins xUnit Plugin v1.100
51
52
Older version with more lenient XSD validation:
53
54
- **Schema**: Uses relaxed junit-10.xsd
55
- **Compatibility**: Standard unittest-xml-reporting output works
56
- **Validation**: Performs XSD validation but accepts additional attributes
57
58
### Jenkins xUnit Plugin v1.104+
59
60
Newer version with strict XSD validation:
61
62
- **Schema**: Uses strict junit-10.xsd
63
- **Compatibility**: Requires transformation to remove unsupported attributes
64
- **Validation**: Strict XSD validation fails on extra attributes
65
66
## Usage Examples
67
68
### Basic Transformation for Jenkins xUnit v1.104+
69
70
```python
71
import io
72
import unittest
73
import xmlrunner
74
from xmlrunner.extra.xunit_plugin import transform
75
76
# Generate XML report in memory
77
output = io.BytesIO()
78
unittest.main(
79
testRunner=xmlrunner.XMLTestRunner(output=output),
80
failfast=False, buffer=False, catchbreak=False, exit=False
81
)
82
83
# Transform for Jenkins xUnit plugin compatibility
84
transformed_xml = transform(output.getvalue())
85
86
# Write to file for Jenkins consumption
87
with open('TEST-report.xml', 'wb') as report:
88
report.write(transformed_xml)
89
```
90
91
### CI/CD Pipeline Integration
92
93
```python
94
# ci_test_runner.py
95
import io
96
import sys
97
import unittest
98
import xmlrunner
99
from xmlrunner.extra.xunit_plugin import transform
100
101
def run_tests_for_jenkins():
102
"""Run tests and generate Jenkins-compatible XML reports."""
103
# Capture XML output
104
output = io.BytesIO()
105
106
# Run tests with XML reporting
107
runner = xmlrunner.XMLTestRunner(
108
output=output,
109
verbosity=2,
110
elapsed_times=True
111
)
112
113
# Discover and run tests
114
loader = unittest.TestLoader()
115
suite = loader.discover('tests', pattern='test_*.py')
116
result = runner.run(suite)
117
118
# Transform for Jenkins compatibility
119
xml_data = output.getvalue()
120
transformed_xml = transform(xml_data)
121
122
# Write Jenkins-compatible report
123
with open('junit-report.xml', 'wb') as report_file:
124
report_file.write(transformed_xml)
125
126
# Return exit code based on test results
127
return 0 if result.wasSuccessful() else 1
128
129
if __name__ == '__main__':
130
sys.exit(run_tests_for_jenkins())
131
```
132
133
### Batch File Transformation
134
135
```python
136
import os
137
import glob
138
from xmlrunner.extra.xunit_plugin import transform
139
140
def transform_reports_directory(input_dir, output_dir):
141
"""Transform all XML reports in a directory for Jenkins compatibility."""
142
os.makedirs(output_dir, exist_ok=True)
143
144
for xml_file in glob.glob(os.path.join(input_dir, 'TEST-*.xml')):
145
# Read original report
146
with open(xml_file, 'rb') as f:
147
xml_data = f.read()
148
149
# Transform for compatibility
150
transformed_xml = transform(xml_data)
151
152
# Write transformed report
153
filename = os.path.basename(xml_file)
154
output_path = os.path.join(output_dir, filename)
155
with open(output_path, 'wb') as f:
156
f.write(transformed_xml)
157
158
print(f"Transformed {xml_file} -> {output_path}")
159
160
# Usage
161
transform_reports_directory('raw-reports', 'jenkins-reports')
162
```
163
164
## Transformation Details
165
166
### Removed Attributes
167
168
The transformation removes the following attributes that cause validation failures:
169
170
- **testcase/@file**: Source file path
171
- **testcase/@line**: Source line number
172
- **testcase/@timestamp**: Individual test timestamp
173
174
### Preserved Elements
175
176
All other elements and attributes are preserved:
177
178
- **testsuite** attributes (name, tests, failures, errors, skipped, time, timestamp)
179
- **testcase** attributes (classname, name, time)
180
- **failure/error/skipped** elements and their attributes
181
- **system-out/system-err** content
182
- **properties** elements
183
184
### XSLT Implementation
185
186
The transformation uses XSLT (Extensible Stylesheet Language Transformations):
187
188
```xslt
189
<?xml version="1.0" encoding="UTF-8"?>
190
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
191
<xsl:output method="xml" indent="yes" />
192
193
<!-- Remove problematic attributes -->
194
<xsl:template match="//testcase/@file" />
195
<xsl:template match="//testcase/@line" />
196
<xsl:template match="//testcase/@timestamp" />
197
198
<!-- Copy everything else -->
199
<xsl:template match="node()|@*">
200
<xsl:copy>
201
<xsl:apply-templates select="node()|@*" />
202
</xsl:copy>
203
</xsl:template>
204
</xsl:stylesheet>
205
```
206
207
## Error Handling
208
209
### Dependency Requirements
210
211
The transformation requires lxml:
212
213
```python
214
try:
215
from xmlrunner.extra.xunit_plugin import transform
216
except ImportError:
217
print("lxml is required for Jenkins compatibility transformation")
218
print("Install with: pip install lxml")
219
sys.exit(1)
220
```
221
222
### Invalid XML Handling
223
224
```python
225
from lxml import etree
226
from xmlrunner.extra.xunit_plugin import transform
227
228
def safe_transform(xml_data):
229
"""Safely transform XML with error handling."""
230
try:
231
return transform(xml_data)
232
except etree.XMLSyntaxError as e:
233
print(f"Invalid XML input: {e}")
234
return xml_data # Return original on error
235
except Exception as e:
236
print(f"Transformation error: {e}")
237
return xml_data
238
```
239
240
## Integration Strategies
241
242
### Conditional Transformation
243
244
```python
245
import os
246
from xmlrunner.extra.xunit_plugin import transform
247
248
def maybe_transform_for_jenkins(xml_data):
249
"""Transform XML only if Jenkins xUnit plugin version requires it."""
250
jenkins_version = os.getenv('JENKINS_XUNIT_VERSION', '1.100')
251
252
if jenkins_version >= '1.104':
253
return transform(xml_data)
254
return xml_data
255
```
256
257
### Custom Transformation Pipeline
258
259
```python
260
from xmlrunner.extra.xunit_plugin import transform
261
import lxml.etree as etree
262
263
def custom_jenkins_transform(xml_data, remove_timestamps=True,
264
remove_file_info=True):
265
"""Custom transformation with configurable options."""
266
if not (remove_timestamps or remove_file_info):
267
return xml_data
268
269
# Parse XML
270
doc = etree.XML(xml_data)
271
272
# Remove attributes based on options
273
if remove_file_info:
274
for elem in doc.xpath('//testcase'):
275
elem.attrib.pop('file', None)
276
elem.attrib.pop('line', None)
277
278
if remove_timestamps:
279
for elem in doc.xpath('//testcase'):
280
elem.attrib.pop('timestamp', None)
281
282
# Return transformed XML
283
return etree.tostring(doc, pretty_print=True, encoding='UTF-8')
284
```
285
286
This transformation system ensures unittest-xml-reporting works seamlessly with various Jenkins plugin versions while maintaining all essential test result information.