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