Merge remote-tracking branch 'origin/master' into varargs-log-error-functions

This commit is contained in:
Richard Berger 2021-04-28 18:13:31 -04:00
commit a9ff9312d6
No known key found for this signature in database
GPG Key ID: A9E83994E0BA0CAB
28 changed files with 899 additions and 73 deletions

View File

@ -26,11 +26,11 @@ __
## I don't want to read this whole thing I just have a question!
> **Note:** Please do not file an issue to ask a general question about LAMMPS, its features, how to use specific commands, or how perform simulations or analysis in LAMMPS. Instead post your question to the ['lammps-users' mailing list](https://lammps.sandia.gov/mail.html). You do not need to be subscribed to post to the list (but a mailing list subscription avoids having your post delayed until it is approved by a mailing list moderator). Most posts to the mailing list receive a response within less than 24 hours. Before posting to the mailing list, please read the [mailing list guidelines](https://lammps.sandia.gov/guidelines.html). Following those guidelines will help greatly to get a helpful response. Always mention which LAMMPS version you are using.
> **Note:** Please do not file an issue to ask a general question about LAMMPS, its features, how to use specific commands, or how perform simulations or analysis in LAMMPS. Instead post your question to either the ['lammps-users' mailing list](https://lammps.sandia.gov/mail.html) or the [LAMMPS Material Science Discourse forum](https://matsci.org/lammps). You do not need to be subscribed to post to the list (but a mailing list subscription avoids having your post delayed until it is approved by a mailing list moderator). Most posts to the mailing list receive a response within less than 24 hours. Before posting to the mailing list, please read the [mailing list guidelines](https://lammps.sandia.gov/guidelines.html). Following those guidelines will help greatly to get a helpful response. Always mention which LAMMPS version you are using. The LAMMPS forum was recently created as part of a larger effort to build a materials science community and have discussions not just about using LAMMPS. Thus the forum may be also used for discussions that would be off-topic for the mailing list. Those will just have to be moved to a more general category.
## How Can I Contribute?
There are several ways how you can actively contribute to the LAMMPS project: you can discuss compiling and using LAMMPS, and solving LAMMPS related problems with other LAMMPS users on the lammps-users mailing list, you can report bugs or suggest enhancements by creating issues on GitHub (or posting them to the lammps-users mailing list), and you can contribute by submitting pull requests on GitHub or e-mail your code
There are several ways how you can actively contribute to the LAMMPS project: you can discuss compiling and using LAMMPS, and solving LAMMPS related problems with other LAMMPS users on the lammps-users mailing list, you can report bugs or suggest enhancements by creating issues on GitHub (or posting them to the lammps-users mailing list or posting in the LAMMPS Materials Science Discourse forum), and you can contribute by submitting pull requests on GitHub or e-mail your code
to one of the [LAMMPS core developers](https://lammps.sandia.gov/authors.html). As you may see from the aforementioned developer page, the LAMMPS software package includes the efforts of a very large number of contributors beyond the principal authors and maintainers.
### Discussing How To Use LAMMPS
@ -42,6 +42,8 @@ Anyone can browse/search previous questions/answers in the archives. You do not
If you post a message and you are a subscriber, your message will appear immediately. If you are not a subscriber, your message will be moderated, which typically takes one business day. Either way, when someone replies the reply will usually be sent to both, your personal email address and the mailing list. When replying to people, that responded to your post to the list, please always included the mailing list in your replies (i.e. use "Reply All" and **not** "Reply"). Responses will appear on the list in a few minutes, but it can take a few hours for postings and replies to show up in the SourceForge archive. Sending replies also to the mailing list is important, so that responses are archived and people with a similar issue can search for possible solutions in the mailing list archive.
The LAMMPS Materials Science Discourse forum was created recently to facilitate discussion not just about LAMMPS and as part of a larger effort towards building a materials science community. The forum contains a read-only sub-category with the continually updated mailing list archive, so you won't miss anything by joining only the forum and not the mailing list.
### Reporting Bugs
While developers writing code for LAMMPS are careful to test their code, LAMMPS is such a large and complex software, that it is impossible to test for all combinations of features under all normal and not so normal circumstances. Thus bugs do happen, and if you suspect, that you have encountered one, please try to document it and report it as an [Issue](https://github.com/lammps/lammps/issues) on the LAMMPS GitHub project web page. However, before reporting a bug, you need to check whether this is something that may have already been corrected. The [Latest Features and Bug Fixes in LAMMPS](https://lammps.sandia.gov/bug.html) web page lists all significant changes to LAMMPS over the years. It also tells you what the current latest development version of LAMMPS is, and you should test whether your issue still applies to that version.

View File

@ -79,9 +79,11 @@ if(INTEL_ARCH STREQUAL "KNL")
else()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
include(CheckCXXCompilerFlag)
foreach(_FLAG -O2 -fp-model fast=2 -no-prec-div -qoverride-limits -qopt-zmm-usage=high -qno-offload -fno-alias -ansi-alias -restrict)
check_cxx_compiler_flag("${_FLAG}" COMPILER_SUPPORTS${_FLAG})
if(COMPILER_SUPPORTS${_FLAG})
foreach(_FLAG -O2 "-fp-model fast=2" -no-prec-div -qoverride-limits -qopt-zmm-usage=high -qno-offload -fno-alias -ansi-alias -restrict)
string(REGEX REPLACE "[ =\"]" "" _FLAGX ${_FLAG})
check_cxx_compiler_flag("${_FLAG}" COMPILER_SUPPORTS${_FLAGX})
if(COMPILER_SUPPORTS${_FLAGX})
separate_arguments(_FLAG UNIX_COMMAND "${_FLAG}")
target_compile_options(lammps PRIVATE ${_FLAG})
endif()
endforeach()

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

@ -9,8 +9,8 @@ reading or writing to files with error checking or translation of
strings into specific types of numbers with checking for validity. This
reduces redundant implementations and encourages consistent behavior.
I/O with status check
^^^^^^^^^^^^^^^^^^^^^
I/O with status check and similar functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The the first two functions are wrappers around the corresponding C
library calls ``fgets()`` or ``fread()``. They will check if there
@ -19,6 +19,14 @@ In that case, the functions will stop with an error message, indicating
the name of the problematic file, if possible unless the *error* argument
is a NULL pointer.
The :cpp:func:`fgets_trunc` function will work similar for ``fgets()``
but it will read in a whole line (i.e. until the end of line or end
of file), but store only as many characters as will fit into the buffer
including a final newline character and the terminating NULL byte.
If the line in the file is longer it will thus be truncated in the buffer.
This function is used by :cpp:func:`read_lines_from_file` to read individual
lines but make certain they follow the size constraints.
The :cpp:func:`read_lines_from_file` function will read the requested
number of lines of a maximum length into a buffer and will return 0
if successful or 1 if not. It also guarantees that all lines are
@ -33,6 +41,9 @@ NULL character.
.. doxygenfunction:: sfread
:project: progguide
.. doxygenfunction:: fgets_trunc
:project: progguide
.. doxygenfunction:: read_lines_from_file
:project: progguide

View File

@ -223,6 +223,9 @@ The structure of the data file is important, though many settings and
sections are optional or can come in any order. See the examples
directory for sample data files for different problems.
The file will be read line by line, but there is a limit of 254
characters per line and characters beyond that limit will be ignored.
A data file has a header and a body. The header appears first. The
first line of the header is always skipped; it typically contains a
description of the file. Then lines are read one at a time. Lines

View File

@ -363,12 +363,14 @@ variable, as discussed below.
The rules for formatting the file are as follows. Each time a set of
per-atom values is read, a non-blank line is searched for in the file.
A comment character "#" can be used anywhere on a line; text starting
with the comment character is stripped. Blank lines are skipped. The
first "word" of a non-blank line, delimited by white-space, is read as
the count N of per-atom lines to immediately follow. N can be the
total number of atoms in the system, or only a subset. The next N
lines have the following format
The file is read line by line but only up to 254 characters are used.
The rest are ignored. A comment character "#" can be used anywhere
on a line and all text following and the "#" character are ignored;
text starting with the comment character is stripped. Blank lines
are skipped. The first "word" of a non-blank line, delimited by
white-space, is read as the count N of per-atom lines to immediately
follow. N can be the total number of atoms in the system, or only a
subset. The next N lines have the following format
.. parsed-literal::

View File

@ -266,7 +266,7 @@ KokkosLMP::KokkosLMP(LAMMPS *lmp, int narg, char **arg) : Pointers(lmp)
#if defined(MPICH) && defined(MVAPICH2_VERSION)
char* str;
gpu_aware_flag = 0;
if ((str = getenv("MV2_ENABLE_CUDA")))
if ((str = getenv("MV2_USE_CUDA")))
if ((strcmp(str,"1") == 0))
gpu_aware_flag = 1;

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

@ -41,24 +41,25 @@
using namespace LAMMPS_NS;
#define MAXLINE 256
#define LB_FACTOR 1.1
#define CHUNK 1024
#define DELTA 4 // must be 2 or larger
#define MAXBODY 32 // max # of lines in one body
static constexpr int MAXLINE = 256;
static constexpr double LB_FACTOR = 1.1;
static constexpr int CHUNK = 1024;
static constexpr int DELTA = 4; // must be 2 or larger
static constexpr int MAXBODY = 32; // max # of lines in one body
// customize for new sections
#define NSECTIONS 25 // change when add to header::section_keywords
// customize for new sections
// change when add to header::section_keywords
static constexpr int NSECTIONS = 25;
enum{NONE,APPEND,VALUE,MERGE};
// pair style suffixes to ignore
// when matching Pair Coeffs comment to currently-defined pair style
const char *suffixes[] = {"/cuda","/gpu","/opt","/omp","/kk",
"/coul/cut","/coul/long","/coul/msm",
"/coul/dsf","/coul/debye","/coul/charmm",
nullptr};
static const char *suffixes[] = {"/cuda","/gpu","/opt","/omp","/kk",
"/coul/cut","/coul/long","/coul/msm",
"/coul/dsf","/coul/debye","/coul/charmm",
nullptr};
/* ---------------------------------------------------------------------- */
@ -304,6 +305,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) {
@ -950,7 +957,7 @@ void ReadData::header(int firstpass)
// skip 1st line of file
if (me == 0) {
char *eof = fgets(line,MAXLINE,fp);
char *eof = utils::fgets_trunc(line,MAXLINE,fp);
if (eof == nullptr) error->one(FLERR,"Unexpected end of data file");
}
@ -959,7 +966,7 @@ void ReadData::header(int firstpass)
// read a line and bcast length
if (me == 0) {
if (fgets(line,MAXLINE,fp) == nullptr) n = 0;
if (utils::fgets_trunc(line,MAXLINE,fp) == nullptr) n = 0;
else n = strlen(line) + 1;
}
MPI_Bcast(&n,1,MPI_INT,0,world);
@ -1662,7 +1669,7 @@ void ReadData::bodies(int firstpass, AtomVec *ptr)
m = 0;
while (nchunk < nmax && nline <= CHUNK-MAXBODY) {
eof = fgets(&buffer[m],MAXLINE,fp);
eof = utils::fgets_trunc(&buffer[m],MAXLINE,fp);
if (eof == nullptr) error->one(FLERR,"Unexpected end of data file");
rv = sscanf(&buffer[m],"%d %d %d",&tmp,&ninteger,&ndouble);
if (rv != 3)
@ -1676,7 +1683,7 @@ void ReadData::bodies(int firstpass, AtomVec *ptr)
nword = 0;
while (nword < ninteger) {
eof = fgets(&buffer[m],MAXLINE,fp);
eof = utils::fgets_trunc(&buffer[m],MAXLINE,fp);
if (eof == nullptr) error->one(FLERR,"Unexpected end of data file");
ncount = utils::trim_and_count_words(&buffer[m]);
if (ncount == 0)
@ -1690,7 +1697,7 @@ void ReadData::bodies(int firstpass, AtomVec *ptr)
nword = 0;
while (nword < ndouble) {
eof = fgets(&buffer[m],MAXLINE,fp);
eof = utils::fgets_trunc(&buffer[m],MAXLINE,fp);
if (eof == nullptr) error->one(FLERR,"Unexpected end of data file");
ncount = utils::trim_and_count_words(&buffer[m]);
if (ncount == 0)
@ -1994,15 +2001,15 @@ void ReadData::parse_keyword(int first)
if (me == 0) {
if (!first) {
if (fgets(line,MAXLINE,fp) == nullptr) eof = 1;
if (utils::fgets_trunc(line,MAXLINE,fp) == nullptr) eof = 1;
}
while (eof == 0 && done == 0) {
int blank = strspn(line," \t\n\r");
if ((blank == (int)strlen(line)) || (line[blank] == '#')) {
if (fgets(line,MAXLINE,fp) == nullptr) eof = 1;
if (utils::fgets_trunc(line,MAXLINE,fp) == nullptr) eof = 1;
} else done = 1;
}
if (fgets(buffer,MAXLINE,fp) == nullptr) {
if (utils::fgets_trunc(buffer,MAXLINE,fp) == nullptr) {
eof = 1;
buffer[0] = '\0';
}
@ -2056,7 +2063,7 @@ void ReadData::skip_lines(bigint n)
if (me) return;
if (n <= 0) return;
char *eof = nullptr;
for (bigint i = 0; i < n; i++) eof = fgets(line,MAXLINE,fp);
for (bigint i = 0; i < n; i++) eof = utils::fgets_trunc(line,MAXLINE,fp);
if (eof == nullptr) error->one(FLERR,"Unexpected end of data file");
}

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

@ -170,6 +170,42 @@ const char *utils::guesspath(char *buf, int len, FILE *fp)
return buf;
}
// read line into buffer. if line is too long keep reading until EOL or EOF
// but return only the first part with a newline at the end.
char *utils::fgets_trunc(char *buf, int size, FILE *fp)
{
constexpr int MAXDUMMY = 256;
char dummy[MAXDUMMY];
char *ptr = fgets(buf,size,fp);
// EOF
if (!ptr) return nullptr;
int n = strlen(buf);
// line is shorter than buffer, append newline if needed,
if (n < size-2) {
if (buf[n-1] != '\n') {
buf[n] = '\n';
buf[n+1] = '\0';
}
return buf;
// line fits exactly. overwrite last but one character.
} else buf[size-2] = '\n';
// continue reading into dummy buffer until end of line or file
do {
ptr = fgets(dummy,MAXDUMMY,fp);
if (ptr) n = strlen(ptr);
else n = 0;
} while (n == MAXDUMMY-1 && ptr[MAXDUMMY-1] != '\n');
// return first chunk
return buf;
}
#define MAXPATHLENBUF 1024
/* like fgets() but aborts with an error or EOF is encountered */
void utils::sfgets(const char *srcname, int srcline, char *s, int size,
@ -240,13 +276,12 @@ int utils::read_lines_from_file(FILE *fp, int nlines, int nmax,
if (me == 0) {
if (fp) {
for (int i = 0; i < nlines; i++) {
ptr = fgets(ptr,nmax,fp);
ptr = fgets_trunc(ptr,nmax,fp);
if (!ptr) break; // EOF?
// advance ptr to end of string and append newline char if needed.
// advance ptr to end of string
ptr += strlen(ptr);
if (*(--ptr) != '\n') *(++ptr) = '\n';
// ensure buffer is null terminated. null char is start of next line.
*(++ptr) = '\0';
*ptr = '\0';
}
}
}

View File

@ -86,6 +86,20 @@ namespace LAMMPS_NS {
std::string getsyserror();
/** Wrapper around fgets() which reads whole lines but truncates the
* data to the buffer size and ensures a newline char at the end.
*
* This function is useful for reading line based text files with
* possible comments that should be parsed later. This applies to
* data files, potential files, atomfile variable files and so on.
* It is used instead of fgets() by utils::read_lines_from_file().
*
* \param s buffer for storing the result of fgets()
* \param size size of buffer s (max number of bytes returned)
* \param fp file pointer used by fgets() */
char *fgets_trunc(char *s, int size, FILE *fp);
/** Safe wrapper around fgets() which aborts on errors
* or EOF and prints a suitable error message to help debugging.
*

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,9 @@ TEST_F(LAMMPS_omp, InitMembers)
EXPECT_STREQ(LAMMPS::git_branch, "(unknown)");
EXPECT_STREQ(LAMMPS::git_descriptor, "(unknown)");
}
#if 0 // temporarily disabled. MacOS behaves different from Linux here.
EXPECT_EQ(lmp->comm->nthreads, 2);
#endif
}
// test fixture for Kokkos tests
@ -318,10 +326,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 +376,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,11 +12,14 @@
------------------------------------------------------------------------- */
#include "../testing/core.h"
#include "../testing/utils.h"
#include "atom.h"
#include "domain.h"
#include "error.h"
#include "info.h"
#include "input.h"
#include "lammps.h"
#include "utils.h"
#include "update.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@ -45,19 +48,29 @@ 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();
out.open("file_with_long_lines_test.txt", std::ios_base::out | std::ios_base::binary);
ASSERT_TRUE(out.good());
out << "zero ##########################################################"
"##################################################################"
"##################################################################"
"############################################################\n";
out << "one line\ntwo_lines\n\n";
for (int i = 0; i < 100; ++i) out << "one two ";
out << "\nthree\nfour five #";
for (int i = 0; i < 1000; ++i) out << '#';
out.close();
}
void TearDown() override
{
LAMMPSTest::TearDown();
remove("safe_file_read_test.txt");
remove("file_with_long_lines_test.txt");
}
};
@ -66,7 +79,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);
@ -97,12 +110,74 @@ TEST_F(FileOperationsTest, safe_fgets)
fclose(fp);
}
#define MAX_BUF_SIZE 128
TEST_F(FileOperationsTest, fgets_trunc)
{
char buf[MAX_BUF_SIZE];
char *ptr;
FILE *fp = fopen("safe_file_read_test.txt", "rb");
ASSERT_NE(fp, nullptr);
memset(buf, 0, MAX_BUF_SIZE);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("one line\n"));
ASSERT_NE(ptr,nullptr);
memset(buf, 0, MAX_BUF_SIZE);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("two_lines\n"));
ASSERT_NE(ptr,nullptr);
memset(buf, 0, MAX_BUF_SIZE);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("\n"));
ASSERT_NE(ptr,nullptr);
memset(buf, 0, MAX_BUF_SIZE);
ptr = utils::fgets_trunc(buf, 4, fp);
ASSERT_THAT(buf, StrEq("no\n"));
ASSERT_NE(ptr,nullptr);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_EQ(ptr,nullptr);
fclose(fp);
fp = fopen("file_with_long_lines_test.txt", "r");
ASSERT_NE(fp,nullptr);
memset(buf, 0, MAX_BUF_SIZE);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_NE(ptr,nullptr);
ASSERT_THAT(buf, StrEq("zero ##########################################################"
"###############################################################\n"));
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("one line\n"));
ASSERT_NE(ptr,nullptr);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("two_lines\n"));
ASSERT_NE(ptr,nullptr);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_THAT(buf, StrEq("\n"));
ASSERT_NE(ptr,nullptr);
ptr = utils::fgets_trunc(buf, MAX_BUF_SIZE, fp);
ASSERT_NE(ptr,nullptr);
ASSERT_THAT(buf, StrEq("one two one two one two one two one two one two one two one two "
"one two one two one two one two one two one two one two one tw\n"));
fclose(fp);
}
#define MAX_BUF_SIZE 128
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);
@ -128,7 +203,7 @@ TEST_F(FileOperationsTest, safe_fread)
TEST_F(FileOperationsTest, read_lines_from_file)
{
char *buf = new char[MAX_BUF_SIZE];
char *buf = new char[MAX_BUF_SIZE];
FILE *fp = nullptr;
MPI_Comm world = MPI_COMM_WORLD;
int me, rv;
@ -225,6 +300,191 @@ TEST_F(FileOperationsTest, error_all_one)
lmp->error->one("testme.cpp", 10, "exit {} {}", "too"););
}
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: Writing to MPI-IO filename when MPIIO package is not inst.*",
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)