diff --git a/.github/workflows/build_and_gtest.yml b/.github/workflows/build_and_gtest.yml new file mode 100644 index 00000000..0d3bcdd6 --- /dev/null +++ b/.github/workflows/build_and_gtest.yml @@ -0,0 +1,42 @@ +name: Build and Test + +on: + push: + branches: + - main + - gtest + pull_request: + branches: + - main + - gtest + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Update Libraries + run: sudo apt-get update + + - name: Install dependencies + run: | + sudo apt-get install -y cmake g++ libgtest-dev libarmadillo-dev libopenblas-dev libsuperlu-dev libeigen3-dev + # Build and install Google Test (if not pre-installed) + cd /usr/src/googletest && sudo cmake . && sudo make && sudo make install + + - name: Create build directory + run: mkdir build + + - name: Run CMake + run: cmake -S . -B build + + - name: Build library + run: cmake --build build + + - name: Run tests + run: | + cd build + make run_tests diff --git a/CMakeLists.txt b/CMakeLists.txt index b032f7fd..cf5d5370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,9 @@ message(STATUS "Detected CXX Compiler ID: ${CMAKE_CXX_COMPILER_ID}") # Compiler-specific CXX_FLAGS and linker flags if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(CMAKE_CXX_FLAGS "-O3 -Xclang -fopenmp -DARMA_DONT_USE_WRAPPER -DARMA_USE_SUPERLU") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/Cellar/libomp/19.1.6/lib -lomp") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/opt/libomp/lib -lomp") message(STATUS "Using AppleClang-specific flags.") - include_directories("/usr/local/Cellar/libomp/19.1.6/include") + include_directories("/usr/local/opt/libomp/include") else() set(CMAKE_CXX_FLAGS "-O3 -fopenmp -DARMA_DONT_USE_WRAPPER -DARMA_USE_SUPERLU") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") @@ -74,8 +74,8 @@ else() endif() find_package(Eigen3 3.3.7 REQUIRED) -find_library(OpenBLAS_LIBRARIES NAMES openblas blas PATHS "/usr/lib/x86_64-linux-gnu" REQUIRED) -find_library(LAPACK_LIBRARY lapack REQUIRED PATHS "/usr/lib" "/usr/lib/x86_64-linux-gnu" "/usr/local/lib" "/usr/local/Cellar/") +find_library(OpenBLAS_LIBRARIES NAMES openblas blas PATHS "/usr/lib/x86_64-linux-gnu" "/usr/local/opt/" REQUIRED) +find_library(LAPACK_LIBRARY lapack REQUIRED PATHS "/usr/lib" "/usr/lib/x86_64-linux-gnu" "/usr/local/lib" "/usr/local/opt/") # Required libraries and link settings set(LINK_LIBS ${ARMADILLO_LIBRARIES} diff --git a/src/cpp/utils.cpp b/src/cpp/utils.cpp index b6879b0a..1edfba5f 100644 --- a/src/cpp/utils.cpp +++ b/src/cpp/utils.cpp @@ -15,6 +15,7 @@ * */ + /* * @file utils.cpp * @brief Helpers for sparse operations and MATLAB analogs diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index 4046ee02..1b988756 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -1,15 +1,28 @@ -# tests_C++ Configuration +# Include directories for the source code include_directories("${CMAKE_SOURCE_DIR}/src/cpp") +# FetchContent module to download and configure Google Test +include(FetchContent) + +# Download and install Google Test +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/heads/main.zip +) + +# Set up the fetched content +FetchContent_MakeAvailable(googletest) + # Find all test*.cpp files file(GLOB TEST_SOURCES test*.cpp) set(TEST_EXECUTABLES "") +# Create test executables and link with Google Test foreach(TEST_SOURCE ${TEST_SOURCES}) get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE) add_executable(${TEST_NAME} ${TEST_SOURCE}) - target_link_libraries(${TEST_NAME} PUBLIC mole_C++ ${LINK_LIBS}) + target_link_libraries(${TEST_NAME} PUBLIC mole_C++ gtest gtest_main ${LINK_LIBS}) list(APPEND TEST_EXECUTABLES ${TEST_NAME}) endforeach() diff --git a/tests/cpp/test1.cpp b/tests/cpp/test1.cpp index 052579ae..66b5432c 100644 --- a/tests/cpp/test1.cpp +++ b/tests/cpp/test1.cpp @@ -1,32 +1,26 @@ -/** - * Nullity test of Divergence operator - */ - #include "mole.h" -#include +#include void run_nullity_test(int k, Real tol) { - int m = 2 * k + 1; - Real dx = 1; + int m = 2 * k + 1; + Real dx = 1; - Divergence D(k, m, dx); - vec field(m + 1, fill::ones); + Divergence D(k, m, dx); + vec field(m + 1, fill::ones); - vec sol = D * field; + vec sol = D * field; - if (norm(sol) > tol) { - cout << "\033[1;31mTest FAILED!\033[0m\n"; - exit(1); - } + EXPECT_NEAR(norm(sol), 0, tol); } -int main() { - Real tol = 1e-10; - - for (int k : {2, 4, 6}) - run_nullity_test(k, tol); - - cout << "\033[1;32mTest PASSED!\033[0m\n"; +TEST(DivergenceTests, Nullity) { + Real tol = 1e-10; + for (int k : {2, 4, 6}) { + run_nullity_test(k, tol); + } +} - return 0; +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/tests/cpp/test2.cpp b/tests/cpp/test2.cpp index 92510145..e2c38cfa 100644 --- a/tests/cpp/test2.cpp +++ b/tests/cpp/test2.cpp @@ -1,33 +1,21 @@ -/** - * Nullity test of Gradient operator - */ - #include "mole.h" -#include +#include void run_nullity_test(int k, Real tol) { - int m = 2 * k + 1; - Real dx = 1; + int m = 2 * k + 1; + Real dx = 1; - Gradient G(k, m, dx); - vec field(m + 2, fill::ones); + Gradient G(k, m, dx); + vec field(m + 2, fill::ones); - vec sol = G * field; + vec sol = G * field; - if (norm(sol) > tol) { - cout << "\033[1;31mTest FAILED!\033[0m\n"; - cout << norm(sol); - exit(1); - } + ASSERT_LT(norm(sol), tol) << "Gradient Nullity Test failed for k = " << k; } -int main() { - Real tol = 1e-10; - - for (int k : {2, 4, 6, 8}) - run_nullity_test(k, tol); - - cout << "\033[1;32mTest PASSED!\033[0m\n"; - - return 0; +TEST(GradientTests, Nullity) { + Real tol = 1e-10; + for (int k : {2, 4, 6, 8}) { + run_nullity_test(k, tol); + } } diff --git a/tests/cpp/test3.cpp b/tests/cpp/test3.cpp index c8855bcf..ad548b2e 100644 --- a/tests/cpp/test3.cpp +++ b/tests/cpp/test3.cpp @@ -1,32 +1,21 @@ -/** - * Nullity test of Laplacian operator - */ - #include "mole.h" -#include +#include void run_nullity_test(int k, Real tol) { - int m = 2 * k + 1; - Real dx = 1; + int m = 2 * k + 1; + Real dx = 1; - Laplacian L(k, m, dx); - vec field(m + 2, fill::ones); + Laplacian L(k, m, dx); + vec field(m + 2, fill::ones); - vec sol = L * field; + vec sol = L * field; - if (norm(sol) > tol) { - cout << "\033[1;31mTest FAILED!\033[0m\n"; - exit(1); - } + ASSERT_LT(norm(sol), tol) << "Laplacian Nullity Test failed for k = " << k; } -int main() { - Real tol = 1e-10; - - for (int k : {2, 4, 6}) - run_nullity_test(k, tol); - - cout << "\033[1;32mTest PASSED!\033[0m\n"; - - return 0; +TEST(LaplacianTests, Nullity) { + Real tol = 1e-10; + for (int k : {2, 4, 6}) { + run_nullity_test(k, tol); + } } diff --git a/tests/cpp/test4.cpp b/tests/cpp/test4.cpp index 160b289f..029b3182 100644 --- a/tests/cpp/test4.cpp +++ b/tests/cpp/test4.cpp @@ -1,47 +1,35 @@ -/** - * Energy test - */ - #include "mole.h" +#include #include -#include - -int main() { - int k = 4; - Real a = -5; - Real b = 5; - int m = 500; - vec grid = linspace(a, b, m); - Real dx = grid(1) - grid(0); - Real tol = 1e-10; +TEST(EnergyTests, EigenvalueTest) { + int k = 4; + Real a = -5; + Real b = 5; + int m = 500; + vec grid = linspace(a, b, m); + Real dx = grid(1) - grid(0); + Real tol = 1e-10; - Laplacian L(k, m - 2, dx); + Laplacian L(k, m - 2, dx); - std::transform(grid.begin(), grid.end(), grid.begin(), - [](Real x) { return x * x; }); + std::transform(grid.begin(), grid.end(), grid.begin(), + [](Real x) { return x * x; }); - sp_mat V(m, m); - V.diag(0) = grid; + sp_mat V(m, m); + V.diag(0) = grid; - sp_mat H = -0.5 * (sp_mat)L + V; + sp_mat H = -0.5 * (sp_mat)L + V; - cx_vec eigval; - eig_gen(eigval, (mat)H); + cx_vec eigval; + eig_gen(eigval, (mat)H); - eigval = sort(eigval); + eigval = sort(eigval); - vec expected{1, 3, 5, 7, 9}; + vec expected{1, 3, 5, 7, 9}; - bool failed = false; - for (int i = 0; i < expected.size(); ++i) - if (std::norm(real(eigval(i) / eigval(0)) - expected(i)) > tol) { - cout << "\033[1;31mTest FAILED!\033[0m\n"; - failed = true; + for (int i = 0; i < expected.size(); ++i) { + ASSERT_LT(std::norm(real(eigval(i) / eigval(0)) - expected(i)), tol) + << "Energy Test failed for eigenvalue index " << i; } - - if (!failed) - cout << "\033[1;32mTest PASSED!\033[0m\n"; - - return 0; } diff --git a/tests/cpp/test5.cpp b/tests/cpp/test5.cpp index e2e5983e..173f4668 100644 --- a/tests/cpp/test5.cpp +++ b/tests/cpp/test5.cpp @@ -1,71 +1,52 @@ -/** - * Poisson accuracy test - */ - #include "mole.h" -#include +#include void run_test(int k, vec grid_sizes) { - Real west = 0; // Domain's limits - Real east = 1; - - vec errors(grid_sizes.size()); + Real west = 0; // Domain's limits + Real east = 1; - for (int i = 0; i < grid_sizes.size(); ++i) { - int m = grid_sizes(i); // Number of cells - Real dx = (east - west) / m; // Step length + vec errors(grid_sizes.size()); - Laplacian L(k, m, dx); + for (int i = 0; i < grid_sizes.size(); ++i) { + int m = grid_sizes(i); // Number of cells + Real dx = (east - west) / m; // Step length - // Impose Robin BC on laplacian operator - RobinBC BC(k, m, dx, 1, 1); - L = L + BC; + Laplacian L(k, m, dx); + RobinBC BC(k, m, dx, 1, 1); + L = L + BC; - // 1D Staggered grid - vec grid(m + 2); - grid(0) = west; - grid(1) = west + dx / 2.0; - for (int j = 2; j <= m; j++) { - grid(j) = grid(j - 1) + dx; - } - grid(m + 1) = east; + vec grid(m + 2); + grid(0) = west; + grid(1) = west + dx / 2.0; + for (int j = 2; j <= m; j++) { + grid(j) = grid(j - 1) + dx; + } + grid(m + 1) = east; - // RHS - vec U = exp(grid); - U(0) = 0; // West BC - U(m + 1) = 2 * exp(1); // East BC + vec U = exp(grid); + U(0) = 0; // West BC + U(m + 1) = 2 * exp(1); // East BC -// Solve the system of linear equations #ifdef EIGEN - // Use Eigen only if SuperLU (faster) is not available - vec computed_solution = Utils::spsolve_eigen(L, U); + vec computed_solution = Utils::spsolve_eigen(L, U); #else - vec computed_solution = spsolve(L, U); // Will use SuperLU + vec computed_solution = spsolve(L, U); // Will use SuperLU #endif - // Compute error - vec analytical_solution = exp(grid); - errors(i) = max(abs(computed_solution - analytical_solution)); - } - - // Compute order of accuracy - vec order(errors.size() - 1); - for (int i = 0; i < errors.size() - 1; ++i) { - order(i) = log2(errors(i) / errors(i + 1)); + vec analytical_solution = exp(grid); + errors(i) = max(abs(computed_solution - analytical_solution)); + } - if (order(i) - k < -0.5) { - cout << "\033[1;31mTest FAILED for k = " << k << "!\033[0m\n"; - exit(1); + vec order(errors.size() - 1); + for (int i = 0; i < errors.size() - 1; ++i) { + order(i) = log2(errors(i) / errors(i + 1)); + ASSERT_GE(order(i), k - 0.5) << "Poisson Test failed for k = " << k; } - } } -int main() { - vec grid_sizes = {20, 40}; // Different grid sizes to test - for (int k : {2, 4, 6}) - run_test(k, grid_sizes); - - cout << "\033[1;32mTest PASSED!\033[0m\n"; - - return 0; +TEST(PoissonTests, Accuracy) { + vec grid_sizes = {20, 40}; + for (int k : {2, 4, 6}) { + run_test(k, grid_sizes); + } }