Repository: hylang/hy

Issue:
ship a py.test plugin for hy
see if this is doable



Discussion:
We got `hytest` as an own lib. Do we still want this or?

I was looking into adding a Py.test plugin for HyTest, to support mixed projects. Then I got bored and distracted and forgot all about it...


Resolving patch:
diff --git a/conftest.py b/conftest.py
new file mode 100644
--- /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/hy/importer.py b/hy/importer.py
--- a/hy/importer.py
+++ b/hy/importer.py
@@ -30,7 +30,6 @@
 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):


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
--- 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
diff --git a/tests/compilers/native/quoting.hy b/tests/compilers/native/quoting.hy
deleted file mode 100644
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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)))