From e44707d5e17faf8a37eb6bab6ec8f03528781eba Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:56:52 -0400 Subject: [PATCH] add unittest support for the fortran interface to LAMMPS --- unittest/CMakeLists.txt | 1 + unittest/fortran/CMakeLists.txt | 30 +++++++ unittest/fortran/fortran-commands.f90 | 111 ++++++++++++++++++++++++++ unittest/fortran/fortran-create.f90 | 86 ++++++++++++++++++++ unittest/fortran/mpi_stubs.f90 | 20 +++++ unittest/fortran/wrap-commands.cpp | 65 +++++++++++++++ unittest/fortran/wrap-create.cpp | 97 ++++++++++++++++++++++ 7 files changed, 410 insertions(+) create mode 100644 unittest/fortran/CMakeLists.txt create mode 100644 unittest/fortran/fortran-commands.f90 create mode 100644 unittest/fortran/fortran-create.f90 create mode 100644 unittest/fortran/mpi_stubs.f90 create mode 100644 unittest/fortran/wrap-commands.cpp create mode 100644 unittest/fortran/wrap-create.cpp diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index bfa4c7d002..dd375112b7 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(formats) add_subdirectory(commands) add_subdirectory(c-library) add_subdirectory(cplusplus) +add_subdirectory(fortran) add_subdirectory(python) add_subdirectory(force-styles) diff --git a/unittest/fortran/CMakeLists.txt b/unittest/fortran/CMakeLists.txt new file mode 100644 index 0000000000..75382479d4 --- /dev/null +++ b/unittest/fortran/CMakeLists.txt @@ -0,0 +1,30 @@ +include(CheckGeneratorSupport) +if(NOT CMAKE_GENERATOR_SUPPORT_FORTRAN) + message(STATUS "Skipping Tests for the LAMMPS Fortran Module: no Fortran support in build tool") + return() +endif() + +include(CheckLanguage) +check_language(Fortran) +if(CMAKE_Fortran_COMPILER) + enable_language(Fortran) + get_filename_component(LAMMPS_FORTRAN_MODULE ${LAMMPS_SOURCE_DIR}/../fortran/lammps.f90 ABSOLUTE) + if(BUILD_MPI) + find_package(MPI REQUIRED) + else() + add_library(fmpi_stubs STATIC mpi_stubs.f90) + add_library(MPI::MPI_Fortran ALIAS fmpi_stubs) + endif() + + add_library(flammps STATIC ${LAMMPS_FORTRAN_MODULE}) + + add_executable(fortran-create wrap-create.cpp fortran-create.f90) + target_link_libraries(fortran-create PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTest GTest::GTestMain) + add_test(FortranOpen fortran-create) + + add_executable(fortran-commands wrap-commands.cpp fortran-commands.f90) + target_link_libraries(fortran-commands PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTest GTest::GTestMain) + add_test(FortranCommands fortran-commands) +else() + message(STATUS "Skipping Tests for the LAMMPS Fortran Module: no Fortran compiler") +endif() diff --git a/unittest/fortran/fortran-commands.f90 b/unittest/fortran/fortran-commands.f90 new file mode 100644 index 0000000000..748bc5774a --- /dev/null +++ b/unittest/fortran/fortran-commands.f90 @@ -0,0 +1,111 @@ +MODULE keepcmds + USE liblammps + TYPE(LAMMPS) :: lmp + CHARACTER(len=*), DIMENSION(*), PARAMETER :: demo_input = & + [ CHARACTER(len=40) :: & + 'region box block 0 $x 0 2 0 2', & + 'create_box 1 box', & + 'create_atoms 1 single 1.0 1.0 ${zpos}' ] + CHARACTER(len=*), DIMENSION(*), PARAMETER :: cont_input = & + [ CHARACTER(len=40) :: & + 'create_atoms 1 single &', & + ' 0.2 0.1 0.1' ] +END MODULE keepcmds + +FUNCTION f_lammps_with_args() BIND(C, name="f_lammps_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_with_args + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', & + '-echo','screen','-nocite','-var','zpos','1.5','-var','x','2'] + + lmp = lammps(args) + f_lammps_with_args = lmp%handle +END FUNCTION f_lammps_with_args + +SUBROUTINE f_lammps_close() BIND(C, name="f_lammps_close") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + + CALL lmp%close() + lmp%handle = c_null_ptr +END SUBROUTINE f_lammps_close + +FUNCTION f_lammps_get_natoms() BIND(C, name="f_lammps_get_natoms") + USE ISO_C_BINDING, ONLY: c_null_ptr, c_double + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + REAL(c_double) :: f_lammps_get_natoms + + f_lammps_get_natoms = lmp%get_natoms() +END FUNCTION f_lammps_get_natoms + +SUBROUTINE f_lammps_file() BIND(C, name="f_lammps_file") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + INTEGER :: i + CHARACTER(len=*), PARAMETER :: demo_file = 'in.test', cont_file = 'in.cont' + + OPEN(10, file=demo_file, status='replace') + WRITE(10, fmt='(A)') (demo_input(i),i=1,SIZE(demo_input)) + CLOSE(10) + OPEN(11, file=cont_file, status='replace') + WRITE(11, fmt='(A)') (cont_input(i),i=1,SIZE(cont_input)) + CLOSE(11) + CALL lmp%file(demo_file) + CALL lmp%file(cont_file) + OPEN(12, file=demo_file, status='old') + CLOSE(12, status='delete') + OPEN(13, file=cont_file, status='old') + CLOSE(13, status='delete') +END SUBROUTINE f_lammps_file + +SUBROUTINE f_lammps_command() BIND(C, name="f_lammps_command") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input + IMPLICIT NONE + INTEGER :: i + + DO i=1,SIZE(demo_input) + call lmp%command(demo_input(i)) + END DO +END SUBROUTINE f_lammps_command + +SUBROUTINE f_lammps_commands_list() BIND(C, name="f_lammps_commands_list") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + + CALL lmp%commands_list(demo_input) + CALL lmp%commands_list(cont_input) +END SUBROUTINE f_lammps_commands_list + +SUBROUTINE f_lammps_commands_string() BIND(C, name="f_lammps_commands_string") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + INTEGER :: i + CHARACTER(len=512) :: cmds + + cmds = '' + DO i=1,SIZE(demo_input) + cmds = TRIM(cmds) // TRIM(demo_input(i)) // NEW_LINE('A') + END DO + DO i=1,SIZE(cont_input) + cmds = TRIM(cmds) // TRIM(cont_input(i)) // NEW_LINE('A') + END DO + + CALL lmp%commands_string(cmds) +END SUBROUTINE f_lammps_commands_string diff --git a/unittest/fortran/fortran-create.f90 b/unittest/fortran/fortran-create.f90 new file mode 100644 index 0000000000..694646e9bd --- /dev/null +++ b/unittest/fortran/fortran-create.f90 @@ -0,0 +1,86 @@ +MODULE keepcreate + USE liblammps + TYPE(LAMMPS) :: lmp + INTEGER :: mycomm +END MODULE keepcreate + +FUNCTION f_lammps_no_mpi_no_args() BIND(C, name="f_lammps_no_mpi_no_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_no_mpi_no_args + + lmp = lammps() + f_lammps_no_mpi_no_args = lmp%handle +END FUNCTION f_lammps_no_mpi_no_args + +FUNCTION f_lammps_no_mpi_with_args() BIND(C, name="f_lammps_no_mpi_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_no_mpi_with_args + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', '-nocite' ] + + lmp = lammps(args) + f_lammps_no_mpi_with_args = lmp%handle +END FUNCTION f_lammps_no_mpi_with_args + +FUNCTION f_lammps_open_no_args() BIND(C, name="f_lammps_open_no_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE MPI, ONLY: MPI_COMM_WORLD, mpi_comm_split + USE liblammps + USE keepcreate, ONLY: lmp,mycomm + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_open_no_args + INTEGER :: color, key, ierr + + color = 1 + key = 1 + CALL mpi_comm_split(MPI_COMM_WORLD, color, key, mycomm, ierr) + lmp = lammps(comm=mycomm) + f_lammps_open_no_args = lmp%handle +END FUNCTION f_lammps_open_no_args + +FUNCTION f_lammps_open_with_args() BIND(C, name="f_lammps_open_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE MPI, ONLY: MPI_COMM_WORLD, mpi_comm_split + USE liblammps + USE keepcreate, ONLY: lmp,mycomm + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_open_with_args + INTEGER :: color, key, ierr + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', '-nocite' ] + + color = 2 + key = 1 + CALL mpi_comm_split(MPI_COMM_WORLD, color, key, mycomm, ierr) + lmp = lammps(args,mycomm) + f_lammps_open_with_args = lmp%handle +END FUNCTION f_lammps_open_with_args + +SUBROUTINE f_lammps_close() BIND(C, name="f_lammps_close") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + + CALL lmp%close() + lmp%handle = c_null_ptr +END SUBROUTINE f_lammps_close + +FUNCTION f_lammps_get_comm() BIND(C, name="f_lammps_get_comm") + USE liblammps + USE keepcreate, ONLY: mycomm + IMPLICIT NONE + INTEGER :: f_lammps_get_comm + + f_lammps_get_comm = mycomm +END FUNCTION f_lammps_get_comm + + diff --git a/unittest/fortran/mpi_stubs.f90 b/unittest/fortran/mpi_stubs.f90 new file mode 100644 index 0000000000..3f87fc38f7 --- /dev/null +++ b/unittest/fortran/mpi_stubs.f90 @@ -0,0 +1,20 @@ +MODULE MPI + IMPLICIT NONE + PRIVATE + + INTEGER, PARAMETER :: MPI_COMM_WORLD=0 + INTEGER, PARAMETER :: MPI_SUCCESS=0 + + PUBLIC :: MPI_COMM_WORLD, MPI_SUCCESS, & + mpi_comm_split + +CONTAINS + + SUBROUTINE mpi_comm_split(comm,color,key,newcomm,ierr) + INTEGER, INTENT(in) :: comm,color,key + INTEGER, INTENT(out) :: newcomm,ierr + + newcomm = comm + 1 + ierr = 0 + END SUBROUTINE mpi_comm_split +END MODULE MPI diff --git a/unittest/fortran/wrap-commands.cpp b/unittest/fortran/wrap-commands.cpp new file mode 100644 index 0000000000..493e7c4590 --- /dev/null +++ b/unittest/fortran/wrap-commands.cpp @@ -0,0 +1,65 @@ +// unit tests for issuing command to a LAMMPS instance through the Fortran wrapper + +#include "lammps.h" +#include +#include // for stdin, stdout +#include + +#include "gtest/gtest.h" + +// prototypes for fortran reverse wrapper functions +extern "C" { + void *f_lammps_with_args(); + void f_lammps_close(); + void f_lammps_file(); + void f_lammps_command(); + void f_lammps_commands_list(); + void f_lammps_commands_string(); + double f_lammps_get_natoms(); +} + +class LAMMPS_commands : public ::testing::Test +{ +protected: + LAMMPS_NS::LAMMPS *lmp; + LAMMPS_commands() {}; + ~LAMMPS_commands() override {}; + + void SetUp() override { + ::testing::internal::CaptureStdout(); + lmp = (LAMMPS_NS::LAMMPS *)f_lammps_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,8).c_str(), "LAMMPS ("); + } + void TearDown() override { + ::testing::internal::CaptureStdout(); + f_lammps_close(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); + lmp = nullptr; + } +}; + +TEST_F(LAMMPS_commands, from_file) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_file(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; + +TEST_F(LAMMPS_commands, from_line) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_command(); + EXPECT_EQ(f_lammps_get_natoms(),1); +}; + +TEST_F(LAMMPS_commands, from_list) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_commands_list(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; + +TEST_F(LAMMPS_commands, from_string) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_commands_string(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; diff --git a/unittest/fortran/wrap-create.cpp b/unittest/fortran/wrap-create.cpp new file mode 100644 index 0000000000..37443aba64 --- /dev/null +++ b/unittest/fortran/wrap-create.cpp @@ -0,0 +1,97 @@ +// unit tests for the LAMMPS base class + +#include "lammps.h" +#include +#include // for stdin, stdout +#include + +#include "gtest/gtest.h" + +// prototypes for fortran reverse wrapper functions +extern "C" { + void *f_lammps_open_no_args(); + void *f_lammps_open_with_args(); + void *f_lammps_no_mpi_no_args(); + void *f_lammps_no_mpi_with_args(); + void f_lammps_close(); + int f_lammps_get_comm(); +} + +TEST(open_no_mpi, no_args) { + ::testing::internal::CaptureStdout(); + int mpi_init=0; + MPI_Initialized(&mpi_init); + EXPECT_EQ(mpi_init,0); + void *handle = f_lammps_no_mpi_no_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + MPI_Initialized(&mpi_init); + EXPECT_NE(mpi_init,0); + EXPECT_EQ(lmp->world, MPI_COMM_WORLD); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_NE(lmp->citeme, nullptr); + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(open_no_mpi, with_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_no_mpi_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_EQ(lmp->logfile, nullptr); + EXPECT_EQ(lmp->citeme, nullptr); + EXPECT_EQ(lmp->world, MPI_COMM_WORLD); + + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(fortran_open, no_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_open_no_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + + int f_comm = f_lammps_get_comm(); + MPI_Comm mycomm = MPI_Comm_f2c(f_comm); + EXPECT_EQ(lmp->world, mycomm); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_NE(lmp->citeme, nullptr); + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(fortran_open, with_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_open_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + + int f_comm = f_lammps_get_comm(); + MPI_Comm mycomm = MPI_Comm_f2c(f_comm); + EXPECT_EQ(lmp->world, mycomm); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_EQ(lmp->logfile, nullptr); + EXPECT_EQ(lmp->citeme, nullptr); + + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +}