Merge pull request #2725 from akohlmey/more-unit-tests

Add more unit tests
This commit is contained in:
Axel Kohlmeyer 2021-04-26 23:30:44 -04:00 committed by GitHub
commit 3455172f7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 713 additions and 34 deletions

View File

@ -17,7 +17,7 @@ if(GIT_FOUND AND EXISTS ${LAMMPS_DIR}/.git)
ERROR_QUIET
WORKING_DIRECTORY ${LAMMPS_DIR}
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --dirty=-modified
execute_process(COMMAND ${GIT_EXECUTABLE} describe --dirty=-modified --always
OUTPUT_VARIABLE temp_git_describe
ERROR_QUIET
WORKING_DIRECTORY ${LAMMPS_DIR}

View File

@ -155,6 +155,7 @@ ComputeChunkAtom::ComputeChunkAtom(LAMMPS *lmp, int narg, char **arg) :
if ((which == ArgInfo::UNKNOWN) || (which == ArgInfo::NONE)
|| (argi.get_dim() > 1))
error->all(FLERR,"Illegal compute chunk/atom command");
iarg = 4;
}
// optional args

View File

@ -304,6 +304,12 @@ void ReadData::command(int narg, char **arg)
extra_dihedral_types || extra_improper_types))
error->all(FLERR,"Cannot use read_data extra with add flag");
// check if data file is available and readable
if (!utils::file_is_readable(arg[0]))
error->all(FLERR,fmt::format("Cannot open file {}: {}",
arg[0], utils::getsyserror()));
// first time system initialization
if (addflag == NONE) {

View File

@ -41,7 +41,7 @@ using namespace LAMMPS_NS;
* \param filetype Description of file type for error messages */
TextFileReader::TextFileReader(const std::string &filename, const std::string &filetype)
: filename(filename), filetype(filetype), ignore_comments(true)
: filetype(filetype), closefp(true), ignore_comments(true)
{
fp = fopen(filename.c_str(), "r");
@ -51,10 +51,33 @@ TextFileReader::TextFileReader(const std::string &filename, const std::string &f
}
}
/**
* \overload
*
\verbatim embed:rst
This function is useful in combination with :cpp:func:`utils::open_potential`.
.. note::
The FILE pointer is not closed in the destructor, but will be advanced
when reading from it.
\endverbatim
*
* \param fp File descriptor of the already opened file
* \param filetype Description of file type for error messages */
TextFileReader::TextFileReader(FILE *fp, const std::string &filetype)
: filetype(filetype), closefp(false), fp(fp), ignore_comments(true)
{
if (fp == nullptr) throw FileReaderException("Invalid file descriptor");
}
/** Closes the file */
TextFileReader::~TextFileReader() {
fclose(fp);
if (closefp) fclose(fp);
}
/** Read the next line and ignore it */
@ -163,5 +186,8 @@ void TextFileReader::next_dvector(double * list, int n) {
* \return ValueTokenizer object for read in text */
ValueTokenizer TextFileReader::next_values(int nparams, const std::string &separators) {
return ValueTokenizer(next_line(nparams), separators);
char *ptr = next_line(nparams);
if (ptr == nullptr)
throw EOFException(fmt::format("Missing line in {} file!", filetype));
return ValueTokenizer(line, separators);
}

View File

@ -25,8 +25,8 @@
namespace LAMMPS_NS
{
class TextFileReader {
std::string filename;
std::string filetype;
bool closefp;
static constexpr int MAXLINE = 1024;
char line[MAXLINE];
FILE *fp;
@ -35,6 +35,8 @@ namespace LAMMPS_NS
bool ignore_comments; //!< Controls whether comments are ignored
TextFileReader(const std::string &filename, const std::string &filetype);
TextFileReader(FILE *fp, const std::string &filetype);
~TextFileReader();
void skip_line();

View File

@ -68,6 +68,28 @@ Tokenizer::Tokenizer(Tokenizer && rhs) :
reset();
}
Tokenizer& Tokenizer::operator=(const Tokenizer& other)
{
Tokenizer tmp(other);
swap(tmp);
return *this;
}
Tokenizer& Tokenizer::operator=(Tokenizer&& other)
{
Tokenizer tmp(std::move(other));
swap(tmp);
return *this;
}
void Tokenizer::swap(Tokenizer& other)
{
std::swap(text, other.text);
std::swap(separators, other.separators);
std::swap(start, other.start);
std::swap(ntokens, other.ntokens);
}
/*! Re-position the tokenizer state to the first word,
* i.e. the first non-separator character */
void Tokenizer::reset() {
@ -181,6 +203,25 @@ ValueTokenizer::ValueTokenizer(const ValueTokenizer &rhs) : tokens(rhs.tokens) {
ValueTokenizer::ValueTokenizer(ValueTokenizer &&rhs) : tokens(std::move(rhs.tokens)) {
}
ValueTokenizer& ValueTokenizer::operator=(const ValueTokenizer& other)
{
ValueTokenizer tmp(other);
swap(tmp);
return *this;
}
ValueTokenizer& ValueTokenizer::operator=(ValueTokenizer&& other)
{
ValueTokenizer tmp(std::move(other));
swap(tmp);
return *this;
}
void ValueTokenizer::swap(ValueTokenizer& other)
{
std::swap(tokens, other.tokens);
}
/*! Indicate whether more tokens are available
*
* \return true if there are more tokens, false if not */

View File

@ -37,8 +37,9 @@ public:
Tokenizer(const std::string &str, const std::string &separators = TOKENIZER_DEFAULT_SEPARATORS);
Tokenizer(Tokenizer &&);
Tokenizer(const Tokenizer &);
Tokenizer& operator=(const Tokenizer&) = default;
Tokenizer& operator=(Tokenizer&&) = default;
Tokenizer& operator=(const Tokenizer&);
Tokenizer& operator=(Tokenizer&&);
void swap(Tokenizer &);
void reset();
void skip(int n=1);
@ -93,8 +94,9 @@ public:
ValueTokenizer(const std::string &str, const std::string &separators = TOKENIZER_DEFAULT_SEPARATORS);
ValueTokenizer(const ValueTokenizer &);
ValueTokenizer(ValueTokenizer &&);
ValueTokenizer& operator=(const ValueTokenizer&) = default;
ValueTokenizer& operator=(ValueTokenizer&&) = default;
ValueTokenizer& operator=(const ValueTokenizer&);
ValueTokenizer& operator=(ValueTokenizer&&);
void swap(ValueTokenizer &);
std::string next_string();
tagint next_tagint();

View File

@ -10,6 +10,22 @@ set_tests_properties(RunLammps PROPERTIES
ENVIRONMENT "TSAN_OPTIONS=ignore_noninstrumented_modules=1"
PASS_REGULAR_EXPRESSION "^LAMMPS \\([0-9]+ [A-Za-z]+ 2[0-9][0-9][0-9]\\)")
# check if the compiled executable will print the help message
add_test(NAME HelpMessage
COMMAND $<TARGET_FILE:lmp> -h
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties(HelpMessage PROPERTIES
ENVIRONMENT "TSAN_OPTIONS=ignore_noninstrumented_modules=1"
PASS_REGULAR_EXPRESSION ".*Large-scale Atomic/Molecular Massively Parallel Simulator -.*Usage example:.*")
# check if the compiled executable will error out on an invalid command line flag
add_test(NAME InvalidFlag
COMMAND $<TARGET_FILE:lmp> -xxx
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties(InvalidFlag PROPERTIES
ENVIRONMENT "TSAN_OPTIONS=ignore_noninstrumented_modules=1"
PASS_REGULAR_EXPRESSION "ERROR: Invalid command-line argument.*")
if(BUILD_MPI)
function(add_mpi_test)
set(MPI_TEST_NUM_PROCS 1)

View File

@ -14,6 +14,7 @@
#include "lammps.h"
#include "citeme.h"
#include "comm.h"
#include "force.h"
#include "info.h"
#include "input.h"
@ -25,6 +26,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "../testing/core.h"
#include "../testing/utils.h"
#include <cstdio>
#include <cstring>
@ -174,6 +176,38 @@ TEST_F(SimpleCommandsTest, Partition)
ASSERT_THAT(text, StrEq(""));
}
TEST_F(SimpleCommandsTest, Processors)
{
// default setting is "*" for all dimensions
ASSERT_EQ(lmp->comm->user_procgrid[0], 0);
ASSERT_EQ(lmp->comm->user_procgrid[1], 0);
ASSERT_EQ(lmp->comm->user_procgrid[2], 0);
BEGIN_HIDE_OUTPUT();
command("processors 1 1 1");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->comm->user_procgrid[0], 1);
ASSERT_EQ(lmp->comm->user_procgrid[1], 1);
ASSERT_EQ(lmp->comm->user_procgrid[2], 1);
BEGIN_HIDE_OUTPUT();
command("processors * 1 *");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->comm->user_procgrid[0], 0);
ASSERT_EQ(lmp->comm->user_procgrid[1], 1);
ASSERT_EQ(lmp->comm->user_procgrid[2], 0);
BEGIN_HIDE_OUTPUT();
command("processors 0 0 0");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->comm->user_procgrid[0], 0);
ASSERT_EQ(lmp->comm->user_procgrid[1], 0);
ASSERT_EQ(lmp->comm->user_procgrid[2], 0);
TEST_FAILURE(".*ERROR: Illegal processors command .*", command("processors -1 0 0"););
TEST_FAILURE(".*ERROR: Specified processors != physical processors.*", command("processors 100 100 100"););
}
TEST_F(SimpleCommandsTest, Quit)
{
BEGIN_HIDE_OUTPUT();

View File

@ -105,8 +105,8 @@ protected:
"# comments only\n five\n#END\n",
fp);
fclose(fp);
fp = fopen("test_variable.atomfile", "w");
fp = fopen("test_variable.atomfile", "w");
fputs("# test file for atomfile style variable\n\n"
"4 # four lines\n4 0.5 #with comment\n"
"2 -0.5 \n3 1.5\n1 -1.5\n\n"

View File

@ -2,6 +2,7 @@
add_executable(test_lammps_class test_lammps_class.cpp)
target_link_libraries(test_lammps_class PRIVATE lammps GTest::GMockMain GTest::GTest GTest::GMock)
add_test(LammpsClass test_lammps_class)
set_tests_properties(LammpsClass PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1")
add_executable(test_input_class test_input_class.cpp)
target_link_libraries(test_input_class PRIVATE lammps GTest::GTest GTest::GTestMain)

View File

@ -1,13 +1,17 @@
// unit tests for the LAMMPS base class
#include "comm.h"
#include "info.h"
#include "lammps.h"
#include <cstdio> // for stdin, stdout
#include <cstdio> // for stdin, stdout
#include <cstdlib> // for setenv
#include <mpi.h>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::MatchesRegex;
using ::testing::StartsWith;
namespace LAMMPS_NS {
@ -95,6 +99,7 @@ TEST_F(LAMMPS_plain, InitMembers)
EXPECT_STREQ(LAMMPS::git_branch, "(unknown)");
EXPECT_STREQ(LAMMPS::git_descriptor, "(unknown)");
}
EXPECT_EQ(lmp->comm->nthreads, 1);
}
TEST_F(LAMMPS_plain, TestStyles)
@ -229,6 +234,7 @@ TEST_F(LAMMPS_omp, InitMembers)
EXPECT_STREQ(LAMMPS::git_branch, "(unknown)");
EXPECT_STREQ(LAMMPS::git_descriptor, "(unknown)");
}
EXPECT_EQ(lmp->comm->nthreads, 2);
}
// test fixture for Kokkos tests
@ -318,10 +324,49 @@ TEST_F(LAMMPS_kokkos, InitMembers)
}
}
// check help message printing
TEST(LAMMPS_help, HelpMessage)
// check if Comm::nthreads is initialized to either 1 or 2 (from the previous tests)
TEST(LAMMPS_init, OpenMP)
{
const char *args[] = {"LAMMPS_test", "-h"};
if (!LAMMPS::is_installed_pkg("USER-OMP")) GTEST_SKIP();
FILE *fp = fopen("in.lammps_empty", "w");
fputs("\n", fp);
fclose(fp);
const char *args[] = {"LAMMPS_init", "-in", "in.lammps_empty", "-log", "none", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
::testing::internal::CaptureStdout();
LAMMPS *lmp = new LAMMPS(argc, argv, MPI_COMM_WORLD);
std::string output = ::testing::internal::GetCapturedStdout();
EXPECT_THAT(output, MatchesRegex(".*using 2 OpenMP thread.*per MPI task.*"));
if (LAMMPS_NS::Info::has_accelerator_feature("USER-OMP", "api", "openmp"))
EXPECT_EQ(lmp->comm->nthreads, 2);
else
EXPECT_EQ(lmp->comm->nthreads, 1);
::testing::internal::CaptureStdout();
delete lmp;
::testing::internal::GetCapturedStdout();
remove("in.lammps_empty");
}
// check no OMP_NUM_THREADS warning message printing. this must be the
// last OpenMP related test as threads will be locked to 1 from here on.
TEST(LAMMPS_init, NoOpenMP)
{
if (!LAMMPS_NS::Info::has_accelerator_feature("USER-OMP", "api", "openmp"))
GTEST_SKIP() << "No threading enabled";
FILE *fp = fopen("in.lammps_class_noomp", "w");
fputs("\n", fp);
fclose(fp);
unsetenv("OMP_NUM_THREADS");
const char *args[] = {"LAMMPS_init", "-in", "in.lammps_class_noomp", "-log", "none", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
@ -329,7 +374,11 @@ TEST(LAMMPS_help, HelpMessage)
LAMMPS *lmp = new LAMMPS(argc, argv, MPI_COMM_WORLD);
std::string output = ::testing::internal::GetCapturedStdout();
EXPECT_THAT(output,
StartsWith("\nLarge-scale Atomic/Molecular Massively Parallel Simulator -"));
MatchesRegex(".*OMP_NUM_THREADS environment is not set.*Defaulting to 1 thread.*"));
EXPECT_EQ(lmp->comm->nthreads, 1);
::testing::internal::CaptureStdout();
delete lmp;
::testing::internal::GetCapturedStdout();
}
} // namespace LAMMPS_NS

