<xsl:value-of select="."/>
</xsl:template>
+<xsl:template match="InteractiveSession">
+ <pre>
+ <xsl:for-each select="*">
+ <xsl:choose>
+ <xsl:when test="starts-with(@Name, 'Output')">
+ <xsl:value-of select="substring(.,2)"/>
+ </xsl:when>
+ <xsl:when test="string-length(.)=1">
+ <xsl:text>►</xsl:text>
+ <xsl:text>¶</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(substring(.,2), ' ')">
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="translate(substring(.,2), ' ', '⏎')"/>
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="substring(.,2)"/>
+ <xsl:text>¶</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>[EOF]</xsl:text>
+ </pre>
+</xsl:template>
+
</xsl:stylesheet>
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);
}
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+foo = resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Variable 'foo = resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+resname RB
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RB' parsed
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+"Name" resname RC
+]]></String>
+ <String Name="Output3"><![CDATA[
+Selection '"Name" resname RC' parsed
+> ]]></String>
+ <String Name="Input4"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RB and \
+]]></String>
+ <String Name="Output1"><![CDATA[
+... ]]></String>
+ <String Name="Input2"><![CDATA[
+resname RC
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RB and resname RC' parsed
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RA;
+]]></String>
+ <String Name="Output1"><![CDATA[
+Selection 'resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+; resname RB;;
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RB' parsed
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+
+]]></String>
+ <String Name="Output3"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+Currently provided selections:
+ 1. resname RA
+ 2. resname RB
+> ]]></String>
+ <String Name="Input4"><![CDATA[
+;
+]]></String>
+ <String Name="Output4"><![CDATA[
+> ]]></String>
+ <String Name="Input5"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+"Sel" resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Selection '"Sel" resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+"Sel2" resname RB
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection '"Sel2" resname RB' parsed
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+
+]]></String>
+ <String Name="Output3"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+Currently provided selections:
+ 1. "Sel" resname RA
+ 2. "Sel2" resname RB
+> ]]></String>
+ <String Name="Input4"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify 2 selections for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RA; resname RB and \
+]]></String>
+ <String Name="Output1"><![CDATA[
+... ]]></String>
+ <String Name="Input2"><![CDATA[
+resname RC
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RA' parsed
+Selection 'resname RB and resname RC' parsed
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RA]]></String>
+ <String Name="Output1"><![CDATA[
+
+Selection 'resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Input1"><![CDATA[
+foo = resname RA
+]]></String>
+ <String Name="Input2"><![CDATA[
+resname RB
+]]></String>
+ <String Name="Input3"><![CDATA[
+"Name" resname RC
+]]></String>
+ <String Name="Input4"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify a selection for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+foo = resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Variable 'foo = resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+resname RA
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RA' parsed
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Input1"><![CDATA[
+foo = resname RA
+]]></String>
+ <String Name="Input2"><![CDATA[
+resname RA
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify a selection for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+foo = resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Variable 'foo = resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+
+]]></String>
+ <String Name="Output2"><![CDATA[
+Specify a selection for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+Currently provided selections:
+ foo = resname RA
+(1 more selection required)
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+resname RB
+]]></String>
+ <String Name="Output3"><![CDATA[
+Selection 'resname RB' parsed
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+
+]]></String>
+ <String Name="Output1"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+Currently provided selections:
+ foo = resname RA
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+bar = resname RC
+]]></String>
+ <String Name="Output2"><![CDATA[
+Variable 'bar = resname RC' parsed
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+resname RA
+]]></String>
+ <String Name="Output3"><![CDATA[
+Selection 'resname RA' parsed
+> ]]></String>
+ <String Name="Input4"><![CDATA[
+
+]]></String>
+ <String Name="Output4"><![CDATA[
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+Currently provided selections:
+ foo = resname RA
+ bar = resname RC
+ 1. resname RA
+> ]]></String>
+ <String Name="Input5"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Available static index groups:
+ Group 0 "GrpA" (5 atoms)
+ Group 1 "GrpB" (5 atoms)
+ Group 2 "GrpUnsorted" (8 atoms)
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Selection 'resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+
+]]></String>
+ <String Name="Output2"><![CDATA[
+Available static index groups:
+ Group 0 "GrpA" (5 atoms)
+ Group 1 "GrpB" (5 atoms)
+ Group 2 "GrpUnsorted" (8 atoms)
+Specify any number of selections for test context:
+(one per line, <enter> for status/groups, 'help' for help, Ctrl-D to end)
+Currently provided selections:
+ 1. resname RA
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify 2 selections for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Selection 'resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+resname RB
+]]></String>
+ <String Name="Output2"><![CDATA[
+Selection 'resname RB' parsed
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <InteractiveSession Name="Interactive">
+ <String Name="Output0"><![CDATA[
+Specify 2 selections for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+> ]]></String>
+ <String Name="Input1"><![CDATA[
+"Sel" resname RA
+]]></String>
+ <String Name="Output1"><![CDATA[
+Selection '"Sel" resname RA' parsed
+> ]]></String>
+ <String Name="Input2"><![CDATA[
+
+]]></String>
+ <String Name="Output2"><![CDATA[
+Specify 2 selections for test context:
+(one per line, <enter> for status/groups, 'help' for help)
+Currently provided selections:
+ 1. "Sel" resname RA
+(1 more selection required)
+> ]]></String>
+ <String Name="Input3"><![CDATA[
+resname RB
+]]></String>
+ <String Name="Output3"><![CDATA[
+Selection 'resname RB' parsed
+]]></String>
+ </InteractiveSession>
+</ReferenceData>
<xsl:value-of select="."/>
</xsl:template>
+<xsl:template match="InteractiveSession">
+ <pre>
+ <xsl:for-each select="*">
+ <xsl:choose>
+ <xsl:when test="starts-with(@Name, 'Output')">
+ <xsl:value-of select="substring(.,2)"/>
+ </xsl:when>
+ <xsl:when test="string-length(.)=1">
+ <xsl:text>►</xsl:text>
+ <xsl:text>¶</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(substring(.,2), ' ')">
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="translate(substring(.,2), ' ', '⏎')"/>
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="substring(.,2)"/>
+ <xsl:text>¶</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>[EOF]</xsl:text>
+ </pre>
+</xsl:template>
+
</xsl:stylesheet>
<xsl:key name="SelectionName" match="ParsedSelections/ParsedSelection" use="@Name"/>
+<xsl:template match="InteractiveSession">
+ <h2>Interactive Session</h2>
+ <xsl:apply-imports />
+</xsl:template>
+
<xsl:template match="ParsedSelections">
<h2>Parsed Selections</h2>
<table border="1">
#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"
}
+/********************************************************************
+ * Test fixture for interactive SelectionCollection tests
+ */
+
+class SelectionCollectionInteractiveTest : public SelectionCollectionTest
+{
+ public:
+ SelectionCollectionInteractiveTest()
+ : helper_(data_.rootChecker())
+ {
+ }
+
+ void runTest(int count, bool bInteractive,
+ const gmx::ConstArrayRef<const char *> &input);
+
+ gmx::test::TestReferenceData data_;
+ gmx::test::InteractiveTestHelper helper_;
+};
+
+void SelectionCollectionInteractiveTest::runTest(
+ int count, bool bInteractive,
+ const gmx::ConstArrayRef<const char *> &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
*/
// 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
<xsl:value-of select="."/>
</xsl:template>
+<xsl:template match="InteractiveSession">
+ <pre>
+ <xsl:for-each select="*">
+ <xsl:choose>
+ <xsl:when test="starts-with(@Name, 'Output')">
+ <xsl:value-of select="substring(.,2)"/>
+ </xsl:when>
+ <xsl:when test="string-length(.)=1">
+ <xsl:text>►</xsl:text>
+ <xsl:text>¶</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(substring(.,2), ' ')">
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="translate(substring(.,2), ' ', '⏎')"/>
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="substring(.,2)"/>
+ <xsl:text>¶</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>[EOF]</xsl:text>
+ </pre>
+</xsl:template>
+
</xsl:stylesheet>
set(TESTUTILS_SOURCES
cmdlinetest.cpp
integrationtests.cpp
+ interactivetest.cpp
mpi-printer.cpp
refdata.cpp
stringtest.cpp
<xsl:value-of select="."/>
</xsl:template>
+<xsl:template match="InteractiveSession">
+ <pre>
+ <xsl:for-each select="*">
+ <xsl:choose>
+ <xsl:when test="starts-with(@Name, 'Output')">
+ <xsl:value-of select="substring(.,2)"/>
+ </xsl:when>
+ <xsl:when test="string-length(.)=1">
+ <xsl:text>►</xsl:text>
+ <xsl:text>¶</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(substring(.,2), ' ')">
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="translate(substring(.,2), ' ', '⏎')"/>
+ <xsl:text> </xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>►</xsl:text>
+ <xsl:value-of select="substring(.,2)"/>
+ <xsl:text>¶</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ <xsl:text>[EOF]</xsl:text>
+ </pre>
+</xsl:template>
+
</xsl:stylesheet>
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \ingroup module_testutils
+ */
+#include "gmxpre.h"
+
+#include "interactivetest.h"
+
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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<int>(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<int>(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<int>(currentLine_+1));
+ checker_.checkPresent(false, id.c_str());
+ }
+
+ TestReferenceChecker checker_;
+ ConstArrayRef<const char *> 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<const char *> &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
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \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<const char *> &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> impl_;
+};
+} // namespace test
+} // namespace gmx
+
+#endif
#
# 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.
# 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)
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \ingroup module_testutils
+ */
+#include "gmxpre.h"
+
+#include "testutils/interactivetest.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <gtest/gtest-spi.h>
+
+#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<Event>::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<EventType, const char *> Event;
+
+ gmx::test::TestReferenceData data_;
+ gmx::test::InteractiveTestHelper helper_;
+ std::vector<const char *> inputLines_;
+ size_t nextInputLine_;
+ std::vector<Event> 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
* 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