diff --git a/.gitignore b/.gitignore index f1cb8d0..503fc27 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ dist .coverage build/ -.noseids +/.cache diff --git a/.travis.yml b/.travis.yml index bd99240..f93fb74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,18 @@ sudo: false language: python -matrix: - include: - - python: 3.5 - env: TOXENV=py35 - - python: 3.6-dev - env: TOXENV=py36 -env: - - TOXENV=py27 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=pypy - - TOXENV=flake8 -install: pip install tox -script: tox -cache: - directories: - - .tox - - $HOME/.cache/pip +python: + - "2.7" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + - pypy +install: + - pip install --upgrade pytest + - pip install -r requirements-travis.txt + - pip install -e . +script: pytest +cache: pip after_success: make coveralls notifications: email: diff --git a/Makefile b/Makefile index 7913fea..1b618a7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ pip_url=https://bootstrap.pypa.io/get-pip.py python=python pip=pip coveralls=coveralls -nose=nosetests all: @echo "No default step. Use setup.py" @@ -41,7 +40,7 @@ endif dev: test flake test: venv - nosetests -sv + pytest tox: venv tox diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..72fa553 --- /dev/null +++ b/conftest.py @@ -0,0 +1,13 @@ +import _pytest +import hy +from hy._compat import PY3 + +def pytest_collect_file(parent, path): + if (path.ext == ".hy" + and "/tests/native_tests/" in path.dirname + "/" + and path.basename != "__init__.hy" + and not ("py3_only" in path.basename and not PY3)): + m = _pytest.python.pytest_pycollect_makemodule(path, parent) + # Spoof the module name to avoid hitting an assertion in pytest. + m.name = m.name[:-len(".hy")] + ".py" + return m diff --git a/docs/hacking.rst b/docs/hacking.rst index 4042208..707745d 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -62,12 +62,11 @@ Do this: Test! ===== -Tests are located in ``tests/``. We use `nose -`_. +Tests are located in ``tests/``. We use `pytest `_. To run the tests:: - $ nosetests + $ pytest Write tests---tests are good! diff --git a/hy/importer.py b/hy/importer.py index 1db67e3..f49129c 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -30,7 +30,6 @@ import marshal import struct import imp import sys -import platform import ast import os import __future__ @@ -114,17 +113,13 @@ def import_file_to_module(module_name, fpath, loader=None): module = imp.new_module(module_name) module.__file__ = fpath code = ast_compile(_ast, fpath, "exec") - if not (platform.python_implementation() == 'PyPy' and - 'nosetests' in sys.argv[0] and - is_package(module_name)): - # Nose can generate spurious errors in this specific situation. - try: - write_code_as_pyc(fpath, code) - except (IOError, OSError): - # We failed to save the bytecode, probably because of a - # permissions issue. The user only asked to import the - # file, so don't bug them about it. - pass + try: + write_code_as_pyc(fpath, code) + except (IOError, OSError): + # We failed to save the bytecode, probably because of a + # permissions issue. The user only asked to import the + # file, so don't bug them about it. + pass eval(code, module.__dict__) except (HyTypeError, LexException) as e: if e.source is None: @@ -136,9 +131,9 @@ def import_file_to_module(module_name, fpath, loader=None): sys.modules.pop(module_name, None) raise sys.modules[module_name] = module - module.__file__ = fpath module.__name__ = module_name + module.__file__ = fpath if loader: module.__loader__ = loader if is_package(module_name): diff --git a/make.bat b/make.bat index 2551ea2..1a010bc 100644 --- a/make.bat +++ b/make.bat @@ -55,7 +55,7 @@ goto :EOF if "%1" == "test" ( :test call :venv - nosetests -sv + pytest -sv goto :EOF ) diff --git a/requirements-dev.txt b/requirements-dev.txt index 63b9481..4e197a9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ -r requirements-travis.txt # test tools -nose +pytest tox # documentation diff --git a/setup.cfg b/setup.cfg index 31c8825..3923777 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,3 @@ -[nosetests] -detailed-errors=1 -with-coverage=1 -cover-package=hy -nocapture=1 - [wheel] universal = 1 @@ -12,7 +6,6 @@ omit = */python?.?/* */lib-python/?.?/*.py */lib_pypy/_*.py - */site-packages/nose/* */pypy/* [coverage:report] @@ -22,3 +15,8 @@ exclude_lines = # We want ignore_errors so we don't get NoSource warnings for loading # byte-compiled Hy modules. ignore_errors = True + +[tool:pytest] +# Be sure to include Hy test functions whose names end with "?", +# which will be mangled to begin with "is_". +python_functions=test_* is_test_* diff --git a/tests/__init__.py b/tests/__init__.py index 1fbc6c6..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,25 +0,0 @@ - -import hy # noqa -from hy._compat import PY3 -from .native_tests.cons import * # noqa -from .native_tests.defclass import * # noqa -from .native_tests.mathematics import * # noqa -from .native_tests.native_macros import * # noqa -from .native_tests.quote import * # noqa -from .native_tests.language import * # noqa -from .native_tests.unless import * # noqa -from .native_tests.when import * # noqa -from .native_tests.with_decorator import * # noqa -from .native_tests.core import * # noqa -from .native_tests.sharp_macros import * # noqa -from .native_tests.operators import * # noqa -from .native_tests.with_test import * # noqa -from .native_tests.extra.anaphoric import * # noqa -from .native_tests.contrib.loop import * # noqa -from .native_tests.contrib.walk import * # noqa -from .native_tests.contrib.multi import * # noqa -from .native_tests.contrib.sequences import * # noqa -from .native_tests.contrib.hy_repr import * # noqa - -if PY3: - from .native_tests.py3_only_tests import * # noqa diff --git a/tests/compilers/native/__init__.hy b/tests/compilers/native/__init__.hy deleted file mode 100644 index e69de29..0000000 diff --git a/tests/compilers/native/quoting.hy b/tests/compilers/native/quoting.hy deleted file mode 100644 index c74bb5a..0000000 --- a/tests/compilers/native/quoting.hy +++ /dev/null @@ -1,11 +0,0 @@ -;;; -;;; - -(import [hy [HyExpression HySymbol HyString HyBytes]]) - - -(defn test-basic-quoting [] - (assert (= (type (quote (foo bar))) HyExpression)) - (assert (= (type (quote foo)) HySymbol)) - (assert (= (type (quote "string")) HyString)) - (assert (= (type (quote b"string")) HyBytes))) diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py index 3f1b397..a6253b4 100644 --- a/tests/compilers/test_compiler.py +++ b/tests/compilers/test_compiler.py @@ -20,96 +20,85 @@ # DEALINGS IN THE SOFTWARE. import ast -import sys from hy import compiler from hy.models import HyExpression, HyList, HySymbol, HyInteger from hy._compat import PY3 -if sys.version_info[0] <= 2 and sys.version_info[1] <= 6: - import unittest2 as unittest -else: - import unittest + +def test_builds_with_dash(): + assert callable(compiler.builds("foobar")) + assert callable(compiler.builds("foo_bar")) + assert callable(compiler.builds("-")) + try: + compiler.builds("foobar-with-dash-") + except TypeError as e: + assert "*post* translated strings" in str(e) + else: + assert False -class CompilerTest(unittest.TestCase): - - def test_builds_with_dash(self): - self.assert_(callable(compiler.builds("foobar"))) - self.assert_(callable(compiler.builds("foo_bar"))) - self.assert_(callable(compiler.builds("-"))) - self.assertRaisesRegexp(TypeError, - r"\*post\* translated strings", - compiler.builds, "foobar-with-dash-") +def make_expression(*args): + h = HyExpression(args) + h.start_line = 1 + h.end_line = 1 + h.start_column = 1 + h.end_column = 1 + return h.replace(h) -class HyASTCompilerTest(unittest.TestCase): +def test_compiler_bare_names(): + """ + Check that the compiler doesn't drop bare names from code branches + """ + e = make_expression(HySymbol("do"), + HySymbol("a"), + HySymbol("b"), + HySymbol("c")) + ret = compiler.HyASTCompiler('test').compile(e) - @staticmethod - def _make_expression(*args): - h = HyExpression(args) - h.start_line = 1 - h.end_line = 1 - h.start_column = 1 - h.end_column = 1 - return h.replace(h) + # We expect two statements and a final expr. - def setUp(self): - self.c = compiler.HyASTCompiler('test') + assert len(ret.stmts) == 2 + for stmt, symbol in zip(ret.stmts, "ab"): + assert isinstance(stmt, ast.Expr) + assert isinstance(stmt.value, ast.Name) + assert stmt.value.id == symbol - def test_compiler_bare_names(self): - """ - Check that the compiler doesn't drop bare names from code branches - """ - ret = self.c.compile(self._make_expression(HySymbol("do"), - HySymbol("a"), - HySymbol("b"), - HySymbol("c"))) + assert isinstance(ret.expr, ast.Name) + assert ret.expr.id == "c" - # We expect two statements and a final expr. - self.assertEqual(len(ret.stmts), 2) - stmt = ret.stmts[0] - self.assertIsInstance(stmt, ast.Expr) - self.assertIsInstance(stmt.value, ast.Name) - self.assertEqual(stmt.value.id, "a") - stmt = ret.stmts[1] - self.assertIsInstance(stmt, ast.Expr) - self.assertIsInstance(stmt.value, ast.Name) - self.assertEqual(stmt.value.id, "b") - expr = ret.expr - self.assertIsInstance(expr, ast.Name) - self.assertEqual(expr.id, "c") - def test_compiler_yield_return(self): - """ - Check that the compiler correctly generates return statements for - a generator function. In Python versions prior to 3.3, the return - statement in a generator can't take a value, so the final expression - should not generate a return statement. From 3.3 onwards a return - value should be generated. - """ - ret = self.c.compile_function_def( - self._make_expression(HySymbol("fn"), - HyList(), - HyExpression([HySymbol("yield"), - HyInteger(2)]), - HyExpression([HySymbol("+"), - HyInteger(1), - HyInteger(1)]))) +def test_compiler_yield_return(): + """ + Check that the compiler correctly generates return statements for + a generator function. In Python versions prior to 3.3, the return + statement in a generator can't take a value, so the final expression + should not generate a return statement. From 3.3 onwards a return + value should be generated. + """ + e = make_expression(HySymbol("fn"), + HyList(), + HyExpression([HySymbol("yield"), + HyInteger(2)]), + HyExpression([HySymbol("+"), + HyInteger(1), + HyInteger(1)])) + ret = compiler.HyASTCompiler('test').compile_function_def(e) - self.assertEqual(len(ret.stmts), 1) - stmt = ret.stmts[0] - self.assertIsInstance(stmt, ast.FunctionDef) - body = stmt.body - self.assertEquals(len(body), 2) - self.assertIsInstance(body[0], ast.Expr) - self.assertIsInstance(body[0].value, ast.Yield) + assert len(ret.stmts) == 1 + stmt, = ret.stmts + assert isinstance(stmt, ast.FunctionDef) + body = stmt.body + assert len(body) == 2 + assert isinstance(body[0], ast.Expr) + assert isinstance(body[0].value, ast.Yield) - if PY3: - # From 3.3+, the final statement becomes a return value - self.assertIsInstance(body[1], ast.Return) - self.assertIsInstance(body[1].value, ast.BinOp) - else: - # In earlier versions, the expression is not returned - self.assertIsInstance(body[1], ast.Expr) - self.assertIsInstance(body[1].value, ast.BinOp) + if PY3: + # From 3.3+, the final statement becomes a return value + assert isinstance(body[1], ast.Return) + assert isinstance(body[1].value, ast.BinOp) + else: + # In earlier versions, the expression is not returned + assert isinstance(body[1], ast.Expr) + assert isinstance(body[1].value, ast.BinOp) diff --git a/tests/compilers/test_quoting.py b/tests/compilers/test_quoting.py deleted file mode 100644 index fb83d8d..0000000 --- a/tests/compilers/test_quoting.py +++ /dev/null @@ -1 +0,0 @@ -from .native.quoting import * # NOQA diff --git a/tests/native_tests/extra/reserved.hy b/tests/native_tests/extra/reserved.hy index cfe236f..7990c6b 100644 --- a/tests/native_tests/extra/reserved.hy +++ b/tests/native_tests/extra/reserved.hy @@ -1,4 +1,4 @@ -(import [hy.extra.reserved [names] hy._compat [PY3]]) +(import [hy.extra.reserved [names]] [hy._compat [PY3]]) (defn test-reserved [] (assert (is (type (names)) frozenset)) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 704bd5d..fbf7ce4 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -2,7 +2,8 @@ [os.path [exists isdir isfile]] [sys :as systest] [operator [or_]] - [hy.errors [HyTypeError]]) + [hy.errors [HyTypeError]] + pytest) (import sys) (import [hy._compat [PY3 PY34 PY35]]) @@ -686,20 +687,6 @@ (assert (= (cut [1 2 3 4 5]) [1 2 3 4 5]))) -(defn test-take [] - "NATIVE: test take" - (assert (= (take 0 [2 3]) [])) - (assert (= (take 1 [2 3]) [2])) - (assert (= (take 2 [2 3]) [2 3]))) - - -(defn test-drop [] - "NATIVE: test drop" - (assert (= (list (drop 0 [2 3])) [2 3])) - (assert (= (list (drop 1 [2 3])) [3])) - (assert (= (list (drop 2 [2 3])) []))) - - (defn test-rest [] "NATIVE: test rest" (assert (= (list (rest [1 2 3 4 5])) [2 3 4 5])) @@ -1175,7 +1162,7 @@ (defn test-try-except-return [] - "NATIVE: test we can return from in a try except" + "NATIVE: test that we can return from an `except` form" (assert (= ((fn [] (try xxx (except [NameError] (+ 1 1))))) 2)) (setv foo (try xxx (except [NameError] (+ 1 1)))) (assert (= foo 2)) @@ -1183,6 +1170,21 @@ (assert (= foo 4))) +#@(pytest.mark.xfail +(defn test-try-else-return [] + "NATIVE: test that we can return from the `else` clause of a `try`" + ; https://github.com/hylang/hy/issues/798 + (assert (= "ef" ((fn [] + (try (+ "a" "b") + (except [NameError] (+ "c" "d")) + (else (+ "e" "f"))))))) + (setv foo + (try (+ "A" "B") + (except [NameError] (+ "C" "D")) + (else (+ "E" "F")))) + (assert (= foo "EF")))) + + (defn test-require [] "NATIVE: test requiring macros from python code" (try (qplah 1 2 3 4) @@ -1252,13 +1254,6 @@ (assert (= "test" (:foo {:foo "test"})))) -(defn test-take [] - "NATIVE: test the take operator" - (assert (= [1 2 3] (list (take 3 [1 2 3])))) - (assert (= [1 2 3] (list (take 4 [1 2 3])))) - (assert (= [1 2] (list (take 2 [1 2 4]))))) - - (defn test-break-breaking [] "NATIVE: test checking if break actually breaks" (defn holy-grail [] (for [x (range 10)] (if (= x 5) (break))) x) @@ -1309,6 +1304,13 @@ (assert (= (macroexpand '(-> (a b) (-> (c d) (e f)))) '(e (c (a b) d) f)))) +#@(pytest.mark.xfail +(defn test-macroexpand-with-named-import [] + ; https://github.com/hylang/hy/issues/1207 + (defmacro m-with-named-import [] + (import [math [pow]]) + (pow 2 3)) + (assert (= (macroexpand '(m-with-named-import)) (** 2 3))))) (defn test-macroexpand-1 [] "Test macroexpand-1 on ->" @@ -1336,8 +1338,7 @@ (defn test-calling-module-name [] "NATIVE: Test the calling-module-name function" - (assert (= (calling-module-name -1) "hy.core.language")) - (assert (= (calling-module-name 0) "tests.native_tests.language"))) + (assert (= (calling-module-name -1) "hy.core.language"))) (defn test-disassemble [] diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy index 1f60650..9ee5c12 100644 --- a/tests/native_tests/operators.hy +++ b/tests/native_tests/operators.hy @@ -1,4 +1,4 @@ -(import [hy._compat [PY35]]) +(import pytest [hy._compat [PY35]]) (defmacro op-and-shadow-test [op &rest body] ; Creates two tests with the given `body`, one where all occurrences @@ -284,3 +284,8 @@ (assert (is (f 3 [1 2]) (!= f-name "in"))) (assert (is (f 2 [1 2]) (= f-name "in"))) (forbid (f 2 [1 2] [3 4]))) + +#@(pytest.mark.xfail +(defn test-apply-op [] + ; https://github.com/hylang/hy/issues/647 + (assert (= (eval '(apply + ["a" "b" "c"])) "abc")))) diff --git a/tests/native_tests/py3_only_tests.hy b/tests/native_tests/py3_only_tests.hy index 6c577b4..365a4fe 100644 --- a/tests/native_tests/py3_only_tests.hy +++ b/tests/native_tests/py3_only_tests.hy @@ -1,8 +1,5 @@ ;; Tests where the emitted code relies on Python 3. -;; Conditionally included in nosetests runs. - -(import [hy.errors [HyCompileError]]) - +;; conftest.py skips this file when running on Python 2. (defn test-exception-cause [] diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 617de5d..8cd6a07 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -1,4 +1,4 @@ -(import hy) +(import [hy [HyExpression HySymbol HyString HyBytes]]) (defn test-quote [] @@ -8,6 +8,13 @@ (assert (= q [(quote a) (quote b) (quote c)]))) +(defn test-basic-quoting [] + (assert (= (type (quote (foo bar))) HyExpression)) + (assert (= (type (quote foo)) HySymbol)) + (assert (= (type (quote "string")) HyString)) + (assert (= (type (quote b"string")) HyBytes))) + + (defn test-quoted-hoistable [] "NATIVE: check whether quote works on hoisted things" (setv f (quote (if True True True))) diff --git a/tox.ini b/tox.ini index a4ab4f0..6f0d909 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ skipsdist = True [testenv] commands = pip install --allow-all-external -e . - nosetests + pytest passenv = TERM deps =