Executing setup.py test that uses 2to3 causes wrong paths to be collected

Issue #241 on hold
Patryk Zawadzki created an issue

When testing with Python 2.x the build dir is not used as there is no build step. The paths are correct:

foo/__init__ 1 0 100%

With Python 3.x and 2to3 the build dir has to be used used to perform code translation. The result is wrong:

build/lib/foo/__init__ 1 0 100%

This is likely because distribute modifies sys.path for the duration of the test command:

    def with_project_on_sys_path(self, func):
        if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False):
            # If we run 2to3 we can not do this inplace:

            # Ensure metadata is up-to-date
            self.reinitialize_command('build_py', inplace=0)
            bpy_cmd = self.get_finalized_command("build_py")
            build_path = normalize_path(bpy_cmd.build_lib)

            # Build extensions
            self.reinitialize_command('egg_info', egg_base=build_path)

            self.reinitialize_command('build_ext', inplace=0)
            # Without 2to3 inplace works fine:

            # Build extensions in-place
            self.reinitialize_command('build_ext', inplace=1)

        ei_cmd = self.get_finalized_command("egg_info")

        old_path = sys.path[:]
        old_modules = sys.modules.copy()

            sys.path.insert(0, normalize_path(ei_cmd.egg_base))
            add_activation_listener(lambda dist: dist.activate())
            require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
            sys.path[:] = old_path

Comments (9)

  1. Ned Batchelder repo owner

    I'm sorry, I don't quite understand. Do you have a sample project I could use to see the problem? And is this a coverage bug or a distribute bug?

  2. Patryk Zawadzki reporter

    When distribute has to build anything prior to testing (like run 2to3 or compile a C extension) it can't test code in place and resorts to creating a build dir, temporarily putting it on sys.path and running tests from there. It seems that it restores sys.path before coverage gets a hold of it though.

  3. Patryk Zawadzki reporter

    Test case:

    $ cat outer.py 
    import sys
    # here the real setup would create the directory,
    # copy inner.py there and run 2to3
    sys.path.insert(0, 'build/lib')
    import inner
    print inner.foo()
    $ cat build/lib/inner.py
    def foo():
        return 'hello world'
    $ coverage report
    Name              Stmts   Miss  Cover
    build/lib/inner       2      0   100%
    outer                 5      0   100%
    TOTAL                 7      0   100%

    It seems it would be enough to maintain a set() of all paths and add set(sys.path) to it each time a new filename is traced then use the set to generate canonical python paths.

    To clarify: while coverage reports the correct path, it's not very useful as the path is generated by setup.py. I believe it would be more useful to report paths relative to sys.path at the moment of import (or python modules instead of file paths).

  4. Ned Batchelder repo owner

    Are you saying you want the report to say simply "inner" instead of "build/lib/inner"?

  5. Ned Batchelder repo owner

    Hmm, I'm not sure what to do about that. The report now is based on file names. Perhaps that is the wrong thing to do. Do you have an actual project with a setup.py that I can try this with?

  6. Log in to comment