changeset 41506:3e89736b98ce

py3: conditionalize test-demandimport.py for Python 3 The Python 3 lazy importer uses the LazyLoader that is part of importlib. On Python 3 and later, LazyLoader is implemented using a custom module type that defines a __getattribute__ which triggers module loading. Furthermore, there are additional differences as well. For example, it appears that Python 3 will return an existing sys.modules entry instead of constructing a new module object. This commit adds additional test coverage for lazy importing behavior to cover the differences between Python 2 and 3. This reveals that the test and some lazy import functionality is kinda busted. For example, the test assumes "contextlib" will be lazy. But in reality an import before it has already imported contextlib! There's definitely room to improve the behavior of the demand importer code, both for Python 2 and 3. But at least the test passes on Python 3 now. Differential Revision: https://phab.mercurial-scm.org/D5796
author Gregory Szorc <gregory.szorc@gmail.com>
date Fri, 01 Feb 2019 16:47:29 -0800
parents dffd6a301570
children 30248d6bc057
files tests/test-demandimport.py
diffstat 1 files changed, 116 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/tests/test-demandimport.py	Fri Feb 01 12:09:05 2019 -0800
+++ b/tests/test-demandimport.py	Fri Feb 01 16:47:29 2019 -0800
@@ -6,6 +6,10 @@
 import os
 import subprocess
 import sys
+import types
+
+# Don't import pycompat because it has too many side-effects.
+ispy3 = sys.version_info[0] >= 3
 
 # Only run if demandimport is allowed
 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
@@ -16,6 +20,16 @@
 if sys.flags.optimize:
     sys.exit(80)
 
+if ispy3:
+    from importlib.util import _LazyModule
+
+    try:
+        from importlib.util import _Module as moduletype
+    except ImportError:
+        moduletype = types.ModuleType
+else:
+    moduletype = types.ModuleType
+
 if os.name != 'nt':
     try:
         import distutils.msvc9compiler
@@ -43,15 +57,26 @@
 
 # We use assert instead of a unittest test case because having imports inside
 # functions changes behavior of the demand importer.
-assert f(node) == "<module 'mercurial.node' from '?'>", f(node)
+if ispy3:
+    assert not isinstance(node, _LazyModule)
+else:
+    assert f(node) == "<module 'mercurial.node' from '?'>", f(node)
 
 # now enable it for real
 del os.environ['HGDEMANDIMPORT']
 demandimport.enable()
 
 # Test access to special attributes through demandmod proxy
+assert 'mercurial.error' not in sys.modules
 from mercurial import error as errorproxy
-assert f(errorproxy) == "<unloaded module 'error'>", f(errorproxy)
+
+if ispy3:
+    # unsure why this isn't lazy.
+    assert not isinstance(f, _LazyModule)
+    assert f(errorproxy) == "<module 'mercurial.error' from '?'>", f(errorproxy)
+else:
+    assert f(errorproxy) == "<unloaded module 'error'>", f(errorproxy)
+
 doc = ' '.join(errorproxy.__doc__.split()[:3])
 assert doc == 'Mercurial exceptions. This', doc
 assert errorproxy.__name__ == 'mercurial.error', errorproxy.__name__
@@ -61,50 +86,122 @@
 name = errorproxy.__dict__['__name__']
 assert name == 'mercurial.error', name
 
-assert f(errorproxy) == "<proxied module 'error'>", f(errorproxy)
+if ispy3:
+    assert not isinstance(errorproxy, _LazyModule)
+    assert f(errorproxy) == "<module 'mercurial.error' from '?'>", f(errorproxy)
+else:
+    assert f(errorproxy) == "<proxied module 'error'>", f(errorproxy)
 
 import os
 
-assert f(os) == "<unloaded module 'os'>", f(os)
+if ispy3:
+    assert not isinstance(os, _LazyModule)
+    assert f(os) == "<module 'os' from '?'>", f(os)
+else:
+    assert f(os) == "<unloaded module 'os'>", f(os)
+
 assert f(os.system) == '<built-in function system>', f(os.system)
 assert f(os) == "<module 'os' from '?'>", f(os)
 
+assert 'mercurial.utils.procutil' not in sys.modules
 from mercurial.utils import procutil
 
-assert f(procutil) == "<unloaded module 'procutil'>", f(procutil)
+if ispy3:
+    assert isinstance(procutil, _LazyModule)
+    assert f(procutil) == "<module 'mercurial.utils.procutil' from '?'>", f(
+        procutil
+    )
+else:
+    assert f(procutil) == "<unloaded module 'procutil'>", f(procutil)
+
 assert f(procutil.system) == '<function system at 0x?>', f(procutil.system)
