Django测试运行器在Ubuntu虚拟环境中失败
我在Ubuntu 14.04的Python虚拟环境中遇到了Django测试运行器的问题,搞了很久也没解决。相同的软件在MacOS上运行得很好,我记得在早期版本的Ubuntu上也没问题。
错误信息是:
ImportError: '<test>' module incorrectly imported from '<base-env>/local/lib/python2.7/site-packages/<package-dir>'. Expected '<base-env>/lib/python2.7/site-packages/<package-dir>'. Is this module globally installed?
错误的详细信息如下:
Traceback (most recent call last):
File "/home/annalist/anenv/bin/django-admin", line 11, in <module>
sys.exit(execute_from_command_line())
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
utility.execute()
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 50, in run_from_argv
super(Command, self).run_from_argv(argv)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
self.execute(*args, **options.__dict__)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 71, in execute
super(Command, self).execute(*args, **options)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
output = self.handle(*args, **options)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 88, in handle
failures = test_runner.run_tests(test_labels)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 147, in run_tests
suite = self.build_suite(test_labels, extra_tests)
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 96, in build_suite
tests = self.test_loader.discover(start_dir=label, **kwargs)
File "/usr/lib/python2.7/unittest/loader.py", line 206, in discover
tests = list(self._find_tests(start_dir, pattern))
File "/usr/lib/python2.7/unittest/loader.py", line 287, in _find_tests
for test in self._find_tests(full_path, pattern):
File "/usr/lib/python2.7/unittest/loader.py", line 287, in _find_tests
for test in self._find_tests(full_path, pattern):
File "/usr/lib/python2.7/unittest/loader.py", line 267, in _find_tests
raise ImportError(msg % (mod_name, module_dir, expected_dir))
ImportError: 'test_entity' module incorrectly imported from '/home/annalist/anenv/local/lib/python2.7/site-packages/annalist_root/annalist/tests'. Expected '/home/annalist/anenv/lib/python2.7/site-packages/annalist_root/annalist/tests'. Is this module globally installed?
在开发环境中,测试用例运行得很好,而且在MacOS开发主机上从源代码包安装到新的虚拟环境中时也能正常运行。但是当我把相同的包安装到Ubuntu 14.04的新的虚拟环境中时,测试运行器就出现了上面的错误信息。
问题出现在我创建的一个管理工具中,这个工具调用了一些django-admin
的功能(还有其他一些功能)。
网上搜索后发现,有人报告了虚拟环境和posix兼容性的问题,这些问题在Ubuntu的更新版本中(2013/14年)得到了相对较新的修复,方法是在虚拟环境中创建一个local
目录,这个目录里面有指向其他目录的符号链接,这些目录在虚拟环境的顶层目录中也可以访问。错误信息中显示的路径对应的就是这些别名目录的路径。
我把这个发出来是想作为一个问题,这样我可以分享我的调查结果和解决方案,希望能对其他人有帮助。因此,我并不是想详细描述我具体的软件设置。
2 个回答
我遇到过完全一样的问题,搞了半天也没弄明白到底怎么回事。最后发现其实是个小问题:
我的文件结构大概是这样的:
my_app/
__init__.py
tests.py
tests/
__init__.py
test_foo.py
问题出在同一个文件夹里同时有一个“tests.py”文件和一个“tests”文件夹。
我只需要删除“tests.py”这个文件,就解决了问题。
希望这对你有帮助。
简短回答
我在代码中的解决办法是使用 os.path.realpath
来获取安装包路径的标准化版本,然后把这个值传递给调用 django-admin
工具的命令行。在我的情况下,代码大致是这样的:
approot = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
还有:
with ChangeCurrentDir(approot):
subprocess_command = (
"django-admin test --pythonpath=%s --settings=%s --top-level-directory=%s"%
(approot, settings_module_name, approot)
)
status = subprocess.call(subprocess_command.split())
(其中 ChangeCurrentDir
是一个上下文处理器,它会在指定的当前工作目录下运行包含的代码)。
更多细节
经过进一步的实验,我发现可以通过在 Python 和/或 Django 库代码中巧妙地把 os.path.abspath
替换为 os.path.realpath
来“修复”这个问题。
我发现的核心问题在于:
/usr/lib/python2.7/unittest/loader.py
具体来说:
File "/usr/lib/python2.7/unittest/loader.py", line 267, in _find_tests
raise ImportError(msg % (mod_name, module_dir, expected_dir))
ImportError: 'test_entity' module incorrectly imported from ...
在 loader.py
中导致这个问题的代码是:
if realpath.lower() != fullpath_noext.lower():
module_dir = os.path.dirname(realpath)
mod_name = os.path.splitext(os.path.basename(full_path))[0]
expected_dir = os.path.dirname(full_path)
msg = ("%r module incorrectly imported from %r. Expected %r. "
"Is this module globally installed?")
raise ImportError(msg % (mod_name, module_dir, expected_dir))
如果我把 if
语句替换成这个:
if os.path.realpath(realpath).lower() != fullpath_noext.lower():
那么一切就正常了。这确认了这是一个符号链接别名的问题,因为 os.path.realpath()
会将任何符号链接解析为实际路径。但这并不是一个适合可安装软件包的解决方案,因为它涉及到修改底层的 Python 安装。所以,在深入探讨了根本问题后,我需要找一个更容易处理的方法。
接下来,我查看了安装在虚拟环境中的 Django 测试运行库。
<base-env>/local/lib/python2.7/site-packages/django/test/runner.py
特别是,关注堆栈跟踪中的这一部分:
File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 96, in build_suite
tests = self.test_loader.discover(start_dir=label, **kwargs)
在这段代码中,我发现问题与 label
参数有关,它的默认值是 '.'
(也就是当前目录)。这里没有明显的简单解决办法,但这表明问题可能与运行 django-admin
时使用的当前目录和/或路径有关。这让我得出了上面的解决方案(可能有点过于复杂——我不确定 --pythonpath=
这个选项是否必要,但它对我有效)。