Sort all includes in src/gromacs
[alexxy/gromacs.git] / src / gromacs / selection / tests / selectioncollection.cpp
index 7596a961077610d88eaf825f3385db78856e6fa7..9e6fd86281b473c7d77929ad4c57422547526ea8 100644 (file)
@@ -1,60 +1,69 @@
 /*
+ * This file is part of the GROMACS molecular simulation package.
  *
- *                This source code is part of
+ * Copyright (c) 2010,2011,2012,2013,2014, 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.
  *
- *                 G   R   O   M   A   C   S
+ * 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.
  *
- *          GROningen MAchine for Chemical Simulations
+ * 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.
  *
- * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
- * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
- * Copyright (c) 2001-2009, The GROMACS development team,
- * check out http://www.gromacs.org for more information.
-
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
+ * 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, 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 www.gromacs.org.
+ * 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 papers on the package - you can find them in the top README file.
- *
- * For more info, check our website at http://www.gromacs.org
+ * the research papers on the package. Check out http://www.gromacs.org.
  */
 /*! \internal \file
  * \brief
  * Tests selection parsing and compilation.
  *
- * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
  * \ingroup module_selection
  */
-#include <gtest/gtest.h>
+#include "gmxpre.h"
 
-#include "gromacs/legacyheaders/smalloc.h"
-#include "gromacs/legacyheaders/statutil.h"
-#include "gromacs/legacyheaders/tpxio.h"
-#include "gromacs/legacyheaders/vec.h"
+#include "gromacs/selection/selectioncollection.h"
+
+#include <gtest/gtest.h>
 
+#include "gromacs/fileio/trx.h"
 #include "gromacs/options/basicoptions.h"
 #include "gromacs/options/options.h"
-#include "gromacs/selection/selectioncollection.h"
+#include "gromacs/selection/indexutil.h"
 #include "gromacs/selection/selection.h"
+#include "gromacs/topology/topology.h"
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/flags.h"
 #include "gromacs/utility/gmxregex.h"
 #include "gromacs/utility/stringutil.h"
 
 #include "testutils/refdata.h"
+#include "testutils/testasserts.h"
 #include "testutils/testfilemanager.h"
 #include "testutils/testoptions.h"
 
+#include "toputils.h"
+
 namespace
 {
 
@@ -65,8 +74,6 @@ namespace
 class SelectionCollectionTest : public ::testing::Test
 {
     public:
-        static void SetUpTestCase();
-
         static int               s_debugLevel;
 
         SelectionCollectionTest();
@@ -74,76 +81,74 @@ class SelectionCollectionTest : public ::testing::Test
 
         void setAtomCount(int natoms)
         {
-            ASSERT_NO_THROW(sc_.setTopology(NULL, natoms));
+            ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, natoms));
         }
         void loadTopology(const char *filename);
-
-        gmx::SelectionCollection sc_;
-        gmx::SelectionList       sel_;
-        t_topology              *top_;
-        t_trxframe              *frame_;
+        void setTopology();
+        void loadIndexGroups(const char *filename);
+
+        gmx::test::TopologyManager  topManager_;
+        gmx::SelectionCollection    sc_;
+        gmx::SelectionList          sel_;
+        t_topology                 *top_;
+        t_trxframe                 *frame_;
+        gmx_ana_indexgrps_t        *grps_;
 };
 
 int SelectionCollectionTest::s_debugLevel = 0;
 
-void SelectionCollectionTest::SetUpTestCase()
+// \cond/\endcond do not seem to work here with Doxygen 1.8.5 parser.
+#ifndef DOXYGEN
+GMX_TEST_OPTIONS(SelectionCollectionTestOptions, options)
 {
-    gmx::Options options(NULL, NULL);
-    options.addOption(gmx::IntegerOption("seldebug").store(&s_debugLevel));
-    gmx::test::parseTestOptions(&options);
+    options->addOption(gmx::IntegerOption("seldebug")
+                           .store(&SelectionCollectionTest::s_debugLevel)
+                           .description("Set selection debug level"));
 }
