: flags_(settings.flags_ | staticFlags),
minValueCount_(settings.minValueCount_),
maxValueCount_(settings.maxValueCount_),
- inSet_(false)
+ bInSet_(false), bSetValuesHadErrors_(false)
{
// Check that user has not provided incorrect values for vectors.
if (hasFlag(efOption_Vector) && (minValueCount_ > 1 || maxValueCount_ < 1))
void AbstractOptionStorage::startSet()
{
- GMX_RELEASE_ASSERT(!inSet_, "finishSet() not called");
+ GMX_RELEASE_ASSERT(!bInSet_, "finishSet() not called");
// The last condition takes care of the situation where multiple
// sources are used, and a later source should be able to reassign
// the value even though the option is already set.
GMX_THROW(InvalidInputError("Option specified multiple times"));
}
clearSet();
- inSet_ = true;
+ bInSet_ = true;
+ bSetValuesHadErrors_ = false;
}
void AbstractOptionStorage::appendValue(const std::string &value)
{
- GMX_RELEASE_ASSERT(inSet_, "startSet() not called");
- convertValue(value);
+ GMX_RELEASE_ASSERT(bInSet_, "startSet() not called");
+ try
+ {
+ convertValue(value);
+ }
+ catch (...)
+ {
+ bSetValuesHadErrors_ = true;
+ throw;
+ }
}
void AbstractOptionStorage::finishSet()
{
- GMX_RELEASE_ASSERT(inSet_, "startSet() not called");
- inSet_ = false;
- // TODO: Should this be set or not when processSet() throws?
+ GMX_RELEASE_ASSERT(bInSet_, "startSet() not called");
+ bInSet_ = false;
+ // We mark the option as set even when there are errors to avoid additional
+ // errors from required options not set.
+ // TODO: There could be a separate flag for this purpose.
setFlag(efOption_Set);
- // TODO: Correct handling of the efOption_ClearOnNextSet requires
- // processSet() and/or convertValue() to check it internally.
- // OptionStorageTemplate takes care of it, but it's error-prone if
- // a custom option is implemented that doesn't use it.
- processSet();
+ if (!bSetValuesHadErrors_)
+ {
+ // TODO: Correct handling of the efOption_ClearOnNextSet requires
+ // processSet() and/or convertValue() to check it internally.
+ // OptionStorageTemplate takes care of it, but it's error-prone if
+ // a custom option is implemented that doesn't use it.
+ processSet();
+ }
+ bSetValuesHadErrors_ = false;
clearFlag(efOption_ClearOnNextSet);
+ clearSet();
}
void AbstractOptionStorage::finish()
{
- GMX_RELEASE_ASSERT(!inSet_, "finishSet() not called");
+ GMX_RELEASE_ASSERT(!bInSet_, "finishSet() not called");
processAll();
if (isRequired() && !isSet())
{
*
* This method may be called multiple times if the underlying option
* can be specified multiple times.
+ * This method is not currently called if one of the convertValue()
+ * calls throwed.
+ *
+ * \todo
+ * Improve the call semantics.
*
* \see OptionStorageTemplate::processSetValues()
*/
//! Maximum allowed number of values (in one set), or -1 if no limit.
int maxValueCount_;
//! Whether we are currently assigning values to a set.
- bool inSet_;
+ bool bInSet_;
+ //! Whether there were errors in set values.
+ bool bSetValuesHadErrors_;
GMX_DISALLOW_COPY_AND_ASSIGN(AbstractOptionStorage);
};
*/
#include "gromacs/options/basicoptions.h"
+#include <cerrno>
#include <cstdio>
#include <cstdlib>
+#include <limits>
#include <string>
#include <vector>
{
const char *ptr = value.c_str();
char *endptr;
+ errno = 0;
long int ival = std::strtol(ptr, &endptr, 10);
- if (*endptr != '\0')
+ if (errno == ERANGE
+ || ival < std::numeric_limits<int>::min()
+ || ival > std::numeric_limits<int>::max())
{
- GMX_THROW(InvalidInputError("Invalid value: " + value));
+ GMX_THROW(InvalidInputError("Invalid value: '" + value
+ + "'; it causes an integer overflow"));
+ }
+ if (*ptr == '\0' || *endptr != '\0')
+ {
+ GMX_THROW(InvalidInputError("Invalid value: '" + value
+ + "'; expected an integer"));
}
addValue(ival);
}
{
const char *ptr = value.c_str();
char *endptr;
+ errno = 0;
double dval = std::strtod(ptr, &endptr);
- if (*endptr != '\0')
+ if (errno == ERANGE)
+ {
+ GMX_THROW(InvalidInputError("Invalid value: '" + value
+ + "'; it causes an overflow/underflow"));
+ }
+ if (*ptr == '\0' || *endptr != '\0')
{
- GMX_THROW(InvalidInputError("Invalid value: " + value));
+ GMX_THROW(InvalidInputError("Invalid value: '" + value
+ + "'; expected a number"));
}
addValue(dval * factor_);
}
EXPECT_CALL(*mock, convertValue("b"))
.WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue)));
- EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "dummy"))));
EXPECT_CALL(*mock, processAll());
}
EXPECT_NO_THROW(assigner.finish());
EXPECT_NO_THROW(options.finish());
- ASSERT_EQ(2U, values.size());
- EXPECT_EQ("a", values[0]);
- EXPECT_EQ("dummy", values[1]);
+ ASSERT_TRUE(values.empty());
}
/*
* \author Teemu Murtola <teemu.murtola@cbr.su.se>
* \ingroup module_options
*/
+#include <limits>
#include <vector>
#include <gtest/gtest.h>
#include "gromacs/options/options.h"
#include "gromacs/options/optionsassigner.h"
#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/stringutil.h"
namespace
{
+/********************************************************************
+ * General assignment tests
+ */
+
TEST(OptionsAssignerTest, HandlesMissingRequiredParameter)
{
gmx::Options options(NULL, NULL);
EXPECT_NO_THROW(assigner.finish());
EXPECT_NO_THROW(options.finish());
- EXPECT_EQ(2, value1);
+ EXPECT_EQ(0, value1);
}
TEST(OptionsAssignerTest, HandlesSubSections)
}
+/********************************************************************
+ * Tests for boolean assignment
+ */
+
TEST(OptionsAssignerBooleanTest, StoresYesValue)
{
gmx::Options options(NULL, NULL);
}
+/********************************************************************
+ * Tests for integer assignment
+ *
+ * These tests also contain tests for general default value handling.
+ */
+
TEST(OptionsAssignerIntegerTest, StoresSingleValue)
{
gmx::Options options(NULL, NULL);
EXPECT_EQ(3, value);
}
+TEST(OptionsAssignerIntegerTest, HandlesEmptyValue)
+{
+ gmx::Options options(NULL, NULL);
+ int value = 1;
+ using gmx::IntegerOption;
+ ASSERT_NO_THROW(options.addOption(IntegerOption("p").store(&value)));
+
+ gmx::OptionsAssigner assigner(&options);
+ EXPECT_NO_THROW(assigner.start());
+ ASSERT_NO_THROW(assigner.startOption("p"));
+ EXPECT_THROW(assigner.appendValue(""), gmx::InvalidInputError);
+ EXPECT_NO_THROW(assigner.finishOption());
+ EXPECT_NO_THROW(assigner.finish());
+ EXPECT_NO_THROW(options.finish());
+
+ EXPECT_EQ(1, value);
+}
+
+TEST(OptionsAssignerIntegerTest, HandlesInvalidValue)
+{
+ gmx::Options options(NULL, NULL);
+ int value = 1;
+ using gmx::IntegerOption;
+ ASSERT_NO_THROW(options.addOption(IntegerOption("p").store(&value)));
+
+ gmx::OptionsAssigner assigner(&options);
+ EXPECT_NO_THROW(assigner.start());
+ ASSERT_NO_THROW(assigner.startOption("p"));
+ EXPECT_THROW(assigner.appendValue("2abc"), gmx::InvalidInputError);
+ EXPECT_NO_THROW(assigner.finishOption());
+ EXPECT_NO_THROW(assigner.finish());
+ EXPECT_NO_THROW(options.finish());
+
+ EXPECT_EQ(1, value);
+}
+
+TEST(OptionsAssignerIntegerTest, HandlesOverflow)
+{
+ gmx::Options options(NULL, NULL);
+ int value = 1;
+ using gmx::IntegerOption;
+ ASSERT_NO_THROW(options.addOption(IntegerOption("p").store(&value)));
+
+ gmx::OptionsAssigner assigner(&options);
+ EXPECT_NO_THROW(assigner.start());
+ ASSERT_NO_THROW(assigner.startOption("p"));
+ std::string overflowValue(
+ gmx::formatString("%d0000", std::numeric_limits<int>::max()));
+ EXPECT_THROW(assigner.appendValue(overflowValue), gmx::InvalidInputError);
+ EXPECT_NO_THROW(assigner.finishOption());
+ EXPECT_NO_THROW(assigner.finish());
+ EXPECT_NO_THROW(options.finish());
+
+ EXPECT_EQ(1, value);
+}
+
TEST(OptionsAssignerIntegerTest, StoresDefaultValue)
{
gmx::Options options(NULL, NULL);
EXPECT_EQ(1, vec2[2]);
}
+
+/********************************************************************
+ * Tests for double assignment
+ */
+
TEST(OptionsAssignerDoubleTest, StoresSingleValue)
{
gmx::Options options(NULL, NULL);
EXPECT_DOUBLE_EQ(2.7, value);
}
+TEST(OptionsAssignerDoubleTest, HandlesEmptyValue)
+{
+ gmx::Options options(NULL, NULL);
+ double value = 1.0;
+ using gmx::DoubleOption;
+ ASSERT_NO_THROW(options.addOption(DoubleOption("p").store(&value)));
+
+ gmx::OptionsAssigner assigner(&options);
+ EXPECT_NO_THROW(assigner.start());
+ ASSERT_NO_THROW(assigner.startOption("p"));
+ EXPECT_THROW(assigner.appendValue(""), gmx::InvalidInputError);
+ EXPECT_NO_THROW(assigner.finishOption());
+ EXPECT_NO_THROW(assigner.finish());
+ EXPECT_NO_THROW(options.finish());
+
+ EXPECT_DOUBLE_EQ(1.0, value);
+}
+
+
+/********************************************************************
+ * Tests for string assignment
+ */
TEST(OptionsAssignerStringTest, StoresSingleValue)
{
EXPECT_NO_THROW(assigner.finishOption());
EXPECT_NO_THROW(assigner.finish());
EXPECT_NO_THROW(options_.finish());
- ASSERT_STREQ("resname RA RB", sel.selectionText());
}