Make test initialization self-registering
Adding or removing tests is a little bit cumbersome currently: the test
needs to be added to CMakeLists.txt, the init function needs to be
predeclared in pybind11_tests.cpp, then called in the plugin
initialization. While this isn't a big deal for tests that are being
committed, it's more of a hassle when working on some new feature or
test code for which I temporarily only care about building and linking
the test being worked on rather than the entire test suite.
This commit changes tests to self-register their initialization by
having each test initialize a local object (which stores the
initialization function in a static variable). This makes changing the
set of tests being build easy: one only needs to add or comment out
test names in tests/CMakeLists.txt.
A couple other minor changes that go along with this:
- test_eigen.cpp is now included in the test list, then removed if eigen
isn't available. This lets you disable the eigen tests by commenting
it out, just like all the other tests, but keeps the build working
without eigen eigen isn't available. (Also, if it's commented out, we
don't even bother looking for and reporting the building with/without
eigen status message).
- pytest is now invoked with all the built test names (with .cpp changed
to .py) so that it doesn't try to run tests that weren't built.
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);
-}
+});