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.
This commit is contained in:
Jason Rhinelander 2016-09-03 14:54:22 -04:00
parent 06d8de113a
commit 52f4be8946
26 changed files with 83 additions and 105 deletions

View File

@ -7,6 +7,7 @@ set(PYBIND11_TEST_FILES
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 @@ set(PYBIND11_TEST_FILES
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 @@ if(NOT PYBIND11_PYTEST_FOUND)
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})

View File

@ -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 @@ PYBIND11_PLUGIN(pybind11_tests) {
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();
}

View File

@ -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);
};

View File

@ -74,7 +74,7 @@ private:
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 @@ void init_ex_buffers(py::module &m) {
);
})
;
}
});

View File

@ -71,7 +71,7 @@ struct Payload {
}
};
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 @@ void init_ex_callbacks(py::module &m) {
m.def("test_dummy_function", &test_dummy_function);
// Export the payload constructor statistics for testing purposes:
m.def("payload_cstats", &ConstructorStats::get<Payload>);
}
});

View File

@ -38,7 +38,7 @@ std::string print_bytes(py::bytes bytes) {
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 @@ void init_ex_constants_and_functions(py::module &m) {
m.def("return_bytes", &return_bytes);
m.def("print_bytes", &print_bytes);
}
});

View File

@ -32,7 +32,7 @@ typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> Ma
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 @@ void init_eigen(py::module &m) {
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 @@ void init_eigen(py::module &m) {
m.def("sparse_passthrough_c", [](const SparseMatrixC &m) -> SparseMatrixC {
return m;
});
}
});

View File

@ -35,7 +35,7 @@ std::string test_scoped_enum(ScopedEnum z) {
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 @@ void init_ex_enum(py::module &m) {
.value("EFirstMode", ClassWithUnscopedEnum::EFirstMode)
.value("ESecondMode", ClassWithUnscopedEnum::ESecondMode)
.export_values();
}
});

View File

@ -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 @@ void init_ex_eval(py::module & m) {
}
return false;
});
}
});

View File

@ -66,7 +66,7 @@ void throws_logic_error() {
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 @@ void init_ex_custom_exceptions(py::module &m) {
m.def("throws3", &throws3);
m.def("throws4", &throws4);
m.def("throws_logic_error", &throws_logic_error);
}
});

View File

@ -44,7 +44,7 @@ struct BaseClass { virtual ~BaseClass() {} };
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 @@ void init_ex_inheritance(py::module &m) {
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; });
}
});

View File

@ -173,3 +173,6 @@ void init_issues(py::module &m) {
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);

View File

@ -25,7 +25,7 @@ public:
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 @@ void init_ex_keep_alive(py::module &m) {
py::class_<Child>(m, "Child")
.def(py::init<>());
}
});

View File

@ -39,7 +39,7 @@ struct KWClass {
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 @@ void init_ex_arg_keywords_and_defaults(py::module &m) {
py::class_<KWClass>(m, "KWClass")
.def("foo0", &KWClass::foo)
.def("foo1", &KWClass::foo, "x"_a, "y"_a);
}
});

View File

@ -53,7 +53,7 @@ public:
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 @@ void init_ex_methods_and_attributes(py::module &m) {
.def("__str__", &ExampleMandA::toString)
.def_readwrite("value", &ExampleMandA::value)
;
}
});

View File

@ -39,7 +39,7 @@ public:
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 @@ void init_ex_modules(py::module &m) {
.def_readwrite("a2", &B::a2);
m.attr("OD") = py::module::import("collections").attr("OrderedDict");
}
});

View File

@ -267,7 +267,7 @@ py::list test_dtype_methods() {
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 @@ void init_ex_numpy_dtypes(py::module &m) {
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

View File

@ -20,7 +20,7 @@ std::complex<double> my_func3(std::complex<double> c) {
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 @@ void init_ex_numpy_vectorize(py::module &m) {
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."; });
}
});

View File

@ -21,7 +21,7 @@ public:
/* 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 @@ void init_ex_opaque_types(py::module &m) {
result->push_back("some value");
return std::unique_ptr<StringList>(result);
});
}
});

View File

@ -52,7 +52,7 @@ private:
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 @@ void init_ex_operator_overloading(py::module &m) {
;
m.attr("Vector") = m.attr("Vector2");
}
});

View File

@ -24,7 +24,7 @@ private:
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 @@ void init_ex_pickling(py::module &m) {
p.setExtra1(t[1].cast<int>());
p.setExtra2(t[2].cast<int>());
});
}
});

View File

@ -167,7 +167,7 @@ public:
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 @@ void init_ex_python_types(py::module &m) {
.def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member")
.def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)")
;
}
});

View File

@ -168,7 +168,7 @@ bool operator==(const NonZeroIterator<std::pair<A, B>>& it, const NonZeroSentine
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 @@ void init_ex_sequences_and_iterators(py::module &m) {
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
}
});

View File

@ -105,7 +105,7 @@ void print_myobject3_2(std::shared_ptr<MyObject3> obj) { std::cout << obj->toStr
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 @@ void init_ex_smart_ptr(py::module &m) {
// Expose constructor stats for the ref type
m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
}
});

View File

@ -24,7 +24,7 @@ std::ostream & operator<<(std::ostream &s, El const&v) {
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 @@ void init_ex_stl_binder_vector(py::module &m) {
py::bind_vector<El>(m, "VectorEl");
py::bind_vector<std::vector<El>>(m, "VectorVectorEl");
}
});

View File

@ -283,7 +283,7 @@ void initialize_inherited_virtuals(py::module &m) {
};
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 @@ void init_ex_virtual_functions(py::module &m) {
m.def("cstats_debug", &ConstructorStats::get<ExampleVirt>);
initialize_inherited_virtuals(m);
}
});