Merge pull request #381 from jagerman/tests-self-registering

Make test initialization self-registering
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5841d82..0efefaa 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -7,6 +7,7 @@
   test_buffers.cpp
   test_callbacks.cpp
   test_constants_and_functions.cpp
+  test_eigen.cpp
   test_enum.cpp
   test_eval.cpp
   test_exceptions.cpp
@@ -28,14 +29,21 @@
   test_virtual_functions.cpp
 )
 
-# Check if Eigen is available
-find_package(Eigen3 QUIET)
+string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}")
 
-if(EIGEN3_FOUND)
-  list(APPEND PYBIND11_TEST_FILES test_eigen.cpp)
-  message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}")
-else()
-  message(STATUS "Building tests WITHOUT Eigen")
+# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
+# keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed"
+# skip message).
+list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I)
+if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
+  find_package(Eigen3 QUIET)
+
+  if(EIGEN3_FOUND)
+    message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}")
+  else()
+    list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
+    message(STATUS "Building tests WITHOUT Eigen")
+  endif()
 endif()
 
 # Create the binding library
@@ -74,5 +82,5 @@
 endif()
 
 # A single command to compile and run the tests
-add_custom_target(pytest COMMAND ${PYTHON_EXECUTABLE} -m pytest -rws
+add_custom_target(pytest COMMAND ${PYTHON_EXECUTABLE} -m pytest -rws ${PYBIND11_PYTEST_FILES}
                   DEPENDS pybind11_tests WORKING_DIRECTORY ${testdir})
diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp
index 525f379..f3f557a 100644
--- a/tests/pybind11_tests.cpp
+++ b/tests/pybind11_tests.cpp
@@ -10,32 +10,14 @@
 #include "pybind11_tests.h"
 #include "constructor_stats.h"
 
-void init_ex_methods_and_attributes(py::module &);
-void init_ex_python_types(py::module &);
-void init_ex_operator_overloading(py::module &);
-void init_ex_constants_and_functions(py::module &);
-void init_ex_callbacks(py::module &);
-void init_ex_sequences_and_iterators(py::module &);
-void init_ex_buffers(py::module &);
-void init_ex_smart_ptr(py::module &);
-void init_ex_modules(py::module &);
-void init_ex_numpy_vectorize(py::module &);
-void init_ex_arg_keywords_and_defaults(py::module &);
-void init_ex_virtual_functions(py::module &);
-void init_ex_keep_alive(py::module &);
-void init_ex_opaque_types(py::module &);
-void init_ex_pickling(py::module &);
-void init_ex_inheritance(py::module &);
-void init_ex_stl_binder_vector(py::module &);
-void init_ex_eval(py::module &);
-void init_ex_custom_exceptions(py::module &);
-void init_ex_numpy_dtypes(py::module &);
-void init_ex_enum(py::module &);
-void init_issues(py::module &);
+std::list<std::function<void(py::module &)>> &initializers() {
+    static std::list<std::function<void(py::module &)>> inits;
+    return inits;
+}
 
-#if defined(PYBIND11_TEST_EIGEN)
-    void init_eigen(py::module &);
-#endif
+test_initializer::test_initializer(std::function<void(py::module &)> initializer) {
+    initializers().push_back(std::move(initializer));
+}
 
 void bind_ConstructorStats(py::module &m) {
     py::class_<ConstructorStats>(m, "ConstructorStats")
@@ -54,35 +36,10 @@
 
     bind_ConstructorStats(m);
 
-    init_ex_methods_and_attributes(m);
-    init_ex_python_types(m);
-    init_ex_operator_overloading(m);
-    init_ex_constants_and_functions(m);
-    init_ex_callbacks(m);
-    init_ex_sequences_and_iterators(m);
-    init_ex_buffers(m);
-    init_ex_smart_ptr(m);
-    init_ex_modules(m);
-    init_ex_numpy_vectorize(m);
-    init_ex_arg_keywords_and_defaults(m);
-    init_ex_virtual_functions(m);
-    init_ex_keep_alive(m);
-    init_ex_opaque_types(m);
-    init_ex_pickling(m);
-    init_ex_inheritance(m);
-    init_ex_stl_binder_vector(m);
-    init_ex_eval(m);
-    init_ex_custom_exceptions(m);
-    init_ex_numpy_dtypes(m);
-    init_ex_enum(m);
-    init_issues(m);
+    for (const auto &initializer : initializers())
+        initializer(m);
 
-#if defined(PYBIND11_TEST_EIGEN)
-    init_eigen(m);
-    m.attr("have_eigen") = py::cast(true);
-#else
-    m.attr("have_eigen") = py::cast(false);
-#endif
+    if (!m.attr("have_eigen")) m.attr("have_eigen") = py::cast(false);
 
     return m.ptr();
 }
diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h
index ab8fff7..8af3154 100644
--- a/tests/pybind11_tests.h
+++ b/tests/pybind11_tests.h
@@ -1,7 +1,15 @@
+#pragma once
 #include <pybind11/pybind11.h>
 #include <iostream>
+#include <functional>
+#include <list>
 
 using std::cout;
 using std::endl;
 
 namespace py = pybind11;
+
+class test_initializer {
+public:
+    test_initializer(std::function<void(py::module &)> initializer);
+};
diff --git a/tests/test_buffers.cpp b/tests/test_buffers.cpp
index 5a4dc67..c3a7a9e 100644
--- a/tests/test_buffers.cpp
+++ b/tests/test_buffers.cpp
@@ -74,7 +74,7 @@
     float *m_data;
 };
 
