Skip to content

Commit 44a9707

Browse files
grievejiafacebook-github-bot
authored andcommitted
Implement pyre coverage (3/3) -- collect&print
Reviewed By: kbansal Differential Revision: D30436430 fbshipit-source-id: b3f338c9bd2b604b568a0b176f30a2a2c15061c3
1 parent 37b4703 commit 44a9707

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

client/commands/v2/coverage.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
# This source code is licensed under the MIT license found in the
44
# LICENSE file in the root directory of this source tree.
55

6+
import dataclasses
7+
import json
68
import logging
9+
import os
710
from pathlib import Path
11+
from typing import Optional, Iterable, List
812

9-
from ... import commands, configuration as configuration_module
13+
import libcst as cst
14+
15+
from ... import (
16+
commands,
17+
configuration as configuration_module,
18+
coverage_collector as collector,
19+
log,
20+
)
1021
from . import remote_logging, statistics
1122

1223
LOG: logging.Logger = logging.getLogger(__name__)
@@ -22,13 +33,60 @@ def find_root(
2233
return working_directory
2334

2435

36+
@dataclasses.dataclass(frozen=True)
37+
class FileCoverage:
38+
filepath: str
39+
covered_lines: List[int]
40+
uncovered_lines: List[int]
41+
42+
43+
def collect_coverage_for_module(relative_path: str, module: cst.Module) -> FileCoverage:
44+
module_with_metadata = cst.MetadataWrapper(module)
45+
coverage_collector = collector.CoverageCollector()
46+
try:
47+
module_with_metadata.visit(coverage_collector)
48+
except RecursionError:
49+
LOG.warning(f"LibCST encountered recursion error in `{relative_path}`")
50+
return FileCoverage(
51+
filepath=relative_path,
52+
covered_lines=sorted(coverage_collector.covered_lines),
53+
uncovered_lines=sorted(coverage_collector.uncovered_lines),
54+
)
55+
56+
57+
def collect_coverage_for_path(
58+
path: Path, working_directory: str
59+
) -> Optional[FileCoverage]:
60+
module = statistics.parse_path_to_module(path)
61+
relative_path = os.path.relpath(str(path), working_directory)
62+
return (
63+
collect_coverage_for_module(relative_path, module)
64+
if module is not None
65+
else None
66+
)
67+
68+
69+
def collect_coverage_for_paths(
70+
paths: Iterable[Path], working_directory: str
71+
) -> List[FileCoverage]:
72+
result: List[FileCoverage] = []
73+
for path in paths:
74+
coverage = collect_coverage_for_path(path, working_directory)
75+
if coverage is not None:
76+
result.append(coverage)
77+
return result
78+
79+
2580
def run_coverage(
2681
configuration: configuration_module.Configuration, working_directory: str
2782
) -> commands.ExitCode:
28-
sources = statistics.find_paths_to_parse(
29-
[find_root(configuration, Path(working_directory))]
83+
data = collect_coverage_for_paths(
84+
statistics.find_paths_to_parse(
85+
[find_root(configuration, Path(working_directory))]
86+
),
87+
working_directory,
3088
)
31-
LOG.warning(f"SOURCES = {list(sources)}")
89+
log.stdout.write(json.dumps([dataclasses.asdict(entry) for entry in data]))
3290
return commands.ExitCode.SUCCESS
3391

3492

client/commands/v2/tests/coverage_test.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
# This source code is licensed under the MIT license found in the
44
# LICENSE file in the root directory of this source tree.
55

6+
import tempfile
67
from pathlib import Path
8+
from typing import List
79

810
import testslide
911

1012
from .... import configuration
11-
from ..coverage import find_root
13+
from ....tests import setup
14+
from ..coverage import find_root, collect_coverage_for_paths, FileCoverage
1215

1316

1417
class CoverageTest(testslide.TestCase):
@@ -33,3 +36,25 @@ def test_find_root(self) -> None:
3336
),
3437
Path("/working/dir"),
3538
)
39+
40+
def test_collect_coverage(self) -> None:
41+
with tempfile.TemporaryDirectory() as root:
42+
root_path: Path = Path(root)
43+
setup.ensure_files_exist(root_path, ["foo.py", "bar.py"])
44+
foo_path = root_path / "foo.py"
45+
bar_path = root_path / "bar.py"
46+
baz_path = root_path / "baz.py"
47+
48+
data: List[FileCoverage] = collect_coverage_for_paths(
49+
[foo_path, bar_path, baz_path], working_directory=root
50+
)
51+
52+
def is_collected(path: Path) -> bool:
53+
return any(
54+
str(path.relative_to(root_path)) == coverage.filepath
55+
for coverage in data
56+
)
57+
58+
self.assertTrue(is_collected(foo_path))
59+
self.assertTrue(is_collected(bar_path))
60+
self.assertFalse(is_collected(baz_path))

0 commit comments

Comments
 (0)