+assert procutil.__class__ == moduletype, procutil.__class__
 assert f(procutil) == "<module 'mercurial.utils.procutil' from '?'>", f(
     procutil
 )
 assert f(procutil.system) == '<function system at 0x?>', f(procutil.system)
 
+assert 'mercurial.hgweb' not in sys.modules
 from mercurial import hgweb
-assert f(hgweb) == "<unloaded module 'hgweb'>", f(hgweb)
-assert f(hgweb.hgweb_mod) == "<unloaded module 'hgweb_mod'>", f(hgweb.hgweb_mod)
+
+if ispy3:
+    assert not isinstance(hgweb, _LazyModule)
+    assert f(hgweb) == "<module 'mercurial.hgweb' from '?'>", f(hgweb)
+    assert isinstance(hgweb.hgweb_mod, _LazyModule)
+    assert (
+        f(hgweb.hgweb_mod) == "<module 'mercurial.hgweb.hgweb_mod' from '?'>"
+    ), f(hgweb.hgweb_mod)
+else:
+    assert f(hgweb) == "<unloaded module 'hgweb'>", f(hgweb)
+    assert f(hgweb.hgweb_mod) == "<unloaded module 'hgweb_mod'>", f(
+        hgweb.hgweb_mod
+    )
+
 assert f(hgweb) == "<module 'mercurial.hgweb' from '?'>", f(hgweb)
 
 import re as fred
-assert f(fred) == "<unloaded module 're'>", f(fred)
+
+if ispy3:
+    assert not isinstance(fred, _LazyModule)
+    assert f(fred) == "<module 're' from '?'>"
+else:
+    assert f(fred) == "<unloaded module 're'>", f(fred)
 
 import re as remod
-assert f(remod) == "<unloaded module 're'>", f(remod)
+
+if ispy3:
+    assert not isinstance(remod, _LazyModule)
+    assert f(remod) == "<module 're' from '?'>"
+else:
+    assert f(remod) == "<unloaded module 're'>", f(remod)
 
 import sys as re
-assert f(re) == "<unloaded module 'sys'>", f(re)
+
+if ispy3:
+    assert not isinstance(re, _LazyModule)
+    assert f(re) == "<module 'sys' (built-in)>"
+else:
+    assert f(re) == "<unloaded module 'sys'>", f(re)
 
-assert f(fred) == "<unloaded module 're'>", f(fred)
+if ispy3:
+    assert not isinstance(fred, _LazyModule)
+    assert f(fred) == "<module 're' from '?'>", f(fred)
+else:
+    assert f(fred) == "<unloaded module 're'>", f(fred)
+
 assert f(fred.sub) == '<function sub at 0x?>', f(fred.sub)
-assert f(fred) == "<proxied module 're'>", f(fred)
+
+if ispy3:
+    assert not isinstance(fred, _LazyModule)
+    assert f(fred) == "<module 're' from '?'>", f(fred)
+else:
+    assert f(fred) == "<proxied module 're'>", f(fred)
 
 remod.escape  # use remod
 assert f(remod) == "<module 're' from '?'>", f(remod)
 
-assert f(re) == "<unloaded module 'sys'>", f(re)
-assert f(re.stderr) == "<open file '<whatever>', mode 'w' at 0x?>", f(re.stderr)
-assert f(re) == "<proxied module 'sys'>", f(re)
+if ispy3:
+    assert not isinstance(re, _LazyModule)
+    assert f(re) == "<module 'sys' (built-in)>"
+    assert f(type(re.stderr)) == "<class '_io.TextIOWrapper'>", f(
+        type(re.stderr)
+    )
+    assert f(re) == "<module 'sys' (built-in)>"
+else:
+    assert f(re) == "<unloaded module 'sys'>", f(re)
+    assert f(re.stderr) == "<open file '<whatever>', mode 'w' at 0x?>", f(
+        re.stderr
+    )
+    assert f(re) == "<proxied module 'sys'>", f(re)
 
 import contextlib
-assert f(contextlib) == "<unloaded module 'contextlib'>", f(contextlib)
+
+if ispy3:
+    assert not isinstance(contextlib, _LazyModule)
+    assert f(contextlib) == "<module 'contextlib' from '?'>"
+else:
+    assert f(contextlib) == "<unloaded module 'contextlib'>", f(contextlib)
+
 try:
     from contextlib import unknownattr
 
@@ -113,7 +210,9 @@
         'module:\ncontextlib.unknownattr = %s' % f(unknownattr)
     )
 except ImportError as inst:
-    assert rsub(r"'", '', str(inst)) == 'cannot import name unknownattr'
+    assert rsub(r"'", '', str(inst)).startswith(
+        'cannot import name unknownattr'
+    )
 
 from mercurial import util