-
+#endif
 
 SelectionCollectionTest::SelectionCollectionTest()
-    : top_(NULL), frame_(NULL)
+    : top_(NULL), frame_(NULL), grps_(NULL)
 {
+    topManager_.requestFrame();
     sc_.setDebugLevel(s_debugLevel);
     sc_.setReferencePosType("atom");
     sc_.setOutputPosType("atom");
 }
 
-
 SelectionCollectionTest::~SelectionCollectionTest()
 {
-    if (top_ != NULL)
+    if (grps_ != NULL)
     {
-        free_t_atoms(&top_->atoms, TRUE);
-        done_top(top_);
-        sfree(top_);
-    }
-
-    if (frame_ != NULL)
-    {
-        sfree(frame_->x);
-        sfree(frame_);
+        gmx_ana_indexgrps_free(grps_);
     }
 }
 
-
 void
 SelectionCollectionTest::loadTopology(const char *filename)
 {
-    char    title[STRLEN];
-    int     ePBC;
-    rvec   *xtop;
-    matrix  box;
-
-    snew(top_, 1);
-    read_tps_conf(gmx::test::TestFileManager::getInputFilePath(filename).c_str(),
-                  title, top_, &ePBC, &xtop, NULL, box, FALSE);
+    topManager_.loadTopology(filename);
+    setTopology();
+}
 
-    snew(frame_, 1);
-    frame_->flags  = TRX_NEED_X;
-    frame_->natoms = top_->atoms.nr;
-    frame_->bX     = TRUE;
-    snew(frame_->x, frame_->natoms);
-    memcpy(frame_->x, xtop, sizeof(*frame_->x) * frame_->natoms);
-    frame_->bBox   = TRUE;
-    copy_mat(box, frame_->box);
+void
+SelectionCollectionTest::setTopology()
+{
+    top_   = topManager_.topology();
+    frame_ = topManager_.frame();
 
-    sfree(xtop);
+    ASSERT_NO_THROW_GMX(sc_.setTopology(top_, -1));
+}
 
-    ASSERT_NO_THROW(sc_.setTopology(top_, -1));
+void
+SelectionCollectionTest::loadIndexGroups(const char *filename)
+{
+    GMX_RELEASE_ASSERT(grps_ == NULL,
+                       "External groups can only be loaded once");
+    std::string fullpath =
+        gmx::test::TestFileManager::getInputFilePath(filename);
+    gmx_ana_indexgrps_init(&grps_, NULL, fullpath.c_str());
+    sc_.setIndexGroups(grps_);
 }
 
 
@@ -160,6 +165,9 @@ class SelectionCollectionDataTest : public SelectionCollectionTest
             efTestPositionAtoms         = 1<<1,
             efTestPositionCoordinates   = 1<<2,
             efTestPositionMapping       = 1<<3,
+            efTestPositionMasses        = 1<<4,
+            efTestPositionCharges       = 1<<5,
+            efTestSelectionNames        = 1<<6,
             efDontTestCompiledAtoms     = 1<<8
         };
         typedef gmx::FlagsTemplate<TestFlag> TestFlags;
@@ -171,35 +179,27 @@ class SelectionCollectionDataTest : public SelectionCollectionTest
 
         void setFlags(TestFlags flags) { flags_ = flags; }
 
-        void runTest(int natoms, const char *const *selections, size_t count);
-        void runTest(const char *filename, const char *const *selections,
-                     size_t count);
-        template <size_t count>
-        void runTest(int natoms, const char *const (&selections)[count])
-        {
-            runTest(natoms, selections, count);
-        }
-        template <size_t count>
-        void runTest(const char *filename, const char *const (&selections)[count])
-        {
-            runTest(filename, selections, count);
-        }
+        void runParser(const gmx::ConstArrayRef<const char *> &selections);
+        void runCompiler();
+        void runEvaluate();
+        void runEvaluateFinal();
+
+        void runTest(int                                     natoms,
+                     const gmx::ConstArrayRef<const char *> &selections);
+        void runTest(const char                             *filename,
+                     const gmx::ConstArrayRef<const char *> &selections);
 
     private:
         static void checkSelection(gmx::test::TestReferenceChecker *checker,
                                    const gmx::Selection &sel, TestFlags flags);
 
