Skip to content

Commit c4d750d

Browse files
committed
Adds support for JUnit XML uploads
1 parent a2018ae commit c4d750d

File tree

5 files changed

+153
-19
lines changed

5 files changed

+153
-19
lines changed

.github/workflows/python-tox.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ jobs:
3939
pip install tox tox-gh-actions
4040
4141
- name: Test with tox and upload coverage results
42-
run: tox -- --codecov --codecov-token=${{ secrets.CODECOV_TOKEN }}
42+
run: tox -- --codecov --codecov-token=${{ secrets.CODECOV_TOKEN }} --junit-xml=junit.xml -o junit_family=legacy

src/pytest_codecov/__init__.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,24 @@ def pytest_addoption(parser, pluginmanager):
8585
default=True,
8686
help='Don\'t upload coverage results on test failure'
8787
)
88+
group.addoption(
89+
'--codecov-exclude-junit-xml',
90+
action='store_false',
91+
dest='codecov_junit_xml',
92+
default=True,
93+
help='Don\'t upload the junit xml file'
94+
)
8895

8996

9097
class CodecovPlugin:
9198

9299
def upload_report(self, terminalreporter, config, cov):
100+
option = config.option
93101
uploader = codecov.CodecovUploader(
94-
config.option.codecov_slug,
95-
commit=config.option.codecov_commit,
96-
branch=config.option.codecov_branch,
97-
token=config.option.codecov_token,
102+
option.codecov_slug,
103+
commit=option.codecov_commit,
104+
branch=option.codecov_branch,
105+
token=option.codecov_token,
98106
)
99107
uploader.write_network_files(git.ls_files())
100108
from coverage.misc import CoverageException
@@ -110,14 +118,21 @@ def upload_report(self, terminalreporter, config, cov):
110118
terminalreporter.line('')
111119
return
112120

113-
if config.option.codecov_dump:
121+
xmlpath = option.xmlpath if option.codecov_junit_xml else None
122+
if xmlpath and os.path.isfile(xmlpath):
123+
uploader.add_junit_xml(xmlpath)
124+
has_junit_xml = True
125+
else:
126+
has_junit_xml = False
127+
128+
if option.codecov_dump:
114129
terminalreporter.section('Prepared Codecov.io payload')
115130
terminalreporter.write_line(uploader.get_payload())
116131
return
117132

118133
terminalreporter.section('Codecov.io upload')
119134

120-
if not config.option.codecov_slug:
135+
if not option.codecov_slug:
121136
terminalreporter.write_line(
122137
'ERROR: Failed to determine git repository slug. '
123138
'Cannot upload without a valid slug.',
@@ -126,24 +141,35 @@ def upload_report(self, terminalreporter, config, cov):
126141
)
127142
terminalreporter.line('')
128143
return
129-
if not config.option.codecov_branch:
144+
if not option.codecov_branch:
130145
terminalreporter.write_line(
131146
'WARNING: Failed to determine git repository branch.',
132147
yellow=True,
133148
bold=True,
134149
)
135-
if not config.option.codecov_commit:
150+
if not option.codecov_commit:
136151
terminalreporter.write_line(
137152
'WARNING: Failed to determine git commit.',
138153
yellow=True,
139154
bold=True,
140155
)
156+
if has_junit_xml and config.getini('junit_family') != 'legacy':
157+
terminalreporter.write_line(
158+
'INFO: We recommend using junit_family=legacy with CodeCov.',
159+
blue=True,
160+
bold=True,
161+
)
162+
141163
terminalreporter.write_line(
142164
'Environment:\n'
143-
f'Slug: {config.option.codecov_slug}\n'
144-
f'Branch: {config.option.codecov_branch}\n'
145-
f'Commit: {config.option.codecov_commit}\n'
165+
f'Slug: {option.codecov_slug}\n'
166+
f'Branch: {option.codecov_branch}\n'
167+
f'Commit: {option.codecov_commit}\n'
146168
)
169+
if has_junit_xml:
170+
terminalreporter.write_line(
171+
'JUnit XML file detected and included in upload.\n'
172+
)
147173
try:
148174
terminalreporter.write_line('Pinging codecov API...')
149175
uploader.ping()
@@ -178,6 +204,10 @@ def pytest_terminal_summary(self, terminalreporter, exitstatus, config):
178204

179205

180206
def pytest_configure(config): # pragma: no cover
207+
# NOTE: Don't report codecov results on worker nodes
208+
if hasattr(config, 'workerinput'):
209+
return
210+
181211
# NOTE: if cov is missing we fail silently
182212
if config.option.codecov and config.pluginmanager.has_plugin('_cov'):
183213
config.pluginmanager.register(CodecovPlugin())

src/pytest_codecov/codecov.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ def add_coverage_report(self, cov, filename='coverage.xml', **kwargs):
4040
self._buffer.write(xml_report.read())
4141
self._buffer.write('\n<<<<<< EOF')
4242

43+
def add_junit_xml(self, path):
44+
with open(path, 'r') as junit_xml:
45+
self._buffer.write('\n# path=./junit.xml\n')
46+
self._buffer.write(junit_xml.read())
47+
self._buffer.write('\n<<<<<< EOF')
48+
4349
def get_payload(self):
4450
return self._buffer.getvalue()
4551

tests/conftest.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,19 @@ class DummyUploader:
113113
# TODO: Implement some basic behavior, so we can test
114114
# more exhaustively.
115115

