Seemingly bogus lack of branch coverage in use of factory_boy LazyFunction

Issue #544 invalid
Dave Schweisguth
created an issue

Running coverage on this code

import random
from decimal import Decimal
import factory
from myapp.models import TransactionTemplate

class TransactionTemplateFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = TransactionTemplate
        strategy = factory.BUILD_STRATEGY

    merchant_name = factory.Sequence(lambda n: f'Merchant{n}')
    identifying_amount = factory.LazyFunction(lambda: TransactionTemplateFactory.random_dollars())
    amount = factory.LazyFunction(lambda: Decimal(TransactionTemplateFactory.random_dollars()))

    @staticmethod
    def random_dollars():
        return f'{random.randint(0, 9999999999)}.{random.randint(0, 99):02d}'

reports missing branch coverage on the identifying_amount line:

Screen Shot 2016-12-31 at 17.24.47.png

The hover says "line 15 didn't run the lambda on line 15".

(Line numbers changed from the code sample but you'll figure it out.)

I'm certain the lambda is executed, as when I construct an instance the attribute has the value it should.

Seems to relate to https://bitbucket.org/ned/coveragepy/issues/460/confusing-html-report-for-certain-partial and https://bitbucket.org/ned/coveragepy/issues/90/lambda-expression-confuses-branch but those are resolved.

The repo isn't and can't be public. I could make an isolated test case if that were necessary but it would take a little while, so please let me know.

Python 3.6.0, coverage 4.3.1.

Comments (6)

  1. Peter Inglesby

    Are you completely sure that the lambda is executed?

    I ask because when factory_boy builds an instance of TransactionTemplate, it'll set its identifying_amount attribute to the result of calling lambda: TransactionTemplateFactory.random_dollars; that is, it'll set it to the random_dollars function, and not to the result of calling the random_dollars, which I'm pretty sure is not what you want.

    If you're completely sure that the lambda's getting called, then it'd be helpful to know the version of Django and factory_boy that you're using.

  2. Dave Schweisguth reporter
    • edited description

    Oops, I introduced the red herring of an incorrect lambda when I refactored this right before posting this issue. I guess something in Django or factory_boy calls the function anyway so my tests still passed. I fixed the code, reran the report and updated the code and screenshot here. Same result.

    Django is 1.10.4 and factory_boy is 2.8.1.

  3. Peter Inglesby

    I'm unable to reproduce this.

    I have the following model:

    class TransactionTemplate(models.Model):
        merchant_name = models.CharField(max_length=255)
        identifying_amount = models.DecimalField(decimal_places=2, max_digits=12)
        amount = models.DecimalField(decimal_places=2, max_digits=12)
    

    I have the factory from the bug report:

    class TransactionTemplateFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = TransactionTemplate
            strategy = factory.BUILD_STRATEGY
    
        merchant_name = factory.Sequence(lambda n: f'Merchant{n}')
        identifying_amount = factory.LazyFunction(lambda: TransactionTemplateFactory.random_dollars())
        amount = factory.LazyFunction(lambda: Decimal(TransactionTemplateFactory.random_dollars()))
    
        @staticmethod
        def random_dollars():
            return f'{random.randint(0, 9999999999)}.{random.randint(0, 99):02d}'
    

    And I have the following test, which simply instantiates the factory:

    class TransactionTemplateTests(TestCase):
        def test_it(self):
            TransactionTemplateFactory()
    

    When I run the test I have full coverage of the factory:

    $ rm .coverage ; coverage run --branch --source myapp ./manage.py test myapp.tests.TransactionTemplateTests.test_it ; coverage report myapp/factories.py 
    Creating test database for alias 'default'...
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.002s
    
    OK
    Destroying test database for alias 'default'...
    Name                 Stmts   Miss Branch BrPart  Cover
    ------------------------------------------------------
    myapp/factories.py      13      0      6      0   100%
    

    I'm using the same version of Python and the same versions of the libraries as Dave:

    $ python --version
    Python 3.6.0
    $ pip freeze
    coverage==4.3.1
    Django==1.10.4
    factory-boy==2.8.1
    Faker==0.7.7
    python-dateutil==2.6.0
    six==1.10.0
    

    To make progress, we'll need more information from Dave. Things to try:

    • Can you reproduce it if you run a single test that only instantiates TransactionTemplateFactory?
    • Can you reproduce it if your model just contains the field definitions?
    • What happens if you exchange the order of identifying_amount = ... and amount = ... in your factory?
    • Can you change the identifying_amount = ... and amount = ... to call different functions, so that you can be sure that both lambdas are being invoked?
  4. Dave Schweisguth reporter

    Ugh, it was my error. Test code that should have been alllowing the factory to set identifying_amount was instead providing identifying_amount so that the lambda never ran. This bug can be closed as invalid. Thanks for your patient help!

  5. Log in to comment