5f23659821fe3c733dc4ec3a74bcb7c7441ef344
[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,2018,2019, 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() { addValue("dummy"); }
99     using MyBase::addValue;
100     using MyBase::commitValues;
101     using MyBase::markAsSet;
102
103     gmx::OptionInfo& optionInfo() override { return info_; }
104     // These are not used.
105     std::string typeString() const override { return "mock"; }
106     std::string formatSingleValue(const std::string& /*value*/) const override { return ""; }
107     std::vector<gmx::Any> normalizeValues(const std::vector<gmx::Any>& values) const override
108     {
109         return values;
110     }
111
112     void convertValue(const gmx::Any& value) override { convertValue(value.cast<std::string>()); }
113
114     MOCK_METHOD1(convertValue, void(const std::string& value));
115     MOCK_METHOD1(processSetValues, void(ValueList* values));
116     MOCK_METHOD0(processAll, void());
117
118 private:
119     MockOptionInfo info_;
120 };
121
122 /*! \internal \brief
123  * Specifies an option that has a mock storage object for unit testing.
124  *
125  * \ingroup module_options
126  */
127 class MockOption : public gmx::OptionTemplate<std::string, MockOption>
128 {
129 public:
130     //! OptionInfo subclass corresponding to this option type.
131     typedef MockOptionInfo InfoType;
132
133     //! Initializes an option with the given name.
134     explicit MockOption(const char* name) : MyBase(name) {}
135
136 private:
137     gmx::AbstractOptionStorage* createStorage(const gmx::OptionManagerContainer& /*managers*/) const override
138     {
139         return new MockOptionStorage(*this);
140     }
141 };
142
143 MockOptionStorage::MockOptionStorage(const MockOption& settings) : MyBase(settings), info_(this)
144 {
145     using ::testing::_;
146     using ::testing::Invoke;
147     using ::testing::WithArg;
148     ON_CALL(*this, convertValue(_)).WillByDefault(WithArg<0>(Invoke(this, &MockOptionStorage::addValue)));
149 }
150
151 MockOptionInfo::MockOptionInfo(MockOptionStorage* option) : gmx::OptionInfo(option) {}
152
153 MockOptionStorage& MockOptionInfo::option()
154 {
155     return static_cast<MockOptionStorage&>(gmx::OptionInfo::option());
156 }
157
158 /*
159  * Tests that finish() can set a required option even if the user has not
160  * provided it.
161  */
162 TEST(AbstractOptionStorageTest, HandlesSetInFinish)
163 {
164     gmx::Options             options;
165     std::vector<std::string> values;
166     MockOptionInfo*    info = options.addOption(MockOption("name").required().storeVector(&values));
167     MockOptionStorage* mock = &info->option();
168     {
169         ::testing::InSequence dummy;
170         using ::testing::DoAll;
171         using ::testing::InvokeWithoutArgs;
172         EXPECT_CALL(*mock, processAll())
173                 .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::markAsSet),
174                                 InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
175                                 InvokeWithoutArgs(mock, &MockOptionStorage::commitValues)));
176     }
177
178     gmx::OptionsAssigner assigner(&options);
179     EXPECT_NO_THROW_GMX(assigner.start());
180     EXPECT_NO_THROW_GMX(assigner.finish());
181     EXPECT_NO_THROW_GMX(options.finish());
182
183     ASSERT_EQ(1U, values.size());
184     EXPECT_EQ("dummy", values[0]);
185 }
186
187 /*
188  * Tests that storage works if the storage object does not add a value in a
189  * call to appendValue().
190  */
191 TEST(AbstractOptionStorageTest, HandlesValueRemoval)
192 {
193     gmx::Options             options;
194     std::vector<std::string> values;
195     MockOptionInfo* info = options.addOption(MockOption("name").storeVector(&values).multiValue());
196     MockOptionStorage* mock = &info->option();
197     {
198         ::testing::InSequence dummy;
199         using ::testing::ElementsAre;
200         using ::testing::Pointee;
201         using ::testing::Return;
202         EXPECT_CALL(*mock, convertValue("a"));
203         EXPECT_CALL(*mock, convertValue("b")).WillOnce(Return());
204         EXPECT_CALL(*mock, convertValue("c"));
205         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "c"))));
206         EXPECT_CALL(*mock, processAll());
207     }
208
209     gmx::OptionsAssigner assigner(&options);
210     EXPECT_NO_THROW_GMX(assigner.start());
211     ASSERT_NO_THROW_GMX(assigner.startOption("name"));
212     EXPECT_NO_THROW_GMX(assigner.appendValue("a"));
213     EXPECT_NO_THROW_GMX(assigner.appendValue("b"));
214     EXPECT_NO_THROW_GMX(assigner.appendValue("c"));
215     EXPECT_NO_THROW_GMX(assigner.finishOption());
216     EXPECT_NO_THROW_GMX(assigner.finish());
217     EXPECT_NO_THROW_GMX(options.finish());
218
219     ASSERT_EQ(2U, values.size());
220     EXPECT_EQ("a", values[0]);
221     EXPECT_EQ("c", values[1]);
222 }
223
224 /*
225  * Tests that storage works if the storage object adds more than one value in
226  * one call to appendValue().
227  */
228 TEST(AbstractOptionStorageTest, HandlesValueAddition)
229 {
230     gmx::Options             options;
231     std::vector<std::string> values;
232     MockOptionInfo* info = options.addOption(MockOption("name").storeVector(&values).multiValue());
233     MockOptionStorage* mock = &info->option();
234     {
235         ::testing::InSequence dummy;
236         using ::testing::DoAll;
237         using ::testing::ElementsAre;
238         using ::testing::InvokeWithoutArgs;
239         using ::testing::Pointee;
240         EXPECT_CALL(*mock, convertValue("a"));
241         EXPECT_CALL(*mock, convertValue("b"))
242                 .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
243                                 InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue)));
244         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre("a", "dummy", "dummy"))));
245         EXPECT_CALL(*mock, processAll());
246     }
247
248     gmx::OptionsAssigner assigner(&options);
249     EXPECT_NO_THROW_GMX(assigner.start());
250     ASSERT_NO_THROW_GMX(assigner.startOption("name"));
251     EXPECT_NO_THROW_GMX(assigner.appendValue("a"));
252     EXPECT_NO_THROW_GMX(assigner.appendValue("b"));
253     EXPECT_NO_THROW_GMX(assigner.finishOption());
254     EXPECT_NO_THROW_GMX(assigner.finish());
255     EXPECT_NO_THROW_GMX(options.finish());
256
257     ASSERT_EQ(3U, values.size());
258     EXPECT_EQ("a", values[0]);
259     EXPECT_EQ("dummy", values[1]);
260     EXPECT_EQ("dummy", values[2]);
261 }
262
263 /*
264  * Tests that storage works if the storage object adds more than one value in
265  * one call to appendValue(), and this results in too many values.
266  */
267 TEST(AbstractOptionStorageTest, HandlesTooManyValueAddition)
268 {
269     gmx::Options             options;
270     std::vector<std::string> values;
271     MockOptionInfo* info = options.addOption(MockOption("name").storeVector(&values).valueCount(2));
272     MockOptionStorage* mock = &info->option();
273     {
274         ::testing::InSequence dummy;
275         using ::testing::DoAll;
276         using ::testing::ElementsAre;
277         using ::testing::InvokeWithoutArgs;
278         using ::testing::Pointee;
279         EXPECT_CALL(*mock, convertValue("a"));
280         EXPECT_CALL(*mock, convertValue("b"))
281                 .WillOnce(DoAll(InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue),
282                                 InvokeWithoutArgs(mock, &MockOptionStorage::addDummyValue)));
283         EXPECT_CALL(*mock, processAll());
284     }
285
286     gmx::OptionsAssigner assigner(&options);
287     EXPECT_NO_THROW_GMX(assigner.start());
288     ASSERT_NO_THROW_GMX(assigner.startOption("name"));
289     EXPECT_NO_THROW_GMX(assigner.appendValue("a"));
290     EXPECT_THROW_GMX(assigner.appendValue("b"), gmx::InvalidInputError);
291     EXPECT_NO_THROW_GMX(assigner.finishOption());
292     EXPECT_NO_THROW_GMX(assigner.finish());
293     EXPECT_NO_THROW_GMX(options.finish());
294
295     ASSERT_TRUE(values.empty());
296 }
297
298 /*
299  * Tests that the storage object is properly invoked even if no output values
300  * should be produced.
301  */
302 TEST(AbstractOptionStorageTest, AllowsEmptyValues)
303 {
304     gmx::Options             options;
305     std::vector<std::string> values;
306     MockOptionInfo* info = options.addOption(MockOption("name").storeVector(&values).valueCount(0));
307     MockOptionStorage* mock = &info->option();
308     {
309         ::testing::InSequence dummy;
310         using ::testing::DoAll;
311         using ::testing::ElementsAre;
312         using ::testing::Pointee;
313         using ::testing::Return;
314         EXPECT_CALL(*mock, convertValue("a")).WillOnce(Return());
315         EXPECT_CALL(*mock, processSetValues(Pointee(ElementsAre())));
316         EXPECT_CALL(*mock, processAll());
317     }
318
319     gmx::OptionsAssigner assigner(&options);
320     EXPECT_NO_THROW_GMX(assigner.start());
321     EXPECT_NO_THROW_GMX(assigner.startOption("name"));
322     EXPECT_NO_THROW_GMX(assigner.appendValue("a"));
323     EXPECT_NO_THROW_GMX(assigner.finishOption());
324     EXPECT_NO_THROW_GMX(assigner.finish());
325     EXPECT_NO_THROW_GMX(options.finish());
326
327     ASSERT_EQ(0U, values.size());
328 }
329
330 } // namespace