From: Teemu Murtola Date: Sat, 27 Jun 2015 17:39:33 +0000 (+0300) Subject: Tests for interactive selection input X-Git-Url: http://biod.pnpi.spb.ru/gitweb/?a=commitdiff_plain;h=aa40ec1fd149313b4402c12dfc499da54abe68d3;hp=180cf1538ad2b19bd6b4108662adc4a519565fcb;p=alexxy%2Fgromacs.git Tests for interactive selection input Add InteractiveTestHelper to test interactive sessions that use streams from textstream.h. Use this to test the behavior of interactive selection input. Errors in the input or the help is not tested (yet), and some corner cases may be missing, but most of the code is now covered. Separate set of tests is needed for SelectionOptionManager prompts (will be added separately). Remove obsolete special case handling from the interactive parser (verified to not change any of the now tested behavior). Change-Id: I448d470bcc240659a380ffa2d3b492949420c64b --- diff --git a/src/gromacs/analysisdata/tests/refdata/common-referencedata.xsl b/src/gromacs/analysisdata/tests/refdata/common-referencedata.xsl index 1ae38d8452..b6e9bcfdb6 100644 --- a/src/gromacs/analysisdata/tests/refdata/common-referencedata.xsl +++ b/src/gromacs/analysisdata/tests/refdata/common-referencedata.xsl @@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations. + +
+        
+            
+                
+                    
+                
+                
+                    
+                    
+                
+                
+                    
+                    
+                    

