diff --git a/CMakeLists.txt b/CMakeLists.txt index 54a3280a02..41d91de396 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ option (BUILD_TEST_APPS "Build test applications" OFF) option (DIST_INSTALLER "Generate installer for distributing vapor binaries. Will generate standard make install if off" OFF) option (USE_OMP "Use OpenMP on some calculations" OFF) option (CONDA_BUILD "Use Conda to build" OFF) +option( BUILD_GOOGLE_TEST "Build unit tests using GoogleTest" OFF ) if (UNIX AND NOT APPLE) include (CMakeDependentOption) cmake_dependent_option (DIST_APPIMAGE "Generate an AppImage for VAPOR's installation across multiple Linux platforms" OFF "DIST_INSTALLER" ON) @@ -116,6 +117,32 @@ if( USE_OMP ) endif() endif() +# +# Build Google Test +# +if( BUILD_GOOGLE_TEST ) + # Control internal options of GoogleTest + # + set( INSTALL_GTEST OFF CACHE INTERNAL "Not install GoogleTest") + set( BUILD_GMOCK ON CACHE INTERNAL "Build gmock") + + # Let's use the new mechanism to incorporate GoogleTest + # + include(FetchContent) + FetchContent_Declare( googletest + URL https://github.com/google/googletest/archive/refs/heads/main.zip + DOWNLOAD_EXTRACT_TIMESTAMP NEW ) + + # Prevent overriding the parent project's compiler/linker settings on Windows + # + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + + enable_testing() # calling this function before adding subdirectory to enable + # invoking ctest from the top-level build directory. + add_subdirectory( googletest_scripts ) +endif() + set (GENERATE_FULL_INSTALLER ON) if (BUILD_GUI) set (BUILD_VDC ON) diff --git a/googletest_scripts/CMakeLists.txt b/googletest_scripts/CMakeLists.txt new file mode 100644 index 0000000000..8ba1bfe0bd --- /dev/null +++ b/googletest_scripts/CMakeLists.txt @@ -0,0 +1,10 @@ +# Add an executable that tests a specific part of VAPOR. +add_executable( ptr_cache_test ptr_cache_test.cpp ) +target_include_directories( ptr_cache_test PRIVATE ${PROJECT_SOURCE_DIR}/include/vapor/ ) +target_link_libraries( ptr_cache_test PUBLIC GTest::gtest_main ) + + +include(GoogleTest) + +# Enable this executable to be tested using `ctest .` +gtest_discover_tests( ptr_cache_test ) diff --git a/googletest_scripts/ptr_cache_test.cpp b/googletest_scripts/ptr_cache_test.cpp new file mode 100644 index 0000000000..44485635ca --- /dev/null +++ b/googletest_scripts/ptr_cache_test.cpp @@ -0,0 +1,112 @@ +#include "gtest/gtest.h" + +#include "ptr_cache.hpp" // Only include the module that's tested + +namespace { + +struct MyObj { + int i = 0, j = 1; +}; + +// Besides correct insertion/query/eviction behaviors the `ptr_cache` has to be +// deleting objects correctly upon eviction. However, memory errors are not +// something GoogleTest can detect. As a result, one needs to run this +// executable in valgrind to make sure that there are no memory errors: +// valgrind ./googletest_scripts/ptr_cache_test + +// Test the cache when queries don't count as a use. +// It needs to insert and evict correctly. +TEST(ptr_cache_query_false, insert_eviction) +{ + VAPoR::ptr_cache cache; + const auto* p = cache.query(1); + EXPECT_EQ(p, nullptr); // nothing in the cache yet, should get nullptr + + cache.insert(1, new MyObj{1, 100}); + cache.insert(2, new MyObj{2, 200}); + cache.insert(3, new MyObj{3, 300}); + + // Make sure that we have all 3 objects + p = cache.query(1); + EXPECT_NE(p, nullptr); // not nullptr + EXPECT_EQ(p->j, 100); + p = cache.query(2); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 200); + p = cache.query(3); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 300); + p = cache.query(4); + EXPECT_EQ(p, nullptr); + + // Insert a new object; make sure that "1" is evicted. + cache.insert(4, new MyObj{4, 400}); + p = cache.query(4); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 400); + p = cache.query(1); + EXPECT_EQ(p, nullptr); + + // Do a query on "2", but queries don't count as a use. + p = cache.query(2); + + // Insert another object; make sure that "2" is evicted. + cache.insert(5, new MyObj{5, 500}); + p = cache.query(5); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 500); + p = cache.query(2); + EXPECT_EQ(p, nullptr); +} + + +// Test the cache when queries count as a use. +TEST(ptr_cache_query_true, insert_eviction) +{ + VAPoR::ptr_cache cache; + + cache.insert(1, new MyObj{1, 100}); + cache.insert(2, new MyObj{2, 200}); + cache.insert(3, new MyObj{3, 300}); + + // Make sure that we have all 3 objects + const auto* p = cache.query(1); + EXPECT_NE(p, nullptr); // not nullptr + EXPECT_EQ(p->j, 100); + p = cache.query(2); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 200); + p = cache.query(3); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 300); + p = cache.query(4); + EXPECT_EQ(p, nullptr); + + // Insert a new object; make sure that "1" is evicted. + cache.insert(4, new MyObj{4, 400}); + p = cache.query(4); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 400); + p = cache.query(1); + EXPECT_EQ(p, nullptr); + + // Do a query on "2", then insert, it should be "3" that's evicted. + p = cache.query(2); + cache.insert(5, new MyObj{5, 500}); + p = cache.query(5); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 500); + p = cache.query(3); + EXPECT_EQ(p, nullptr); + + // Do another query on "2", then insert, it should be "4" that's evicted. + p = cache.query(2); + cache.insert(6, new MyObj{6, 600}); + p = cache.query(6); + EXPECT_NE(p, nullptr); + EXPECT_EQ(p->j, 600); + p = cache.query(4); + EXPECT_EQ(p, nullptr); +} + +} // End of namespace