eb05df8a9bf31eb6c8b91fc61e229ffb77b624a9
[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, 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/filestream.h"
59 #include "gromacs/utility/gmxassert.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/refdata.h"
66 #include "testutils/testfilemanager.h"
67 #include "testutils/textblockmatchers.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 == NULL)
98         {
99             throw std::bad_alloc();
100         }
101         args_.push_back(arg);
102         argv_.push_back(arg);
103     }
104     argv_.push_back(NULL);
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(NULL, 0))
121 {
122 }
123
124 CommandLine::CommandLine(const ConstArrayRef<const char *> &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 ConstArrayRef<const char *> &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 == NULL)
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(NULL);
159     impl_->argc_ = static_cast<int>(newSize);
160 }
161
162 namespace
163 {
164
165 //! Helper function for converting values to strings
166 template <typename T>
167 std::string value2string(T value)
168 {
169     std::stringstream ss;
170     ss << value;
171     return ss.str();
172 }
173
174 }       // namespace
175
176 void CommandLine::addOption(const char *name, const char *value)
177 {
178     append(name);
179     append(value);
180 }
181
182 void CommandLine::addOption(const char *name, const std::string &value)
183 {
184     addOption(name, value.c_str());
185 }
186
187 void CommandLine::addOption(const char *name, int value)
188 {
189     append(name);
190     append(value2string(value));
191 }
192
193 void CommandLine::addOption(const char *name, double value)
194 {
195     append(name);
196     append(value2string(value));
197 }
198
199 void CommandLine::merge(const CommandLine &args)
200 {
201     if (args.argc() > 0)
202     {
203         // Skip first argument if it is the module name.
204         const int firstArg = (args.arg(0)[0] == '-' ? 0 : 1);
205         for (int i = firstArg; i < args.argc(); ++i)
206         {
207             append(args.arg(i));
208         }
209     }
210 }
211
212 int &CommandLine::argc()
213 {
214     return impl_->argc_;
215 }
216 char **CommandLine::argv()
217 {
218     return &impl_->argv_[0];
219 }
220 int CommandLine::argc() const
221 {
222     return impl_->argc_;
223 }
224 const char *const *CommandLine::argv() const
225 {
226     return &impl_->argv_[0];
227 }
228 const char *CommandLine::arg(int i) const
229 {
230     return impl_->argv_[i];
231 }
232
233 std::string CommandLine::toString() const
234 {
235     return CommandLineProgramContext(argc(), argv()).commandLine();
236 }
237
238 /********************************************************************
239  * CommandLineTestHelper::Impl
240  */
241
242 class CommandLineTestHelper::Impl
243 {
244     public:
245         struct OutputFileInfo
246         {
247             OutputFileInfo(const char *option, const std::string &path,
248                            TextBlockMatcherPointer matcher)
249                 : option(option), path(path), matcher(move(matcher))
250             {
251             }
252             OutputFileInfo(OutputFileInfo &&other)
253                 : option(std::move(other.option)), path(std::move(other.path)),
254                   matcher(std::move(other.matcher))
255             {
256             }
257
258             OutputFileInfo &operator=(OutputFileInfo &&other)
259             {
260                 option  = std::move(other.option);
261                 path    = std::move(other.path);
262                 matcher = std::move(other.matcher);
263                 return *this;
264             }
265
266             std::string              option;
267             std::string              path;
268             TextBlockMatcherPointer  matcher;
269         };
270
271         typedef std::vector<OutputFileInfo>        OutputFileList;
272
273         explicit Impl(TestFileManager *fileManager)
274             : fileManager_(*fileManager)
275         {
276         }
277
278         TestFileManager &fileManager_;
279         OutputFileList   outputFiles_;
280 };
281
282 /********************************************************************
283  * CommandLineTestHelper
284  */
285
286 // static
287 int CommandLineTestHelper::runModuleDirect(
288         ICommandLineModule *module, CommandLine *commandLine)
289 {
290     CommandLineModuleSettings settings;
291     module->init(&settings);
292     return module->run(commandLine->argc(), commandLine->argv());
293 }
294
295 // static
296 int CommandLineTestHelper::runModuleDirect(
297         std::unique_ptr<ICommandLineOptionsModule> module, CommandLine *commandLine)
298 {
299     // The name and description are not used in the tests, so they can be NULL.
300     const std::unique_ptr<ICommandLineModule> wrapperModule(
301             ICommandLineOptionsModule::createModule(NULL, NULL, std::move(module)));
302     return runModuleDirect(wrapperModule.get(), commandLine);
303 }
304
305 // static
306 int CommandLineTestHelper::runModuleFactory(
307         std::function<std::unique_ptr<ICommandLineOptionsModule>()>  factory,
308         CommandLine                                                 *commandLine)
309 {
310     return runModuleDirect(factory(), commandLine);
311 }
312
313 CommandLineTestHelper::CommandLineTestHelper(TestFileManager *fileManager)
314     : impl_(new Impl(fileManager))
315 {
316 }
317
318 CommandLineTestHelper::~CommandLineTestHelper()
319 {
320 }
321
322 void CommandLineTestHelper::setInputFileContents(
323         CommandLine *args, const char *option, const char *extension,
324         const std::string &contents)
325 {
326     GMX_ASSERT(extension[0] != '.', "Extension should not contain a dot");
327     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(
328                 formatString("%d.%s", args->argc(), extension));
329     TextWriter::writeFileFromString(fullFilename, contents);
330     args->addOption(option, fullFilename);
331 }
332
333 void CommandLineTestHelper::setInputFileContents(
334         CommandLine *args, const char *option, const char *extension,
335         const ConstArrayRef<const char *> &contents)
336 {
337     GMX_ASSERT(extension[0] != '.', "Extension should not contain a dot");
338     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(
339                 formatString("%d.%s", args->argc(), extension));
340     TextWriter  file(fullFilename);
341     ConstArrayRef<const char *>::const_iterator i;
342     for (i = contents.begin(); i != contents.end(); ++i)
343     {
344         file.writeLine(*i);
345     }
346     file.close();
347     args->addOption(option, fullFilename);
348 }
349
350 void CommandLineTestHelper::setOutputFile(
351         CommandLine *args, const char *option, const char *filename,
352         const ITextBlockMatcherSettings &matcher)
353 {
354     std::string suffix(filename);
355     if (startsWith(filename, "."))
356     {
357         suffix = formatString("%d.%s", args->argc(), filename);
358     }
359     std::string fullFilename = impl_->fileManager_.getTemporaryFilePath(suffix);
360     args->addOption(option, fullFilename);
361     impl_->outputFiles_.emplace_back(option, fullFilename, matcher.createMatcher());
362 }
363
364 void CommandLineTestHelper::checkOutputFiles(TestReferenceChecker checker) const
365 {
366     if (!impl_->outputFiles_.empty())
367     {
368         TestReferenceChecker                 outputChecker(
369                 checker.checkCompound("OutputFiles", "Files"));
370         Impl::OutputFileList::const_iterator outfile;
371         for (outfile = impl_->outputFiles_.begin();
372              outfile != impl_->outputFiles_.end();
373              ++outfile)
374         {
375             TestReferenceChecker fileChecker(
376                     outputChecker.checkCompound("File", outfile->option.c_str()));
377             TextInputFile        stream(outfile->path);
378             outfile->matcher->checkStream(&stream, &fileChecker);
379             stream.close();
380         }
381     }
382 }
383
384 /********************************************************************
385  * CommandLineTestBase::Impl
386  */
387
388 class CommandLineTestBase::Impl
389 {
390     public:
391         Impl() : helper_(&tempFiles_)
392         {
393             cmdline_.append("module");
394         }
395
396         TestReferenceData     data_;
397         TestFileManager       tempFiles_;
398         CommandLineTestHelper helper_;
399         CommandLine           cmdline_;
400 };
401
402 /********************************************************************
403  * CommandLineTestBase
404  */
405
406 CommandLineTestBase::CommandLineTestBase()
407     : impl_(new Impl)
408 {
409 }
410
411 CommandLineTestBase::~CommandLineTestBase()
412 {
413 }
414
415 void CommandLineTestBase::setInputFile(
416         const char *option, const char *filename)
417 {
418     impl_->cmdline_.addOption(option, TestFileManager::getInputFilePath(filename));
419 }
420
421 void CommandLineTestBase::setInputFileContents(
422         const char *option, const char *extension, const std::string &contents)
423 {
424     impl_->helper_.setInputFileContents(&impl_->cmdline_, option, extension,
425                                         contents);
426 }
427
428 void CommandLineTestBase::setInputFileContents(
429         const char *option, const char *extension,
430         const ConstArrayRef<const char *> &contents)
431 {
432     impl_->helper_.setInputFileContents(&impl_->cmdline_, option, extension,
433                                         contents);
434 }
435
436 void CommandLineTestBase::setOutputFile(
437         const char *option, const char *filename,
438         const ITextBlockMatcherSettings &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::testWriteHelp(ICommandLineModule *module)
459 {
460     StringOutputStream     stream;
461     TextWriter             writer(&stream);
462     CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, nullptr, "test");
463     context.setModuleDisplayName(formatString("%s %s", "test", module->name()));
464     module->writeHelp(context);
465     TestReferenceChecker   checker(rootChecker());
466     checker.checkTextBlock(stream.toString(), "HelpOutput");
467 }
468
469 void CommandLineTestBase::checkOutputFiles()
470 {
471     impl_->helper_.checkOutputFiles(rootChecker());
472 }
473
474 } // namespace test
475 } // namespace gmx