Repository navigation
Coverage error with green's DjangoRunner #227
Copy link
Copy link
Open
Labels
Description
When using coverage with green in django, all import statements, class heads and function heads (presumably etc.) are marked as not covered. I have no idea about the inner workings of the TestRunner, Django, Green and Coverage, but could it maybe be that coverage is "started" to late, and doesn't catch the initial import of these files? (As all those things are lines that are evaluated when the file is first read by Python)
It does work perfectly fine when using django's standard testRunner.
For example, here is the test.py:
from django.test import TestCase
import main.models
class MainTests(TestCase):
def test_setting_and_retrieving_setting_works(self):
"""Setting and retrieving a setting work as expected.
"""
setting_name = "some setting"
setting_value = "a value"
main.models.Settings.set_setting(setting_name, setting_value)
self.assertEqual(
setting_value, main.models.Settings.get_setting(setting_name)
)
def test_trying_to_get_an_unset_setting_returns_none(self):
"""When trying to get an unknown setting, None is returned.
"""
self.assertIsNone(main.models.Settings.get_setting("Something"))
def test_trying_to_get_an_unset_setting_returns_default(self):
"""When trying to get an unknown setting, None is returned.
"""
self.assertEqual(
"Default", main.models.Settings.get_setting("Something", "Default")
)Here is the main/models.py:
from django.db import models
from django.db.utils import OperationalError
class Settings(models.Model):
"""
Stores settings as simple key/value pairs. These can then be used elsewhere in the app.
"""
name = models.CharField(max_length=200, primary_key=True)
value = models.CharField(max_length=200)
@classmethod
def get_setting(cls, name: str, default: str = None) -> str:
"""Retrieves a setting's value, or None if it doesn't exist."""
try:
return cls.objects.get(pk=name).value
except (cls.DoesNotExist, OperationalError):
return default
@classmethod
def set_setting(cls, name: str, value: str) -> None:
"""Sets the specified setting to the specified value, creates it if it doesn't exist."""
cls.objects.update_or_create(name=name, defaults={"value": value})For completeness sake, here is the .green file:
verbose = 3
run-coverage = True
cov-config-file = .coveragerc
processes = 1
And .coveragerc:
[run]
branch = True
[xml]
output = coverage.xml
And coverage's xml output:
<?xml version="1.0" ?>
<coverage version="5.2" timestamp="1594479177454" lines-valid="14" lines-covered="5" line-rate="0.3571" branches-valid="0" branches-covered="0" branch-rate="1" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/home/flix/PythonProjects/imagetagger</source>
</sources>
<packages>
<package name="main" line-rate="0.3571" branch-rate="1" complexity="0">
<classes>
<class name="models.py" filename="main/models.py" complexity="0" line-rate="0.3571" branch-rate="1">
<methods/>
<lines>
<line number="1" hits="0"/>
<line number="2" hits="0"/>
<line number="5" hits="0"/>
<line number="10" hits="0"/>
<line number="11" hits="0"/>
<line number="13" hits="0"/>
<line number="14" hits="0"/>
<line number="16" hits="1"/>
<line number="17" hits="1"/>
<line number="18" hits="1"/>
<line number="19" hits="1"/>
<line number="21" hits="0"/>
<line number="22" hits="0"/>
<line number="24" hits="1"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
Reactions are currently unavailable
Activity
CleanCut commentedon Aug 10, 2020
Please try disabling green's coverage in your
.greenfile and instead use the way django suggests to do coverage. Let me know if that works!I'm going to close this in anticipation of it working, but if it doesn't please feel free to reopen this issue and let me know what actually happened.
macgeneral commentedon Nov 17, 2020
Coverage doesn't seem to work reliably with Green and Django right now at all.
If I use the
DjangoRunnerwith coverage enabled in the.greenconfiguration and run my tests withpython3 manage.py test myappthe scores don't add up.Green creates multiple .coverage.x_abcd files even though
data_file = .coverageis configured in.coveragerc.(I use coverage combine before creating the report, but it doesn't change the score)
In the end my score is 31 points (60% vs 91%) lower than when using the normal
DiscoverRunner- and showing 0% coverage for some code paths I definitely know are covered, and < 50% coverage for some files I know are > 98% covered (not omitting any lines in them).clear-omit = Truein the.greenconfiguration doesn't help here either.If I run it using the Django way (
coverage run --source='.' manage.py test myapp) and disable the configuration parts in.greenit respects my .coveragerc settings, but the final score is still 31 to even up to 57 points lower than when using theDiscoverRunner. It seems like theDjangoRunnerbreaks coverage's ability to track the executed code paths.This looks like a great project and I would be really happy to use it, but unfortunately in the current state it's unusable to me. Especially the minimum-coverage option (and of course the great, clear output) looked promising.
CleanCut commentedon Nov 23, 2020
@macgeneral Unless coverage is enabled, green doesn't touch it:
green/green/process.py
Lines 292 to 311 in 639a0f4
Here's the possibilities I can see:
.coverage) messed with django's coverage run. (delete them)processes = 1to your green config and see if that helpsmacgeneral commentedon Nov 24, 2020
@CleanCut: Thank you for your reply. I'm sorry I should have described my problem in a more verbose / bug report manner.
I did disable coverage in the green configuration and the result is still as bad as when it's enabled:
The result I get is this:
Compare it to a run using the RedGreenDiscoverRunner (or the regular DiscoverRunner):
Both tests were run after deleting the coverage results from the previous run, but it doesn't change anything:
And green doesn't create any .coverage_abcd files during the test run. Just one .coverage file get's created by coverage itself.
The only difference I noticed are the 48 vs 43 tests, but that won't explain why many of my source files show close to zero coverage when I do know, that those code paths were executed.
macgeneral commentedon Nov 24, 2020
Just to double check it isn't just me.
Regular test run
Coverage run
Coverage run with Green
As you can see for otherwise equal test runs, with green used as test runner coverage only reports a fraction of the green-less test run.
Also in my previous edit: the numbers between green and coverage without green / regular run were even further apart as the tests run and errors differed by a large amount - in my smaller project I experienced test runner crashes with green as well when neither the DiscoverRunner nor RedGreenDiscoverRunner would fail on the same code base - I know this doesn't help much, but I assume it has something to do with your
green.runner.runimplementation.I could also nearly double my coverage score by creating a child class of your
DjangoRunner, copy and pasting thedef run_tests(self, test_labels, extra_tests=None, **kwargs)function and adding some missing parts and adjusting the structure more to the Django DiscoverRunner.I assume it's not possible for your implementation to overwrite
run_suiteinstead (which would be less error prone). Sorry I can't track it down any further but I'm not too much into Test Runners to know what to look for (except for writing some smaller child classes to override some functions).EDIT: switched test runs to only test wagtail.core for faster and more reliable comparisons
macgeneral commentedon Nov 24, 2020
Here's my modifed
run_tests:Wagtail Test
My Project
If I don't comment
self.run_checks()in my project, the coverage score goes up to 48% and with some tinkering with the run_suite method I got up to ~60% once, but I unfortunately stashed my changes.EDIT: with the following modification I got wagtail.cores score up to 29%
[..] self.run_checks(databases) # does not match the Django DiscoverRunner in 2.2, but latest dev version and wigtail uses Django 3.1 [..]It still seems totally random to me why this happens.
macgeneral commentedon Nov 24, 2020
Sorry for spamming so much, but just in case it helps you track down any errors:
doesn't seem to break anything obvious besides missing some parts (
args.targets = test_labelsand everything related to that):I don't know how green handles multi processing inside of run. I think if I recall correctly coverage and django's
manage.py test --paralleloption don't blend in well together either. And I'm not sure if it's possible for you to userun_suiteinstead ofrun_tests, because it barely seems to change compared to the original implementation while therun_testsfunction seems to change quite often.macgeneral commentedon Nov 24, 2020
One last spam message:
Intercepts the test_labels without overriding too much of the Django source.
You already set test_labels to ["."] like Django does in runner.py.
It doesn't directly solve the low coverage scores (just increases them to 48% compared to the current implementation's 31% for my project), but might save you some headache because you don't have to keep up with the run_tests function.
macgeneral commentedon Nov 27, 2020
Looking at your code and the coverage documentation I found the solution:
Because your
runner.runfunction always uses amultiprocessing.Manager()coverage can't keep up even when it's just using one process because it defaults tothread.As far as I can tell (for my project), my implementation above, the
DiscoverRunnerand your currentDjangoRunnerimplementation all produce the exact same coverage scores (there's a small difference for the wagtail project). I would still advise against overriding therun_testsfunction but you probably have your reasons.CleanCut commentedon Dec 1, 2020
Reopening until we investigate and see if there's a way we can integrate the solution into green's DjangoRunner.