View File

@ -33,7 +33,7 @@ else()
target_link_libraries(style_tests PUBLIC mpi_stubs)
endif()
# propagate sanitizer options to test tools
if (NOT ENABLE_SANITIZER STREQUAL "none")
if (ENABLE_SANITIZER AND (NOT ENABLE_SANITIZER STREQUAL "none"))
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
target_compile_options(style_tests PUBLIC -fsanitize=${ENABLE_SANITIZER})
target_link_options(style_tests PUBLIC -fsanitize=${ENABLE_SANITIZER})

View File

@ -1,7 +1,7 @@
---
lammps_version: 10 Feb 2021
date_generated: Fri Feb 26 23:09:10 2021
epsilon: 5e-14
epsilon: 2e-13
prerequisites: ! |
atom sphere
pair yukawa/colloid

View File

@ -28,6 +28,11 @@ if(PKG_MANYBODY)
set_tests_properties(EIMPotentialFileReader PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}")
endif()
add_executable(test_text_file_reader test_text_file_reader.cpp)
target_link_libraries(test_text_file_reader PRIVATE lammps GTest::GMock GTest::GTest)
add_test(NAME TextFileReader COMMAND test_text_file_reader WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties(TextFileReader PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}")
add_executable(test_file_operations test_file_operations.cpp)
target_link_libraries(test_file_operations PRIVATE lammps GTest::GMock GTest::GTest)
add_test(NAME FileOperations COMMAND test_file_operations WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -75,6 +75,31 @@ TEST_F(DumpCfgTest, run0)
delete_file("dump_cfg_run0.melt.cfg");
}
TEST_F(DumpCfgTest, write_dump)
{
auto dump_file = "dump_cfg_run*.melt.cfg";
auto fields = "mass type xs ys zs id proc procp1 x y z ix iy iz vx vy vz fx fy fz";
BEGIN_HIDE_OUTPUT();
command(std::string("write_dump all cfg dump_cfg.melt.cfg ") + fields);
command(std::string("write_dump all cfg dump_cfg*.melt.cfg ") + fields);
END_HIDE_OUTPUT();
ASSERT_FILE_EXISTS("dump_cfg.melt.cfg");
auto lines = read_lines("dump_cfg.melt.cfg");
ASSERT_EQ(lines.size(), 124);
ASSERT_THAT(lines[0], Eq("Number of particles = 32"));
delete_file("dump_cfg.melt.cfg");
ASSERT_FILE_EXISTS("dump_cfg0.melt.cfg");
lines = read_lines("dump_cfg0.melt.cfg");
ASSERT_EQ(lines.size(), 124);
ASSERT_THAT(lines[0], Eq("Number of particles = 32"));
delete_file("dump_cfg0.melt.cfg");
TEST_FAILURE(".*ERROR: Unrecognized dump style 'xxx'.*", command("write_dump all xxx test.xxx"););
}
TEST_F(DumpCfgTest, unwrap_run0)
{
auto dump_file = "dump_cfg_unwrap_run*.melt.cfg";

View File

@ -12,10 +12,13 @@
------------------------------------------------------------------------- */
#include "../testing/core.h"
#include "../testing/utils.h"
#include "atom.h"
#include "domain.h"
#include "info.h"
#include "input.h"
#include "lammps.h"
#include "utils.h"
#include "update.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -44,13 +47,10 @@ protected:
LAMMPSTest::SetUp();
ASSERT_NE(lmp, nullptr);
FILE *fp = fopen("safe_file_read_test.txt", "wb");
ASSERT_NE(fp, nullptr);
fputs("one line\n", fp);
fputs("two_lines\n", fp);
fputs("\n", fp);
fputs("no newline", fp);
fclose(fp);
std::ofstream out("safe_file_read_test.txt", std::ios_base::out | std::ios_base::binary);
ASSERT_TRUE(out.good());
out << "one line\ntwo_lines\n\nno newline";
out.close();
}
void TearDown() override
@ -65,7 +65,7 @@ TEST_F(FileOperationsTest, safe_fgets)
{
char buf[MAX_BUF_SIZE];
FILE *fp = fopen("safe_file_read_test.txt", "r");
FILE *fp = fopen("safe_file_read_test.txt", "rb");
ASSERT_NE(fp, nullptr);
memset(buf, 0, MAX_BUF_SIZE);
@ -101,7 +101,7 @@ TEST_F(FileOperationsTest, safe_fread)
{
char buf[MAX_BUF_SIZE];
FILE *fp = fopen("safe_file_read_test.txt", "r");
FILE *fp = fopen("safe_file_read_test.txt", "rb");
ASSERT_NE(fp, nullptr);
memset(buf, 0, MAX_BUF_SIZE);
@ -177,6 +177,191 @@ TEST_F(FileOperationsTest, logmesg)
remove("test_logmesg.log");
}
TEST_F(FileOperationsTest, write_restart)
{
BEGIN_HIDE_OUTPUT();
command("echo none");
END_HIDE_OUTPUT();
TEST_FAILURE(".*ERROR: Write_restart command before simulation box is defined.*",
command("write_restart test.restart"););
BEGIN_HIDE_OUTPUT();
command("region box block -2 2 -2 2 -2 2");
command("create_box 1 box");
command("create_atoms 1 single 0.0 0.0 0.0");
command("mass 1 1.0");
command("reset_timestep 333");
command("comm_modify cutoff 0.2");
command("write_restart noinit.restart noinit");
command("run 0 post no");
command("write_restart test.restart");
command("write_restart step*.restart");
command("write_restart multi-%.restart");
command("write_restart multi2-%.restart fileper 2");
command("write_restart multi3-%.restart nfile 1");
if (info->has_package("MPIIO")) command("write_restart test.restart.mpiio");
END_HIDE_OUTPUT();
ASSERT_FILE_EXISTS("noinit.restart");
ASSERT_FILE_EXISTS("test.restart");
ASSERT_FILE_EXISTS("step333.restart");
ASSERT_FILE_EXISTS("multi-base.restart");
ASSERT_FILE_EXISTS("multi-0.restart");
ASSERT_FILE_EXISTS("multi2-base.restart");
ASSERT_FILE_EXISTS("multi2-0.restart");
ASSERT_FILE_EXISTS("multi3-base.restart");
ASSERT_FILE_EXISTS("multi3-0.restart");
if (info->has_package("MPIIO")) ASSERT_FILE_EXISTS("test.restart.mpiio");
if (!info->has_package("MPIIO")) {
TEST_FAILURE(".*ERROR: Illegal write_restart command.*",
command("write_restart test.restart.mpiio"););
} else {
TEST_FAILURE(".*ERROR: Restart file MPI-IO output not allowed with % in filename.*",
command("write_restart test.restart-%.mpiio"););
}
TEST_FAILURE(".*ERROR: Illegal write_restart command.*", command("write_restart"););
TEST_FAILURE(".*ERROR: Illegal write_restart command.*",
command("write_restart test.restart xxxx"););
TEST_FAILURE(".*ERROR on proc 0: Cannot open restart file some_crazy_dir/test.restart:"
" No such file or directory.*",
command("write_restart some_crazy_dir/test.restart"););
BEGIN_HIDE_OUTPUT();
command("clear");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 0);
ASSERT_EQ(lmp->update->ntimestep, 0);
ASSERT_EQ(lmp->domain->triclinic, 0);
TEST_FAILURE(
".*ERROR on proc 0: Cannot open restart file noexist.restart: No such file or directory.*",
command("read_restart noexist.restart"););
BEGIN_HIDE_OUTPUT();
command("read_restart step333.restart");
command("change_box all triclinic");
command("write_restart triclinic.restart");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 1);
ASSERT_EQ(lmp->update->ntimestep, 333);
ASSERT_EQ(lmp->domain->triclinic, 1);
BEGIN_HIDE_OUTPUT();
command("clear");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 0);
ASSERT_EQ(lmp->update->ntimestep, 0);
ASSERT_EQ(lmp->domain->triclinic, 0);
BEGIN_HIDE_OUTPUT();
command("read_restart triclinic.restart");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 1);
ASSERT_EQ(lmp->update->ntimestep, 333);
ASSERT_EQ(lmp->domain->triclinic, 1);
// clean up
delete_file("noinit.restart");
delete_file("test.restart");
delete_file("step333.restart");
delete_file("multi-base.restart");
delete_file("multi-0.restart");
delete_file("multi2-base.restart");
delete_file("multi2-0.restart");
delete_file("multi3-base.restart");
delete_file("multi3-0.restart");
delete_file("triclinic.restart");
if (info->has_package("MPIIO")) delete_file("test.restart.mpiio");
}
TEST_F(FileOperationsTest, write_data)
{
BEGIN_HIDE_OUTPUT();
command("echo none");
END_HIDE_OUTPUT();
TEST_FAILURE(".*ERROR: Write_data command before simulation box is defined.*",
command("write_data test.data"););
BEGIN_HIDE_OUTPUT();
command("region box block -2 2 -2 2 -2 2");
command("create_box 2 box");
command("create_atoms 1 single 0.5 0.0 0.0");
command("pair_style zero 1.0");
command("pair_coeff * *");
command("mass * 1.0");
command("reset_timestep 333");
command("write_data noinit.data noinit");
command("write_data nocoeff.data nocoeff");
command("run 0 post no");
command("write_data test.data");
command("write_data step*.data pair ij");
command("fix q all property/atom q");
command("set type 1 charge -0.5");
command("write_data charge.data");
command("write_data nofix.data nofix");
END_HIDE_OUTPUT();
ASSERT_FILE_EXISTS("noinit.data");
ASSERT_EQ(count_lines("noinit.data"), 26);
ASSERT_FILE_EXISTS("test.data");
ASSERT_EQ(count_lines("test.data"), 26);
ASSERT_FILE_EXISTS("step333.data");
ASSERT_EQ(count_lines("step333.data"), 27);
ASSERT_FILE_EXISTS("nocoeff.data");
ASSERT_EQ(count_lines("nocoeff.data"), 21);
ASSERT_FILE_EXISTS("nofix.data");
ASSERT_EQ(count_lines("nofix.data"), 26);
ASSERT_FILE_EXISTS("charge.data");
ASSERT_EQ(count_lines("charge.data"), 30);
TEST_FAILURE(".*ERROR: Illegal write_data command.*", command("write_data"););
TEST_FAILURE(".*ERROR: Illegal write_data command.*", command("write_data test.data xxxx"););
TEST_FAILURE(".*ERROR: Illegal write_data command.*", command("write_data test.data pair xx"););
TEST_FAILURE(".*ERROR on proc 0: Cannot open data file some_crazy_dir/test.data:"
" No such file or directory.*",
command("write_data some_crazy_dir/test.data"););
BEGIN_HIDE_OUTPUT();
command("clear");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->domain->box_exist, 0);
ASSERT_EQ(lmp->atom->natoms, 0);
ASSERT_EQ(lmp->update->ntimestep, 0);
ASSERT_EQ(lmp->domain->triclinic, 0);
TEST_FAILURE(".*ERROR: Cannot open file noexist.data: No such file or directory.*",
command("read_data noexist.data"););
BEGIN_HIDE_OUTPUT();
command("pair_style zero 1.0");
command("read_data step333.data");
command("change_box all triclinic");
command("write_data triclinic.data");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 1);
ASSERT_EQ(lmp->update->ntimestep, 0);
ASSERT_EQ(lmp->domain->triclinic, 1);
BEGIN_HIDE_OUTPUT();
command("clear");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 0);
ASSERT_EQ(lmp->domain->triclinic, 0);
BEGIN_HIDE_OUTPUT();
command("pair_style zero 1.0");
command("read_data triclinic.data");
END_HIDE_OUTPUT();
ASSERT_EQ(lmp->atom->natoms, 1);
ASSERT_EQ(lmp->domain->triclinic, 1);
// clean up
delete_file("charge.data");
delete_file("nocoeff.data");
delete_file("noinit.data");
delete_file("nofix.data");
delete_file("test.data");
delete_file("step333.data");
delete_file("triclinic.data");
}
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);

