hy/tests/importer/test_importer.py
2018-08-27 01:02:29 -05:00

269 lines
7.4 KiB
Python

# Copyright 2018 the authors.
# This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE.
import os
import sys
import ast
import imp
import tempfile
import runpy
import importlib
from fractions import Fraction
import pytest
import hy
from hy._compat import bytes_type
from hy.errors import HyTypeError
from hy.lex import LexException
from hy.compiler import hy_compile
from hy.importer import hy_parse, HyLoader, cache_from_source
def test_basics():
"Make sure the basics of the importer work"
assert os.path.isfile('tests/resources/__init__.py')
resources_mod = importlib.import_module('tests.resources')
assert hasattr(resources_mod, 'kwtest')
assert os.path.isfile('tests/resources/bin/__init__.hy')
bin_mod = importlib.import_module('tests.resources.bin')
assert hasattr(bin_mod, '_null_fn_for_import_test')
def test_runpy():
# XXX: `runpy` won't update cached bytecode! Don't know if that's
# intentional or not.
basic_ns = runpy.run_path('tests/resources/importer/basic.hy')
assert 'square' in basic_ns
main_ns = runpy.run_path('tests/resources/bin')
assert main_ns['visited_main'] == 1
del main_ns
main_ns = runpy.run_module('tests.resources.bin')
assert main_ns['visited_main'] == 1
with pytest.raises(IOError):
runpy.run_path('tests/resources/foobarbaz.py')
def test_stringer():
_ast = hy_compile(hy_parse("(defn square [x] (* x x))"), '')
assert type(_ast.body[0]) == ast.FunctionDef
def test_imports():
path = os.getcwd() + "/tests/resources/importer/a.hy"
testLoader = HyLoader("tests.resources.importer.a", path)
def _import_test():
try:
return testLoader.load_module()
except:
return "Error"
assert _import_test() == "Error"
assert _import_test() is not None
def test_import_error_reporting():
"Make sure that (import) reports errors correctly."
def _import_error_test():
try:
_ = hy_compile(hy_parse("(import \"sys\")"), '')
except HyTypeError:
return "Error reported"
assert _import_error_test() == "Error reported"
assert _import_error_test() is not None
@pytest.mark.skipif(sys.dont_write_bytecode,
reason="Bytecode generation is suppressed")
def test_import_autocompiles():
"Test that (import) byte-compiles the module."
with tempfile.NamedTemporaryFile(suffix='.hy', delete=True) as f:
f.write(b'(defn pyctest [s] (+ "X" s "Y"))')
f.flush()
pyc_path = cache_from_source(f.name)
try:
os.remove(pyc_path)
except (IOError, OSError):
pass
test_loader = HyLoader("mymodule", f.name).load_module()
assert hasattr(test_loader, 'pyctest')
assert os.path.exists(pyc_path)
os.remove(pyc_path)
def test_eval():
def eval_str(s):
return hy.eval(hy.read_str(s))
assert eval_str('[1 2 3]') == [1, 2, 3]
assert eval_str('{"dog" "bark" "cat" "meow"}') == {
'dog': 'bark', 'cat': 'meow'}
assert eval_str('(, 1 2 3)') == (1, 2, 3)
assert eval_str('#{3 1 2}') == {1, 2, 3}
assert eval_str('1/2') == Fraction(1, 2)
assert eval_str('(.strip " fooooo ")') == 'fooooo'
assert eval_str(
'(if True "this is if true" "this is if false")') == "this is if true"
assert eval_str('(lfor num (range 100) :if (= (% num 2) 1) (pow num 2))') == [
pow(num, 2) for num in range(100) if num % 2 == 1]
def test_reload():
"""Copied from CPython's `test_import.py`"""
def unlink(filename):
os.unlink(source)
bytecode = cache_from_source(source)
if os.path.isfile(bytecode):
os.unlink(bytecode)
TESTFN = 'testfn'
source = TESTFN + os.extsep + "hy"
with open(source, "w") as f:
f.write("(setv a 1)")
f.write("(setv b 2)")
sys.path.insert(0, os.curdir)
try:
mod = importlib.import_module(TESTFN)
assert TESTFN in sys.modules
assert mod.a == 1
assert mod.b == 2
# On WinXP, just replacing the .py file wasn't enough to
# convince reload() to reparse it. Maybe the timestamp didn't
# move enough. We force it to get reparsed by removing the
# compiled file too.
unlink(source)
# Now damage the module.
with open(source, "w") as f:
f.write("(setv a 10)")
f.write("(setv b (// 20 0))")
with pytest.raises(ZeroDivisionError):
imp.reload(mod)
# But we still expect the module to be in sys.modules.
mod = sys.modules.get(TESTFN)
assert mod is not None
# We should have replaced a w/ 10, but the old b value should
# stick.
assert mod.a == 10
assert mod.b == 2
# Now fix the issue and reload the module.
unlink(source)
with open(source, "w") as f:
f.write("(setv a 11)")
f.write("(setv b (// 20 1))")
imp.reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 11
assert mod.b == 20
# Now cause a LexException
unlink(source)
with open(source, "w") as f:
f.write("(setv a 11")
f.write("(setv b (// 20 1))")
with pytest.raises(LexException):
imp.reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 11
assert mod.b == 20
# Fix it and retry
unlink(source)
with open(source, "w") as f:
f.write("(setv a 12)")
f.write("(setv b (// 10 1))")
imp.reload(mod)
mod = sys.modules.get(TESTFN)
assert mod is not None
assert mod.a == 12
assert mod.b == 10
finally:
del sys.path[0]
unlink(source)
del sys.modules[TESTFN]
def test_circular():
"""Test circular imports by creating a temporary file/module that calls a
function that imports itself."""
sys.path.insert(0, os.path.abspath('tests/resources/importer'))
try:
mod = runpy.run_module('circular')
assert mod['f']() == 1
finally:
sys.path.pop(0)
def test_shadowed_basename():
"""Make sure Hy loads `.hy` files instead of their `.py` counterparts (.e.g
`__init__.py` and `__init__.hy`).
"""
sys.path.insert(0, os.path.realpath('tests/resources/importer'))
try:
assert os.path.isfile('tests/resources/importer/foo/__init__.hy')
assert os.path.isfile('tests/resources/importer/foo/__init__.py')
assert os.path.isfile('tests/resources/importer/foo/some_mod.hy')
assert os.path.isfile('tests/resources/importer/foo/some_mod.py')
foo = importlib.import_module('foo')
assert foo.__file__.endswith('foo/__init__.hy')
assert foo.ext == 'hy'
some_mod = importlib.import_module('foo.some_mod')
assert some_mod.__file__.endswith('foo/some_mod.hy')
assert some_mod.ext == 'hy'
finally:
sys.path.pop(0)
def test_docstring():
"""Make sure a module's docstring is loaded."""
sys.path.insert(0, os.path.realpath('tests/resources/importer'))
try:
mod = importlib.import_module('docstring')
expected_doc = ("This module has a docstring.\n\n"
"It covers multiple lines, too!\n")
assert mod.__doc__ == expected_doc
assert mod.a == 1
finally:
sys.path.pop(0)