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