-void init_ex_buffers(py::module &m) {
+test_initializer buffers([](py::module &m) {
     py::class_<Matrix> mtx(m, "Matrix");
 
     mtx.def(py::init<size_t, size_t>())
@@ -114,4 +114,4 @@
             );
         })
         ;
-}
+});
diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp
index 270ff5c..31d0740 100644
--- a/tests/test_callbacks.cpp
+++ b/tests/test_callbacks.cpp
@@ -71,7 +71,7 @@
     }
 };
 
-void init_ex_callbacks(py::module &m) {
+test_initializer callbacks([](py::module &m) {
     m.def("test_callback1", &test_callback1);
     m.def("test_callback2", &test_callback2);
     m.def("test_callback3", &test_callback3);
@@ -95,4 +95,4 @@
     m.def("test_dummy_function", &test_dummy_function);
     // Export the payload constructor statistics for testing purposes:
     m.def("payload_cstats", &ConstructorStats::get<Payload>);
-}
+});
diff --git a/tests/test_constants_and_functions.cpp b/tests/test_constants_and_functions.cpp
index 03f4177..29a2796 100644
--- a/tests/test_constants_and_functions.cpp
+++ b/tests/test_constants_and_functions.cpp
@@ -38,7 +38,7 @@
     return ret;
 }
 
-void init_ex_constants_and_functions(py::module &m) {
+test_initializer constants_and_functions([](py::module &m) {
     m.attr("some_constant") = py::int_(14);
 
     m.def("test_function", &test_function1);
@@ -52,4 +52,4 @@
 
     m.def("return_bytes", &return_bytes);
     m.def("print_bytes", &print_bytes);
-}
+});
diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp
index 42bb969..518cfec 100644
--- a/tests/test_eigen.cpp
+++ b/tests/test_eigen.cpp
@@ -32,7 +32,7 @@
 MatrixXfRowMajor double_mat_rm(const MatrixXfRowMajor& x)
 { return 2.0f * x; }
 
-void init_eigen(py::module &m) {
+test_initializer eigen([](py::module &m) {
     typedef Eigen::Matrix<float, 5, 6, Eigen::RowMajor> FixedMatrixR;
     typedef Eigen::Matrix<float, 5, 6> FixedMatrixC;
     typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> DenseMatrixR;
@@ -40,6 +40,8 @@
     typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR;
     typedef Eigen::SparseMatrix<float> SparseMatrixC;
 
+    m.attr("have_eigen") = py::cast(true);
+
     // Non-symmetric matrix with zero elements
     Eigen::MatrixXf mat(5, 6);
     mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0,
@@ -129,4 +131,4 @@
     m.def("sparse_passthrough_c", [](const SparseMatrixC &m) -> SparseMatrixC { 
         return m;
     });
-}
+});
diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp
index e4b2594..6ed0a6a 100644
--- a/tests/test_enum.cpp
+++ b/tests/test_enum.cpp
@@ -35,7 +35,7 @@
     return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three");
 }
 