-        void runParser(const char *const *selections, size_t count);
-        void runCompiler();
         void checkCompiled();
-        void runEvaluate();
-        void runEvaluateFinal();
 
-        gmx::test::TestReferenceData  data_;
+        gmx::test::TestReferenceData    data_;
         gmx::test::TestReferenceChecker checker_;
-        size_t                        count_;
-        int                           framenr_;
-        TestFlags                     flags_;
+        size_t                          count_;
+        int                             framenr_;
+        TestFlags                       flags_;
 };
 
 
@@ -216,13 +216,15 @@ SelectionCollectionDataTest::checkSelection(
     }
     if (flags.test(efTestPositionAtoms)
         || flags.test(efTestPositionCoordinates)
-        || flags.test(efTestPositionMapping))
+        || flags.test(efTestPositionMapping)
+        || flags.test(efTestPositionMasses)
+        || flags.test(efTestPositionCharges))
     {
         TestReferenceChecker compound(
                 checker->checkSequenceCompound("Positions", sel.posCount()));
         for (int i = 0; i < sel.posCount(); ++i)
         {
-            TestReferenceChecker poscompound(compound.checkCompound("Position", NULL));
+            TestReferenceChecker          poscompound(compound.checkCompound("Position", NULL));
             const gmx::SelectionPosition &p = sel.position(i);
             if (flags.test(efTestPositionAtoms))
             {
@@ -238,30 +240,38 @@ SelectionCollectionDataTest::checkSelection(
                 poscompound.checkInteger(p.refId(), "RefId");
                 poscompound.checkInteger(p.mappedId(), "MappedId");
             }
+            if (flags.test(efTestPositionMasses))
+            {
+                poscompound.checkReal(p.mass(), "Mass");
+            }
+            if (flags.test(efTestPositionCharges))
+            {
+                poscompound.checkReal(p.charge(), "Charge");
+            }
         }
     }
 }
 
 
 void
