2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018, 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.
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.
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.
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.
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.
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.
37 * Tests option support for operations on KeyValueTree.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_options
44 #include "gromacs/options/treesupport.h"
49 #include <gtest/gtest.h>
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"
64 #include "testutils/refdata.h"
65 #include "testutils/testasserts.h"
70 /********************************************************************
71 * Tests for assignOptionsFromKeyValueTree()
74 TEST(TreeValueSupportAssignTest
, AssignsFromTree
)
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
));
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();
92 ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options
, tree
, nullptr));
93 EXPECT_NO_THROW_GMX(options
.finish());
105 TEST(TreeValueSupportAssignTest
, AssignsFromTreeWithArrays
)
108 std::vector
<SectionData
> s
;
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
));
115 gmx::KeyValueTreeBuilder builder
;
116 auto array
= builder
.rootObject().addUniformArray
<int>("a");
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();
126 ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options
, tree
, nullptr));
127 EXPECT_NO_THROW_GMX(options
.finish());
129 ASSERT_EQ(2U, a0
.size());
132 ASSERT_EQ(2U, s
.size());
133 EXPECT_EQ(3, s
[0].a
);
134 EXPECT_EQ(4, s
[1].a
);
137 TEST(TreeValueSupportAssignErrorTest
, HandlesInvalidValue
)
141 gmx::Options options
;
142 auto sec
= options
.addSection(gmx::OptionSection("s"));
143 sec
.addOption(gmx::IntegerOption("a").store(&a1
));
145 gmx::KeyValueTreeBuilder builder
;
146 auto obj
= builder
.rootObject().addObject("s");
147 obj
.addValue
<std::string
>("a", "foo");
148 gmx::KeyValueTreeObject tree
= builder
.build();
150 EXPECT_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options
, tree
, nullptr),
151 gmx::InvalidInputError
);
154 /********************************************************************
155 * Tests for checkForUnknownOptionsInKeyValueTree()
158 class TreeValueSupportCheckTest
: public ::testing::Test
161 TreeValueSupportCheckTest()
163 auto sec1
= options_
.addSection(gmx::OptionSection("s"));
164 auto sec2
= options_
.addSection(gmx::OptionSection("r"));
165 options_
.addOption(gmx::IntegerOption("a"));
166 sec1
.addOption(gmx::IntegerOption("a"));
167 sec1
.addOption(gmx::IntegerOption("b"));
168 sec2
.addOption(gmx::IntegerOption("b"));
171 gmx::Options options_
;
172 gmx::KeyValueTreeBuilder builder_
;
175 TEST_F(TreeValueSupportCheckTest
, HandlesEmpty
)
177 EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_
.build(), options_
));
180 TEST_F(TreeValueSupportCheckTest
, HandlesMatchingTree
)
182 auto root
= builder_
.rootObject();
183 root
.addValue
<int>("a", 1);
184 auto obj1
= root
.addObject("s");
185 obj1
.addValue
<int>("a", 1);
186 obj1
.addValue
<int>("b", 2);
187 auto obj2
= root
.addObject("r");
188 obj2
.addValue
<int>("b", 3);
190 EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_
.build(), options_
));
193 TEST_F(TreeValueSupportCheckTest
, HandlesSmallerTree1
)
195 auto root
= builder_
.rootObject();
196 root
.addValue
<int>("a", 1);
197 auto obj1
= root
.addObject("s");
198 obj1
.addValue
<int>("b", 2);
200 EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_
.build(), options_
));
203 TEST_F(TreeValueSupportCheckTest
, HandlesSmallerTree2
)
205 auto root
= builder_
.rootObject();
206 auto obj1
= root
.addObject("s");
207 obj1
.addValue
<int>("a", 1);
208 obj1
.addValue
<int>("b", 2);
210 EXPECT_NO_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_
.build(), options_
));
213 TEST_F(TreeValueSupportCheckTest
, DetectsExtraValue
)
215 auto root
= builder_
.rootObject();
216 auto obj2
= root
.addObject("r");
217 obj2
.addValue
<int>("a", 1);
218 obj2
.addValue
<int>("b", 3);
220 EXPECT_THROW_GMX(gmx::checkForUnknownOptionsInKeyValueTree(builder_
.build(), options_
),
221 gmx::InvalidInputError
);
224 /********************************************************************
225 * Tests for adjustKeyValueTreeFromOptions()
228 class TreeValueSupportAdjustTest
: public ::testing::Test
233 gmx::test::TestReferenceData refdata
;
234 gmx::test::TestReferenceChecker
checker(refdata
.rootChecker());
235 gmx::KeyValueTreeObject
tree(builder_
.build());
236 checker
.checkKeyValueTreeObject(tree
, "Input");
237 ASSERT_NO_THROW_GMX(tree
= gmx::adjustKeyValueTreeFromOptions(tree
, options_
));
238 checker
.checkKeyValueTreeObject(tree
, "Output");
241 gmx::Options options_
;
242 gmx::KeyValueTreeBuilder builder_
;
245 TEST_F(TreeValueSupportAdjustTest
, FillsDefaultValues
)
247 options_
.addOption(gmx::IntegerOption("a").defaultValue(2));
251 TEST_F(TreeValueSupportAdjustTest
, FillsDefaultVectorValues
)
253 int v
[3] = {1, 2, 3};
254 options_
.addOption(gmx::IntegerOption("a").store(v
).vector());
258 TEST_F(TreeValueSupportAdjustTest
, FillsDefaultObjectValues
)
260 auto sec1
= options_
.addSection(gmx::OptionSection("s"));
261 sec1
.addOption(gmx::IntegerOption("a").defaultValue(1));
262 auto sec2
= options_
.addSection(gmx::OptionSection("r"));
263 sec2
.addOption(gmx::IntegerOption("a").defaultValue(2));
264 options_
.addOption(gmx::IntegerOption("a").defaultValue(3));
268 TEST_F(TreeValueSupportAdjustTest
, NormalizesValues
)
270 options_
.addOption(gmx::IntegerOption("a"));
271 builder_
.rootObject().addValue
<std::string
>("a", "2");
275 TEST_F(TreeValueSupportAdjustTest
, MergesDefaultValues
)
277 builder_
.rootObject().addValue
<int>("b", 1);
278 options_
.addOption(gmx::IntegerOption("a").defaultValue(2));
279 options_
.addOption(gmx::IntegerOption("b").defaultValue(3));
283 TEST_F(TreeValueSupportAdjustTest
, OrdersValues
)
285 builder_
.rootObject().addValue
<int>("a", 1);
286 builder_
.rootObject().addValue
<int>("c", 1);
287 builder_
.rootObject().addValue
<int>("b", 1);
288 options_
.addOption(gmx::IntegerOption("b").defaultValue(2));
289 options_
.addOption(gmx::IntegerOption("a").defaultValue(1));
290 options_
.addOption(gmx::IntegerOption("c").defaultValue(3));
291 // TODO: This does not actually test the correct ordering, since the
292 // reference data is not currently order-sensitive, but the order can be
293 // checked manually from the reference data.
297 /********************************************************************
298 * Support for different option types
301 class TreeValueSupportTest
: public ::testing::Test
306 gmx::test::TestReferenceData refdata
;
307 gmx::test::TestReferenceChecker
checker(refdata
.rootChecker());
308 gmx::KeyValueTreeObject
tree(builder_
.build());
309 checker
.checkKeyValueTreeObject(tree
, "Input");
310 // Check that adjustment works.
311 ASSERT_NO_THROW_GMX(tree
= gmx::adjustKeyValueTreeFromOptions(tree
, options_
));
312 checker
.checkKeyValueTreeObject(tree
, "Adjusted");
313 // Check that assignment works.
314 ASSERT_NO_THROW_GMX(gmx::assignOptionsFromKeyValueTree(&options_
, tree
, nullptr));
315 // Check that serialization works.
317 std::vector
<char> buffer
= serializeTree(tree
);
318 gmx::InMemoryDeserializer
deserializer(buffer
);
319 gmx::KeyValueTreeObject output
320 = gmx::deserializeKeyValueTree(&deserializer
);
321 SCOPED_TRACE("After serialization/deserialization\n Buffer: "
322 + formatBuffer(buffer
));
323 checker
.checkKeyValueTreeObject(output
, "Adjusted");
325 // Check that dumping works.
327 gmx::StringOutputStream stream
;
328 gmx::TextWriter
writer(&stream
);
329 ASSERT_NO_THROW_GMX(gmx::dumpKeyValueTree(&writer
, tree
));
330 checker
.checkTextBlock(stream
.toString(), "Dumped");
332 // Check that comparison works.
334 gmx::StringOutputStream stream
;
335 gmx::TextWriter
writer(&stream
);
336 ASSERT_NO_THROW_GMX(gmx::compareKeyValueTrees(&writer
, tree
, tree
, 0.0, 0.0));
337 checker
.checkTextBlock(stream
.toString(), "Compared");
339 // Check that comparison works against an empty tree.
341 gmx::StringOutputStream stream
;
342 gmx::TextWriter
writer(&stream
);
343 gmx::KeyValueTreeObject empty
;
344 ASSERT_NO_THROW_GMX(gmx::compareKeyValueTrees(&writer
, tree
, empty
, 0.0, 0.0));
345 checker
.checkTextBlock(stream
.toString(), "ComparedAgainstEmpty");
349 gmx::Options options_
;
350 gmx::KeyValueTreeBuilder builder_
;
353 std::vector
<char> serializeTree(const gmx::KeyValueTreeObject
&tree
)
355 gmx::InMemorySerializer serializer
;
356 gmx::serializeKeyValueTree(tree
, &serializer
);
357 return serializer
.finishAndGetBuffer();
360 std::string
formatBuffer(const std::vector
<char> &buffer
)
362 return gmx::formatAndJoin(buffer
, " ", [](char c
) { return gmx::formatString("%02x", static_cast<unsigned char>(c
)); });
366 TEST_F(TreeValueSupportTest
, SupportsBooleanOption
)
368 options_
.addOption(gmx::BooleanOption("a").defaultValue(true));
372 TEST_F(TreeValueSupportTest
, SupportsIntegerOption
)
374 options_
.addOption(gmx::IntegerOption("a").defaultValue(2));
378 TEST_F(TreeValueSupportTest
, SupportsInt64Option
)
380 options_
.addOption(gmx::Int64Option("a").defaultValue(2));
384 TEST_F(TreeValueSupportTest
, SupportsStringOption
)
386 options_
.addOption(gmx::StringOption("a").defaultValue("s"));
390 TEST_F(TreeValueSupportTest
, SupportsFloatOption
)
392 options_
.addOption(gmx::FloatOption("a").defaultValue(1.5));
396 TEST_F(TreeValueSupportTest
, SupportsDoubleOption
)
398 options_
.addOption(gmx::DoubleOption("a").defaultValue(1.5));
402 TEST_F(TreeValueSupportTest
, SupportsEnumIntOption
)
404 const char *const values
[] = {"foo", "bar"};
405 options_
.addOption(gmx::EnumIntOption("a").enumValue(values
).defaultValue(0));
409 //! Enum for testing EnumOption.
415 TEST_F(TreeValueSupportTest
, SupportsEnumOption
)
417 const char *const values
[] = {"foo", "bar"};
418 options_
.addOption(gmx::EnumOption
<TestEnum
>("a").enumValue(values
)
419 .defaultValue(TestEnum::Foo
));