-void init_ex_enum(py::module &m) {
+test_initializer enums([](py::module &m) {
     m.def("test_scoped_enum", &test_scoped_enum);
 
     py::enum_<UnscopedEnum>(m, "UnscopedEnum")
@@ -54,4 +54,4 @@
         .value("EFirstMode", ClassWithUnscopedEnum::EFirstMode)
         .value("ESecondMode", ClassWithUnscopedEnum::ESecondMode)
         .export_values();
-}
+});
diff --git a/tests/test_eval.cpp b/tests/test_eval.cpp
index 6b16e7c..45d811d 100644
--- a/tests/test_eval.cpp
+++ b/tests/test_eval.cpp
@@ -11,7 +11,7 @@
 #include <pybind11/eval.h>
 #include "pybind11_tests.h"
 
-void init_ex_eval(py::module & m) {
+test_initializer eval([](py::module &m) {
     auto global = py::dict(py::module::import("__main__").attr("__dict__"));
 
     m.def("test_eval_statements", [global]() {
@@ -77,4 +77,4 @@
         }
         return false;
     });
-}
+});
diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp
index 492308f..534f23b 100644
--- a/tests/test_exceptions.cpp
+++ b/tests/test_exceptions.cpp
@@ -66,7 +66,7 @@
     throw std::logic_error("this error should fall through to the standard handler");
 }
 
-void init_ex_custom_exceptions(py::module &m) {
+test_initializer custom_exceptions([](py::module &m) {
     // make a new custom exception and use it as a translation target
     static py::exception<MyException> ex(m, "MyException");
     py::register_exception_translator([](std::exception_ptr p) {
@@ -104,5 +104,5 @@
     m.def("throws3", &throws3);
     m.def("throws4", &throws4);
     m.def("throws_logic_error", &throws_logic_error);
-}
+});
 
diff --git a/tests/test_inheritance.cpp b/tests/test_inheritance.cpp
index 2997cce..e1aad99 100644
--- a/tests/test_inheritance.cpp
+++ b/tests/test_inheritance.cpp
@@ -44,7 +44,7 @@
 struct DerivedClass1 : BaseClass { };
 struct DerivedClass2 : BaseClass { };
 
-void init_ex_inheritance(py::module &m) {
+test_initializer inheritance([](py::module &m) {
     py::class_<Pet> pet_class(m, "Pet");
     pet_class
         .def(py::init<std::string, std::string>())
@@ -69,4 +69,4 @@
     m.def("return_class_1", []() -> BaseClass* { return new DerivedClass1(); });
     m.def("return_class_2", []() -> BaseClass* { return new DerivedClass2(); });
     m.def("return_none", []() -> BaseClass* { return nullptr; });
-}
+});
diff --git a/tests/test_issues.cpp b/tests/test_issues.cpp
index 5f680eb..51013e5 100644
--- a/tests/test_issues.cpp
+++ b/tests/test_issues.cpp
@@ -173,3 +173,6 @@
     m2.def("get_NestB", [](const NestB &b) { return b.value; });
     m2.def("get_NestC", [](const NestC &c) { return c.value; });
 }
+
+// MSVC workaround: trying to use a lambda here crashes MSCV
+test_initializer issues(&init_issues);
diff --git a/tests/test_keep_alive.cpp b/tests/test_keep_alive.cpp
index 25f852e..a07670b 100644
--- a/tests/test_keep_alive.cpp
+++ b/tests/test_keep_alive.cpp
@@ -25,7 +25,7 @@
     Child *returnNullChild() { return nullptr; }
 };
 
-void init_ex_keep_alive(py::module &m) {
+test_initializer keep_alive([](py::module &m) {
     py::class_<Parent>(m, "Parent")
         .def(py::init<>())
         .def("addChild", &Parent::addChild)
@@ -37,4 +37,4 @@
 
     py::class_<Child>(m, "Child")
         .def(py::init<>());
-}
+});
diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp
index 349b7ea..0656cc5 100644
--- a/tests/test_kwargs_and_defaults.cpp
+++ b/tests/test_kwargs_and_defaults.cpp
@@ -39,7 +39,7 @@
     void foo(int, float) {}
 };
 
-void init_ex_arg_keywords_and_defaults(py::module &m) {
+test_initializer arg_keywords_and_defaults([](py::module &m) {
     m.def("kw_func0", &kw_func);
     m.def("kw_func1", &kw_func, py::arg("x"), py::arg("y"));
     m.def("kw_func2", &kw_func, py::arg("x") = 100, py::arg("y") = 200);
@@ -63,4 +63,4 @@
     py::class_<KWClass>(m, "KWClass")
         .def("foo0", &KWClass::foo)
         .def("foo1", &KWClass::foo, "x"_a, "y"_a);
-}
+});
diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp
index 9317d78..8b0351e 100644
--- a/tests/test_methods_and_attributes.cpp
+++ b/tests/test_methods_and_attributes.cpp
@@ -53,7 +53,7 @@
     int value = 0;
 };
 