-SelectionCollectionDataTest::runParser(const char *const *selections,
-                                       size_t count)
+SelectionCollectionDataTest::runParser(
+        const gmx::ConstArrayRef<const char *> &selections)
 {
     using gmx::test::TestReferenceChecker;
 
     TestReferenceChecker compound(checker_.checkCompound("ParsedSelections", "Parsed"));
-    size_t varcount = 0;
+    size_t               varcount = 0;
     count_ = 0;
-    for (size_t i = 0; i < count; ++i)
+    for (size_t i = 0; i < selections.size(); ++i)
     {
         SCOPED_TRACE(std::string("Parsing selection \"")
                      + selections[i] + "\"");
         gmx::SelectionList result;
-        ASSERT_NO_THROW(result = sc_.parseFromString(selections[i]));
+        ASSERT_NO_THROW_GMX(result = sc_.parseFromString(selections[i]));
         sel_.insert(sel_.end(), result.begin(), result.end());
         if (sel_.size() == count_)
         {
-            std::string id = gmx::formatString("Variable%d", static_cast<int>(varcount + 1));
+            std::string          id = gmx::formatString("Variable%d", static_cast<int>(varcount + 1));
             TestReferenceChecker varcompound(
                     compound.checkCompound("ParsedVariable", id.c_str()));
             varcompound.checkString(selections[i], "Input");
@@ -269,11 +279,14 @@ SelectionCollectionDataTest::runParser(const char *const *selections,
         }
         else
         {
-            std::string id = gmx::formatString("Selection%d", static_cast<int>(count_ + 1));
+            std::string          id = gmx::formatString("Selection%d", static_cast<int>(count_ + 1));
             TestReferenceChecker selcompound(
                     compound.checkCompound("ParsedSelection", id.c_str()));
             selcompound.checkString(selections[i], "Input");
-            selcompound.checkString(sel_[count_].name(), "Name");
+            if (flags_.test(efTestSelectionNames))
+            {
+                selcompound.checkString(sel_[count_].name(), "Name");
+            }
             selcompound.checkString(sel_[count_].selectionText(), "Text");
             selcompound.checkBoolean(sel_[count_].isDynamic(), "Dynamic");
             ++count_;
@@ -285,7 +298,7 @@ SelectionCollectionDataTest::runParser(const char *const *selections,
 void
 SelectionCollectionDataTest::runCompiler()
 {
-    ASSERT_NO_THROW(sc_.compile());
+    ASSERT_NO_THROW_GMX(sc_.compile());
     ASSERT_EQ(count_, sel_.size());
     checkCompiled();
 }
@@ -295,16 +308,20 @@ void
 SelectionCollectionDataTest::checkCompiled()
 {
     using gmx::test::TestReferenceChecker;
-    const TestFlags mask = ~TestFlags(efTestPositionCoordinates);
+    const TestFlags      mask = ~TestFlags(efTestPositionCoordinates);
 
     TestReferenceChecker compound(checker_.checkCompound("CompiledSelections", "Compiled"));
     for (size_t i = 0; i < count_; ++i)
     {
         SCOPED_TRACE(std::string("Checking selection \"") +
                      sel_[i].selectionText() + "\"");
-        std::string id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
+        std::string          id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
         TestReferenceChecker selcompound(
                 compound.checkCompound("Selection", id.c_str()));
+        if (flags_.test(efTestSelectionNames))
+        {
+            selcompound.checkString(sel_[i].name(), "Name");
+        }
         if (!flags_.test(efDontTestCompiledAtoms))
         {
             checkSelection(&selcompound, sel_[i], flags_ & mask);
@@ -319,15 +336,15 @@ SelectionCollectionDataTest::runEvaluate()
     using gmx::test::TestReferenceChecker;
 
     ++framenr_;
-    ASSERT_NO_THROW(sc_.evaluate(frame_, NULL));
-    std::string frame = gmx::formatString("Frame%d", framenr_);
+    ASSERT_NO_THROW_GMX(sc_.evaluate(frame_, NULL));
+    std::string          frame = gmx::formatString("Frame%d", framenr_);
     TestReferenceChecker compound(
             checker_.checkCompound("EvaluatedSelections", frame.c_str()));
     for (size_t i = 0; i < count_; ++i)
     {
         SCOPED_TRACE(std::string("Checking selection \"") +
                      sel_[i].selectionText() + "\"");
-        std::string id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
+        std::string          id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
         TestReferenceChecker selcompound(
                 compound.checkCompound("Selection", id.c_str()));
         checkSelection(&selcompound, sel_[i], flags_);
@@ -338,30 +355,26 @@ SelectionCollectionDataTest::runEvaluate()
 void
 SelectionCollectionDataTest::runEvaluateFinal()
 {
-    ASSERT_NO_THROW(sc_.evaluateFinal(framenr_));
-    if (!checker_.isWriteMode())
-    {
-        checkCompiled();
-    }
+    ASSERT_NO_THROW_GMX(sc_.evaluateFinal(framenr_));
+    checkCompiled();
 }
 
 
 void
-SelectionCollectionDataTest::runTest(int natoms, const char * const *selections,
-                                     size_t count)
+SelectionCollectionDataTest::runTest(
+        int natoms, const gmx::ConstArrayRef<const char *> &selections)
 {
-    ASSERT_NO_FATAL_FAILURE(runParser(selections, count));
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
     ASSERT_NO_FATAL_FAILURE(setAtomCount(natoms));
     ASSERT_NO_FATAL_FAILURE(runCompiler());
 }
 
 
 void
-SelectionCollectionDataTest::runTest(const char *filename,
-                                     const char * const *selections,
-                                     size_t count)
+SelectionCollectionDataTest::runTest(
+        const char *filename, const gmx::ConstArrayRef<const char *> &selections)
 {
-    ASSERT_NO_FATAL_FAILURE(runParser(selections, count));
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
     ASSERT_NO_FATAL_FAILURE(loadTopology(filename));
     ASSERT_NO_FATAL_FAILURE(runCompiler());
     if (flags_.test(efTestEvaluation))
@@ -379,26 +392,51 @@ SelectionCollectionDataTest::runTest(const char *filename,
 TEST_F(SelectionCollectionTest, HandlesNoSelections)
 {
     EXPECT_FALSE(sc_.requiresTopology());
-    EXPECT_NO_THROW(sc_.compile());
+    EXPECT_NO_THROW_GMX(sc_.compile());
+}
+
+TEST_F(SelectionCollectionTest, HandlesVelocityAndForceRequests)
+{
+    ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr 1 to 10; none"));
+    ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
+    ASSERT_EQ(2U, sel_.size());
+    ASSERT_NO_THROW_GMX(sel_[0].setEvaluateVelocities(true));
+    ASSERT_NO_THROW_GMX(sel_[1].setEvaluateVelocities(true));
+    ASSERT_NO_THROW_GMX(sel_[0].setEvaluateForces(true));
+    ASSERT_NO_THROW_GMX(sel_[1].setEvaluateForces(true));
+    ASSERT_NO_THROW_GMX(sc_.compile());
+    EXPECT_TRUE(sel_[0].hasVelocities());
+    EXPECT_TRUE(sel_[1].hasVelocities());
+    EXPECT_TRUE(sel_[0].hasForces());
+    EXPECT_TRUE(sel_[1].hasForces());
 }
 
 TEST_F(SelectionCollectionTest, ParsesSelectionsFromFile)
 {
-    ASSERT_NO_THROW(sel_ = sc_.parseFromFile(
-                gmx::test::TestFileManager::getInputFilePath("selfile.dat")));
+    ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromFile(
+                                    gmx::test::TestFileManager::getInputFilePath("selfile.dat")));
     // These should match the contents of selfile.dat
     ASSERT_EQ(2U, sel_.size());
     EXPECT_STREQ("resname RA RB", sel_[0].selectionText());
     EXPECT_STREQ("resname RB RC", sel_[1].selectionText());
 }
 
+TEST_F(SelectionCollectionTest, HandlesAtypicalWhitespace)
+{
+    ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr\n1\r\nto\t10;\vatomnr 3\f to 14\r"));
+    ASSERT_EQ(2U, sel_.size());
+    EXPECT_STREQ("atomnr 1 to 10", sel_[0].selectionText());
+    // TODO: Get rid of the trailing whitespace.
+    EXPECT_STREQ("atomnr 3 to 14 ", sel_[1].selectionText());
+}
+
 TEST_F(SelectionCollectionTest, HandlesInvalidRegularExpressions)
 {
     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-    EXPECT_THROW({
-            sc_.parseFromString("resname ~ \"R[A\"");
-            sc_.compile();
-        }, gmx::InvalidInputError);
+    EXPECT_THROW_GMX({
+                         sc_.parseFromString("resname ~ \"R[A\"");
+                         sc_.compile();
+                     }, gmx::InvalidInputError);
 }
 
 TEST_F(SelectionCollectionTest, HandlesUnsupportedRegularExpressions)
@@ -406,91 +444,178 @@ TEST_F(SelectionCollectionTest, HandlesUnsupportedRegularExpressions)
     if (!gmx::Regex::isSupported())
     {
         ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-        EXPECT_THROW({
-                sc_.parseFromString("resname \"R[AD]\"");
-                sc_.compile();
-            }, gmx::InvalidInputError);
+        EXPECT_THROW_GMX({
+                             sc_.parseFromString("resname \"R[AD]\"");
+                             sc_.compile();
+                         }, gmx::InvalidInputError);
     }
 }
 
 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue)
 {
-    EXPECT_THROW(sc_.parseFromString("mindist from atomnr 1 cutoff"),
-                 gmx::InvalidInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("mindist from atomnr 1 cutoff"),
+                     gmx::InvalidInputError);
 }
 
 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue2)
 {
-    EXPECT_THROW(sc_.parseFromString("within 1 of"),
-                 gmx::InvalidInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("within 1 of"),
+                     gmx::InvalidInputError);
 }
 
 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue3)
 {
-    EXPECT_THROW(sc_.parseFromString("within of atomnr 1"),
-                 gmx::InvalidInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("within of atomnr 1"),
+                     gmx::InvalidInputError);
 }
 
-TEST_F(SelectionCollectionTest, HandlesHelpKeywordInInvalidContext)
+// TODO: Tests for more parser errors
+
+TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser1)
 {
-    EXPECT_THROW(sc_.parseFromString("resname help"),
-                 gmx::InvalidInputError);
+    ASSERT_NO_THROW_GMX(sc_.setIndexGroups(NULL));
+    EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
 }
 
-// TODO: Tests for more parser errors
+TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser2)
+{
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
+}
 
-TEST_F(SelectionCollectionTest, RecoversFromUnknownGroupReference)
+TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed1)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("group \"foo\""));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"foo\""));
     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
