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