View File

@ -0,0 +1,182 @@
/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
https://lammps.sandia.gov/, Sandia National Laboratories
Steve Plimpton, sjplimp@sandia.gov
Copyright (2003) Sandia Corporation. Under the terms of Contract
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
certain rights in this software. This software is distributed under
the GNU General Public License.
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
#include "info.h"
#include "input.h"
#include "text_file_reader.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "../testing/core.h"
#include <cstring>
#include <iostream>
#include <mpi.h>
#include <vector>
using namespace LAMMPS_NS;
using LAMMPS_NS::utils::split_words;
// whether to print verbose output (i.e. not capturing LAMMPS screen output).
bool verbose = false;
class TextFileReaderTest : public ::testing::Test {
protected:
void TearDown() override
{
unlink("text_reader_one.file");
unlink("text_reader_two.file");
}
void test_files()
{
FILE *fp = fopen("text_reader_one.file", "w");
fputs("# test file 1 for text file reader\n\n\none\n two \n\n"
"three # with comment\nfour ! with non-comment\n"
"# comments only\n five\n#END\n",
fp);
fclose(fp);
fp = fopen("text_reader_two.file", "w");
fputs("# test file for atomfile style variable\n\n"
"4 # four lines\n4 0.5 #with comment\n"
"2 -0.5 \n3 1.5\n1 -1.5\n\n"
"2\n10 1.0 # test\n13 1.0\n\n######\n"
"4\n1 4.0 # test\n2 3.0\n3 2.0\n4 1.0\n#END\n",
fp);
fclose(fp);
}
};
TEST_F(TextFileReaderTest, nofile)
{
ASSERT_THROW({ TextFileReader reader("text_reader_noexist.file", "test"); },
FileReaderException);
}
TEST_F(TextFileReaderTest, permissions)
{
FILE *fp = fopen("text_reader_noperms.file", "w");
fputs("word\n", fp);
fclose(fp);
chmod("text_reader_noperms.file", 0);
ASSERT_THROW({ TextFileReader reader("text_reader_noperms.file", "test"); },
FileReaderException);
unlink("text_reader_noperms.file");
}
TEST_F(TextFileReaderTest, nofp)
{
ASSERT_THROW({ TextFileReader reader(nullptr, "test"); },
FileReaderException);
}
TEST_F(TextFileReaderTest, usefp)
{
test_files();
FILE *fp = fopen("text_reader_two.file","r");
ASSERT_NE(fp,nullptr);
auto reader = new TextFileReader(fp, "test");
auto line = reader->next_line();
ASSERT_STREQ(line, "4 ");
line = reader->next_line(1);
ASSERT_STREQ(line, "4 0.5 ");
ASSERT_NO_THROW({ reader->skip_line(); });
auto values = reader->next_values(1);
ASSERT_EQ(values.count(), 2);
ASSERT_EQ(values.next_int(), 3);
ASSERT_STREQ(values.next_string().c_str(), "1.5");
ASSERT_NE(reader->next_line(), nullptr);
double data[20];
ASSERT_THROW({ reader->next_dvector(data,20); }, FileReaderException);
ASSERT_THROW({ reader->skip_line(); }, EOFException);
ASSERT_EQ(reader->next_line(), nullptr);
delete reader;
// check that we reached EOF and the destructor didn't close the file.
ASSERT_EQ(feof(fp),1);
ASSERT_EQ(fclose(fp),0);
}
TEST_F(TextFileReaderTest, comments)
{
test_files();
TextFileReader reader("text_reader_two.file", "test");
reader.ignore_comments = true;
auto line = reader.next_line();
ASSERT_STREQ(line, "4 ");
line = reader.next_line(1);
ASSERT_STREQ(line, "4 0.5 ");
ASSERT_NO_THROW({ reader.skip_line(); });
auto values = reader.next_values(1);
ASSERT_EQ(values.count(), 2);
ASSERT_EQ(values.next_int(), 3);
ASSERT_STREQ(values.next_string().c_str(), "1.5");
ASSERT_NE(reader.next_line(), nullptr);
double data[20];
ASSERT_THROW({ reader.next_dvector(data,20); }, FileReaderException);
ASSERT_THROW({ reader.skip_line(); }, EOFException);
ASSERT_EQ(reader.next_line(), nullptr);
}
TEST_F(TextFileReaderTest, nocomments)
{
test_files();
TextFileReader reader("text_reader_one.file", "test");
reader.ignore_comments = false;
auto line = reader.next_line();
ASSERT_STREQ(line, "# test file 1 for text file reader\n");
line = reader.next_line(1);
ASSERT_STREQ(line, "one\n");
ASSERT_NO_THROW({ reader.skip_line(); });
auto values = reader.next_values(4);
ASSERT_EQ(values.count(), 4);
ASSERT_STREQ(values.next_string().c_str(), "three");
ASSERT_STREQ(values.next_string().c_str(), "#");
ASSERT_STREQ(values.next_string().c_str(), "with");
try {
reader.next_values(100);
FAIL() << "No exception thrown\n";
} catch (EOFException &e) {
ASSERT_STREQ(e.what(), "Incorrect format in test file! 9/100 parameters");
}
ASSERT_THROW({ reader.skip_line(); }, EOFException);
ASSERT_EQ(reader.next_line(), nullptr);
}
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);
::testing::InitGoogleMock(&argc, argv);
if (Info::get_mpi_vendor() == "Open MPI" && !LAMMPS_NS::Info::has_exceptions())
std::cout << "Warning: using OpenMPI without exceptions. "
"Death tests will be skipped\n";
// handle arguments passed via environment variable
if (const char *var = getenv("TEST_ARGS")) {
std::vector<std::string> env = split_words(var);
for (auto arg : env) {
if (arg == "-v") {
verbose = true;
}
}
}
if ((argc > 1) && (strcmp(argv[1], "-v") == 0)) verbose = true;
int rv = RUN_ALL_TESTS();
MPI_Finalize();
return rv;
}