-    EXPECT_THROW(sc_.setIndexGroups(NULL), gmx::InvalidInputError);
-    EXPECT_THROW(sc_.compile(), gmx::APIError);
+    EXPECT_THROW_GMX(sc_.setIndexGroups(NULL), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed2)
+{
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("group 4; group \"foo\""));
+    ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
+    EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReference)
+{
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    EXPECT_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""),
+                     gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("group 2 or atomnr 2 to 5"),
+                     gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.parseFromString("within 1 of group 2"),
+                     gmx::InconsistentInputError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReferenceDelayed)
+{
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group 2"));
+    EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
+    // TODO: Add a separate check in the selection compiler for a safer API
+    // (makes sense in the future if the compiler needs the information for
+    // other purposes as well).
+    // EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroup)
+{
+    ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, 5));
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    EXPECT_THROW_GMX(sc_.parseFromString("group \"GrpB\""), gmx::InconsistentInputError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed)
+{
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
+    EXPECT_THROW_GMX(sc_.setTopology(NULL, 5), gmx::InconsistentInputError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed2)
+{
+    ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, 5));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
+    EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromMissingMoleculeInfo)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("molindex 1 to 5"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("molindex 1 to 5"));
     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-    EXPECT_THROW(sc_.compile(), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromMissingAtomTypes)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("type CA"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("type CA"));
     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-    EXPECT_THROW(sc_.compile(), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromMissingPDBInfo)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("altloc A"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("altloc A"));
     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-    EXPECT_THROW(sc_.compile(), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("all permute 1 1"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 1 1"));
     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
-    EXPECT_THROW(sc_.compile(), gmx::InvalidInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::InvalidInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation2)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("all permute 3 2 1"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 3 2 1"));
     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
-    EXPECT_THROW(sc_.compile(), gmx::InconsistentInputError);
+    EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
 }
 
 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation3)
 {
-    ASSERT_NO_THROW(sc_.parseFromString("x < 1.5 permute 3 2 1"));
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("x < 1.5 permute 3 2 1"));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    ASSERT_NO_THROW_GMX(sc_.compile());
+    EXPECT_THROW_GMX(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets)
+{
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 3 to 10"));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    ASSERT_NO_THROW_GMX(sc_.compile());
+    frame_->natoms = 8;
+    EXPECT_THROW_GMX(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
+}
+
+TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets2)
+{
+    // Evaluating the positions will require atoms 1-9.
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("whole_res_com of atomnr 2 5 7"));
     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
-    ASSERT_NO_THROW(sc_.compile());
-    EXPECT_THROW(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
+    ASSERT_NO_THROW_GMX(sc_.compile());
+    frame_->natoms = 8;
+    EXPECT_THROW_GMX(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
 }
 
-// TODO: Tests for evaluation errors
+TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets3)
+{
+    ASSERT_NO_THROW_GMX(sc_.parseFromString("mindistance from atomnr 1 to 5 < 2"));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    ASSERT_NO_THROW_GMX(sc_.compile());
+    frame_->natoms = 10;
+    EXPECT_THROW_GMX(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
+}
+
+// TODO: Tests for more evaluation errors
 
 
 /********************************************************************
@@ -534,7 +659,17 @@ TEST_F(SelectionCollectionDataTest, HandlesResIndex)
     runTest("simple.pdb", selections);
 }
 
-// TODO: Add test for "molindex"
+TEST_F(SelectionCollectionDataTest, HandlesMolIndex)
+{
+    static const char * const selections[] = {
+        "molindex 1 4",
+        "molecule 2 3 5"
+    };
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    topManager_.initUniformMolecules(3);
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
 
 TEST_F(SelectionCollectionDataTest, HandlesAtomname)
 {
@@ -556,7 +691,18 @@ TEST_F(SelectionCollectionDataTest, HandlesPdbAtomname)
     runTest("simple.pdb", selections);
 }
 
-// TODO: Add test for atomtype
+
+TEST_F(SelectionCollectionDataTest, HandlesAtomtype)
+{
+    static const char * const selections[] = {
+        "atomtype CA"
+    };
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    const char *const types[] = { "CA", "SA", "SB" };
+    topManager_.initAtomTypes(types);
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
 
 TEST_F(SelectionCollectionDataTest, HandlesChain)
 {
@@ -567,8 +713,33 @@ TEST_F(SelectionCollectionDataTest, HandlesChain)
     runTest("simple.pdb", selections);
 }
 
-// TODO: Add test for mass
-// TODO: Add test for charge
+TEST_F(SelectionCollectionDataTest, HandlesMass)
+{
+    static const char * const selections[] = {
+        "mass > 5"
+    };
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    for (int i = 0; i < top_->atoms.nr; ++i)
+    {
+        top_->atoms.atom[i].m = 1.0 + i;
+    }
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
+
+TEST_F(SelectionCollectionDataTest, HandlesCharge)
+{
+    static const char * const selections[] = {
+        "charge < 0.5"
+    };
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    for (int i = 0; i < top_->atoms.nr; ++i)
+    {
+        top_->atoms.atom[i].q = i / 10.0;
+    }
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
 
 TEST_F(SelectionCollectionDataTest, HandlesAltLoc)
 {
@@ -741,10 +912,122 @@ TEST_F(SelectionCollectionDataTest, HandlesMergeModifier)
 }
 
 
+/********************************************************************
+ * Tests for generic selection evaluation
+ */
+
+TEST_F(SelectionCollectionDataTest, ComputesMassesAndCharges)
+{
+    static const char * const selections[] = {
+        "name CB",
+        "y > 2",
+        "res_cog of y > 2"
+    };
+    setFlags(TestFlags() | efTestEvaluation | efTestPositionAtoms
+             | efTestPositionMasses | efTestPositionCharges);
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    for (int i = 0; i < top_->atoms.nr; ++i)
+    {
+        top_->atoms.atom[i].m =   1.0 + i / 100.0;
+        top_->atoms.atom[i].q = -(1.0 + i / 100.0);
+    }
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+    ASSERT_NO_FATAL_FAILURE(runEvaluate());
+    ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
+}
+
+TEST_F(SelectionCollectionDataTest, ComputesMassesAndChargesWithoutTopology)
+{
+    static const char * const selections[] = {
+        "atomnr 1 to 3 8 to 9",
+        "y > 2",
+        "cog of (y > 2)"
+    };
+    setFlags(TestFlags() | efTestPositionAtoms
+             | efTestPositionMasses | efTestPositionCharges);
+    runTest(10, selections);
+}
+
+
 /********************************************************************
  * Tests for selection syntactic constructs
  */
 
+TEST_F(SelectionCollectionDataTest, HandlesSelectionNames)
+{
+    static const char * const selections[] = {
+        "\"GroupSelection\" group \"GrpA\"",
+        "\"DynamicSelection\" x < 5",
+        "y < 3"
+    };
+    setFlags(TestFlags() | efTestSelectionNames);
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    runTest(10, selections);
+}
+
+TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelections)
+{
+    static const char * const selections[] = {
+        "group \"GrpA\"",
+        "GrpB",
+        "1",
+        "group \"GrpB\" and resname RB"
+    };
+    setFlags(TestFlags() | efTestSelectionNames);
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    runTest("simple.gro", selections);
+}
+
+TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelectionsDelayed)
+{
+    static const char * const selections[] = {
+        "group \"GrpA\"",
+        "GrpB",
+        "1",
+        "group \"GrpB\" and resname RB"
+    };
+    setFlags(TestFlags() | efTestSelectionNames);
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
+
+TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelections)
+{
+    static const char * const selections[] = {
+        "foo = group \"GrpUnsorted\"",
+        "group \"GrpUnsorted\"",
+        "GrpUnsorted",
+        "2",
+        "res_cog of group \"GrpUnsorted\"",
+        "group \"GrpUnsorted\" permute 2 1",
+        "foo"
+    };
+    setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMapping
+             | efTestSelectionNames);
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    runTest("simple.gro", selections);
+}
+
+TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelectionsDelayed)
+{
+    static const char * const selections[] = {
+        "foo = group \"GrpUnsorted\"",
+        "group \"GrpUnsorted\"",
+        "GrpUnsorted",
+        "2",
+        "res_cog of group \"GrpUnsorted\"",
+        "group \"GrpUnsorted\" permute 2 1",
+        "foo"
+    };
+    ASSERT_NO_FATAL_FAILURE(runParser(selections));
+    ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+    ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+    ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
+
 TEST_F(SelectionCollectionDataTest, HandlesConstantPositions)
 {
     static const char * const selections[] = {
@@ -811,6 +1094,27 @@ TEST_F(SelectionCollectionDataTest, HandlesBasicBoolean)
 }
 
 
+TEST_F(SelectionCollectionDataTest, HandlesDynamicAtomValuedParameters)
+{
+    static const char * const selections[] = {
+        "same residue as (atomnr 3 5 13 or y > 5)",
+        "(resnr 1 3 5 or x > 10) and same residue as (atomnr 3 5 13 or z > 5)"
+    };
+    setFlags(TestFlags() | efTestEvaluation);
+    runTest("simple.gro", selections);
+}
+
+
+TEST_F(SelectionCollectionDataTest, HandlesEmptySelectionWithUnevaluatedExpressions)
+{
+    static const char * const selections[] = {
+        "none and x > 2",
+        "none and same resname as resnr 2"
+    };
+    runTest("simple.gro", selections);
+}
+
+
 TEST_F(SelectionCollectionDataTest, HandlesNumericComparisons)
 {
     static const char * const selections[] = {
@@ -889,7 +1193,7 @@ TEST_F(SelectionCollectionDataTest, HandlesConstantPositionInVariable)
         "within 2 of constpos"
     };
     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
-            | efTestPositionAtoms);
+             | efTestPositionAtoms);
     runTest("simple.gro", selections);
 }
 
@@ -993,4 +1297,16 @@ TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups)
 }
 
 
+TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups2)
+{
+    static const char * const selections[] = {
+        "foo = atomnr 1 to 8 and x < 10",
+        "atomnr 1 to 5 and y < 10 and foo",
+        "foo"
+    };
+    setFlags(TestFlags() | efTestEvaluation);
+    runTest("simple.gro", selections);
+}
+
+
 } // namespace