diff --git a/Makefile b/Makefile index a9f5c43f..839b3bb8 100644 --- a/Makefile +++ b/Makefile @@ -150,10 +150,10 @@ GTEST_SRCS := $(wildcard $(GTEST_DIR)/src/*.cc) \ $(wildcard $(GTEST_DIR)/src/*.h) $(GTEST_HEADERS) GTEST_FILTER ?= '*' -TEST_SOURCES := $(wildcard tests/*.cc) +TEST_SOURCES := $(wildcard tests/*.cc) $(wildcard tests/library_wrappers/*.cc) TEST_DFILES := $(TEST_SOURCES:.cc=.d) TEST_OBJECTS := $(TEST_SOURCES:.cc=.o) -TEST_HEADERS := $(wildcard tests/*.h) $(GTEST_HEADERS) +TEST_HEADERS := $(wildcard tests/*.h) $(wildcard tests/library_wrappers/*.h) $(GTEST_HEADERS) TEST_INCLUDE := -I tests -I $(GTEST_DIR)/include TEST_MODULE := tests/_runner$(SO_SUFFIX) PYTHON_TESTS := $(wildcard tests/*.py) diff --git a/README.rst b/README.rst index 32604a1a..6fa83036 100644 --- a/README.rst +++ b/README.rst @@ -28,25 +28,13 @@ libpy requires: - gcc>=8 or clang>=10 - numpy>=1.11.3 -libpy also depends on the following system packages: +Optional Requirements +--------------------- -- google sparsehash - -To install these dependencies: - -ubuntu -~~~~~~ - -.. code-block:: bash +libpy optionally provides wrappers for the following libraries: - $ sudo apt install libsparsehash-dev - -macOS -~~~~~ - -.. code-block:: bash +- google sparsehash - $ brew install google-sparsehash Install ------- diff --git a/docs/source/install.rst b/docs/source/install.rst index ddc89f21..0381e361 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -14,25 +14,12 @@ lipy requires: - gcc>=8 or clang>=10 - numpy>=1.11.3 -libpy also depends on the following system packages: +Optional Requirements +--------------------- -- google sparsehash - -To install these dependencies: - -ubuntu -~~~~~~ - -.. code-block:: bash +libpy optionally provides wrappers for the following libraries: - $ sudo apt install libsparsehash-dev - -macOS -~~~~~ - -.. code-block:: bash - - $ brew install google-sparsehash +- google sparsehash Install ------- diff --git a/include/libpy/dense_hash_map.h b/include/libpy/library_wrappers/sparsehash.h similarity index 84% rename from include/libpy/dense_hash_map.h rename to include/libpy/library_wrappers/sparsehash.h index add4793b..99132ac2 100644 --- a/include/libpy/dense_hash_map.h +++ b/include/libpy/library_wrappers/sparsehash.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "libpy/to_object.h" @@ -153,5 +154,21 @@ template struct to_object> : public set_to_object> {}; +template +struct to_object> + : public map_to_object> {}; + +template +struct to_object> + : public map_to_object> {}; + +template +struct to_object> + : public set_to_object> {}; + +template +struct to_object> + : public set_to_object> {}; + } // namespace dispatch } // namespace py diff --git a/tests/test_dense_hash_map.cc b/tests/library_wrappers/test_sparsehash.cc similarity index 59% rename from tests/test_dense_hash_map.cc rename to tests/library_wrappers/test_sparsehash.cc index afd235b2..dcdd882e 100644 --- a/tests/test_dense_hash_map.cc +++ b/tests/library_wrappers/test_sparsehash.cc @@ -4,9 +4,44 @@ #include "gtest/gtest.h" #include "libpy/datetime64.h" -#include "libpy/dense_hash_map.h" +#include "libpy/itertools.h" +#include "libpy/library_wrappers/sparsehash.h" + +#include "test_utils.h" + +namespace test_sparsehash { + +using namespace std::literals; +using namespace py::cs::literals; + +class sparsehash_to_object : public with_python_interpreter {}; + +TEST_F(sparsehash_to_object, sparse_hash_map) { + auto map = google::sparse_hash_map(); + py_test::test_map_to_object_impl(map); +} + +TEST_F(sparsehash_to_object, dense_hash_map) { + auto map = google::dense_hash_map(); + map.set_empty_key("the_empty_key"s); + + py_test::test_map_to_object_impl(map); +} + +TEST_F(sparsehash_to_object, sparse_hash_set) { + auto filler = py_test::examples(); + auto a = google::sparse_hash_set(filler.begin(), filler.end()); + py_test::test_set_to_object_impl(a); +} + +TEST_F(sparsehash_to_object, dense_hash_set) { + auto filler = py_test::examples(); + auto a = google::dense_hash_set(filler.begin(), + filler.end(), + "the_empty_key"s); + py_test::test_set_to_object_impl(a); +} -namespace test_dense_hash_map { TEST(dense_hash_map, invalid_empty_key) { using double_key = py::dense_hash_map; EXPECT_THROW((double_key{std::numeric_limits::quiet_NaN()}), @@ -42,4 +77,5 @@ TEST(dense_hash_set, invalid_empty_key) { EXPECT_THROW((M8_key{py::datetime64ns::nat()}), std::invalid_argument); EXPECT_THROW((M8_key{py::datetime64ns::nat(), 10}), std::invalid_argument); } -} // namespace test_dense_hash_map + +} // namespace test_sparsehash diff --git a/tests/test_any.cc b/tests/test_any.cc index d2ad61b5..41aed8d9 100644 --- a/tests/test_any.cc +++ b/tests/test_any.cc @@ -4,7 +4,6 @@ #include "gtest/gtest.h" #include "libpy/any.h" -#include "libpy/dense_hash_map.h" namespace test_any { TEST(any_vtable, void_vtable) { @@ -88,7 +87,7 @@ TEST(any_vtable, ostream_format) { } TEST(any_vtable, map_key) { - py::dense_hash_map map(py::any_vtable{}); + std::unordered_map map; map[py::any_vtable::make()] = 0; map[py::any_vtable::make()] = 1; diff --git a/tests/test_object_map_key.cc b/tests/test_object_map_key.cc index 824b4993..9c44779d 100644 --- a/tests/test_object_map_key.cc +++ b/tests/test_object_map_key.cc @@ -3,7 +3,6 @@ #include "gtest/gtest.h" -#include "libpy/dense_hash_map.h" #include "libpy/exception.h" #include "libpy/meta.h" #include "libpy/object_map_key.h" @@ -241,8 +240,6 @@ void test_use_in_map(M map) { TEST_F(object_map_key, use_in_map) { test_use_in_map(std::map{}); test_use_in_map(std::unordered_map{}); - test_use_in_map(py::sparse_hash_map{}); - test_use_in_map(py::dense_hash_map{py::object_map_key{}}); } TEST_F(object_map_key, convert) { diff --git a/tests/test_to_object.cc b/tests/test_to_object.cc index 09bbbf81..388a7972 100644 --- a/tests/test_to_object.cc +++ b/tests/test_to_object.cc @@ -6,7 +6,6 @@ #include "libpy/any.h" #include "libpy/char_sequence.h" -#include "libpy/dense_hash_map.h" #include "libpy/itertools.h" #include "libpy/meta.h" #include "libpy/numpy_utils.h" @@ -21,162 +20,26 @@ using namespace py::cs::literals; class to_object : public with_python_interpreter {}; -template -std::array examples(); - -template<> -std::array examples() { - return {-200, 0, 1000}; -} - -template<> -std::array examples() { - return {"foo", "", "arglebargle"}; -} - -template<> -std::array, 3> examples() { - std::array foo{'f', 'o', 'o'}; - std::array bar{'b', 'a', 'r'}; - std::array baz{'b', 'a', 'z'}; - return {foo, bar, baz}; -} - -template<> -std::array examples() { - return {true, false, true}; -} - -template<> -std::array examples() { - return {-1.0, -0.0, 100.0}; -} - -template<> -std::array, 3> examples() { - Py_INCREF(Py_True); - Py_INCREF(Py_False); - Py_INCREF(Py_None); - return {py::owned_ref<>(Py_True), - py::owned_ref<>(Py_False), - py::owned_ref<>(Py_None)}; -} - -template -void test_map_to_object_impl(M m) { - - // Fill the map with some example values. - auto it = py::zip(examples(), - examples()); - for (auto [key, value] : it) { - m[key] = value; - } - - auto check_python_map = [&](py::owned_ref ob) { - ASSERT_TRUE(ob) << "to_object should not return null"; - EXPECT_TRUE(PyDict_Check(ob.get())); - - // Python map should be the same length as C++ map. - Py_ssize_t len = PyDict_Size(ob.get()); - EXPECT_EQ(std::size_t(len), m.size()) - << "Python dict length should match C++ map length."; - - // Key/Value pairs in the python map should match the result of calling - // to_object on each key/value pair in the C++ map. - for (auto& [cxx_key, cxx_value] : m) { - auto py_key = py::to_object(cxx_key); - auto py_value = py::to_object(cxx_value); - - py::borrowed_ref result = PyDict_GetItem(ob.get(), py_key.get()); - ASSERT_TRUE(result) << "Key should have been in the map"; - - bool values_equal = - PyObject_RichCompareBool(py_value.get(), result.get(), Py_EQ); - EXPECT_EQ(values_equal, 1) << "Dict values were not equal"; - } - }; - - // Check to_object with value, const value, and rvalue reference. - - py::owned_ref result = py::to_object(m); - check_python_map(result); - - const M& const_ref = m; - py::owned_ref constref_result = py::to_object(const_ref); - check_python_map(constref_result); - - M copy = m; // Make a copy before moving b/c the lambda above uses ``m``. - py::owned_ref rvalueref_result = py::to_object(std::move(copy)); - check_python_map(rvalueref_result); -} - TEST_F(to_object, map_to_object) { - // NOTE: This test takes a long time to compile (about a .5s per entry in this - // tuple). This is just enough coverage to test all three of our hash table types, - // and a few important key/value types. - auto maps = std::make_tuple(py::dense_hash_map>( - "missing_value"s), - py::sparse_hash_map>(), - std::unordered_map()); - - // Call test_map_to_object_impl on each entry in ``maps``. - std::apply([&](auto... map) { (test_map_to_object_impl(map), ...); }, maps); -} - -template -void test_sequence_to_object_impl(V v) { - auto check_python_list = [&](py::owned_ref ob) { - ASSERT_TRUE(ob) << "to_object should not return null"; - EXPECT_EQ(PyList_Check(ob.get()), 1) << "ob should be a list"; - - Py_ssize_t len = PyList_GET_SIZE(ob.get()); - EXPECT_EQ(std::size_t(len), v.size()) - << "Python list length should match C++ vector length."; - - // Values in Python list should be the result of calling to_object on each entry - // in the C++ vector. - for (auto [i, cxx_value] : py::enumerate(v)) { - auto py_value = py::to_object(cxx_value); - - py::borrowed_ref result = PyList_GetItem(ob.get(), i); - ASSERT_TRUE(result) << "Should have had a value at index " << i; - - bool values_equal = - PyObject_RichCompareBool(py_value.get(), result.get(), Py_EQ); - EXPECT_EQ(values_equal, 1) - << "List values at index " << i << " were not equal"; - } - }; - - // Check to_object with value, const value, and rvalue reference. - - py::owned_ref result = py::to_object(v); - check_python_list(result); - - const V& const_ref = v; - py::owned_ref constref_result = py::to_object(const_ref); - check_python_list(constref_result); - - V copy = v; // Make a copy before moving b/c the lambda above uses ``v``. - py::owned_ref rvalueref_result = py::to_object(std::move(copy)); - check_python_list(rvalueref_result); + auto map = std::unordered_map(); + py_test::test_map_to_object_impl(map); } TEST_F(to_object, vector_to_object) { auto to_vec = [](const auto& arr) { return std::vector(arr.begin(), arr.end()); }; - auto vectors = std::make_tuple(to_vec(examples()), - to_vec(examples()), - to_vec(examples>())); + auto vectors = std::make_tuple(to_vec(py_test::examples()), + to_vec(py_test::examples()), + to_vec(py_test::examples>())); // Call test_sequence_to_object_impl on each entry in `vectors`. - std::apply([&](auto... vec) { (test_sequence_to_object_impl(vec), ...); }, vectors); + std::apply([&](auto... vec) { (py_test::test_sequence_to_object_impl(vec), ...); }, vectors); } TEST_F(to_object, array_to_object) { - auto arrays = std::make_tuple(examples(), - examples(), - examples>()); + auto arrays = std::make_tuple(py_test::examples(), + py_test::examples(), + py_test::examples>()); // Call test_sequence_to_object_impl on each entry in `arrays`. - std::apply([&](auto... arr) { (test_sequence_to_object_impl(arr), ...); }, arrays); + std::apply([&](auto... arr) { (py_test::test_sequence_to_object_impl(arr), ...); }, arrays); } template diff --git a/tests/test_utils.h b/tests/test_utils.h index f6bb33c7..0d8252bf 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -8,6 +8,7 @@ #include "libpy/call_function.h" #include "libpy/detail/python.h" #include "libpy/exception.h" +#include "libpy/itertools.h" #include "libpy/owned_ref.h" #include "libpy/util.h" @@ -187,3 +188,174 @@ inline py::owned_ref<> run_python( */ #define EVAL_PYTHON(python_source, ...) \ ::detail::run_python(python_source, __FILE__, __LINE__, true, ##__VA_ARGS__) + +namespace py_test { + +template +std::array examples(); + +template<> +inline std::array examples() { + return {-200, 0, 1000}; +} + +template<> +inline std::array examples() { + return {"foo", "", "arglebargle"}; +} + +template<> +inline std::array, 3> examples() { + std::array foo{'f', 'o', 'o'}; + std::array bar{'b', 'a', 'r'}; + std::array baz{'b', 'a', 'z'}; + return {foo, bar, baz}; +} + +template<> +inline std::array examples() { + return {true, false, true}; +} + +template<> +inline std::array examples() { + return {-1.0, -0.0, 100.0}; +} + +template<> +inline std::array, 3> examples() { + Py_INCREF(Py_True); + Py_INCREF(Py_False); + Py_INCREF(Py_None); + return {py::owned_ref<>(Py_True), + py::owned_ref<>(Py_False), + py::owned_ref<>(Py_None)}; +} + +template +void test_map_to_object_impl(M m) { + + // Fill the map with some example values. + auto it = py::zip(examples(), + examples()); + for (auto [key, value] : it) { + m[key] = value; + } + + auto check_python_map = [&](py::owned_ref ob) { + ASSERT_TRUE(ob) << "to_object should not return null"; + EXPECT_TRUE(PyDict_Check(ob.get())); + + // Python map should be the same length as C++ map. + Py_ssize_t len = PyDict_Size(ob.get()); + EXPECT_EQ(std::size_t(len), m.size()) + << "Python dict length should match C++ map length."; + + // Key/Value pairs in the python map should match the result of calling + // to_object on each key/value pair in the C++ map. + for (auto& [cxx_key, cxx_value] : m) { + auto py_key = py::to_object(cxx_key); + auto py_value = py::to_object(cxx_value); + + py::borrowed_ref result = PyDict_GetItem(ob.get(), py_key.get()); + ASSERT_TRUE(result) << "Key should have been in the map"; + + bool values_equal = + PyObject_RichCompareBool(py_value.get(), result.get(), Py_EQ); + EXPECT_EQ(values_equal, 1) << "Dict values were not equal"; + } + }; + + // Check to_object with value, const value, and rvalue reference. + + py::owned_ref result = py::to_object(m); + check_python_map(result); + + const M& const_ref = m; + py::owned_ref constref_result = py::to_object(const_ref); + check_python_map(constref_result); + + M copy = m; // Make a copy before moving b/c the lambda above uses ``m``. + py::owned_ref rvalueref_result = py::to_object(std::move(copy)); + check_python_map(rvalueref_result); +} + +template +void test_sequence_to_object_impl(V v) { + auto check_python_list = [&](py::owned_ref ob) { + ASSERT_TRUE(ob) << "to_object should not return null"; + EXPECT_EQ(PyList_Check(ob.get()), 1) << "ob should be a list"; + + Py_ssize_t len = PyList_GET_SIZE(ob.get()); + EXPECT_EQ(std::size_t(len), v.size()) + << "Python list length should match C++ vector length."; + + // Values in Python list should be the result of calling to_object on each entry + // in the C++ vector. + for (auto [i, cxx_value] : py::enumerate(v)) { + auto py_value = py::to_object(cxx_value); + + py::borrowed_ref result = PyList_GetItem(ob.get(), i); + ASSERT_TRUE(result) << "Should have had a value at index " << i; + + bool values_equal = + PyObject_RichCompareBool(py_value.get(), result.get(), Py_EQ); + EXPECT_EQ(values_equal, 1) + << "List values at index " << i << " were not equal"; + } + }; + + // Check to_object with value, const value, and rvalue reference. + + py::owned_ref result = py::to_object(v); + check_python_list(result); + + const V& const_ref = v; + py::owned_ref constref_result = py::to_object(const_ref); + check_python_list(constref_result); + + V copy = v; // Make a copy before moving b/c the lambda above uses ``v``. + py::owned_ref rvalueref_result = py::to_object(std::move(copy)); + check_python_list(rvalueref_result); +} + +template +void test_set_to_object_impl(V v) { + auto check_python_set = [&](py::owned_ref ob) { + ASSERT_TRUE(ob) << "to_object should not return null"; + EXPECT_EQ(PySet_Check(ob.get()), 1) << "ob should be a set"; + + Py_ssize_t len = PySet_GET_SIZE(ob.get()); + EXPECT_EQ(std::size_t(len), v.size()) + << "Python set length should match C++ set length."; + + // Values in Python list should be the result of calling to_object on each entry + // in the C++ vector. + for (auto cxx_value : v) { + auto py_value = py::to_object(cxx_value); + + auto result = PySet_Contains(ob.get(), py_value.get()); + ASSERT_TRUE(result) << "Should contain value " << cxx_value; + + // bool values_equal = + // PyObject_RichCompareBool(py_value.get(), result.get(), Py_EQ); + // EXPECT_EQ(values_equal, 1) + // << "List values at index " << i << " were not equal"; + } + }; + + // Check to_object with value, const value, and rvalue reference. + + py::owned_ref result = py::to_object(v); + check_python_set(result); + + const V& const_ref = v; + py::owned_ref constref_result = py::to_object(const_ref); + check_python_set(constref_result); + + V copy = v; // Make a copy before moving b/c the lambda above uses ``v``. + py::owned_ref rvalueref_result = py::to_object(std::move(copy)); + check_python_set(rvalueref_result); +} + +} // namespace py_test