a06ddc913eb4f50c371fc51b6124ec81205170ef
[alexxy/gromacs.git] / src / testutils / include / testutils / cmdlinetest.h
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,2018,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 /*! \libinternal \file
37  * \brief
38  * Declares utilities testing command-line programs.
39  *
40  * \author Teemu Murtola <teemu.murtola@gmail.com>
41  * \inlibraryapi
42  * \ingroup module_testutils
43  */
44 #ifndef GMX_TESTUTILS_CMDLINETEST_H
45 #define GMX_TESTUTILS_CMDLINETEST_H
46
47 #include <functional>
48 #include <memory>
49 #include <string>
50
51 #include <gtest/gtest.h>
52
53 // arrayref.h is not strictly necessary for this header, but nearly all
54 // callers will need it to use the constructor that takes ArrayRef.
55 #include "gromacs/utility/arrayref.h"
56 #include "gromacs/utility/classhelpers.h"
57
58 namespace gmx
59 {
60
61 class ICommandLineModule;
62 class ICommandLineOptionsModule;
63
64 namespace test
65 {
66
67 class FloatingPointTolerance;
68 class IFileMatcherSettings;
69 class ITextBlockMatcherSettings;
70 class TestFileManager;
71 class TestReferenceChecker;
72
73 /*! \libinternal \brief
74  * Helper class for tests that need to construct command lines.
75  *
76  * This class helps in writing tests for command-line handling.
77  * The constructor method takes an array of const char pointers, specifying the
78  * command-line arguments, each as one array element.  It is also possible to
79  * construct the command line by adding individual arguments with append() and
80  * addOption().
81  * The argc() and argv() methods can then be used to obtain `argc` and `argv`
82  * (non-const char pointers) arrays for passing into methods that expect these.
83  *
84  * Note that although the interface allows passing the argc and argv pointers
85  * to methods that modify them (typically as \p f(&argc(), argv())), currently
86  * the CommandLine object is not in a consistent state internally if the
87  * parameters are actually modified.  Reading the command line is possible
88  * afterwards, but modification is not.
89  *
90  * If you need to construct command lines that refer to files on the file
91  * system, see CommandLineTestHelper and CommandLineTestBase for additional
92  * convenience utilities.
93  *
94  * All constructors and methods that modify this class may throw an
95  * std::bad_alloc.  Const methods and accessors do not throw.
96  *
97  * \inlibraryapi
98  * \ingroup module_testutils
99  */
100 class CommandLine
101 {
102 public:
103     //! Initializes an empty command-line object.
104     CommandLine();
105     /*! \brief
106      * Initializes a command-line object from an array.
107      *
108      * \param[in] cmdline  Array of command-line arguments.
109      *
110      * \p cmdline should include the binary name as the first element if
111      * that is desired in the output.
112      *
113      * This constructor is not explicit to make it possible to create a
114      * CommandLine object directly from a C array.
115      */
116     CommandLine(const ArrayRef<const char* const>& cmdline);
117     //! \copydoc CommandLine(const ArrayRef<const char *const> &)
118     CommandLine(const ArrayRef<const std::string>& cmdline);
119     //! Creates a deep copy of a command-line object.
120     CommandLine(const CommandLine& other);
121     ~CommandLine();
122
123     /*! \brief
124      * Initializes a command-line object in-place from an array.
125      *
126      * \param[in] cmdline  Array of command-line arguments.
127      *
128      * \p cmdline should include the binary name as the first element if
129      * that is desired in the output.
130      *
131      * This function does the same as the constructor that takes a
132      * ArrayRef.  Any earlier contents of the object are discarded.
133      *
134      * Strong exception safety.
135      */
136     void initFromArray(const ArrayRef<const char* const>& cmdline);
137
138     /*! \brief
139      * Appends an argument to the command line.
140      *
141      * \param[in] arg  Argument to append.
142      *
143      * Strong exception safety.
144      */
145     void append(const char* arg);
146     //! Convenience overload taking a std::string.
147     void append(const std::string& arg) { append(arg.c_str()); }
148     /*! \brief
149      * Adds an option to the command line, typically a boolean.
150      *
151      * \param[in] name   Name of the option to append, which
152      *                   should start with "-".
153      */
154     void addOption(const char* name);
155     /*! \brief
156      * Adds an option-value pair to the command line.
157      *
158      * \param[in] name   Name of the option to append, which
159      *                   should start with "-".
160      * \param[in] value  Value of the argument to append.
161      */
162     void addOption(const char* name, const char* value);
163     //! Convenience overload taking a std::string.
164     void addOption(const char* name, const std::string& value);
165     //! Overload taking an int.
166     void addOption(const char* name, int value);
167     //! Overload taking a double.
168     void addOption(const char* name, double value);
169     /*! \brief
170      * Appends all arguments from \p args to the command line.
171      *
172      * If the first argument of \p args does not start with a `-`, it is
173      * skipped, assuming it is a gmx module name and thus useless.
174      */
175     void merge(const CommandLine& args);
176
177     //! Returns argc for passing into C-style command-line handling.
178     int& argc();
179     //! Returns argv for passing into C-style command-line handling.
180     char** argv();
181     //! Returns argc for passing into C-style command-line handling.
182     int argc() const;
183     //! Returns argv for passing into C-style command-line handling.
184     const char* const* argv() const;
185     //! Returns a single argument.
186     const char* arg(int i) const;
187
188     //! Returns the command line formatted as a single string.
189     std::string toString() const;
190
191     //! Whether the command line contains the given option.
192     bool contains(const char* name) const;
193
194 private:
195     class Impl;
196
197     PrivateImplPointer<Impl> impl_;
198 };
199
200 /*! \libinternal \brief
201  * Helper class for tests that construct command lines that need to reference
202  * existing files.
203  *
204  * This class provides helper methods for:
205  *
206  *   1. Adding input files to a CommandLine instance by generating them from a
207  *      string provided in the test (setInputFileContents()).
208  *   2. Adding output files to a CommandLine instance (setOutputFile()).
209  *   3. Checking the contents of some of the output files using
210  *      TestReferenceData (setOutputFile() and checkOutputFiles()).
211  *   4. Static methods for easily executing command-line modules
212  *      (various overloads of runModule()).
213  *
214  * All files created during the test are cleaned up at the end of the test.
215  *
216  * All methods can throw std::bad_alloc.
217  *
218  * \see TestFileManager
219  * \inlibraryapi
220  * \ingroup module_testutils
221  */
222 class CommandLineTestHelper
223 {
224 public:
225     /*! \brief
226      * Runs a command-line program that implements ICommandLineModule.
227      *
228      * \param[in,out] module       Module to run.
229      *     The function does not take ownership.
230      * \param[in,out] commandLine  Command line parameters to pass.
231      *     This is only modified if \p module modifies it.
232      * \returns The return value of the module.
233      * \throws  unspecified  Any exception thrown by the module.
234      */
235     static int runModuleDirect(ICommandLineModule* module, CommandLine* commandLine);
236     /*! \brief
237      * Runs a command-line program that implements
238      * ICommandLineOptionsModule.
239      *
240      * \param[in,out] module       Module to run.
241      * \param[in,out] commandLine  Command line parameters to pass.
242      *     This is only modified if \p module modifies it.
243      * \returns The return value of the module.
244      * \throws  unspecified  Any exception thrown by the module.
245      */
246     static int runModuleDirect(std::unique_ptr<ICommandLineOptionsModule> module, CommandLine* commandLine);
247     /*! \brief
248      * Runs a command-line program that implements
249      * ICommandLineOptionsModule.
250      *
251      * \param[in] factory          Factory method for the module to run.
252      * \param[in,out] commandLine  Command line parameters to pass.
253      *     This is only modified if the module modifies it.
254      * \returns The return value of the module.
255      * \throws  unspecified  Any exception thrown by the factory or the
256      *     module.
257      */
258     static int runModuleFactory(const std::function<std::unique_ptr<ICommandLineOptionsModule>()>& factory,
259                                 CommandLine* commandLine);
260
261     /*! \brief
262      * Initializes an instance.
263      *
264      * \param  fileManager  File manager to use for generating temporary
265      *     file names and to track temporary files.
266      */
267     explicit CommandLineTestHelper(TestFileManager* fileManager);
268     ~CommandLineTestHelper();
269
270     /*! \brief
271      * Generates and sets an input file.
272      *
273      * \param[in,out] args      CommandLine to which to add the option.
274      * \param[in]     option    Option to set.
275      * \param[in]     extension Extension for the file to create.
276      * \param[in]     contents  Text to write to the input file.
277      *
278      * Creates a temporary file with contents from \p contents, and adds
279      * \p option to \p args with a value that points to the generated file.
280      */
281     void setInputFileContents(CommandLine*       args,
282                               const char*        option,
283                               const char*        extension,
284                               const std::string& contents);
285     /*! \brief
286      * Generates and sets an input file.
287      *
288      * \param[in,out] args      CommandLine to which to add the option.
289      * \param[in]     option    Option to set.
290      * \param[in]     extension Extension for the file to create.
291      * \param[in]     contents  Text to write to the input file.
292      *
293      * Creates a temporary file with contents from \p contents (each array
294      * entry on its own line), and adds \p option to \p args with a value
295      * that points to the generated file.
296      */
297     void setInputFileContents(CommandLine*                       args,
298                               const char*                        option,
299                               const char*                        extension,
300                               const ArrayRef<const char* const>& contents);
301     /*! \brief
302      * Sets an output file parameter and adds it to the set of tested files.
303      *
304      * \param[in,out] args      CommandLine to which to add the option.
305      * \param[in]     option    Option to set.
306      * \param[in]     filename  Name of the output file.
307      * \param[in]     matcher   Specifies how the contents of the file are
308      *     tested.
309      *
310      * This method does the following:
311      *  - Adds \p option to \p args to point a temporary file name
312      *    constructed from \p filename.
313      *  - Makes checkOutputFiles() to check the contents of the file
314      *    against reference data, using \p matcher.
315      *  - Marks the temporary file for removal at test teardown.
316      *
317      * \p filename is given to TestTemporaryFileManager to make a unique
318      * filename for the temporary file.
319      * If \p filename starts with a dot, a unique number is prefixed (such
320      * that it is possible to create multiple files with the same extension
321      * by just specifying the extension for every call of setOutputFile()).
322      *
323      * If the output file is needed to trigger some computation, or is
324      * unconditionally produced by the code under test, but the contents
325      * are not interesting for the test, use NoContentsMatch as the matcher.
326      * Note that the existence of the output file is still verified.
327      */
328     void setOutputFile(CommandLine*                     args,
329                        const char*                      option,
330                        const char*                      filename,
331                        const ITextBlockMatcherSettings& matcher);
332     //! \copydoc setOutputFile(CommandLine *, const char *, const char *, const ITextBlockMatcherSettings &)
333     void setOutputFile(CommandLine*                args,
334                        const char*                 option,
335                        const char*                 filename,
336                        const IFileMatcherSettings& matcher);
337
338     /*! \brief
339      * Checks output files added with setOutputFile() against reference
340      * data.
341      *
342      * \param     checker  Reference data root location where the reference
343      *     data is stored.
344      *
345      * The file contents are tested verbatim, using direct string
346      * comparison.  The text can be found verbatim in the reference data
347      * XML files for manual inspection.
348      *
349      * Generates non-fatal test failures if some output file contents do
350      * not match the reference data.
351      */
352     void checkOutputFiles(TestReferenceChecker checker) const;
353
354 private:
355     class Impl;
356
357     PrivateImplPointer<Impl> impl_;
358 };
359
360 /*! \libinternal \brief
361  * Test fixture for tests that call a single command-line program with
362  * input/output files.
363  *
364  * This class provides a convenient package for using CommandLineTestHelper in
365  * a test that do not need special customization.  It takes care of creating
366  * the other necessary objects (like TestFileManager, TestReferenceData, and
367  * CommandLine) and wrapping the methods from CommandLineTestHelper such that
368  * extra parameters are not needed.  Additionally, it provides setInputFile()
369  * as a convenience function for adding a fixed input file, pointing to a file
370  * that resides in the source tree.
371  *
372  * \see CommandLineTestHelper
373  * \inlibraryapi
374  * \ingroup module_testutils
375  */
376 class CommandLineTestBase : public ::testing::Test
377 {
378 public:
379     CommandLineTestBase();
380     ~CommandLineTestBase() override;
381
382     /*! \brief
383      * Sets an input file.
384      *
385      * \param[in]     option    Option to set.
386      * \param[in]     filename  Name of the input file.
387      *
388      * \see TestFileManager::getInputFilePath()
389      */
390     void setInputFile(const char* option, const char* filename);
391     //! \copydoc setInputFile(const char *, const char *);
392     void setInputFile(const char* option, const std::string& filename);
393     /*! \brief
394      * Sets an input file that may be modified. The file is copied to a
395      * temporary file, which is used as the test input
396      *
397      * \param[in]     option    Option to set.
398      * \param[in]     filename  Name of the input file.
399      *
400      */
401     void setModifiableInputFile(const char* option, const char* filename);
402     //! \copydoc setModifiableInputFile(const char *, const char *);
403     void setModifiableInputFile(const char* option, const std::string& filename);
404     /*! \brief
405      * Generates and sets an input file.
406      *
407      * \see CommandLineTestHelper::setInputFileContents()
408      */
409     void setInputFileContents(const char* option, const char* extension, const std::string& contents);
410     /*! \brief
411      * Generates and sets an input file.
412      *
413      * \see CommandLineTestHelper::setInputFileContents()
414      */
415     void setInputFileContents(const char*                        option,
416                               const char*                        extension,
417                               const ArrayRef<const char* const>& contents);
418     /*! \brief
419      * Sets an output file parameter and adds it to the set of tested files.
420      *
421      * \see CommandLineTestHelper::setOutputFile()
422      */
423     void setOutputFile(const char* option, const char* filename, const ITextBlockMatcherSettings& matcher);
424     /*! \brief
425      * Sets an output file parameter and adds it to the set of tested files.
426      *
427      * \see CommandLineTestHelper::setOutputFile()
428      */
429     void setOutputFile(const char* option, const char* filename, const IFileMatcherSettings& matcher);
430     /*! \brief
431      * Sets a file parameter that is used for input and modified as output. The input file
432      * is copied to a temporary file that is used as input and can be modified.
433      */
434     void setInputAndOutputFile(const char*                      option,
435                                const char*                      filename,
436                                const ITextBlockMatcherSettings& matcher);
437     //! \copydoc setInputAndOutputFile(const char *, const char *, const ITextBlockMatcherSettings&);
438     void setInputAndOutputFile(const char* option, const char* filename, const IFileMatcherSettings& matcher);
439
440     /*! \brief
441      * Returns the internal CommandLine object used to construct the
442      * command line for the test.
443      *
444      * Derived test fixtures can use this to add additional options, and
445      * to access the final command line to do the actual call that is being
446      * tested.
447      *
448      * Does not throw.
449      */
450     CommandLine& commandLine();
451     /*! \brief
452      * Returns the internal TestFileManager object used to manage the
453      * files.
454      *
455      * Derived test fixtures can use this to manage files in cases the
456      * canned methods are not sufficient.
457      *
458      * Does not throw.
459      */
460     TestFileManager& fileManager();
461     /*! \brief
462      * Returns the root reference data checker.
463      *
464      * Derived test fixtures can use this to check other things than output
465      * file contents.
466      */
467     TestReferenceChecker rootChecker();
468     /*! \brief
469      * Sets the tolerance for floating-point comparisons.
470      *
471      * All following floating-point comparisons using the checker will use
472      * the new tolerance.
473      *
474      * Does not throw.
475      */
476     void setDefaultTolerance(const FloatingPointTolerance& tolerance);
477     /*! \brief
478      * Checks the output of writeHelp() against reference data.
479      */
480     void testWriteHelp(ICommandLineModule* module);
481     /*! \brief
482      * Checks output files added with setOutputFile() against reference
483      * data.
484      *
485      * \see CommandLineTestHelper::checkOutputFiles()
486      */
487     void checkOutputFiles();
488
489 private:
490     class Impl;
491
492     PrivateImplPointer<Impl> impl_;
493 };
494
495 } // namespace test
496 } // namespace gmx
497
498 #endif