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