Tests for interactive selection input
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 27 Jun 2015 17:39:33 +0000 (20:39 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Sat, 4 Jul 2015 17:50:27 +0000 (20:50 +0300)
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

27 files changed:
src/gromacs/analysisdata/tests/refdata/common-referencedata.xsl
src/gromacs/selection/selectioncollection.cpp
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesBasicInput.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml [new file with mode: 0644]
src/gromacs/selection/tests/refdata/common-referencedata.xsl
src/gromacs/selection/tests/refdata/referencedata.xsl
src/gromacs/selection/tests/selectioncollection.cpp
src/gromacs/trajectoryanalysis/tests/refdata/common-referencedata.xsl
src/testutils/CMakeLists.txt
src/testutils/common-referencedata.xsl
src/testutils/interactivetest.cpp [new file with mode: 0644]
src/testutils/interactivetest.h [new file with mode: 0644]
src/testutils/tests/CMakeLists.txt
src/testutils/tests/interactivetest.cpp [new file with mode: 0644]
src/testutils/testutils-doc.h

index 1ae38d84525b8dfc62b0f5178ad9305cb3948268..b6e9bcfdb67f16b17e6b79715d4980024cf82b08 100644 (file)
@@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations.
     <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>&#x25ba;</xsl:text>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:when>
+                <xsl:when test="contains(substring(.,2), '&#10;')">
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="translate(substring(.,2), '&#10;', '&#x23ce;')"/>
+                    <xsl:text>&#10;</xsl:text>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="substring(.,2)"/>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:for-each>
+        <xsl:text>[EOF]</xsl:text>
+    </pre>
+</xsl:template>
+
 </xsl:stylesheet>
index bbe3f11df267e7b05191d1c847b3823bfb9ab351..d124cf2800bcc15109af43e2dcc3f4caa375b1bf 100644 (file)
@@ -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 (file)
index 0000000..b6dab1a
--- /dev/null
@@ -0,0 +1,30 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesContinuation.xml
new file mode 100644 (file)
index 0000000..55bba6e
--- /dev/null
@@ -0,0 +1,23 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesEmptySelections.xml
new file mode 100644 (file)
index 0000000..3f50325
--- /dev/null
@@ -0,0 +1,39 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultiSelectionInputStatus.xml
new file mode 100644 (file)
index 0000000..8a1d9bf
--- /dev/null
@@ -0,0 +1,34 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesMultipleSelectionsOnLine.xml
new file mode 100644 (file)
index 0000000..6f4135d
--- /dev/null
@@ -0,0 +1,22 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoFinalNewline.xml
new file mode 100644 (file)
index 0000000..9555a9b
--- /dev/null
@@ -0,0 +1,18 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesNoninteractiveInput.xml
new file mode 100644 (file)
index 0000000..515754e
--- /dev/null
@@ -0,0 +1,17 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInput.xml
new file mode 100644 (file)
index 0000000..7e469d3
--- /dev/null
@@ -0,0 +1,22 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputNoninteractively.xml
new file mode 100644 (file)
index 0000000..32abef4
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesSingleSelectionInputStatus.xml
new file mode 100644 (file)
index 0000000..1e83b16
--- /dev/null
@@ -0,0 +1,32 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithExistingSelections.xml
new file mode 100644 (file)
index 0000000..fc32bf7
--- /dev/null
@@ -0,0 +1,44 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesStatusWithGroups.xml
new file mode 100644 (file)
index 0000000..1bc4c4e
--- /dev/null
@@ -0,0 +1,35 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInput.xml
new file mode 100644 (file)
index 0000000..cef23d4
--- /dev/null
@@ -0,0 +1,22 @@
+<?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>
diff --git a/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml b/src/gromacs/selection/tests/refdata/SelectionCollectionInteractiveTest_HandlesTwoSelectionInputStatus.xml
new file mode 100644 (file)
index 0000000..2f77680
--- /dev/null
@@ -0,0 +1,32 @@
+<?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>
index 1ae38d84525b8dfc62b0f5178ad9305cb3948268..b6e9bcfdb67f16b17e6b79715d4980024cf82b08 100644 (file)
@@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations.
     <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>&#x25ba;</xsl:text>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:when>
+                <xsl:when test="contains(substring(.,2), '&#10;')">
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="translate(substring(.,2), '&#10;', '&#x23ce;')"/>
+                    <xsl:text>&#10;</xsl:text>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="substring(.,2)"/>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:for-each>
+        <xsl:text>[EOF]</xsl:text>
+    </pre>
+</xsl:template>
+
 </xsl:stylesheet>
index d26d2e2b18ff1d13c151e92af80d45a3e50887e4..57048feab7ecfbd598cf2b7aa52d163c550c5929 100644 (file)
 
 <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">
index c04539a03fe54ac7c4631ef734fe1af79824c7e9..436abee87dafae069916d136e170ff7adefb6a08 100644 (file)
@@ -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<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
  */
@@ -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
index 1ae38d84525b8dfc62b0f5178ad9305cb3948268..b6e9bcfdb67f16b17e6b79715d4980024cf82b08 100644 (file)
@@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations.
     <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>&#x25ba;</xsl:text>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:when>
+                <xsl:when test="contains(substring(.,2), '&#10;')">
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="translate(substring(.,2), '&#10;', '&#x23ce;')"/>
+                    <xsl:text>&#10;</xsl:text>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="substring(.,2)"/>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:for-each>
+        <xsl:text>[EOF]</xsl:text>
+    </pre>
+</xsl:template>
+
 </xsl:stylesheet>
index 131d9351f5de5ed471f404d874e6d7fa806807b4..a2a5cdde513b344965bc02c57a480a67d217da30 100644 (file)
@@ -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
index 1ae38d84525b8dfc62b0f5178ad9305cb3948268..b6e9bcfdb67f16b17e6b79715d4980024cf82b08 100644 (file)
@@ -68,4 +68,31 @@ and use the copy_xsl.sh script to copy it to relevant locations.
     <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>&#x25ba;</xsl:text>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:when>
+                <xsl:when test="contains(substring(.,2), '&#10;')">
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="translate(substring(.,2), '&#10;', '&#x23ce;')"/>
+                    <xsl:text>&#10;</xsl:text>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:text>&#x25ba;</xsl:text>
+                    <xsl:value-of select="substring(.,2)"/>
+                    <xsl:text>&#xb6;</xsl:text>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:for-each>
+        <xsl:text>[EOF]</xsl:text>
+    </pre>
+</xsl:template>
+
 </xsl:stylesheet>
diff --git a/src/testutils/interactivetest.cpp b/src/testutils/interactivetest.cpp
new file mode 100644 (file)
index 0000000..ce8c854
--- /dev/null
@@ -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 <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
diff --git a/src/testutils/interactivetest.h b/src/testutils/interactivetest.h
new file mode 100644 (file)
index 0000000..66167f1
--- /dev/null
@@ -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 <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
index 045e7c5616b2e4ace992cdb142f168d2f0d3db8f..351584dab7ea6fa08b6fb86b64cc3588ca669f93 100644 (file)
@@ -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 (file)
index 0000000..4a3d390
--- /dev/null
@@ -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 <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
index 0188dff2b4e793e335e4d400daadd318cd3b90d4..a82192dc4dca93c0ad47c63ad3ecbaf33bd9f114 100644 (file)
  *    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