View File

@ -10,14 +10,12 @@
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
#ifndef TEST_EXTENSIONS__H
#define TEST_EXTENSIONS__H
#ifndef LMP_TESTING_UTILS_H
#define LMP_TESTING_UTILS_H
#include <fstream>
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
static void delete_file(const std::string &filename)
@ -65,8 +63,8 @@ static std::vector<std::string> read_lines(const std::string &filename)
static bool file_exists(const std::string &filename)
{
struct stat result;
return stat(filename.c_str(), &result) == 0;
std::ifstream infile(filename);
return infile.good();
}
#define ASSERT_FILE_EXISTS(NAME) ASSERT_TRUE(file_exists(NAME))

View File

@ -53,6 +53,11 @@ TEST(Tokenizer, skip)
ASSERT_FALSE(t.has_next());
ASSERT_EQ(t.count(), 2);
ASSERT_THROW(t.skip(), TokenizerException);
try {
t.skip();
} catch (TokenizerException &e) {
ASSERT_STREQ(e.what(), "No more tokens");
}
}
TEST(Tokenizer, prefix_separators)
@ -87,6 +92,47 @@ TEST(Tokenizer, copy_constructor)
ASSERT_EQ(u.count(), 2);
}
TEST(Tokenizer, move_constructor)
{
Tokenizer u = std::move(Tokenizer("test new word ", " "));
ASSERT_THAT(u.next(), Eq("test"));
ASSERT_THAT(u.next(), Eq("new"));
ASSERT_THAT(u.next(), Eq("word"));
ASSERT_EQ(u.count(), 3);
}
TEST(Tokenizer, copy_assignment)
{
Tokenizer t(" test word ", " ");
Tokenizer u(" test2 word2 other2 ", " ");
ASSERT_THAT(t.next(), Eq("test"));
ASSERT_THAT(t.next(), Eq("word"));
ASSERT_EQ(t.count(), 2);
Tokenizer v = u;
u = t;
ASSERT_THAT(u.next(), Eq("test"));
ASSERT_THAT(u.next(), Eq("word"));
ASSERT_EQ(u.count(), 2);
ASSERT_THAT(v.next(), Eq("test2"));
ASSERT_THAT(v.next(), Eq("word2"));
ASSERT_THAT(v.next(), Eq("other2"));
ASSERT_EQ(v.count(), 3);
}
TEST(Tokenizer, move_assignment)
{
Tokenizer t(" test word ", " ");
ASSERT_THAT(t.next(), Eq("test"));
ASSERT_THAT(t.next(), Eq("word"));
ASSERT_EQ(t.count(), 2);
t = Tokenizer("test new word ", " ");
ASSERT_THAT(t.next(), Eq("test"));
ASSERT_THAT(t.next(), Eq("new"));
ASSERT_THAT(t.next(), Eq("word"));
ASSERT_EQ(t.count(), 3);
}
TEST(Tokenizer, no_separator_path)
{
Tokenizer t("one", ":");
@ -181,12 +227,72 @@ TEST(ValueTokenizer, skip)
ASSERT_FALSE(t.has_next());
ASSERT_EQ(t.count(), 2);
ASSERT_THROW(t.skip(), TokenizerException);
try {
t.skip();
} catch (TokenizerException &e) {
ASSERT_STREQ(e.what(), "No more tokens");
}
}
TEST(ValueTokenizer, copy_constructor)
{
ValueTokenizer t(" test word ", " ");
ASSERT_THAT(t.next_string(), Eq("test"));
ASSERT_THAT(t.next_string(), Eq("word"));
ASSERT_EQ(t.count(), 2);
ValueTokenizer u(t);
ASSERT_THAT(u.next_string(), Eq("test"));
ASSERT_THAT(u.next_string(), Eq("word"));
ASSERT_EQ(u.count(), 2);
}
TEST(ValueTokenizer, move_constructor)
{
ValueTokenizer u = std::move(ValueTokenizer(" test new word ", " "));
ASSERT_THAT(u.next_string(), Eq("test"));
ASSERT_THAT(u.next_string(), Eq("new"));
ASSERT_THAT(u.next_string(), Eq("word"));
ASSERT_EQ(u.count(), 3);
}
TEST(ValueTokenizer, copy_assignment)
{
ValueTokenizer t(" test word ", " ");
ValueTokenizer u(" test2 word2 other2 ", " ");
ASSERT_THAT(t.next_string(), Eq("test"));
ASSERT_THAT(t.next_string(), Eq("word"));
ASSERT_EQ(t.count(), 2);
ValueTokenizer v = u;
u = t;
ASSERT_THAT(u.next_string(), Eq("test"));
ASSERT_THAT(u.next_string(), Eq("word"));
ASSERT_EQ(u.count(), 2);
ASSERT_THAT(v.next_string(), Eq("test2"));
ASSERT_THAT(v.next_string(), Eq("word2"));
ASSERT_THAT(v.next_string(), Eq("other2"));
ASSERT_EQ(v.count(), 3);
}
TEST(ValueTokenizer, move_assignment)
{
ValueTokenizer t(" test word ", " ");
ASSERT_THAT(t.next_string(), Eq("test"));
ASSERT_THAT(t.next_string(), Eq("word"));
ASSERT_EQ(t.count(), 2);
t = ValueTokenizer("test new word ", " ");
ASSERT_THAT(t.next_string(), Eq("test"));
ASSERT_THAT(t.next_string(), Eq("new"));
ASSERT_THAT(t.next_string(), Eq("word"));
ASSERT_EQ(t.count(), 3);
}
TEST(ValueTokenizer, bad_integer)
{
ValueTokenizer values("f10");
ValueTokenizer values("f10 f11 f12");
ASSERT_THROW(values.next_int(), InvalidIntegerException);
ASSERT_THROW(values.next_bigint(), InvalidIntegerException);
ASSERT_THROW(values.next_tagint(), InvalidIntegerException);
}
TEST(ValueTokenizer, bad_double)