+                
+                
+                    
+                    
+                    
+                
+            
+        
+        [EOF]
+    
+
+ diff --git a/src/gromacs/selection/selectioncollection.cpp b/src/gromacs/selection/selectioncollection.cpp index bbe3f11df2..d124cf2800 100644 --- a/src/gromacs/selection/selectioncollection.cpp +++ b/src/gromacs/selection/selectioncollection.cpp @@ -188,26 +188,14 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState, bool bInteractive) { int status = YYPUSH_MORE; - int prevToken = 0; do { YYSTYPE value; YYLTYPE location; int token = _gmx_sel_yylex(&value, &location, scanner); - if (bInteractive) + if (bInteractive && token == 0) { - if (token == 0) - { - break; - } - // Empty commands cause the interactive parser to print out - // status information. This avoids producing those unnecessarily, - // e.g., from "resname RA;;". - if (prevToken == CMD_SEP && token == CMD_SEP) - { - continue; - } - prevToken = token; + break; } status = _gmx_sel_yypush_parse(parserState, token, &value, &location, scanner); } diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesBasicInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesBasicInput.xml new file mode 100644 index 0000000000..b6dab1ac7b --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesBasicInput.xml @@ -0,0 +1,30 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + ]]> + + ]]> + + ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml new file mode 100644 index 0000000000..55bba6efe1 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml @@ -0,0 +1,23 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + + + ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml new file mode 100644 index 0000000000..3f50325826 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml @@ -0,0 +1,39 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + ]]> + + ]]> + + for status/groups, 'help' for help, Ctrl-D to end) +Currently provided selections: + 1. resname RA + 2. resname RB +> ]]> + + ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml new file mode 100644 index 0000000000..8a1d9bfb33 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml @@ -0,0 +1,34 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + ]]> + + ]]> + + for status/groups, 'help' for help, Ctrl-D to end) +Currently provided selections: + 1. "Sel" resname RA + 2. "Sel2" resname RB +> ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml new file mode 100644 index 0000000000..6f4135d718 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml @@ -0,0 +1,22 @@ + + + + + for status/groups, 'help' for help) +> ]]> + + + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml new file mode 100644 index 0000000000..9555a9bfe1 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml @@ -0,0 +1,18 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml new file mode 100644 index 0000000000..515754e997 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml new file mode 100644 index 0000000000..7e469d39d4 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml @@ -0,0 +1,22 @@ + + + + + for status/groups, 'help' for help) +> ]]> + + ]]> + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml new file mode 100644 index 0000000000..32abef423e --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml new file mode 100644 index 0000000000..1e83b16bb1 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml @@ -0,0 +1,32 @@ + + + + + for status/groups, 'help' for help) +> ]]> + + ]]> + + for status/groups, 'help' for help) +Currently provided selections: + foo = resname RA +(1 more selection required) +> ]]> + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml new file mode 100644 index 0000000000..fc32bf7280 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml @@ -0,0 +1,44 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + for status/groups, 'help' for help, Ctrl-D to end) +Currently provided selections: + foo = resname RA +> ]]> + + ]]> + + ]]> + + for status/groups, 'help' for help, Ctrl-D to end) +Currently provided selections: + foo = resname RA + bar = resname RC + 1. resname RA +> ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml new file mode 100644 index 0000000000..1bc4c4ea3b --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml @@ -0,0 +1,35 @@ + + + + + for status/groups, 'help' for help, Ctrl-D to end) +> ]]> + + ]]> + + for status/groups, 'help' for help, Ctrl-D to end) +Currently provided selections: + 1. resname RA +> ]]> + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml new file mode 100644 index 0000000000..cef23d43f7 --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml @@ -0,0 +1,22 @@ + + + + + for status/groups, 'help' for help) +> ]]> + + ]]> + + + + diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml new file mode 100644 index 0000000000..2f7768051c --- /dev/null +++ b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml @@ -0,0 +1,32 @@ + + + + + for status/groups, 'help' for help) +> ]]> + + ]]> + + for status/groups, 'help' for help) +Currently provided selections: + 1. "Sel" resname RA +(1 more selection required) +> ]]> + + + + diff --git a/src/gromacs/selection/tests/refdata/common-referencedata.xsl b/src/gromacs/selection/tests/refdata/common-referencedata.xsl index 1ae38d8452..b6e9bcfdb6 100644 --- a/src/gromacs/selection/tests/refdata/common-referencedata.xsl +++ b/src/gromacs/selection/tests/refdata/common-referencedata.xsl @@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations. + +
+        
+            
+                
+                    
+                
+                
+                    
+                    
+                
+                
+                    
+                    
+                    

+                
+                
+                    
+                    
+                    
+                
+            
+        
+        [EOF]
+    
+
+ diff --git a/src/gromacs/selection/tests/refdata/referencedata.xsl b/src/gromacs/selection/tests/refdata/referencedata.xsl index d26d2e2b18..57048feab7 100644 --- a/src/gromacs/selection/tests/refdata/referencedata.xsl +++ b/src/gromacs/selection/tests/refdata/referencedata.xsl @@ -165,6 +165,11 @@ + +

Interactive Session

+ +
+

Parsed Selections

diff --git a/src/gromacs/selection/tests/selectioncollection.cpp b/src/gromacs/selection/tests/selectioncollection.cpp index c04539a03f..436abee87d 100644 --- a/src/gromacs/selection/tests/selectioncollection.cpp +++ b/src/gromacs/selection/tests/selectioncollection.cpp @@ -57,6 +57,7 @@ #include "gromacs/utility/gmxregex.h" #include "gromacs/utility/stringutil.h" +#include "testutils/interactivetest.h" #include "testutils/refdata.h" #include "testutils/testasserts.h" #include "testutils/testfilemanager.h" @@ -152,6 +153,39 @@ SelectionCollectionTest::loadIndexGroups(const char *filename) } +/******************************************************************** + * Test fixture for interactive SelectionCollection tests + */ + +class SelectionCollectionInteractiveTest : public SelectionCollectionTest +{ + public: + SelectionCollectionInteractiveTest() + : helper_(data_.rootChecker()) + { + } + + void runTest(int count, bool bInteractive, + const gmx::ConstArrayRef &input); + + gmx::test::TestReferenceData data_; + gmx::test::InteractiveTestHelper helper_; +}; + +void SelectionCollectionInteractiveTest::runTest( + int count, bool bInteractive, + const gmx::ConstArrayRef &inputLines) +{ + helper_.setInputLines(inputLines); + // TODO: Check something about the returned selections as well. + ASSERT_NO_THROW_GMX(sc_.parseInteractive( + count, &helper_.inputStream(), + bInteractive ? &helper_.outputStream() : NULL, + "for test context")); + helper_.checkSession(); +} + + /******************************************************************** * Test fixture for selection testing with reference data */ @@ -617,6 +651,150 @@ TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets3) // TODO: Tests for more evaluation errors +/******************************************************************** + * Tests for interactive selection input + */ + +TEST_F(SelectionCollectionInteractiveTest, HandlesBasicInput) +{ + const char *const input[] = { + "foo = resname RA", + "resname RB", + "\"Name\" resname RC" + }; + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesContinuation) +{ + const char *const input[] = { + "resname RB and \\", + "resname RC" + }; + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInput) +{ + const char *const input[] = { + "foo = resname RA", + "resname RA" + }; + runTest(1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInput) +{ + const char *const input[] = { + "resname RA", + "resname RB" + }; + runTest(2, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithGroups) +{ + const char *const input[] = { + "resname RA", + "" + }; + loadIndexGroups("simple.ndx"); + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithExistingSelections) +{ + const char *const input[] = { + "", + "bar = resname RC", + "resname RA", + "" + }; + ASSERT_NO_THROW_GMX(sc_.parseFromString("foo = resname RA")); + ASSERT_NO_THROW_GMX(sc_.parseFromString("resname RB")); + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputStatus) +{ + const char *const input[] = { + "foo = resname RA", + "", + "resname RB" + }; + runTest(1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInputStatus) +{ + const char *const input[] = { + "\"Sel\" resname RA", + "", + "resname RB" + }; + runTest(2, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesMultiSelectionInputStatus) +{ + const char *const input[] = { + "\"Sel\" resname RA", + "\"Sel2\" resname RB", + "" + }; + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesNoFinalNewline) +{ + // TODO: There is an extra prompt printed after the input is finished; it + // would be cleaner not to have it, but it's only a cosmetic issue. + const char *const input[] = { + "resname RA" + }; + helper_.setLastNewline(false); + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesEmptySelections) +{ + const char *const input[] = { + "resname RA;", + "; resname RB;;", + " ", + ";" + }; + runTest(-1, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesMultipleSelectionsOnLine) +{ + const char *const input[] = { + "resname RA; resname RB and \\", + "resname RC" + }; + runTest(2, true, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesNoninteractiveInput) +{ + const char *const input[] = { + "foo = resname RA", + "resname RB", + "\"Name\" resname RC" + }; + runTest(-1, false, input); +} + +TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputNoninteractively) +{ + const char *const input[] = { + "foo = resname RA", + "resname RA" + }; + runTest(1, false, input); +} + /******************************************************************** * Tests for selection keywords diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/common-referencedata.xsl b/src/gromacs/trajectoryanalysis/tests/refdata/common-referencedata.xsl index 1ae38d8452..b6e9bcfdb6 100644 --- a/src/gromacs/trajectoryanalysis/tests/refdata/common-referencedata.xsl +++ b/src/gromacs/trajectoryanalysis/tests/refdata/common-referencedata.xsl @@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations. + +
+        
+            
+                
+                    
+                
+                
+                    
+                    
+                
+                
+                    
+                    
+                    

+                
+                
+                    
+                    
+                    
+                
+            
+        
+        [EOF]
+    
+
+ diff --git a/src/testutils/CMakeLists.txt b/src/testutils/CMakeLists.txt index 131d9351f5..a2a5cdde51 100644 --- a/src/testutils/CMakeLists.txt +++ b/src/testutils/CMakeLists.txt @@ -37,6 +37,7 @@ include_directories(${LIBXML2_INCLUDE_DIR}) set(TESTUTILS_SOURCES cmdlinetest.cpp integrationtests.cpp + interactivetest.cpp mpi-printer.cpp refdata.cpp stringtest.cpp diff --git a/src/testutils/common-referencedata.xsl b/src/testutils/common-referencedata.xsl index 1ae38d8452..b6e9bcfdb6 100644 --- a/src/testutils/common-referencedata.xsl +++ b/src/testutils/common-referencedata.xsl @@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations. + +
+        
+            
+                
+                    
+                
+                
+                    
+                    
+                
+                
+                    
+                    
+                    

+                
+                
+                    
+                    
+                    
+                
+            
+        
+        [EOF]
+    
+
+ diff --git a/src/testutils/interactivetest.cpp b/src/testutils/interactivetest.cpp new file mode 100644 index 0000000000..ce8c8541b5 --- /dev/null +++ b/src/testutils/interactivetest.cpp @@ -0,0 +1,187 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal \file + * \brief + * Implements classes from interactivetest.h. + * + * \author Teemu Murtola + * \ingroup module_testutils + */ +#include "gmxpre.h" + +#include "interactivetest.h" + +#include + +#include +#include + +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textstream.h" + +#include "testutils/refdata.h" +#include "testutils/stringtest.h" + +namespace gmx +{ +namespace test +{ + +// These two classes cannot be in an unnamed namespace (easily), since +// then their use as members below would trigger warnings. +// But if anyone needs these outside this file, they can easily be moved to a +// separate header. + +class MockTextInputStream : public TextInputStream +{ + public: + MOCK_METHOD1(readLine, bool(std::string *)); + MOCK_METHOD0(close, void()); +}; + +class MockTextOutputStream : public TextOutputStream +{ + public: + MOCK_METHOD1(write, void(const char *)); + MOCK_METHOD0(close, void()); +}; + +class InteractiveTestHelper::Impl +{ + public: + explicit Impl(TestReferenceChecker checker) + : checker_(checker), bLastNewline_(true), + currentLine_(0), bHasOutput_(false) + { + using ::testing::_; + using ::testing::Invoke; + EXPECT_CALL(inputStream_, readLine(_)) + .WillRepeatedly(Invoke(this, &Impl::readInputLine)); + EXPECT_CALL(inputStream_, close()).Times(0); + EXPECT_CALL(outputStream_, write(_)) + .WillRepeatedly(Invoke(this, &Impl::addOutput)); + EXPECT_CALL(outputStream_, close()).Times(0); + } + + bool readInputLine(std::string *line) + { + checkOutput(); + line->clear(); + const bool bPresent = (currentLine_ < inputLines_.size()); + if (bPresent) + { + line->assign(inputLines_[currentLine_]); + if (bLastNewline_ || currentLine_ + 1 < inputLines_.size()) + { + line->append("\n"); + } + } + ++currentLine_; + const std::string id = formatString("Input%d", static_cast(currentLine_)); + StringTestBase::checkText(&checker_, *line, id.c_str()); + return bPresent; + } + void addOutput(const char *str) + { + bHasOutput_ = true; + currentOutput_.append(str); + } + + void checkOutput() + { + const std::string id = formatString("Output%d", static_cast(currentLine_)); + if (checker_.checkPresent(bHasOutput_, id.c_str())) + { + StringTestBase::checkText(&checker_, currentOutput_, id.c_str()); + } + bHasOutput_ = false; + currentOutput_.clear(); + } + void checkPendingInput() + { + const std::string id = formatString("Input%d", static_cast(currentLine_+1)); + checker_.checkPresent(false, id.c_str()); + } + + TestReferenceChecker checker_; + ConstArrayRef inputLines_; + bool bLastNewline_; + size_t currentLine_; + bool bHasOutput_; + std::string currentOutput_; + MockTextInputStream inputStream_; + MockTextOutputStream outputStream_; +}; + +InteractiveTestHelper::InteractiveTestHelper(TestReferenceChecker checker) + : impl_(new Impl(checker.checkCompound("InteractiveSession", "Interactive"))) +{ +} + +InteractiveTestHelper::~InteractiveTestHelper() +{ +} + +void InteractiveTestHelper::setLastNewline(bool bInclude) +{ + impl_->bLastNewline_ = bInclude; +} + +void InteractiveTestHelper::setInputLines( + const ConstArrayRef &inputLines) +{ + impl_->inputLines_ = inputLines; + impl_->currentLine_ = 0; +} + +TextInputStream &InteractiveTestHelper::inputStream() +{ + return impl_->inputStream_; +} + +TextOutputStream &InteractiveTestHelper::outputStream() +{ + return impl_->outputStream_; +} + +void InteractiveTestHelper::checkSession() +{ + impl_->checkOutput(); + impl_->checkPendingInput(); +} + +} // namespace test +} // namespace gmx diff --git a/src/testutils/interactivetest.h b/src/testutils/interactivetest.h new file mode 100644 index 0000000000..66167f1d60 --- /dev/null +++ b/src/testutils/interactivetest.h @@ -0,0 +1,126 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \libinternal \file + * \brief + * Provides helper classes for testing interactive prompts. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_testutils + */ +#ifndef GMX_TESTUTILS_INTERACTIVETEST_H +#define GMX_TESTUTILS_INTERACTIVETEST_H + +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/classhelpers.h" + +namespace gmx +{ + +class TextInputStream; +class TextOutputStream; + +namespace test +{ + +class TestReferenceChecker; + +/*! \libinternal \brief + * Helper class for testing interactive sessions. + * + * The calling test can set the user input using setInputLines() (and possibly + * setLastNewline()), pass the streams from inputStream() and outputStream() to + * the code that executes the interactive session, and then call checkSession() + * after the session is finished. + * The input is provided from the array set with setInputLines(), and all + * output is checked using the reference data framework. + * The reference XML data can be viewed with the XSLT stylesheet to show + * exactly how the session went. + * + * \inlibraryapi + * \ingroup module_testutils + */ +class InteractiveTestHelper +{ + public: + /*! \brief + * Initializes the helper. + * + * \param[in] checker Parent reference checker to use. + * + * The helper creates a compound item under \p checker for the + * interactive session it tests. + */ + explicit InteractiveTestHelper(gmx::test::TestReferenceChecker checker); + ~InteractiveTestHelper(); + + //! Sets whether the last input line contains a newline (by default, it does). + void setLastNewline(bool bInclude); + /*! \brief + * Sets the input lines for the interactive session. + * + * Calls to TextInputStream::readLine() will return strings from this + * array in sequence. + * Newlines are added at the end automatically (except for the last + * line if `setLastNewLine(false)` has been called). + * If there are more `readLine()` calls than there are input lines, + * the remaining calls return end-of-input. + */ + void setInputLines(const ConstArrayRef &inputLines); + + //! Returns the input stream for the session. + TextInputStream &inputStream(); + //! Returns the output stream for the session. + TextOutputStream &outputStream(); + + /*! \brief + * Finalizes the checking for the session. + * + * This must be called after all input and output from a session has + * occurred, as the helper will not otherwise know when output after + * the last input has finished. This method also checks that the + * required number of input lines were read in the session. + */ + void checkSession(); + + private: + class Impl; + + PrivateImplPointer impl_; +}; +} // namespace test +} // namespace gmx + +#endif diff --git a/src/testutils/tests/CMakeLists.txt b/src/testutils/tests/CMakeLists.txt index 045e7c5616..351584dab7 100644 --- a/src/testutils/tests/CMakeLists.txt +++ b/src/testutils/tests/CMakeLists.txt @@ -1,7 +1,7 @@ # # This file is part of the GROMACS molecular simulation package. # -# Copyright (c) 2011,2012,2014, by the GROMACS development team, led by +# Copyright (c) 2011,2012,2014,2015, by the GROMACS development team, led by # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, # and including many others, as listed in the AUTHORS file in the # top-level source directory and at http://www.gromacs.org. @@ -33,5 +33,6 @@ # the research papers on the package. Check out http://www.gromacs.org. gmx_add_unit_test(TestUtilsUnitTests testutils-test + interactivetest.cpp refdata_tests.cpp testasserts_tests.cpp) diff --git a/src/testutils/tests/interactivetest.cpp b/src/testutils/tests/interactivetest.cpp new file mode 100644 index 0000000000..4a3d3909d0 --- /dev/null +++ b/src/testutils/tests/interactivetest.cpp @@ -0,0 +1,378 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal \file + * \brief + * Self-tests for interactive test helpers. + * + * \author Teemu Murtola + * \ingroup module_testutils + */ +#include "gmxpre.h" + +#include "testutils/interactivetest.h" + +#include + +#include +#include + +#include "gromacs/utility/textstream.h" + +#include "testutils/refdata.h" + +namespace +{ + +class InteractiveSession +{ + public: + InteractiveSession(gmx::test::ReferenceDataMode mode) + : data_(mode), helper_(data_.rootChecker()), nextInputLine_(0) + { + } + + void addOutput(const char *output) + { + events_.push_back(Event(WriteOutput, output)); + } + void addInputLine(const char *inputLine) + { + inputLines_.push_back(inputLine); + } + void addReadInput() + { + events_.push_back(Event(ReadInput, "")); + } + void addInput(const char *inputLine) + { + addInputLine(inputLine); + addReadInput(); + } + void addInputNoNewline(const char *inputLine) + { + addInputLine(inputLine); + helper_.setLastNewline(false); + events_.push_back(Event(ReadInputNoNewline, "")); + } + + void run() + { + gmx::TextInputStream &input = helper_.inputStream(); + gmx::TextOutputStream &output = helper_.outputStream(); + helper_.setInputLines(inputLines_); + std::vector::const_iterator event; + for (event = events_.begin(); event != events_.end(); ++event) + { + if (event->first == WriteOutput) + { + output.write(event->second); + } + else + { + std::string expectedLine; + const bool bInputRemaining = (nextInputLine_ < inputLines_.size()); + if (bInputRemaining) + { + expectedLine = inputLines_[nextInputLine_]; + if (event->first != ReadInputNoNewline) + { + expectedLine.append("\n"); + } + } + ++nextInputLine_; + std::string line; + EXPECT_EQ(bInputRemaining, input.readLine(&line)); + EXPECT_EQ(expectedLine, line); + } + } + helper_.checkSession(); + } + + private: + enum EventType + { + ReadInput, + ReadInputNoNewline, + WriteOutput + }; + // The latter is the output string. + typedef std::pair Event; + + gmx::test::TestReferenceData data_; + gmx::test::InteractiveTestHelper helper_; + std::vector inputLines_; + size_t nextInputLine_; + std::vector events_; +}; + +TEST(InteractiveTestHelperTest, ChecksSimpleSession) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n"); + session.addOutput("> "); + session.addInput("input"); + session.addOutput("Second line\n"); + session.addOutput("> "); + session.addReadInput(); + session.addOutput("\n"); + session.addOutput(".\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n"); + session.addOutput("> "); + session.addInput("input"); + session.addOutput("Second line\n"); + session.addOutput("> "); + session.addReadInput(); + session.addOutput("\n"); + session.addOutput(".\n"); + session.run(); + } +} + +TEST(InteractiveTestHelperTest, ChecksSessionWithoutLastNewline) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n"); + session.addOutput("> "); + session.addInput("input"); + session.addOutput("Second line\n"); + session.addOutput("> "); + session.addInputNoNewline("input2"); + session.addOutput("\n"); + session.addOutput(".\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n"); + session.addOutput("> "); + session.addInput("input"); + session.addOutput("Second line\n"); + session.addOutput("> "); + session.addInputNoNewline("input2"); + session.addOutput("\n"); + session.addOutput(".\n"); + session.run(); + } +} + +TEST(InteractiveTestHelperTest, ChecksSessionWithMissingOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addInput("input2"); + session.addOutput("Second line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addInput("input2"); + session.addOutput("Second line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } +} + +TEST(InteractiveTestHelperTest, ChecksSessionWithEquivalentOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n"); + session.addOutput("> "); + session.addInput("input"); + session.addOutput("Second line\n> "); + session.addReadInput(); + session.addOutput("\n"); + session.addOutput(".\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Second line\n"); + session.addOutput("> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } +} + +TEST(InteractiveTestHelperTest, DetectsIncorrectOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Second line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Incorrect line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +TEST(InteractiveTestHelperTest, DetectsMissingOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Second line\n> "); + session.addInput("input2"); + session.addOutput("Third line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addInput("input2"); + session.addOutput("Third line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +TEST(InteractiveTestHelperTest, DetectsMissingFinalOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Second line\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Second line\n> "); + session.addReadInput(); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +TEST(InteractiveTestHelperTest, DetectsExtraOutput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addInput("input2"); + session.addOutput("More output\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addOutput("First line\n> "); + session.addInput("input"); + session.addOutput("Extra output\n> "); + session.addInput("input2"); + session.addOutput("More output\n> "); + session.addReadInput(); + session.addOutput("\n.\n"); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +TEST(InteractiveTestHelperTest, DetectsMissingInput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addInput("input"); + session.addInput("input2"); + session.addReadInput(); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addInputLine("input"); + session.addInputLine("input2"); + session.addReadInput(); + session.addReadInput(); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +TEST(InteractiveTestHelperTest, DetectsExtraInput) +{ + { + InteractiveSession session(gmx::test::erefdataUpdateAll); + session.addInput("input"); + session.addInput("input2"); + session.addReadInput(); + session.run(); + } + { + InteractiveSession session(gmx::test::erefdataCompare); + session.addInputLine("input"); + session.addInputLine("input2"); + session.addReadInput(); + session.addReadInput(); + session.addReadInput(); + session.addReadInput(); + EXPECT_NONFATAL_FAILURE(session.run(), ""); + } +} + +} // namespace diff --git a/src/testutils/testutils-doc.h b/src/testutils/testutils-doc.h index 0188dff2b4..a82192dc4d 100644 --- a/src/testutils/testutils-doc.h +++ b/src/testutils/testutils-doc.h @@ -58,6 +58,10 @@ * functionality for capturing file output (including `stdout`) from code * that uses gmx::FileOutputRedirectorInterface, and checking that output * against reference data. + * - gmx::test::InteractiveTestHelper (in interactivetest.h) provides + * a helper class for testing an interactive session that uses + * gmx::TextInputStream and gmx::TextOutputStream for prompting input and + * printing status messages. * - #GMX_TEST_OPTIONS macro provides facilities for adding custom command * line options for the test binary. * - testasserts.h provides several custom test assertions for better