Python pytest(py.test)在cygwin中启动非常慢

Python pytest(py.test)在cygwin中启动非常慢,python,pytest,Python,Pytest,在cygwin中,py.test启动非常慢。这看起来不像是一个收集问题,因为有两个原因:相同的测试在linux中很快启动。有时,如果在cygwin中以足够快的速度重新运行相同的测试,它将在不到1秒的时间内启动。运行time命令时,如果提供了--collection only选项以避免运行实际测试,则它将在0.4秒或11.7秒后启动。我还向hookspytest\u configure()和pytest\u ignore\u collect()添加了一个打印,以确保它确实在收集开始之前 还有其他的

在cygwin中,py.test启动非常慢。这看起来不像是一个收集问题,因为有两个原因:相同的测试在linux中很快启动。有时,如果在cygwin中以足够快的速度重新运行相同的测试,它将在不到1秒的时间内启动。运行time命令时,如果提供了
--collection only
选项以避免运行实际测试,则它将在0.4秒或11.7秒后启动。我还向hooks
pytest\u configure()
pytest\u ignore\u collect()
添加了一个打印,以确保它确实在收集开始之前

还有其他的问题,比如linke等等,但我不知道为什么在cygwin的领导下它会变慢,以及如何解决这个问题

更新:通过
python-mcprofile-scumulat~/…/py.test conftest.py
运行分析。下面是排名前20位的结果。我倾向于认为这是cygwin或cygwin python包中的
posix.stat
的问题,或者
存在于不同的地方

   104699 function calls (102659 primitive calls) in 12.223 CPU seconds
   Ordered by: cumulative time
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      1    0.016    0.016   12.223   12.223 {execfile}
      1    0.000    0.000   12.223   12.223 <string>:1(<module>)
      1    0.000    0.000   12.207   12.207 py.test:4(<module>)
      1    0.000    0.000   12.051   12.051 config.py:23(main)
  48/22    0.000    0.000   12.051    0.548 core.py:526(_docall)
  48/22    0.000    0.000   12.051    0.548 core.py:520(__call__)
 129/82    0.000    0.000   12.051    0.147 core.py:387(execute)
      1    0.000    0.000   11.926   11.926 config.py:634(pytest_cmdline_parse)
      1    0.000    0.000   11.926   11.926 config.py:70(_prepareconfig)
      1    0.000    0.000   11.926   11.926 config.py:741(parse)
    4/3    0.000    0.000   11.926    3.975 core.py:97(wrapped_call)
    4/3    0.000    0.000   11.926    3.975 core.py:121(__init__)
      1    0.000    0.000   11.911   11.911 config.py:706(_preparse)
     70    0.000    0.000   11.817    0.169 local.py:363(check)
    260   11.817    0.045   11.817    0.045 {posix.stat}  <<<<this one???
      1    0.000    0.000    9.302    9.302 config.py:698(_initini)
      1    0.000    0.000    9.286    9.286 config.py:896(determine_setup)
    188    0.000    0.000    9.286    0.049 genericpath.py:15(exists)  <<<<this one???
     18    0.000    0.000    6.861    0.381 config.py:845(exists)  <<<<this one???
      1    0.000    0.000    6.861    6.861 config.py:851(getcfg)  <<<<this one???
      1    0.000    0.000    2.531    2.531 config.py:694(pytest_load_initial_conftests)
      1    0.000    0.000    2.531    2.531 config.py:477(setinitial)
      1    0.000    0.000    2.531    2.531 config.py:503(_try_load_conftest)
     13    0.000    0.000    2.531    0.195 config.py:511(getconftestmodules)
     32    0.000    0.000    2.531    0.079 genericpath.py:26(isfile)  <<<<this one???
      8    0.000    0.000    2.425    0.303 common.py:261(exists)
      1    0.000    0.000    0.156    0.156 pytest.py:4(<module>)
      1    0.000    0.000    0.125    0.125 main.py:73(wrap_session)
      1    0.000    0.000    0.125    0.125 config.py:615(do_configure)
      1    0.000    0.000    0.125    0.125 main.py:115(pytest_cmdline_main)

结论摘要:格式错误的文件路径,如
//setup.py
(应该是
/setup.py
,开头没有双斜杠),显示了四个软件部分之一的问题:pytest config.determinate\u setup()、pytest config使用的py.path、cygwin python库、,或者cygwin posix实现