-void init_ex_methods_and_attributes(py::module &m) {
+test_initializer methods_and_attributes([](py::module &m) {
     py::class_<ExampleMandA>(m, "ExampleMandA")
         .def(py::init<>())
         .def(py::init<int>())
@@ -81,4 +81,4 @@
         .def("__str__", &ExampleMandA::toString)
         .def_readwrite("value", &ExampleMandA::value)
         ;
-}
+});
diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp
index b77dacc..50c7d84 100644
--- a/tests/test_modules.cpp
+++ b/tests/test_modules.cpp
@@ -39,7 +39,7 @@
     A a2{2};
 };
 
-void init_ex_modules(py::module &m) {
+test_initializer modules([](py::module &m) {
     py::module m_sub = m.def_submodule("submodule");
     m_sub.def("submodule_func", &submodule_func);
 
@@ -55,4 +55,4 @@
         .def_readwrite("a2", &B::a2);
 
     m.attr("OD") = py::module::import("collections").attr("OrderedDict");
-}
+});
diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp
index 7133a80..3041e55 100644
--- a/tests/test_numpy_dtypes.cpp
+++ b/tests/test_numpy_dtypes.cpp
@@ -267,7 +267,7 @@
     return list;
 }
 
-void init_ex_numpy_dtypes(py::module &m) {
+test_initializer numpy_dtypes([](py::module &m) {
     try {
         py::module::import("numpy");
     } catch (...) {
@@ -297,6 +297,6 @@
     m.def("test_array_ctors", &test_array_ctors);
     m.def("test_dtype_ctors", &test_dtype_ctors);
     m.def("test_dtype_methods", &test_dtype_methods);
-}
+});
 
 #undef PYBIND11_PACKED
diff --git a/tests/test_numpy_vectorize.cpp b/tests/test_numpy_vectorize.cpp
index f05c655..7b0c51e 100644
--- a/tests/test_numpy_vectorize.cpp
+++ b/tests/test_numpy_vectorize.cpp
@@ -20,7 +20,7 @@
     return c * std::complex<double>(2.f);
 }
 
-void init_ex_numpy_vectorize(py::module &m) {
+test_initializer numpy_vectorize([](py::module &m) {
     // Vectorize all arguments of a function (though non-vector arguments are also allowed)
     m.def("vectorized_func", py::vectorize(my_func));
 
@@ -38,4 +38,4 @@
     m.def("selective_func", [](py::array_t<int, py::array::c_style>) { return "Int branch taken."; });
     m.def("selective_func", [](py::array_t<float, py::array::c_style>) { return "Float branch taken."; });
     m.def("selective_func", [](py::array_t<std::complex<float>, py::array::c_style>) { return "Complex float branch taken."; });
-}
+});
diff --git a/tests/test_opaque_types.cpp b/tests/test_opaque_types.cpp
index 901f05e..54f4dc7 100644
--- a/tests/test_opaque_types.cpp
+++ b/tests/test_opaque_types.cpp
@@ -21,7 +21,7 @@
 /* IMPORTANT: Disable internal pybind11 translation mechanisms for STL data structures */
 PYBIND11_MAKE_OPAQUE(StringList);
 
-void init_ex_opaque_types(py::module &m) {
+test_initializer opaque_types([](py::module &m) {
     py::class_<StringList>(m, "StringList")
         .def(py::init<>())
         .def("pop_back", &StringList::pop_back)
@@ -59,4 +59,4 @@
         result->push_back("some value");
         return std::unique_ptr<StringList>(result);
     });
-}
+});
diff --git a/tests/test_operator_overloading.cpp b/tests/test_operator_overloading.cpp
index b84a5b8..93aea80 100644
--- a/tests/test_operator_overloading.cpp
+++ b/tests/test_operator_overloading.cpp
@@ -52,7 +52,7 @@
     float x, y;
 };
 
