c8dd3e9f28191149d126b22d041ccd7ff19581d7
[alexxy/gromacs.git] / src / gromacs / commandline / tests / cmdlinehelpwriter.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5  * Copyright (c) 2017,2019,2020, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 /*! \internal \file
37  * \brief
38  * Tests gmx::CommandLineHelpWriter.
39  *
40  * These tests fail for any change in the output, and it should be reviewed
41  * whether the change was intentional.
42  * For development, the tests can be run with a '-stdout' command-line option
43  * to print out the help to stdout instead of using the XML reference
44  * framework.
45  *
46  * \author Teemu Murtola <teemu.murtola@gmail.com>
47  * \ingroup module_commandline
48  */
49 #include "gmxpre.h"
50
51 #include "gromacs/commandline/cmdlinehelpwriter.h"
52
53 #include <gtest/gtest.h>
54
55 #include "gromacs/commandline/cmdlinehelpcontext.h"
56 #include "gromacs/math/vectypes.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/filenameoption.h"
59 #include "gromacs/options/options.h"
60 #include "gromacs/utility/arrayref.h"
61 #include "gromacs/utility/enumerationhelpers.h"
62 #include "gromacs/utility/stringstream.h"
63 #include "gromacs/utility/textwriter.h"
64
65 #include "testutils/stringtest.h"
66
67 namespace
68 {
69
70 class CommandLineHelpWriterTest : public ::gmx::test::StringTestBase
71 {
72 public:
73     CommandLineHelpWriterTest() : bHidden_(false) {}
74
75     void checkHelp(gmx::CommandLineHelpWriter* writer);
76
77     bool bHidden_;
78 };
79
80 void CommandLineHelpWriterTest::checkHelp(gmx::CommandLineHelpWriter* writer)
81 {
82     gmx::StringOutputStream     stream;
83     gmx::TextWriter             streamWriter(&stream);
84     gmx::CommandLineHelpContext context(&streamWriter, gmx::eHelpOutputFormat_Console, nullptr,
85                                         "test");
86     context.setShowHidden(bHidden_);
87     writer->writeHelp(context);
88     stream.close();
89
90     checkText(stream.toString(), "HelpText");
91 }
92
93
94 /********************************************************************
95  * Tests start here
96  */
97
98 /*
99  * Tests help printing for each option type, but doesn't contain much
100  * variablity in the options.
101  */
102 TEST_F(CommandLineHelpWriterTest, HandlesOptionTypes)
103 {
104     using namespace gmx;
105
106     Options options;
107     options.addOption(BooleanOption("bool").description("Boolean option").defaultValue(true));
108     options.addOption(BooleanOption("hidden").description("Hidden option").hidden().defaultValue(true));
109     options.addOption(IntegerOption("int").description("Integer option").defaultValue(2));
110     ivec intvec = { 1, 2, 3 };
111     options.addOption(IntegerOption("ivec").description("Integer vector option").vector().store(intvec));
112     options.addOption(DoubleOption("double").description("Double option").defaultValue(2.5));
113     dvec dblvec = { 1.1, 2.3, 3.2 };
114     options.addOption(DoubleOption("dvec").description("Double vector option").vector().store(dblvec));
115     options.addOption(DoubleOption("time").description("Time option (%t)").timeValue().defaultValue(10.0));
116     options.addOption(StringOption("string").description("String option").defaultValue("test"));
117     const char* const enumValues[] = { "no", "opt1", "opt2" };
118     enum class TestEnum : int
119     {
120         No,
121         Opt1,
122         Opt2,
123         Count
124     };
125     const gmx::EnumerationArray<TestEnum, const char*> testEnumNames = { { "no", "opt1", "opt2" } };
126
127     options.addOption(
128             StringOption("enum").description("Enum option").enumValue(enumValues).defaultEnumIndex(0));
129     options.addOption(
130             EnumOption<TestEnum>("ienum").description("Enum option").enumValue(testEnumNames).defaultValue(TestEnum::Opt1));
131
132     std::string filename;
133     options.addOption(FileNameOption("f")
134                               .description("Input file description")
135                               .filetype(eftTrajectory)
136                               .inputFile()
137                               .required()
138                               .defaultBasename("traj"));
139     options.addOption(FileNameOption("mult")
140                               .description("Multiple file description")
141                               .filetype(eftTrajectory)
142                               .inputFile()
143                               .multiValue()
144                               .defaultBasename("traj"));
145     options.addOption(FileNameOption("lib")
146                               .description("Library file description")
147                               .filetype(eftGenericData)
148                               .inputFile()
149                               .libraryFile()
150                               .defaultBasename("libdata"));
151     options.addOption(FileNameOption("io")
152                               .store(&filename)
153                               .description("Input/Output file description")
154                               .filetype(eftGenericData)
155                               .inputOutputFile()
156                               .defaultBasename("inout"));
157     options.addOption(
158             FileNameOption("o").description("Output file description").filetype(eftPlot).outputFile());
159
160     CommandLineHelpWriter writer(options);
161     bHidden_ = true;
162     checkHelp(&writer);
163 }
164
165 //! Enum value for testing.
166 enum class TestEnum : int
167 {
168     Foo,
169     Bar,
170     Count
171 };
172
173 /*
174  * Tests that default values taken from variables are properly visible in the
175  * help output.
176  */
177 TEST_F(CommandLineHelpWriterTest, HandlesDefaultValuesFromVariables)
178 {
179     using namespace gmx;
180
181     Options options;
182
183     bool bValue = true;
184     options.addOption(BooleanOption("bool").description("Boolean option").store(&bValue));
185
186     int ivalue = 3;
187     options.addOption(IntegerOption("int").description("Integer option").store(&ivalue));
188
189     int iavalue[] = { 2, 3 };
190     options.addOption(
191             IntegerOption("int2").description("Integer 2-value option").store(iavalue).valueCount(2));
192
193     std::vector<std::string> svalues;
194     svalues.emplace_back("foo");
195     options.addOption(StringOption("str").description("String option").storeVector(&svalues).multiValue());
196
197     TestEnum                                           evalue  = TestEnum::Bar;
198     const gmx::EnumerationArray<TestEnum, const char*> allowed = { { "foo", "bar" } };
199     options.addOption(
200             EnumOption<TestEnum>("enum").description("Enum option").enumValue(allowed).store(&evalue));
201
202     CommandLineHelpWriter writer(options);
203     checkHelp(&writer);
204 }
205
206 /*
207  * Tests help printing with file name options with various values that don't
208  * fit into the allocated columns.
209  */
210 TEST_F(CommandLineHelpWriterTest, HandlesLongFileOptions)
211 {
212     using gmx::eftGenericData;
213     using gmx::eftTrajectory;
214     using gmx::FileNameOption;
215
216     gmx::Options options;
217     options.addOption(FileNameOption("f")
218                               .description("File name option with a long value")
219                               .filetype(eftTrajectory)
220                               .inputFile()
221                               .required()
222                               .defaultBasename("path/to/long/trajectory/name"));
223     options.addOption(FileNameOption("f2")
224                               .description("File name option with a long value")
225                               .filetype(eftTrajectory)
226                               .inputFile()
227                               .required()
228                               .defaultBasename("path/to/long/trajectory"));
229     options.addOption(FileNameOption("lib")
230                               .description("File name option with a long value and type")
231                               .filetype(eftTrajectory)
232                               .inputFile()
233                               .libraryFile()
234                               .defaultBasename("path/to/long/trajectory/name"));
235     options.addOption(FileNameOption("longfileopt")
236                               .description("File name option with a long name")
237                               .filetype(eftGenericData)
238                               .inputFile()
239                               .defaultBasename("deffile"));
240     options.addOption(FileNameOption("longfileopt2")
241                               .description("File name option with multiple long fields")
242                               .filetype(eftGenericData)
243                               .inputFile()
244                               .libraryFile()
245                               .defaultBasename("path/to/long/file/name"));
246
247     gmx::CommandLineHelpWriter writer(options);
248     checkHelp(&writer);
249 }
250
251 /*
252  * Tests help printing with general options with various values that don't
253  * fit into the allocated columns.
254  */
255 TEST_F(CommandLineHelpWriterTest, HandlesLongOptions)
256 {
257     using gmx::BooleanOption;
258     using gmx::DoubleOption;
259     using gmx::StringOption;
260
261     gmx::Options options;
262     options.addOption(
263             BooleanOption("longboolean").description("Boolean option with a long name").defaultValue(true));
264     dvec dblvec = { 1.135, 2.32, 3.2132 };
265     options.addOption(DoubleOption("dvec").description("Double vector option").vector().store(dblvec));
266     std::vector<std::string> values;
267     values.emplace_back("A very long string value that overflows even the description column");
268     values.emplace_back(
269             "Another very long string value that overflows even the description column");
270     options.addOption(StringOption("string")
271                               .description("String option with very long values (may "
272                                            "be less relevant with selections having "
273                                            "their own option type)")
274                               .storeVector(&values));
275
276     gmx::CommandLineHelpWriter writer(options);
277     checkHelp(&writer);
278 }
279
280 /* TODO: Add corresponding tests to either the selection module, or as part of
281  * trajectoryanalysis tests.
282  * Tests help printing with selection options with values.
283  */
284 #if 0
285 TEST_F(CommandLineHelpWriterTest, HandlesSelectionOptions)
286 {
287     using gmx::SelectionFileOption;
288     using gmx::SelectionOption;
289
290     gmx::Options                options;
291     gmx::SelectionCollection    selections;
292     gmx::SelectionOptionManager manager(&selections);
293     options.addManager(&manager);
294     options.addOption(SelectionFileOption("sf"));
295     options.addOption(SelectionOption("refsel").required()
296                           .description("Reference selection option"));
297     options.addOption(SelectionOption("sel").required().valueCount(2)
298                           .description("Selection option"));
299     options.finish();
300     manager.parseRequestedFromString(
301             "resname SOL;"
302             "surface = within 0.5 of resname SOL;"
303             "group \"Protein\" and surface;"
304             "group \"Protein\" and not surface;");
305
306     gmx::CommandLineHelpWriter writer(options);
307     checkHelp(&writer);
308 }
309 #endif
310
311 /*
312  * Tests help output with option groups.
313  */
314 TEST_F(CommandLineHelpWriterTest, HandlesOptionGroups)
315 {
316     using gmx::IntegerOption;
317
318     gmx::Options            options;
319     gmx::IOptionsContainer& group1 = options.addGroup();
320     gmx::IOptionsContainer& group2 = options.addGroup();
321     group2.addOption(IntegerOption("sub2").description("Option in group 2"));
322     group1.addOption(IntegerOption("sub11").description("Option in group 1"));
323     options.addOption(IntegerOption("main").description("Option in root group"));
324     group1.addOption(IntegerOption("sub12").description("Option in group 1"));
325
326     gmx::CommandLineHelpWriter writer(options);
327     checkHelp(&writer);
328 }
329
330 /*
331  * Tests help output using a help text.
332  */
333 TEST_F(CommandLineHelpWriterTest, HandlesHelpText)
334 {
335     const char* const help[] = { "Help text", "for testing." };
336     using gmx::IntegerOption;
337
338     gmx::Options options;
339     options.addOption(IntegerOption("int").description("Integer option").defaultValue(2));
340
341     gmx::CommandLineHelpWriter writer(options);
342     writer.setHelpText(help);
343     checkHelp(&writer);
344 }
345
346 /*
347  * Test known issue output.
348  */
349 TEST_F(CommandLineHelpWriterTest, HandlesKnownIssues)
350 {
351     const char* const bugs[] = { "This is a bug.", "And this is another one." };
352     using gmx::IntegerOption;
353
354     gmx::Options options;
355     options.addOption(IntegerOption("int").description("Integer option").defaultValue(2));
356
357     gmx::CommandLineHelpWriter writer(options);
358     writer.setKnownIssues(bugs);
359     checkHelp(&writer);
360 }
361
362 } // namespace