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

Commit ab66655

Browse files
Don't send unecessary files in static analysis (#130)
You can see that we were already calculating `files_that_need_upload` in static analysis but still iterating over all the files in the response from codecov. This means we were effectively uploading all files everytime. This is unecessary and takes a long time and consumes more data than we need. Plus it was causing issues with the TTL of the pre-signed PUT as well. The fix is very obvious, just iterate over the files that actually need to be updated. I also took this opportunity to add some tests to static analysis (coverage is terrible in this command) THe new requirement is to test the async function in the service. Maybe it should be in a dedicated requirements.txt inside `tests`?...
1 parent 2232a1a commit ab66655

File tree

6 files changed

+141
-2
lines changed

6 files changed

+141
-2
lines changed

codecov_cli/services/staticanalysis/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ async def run_analysis_entrypoint(
112112
limits = httpx.Limits(max_keepalive_connections=3, max_connections=5)
113113
async with httpx.AsyncClient(limits=limits) as client:
114114
all_tasks = []
115-
for el in response_json["filepaths"]:
115+
for el in files_that_need_upload:
116116
all_tasks.append(send_single_upload_put(client, all_data, el))
117117
bar.update(1, all_data[el["filepath"]])
118118
resps = await asyncio.gather(*all_tasks)

codecov_cli/services/staticanalysis/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import pathlib
12
from dataclasses import dataclass
23
from typing import Optional
34

45

56
@dataclass
67
class FileAnalysisRequest(object):
78
result_filename: str
8-
actual_filepath: str
9+
actual_filepath: pathlib.Path
910

1011

1112
@dataclass

requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ click
22
pytest
33
pytest-cov
44
pytest-mock
5+
pytest-asyncio
56
pyyaml
67
responses
78
httpx

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ pyparsing==3.0.9
4343
pytest==7.1.2
4444
# via
4545
# -r requirements.in
46+
# pytest-asyncio
4647
# pytest-cov
4748
# pytest-mock
49+
pytest-asyncio==0.21.0
50+
# via -r requirements.in
4851
pytest-cov==3.0.0
4952
# via -r requirements.in
5053
pytest-mock==3.8.2

tests/test_samples.py renamed to tests/services/static_analysis/test_analyse_file.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from pathlib import Path
3+
from unittest.mock import MagicMock, patch
34

45
import pytest
56

@@ -40,3 +41,16 @@ def test_sample_analysis(input_filename, output_filename):
4041
)
4142
assert res_dict["result"] == expected_result["result"]
4243
assert res_dict == expected_result
44+
45+
46+
@patch("builtins.open")
47+
@patch("codecov_cli.services.staticanalysis.get_best_analyzer", return_value=None)
48+
def test_analyse_file_no_analyser(mock_get_analyser, mock_open):
49+
fake_contents = MagicMock()
50+
file_name = MagicMock(actual_filepath="filepath")
51+
mock_open.return_value.read.return_value = fake_contents
52+
config = {}
53+
res = analyze_file(config, file_name)
54+
assert res == None
55+
assert mock_open.called_with("filepath", "rb")
56+
assert mock_get_analyser.called_with(file_name, fake_contents)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from pathlib import Path
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
5+
import responses
6+
from responses import matchers
7+
8+
from codecov_cli.services.staticanalysis import run_analysis_entrypoint
9+
from codecov_cli.services.staticanalysis.types import FileAnalysisRequest
10+
11+
12+
class TestStaticAnalysisService:
13+
@pytest.mark.asyncio
14+
@patch("codecov_cli.services.staticanalysis.select_file_finder")
15+
@patch("codecov_cli.services.staticanalysis.send_single_upload_put")
16+
async def test_static_analysis_service(
17+
self, mock_send_upload_put, mock_file_finder
18+
):
19+
files_found = map(
20+
lambda filename: FileAnalysisRequest(str(filename), Path(filename)),
21+
[
22+
"samples/inputs/sample_001.py",
23+
"samples/inputs/sample_002.py",
24+
],
25+
)
26+
mock_file_finder.return_value.find_files = MagicMock(return_value=files_found)
27+
with responses.RequestsMock() as rsps:
28+
rsps.add(
29+
responses.POST,
30+
"https://api.codecov.io/staticanalysis/analyses",
31+
json={
32+
"filepaths": [
33+
{
34+
"state": "created",
35+
"filepath": "samples/inputs/sample_001.py",
36+
"raw_upload_location": "some URL",
37+
},
38+
{
39+
"state": "valid",
40+
"filepath": "samples/inputs/sample_002.py",
41+
"raw_upload_location": "some URL",
42+
},
43+
]
44+
},
45+
status=200,
46+
match=[
47+
matchers.header_matcher({"Authorization": "Repotoken STATIC_TOKEN"})
48+
],
49+
)
50+
await run_analysis_entrypoint(
51+
config={},
52+
folder=".",
53+
numberprocesses=1,
54+
pattern="*.py",
55+
token="STATIC_TOKEN",
56+
commit="COMMIT",
57+
should_force=False,
58+
folders_to_exclude=[],
59+
)
60+
mock_file_finder.assert_called_with({})
61+
mock_file_finder.return_value.find_files.assert_called()
62+
assert mock_send_upload_put.call_count == 1
63+
args, _ = mock_send_upload_put.call_args
64+
assert args[2] == {
65+
"state": "created",
66+
"filepath": "samples/inputs/sample_001.py",
67+
"raw_upload_location": "some URL",
68+
}
69+
70+
@pytest.mark.asyncio
71+
@patch("codecov_cli.services.staticanalysis.select_file_finder")
72+
@patch("codecov_cli.services.staticanalysis.send_single_upload_put")
73+
async def test_static_analysis_service_should_force_option(
74+
self, mock_send_upload_put, mock_file_finder
75+
):
76+
files_found = map(
77+
lambda filename: FileAnalysisRequest(str(filename), Path(filename)),
78+
[
79+
"samples/inputs/sample_001.py",
80+
"samples/inputs/sample_002.py",
81+
],
82+
)
83+
mock_file_finder.return_value.find_files = MagicMock(return_value=files_found)
84+
with responses.RequestsMock() as rsps:
85+
rsps.add(
86+
responses.POST,
87+
"https://api.codecov.io/staticanalysis/analyses",
88+
json={
89+
"filepaths": [
90+
{
91+
"state": "created",
92+
"filepath": "samples/inputs/sample_001.py",
93+
"raw_upload_location": "some URL",
94+
},
95+
{
96+
"state": "valid",
97+
"filepath": "samples/inputs/sample_002.py",
98+
"raw_upload_location": "some URL",
99+
},
100+
]
101+
},
102+
status=200,
103+
match=[
104+
matchers.header_matcher({"Authorization": "Repotoken STATIC_TOKEN"})
105+
],
106+
)
107+
await run_analysis_entrypoint(
108+
config={},
109+
folder=".",
110+
numberprocesses=1,
111+
pattern="*.py",
112+
token="STATIC_TOKEN",
113+
commit="COMMIT",
114+
should_force=True,
115+
folders_to_exclude=[],
116+
)
117+
mock_file_finder.assert_called_with({})
118+
mock_file_finder.return_value.find_files.assert_called()
119+
assert mock_send_upload_put.call_count == 2
120+
args, _ = mock_send_upload_put.call_args

0 commit comments

Comments
 (0)