Merge "Merge remote-tracking branch 'origin/release-4-6'"
[alexxy/gromacs.git] / src / gromacs / options / tests / abstractoptionstorage.cpp
1 /*
2  *
3  *                This source code is part of
4  *
5  *                 G   R   O   M   A   C   S
6  *
7  *          GROningen MAchine for Chemical Simulations
8  *
9  * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
10  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
11  * Copyright (c) 2001-2009, The GROMACS development team,
12  * check out http://www.gromacs.org for more information.
13
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * If you want to redistribute modifications, please consider that
20  * scientific software is very special. Version control is crucial -
21  * bugs must be traceable. We will be happy to consider code for
22  * inclusion in the official distribution, but derived work must not
23  * be called official GROMACS. Details are found in the README & COPYING
24  * files - if they are missing, get the official version at www.gromacs.org.
25  *
26  * To help us fund GROMACS development, we humbly ask that you cite
27  * the papers on the package - you can find them in the top README file.
28  *
29  * For more info, check our website at http://www.gromacs.org
30  */
31 /*! \internal \file
32  * \brief
33  * Tests proper handling of option storage.
34  *
35  * These tests check that methods in storage objects are called properly in all
36  * situations, and also that the OptionStorageTemplate class behaves properly.
37  *
38  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
39  * \ingroup module_options
40  */
41 #include <vector>
42 #include <string>
43
44 #include <gmock/gmock.h>
45 #include <gtest/gtest.h>
46
47 #include "gromacs/options/abstractoption.h"
48 #include "gromacs/options/options.h"
49 #include "gromacs/options/optionstoragetemplate.h"
50 #include "gromacs/options/optionsassigner.h"
51 #include "gromacs/utility/exceptions.h"
52 #include "testutils/testexceptions.h"
53
54 namespace
55 {
56
57 class MockOption;
58
59 /*! \internal \brief
60  * Mock implementation of an option storage class for unit testing.
61  *
62  * Provides facilities for checking that correct methods are called, and for
63  * controlling how they add values using the base class methods.
64  *
65  * \ingroup module_options
66  */
67 class MockOptionStorage : public gmx::OptionStorageTemplate<std::string>
68 {
69     public:
70         /*! \brief
71          * Initializes the storage from option settings.
72          *
73          * \param[in] settings   Storage settings.
74          */
75         MockOptionStorage(const MockOption &settings);
76
77         /*! \brief
78          * Calls addValue("dummy") in the base class.
79          */
80         void addDummyValue()
81         {
82             addValue("dummy");
83         }
84         // using MyBase::markAsSet;
85         // using MyBase::addValue;
86         // using MyBase::commitValues;
87         // "using" is correct but MSVC gives error C2248. Workaround:
88         void markAsSet()
89         {
90             MyBase::markAsSet();
91         }
92         void addValue(const std::string &value)
93         {
94             MyBase::addValue(value);
95         }
96         void commitValues()
97         {
98             MyBase::commitValues();
99         }
100
101         // These are not used.
102         virtual gmx::OptionInfo &optionInfo()
103         {
104             GMX_THROW(gmx::test::TestException("Not implemented"));
105         }
106         virtual const char *typeString() const { return "mock"; }
107         virtual std::string formatSingleValue(const std::string &/*value*/) const
108         {
109             return "";
110         }
111
112         MOCK_METHOD1(convertValue, void(const std::string &value));
113         MOCK_METHOD1(processSetValues, void(ValueList *values));
114         MOCK_METHOD0(processAll, void());
115 };
116
117 /*! \internal \brief
118  * Specifies an option that has a mock storage object for unit testing.
119  *
120  * \ingroup module_options
121  */
122 class MockOption : public gmx::OptionTemplate<std::string, MockOption>
123 {
124     public:
125         //! Initializes an option with the given name.
126         explicit MockOption(const char *name)
127             : MyBase(name), storagePtr_(NULL)
128         {
129         }
130
131         //! Sets an output pointer to give access to the created storage object.
132         MyClass &storageObject(MockOptionStorage **storagePtr)
133         { storagePtr_ = storagePtr; return me(); }
134
135     private:
136         virtual gmx::AbstractOptionStoragePointer createStorage() const
137         {
138             MockOptionStorage *storage = new MockOptionStorage(*this);
139             if (storagePtr_ != NULL)
140             {
141                 *storagePtr_ = storage;
142             }
143             return gmx::AbstractOptionStoragePointer(storage);
144         }
145
146         MockOptionStorage     **storagePtr_;
147 };
148
149 MockOptionStorage::MockOptionStorage(const MockOption &settings)
150     : MyBase(settings)
151 {
152     using ::testing::_;
153     using ::testing::Invoke;
154     using ::testing::WithArg;
155     ON_CALL(*this, convertValue(_))
156         .WillByDefault(WithArg<0>(Invoke(this, &MockOptionStorage::addValue)));
157 }
158
159 /*
160  * Tests that finish() can set a required option even if the user has not
161  * provided it.
162  */
163 TEST(AbstractOptionStorageTest, HandlesSetInFinish)
164 {
165     gmx::Options                options(NULL, NULL);
166     std::vector<std::string>    values;
167     MockOptionStorage          *mock = NULL;
168     ASSERT_NO_THROW(options.addOption(
169                         MockOption("name").storageObject(&mock).required()
170                             .storeVector(&values)));
171     ASSERT_TRUE(mock != NULL);
172     {
173         ::testing::InSequence dummy;
174         using ::testing::DoAll;
175         using ::testing::InvokeWithoutArgs;
176         EXPECT_CALL(*mock, processAll())
177             .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::markAsSet),
178                             InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
179                             InvokeWithoutArgs(mock, &MockOptionStorage::commitValues)));
180     }
181
182     gmx::OptionsAssigner assigner(&options);
183     EXPECT_NO_THROW(assigner.start());
184     EXPECT_NO_THROW(assigner.finish());
185     EXPECT_NO_THROW(options.finish());
186
187     ASSERT_EQ(1U, values.size());
188     EXPECT_EQ("dummy", values[0]);
189 }
190
191 /*
192  * Tests that storage works if the storage object does not add a value in a
193  * call to appendValue().
194  */
195 TEST(AbstractOptionStorageTest, HandlesValueRemoval)
196 {
197     gmx::Options                options(NULL, NULL);
198     std::vector<std::string>    values;
199     MockOptionStorage          *mock;
200     ASSERT_NO_THROW(options.addOption(
201                         MockOption("name").storageObject(&mock)
202                             .storeVector(&values).multiValue()));
203
204     {
205         ::testing::InSequence dummy;
206         using ::testing::ElementsAre;
207         using ::testing::Pointee;
208         using ::testing::Return;
209         EXPECT_CALL(*mock, convertValue("a"));
210         EXPECT_CALL(*mock, convertValue("b"))
211             .WillOnce(Return());
212         EXPECT_CALL(*mock, convertValue("c"));
213         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "c"))));
214         EXPECT_CALL(*mock, processAll());
215     }
216
217     gmx::OptionsAssigner assigner(&options);
218     EXPECT_NO_THROW(assigner.start());
219     ASSERT_NO_THROW(assigner.startOption("name"));
220     EXPECT_NO_THROW(assigner.appendValue("a"));
221     EXPECT_NO_THROW(assigner.appendValue("b"));
222     EXPECT_NO_THROW(assigner.appendValue("c"));
223     EXPECT_NO_THROW(assigner.finishOption());
224     EXPECT_NO_THROW(assigner.finish());
225     EXPECT_NO_THROW(options.finish());
226
227     ASSERT_EQ(2U, values.size());
228     EXPECT_EQ("a", values[0]);
229     EXPECT_EQ("c", values[1]);
230 }
231
232 /*
233  * Tests that storage works if the storage object adds more than one value in
234  * one call to appendValue().
235  */
236 TEST(AbstractOptionStorageTest, HandlesValueAddition)
237 {
238     gmx::Options                options(NULL, NULL);
239     std::vector<std::string>    values;
240     MockOptionStorage          *mock=NULL;
241     ASSERT_NO_THROW(options.addOption(
242                         MockOption("name").storageObject(&mock)
243                             .storeVector(&values).multiValue()));
244     ASSERT_TRUE(mock != NULL);
245     {
246         ::testing::InSequence dummy;
247         using ::testing::DoAll;
248         using ::testing::ElementsAre;
249         using ::testing::InvokeWithoutArgs;
250         using ::testing::Pointee;
251         EXPECT_CALL(*mock, convertValue("a"));
252         EXPECT_CALL(*mock, convertValue("b"))
253             .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
254                             InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue)));
255         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "dummy", "dummy"))));
256         EXPECT_CALL(*mock, processAll());
257     }
258
259     gmx::OptionsAssigner assigner(&options);
260     EXPECT_NO_THROW(assigner.start());
261     ASSERT_NO_THROW(assigner.startOption("name"));
262     EXPECT_NO_THROW(assigner.appendValue("a"));
263     EXPECT_NO_THROW(assigner.appendValue("b"));
264     EXPECT_NO_THROW(assigner.finishOption());
265     EXPECT_NO_THROW(assigner.finish());
266     EXPECT_NO_THROW(options.finish());
267
268     ASSERT_EQ(3U, values.size());
269     EXPECT_EQ("a", values[0]);
270     EXPECT_EQ("dummy", values[1]);
271     EXPECT_EQ("dummy", values[2]);
272 }
273
274 /*
275  * Tests that storage works if the storage object adds more than one value in
276  * one call to appendValue(), and this results in too many values.
277  */
278 TEST(AbstractOptionStorageTest, HandlesTooManyValueAddition)
279 {
280     gmx::Options                options(NULL, NULL);
281     std::vector<std::string>    values;
282     MockOptionStorage          *mock=NULL;
283     ASSERT_NO_THROW(options.addOption(
284                         MockOption("name").storageObject(&mock)
285                             .storeVector(&values).valueCount(2)));
286     ASSERT_TRUE(mock != NULL);
287     {
288         ::testing::InSequence dummy;
289         using ::testing::DoAll;
290         using ::testing::ElementsAre;
291         using ::testing::InvokeWithoutArgs;
292         using ::testing::Pointee;
293         EXPECT_CALL(*mock, convertValue("a"));
294         EXPECT_CALL(*mock, convertValue("b"))
295             .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
296                             InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue)));
297         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "dummy"))));
298         EXPECT_CALL(*mock, processAll());
299     }
300
301     gmx::OptionsAssigner assigner(&options);
302     EXPECT_NO_THROW(assigner.start());
303     ASSERT_NO_THROW(assigner.startOption("name"));
304     EXPECT_NO_THROW(assigner.appendValue("a"));
305     EXPECT_THROW(assigner.appendValue("b"), gmx::InvalidInputError);
306     EXPECT_NO_THROW(assigner.finishOption());
307     EXPECT_NO_THROW(assigner.finish());
308     EXPECT_NO_THROW(options.finish());
309
310     ASSERT_EQ(2U, values.size());
311     EXPECT_EQ("a", values[0]);
312     EXPECT_EQ("dummy", values[1]);
313 }
314
315 /*
316  * Tests that the storage object is properly invoked even if no output values
317  * should be produced.
318  */
319 TEST(AbstractOptionStorageTest, AllowsEmptyValues)
320 {
321     gmx::Options                options(NULL, NULL);
322     std::vector<std::string>    values;
323     MockOptionStorage          *mock;
324     ASSERT_NO_THROW(options.addOption(
325                         MockOption("name").storageObject(&mock)
326                             .storeVector(&values).valueCount(0)));
327
328     {
329         ::testing::InSequence dummy;
330         using ::testing::DoAll;
331         using ::testing::ElementsAre;
332         using ::testing::Pointee;
333         using ::testing::Return;
334         EXPECT_CALL(*mock, convertValue("a"))
335             .WillOnce(Return());
336         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre())));
337         EXPECT_CALL(*mock, processAll());
338     }
339
340     gmx::OptionsAssigner assigner(&options);
341     EXPECT_NO_THROW(assigner.start());
342     EXPECT_NO_THROW(assigner.startOption("name"));
343     EXPECT_NO_THROW(assigner.appendValue("a"));
344     EXPECT_NO_THROW(assigner.finishOption());
345     EXPECT_NO_THROW(assigner.finish());
346     EXPECT_NO_THROW(options.finish());
347
348     ASSERT_EQ(0U, values.size());
349 }
350
351 } // namespace