116-
def __init__(self, slug, **kwargs):
117-
self.fail_report_generation = False
116+
def __init__(self, factory, slug, **kwargs):
117+
self.factory = factory
118118

119119
def write_network_files(self, files):
120120
pass
121121

122122
def add_coverage_report(self, cov, **kwargs):
123-
if self.fail_report_generation:
123+
if self.factory.fail_report_generation:
124124
raise CoverageException('test exception')
125125

126+
def add_junit_xml(self, path):
127+
self.factory.junit_xml = path
128+
126129
def get_payload(self):
127130
return 'stub'
128131

@@ -135,12 +138,15 @@ def upload(self):
135138

136139
class DummyUploaderFactory:
137140

138-
fail_report_generation = False
141+
def __init__(self):
142+
self.fail_report_generation = False
143+
self.junit_xml = None
139144

140145
def __call__(self, slug, **kwargs):
141-
inst = DummyUploader(slug, **kwargs)
142-
inst.fail_report_generation = self.fail_report_generation
143-
return inst
146+
return DummyUploader(self, slug, **kwargs)
147+
148+
def clear(self):
149+
self.junit_xml = None
144150

145151

146152
@pytest.fixture

tests/test_plugin.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,98 @@ def test_upload_report(pytester, dummy_reporter, dummy_uploader,
113113
) in dummy_reporter.text
114114

115115

116+
def test_upload_report_junit(pytester, dummy_reporter, dummy_uploader,
117+
dummy_cov, no_gitpython, tmp_path):
118+
119+
# create a junit xml
120+
junit_xml = tmp_path / 'junit.xml'
121+
junit_xml.write_text('foo')
122+
config = pytester.parseconfig(
123+
f'--junit-xml={junit_xml}',
124+
'-o',
125+
'junit_family=legacy',
126+
'--codecov',
127+
'--codecov-token=12345678-1234-1234-1234-1234567890ab',
128+
'--codecov-slug=foo/bar',
129+
'--codecov-branch=master',
130+
'--codecov-commit=deadbeef'
131+
)
132+
plugin = CodecovPlugin()
133+
plugin.upload_report(dummy_reporter, config, dummy_cov)
134+
assert (
135+
'Environment:\n'
136+
'Slug: foo/bar\n'
137+
'Branch: master\n'
138+
'Commit: deadbeef\n'
139+
'\n'
140+
'JUnit XML file detected and included in upload.\n'
141+
) in dummy_reporter.text
142+
assert (
143+
'INFO: We recommend using junit_family=legacy with CodeCov.'
144+
) not in dummy_reporter.text
145+
assert dummy_uploader.junit_xml == str(junit_xml)
146+
147+
148+
def test_upload_report_junit_info(pytester, dummy_reporter, dummy_uploader,
149+
dummy_cov, no_gitpython, tmp_path):
150+
151+
# create a junit xml
152+
junit_xml = tmp_path / 'junit.xml'
153+
junit_xml.write_text('foo')
154+
config = pytester.parseconfig(
155+
f'--junit-xml={junit_xml}',
156+
'--codecov',
157+
'--codecov-token=12345678-1234-1234-1234-1234567890ab',
158+
'--codecov-slug=foo/bar',
159+
'--codecov-branch=master',
160+
'--codecov-commit=deadbeef'
161+
)
162+
plugin = CodecovPlugin()
163+
plugin.upload_report(dummy_reporter, config, dummy_cov)
164+
assert (
165+
'INFO: We recommend using junit_family=legacy with CodeCov.\n'
166+
'Environment:\n'
167+
'Slug: foo/bar\n'
168+
'Branch: master\n'
169+
'Commit: deadbeef\n'
170+
'\n'
171+
'JUnit XML file detected and included in upload.\n'
172+
) in dummy_reporter.text
173+
assert dummy_uploader.junit_xml == str(junit_xml)
174+
175+
176+
def test_no_upload_report_junit(pytester, dummy_reporter, dummy_uploader,
177+
dummy_cov, no_gitpython, tmp_path):
178+
179+
# create a junit xml
180+
junit_xml = tmp_path / 'junit.xml'
181+
junit_xml.write_text('foo')
182+
config = pytester.parseconfig(
183+
f'--junit-xml={junit_xml}',
184+
'--codecov',
185+
'--codecov-token=12345678-1234-1234-1234-1234567890ab',
186+
'--codecov-slug=foo/bar',
187+
'--codecov-branch=master',
188+
'--codecov-commit=deadbeef',
189+
'--codecov-exclude-junit-xml'
190+
)
191+
plugin = CodecovPlugin()
192+
plugin.upload_report(dummy_reporter, config, dummy_cov)
193+
assert (
194+
'Environment:\n'
195+
'Slug: foo/bar\n'
196+
'Branch: master\n'
197+
'Commit: deadbeef\n'
198+
) in dummy_reporter.text
199+
assert (
200+
'JUnit XML file detected and included in upload.'
201+
) not in dummy_reporter.text
202+
assert (
203+
'INFO: We recommend using junit_family=legacy with CodeCov.'
204+
) not in dummy_reporter.text
205+
assert dummy_uploader.junit_xml is None
206+
207+
116208
def test_upload_report_generation_failure(
117209
pytester, dummy_reporter, dummy_uploader,
118210
dummy_cov, no_gitpython

0 commit comments

Comments
 (0)