New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Turn on branch coverage checking. #887
Conversation
70654ea
to
cf31773
Compare
@mhsmith @rmartin16 This is still in draft form, but I've got it producing a coverage report as an alternative to CodeCov. We'll likely need to get to 100% coverage for this report to be useful; we'd be losing the ability to report on "coverage of this PR", and the ability to ratchet the minimum allowed coverage as tests are added. I'd be interested in your thoughts on how this output compares. |
cf31773
to
7ae7b63
Compare
I'm mostly indifferent regarding exactly how coverage is tracked....but I don't think I'm convinced removing CodeCov creates a better situation necessarily. I think the benefits outweighs the negatives....and we can probably address the negatives. CodeCov notification noise
"Coverage decrease" false positives Final coverage report
|
Uploading a single combined report is a good thought - I hadn't considered that, but it would seem to address the biggest complaint described by #848. I'm not sure the threshold approach fixes the problem; the issue is with the algorithm used to compute coverage. If a file has 100 lines and 95% coverage, deleting 10 lines drops coverage to ~85%. The only way to address this is to fix this is to have 100% coverage to start with. The good news is that from looking at the current coverage misses, we're actually not that far from getting 100% coverage. The missing cases are mostly things like "checking that the Windows filename actually has .bat", or "actually running validate_tools() on a build step" - things that are easy to add tests for, if we have a focussed effort. Looking at the HTML coverage report - unfortunately, there aren't a whole lot of options there. That said - I'm actually wondering whether the HTML report is valuable at all. The CI failure includes a summary report which details the specific lines that are missing; those lines are easy enough to find locally in your editor. The printed code context is pretty... but I'm not sure it's necessarily helpful. If we can get to 100% coverage as a baseline, then a new PR that misses coverage will only be reporting lines that are a problem in that PR. |
6233318
to
5ada7d1
Compare
Yeah; that makes sense. Although, I think this issue is moot if we need to get to 100% coverage either way. Another feature of CodeCov I really like is the lines without coverage are called out directly in the github diff. Although, that information is just as available from the |
@@ -16,7 +17,7 @@ def main(): | |||
command = Command(logger=logger, console=console) | |||
options = command.parse_options(extra=extra_cmdline) | |||
command.check_obsolete_data_dir() | |||
command.parse_config("pyproject.toml") | |||
command.parse_config(Path.cwd() / "pyproject.toml") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes the path absolute, which means it can be tested reliably by monkeypatching cwd.
@@ -41,8 +42,8 @@ def main(): | |||
with suppress(KeyboardInterrupt): | |||
logger.save_log_to_file(command) | |||
|
|||
sys.exit(result) | |||
return result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes it easier to test the result, as you can test the return value, rather than catching the sys.exit()
@@ -117,7 +117,7 @@ def __init__( | |||
console: Console, | |||
tools: ToolCache = None, | |||
apps: dict = None, | |||
base_path: Path = Path.cwd(), | |||
base_path: Path = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures that Path.cwd()
is evaluated when the instance is instantiated, rather than when the code is imported, which means it's testable for the default case.
) | ||
except IndexError as e: | ||
|
||
if len(app_home) == 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bug; previously assumed to be caught as an IndexError, it was being returned as a BriefcaseCommandError
with the wrong message.
if app_path.is_dir(): | ||
self.tools.shutil.rmtree(app_path) | ||
self.tools.os.mkdir(app_path) | ||
self.tools.shutil.rmtree(app_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if is_dir
wasn't needed, as the app_path is always a directory, generated by the template.
self.logger.info(f"TODO: Publish {app.app_name} to {channel}") | ||
self.logger.info( | ||
f"TODO: Publish {app.app_name} to {channel}" | ||
) # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is effectively a raise NotImplementedError()
, but with nicer optics if it's invoked.
os.fsdecode(Path(tmpdir) / "lib"), | ||
os.fsdecode(lib_path), | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was only needed to support old-style support packages; now that we're version locked on support packages, this isn't needed.
Down to just 5 uncovered branches - but it's not obvious what those 5 are even describing... |
This means the tests don't cover the case where the
As above, but in this case the
As above, but in this case the
Not sure about these ones, but based on the discussion here it might have something to do with the code throwing an exception and therefore never completing the function normally. |
🤦 That one was obvious when I took a second look. I was missing the "prefix" requirement for that for that line to be (not) executed.
That's what I thought too... but we run the test suite on Windows, which should (and does, based on my testing) trigger that branch. Looking closer, I'm wondering if it might be a problem with the merging of the coverage data.
Ah - now I see what was tripping me up. It's a case that can't actually happen (
Hrm... that's as good a theory as any, I suppose... I'll keep poking. |
c35b6b7
to
5f86e9d
Compare
@@ -493,7 +493,7 @@ def _stream_output_thread(self, popen_process): | |||
output_line = ensure_str(popen_process.stdout.readline()) | |||
if output_line: | |||
self.tools.logger.info(output_line) | |||
elif output_line == "": | |||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old version of this left an open coverage case for readline()
returning None. This shouldn't ever happen; if it does, it's probably safe to assume that the process has ended, so I've made this a bucket else case.
5f86e9d
to
7fa5a9a
Compare
This is really weird - it looks like there's a discrepancy between 3.9 and 3.10 in how they report exiting a context manager. Looking at the missing branch on
I'm guessing that since we're not reporting 3.10 on Windows, there's no match for the 298-264 missing branch, which is then reported as a miss in the overall results. |
5c1a8dc
to
0a2b483
Compare
0a2b483
to
0c7b5e2
Compare
💯 |
Codecov Report
|
Now that we're at 100% coverage, the only question that remains is whether we stick with "self-hosted" coverage testing, or revert to CodeCov reporting. As I see it, CodeCov is an external dependency which has been flaky from time to time, and I'm always in favour of having less external dependencies if we can. It's also very noisy, although only uploading a single CI report may mitigate that somewhat. The major advantage is that it provides an in-browser way to view coverage results. Self-hosted coverage isn't as pretty, but it does present the specific lines in the test failure, rather than needing a couple of clicks to get there. It also matches the report you'll get if you run coverage locally. We can generate HTML reports, but they're not easy to browse (you have to download to view them). On that basis, I have a mild preference to drop CodeCov and go self-hosted, dropping the upload of the coverage HTML result. However, if anyone has a strong preference to retain CodeCov, I won't put up much of a fight. Opinions? |
The only benefit of CodeCov was the fact that it helped us make sure that every PR is keeping the total coverage rate high enough and that every new change is covered by tests. Now that we have finally got to 100% coverage, this feature is no longer needed. Simply add the directive The "downsize" of this is that now we will have to enforce 100% coverage all the time, without exceptions (or else, the CI process will not pass). In my opinion, it's a feature, not a bug :) |
I wasn't aware that (a) coverage had an option for branch coverage, and (b) that it wasn't turned on by default.
This turns on branch coverage, and moves the coverage configuration to pyproject.toml. This reveals a handful of edge cases that aren't currently covered.
This also addresses the noisy emails caused by CodeCov... by removing codecov.
Fixes #848.
PR Checklist: