Merge branch release-2018
[alexxy/gromacs.git] / src / testutils / cmdlinetest.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,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.
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  * Implements classes from cmdlinetest.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_testutils
41  */
42 #include "gmxpre.h"
43
44 #include "cmdlinetest.h"
45
46 #include <cstdlib>
47 #include <cstring>
48
49 #include <memory>
50 #include <new>
51 #include <sstream>
52 #include <vector>
53
54 #include "gromacs/commandline/cmdlinehelpcontext.h"
55 #include "gromacs/commandline/cmdlineoptionsmodule.h"
56 #include "gromacs/commandline/cmdlineprogramcontext.h"
57 #include "gromacs/utility/arrayref.h"
58 #include "gromacs/utility/gmxassert.h"
59 #include "gromacs/utility/strconvert.h"
60 #include "gromacs/utility/stringstream.h"
61 #include "gromacs/utility/stringutil.h"
62 #include "gromacs/utility/textreader.h"
63 #include "gromacs/utility/textwriter.h"
64
65 #include "testutils/filematchers.h"
66 #include "testutils/refdata.h"
67 #include "testutils/testfilemanager.h"
68
69 namespace gmx
70 {
71 namespace test
72 {
73
74 /********************************************************************
75  * CommandLine::Impl
76  */
77
78 class CommandLine::Impl
79 {
80     public:
81         Impl(const char *const cmdline[], size_t count);
82         ~Impl();
83
84         std::vector<char *>     args_;
85         std::vector<char *>     argv_;
86         int                     argc_;
87 };
88
89 CommandLine::Impl::Impl(const char *const cmdline[], size_t count)
90 {
91     args_.reserve(count);
92     argv_.reserve(count + 1);
93     argc_ = static_cast<int>(count);
94     for (size_t i = 0; i < count; ++i)
95     {
96         char *arg = strdup(cmdline[i]);
97         if (arg == nullptr)
98         {
99             throw std::bad_alloc();
100         }
101         args_.push_back(arg);
102         argv_.push_back(arg);
103     }
104     argv_.push_back(nullptr);
105 }
106
107 CommandLine::Impl::~Impl()
108 {
109     for (size_t i = 0; i < args_.size(); ++i)
110     {
111         std::free(args_[i]);
112     }
113 }
114
115 /********************************************************************
116  * CommandLine
117  */
118
119 CommandLine::CommandLine()
120     : impl_(new Impl(nullptr, 0))
121 {
122 }
123
124 CommandLine::CommandLine(const ArrayRef<const char *const> &cmdline)
125     : impl_(new Impl(cmdline.data(), cmdline.size()))
126 {
127 }
128
129 CommandLine::CommandLine(const CommandLine &other)
130     : impl_(new Impl(other.argv(), other.argc()))
131 {
132 }
133
134 CommandLine::~CommandLine()
135 {
136 }
137
138 void CommandLine::initFromArray(const ArrayRef<const char *const> &cmdline)
139 {
140     impl_.reset(new Impl(cmdline.data(), cmdline.size()));
141 }
142
143 void CommandLine::append(const char *arg)
144 {
145     GMX_RELEASE_ASSERT(impl_->argc_ == static_cast<int>(impl_->args_.size()),
146                        "Command-line has been modified externally");
147     size_t newSize = impl_->args_.size() + 1;
148     impl_->args_.reserve(newSize);
149     impl_->argv_.reserve(newSize + 1);
150     char *newArg = strdup(arg);
151     if (newArg == nullptr)
152     {
153         throw std::bad_alloc();
154     }
155     impl_->args_.push_back(newArg);
156     impl_->argv_.pop_back(); // Remove the trailing NULL.
157     impl_->argv_.push_back(newArg);
158     impl_->argv_.push_back(nullptr);
159     impl_->argc_ = static_cast<int>(newSize);
160 }
161
162 void CommandLine::addOption(const char *name)
163 {
164     append(name);
165 }
166
167 void CommandLine::addOption(const char *name, const char *value)
168 {
169     append(name);
170     append(value);
171 }
172
173 void CommandLine::addOption(const char *name, const std::string &value)
174 {
175     addOption(name, value.c_str());
176 }
177
178 void CommandLine::addOption(const char *name, int value)
179 {
180     append(name);
181     append(gmx::toString(value));
182 }
183
184 void CommandLine::addOption(const char *name, double value)
185 {
186     append(name);
187     append(gmx::toString(value));
188 }
189
190 void CommandLine::merge(const CommandLine &args)
191 {
192     if (args.argc() > 0)
193     {
194         // Skip first argument if it is the module name.
195         const int firstArg = (args.arg(0)[0] == '-' ? 0 : 1);
196         for (int i = firstArg; i < args.argc(); ++i)
197         {
198             append(args.arg(i));
199         }
200     }
201 }
202
203 int &CommandLine::argc()
204 {
205     return impl_->argc_;
206 }
207 char **CommandLine::argv()
208 {
209     return &impl_->argv_[0];
210 }
211 int CommandLine::argc() const
212 {
213     return impl_->argc_;
214 }
215 const char *const *CommandLine::argv() const
216 {
217     return &impl_->argv_[0];
218 }
219 const char *CommandLine::arg(int i) const
220 {
221     return impl_->argv_[i];
222 }
223
224 std::string CommandLine::toString() const
225 {
226     return CommandLineProgramContext(argc(), argv()).commandLine();
227 }
228
229 bool CommandLine::contains(const char *name) const
230 {
231     for (int i = 0; i < impl_->argc_; ++i)
232     {
233         if (std::strcmp(arg(i), name) == 0)
234         {
235             return true;
236         }
237     }
238     return false;
239 }
240
241 /********************************************************************
242  * CommandLineTestHelper::Impl
243  */
244
245 class CommandLineTestHelper::Impl
246 {
247     public:
248         struct OutputFileInfo
249         {
250             OutputFileInfo(const char *option, const std::string &path,
251                            FileMatcherPointer matcher)
252                 : option(option), path(path), matcher(move(matcher))
253             {
254             }
255
256             std::string              option;
257             std::string              path;
258             FileMatcherPointer       matcher;
259         };
260
261         typedef std::vector<OutputFileInfo>        OutputFileList;
262
263         explicit Impl(TestFileManager *fileManager)
264             : fileManager_(*fileManager)
265         {
266         }
267
268         TestFileManager &fileManager_;
269         OutputFileList   outputFiles_;
270 };
271
272 /********************************************************************
273  * CommandLineTestHelper
274  */
275
276 // static
277 int CommandLineTestHelper::runModuleDirect(
278         ICommandLineModule *module, CommandLine *commandLine)
279 {
280     CommandLineModuleSettings settings;
281     module->init(&settings);
282     return module->run(commandLine->argc(), commandLine->argv());
283 }
284
285 // static
286 int CommandLineTestHelper::runModuleDirect(
287         std::unique_ptr<ICommandLineOptionsModule> module, CommandLine *commandLine)
288 {
289     // The name and description are not used in the tests, so they can be NULL.
290     const std::unique_ptr<ICommandLineModule> wrapperModule(
291             ICommandLineOptionsModule::createModule(nullptr, nullptr, std::move(module)));
292     return runModuleDirect(wrapperModule.get(), commandLine);
293 }
294
295 // static
296 int CommandLineTestHelper::runModuleFactory(
297         std::function<std::unique_ptr<ICommandLineOptionsModule>()>  factory,
298         CommandLine                                                 *commandLine)
299 {
300     return runModuleDirect(factory(), commandLine);
301 }
302
303 CommandLineTestHelper::CommandLineTestHelper(TestFileManager *fileManager)
304     : impl_(new Impl(fileManager))
305 {
306 }
307
308 CommandLineTestHelper::~CommandLineTestHelper()
309 {
310 }
311
312 void CommandLineTestHelper::setInputFileContents(
313         CommandLine *args, const char *option, const char *extension,
314         const std::string &contents)
315 {
316     GMX_ASSERT(extension[0] != '.', "Extension should not contain a dot");
317     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(
318                 formatString("%d.%s", args->argc(), extension));
319     TextWriter::writeFileFromString(fullFilename, contents);
320     args->addOption(option, fullFilename);
321 }
322
323 void CommandLineTestHelper::setInputFileContents(
324         CommandLine *args, const char *option, const char *extension,
325         const ArrayRef<const char *const> &contents)
326 {
327     GMX_ASSERT(extension[0] != '.', "Extension should not contain a dot");
328     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(
329                 formatString("%d.%s", args->argc(), extension));
330     TextWriter  file(fullFilename);
331     ArrayRef<const char *const>::const_iterator i;
332     for (i = contents.begin(); i != contents.end(); ++i)
333     {
334         file.writeLine(*i);
335     }
336     file.close();
337     args->addOption(option, fullFilename);
338 }
339
340 void CommandLineTestHelper::setOutputFile(
341         CommandLine *args, const char *option, const char *filename,
342         const ITextBlockMatcherSettings &matcher)
343 {
344     setOutputFile(args, option, filename, TextFileMatch(matcher));
345 }
346
347 void CommandLineTestHelper::setOutputFile(
348         CommandLine *args, const char *option, const char *filename,
349         const IFileMatcherSettings &matcher)
350 {
351     std::string suffix(filename);
352     if (startsWith(filename, "."))
353     {
354         suffix = formatString("%d.%s", args->argc(), filename);
355     }
356     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(suffix);
357     args->addOption(option, fullFilename);
358     impl_->outputFiles_.emplace_back(option, fullFilename, matcher.createFileMatcher());
359 }
360
361 void CommandLineTestHelper::checkOutputFiles(TestReferenceChecker checker) const
362 {
363     if (!impl_->outputFiles_.empty())
364     {
365         TestReferenceChecker                 outputChecker(
366                 checker.checkCompound("OutputFiles", "Files"));
367         Impl::OutputFileList::const_iterator outfile;
368         for (const auto &outfile : impl_->outputFiles_)
369         {
370             TestReferenceChecker fileChecker(
371                     outputChecker.checkCompound("File", outfile.option.c_str()));
372             outfile.matcher->checkFile(outfile.path, &fileChecker);
373         }
374     }
375 }
376
377 /********************************************************************
378  * CommandLineTestBase::Impl
379  */
380
381 class CommandLineTestBase::Impl
382 {
383     public:
384         Impl() : helper_(&tempFiles_)
385         {
386             cmdline_.append("module");
387         }
388
389         TestReferenceData     data_;
390         TestFileManager       tempFiles_;
391         CommandLineTestHelper helper_;
392         CommandLine           cmdline_;
393 };
394
395 /********************************************************************
396  * CommandLineTestBase
397  */
398
399 CommandLineTestBase::CommandLineTestBase()
400     : impl_(new Impl)
401 {
402 }
403
404 CommandLineTestBase::~CommandLineTestBase()
405 {
406 }
407
408 void CommandLineTestBase::setInputFile(
409         const char *option, const char *filename)
410 {
411     impl_->cmdline_.addOption(option, TestFileManager::getInputFilePath(filename));
412 }
413
414 void CommandLineTestBase::setInputFileContents(
415         const char *option, const char *extension, const std::string &contents)
416 {
417     impl_->helper_.setInputFileContents(&impl_->cmdline_, option, extension,
418                                         contents);
419 }
420
421 void CommandLineTestBase::setInputFileContents(
422         const char *option, const char *extension,
423         const ArrayRef<const char *const> &contents)
424 {
425     impl_->helper_.setInputFileContents(&impl_->cmdline_, option, extension,
426                                         contents);
427 }
428
429 void CommandLineTestBase::setOutputFile(
430         const char *option, const char *filename,
431         const ITextBlockMatcherSettings &matcher)
432 {
433     impl_->helper_.setOutputFile(&impl_->cmdline_, option, filename, matcher);
434 }
435
436 void CommandLineTestBase::setOutputFile(
437         const char *option, const char *filename,
438         const IFileMatcherSettings &matcher)
439 {
440     impl_->helper_.setOutputFile(&impl_->cmdline_, option, filename, matcher);
441 }
442
443 CommandLine &CommandLineTestBase::commandLine()
444 {
445     return impl_->cmdline_;
446 }
447
448 TestFileManager &CommandLineTestBase::fileManager()
449 {
450     return impl_->tempFiles_;
451 }
452
453 TestReferenceChecker CommandLineTestBase::rootChecker()
454 {
455     return impl_->data_.rootChecker();
456 }
457
458 void CommandLineTestBase::setDefaultTolerance(const FloatingPointTolerance &tolerance)
459 {
460     impl_->data_.rootChecker().setDefaultTolerance(tolerance);
461 }
462
463 void CommandLineTestBase::testWriteHelp(ICommandLineModule *module)
464 {
465     StringOutputStream     stream;
466     TextWriter             writer(&stream);
467     CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, nullptr, "test");
468     context.setModuleDisplayName(formatString("%s %s", "test", module->name()));
469     module->writeHelp(context);
470     TestReferenceChecker   checker(rootChecker());
471     checker.checkTextBlock(stream.toString(), "HelpOutput");
472 }
473
474 void CommandLineTestBase::checkOutputFiles()
475 {
476     impl_->helper_.checkOutputFiles(rootChecker());
477 }
478
479 } // namespace test
480 } // namespace gmx