Skip to content

Commit aea1824

Browse files
committed
Fixes JUnit XML upload
1 parent c4d750d commit aea1824

File tree

6 files changed

+72
-29
lines changed

6 files changed

+72
-29
lines changed

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ legacy_tox_ini = """
8383
[testenv]
8484
setenv =
8585
py{38,39,310,311,312,313}: COVERAGE_FILE = .coverage.{envname}
86-
commands = pytest --cov --cov-report= {posargs:tests}
86+
commands =
87+
pytest --cov --cov-report= {posargs:tests}
88+
pytest -n2 --cov --cov-report= {posargs:tests}
8789
deps =
8890
pytest
8991
coverage
9092
pytest-cov
93+
pytest-xdist
9194
.
9295
GitPython
9396

src/pytest_codecov/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def upload_report(self, terminalreporter, config, cov):
104104
branch=option.codecov_branch,
105105
token=option.codecov_token,
106106
)
107-
uploader.write_network_files(git.ls_files())
107+
uploader.add_network_files(git.ls_files())
108108
from coverage.misc import CoverageException
109109
try:
110110
uploader.add_coverage_report(cov)
@@ -155,7 +155,7 @@ def upload_report(self, terminalreporter, config, cov):
155155
)
156156
if has_junit_xml and config.getini('junit_family') != 'legacy':
157157
terminalreporter.write_line(
158-
'INFO: We recommend using junit_family=legacy with CodeCov.',
158+
'INFO: We recommend using junit_family=legacy with Codecov.',
159159
blue=True,
160160
bold=True,
161161
)

src/pytest_codecov/codecov.py

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import gzip
22
import io
3+
import json
34
import requests
45
import tempfile
6+
import zlib
7+
from base64 import b64encode
58
from urllib.parse import urljoin
69

710

@@ -23,31 +26,38 @@ def __init__(self, slug, commit=None, branch=None, token=None):
2326
self.commit = commit
2427
self.branch = branch
2528
self.token = token
26-
self.store_url = None
27-
self._buffer = io.StringIO()
29+
self._coverage_store_url = None
30+
self._coverage_buffer = io.StringIO()
31+
self._test_result_store_url = None
32+
self._test_result_files = []
2833

29-
def write_network_files(self, files):
30-
self._buffer.write(
34+
def add_network_files(self, files):
35+
self._coverage_buffer.write(
3136
'\n'.join(files + ['<<<<<< network'])
3237
)
3338

3439
def add_coverage_report(self, cov, filename='coverage.xml', **kwargs):
3540
with tempfile.NamedTemporaryFile(mode='r') as xml_report:
3641
# embed xml report
37-
self._buffer.write(f'\n# path=./{filename}\n')
42+
self._coverage_buffer.write(f'\n# path=./{filename}\n')
3843
cov.xml_report(outfile=xml_report.name)
3944
xml_report.seek(0)
40-
self._buffer.write(xml_report.read())
41-
self._buffer.write('\n<<<<<< EOF')
42-
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')
45+
self._coverage_buffer.write(xml_report.read())
46+
self._coverage_buffer.write('\n<<<<<< EOF')
47+
48+
def add_junit_xml(self, path, filename='junit.xml'):
49+
with open(path, 'rb') as junit_xml:
50+
self._test_result_files.append({
51+
'filename': filename,
52+
'format': 'base64+compressed',
53+
'data': b64encode(
54+
zlib.compress(junit_xml.read())
55+
).decode('ascii'),
56+
'labels': '',
57+
})
4858

4959
def get_payload(self):
50-
return self._buffer.getvalue()
60+
return self._coverage_buffer.getvalue()
5161

5262
def ping(self):
5363
if not self.slug:
@@ -83,10 +93,30 @@ def ping(self):
8393
raise CodecovError(
8494
f'Invalid response from codecov API:\n{response.text}'
8595
)
86-
self.store_url = lines[1]
96+
self._coverage_store_url = lines[1]
97+
98+
if not self._test_result_files:
99+
return
100+
101+
headers = {} if self.token is None else {
102+
'Authorization': f'token {self.token}',
103+
'User-Agent': package()
104+
}
105+
data = {
106+
'slug': self.slug,
107+
'branch': self.branch or '',
108+
'commit': self.commit or '',
109+
}
110+
api_url = urljoin(self.api_endpoint, '/upload/test_results/v1')
111+
response = requests.post(api_url, headers=headers, json=data)
112+
if response.ok:
113+
# TODO: Fail more loudly?
114+
url = response.json()['raw_upload_location']
115+
if url.startswith(self.storage_endpoint):
116+
self._test_result_store_url = url
87117

88118
def upload(self):
89-
if not self.store_url:
119+
if not self._coverage_store_url:
90120
raise CodecovError('Need to ping API before upload.')
91121

92122
headers = {
@@ -98,10 +128,20 @@ def upload(self):
98128
payload.write(self.get_payload().encode('utf-8'))
99129
gz_payload.seek(0)
100130
response = requests.put(
101-
self.store_url, headers=headers, data=gz_payload
131+
self._coverage_store_url, headers=headers, data=gz_payload
102132
)
103133

104134
if not response.ok:
105135
raise CodecovError('Failed to upload report to storage endpoint.')
106136

107-
self.store_url = None # NOTE: Invalidate store url after upload
137+
self._coverage_store_url = None
138+
139+
if not self._test_result_store_url or not self._test_result_files:
140+
return
141+
142+
json_payload = json.dumps({
143+
'test_result_files': self._test_result_files
144+
}).encode('ascii')
145+
# TODO: Fail more loudly?
146+
requests.put(self._test_result_store_url, data=json_payload)
147+
self._test_result_store_url = None

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class DummyUploader:
116116
def __init__(self, factory, slug, **kwargs):
117117
self.factory = factory
118118

119-
def write_network_files(self, files):
119+
def add_network_files(self, files):
120120
pass
121121

122122
def add_coverage_report(self, cov, **kwargs):

tests/test_codecov.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_init():
1515

1616
def test_write_network_files():
1717
uploader = CodecovUploader('seantis/pytest-codecov')
18-
uploader.write_network_files(['foo.py'])
18+
uploader.add_network_files(['foo.py'])
1919
assert uploader.get_payload() == (
2020
'foo.py\n'
2121
'<<<<<< network'
@@ -24,7 +24,7 @@ def test_write_network_files():
2424

2525
def test_add_coverage_report(dummy_cov):
2626
uploader = CodecovUploader('seantis/pytest-codecov')
27-
uploader.write_network_files(['foo.py'])
27+
uploader.add_network_files(['foo.py'])
2828
uploader.add_coverage_report(dummy_cov)
2929
assert uploader.get_payload() == (
3030
'foo.py\n'
@@ -55,7 +55,7 @@ def test_ping(dummy_cov, mock_requests):
5555

5656
mock_requests.set_response(f'codecov.io\n{uploader.storage_endpoint}')
5757
uploader.ping()
58-
assert uploader.store_url == uploader.storage_endpoint
58+
assert uploader._coverage_store_url == uploader.storage_endpoint
5959

6060
# TODO: Verify correct url/headers/params
6161

@@ -80,6 +80,6 @@ def test_upload(dummy_cov, mock_requests):
8080

8181
mock_requests.set_response('')
8282
uploader.upload()
83-
assert uploader.store_url is None
83+
assert uploader._coverage_store_url is None
8484

8585
# TODO: Verify correct url/headers/params

tests/test_plugin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_upload_report_junit(pytester, dummy_reporter, dummy_uploader,
140140
'JUnit XML file detected and included in upload.\n'
141141
) in dummy_reporter.text
142142
assert (
143-
'INFO: We recommend using junit_family=legacy with CodeCov.'
143+
'INFO: We recommend using junit_family=legacy with Codecov.'
144144
) not in dummy_reporter.text
145145
assert dummy_uploader.junit_xml == str(junit_xml)
146146

@@ -162,7 +162,7 @@ def test_upload_report_junit_info(pytester, dummy_reporter, dummy_uploader,
162162
plugin = CodecovPlugin()
163163
plugin.upload_report(dummy_reporter, config, dummy_cov)
164164
assert (
165-
'INFO: We recommend using junit_family=legacy with CodeCov.\n'
165+
'INFO: We recommend using junit_family=legacy with Codecov.\n'
166166
'Environment:\n'
167167
'Slug: foo/bar\n'
168168
'Branch: master\n'
@@ -200,7 +200,7 @@ def test_no_upload_report_junit(pytester, dummy_reporter, dummy_uploader,
200200
'JUnit XML file detected and included in upload.'
201201
) not in dummy_reporter.text
202202
assert (
203-
'INFO: We recommend using junit_family=legacy with CodeCov.'
203+
'INFO: We recommend using junit_family=legacy with Codecov.'
204204
) not in dummy_reporter.text
205205
assert dummy_uploader.junit_xml is None
206206

0 commit comments

Comments
 (0)