Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 2165dc1

Browse files
authored
Test on all OSs (#667)
This adds Windows and MacOS to our CI test matrix. It also changes quite a lot of internal functionality and tests to use `Path.as_posix()` which always prints paths with forward slashes. This was primarily done to make a ton of tests work on Windows, but IMO it is also a good idea to normalize paths internally to use forward slashes.
1 parent c602edd commit 2165dc1

File tree

17 files changed

+164
-144
lines changed

17 files changed

+164
-144
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,12 @@ jobs:
7070
codecovcli create-report -t ${{ secrets.CODECOV_TOKEN }} --git-service github
7171
7272
build-test-upload:
73-
runs-on: ubuntu-latest
7473
strategy:
7574
fail-fast: false
7675
matrix:
77-
include:
78-
- python-version: "3.13"
79-
- python-version: "3.12"
80-
- python-version: "3.11"
81-
- python-version: "3.10"
82-
- python-version: "3.9"
76+
os: [ubuntu-latest, windows-latest, macos-latest]
77+
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
78+
runs-on: ${{matrix.os}}
8379
steps:
8480
- uses: actions/checkout@v4
8581
with:
@@ -97,7 +93,7 @@ jobs:
9793
pip install -r tests/requirements.txt
9894
- name: Test with pytest
9995
run: |
100-
pytest --cov --junitxml=${{matrix.python-version}}junit.xml
96+
pytest --cov --junitxml=${{matrix.os}}-${{matrix.python-version}}junit.xml
10197
env:
10298
CODECOV_ENV: test
10399
- name: Dogfooding codecov-cli
@@ -109,8 +105,8 @@ jobs:
109105
if: ${{ !cancelled() }}
110106
uses: actions/upload-artifact@v4
111107
with:
112-
name: ${{matrix.python-version}}junit.xml
113-
path: ${{matrix.python-version}}junit.xml
108+
name: ${{matrix.os}}-${{matrix.python-version}}junit.xml
109+
path: ${{matrix.os}}-${{matrix.python-version}}junit.xml
114110

115111
process-test-results:
116112
if: ${{ always() }}

codecov_cli/commands/process_test_results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,6 @@ def generate_message_payload(
268268
payload.passed += 1
269269
except ParserError as err:
270270
raise click.ClickException(
271-
f"Error parsing {str(result.get_filename(), 'utf8')} with error: {err}"
271+
f"Error parsing {result.get_filename()} with error: {err}"
272272
)
273273
return payload

codecov_cli/helpers/folder_searcher.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def _is_included(
1717
):
1818
return filename_include_regex.match(path.name) and (
1919
multipart_include_regex is None
20-
or multipart_include_regex.match(str(path.resolve()))
20+
or multipart_include_regex.match(path.resolve().as_posix())
2121
)
2222

2323

@@ -29,7 +29,8 @@ def _is_excluded(
2929
return (
3030
filename_exclude_regex is not None and filename_exclude_regex.match(path.name)
3131
) or (
32-
multipart_exclude_regex is not None and multipart_exclude_regex.match(str(path))
32+
multipart_exclude_regex is not None
33+
and multipart_exclude_regex.match(path.as_posix())
3334
)
3435

3536

@@ -69,7 +70,9 @@ def search_files(
6970
dirs_to_remove.union(
7071
directory
7172
for directory in dirnames
72-
if multipart_exclude_regex.match(str(pathlib.Path(dirpath) / directory))
73+
if multipart_exclude_regex.match(
74+
(pathlib.Path(dirpath) / directory).as_posix()
75+
)
7376
)
7477

7578
for directory in dirs_to_remove:

codecov_cli/helpers/versioning_systems.py

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,38 @@
1212
logger = logging.getLogger("codecovcli")
1313

1414
IGNORE_DIRS = [
15-
'*.egg-info',
16-
'.DS_Store',
17-
'.circleci',
18-
'.env',
19-
'.envs',
20-
'.git',
21-
'.gitignore',
22-
'.mypy_cache',
23-
'.nvmrc',
24-
'.nyc_output',
25-
'.ruff_cache',
26-
'.venv',
27-
'.venvns',
28-
'.virtualenv',
29-
'.virtualenvs',
30-
'__pycache__',
31-
'bower_components',
32-
'build/lib/',
33-
'jspm_packages',
34-
'node_modules',
35-
'vendor',
36-
'virtualenv',
37-
'virtualenvs',
15+
"*.egg-info",
16+
".DS_Store",
17+
".circleci",
18+
".env",
19+
".envs",
20+
".git",
21+
".gitignore",
22+
".mypy_cache",
23+
".nvmrc",
24+
".nyc_output",
25+
".ruff_cache",
26+
".venv",
27+
".venvns",
28+
".virtualenv",
29+
".virtualenvs",
30+
"__pycache__",
31+
"bower_components",
32+
"build/lib/",
33+
"jspm_packages",
34+
"node_modules",
35+
"vendor",
36+
"virtualenv",
37+
"virtualenvs",
3838
]
3939

4040
IGNORE_PATHS = [
41-
'*.gif',
42-
'*.jpeg',
43-
'*.jpg',
44-
'*.md',
45-
'*.png',
46-
'shunit2*',
41+
"*.gif",
42+
"*.jpeg",
43+
"*.jpg",
44+
"*.md",
45+
"*.png",
46+
"shunit2*",
4747
]
4848

4949

@@ -203,12 +203,20 @@ def list_relevant_files(
203203

204204
cmd = [
205205
"find",
206-
dir_to_use,
207-
*chain.from_iterable(["-name", block, "-prune", "-o"] for block in IGNORE_DIRS),
208-
*chain.from_iterable(["-path", block, "-prune", "-o"] for block in IGNORE_PATHS),
206+
str(dir_to_use),
207+
*chain.from_iterable(
208+
["-name", block, "-prune", "-o"] for block in IGNORE_DIRS
209+
),
210+
*chain.from_iterable(
211+
["-path", block, "-prune", "-o"] for block in IGNORE_PATHS
212+
),
209213
"-type",
210214
"f",
211215
"-print",
212216
]
213217
res = subprocess.run(cmd, capture_output=True)
214-
return [filename for filename in res.stdout.decode("unicode_escape").strip().split("\n") if filename]
218+
return [
219+
filename
220+
for filename in res.stdout.decode("unicode_escape").strip().split("\n")
221+
if filename
222+
]

codecov_cli/plugins/xcode.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,66 +42,70 @@ def run_preparation(self, collector) -> PreparationPluginReturn:
4242

4343
filename_include_regex = globs_to_regex(["*.profdata"])
4444

45-
matched_paths = [
46-
str(path)
47-
for path in search_files(
45+
matched_paths = list(
46+
search_files(
4847
folder_to_search=self.derived_data_folder,
4948
folders_to_ignore=[],
5049
filename_include_regex=filename_include_regex,
5150
)
52-
]
51+
)
52+
5353
if not matched_paths:
5454
logger.warning("No swift data found.")
5555
return
5656

5757
logger.info(
5858
"Running swift coverage on the following list of files:",
59-
extra=dict(extra_log_attributes=dict(matched_paths=matched_paths)),
59+
extra=dict(
60+
extra_log_attributes=dict(
61+
matched_paths=[p.as_posix() for p in matched_paths]
62+
)
63+
),
6064
)
6165

6266
for path in matched_paths:
6367
self.swiftcov(path, self.app_name)
6468

6569
return PreparationPluginReturn(success=True, messages="")
6670

67-
def swiftcov(self, path, app_name: str):
71+
def swiftcov(self, path: pathlib.Path, app_name: str) -> None:
6872
directory = os.path.dirname(path)
6973
build_dir = pathlib.Path(re.sub("(Build).*", "Build", directory))
7074

7175
for type in ["app", "framework", "xctest"]:
7276
filename_include_regex = re.compile(translate(f"*.{type}"))
73-
matched_dir_paths = [
74-
str(path)
75-
for path in search_files(
76-
folder_to_search=pathlib.Path(build_dir),
77-
folders_to_ignore=[],
78-
filename_include_regex=filename_include_regex,
79-
search_for_directories=True,
80-
)
81-
]
77+
matched_dir_paths = search_files(
78+
folder_to_search=build_dir,
79+
folders_to_ignore=[],
80+
filename_include_regex=filename_include_regex,
81+
search_for_directories=True,
82+
)
83+
8284
for dir_path in matched_dir_paths:
8385
# proj name without extension
84-
proj = pathlib.Path(dir_path).stem
86+
proj = dir_path.stem
8587
if app_name == "" or (app_name.lower() in proj.lower()):
8688
logger.info(f"+ Building reports for {proj} {type}")
87-
proj_path = pathlib.Path(pathlib.Path(dir_path) / proj)
89+
proj_path = dir_path / proj
8890
dest = (
8991
proj_path
9092
if proj_path.is_file()
91-
else pathlib.Path(f"{dir_path}/Contents/MacOS/{proj}")
93+
else dir_path / "Contents/MacOS/{proj}"
9294
)
9395
output_file_name = f"{proj}.{type}.coverage.txt".replace(" ", "")
9496
self.run_llvm_cov(output_file_name, path, dest)
9597

96-
def run_llvm_cov(self, output_file_name, path, dest):
98+
def run_llvm_cov(
99+
self, output_file_name: str, path: pathlib.Path, dest: pathlib.Path
100+
) -> None:
97101
with open(output_file_name, "w") as output_file:
98102
s = subprocess.run(
99103
[
100104
"xcrun",
101105
"llvm-cov",
102106
"show",
103107
"-instr-profile",
104-
path,
108+
str(path),
105109
str(dest),
106110
],
107111
stdout=output_file,

codecov_cli/services/upload/file_finder.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@ def __init__(
197197
report_type: ReportType = ReportType.COVERAGE,
198198
):
199199
self.search_root = search_root or Path(os.getcwd())
200-
self.folders_to_ignore = list(folders_to_ignore) if folders_to_ignore else []
201-
self.explicitly_listed_files = explicitly_listed_files or None
200+
self.folders_to_ignore = (
201+
[f.as_posix() for f in folders_to_ignore] if folders_to_ignore else []
202+
)
203+
self.explicitly_listed_files = explicitly_listed_files or []
202204
self.disable_search = disable_search
203205
self.report_type: ReportType = report_type
204206

@@ -223,8 +225,7 @@ def find_files(self) -> List[UploadCollectionResultFile]:
223225
assert regex_patterns_to_include # this is never `None`
224226
files_paths = search_files(
225227
self.search_root,
226-
default_folders_to_ignore
227-
+ [str(folder) for folder in self.folders_to_ignore],
228+
default_folders_to_ignore + self.folders_to_ignore,
228229
filename_include_regex=regex_patterns_to_include,
229230
filename_exclude_regex=regex_patterns_to_exclude,
230231
)
@@ -252,7 +253,7 @@ def get_user_specified_files(self, regex_patterns_to_exclude: Pattern):
252253
for file in self.explicitly_listed_files:
253254
user_filenames_to_include.append(file.name)
254255
if regex_patterns_to_exclude.match(file.name):
255-
files_excluded_but_user_includes.append(str(file))
256+
files_excluded_but_user_includes.append(file.as_posix())
256257
if files_excluded_but_user_includes:
257258
logger.warning(
258259
"Some files being explicitly added are found in the list of excluded files for upload. We are still going to search for the explicitly added files.",
@@ -262,7 +263,7 @@ def get_user_specified_files(self, regex_patterns_to_exclude: Pattern):
262263
)
263264
regex_patterns_to_include = globs_to_regex(user_filenames_to_include)
264265
multipart_include_regex = globs_to_regex(
265-
[str(path.resolve()) for path in self.explicitly_listed_files]
266+
[path.resolve().as_posix() for path in self.explicitly_listed_files]
266267
)
267268
user_files_paths = list(
268269
search_files(

codecov_cli/services/upload/legacy_upload_sender.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def _generate_coverage_files_section(self, upload_data: UploadCollectionResult):
125125
return b"".join(self._format_coverage_file(file) for file in upload_data.files)
126126

127127
def _format_coverage_file(self, file: UploadCollectionResultFile) -> bytes:
128-
header = b"# path=" + file.get_filename() + b"\n"
128+
header = b"# path=" + file.get_filename().encode() + b"\n"
129129
file_content = file.get_content() + b"\n"
130130
file_end = b"<<<<<< EOF\n"
131131

codecov_cli/services/upload/upload_sender.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def _get_file_fixers(
176176
total_fixed_lines = list(
177177
file_fixer.fixed_lines_without_reason.union(fixed_lines_with_reason)
178178
)
179-
file_fixers[str(file_fixer.path)] = {
179+
file_fixers[file_fixer.path.as_posix()] = {
180180
"eof": file_fixer.eof,
181181
"lines": total_fixed_lines,
182182
}
@@ -189,7 +189,7 @@ def _get_files(self, upload_data: UploadCollectionResult):
189189
def _format_file(self, file: UploadCollectionResultFile):
190190
format, formatted_content = self._get_format_info(file)
191191
return {
192-
"filename": file.get_filename().decode(),
192+
"filename": file.get_filename(),
193193
"format": format,
194194
"data": formatted_content,
195195
"labels": "",

codecov_cli/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class UploadCollectionResultFile(object):
2323
def __init__(self, path: pathlib.Path):
2424
self.path = path
2525

26-
def get_filename(self) -> bytes:
27-
return bytes(self.path)
26+
def get_filename(self) -> str:
27+
return self.path.as_posix()
2828

2929
def get_content(self) -> bytes:
3030
with open(self.path, "rb") as f:

tests/helpers/test_args.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
2-
from pathlib import PosixPath
2+
import sys
33

44
import click
5+
import pytest
56

67
from codecov_cli import __version__
78
from codecov_cli.helpers.args import get_cli_args
@@ -28,7 +29,10 @@ def test_get_cli_args():
2829
assert get_cli_args(ctx) == expected
2930

3031

32+
@pytest.mark.skipif(sys.platform == "win32", reason="requires posix platform")
3133
def test_get_cli_args_with_posix():
34+
from pathlib import PosixPath
35+
3236
ctx = click.Context(click.Command("do-upload"))
3337
ctx.obj = {}
3438
ctx.obj["cli_args"] = {

0 commit comments

Comments
 (0)