Replace EnumOption with EnumerationArrayOption
[alexxy/gromacs.git] / src / gromacs / options / tests / treesupport.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019,2020, 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 option support for operations on KeyValueTree.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_options
41  */
42 #include "gmxpre.h"
43
44 #include "gromacs/options/treesupport.h"
45
46 #include <string>
47 #include <vector>
48
49 #include <gtest/gtest.h>
50
51 #include "gromacs/options/basicoptions.h"
52 #include "gromacs/options/options.h"
53 #include "gromacs/options/optionsection.h"
54 #include "gromacs/options/repeatingsection.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/inmemoryserializer.h"
57 #include "gromacs/utility/keyvaluetree.h"
58 #include "gromacs/utility/keyvaluetreebuilder.h"
59 #include "gromacs/utility/keyvaluetreeserializer.h"
60 #include "gromacs/utility/stringstream.h"
61 #include "gromacs/utility/stringutil.h"
62 #include "gromacs/utility/textwriter.h"
63
64 #include "testutils/refdata.h"
65 #include "testutils/testasserts.h"
66
67 namespace
68 {
69
70 /********************************************************************
71  * Tests for assignOptionsFromKeyValueTree()
72  */
73
74 TEST(TreeValueSupportAssignTest, AssignsFromTree)
75 {
76     int         a0 = 0, a1 = 0;
77     std::string b1;
78
79     gmx::Options options;
80     options.addOption(gmx::IntegerOption("a").store(&a0));
81     auto sec = options.addSection(gmx::OptionSection("s"));
82     sec.addOption(gmx::IntegerOption("a").store(&a1));
83     sec.addOption(gmx::StringOption("b").store(&b1));
84
85     gmx::KeyValueTreeBuilder builder;
86     builder.rootObject().addValue<int>("a", 2);
87     auto obj = builder.rootObject().addObject("s");
88     obj.addValue<int>("a", 1);
89     obj.addValue<std::string>("b", "foo");
90     gmx::KeyValueTreeObject tree = builder.build();
91
92     ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options, tree, nullptr));
93     EXPECT_NO_THROW_GMX(options.finish());
94
95     EXPECT_EQ(2, a0);
96     EXPECT_EQ(1, a1);
97     EXPECT_EQ("foo", b1);
98 }
99
100 struct SectionData
101 {
102     int a;
103 };
104
105 TEST(TreeValueSupportAssignTest, AssignsFromTreeWithArrays)
106 {
107     std::vector<int>         a0;
108     std::vector<SectionData> s;
109
110     gmx::Options options;
111     options.addOption(gmx::IntegerOption("a").storeVector(&a0).multiValue());
112     auto sec = options.addSection(gmx::RepeatingOptionSection<SectionData>("s").storeVector(&s));
113     sec.addOption(gmx::IntegerOption("a").store(&sec.bind().a));
114
115     gmx::KeyValueTreeBuilder builder;
116     auto                     array = builder.rootObject().addUniformArray<int>("a");
117     array.addValue(1);
118     array.addValue(2);
119     auto objArray = builder.rootObject().addObjectArray("s");
120     auto obj1     = objArray.addObject();
121     obj1.addValue<int>("a", 3);
122     auto obj2 = objArray.addObject();
123     obj2.addValue<int>("a", 4);
124     gmx::KeyValueTreeObject tree = builder.build();
125
126     ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options, tree, nullptr));
127     EXPECT_NO_THROW_GMX(options.finish());
128
129     ASSERT_EQ(2U, a0.size());
130     EXPECT_EQ(1, a0[0]);
131     EXPECT_EQ(2, a0[1]);
132     ASSERT_EQ(2U, s.size());
133     EXPECT_EQ(3, s[0].a);
134     EXPECT_EQ(4, s[1].a);
135 }
136
137 TEST(TreeValueSupportAssignErrorTest, HandlesInvalidValue)
138 {
139     int a1 = 0;
140
141     gmx::Options options;
142     auto         sec = options.addSection(gmx::OptionSection("s"));
143     sec.addOption(gmx::IntegerOption("a").store(&a1));
144
145     gmx::KeyValueTreeBuilder builder;
146     auto                     obj = builder.rootObject().addObject("s");
147     obj.addValue<std::string>("a", "foo");
148     gmx::KeyValueTreeObject tree = builder.build();
149
150     EXPECT_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options, tree, nullptr), gmx::InvalidInputError);
151 }
152
153 /********************************************************************
154  * Tests for checkForUnknownOptionsInKeyValueTree()
155  */
156
157 class TreeValueSupportCheckTest : public ::testing::Test
158 {
159 public:
160     TreeValueSupportCheckTest()
161     {
162         auto sec1 = options_.addSection(gmx::OptionSection("s"));
163         auto sec2 = options_.addSection(gmx::OptionSection("r"));
164         options_.addOption(gmx::IntegerOption("a"));
165         sec1.addOption(gmx::IntegerOption("a"));
166         sec1.addOption(gmx::IntegerOption("b"));
167         sec2.addOption(gmx::IntegerOption("b"));
168     }
169
170     gmx::Options             options_;
171     gmx::KeyValueTreeBuilder builder_;
172 };
173
174 TEST_F(TreeValueSupportCheckTest, HandlesEmpty)
175 {
176     EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_.build(), options_));
177 }
178
179 TEST_F(TreeValueSupportCheckTest, HandlesMatchingTree)
180 {
181     auto root = builder_.rootObject();
182     root.addValue<int>("a", 1);
183     auto obj1 = root.addObject("s");
184     obj1.addValue<int>("a", 1);
185     obj1.addValue<int>("b", 2);
186     auto obj2 = root.addObject("r");
187     obj2.addValue<int>("b", 3);
188
189     EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_.build(), options_));
190 }
191
192 TEST_F(TreeValueSupportCheckTest, HandlesSmallerTree1)
193 {
194     auto root = builder_.rootObject();
195     root.addValue<int>("a", 1);
196     auto obj1 = root.addObject("s");
197     obj1.addValue<int>("b", 2);
198
199     EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_.build(), options_));
200 }
201
202 TEST_F(TreeValueSupportCheckTest, HandlesSmallerTree2)
203 {
204     auto root = builder_.rootObject();
205     auto obj1 = root.addObject("s");
206     obj1.addValue<int>("a", 1);
207     obj1.addValue<int>("b", 2);
208
209     EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_.build(), options_));
210 }
211
212 TEST_F(TreeValueSupportCheckTest, DetectsExtraValue)
213 {
214     auto root = builder_.rootObject();
215     auto obj2 = root.addObject("r");
216     obj2.addValue<int>("a", 1);
217     obj2.addValue<int>("b", 3);
218
219     EXPECT_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_.build(), options_),
220                      gmx::InvalidInputError);
221 }
222
223 /********************************************************************
224  * Tests for adjustKeyValueTreeFromOptions()
225  */
226
227 class TreeValueSupportAdjustTest : public ::testing::Test
228 {
229 public:
230     void runTest()
231     {
232         gmx::test::TestReferenceData    refdata;
233         gmx::test::TestReferenceChecker checker(refdata.rootChecker());
234         gmx::KeyValueTreeObject         tree(builder_.build());
235         checker.checkKeyValueTreeObject(tree, "Input");
236         ASSERT_NO_THROW_GMX(tree = gmx::adjustKeyValueTreeFromOptions(tree, options_));
237         checker.checkKeyValueTreeObject(tree, "Output");
238     }
239
240     gmx::Options             options_;
241     gmx::KeyValueTreeBuilder builder_;
242 };
243
244 TEST_F(TreeValueSupportAdjustTest, FillsDefaultValues)
245 {
246     options_.addOption(gmx::IntegerOption("a").defaultValue(2));
247     runTest();
248 }
249
250 TEST_F(TreeValueSupportAdjustTest, FillsDefaultVectorValues)
251 {
252     int v[3] = { 1, 2, 3 };
253     options_.addOption(gmx::IntegerOption("a").store(v).vector());
254     runTest();
255 }
256
257 TEST_F(TreeValueSupportAdjustTest, FillsDefaultObjectValues)
258 {
259     auto sec1 = options_.addSection(gmx::OptionSection("s"));
260     sec1.addOption(gmx::IntegerOption("a").defaultValue(1));
261     auto sec2 = options_.addSection(gmx::OptionSection("r"));
262     sec2.addOption(gmx::IntegerOption("a").defaultValue(2));
263     options_.addOption(gmx::IntegerOption("a").defaultValue(3));
264     runTest();
265 }
266
267 TEST_F(TreeValueSupportAdjustTest, NormalizesValues)
268 {
269     options_.addOption(gmx::IntegerOption("a"));
270     builder_.rootObject().addValue<std::string>("a", "2");
271     runTest();
272 }
273
274 TEST_F(TreeValueSupportAdjustTest, MergesDefaultValues)
275 {
276     builder_.rootObject().addValue<int>("b", 1);
277     options_.addOption(gmx::IntegerOption("a").defaultValue(2));
278     options_.addOption(gmx::IntegerOption("b").defaultValue(3));
279     runTest();
280 }
281
282 TEST_F(TreeValueSupportAdjustTest, OrdersValues)
283 {
284     builder_.rootObject().addValue<int>("a", 1);
285     builder_.rootObject().addValue<int>("c", 1);
286     builder_.rootObject().addValue<int>("b", 1);
287     options_.addOption(gmx::IntegerOption("b").defaultValue(2));
288     options_.addOption(gmx::IntegerOption("a").defaultValue(1));
289     options_.addOption(gmx::IntegerOption("c").defaultValue(3));
290     // TODO: This does not actually test the correct ordering, since the
291     // reference data is not currently order-sensitive, but the order can be
292     // checked manually from the reference data.
293     runTest();
294 }
295
296 /********************************************************************
297  * Support for different option types
298  */
299
300 class TreeValueSupportTest : public ::testing::Test
301 {
302 public:
303     void runTest()
304     {
305         gmx::test::TestReferenceData    refdata;
306         gmx::test::TestReferenceChecker checker(refdata.rootChecker());
307         gmx::KeyValueTreeObject         tree(builder_.build());
308         checker.checkKeyValueTreeObject(tree, "Input");
309         // Check that adjustment works.
310         ASSERT_NO_THROW_GMX(tree = gmx::adjustKeyValueTreeFromOptions(tree, options_));
311         checker.checkKeyValueTreeObject(tree, "Adjusted");
312         // Check that assignment works.
313         ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options_, tree, nullptr));
314         // Check that serialization works.
315         {
316             std::vector<char>         buffer = serializeTree(tree);
317             gmx::InMemoryDeserializer deserializer(buffer, false);
318             gmx::KeyValueTreeObject   output = gmx::deserializeKeyValueTree(&deserializer);
319             SCOPED_TRACE("After serialization/deserialization\n  Buffer: " + formatBuffer(buffer));
320             checker.checkKeyValueTreeObject(output, "Adjusted");
321         }
322         // Check that dumping works.
323         {
324             gmx::StringOutputStream stream;
325             gmx::TextWriter         writer(&stream);
326             ASSERT_NO_THROW_GMX(gmx::dumpKeyValueTree(&writer, tree));
327             checker.checkTextBlock(stream.toString(), "Dumped");
328         }
329         // Check that comparison works.
330         {
331             gmx::StringOutputStream stream;
332             gmx::TextWriter         writer(&stream);
333             ASSERT_NO_THROW_GMX(gmx::compareKeyValueTrees(&writer, tree, tree, 0.0, 0.0));
334             checker.checkTextBlock(stream.toString(), "Compared");
335         }
336         // Check that comparison works against an empty tree.
337         {
338             gmx::StringOutputStream stream;
339             gmx::TextWriter         writer(&stream);
340             gmx::KeyValueTreeObject empty;
341             ASSERT_NO_THROW_GMX(gmx::compareKeyValueTrees(&writer, tree, empty, 0.0, 0.0));
342             checker.checkTextBlock(stream.toString(), "ComparedAgainstEmpty");
343         }
344     }
345
346     gmx::Options             options_;
347     gmx::KeyValueTreeBuilder builder_;
348
349 private:
350     std::vector<char> serializeTree(const gmx::KeyValueTreeObject& tree)
351     {
352         gmx::InMemorySerializer serializer;
353         gmx::serializeKeyValueTree(tree, &serializer);
354         return serializer.finishAndGetBuffer();
355     }
356
357     std::string formatBuffer(const std::vector<char>& buffer)
358     {
359         return gmx::formatAndJoin(buffer, " ", [](char c) {
360             return gmx::formatString("%02x", static_cast<unsigned char>(c));
361         });
362     }
363 };
364
365 TEST_F(TreeValueSupportTest, SupportsBooleanOption)
366 {
367     options_.addOption(gmx::BooleanOption("a").defaultValue(true));
368     runTest();
369 }
370
371 TEST_F(TreeValueSupportTest, SupportsIntegerOption)
372 {
373     options_.addOption(gmx::IntegerOption("a").defaultValue(2));
374     runTest();
375 }
376
377 TEST_F(TreeValueSupportTest, SupportsInt64Option)
378 {
379     options_.addOption(gmx::Int64Option("a").defaultValue(2));
380     runTest();
381 }
382
383 TEST_F(TreeValueSupportTest, SupportsStringOption)
384 {
385     options_.addOption(gmx::StringOption("a").defaultValue("s"));
386     runTest();
387 }
388
389 TEST_F(TreeValueSupportTest, SupportsFloatOption)
390 {
391     options_.addOption(gmx::FloatOption("a").defaultValue(1.5));
392     runTest();
393 }
394
395 TEST_F(TreeValueSupportTest, SupportsDoubleOption)
396 {
397     options_.addOption(gmx::DoubleOption("a").defaultValue(1.5));
398     runTest();
399 }
400
401 //! Enum for testing EnumOption.
402 enum class TestEnum : int
403 {
404     Foo,
405     Bar
406 };
407
408 TEST_F(TreeValueSupportTest, SupportsEnumOption)
409 {
410     enum class TestEnum : int
411     {
412         Foo,
413         Bar,
414         Count
415     };
416     const gmx::EnumerationArray<TestEnum, const char*> testEnumNames = { { "foo", "bar" } };
417     options_.addOption(gmx::EnumOption<TestEnum>("a").enumValue(testEnumNames).defaultValue(TestEnum::Foo));
418     runTest();
419 }
420
421 } // namespace