-void init_ex_operator_overloading(py::module &m) {
+test_initializer operator_overloading([](py::module &m) {
     py::class_<Vector2>(m, "Vector2")
         .def(py::init<float, float>())
         .def(py::self + py::self)
@@ -73,4 +73,4 @@
         ;
 
     m.attr("Vector") = m.attr("Vector2");
-}
+});
diff --git a/tests/test_pickling.cpp b/tests/test_pickling.cpp
index 4a48452..4494c24 100644
--- a/tests/test_pickling.cpp
+++ b/tests/test_pickling.cpp
@@ -24,7 +24,7 @@
     int m_extra2 = 0;
 };
 
-void init_ex_pickling(py::module &m) {
+test_initializer pickling([](py::module &m) {
     py::class_<Pickleable>(m, "Pickleable")
         .def(py::init<std::string>())
         .def("value", &Pickleable::value)
@@ -48,4 +48,4 @@
             p.setExtra1(t[1].cast<int>());
             p.setExtra2(t[2].cast<int>());
         });
-}
+});
diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp
index 39e43eb..8ec7e26 100644
--- a/tests/test_python_types.cpp
+++ b/tests/test_python_types.cpp
@@ -167,7 +167,7 @@
 int ExamplePythonTypes::value = 0;
 const int ExamplePythonTypes::value2 = 5;
 
-void init_ex_python_types(py::module &m) {
+test_initializer python_types([](py::module &m) {
     /* No constructor is explicitly defined below. An exception is raised when
        trying to construct it directly from Python */
     py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation")
@@ -197,4 +197,4 @@
         .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member")
         .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)")
         ;
-}
+});
diff --git a/tests/test_sequences_and_iterators.cpp b/tests/test_sequences_and_iterators.cpp
index 39e342b..0a88cef 100644
--- a/tests/test_sequences_and_iterators.cpp
+++ b/tests/test_sequences_and_iterators.cpp
@@ -168,7 +168,7 @@
     return !(*it).first || !(*it).second;
 }
 
-void init_ex_sequences_and_iterators(py::module &m) {
+test_initializer sequences_and_iterators([](py::module &m) {
 
     py::class_<Sequence> seq(m, "Sequence");
 
@@ -271,4 +271,4 @@
     On the actual Sequence object, the iterator would be constructed as follows:
     .def("__iter__", [](py::object s) { return PySequenceIterator(s.cast<const Sequence &>(), s); })
 #endif
-}
+});
diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp
index 46c83b3..d425969 100644
--- a/tests/test_smart_ptr.cpp
+++ b/tests/test_smart_ptr.cpp
@@ -105,7 +105,7 @@
 void print_myobject3_3(const std::shared_ptr<MyObject3> &obj) { std::cout << obj->toString() << std::endl; }
 void print_myobject3_4(const std::shared_ptr<MyObject3> *obj) { std::cout << (*obj)->toString() << std::endl; }
 
-void init_ex_smart_ptr(py::module &m) {
+test_initializer smart_ptr([](py::module &m) {
     py::class_<Object, ref<Object>> obj(m, "Object");
     obj.def("getRefCount", &Object::getRefCount);
 
@@ -147,4 +147,4 @@
 
     // Expose constructor stats for the ref type
     m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
-}
+});
diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp
index da854fc..a6ed6bf 100644
--- a/tests/test_stl_binders.cpp
+++ b/tests/test_stl_binders.cpp
@@ -24,7 +24,7 @@
     return s;
 }
 
-void init_ex_stl_binder_vector(py::module &m) {
+test_initializer stl_binder_vector([](py::module &m) {
     py::class_<El>(m, "El")
         .def(py::init<int>());
 
@@ -34,4 +34,4 @@
     py::bind_vector<El>(m, "VectorEl");
 
     py::bind_vector<std::vector<El>>(m, "VectorVectorEl");
-}
+});
diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp
index f0f4702..e591ba9 100644
--- a/tests/test_virtual_functions.cpp
+++ b/tests/test_virtual_functions.cpp
@@ -283,7 +283,7 @@
 };
 
 
-void init_ex_virtual_functions(py::module &m) {
+test_initializer virtual_functions([](py::module &m) {
     /* Important: indicate the trampoline class PyExampleVirt using the third
        argument to py::class_. The second argument with the unique pointer
        is simply the default holder type used by pybind11. */
@@ -315,4 +315,4 @@
 
     m.def("cstats_debug", &ConstructorStats::get<ExampleVirt>);
     initialize_inherited_virtuals(m);
-}
+});