问题在于pytest搜索
//pytest.ini
//tox.ini
//setup.cfg
//setup.py
。它们都导致
genericpath.exists()
genericpath.isfile()
消耗约2.5秒

修复方法是将下面的行添加到
genericpath.exists()
genericpath.isfile()
以跳过这四个特定路径

if path.startswith(r'//'):
    return False
另一种修复方法是修改
\u pytest/config.py
,这样它就不会在搜索路径中形成那些双斜杠

用于找出确切问题的代码粘贴在下面。设置
myshow=True
,这样它将显示搜索的每个文件的时间消耗情况

$ diff -u /usr/lib/python2.6/genericpath.py genericpath.py
--- /usr/lib/python2.6/genericpath.py   2012-06-09 08:33:12.000000000 -0700
+++ genericpath.py      2015-06-11 11:46:33.674285900 -0700
@@ -9,14 +9,29 @@
 __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
            'getsize', 'isdir', 'isfile']

+myshow = False
+import time as mytime
+mybasetime = mytime.time()
+def myshowtime():
+    currenttime = mytime.time()
+    tmdiff = currenttime - mybasetime
+    global mybasetime
+    mybasetime = currenttime
+    return tmdiff

 # Does a path exist?
 # This is false for dangling symbolic links on systems that support them.
 def exists(path):
     """Test whether a path exists.  Returns False for broken symbolic links"""
+    pretime = myshowtime()
+    if path.startswith(r'//'):
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
+        return False
     try:
         st = os.stat(path)
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
     except os.error:
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
         return False
     return True

@@ -25,9 +40,15 @@
 # for the same path ono systems that support symlinks
 def isfile(path):
     """Test whether a path is a regular file"""
+    pretime = myshowtime()
+    if path.startswith(r'//'):
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
+        return False
     try:
         st = os.stat(path)
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
     except os.error:
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
         return False
     return stat.S_ISREG(st.st_mode)

另一种不编辑/破解安装的方法是让cygwin调用windows本机python而不是cygwin python。也就是说,如果您的windows python安装在
c:/python/path/Scripts/python.exe c:/ython/path/Scripts/py.test script.py
,则将命令
py.test
替换为
c:/python/path
。缩短路径的相对路径或使用非cygwin Python不会遇到同样的问题。具体问题是,格式错误的文件路径,如
//setup.py
(应该是
/setup.py
),揭示了四个软件部分之一的问题:pytest config.determinate\u setup()、py.path、cygwin python、,绝对路径如果不是以双斜杠开头的话,那么它可以很好地工作。关于绝对路径或相对路径。要记住的主要一点是,每个路径组件上都有时间。因此,您希望使用路径组件数最少的组件。在文件系统之间切换会有时间损失,绝对路径比相对路径更容易发生这种情况。尤其是在cygwin上,使用路径的速度似乎很慢。双斜杠使这个问题变得尖锐,但很显然,你需要记住这一点。
$ diff -u /usr/lib/python2.6/genericpath.py genericpath.py
--- /usr/lib/python2.6/genericpath.py   2012-06-09 08:33:12.000000000 -0700
+++ genericpath.py      2015-06-11 11:46:33.674285900 -0700
@@ -9,14 +9,29 @@
 __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
            'getsize', 'isdir', 'isfile']

+myshow = False
+import time as mytime
+mybasetime = mytime.time()
+def myshowtime():
+    currenttime = mytime.time()
+    tmdiff = currenttime - mybasetime
+    global mybasetime
+    mybasetime = currenttime
+    return tmdiff

 # Does a path exist?
 # This is false for dangling symbolic links on systems that support them.
 def exists(path):
     """Test whether a path exists.  Returns False for broken symbolic links"""
+    pretime = myshowtime()
+    if path.startswith(r'//'):
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
+        return False
     try:
         st = os.stat(path)
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
     except os.error:
+        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
         return False
     return True

@@ -25,9 +40,15 @@
 # for the same path ono systems that support symlinks
 def isfile(path):
     """Test whether a path is a regular file"""
+    pretime = myshowtime()
+    if path.startswith(r'//'):
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
+        return False
     try:
         st = os.stat(path)
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
     except os.error:
+        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
         return False
     return stat.S_ISREG(st.st_mode)