From 125bb863956da2ddaa463c70c122d86da9f9e420 Mon Sep 17 00:00:00 2001 From: Teemu Murtola Date: Sat, 16 May 2015 07:16:04 +0300 Subject: [PATCH] Some tests for 'gmx help -export rst' Add basic test that checks 'gmx help -export rst' for regression. The mechanism also makes it easier to test additional work in this area. Minor changes in the command-line module to support testing: - Make all output from 'gmx help -export' capturable with a new FileOutputRedirectorInterface interface. - Make it easier to mock a CommandLineOptionsModuleInterface Change-Id: I58982b3de7e946c373ffb737ef994b4e527418bc --- src/gromacs/commandline/cmdlinehelpmodule.cpp | 43 ++--- src/gromacs/commandline/cmdlinehelpmodule.h | 10 +- .../commandline/cmdlinemodulemanager.cpp | 5 +- .../commandline/cmdlinemodulemanager.h | 22 ++- .../commandline/cmdlineoptionsmodule.cpp | 33 +++- .../commandline/cmdlineoptionsmodule.h | 25 ++- .../tests/cmdlinemodulemanager.cpp | 132 ++++++++++++--- ...mmandLineModuleManagerTest_ExportsHelp.xml | 110 ++++++++++++ ...dLineModuleManagerTest_RunsGeneralHelp.xml | 26 +++ src/gromacs/utility/file.cpp | 6 + src/gromacs/utility/file.h | 45 +++++ src/gromacs/utility/fileredirector.cpp | 90 ++++++++++ src/gromacs/utility/fileredirector.h | 107 ++++++++++++ src/testutils/stringtest.cpp | 159 ++++++++++++++++-- src/testutils/stringtest.h | 46 ++++- 15 files changed, 776 insertions(+), 83 deletions(-) create mode 100644 src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_ExportsHelp.xml create mode 100644 src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_RunsGeneralHelp.xml create mode 100644 src/gromacs/utility/fileredirector.cpp create mode 100644 src/gromacs/utility/fileredirector.h diff --git a/src/gromacs/commandline/cmdlinehelpmodule.cpp b/src/gromacs/commandline/cmdlinehelpmodule.cpp index 51241de071..17233eba54 100644 --- a/src/gromacs/commandline/cmdlinehelpmodule.cpp +++ b/src/gromacs/commandline/cmdlinehelpmodule.cpp @@ -60,6 +60,7 @@ #include "gromacs/utility/baseversion.h" #include "gromacs/utility/exceptions.h" #include "gromacs/utility/file.h" +#include "gromacs/utility/fileredirector.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/programcontext.h" #include "gromacs/utility/stringutil.h" @@ -99,7 +100,7 @@ class CommandLineHelpModuleImpl const CommandLineModuleInterface *moduleOverride_; bool bHidden_; - File *outputOverride_; + FileOutputRedirectorInterface *outputRedirector_; GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl); }; @@ -171,6 +172,7 @@ void RootHelpTopic::writeHelp(const HelpWriterContext &context) const // to determine such a set... writeSubTopicList(context, "Additional help is available on the following topics:"); + // TODO: Make these respect the binary name passed in, to make tests work better. context.writeTextBlock("To access the help, use '[PROGRAM] help '."); context.writeTextBlock("For help on a command, use '[PROGRAM] help '."); } @@ -434,14 +436,16 @@ class HelpExportReStructuredText : public HelpExportInterface virtual void finishModuleGroupExport(); private: - HelpLinks links_; - boost::scoped_ptr indexFile_; - boost::scoped_ptr manPagesFile_; + FileOutputRedirectorInterface *outputRedirector_; + HelpLinks links_; + boost::scoped_ptr indexFile_; + boost::scoped_ptr manPagesFile_; }; HelpExportReStructuredText::HelpExportReStructuredText( const CommandLineHelpModuleImpl &helpModule) - : links_(eHelpOutputFormat_Rst) + : outputRedirector_(helpModule.outputRedirector_), + links_(eHelpOutputFormat_Rst) { File linksFile("links.dat", "r"); std::string line; @@ -458,10 +462,12 @@ HelpExportReStructuredText::HelpExportReStructuredText( void HelpExportReStructuredText::startModuleExport() { - indexFile_.reset(new File("programs/byname.rst", "w")); + indexFile_.reset( + new File(outputRedirector_->openFileForWriting("programs/byname.rst"))); indexFile_->writeLine("Tools by Name"); indexFile_->writeLine("============="); - manPagesFile_.reset(new File("conf-man.py", "w")); + manPagesFile_.reset( + new File(outputRedirector_->openFileForWriting("conf-man.py"))); manPagesFile_->writeLine("man_pages = ["); } @@ -472,7 +478,7 @@ void HelpExportReStructuredText::exportModuleHelp( { // TODO: Ideally, the file would only be touched if it really changes. // This would make Sphinx reruns much faster. - File file("programs/" + tag + ".rst", "w"); + File file(outputRedirector_->openFileForWriting("programs/" + tag + ".rst")); file.writeLine(formatString(".. _%s:", displayName.c_str())); if (0 == displayName.compare("gmx mdrun")) { @@ -518,10 +524,12 @@ void HelpExportReStructuredText::finishModuleExport() void HelpExportReStructuredText::startModuleGroupExport() { - indexFile_.reset(new File("programs/bytopic.rst", "w")); + indexFile_.reset( + new File(outputRedirector_->openFileForWriting("programs/bytopic.rst"))); indexFile_->writeLine("Tools by Topic"); indexFile_->writeLine("=============="); - manPagesFile_.reset(new File("man/bytopic.rst", "w")); + manPagesFile_.reset( + new File(outputRedirector_->openFileForWriting("man/bytopic.rst"))); } void HelpExportReStructuredText::exportModuleGroup( @@ -642,7 +650,7 @@ CommandLineHelpModuleImpl::CommandLineHelpModuleImpl( : rootTopic_(new RootHelpTopic(*this)), programContext_(programContext), binaryName_(binaryName), modules_(modules), groups_(groups), context_(NULL), moduleOverride_(NULL), bHidden_(false), - outputOverride_(NULL) + outputRedirector_(&defaultFileOutputRedirector()) { } @@ -714,9 +722,10 @@ void CommandLineHelpModule::setModuleOverride( impl_->moduleOverride_ = &module; } -void CommandLineHelpModule::setOutputRedirect(File *output) +void CommandLineHelpModule::setOutputRedirector( + FileOutputRedirectorInterface *output) { - impl_->outputOverride_ = output; + impl_->outputRedirector_ = output; } int CommandLineHelpModule::run(int argc, char *argv[]) @@ -749,15 +758,11 @@ int CommandLineHelpModule::run(int argc, char *argv[]) return 0; } - File *outputFile = &File::standardOutput(); - if (impl_->outputOverride_ != NULL) - { - outputFile = impl_->outputOverride_; - } + File &outputFile = impl_->outputRedirector_->standardOutput(); HelpLinks links(eHelpOutputFormat_Console); initProgramLinks(&links, *impl_); boost::scoped_ptr context( - new CommandLineHelpContext(outputFile, + new CommandLineHelpContext(&outputFile, eHelpOutputFormat_Console, &links)); context->setShowHidden(impl_->bHidden_); if (impl_->moduleOverride_ != NULL) diff --git a/src/gromacs/commandline/cmdlinehelpmodule.h b/src/gromacs/commandline/cmdlinehelpmodule.h index 25eb2376b2..42f25078d6 100644 --- a/src/gromacs/commandline/cmdlinehelpmodule.h +++ b/src/gromacs/commandline/cmdlinehelpmodule.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -52,7 +52,7 @@ namespace gmx { class CommandLineHelpContext; -class File; +class FileOutputRedirectorInterface; class ProgramContextInterface; class CommandLineHelpModuleImpl; @@ -115,12 +115,12 @@ class CommandLineHelpModule : public CommandLineModuleInterface void setModuleOverride(const CommandLineModuleInterface &module); /*! \brief - * Sets a file to write help output to instead of default `stdout`. + * Sets a file redirector for writing help output. * * Used for unit testing; see - * CommandLineModuleManager::setOutputRedirect() for more details. + * CommandLineModuleManager::setOutputRedirector() for more details. */ - void setOutputRedirect(File *output); + void setOutputRedirector(FileOutputRedirectorInterface *output); virtual const char *name() const { return "help"; } virtual const char *shortDescription() const diff --git a/src/gromacs/commandline/cmdlinemodulemanager.cpp b/src/gromacs/commandline/cmdlinemodulemanager.cpp index d29a8212af..cd2c659575 100644 --- a/src/gromacs/commandline/cmdlinemodulemanager.cpp +++ b/src/gromacs/commandline/cmdlinemodulemanager.cpp @@ -436,10 +436,11 @@ void CommandLineModuleManager::setQuiet(bool bQuiet) impl_->bQuiet_ = bQuiet; } -void CommandLineModuleManager::setOutputRedirect(File *output) +void CommandLineModuleManager::setOutputRedirector( + FileOutputRedirectorInterface *output) { impl_->ensureHelpModuleExists(); - impl_->helpModule_->setOutputRedirect(output); + impl_->helpModule_->setOutputRedirector(output); } void CommandLineModuleManager::setSingleModule(CommandLineModuleInterface *module) diff --git a/src/gromacs/commandline/cmdlinemodulemanager.h b/src/gromacs/commandline/cmdlinemodulemanager.h index 8a0522b595..4214ab1f1b 100644 --- a/src/gromacs/commandline/cmdlinemodulemanager.h +++ b/src/gromacs/commandline/cmdlinemodulemanager.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -54,7 +54,7 @@ class CommandLineModuleGroup; class CommandLineModuleGroupData; class CommandLineModuleInterface; class CommandLineProgramContext; -class File; +class FileOutputRedirectorInterface; //! \addtogroup module_commandline //! \{ @@ -192,17 +192,21 @@ class CommandLineModuleManager /*! \brief * Redirects the output of the module manager to a file. * - * \param[in] output File to write the output to. + * \param[in] output File redirector to use for output. * * Normally, the module manager prints explicitly requested text such * as help output to `stdout`, but this method can be used to redirect - * that output to a file. This is used for unit tests, either to keep - * them quiet or to verify that output. To keep implementation options - * open, behavior with `output == NULL` is undefined and should not be - * relied on. For tests, there should only be need to call this a - * single time, right after creating the manager. + * that output to a file. For exporting help from the module manager, + * several files are written, and can be redirected with this method as + * well. + * + * This is used for unit tests, either to keep them quiet or to verify + * that output. To keep implementation options open, behavior with + * `output == NULL` is undefined and should not be relied on. + * For tests, there should only be need to call this a single time, + * right after creating the manager. */ - void setOutputRedirect(File *output); + void setOutputRedirector(FileOutputRedirectorInterface *output); /*! \brief * Makes the manager always run a single module. diff --git a/src/gromacs/commandline/cmdlineoptionsmodule.cpp b/src/gromacs/commandline/cmdlineoptionsmodule.cpp index 7ea4c2511b..a0cb556813 100644 --- a/src/gromacs/commandline/cmdlineoptionsmodule.cpp +++ b/src/gromacs/commandline/cmdlineoptionsmodule.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2014, by the GROMACS development team, led by + * Copyright (c) 2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -73,6 +73,12 @@ class CommandLineOptionsModule : public CommandLineModuleInterface : name_(name), description_(description), factory_(factory) { } + CommandLineOptionsModule(const char *name, const char *description, + CommandLineOptionsModuleInterface *module) + : name_(name), description_(description), factory_(NULL), + module_(module) + { + } virtual const char *name() const { return name_; } virtual const char *shortDescription() const { return description_; } @@ -91,7 +97,11 @@ class CommandLineOptionsModule : public CommandLineModuleInterface void CommandLineOptionsModule::init(CommandLineModuleSettings *settings) { - module_.reset(factory_()); + if (!module_) + { + GMX_RELEASE_ASSERT(factory_ != NULL, "Neither factory nor module provided"); + module_.reset(factory_()); + } module_->init(settings); } @@ -104,7 +114,14 @@ int CommandLineOptionsModule::run(int argc, char *argv[]) void CommandLineOptionsModule::writeHelp(const CommandLineHelpContext &context) const { - boost::scoped_ptr module(factory_()); + boost::scoped_ptr moduleGuard; + CommandLineOptionsModuleInterface *module = module_.get(); + if (!module) + { + GMX_RELEASE_ASSERT(factory_ != NULL, "Neither factory nor module provided"); + moduleGuard.reset(factory_()); + module = moduleGuard.get(); + } Options options(name(), shortDescription()); module->initOptions(&options); CommandLineHelpWriter(options) @@ -164,4 +181,14 @@ void CommandLineOptionsModuleInterface::registerModule( manager->addModule(move(module)); } +// static +void CommandLineOptionsModuleInterface::registerModule( + CommandLineModuleManager *manager, const char *name, + const char *description, CommandLineOptionsModuleInterface *module) +{ + CommandLineModulePointer wrapperModule( + new CommandLineOptionsModule(name, description, module)); + manager->addModule(move(wrapperModule)); +} + } // namespace gmx diff --git a/src/gromacs/commandline/cmdlineoptionsmodule.h b/src/gromacs/commandline/cmdlineoptionsmodule.h index 5958806576..f44022fb08 100644 --- a/src/gromacs/commandline/cmdlineoptionsmodule.h +++ b/src/gromacs/commandline/cmdlineoptionsmodule.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2014, by the GROMACS development team, led by + * Copyright (c) 2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -108,7 +108,7 @@ class CommandLineOptionsModuleInterface * returned by \p factory. Caller must `delete` the object. * \throws std::bad_alloc if out of memory. * - * This is mainly used by unit tests that want to bypass + * This is mainly used by tests that want to bypass * CommandLineModuleManager. */ static CommandLineModuleInterface * @@ -152,6 +152,27 @@ class CommandLineOptionsModuleInterface registerModule(CommandLineModuleManager *manager, const char *name, const char *description, FactoryMethod factory); + /*! \brief + * Registers a module to this manager. + * + * \param manager Manager to register to. + * \param[in] name Name for the module. + * \param[in] description Short description for the module. + * \param[in] module Module to register. + * The method takes ownership (must have been allocated with `new`). + * \throws std::bad_alloc if out of memory. + * + * This method internally creates a CommandLineModuleInterface module + * with the given \p name and \p description, and adds that to + * \p manager. + * + * This method is mainly used by tests that need to have a reference to + * the CommandLineOptionsModuleInterface instance (e.g., for mocking). + */ + static void + registerModule(CommandLineModuleManager *manager, + const char *name, const char *description, + CommandLineOptionsModuleInterface *module); virtual ~CommandLineOptionsModuleInterface(); diff --git a/src/gromacs/commandline/tests/cmdlinemodulemanager.cpp b/src/gromacs/commandline/tests/cmdlinemodulemanager.cpp index d680cfef6e..6ff23d78ec 100644 --- a/src/gromacs/commandline/tests/cmdlinemodulemanager.cpp +++ b/src/gromacs/commandline/tests/cmdlinemodulemanager.cpp @@ -43,17 +43,21 @@ #include "gromacs/commandline/cmdlinemodulemanager.h" -#include +#include #include #include "gromacs/commandline/cmdlinehelpcontext.h" #include "gromacs/commandline/cmdlinemodule.h" +#include "gromacs/commandline/cmdlineoptionsmodule.h" #include "gromacs/commandline/cmdlineprogramcontext.h" +#include "gromacs/options/basicoptions.h" +#include "gromacs/options/options.h" #include "gromacs/utility/file.h" #include "gromacs/onlinehelp/tests/mock_helptopic.h" #include "testutils/cmdlinetest.h" +#include "testutils/stringtest.h" #include "testutils/testasserts.h" #include "testutils/testfilemanager.h" @@ -63,6 +67,16 @@ namespace using gmx::test::CommandLine; using gmx::test::MockHelpTopic; +/*! \brief + * Helper method to disable nice() calls from CommandLineModuleManager. + * + * \ingroup module_commandline + */ +void disableNice(gmx::CommandLineModuleSettings *settings) +{ + settings->setDefaultNiceLevel(0); +} + /******************************************************************** * MockModule */ @@ -92,11 +106,6 @@ class MockModule : public gmx::CommandLineModuleInterface } private: - //! Disable nice() calls for tests. - void disableNice(gmx::CommandLineModuleSettings *settings) - { - settings->setDefaultNiceLevel(0); - } //! Checks the context passed to writeHelp(). void checkHelpContext(const gmx::CommandLineHelpContext &context) const; @@ -110,11 +119,10 @@ MockModule::MockModule(const char *name, const char *description) { using ::testing::_; using ::testing::Invoke; - using ::testing::WithArg; ON_CALL(*this, init(_)) - .WillByDefault(WithArg<0>(Invoke(this, &MockModule::disableNice))); + .WillByDefault(Invoke(&disableNice)); ON_CALL(*this, writeHelp(_)) - .WillByDefault(WithArg<0>(Invoke(this, &MockModule::checkHelpContext))); + .WillByDefault(Invoke(this, &MockModule::checkHelpContext)); } void MockModule::checkHelpContext(const gmx::CommandLineHelpContext &context) const @@ -128,26 +136,57 @@ void MockModule::checkHelpContext(const gmx::CommandLineHelpContext &context) co EXPECT_EQ(expectedDisplayName_, moduleName); } +/******************************************************************** + * MockOptionsModule + */ + +/*! \internal \brief + * Mock implementation of gmx::CommandLineOptionsModuleInterface. + * + * \ingroup module_commandline + */ +class MockOptionsModule : public gmx::CommandLineOptionsModuleInterface +{ + public: + MockOptionsModule(); + + MOCK_METHOD1(init, void(gmx::CommandLineModuleSettings *settings)); + MOCK_METHOD1(initOptions, void(gmx::Options *options)); + MOCK_METHOD1(optionsFinished, void(gmx::Options *options)); + MOCK_METHOD0(run, int()); +}; + +MockOptionsModule::MockOptionsModule() +{ + using ::testing::_; + using ::testing::Invoke; + ON_CALL(*this, init(_)) + .WillByDefault(Invoke(&disableNice)); +} + /******************************************************************** * Test fixture for the tests */ -class CommandLineModuleManagerTest : public ::testing::Test +class CommandLineModuleManagerTest : public gmx::test::StringTestBase { public: void initManager(const CommandLine &args, const char *realBinaryName); - MockModule &addModule(const char *name, const char *description); - MockHelpTopic &addHelpTopic(const char *name, const char *title); + MockModule &addModule(const char *name, const char *description); + MockOptionsModule &addOptionsModule(const char *name, const char *description); + MockHelpTopic &addHelpTopic(const char *name, const char *title); gmx::CommandLineModuleManager &manager() { return *manager_; } - void ignoreManagerOutput(); + void redirectManagerOutput() + { + manager_->setOutputRedirector(&initOutputRedirector(&fileManager_)); + } private: boost::scoped_ptr programContext_; boost::scoped_ptr manager_; gmx::test::TestFileManager fileManager_; - boost::scoped_ptr outputFile_; }; void CommandLineModuleManagerTest::initManager( @@ -169,6 +208,15 @@ CommandLineModuleManagerTest::addModule(const char *name, const char *descriptio return *module; } +MockOptionsModule & +CommandLineModuleManagerTest::addOptionsModule(const char *name, const char *description) +{ + MockOptionsModule *module = new MockOptionsModule(); + gmx::CommandLineOptionsModuleInterface::registerModule( + &manager(), name, description, module); + return *module; +} + MockHelpTopic & CommandLineModuleManagerTest::addHelpTopic(const char *name, const char *title) { @@ -177,13 +225,6 @@ CommandLineModuleManagerTest::addHelpTopic(const char *name, const char *title) return *topic; } -void CommandLineModuleManagerTest::ignoreManagerOutput() -{ - outputFile_.reset( - new gmx::File(fileManager_.getTemporaryFilePath("out.txt"), "w")); - manager().setOutputRedirect(outputFile_.get()); -} - /******************************************************************** * Actual tests */ @@ -195,12 +236,13 @@ TEST_F(CommandLineModuleManagerTest, RunsGeneralHelp) }; CommandLine args(cmdline); initManager(args, "test"); - ignoreManagerOutput(); + redirectManagerOutput(); addModule("module", "First module"); addModule("other", "Second module"); int rc = 0; ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv())); ASSERT_EQ(0, rc); + checkRedirectedOutputFiles(); } TEST_F(CommandLineModuleManagerTest, RunsModule) @@ -310,4 +352,50 @@ TEST_F(CommandLineModuleManagerTest, HandlesConflictingBinaryAndModuleNames) ASSERT_EQ(0, rc); } +/*! \brief + * Initializes Options for help export tests. + * + * \ingroup module_commandline + */ +void initOptionsBasic(gmx::Options *options) +{ + const char *const desc[] = { + "Sample description", + "for testing [THISMODULE]." + }; + options->setDescription(desc); + options->addOption(gmx::IntegerOption("int")); +} + +TEST_F(CommandLineModuleManagerTest, ExportsHelp) +{ + const char *const cmdline[] = { + "test", "help", "-export", "rst" + }; + // TODO: Find a more elegant solution, or get rid of the links.dat altogether. + gmx::File::writeFileFromString("links.dat", ""); + CommandLine args(cmdline); + initManager(args, "test"); + redirectManagerOutput(); + MockOptionsModule &mod1 = addOptionsModule("module", "First module"); + MockOptionsModule &mod2 = addOptionsModule("other", "Second module"); + { + gmx::CommandLineModuleGroup group = manager().addModuleGroup("Group 1"); + group.addModule("module"); + } + { + gmx::CommandLineModuleGroup group = manager().addModuleGroup("Group 2"); + group.addModule("other"); + } + using ::testing::_; + using ::testing::Invoke; + EXPECT_CALL(mod1, initOptions(_)).WillOnce(Invoke(&initOptionsBasic)); + EXPECT_CALL(mod2, initOptions(_)); + int rc = 0; + ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv())); + ASSERT_EQ(0, rc); + checkRedirectedOutputFiles(); + std::remove("links.dat"); +} + } // namespace diff --git a/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_ExportsHelp.xml b/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_ExportsHelp.xml new file mode 100644 index 0000000000..d8b6126b9c --- /dev/null +++ b/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_ExportsHelp.xml @@ -0,0 +1,110 @@ + + + + ` - Print help information +* :doc:`test module ` - First module +* :doc:`test other ` - Second module +]]> + + . +]]> + ] + +Description +----------- +Sample description +for testing ``test module``. + +Options +------- +Other options: + +``-int`` + + + +.. only:: man + + See also + -------- + + :manpage:`gromacs(7)` + + More information about |Gromacs| is available at . +]]> + . +]]> + ` - First module + +Group 2 +------- +| :doc:`test other ` - Second module + +]]> + + diff --git a/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_RunsGeneralHelp.xml b/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_RunsGeneralHelp.xml new file mode 100644 index 0000000000..54c0bfcc9f --- /dev/null +++ b/src/gromacs/commandline/tests/refdata/CommandLineModuleManagerTest_RunsGeneralHelp.xml @@ -0,0 +1,26 @@ + + + + ] + [-[no]backup] + +OPTIONS + +Other options: + + -[no]h (no) Print help and quit + -[no]quiet (no) Do not print common startup info or quotes + -[no]version (no) Print extended version information and quit + -[no]copyright (yes) Print copyright information on startup + -nice (19) Set the nicelevel (default depends on command) + -[no]backup (yes) Write backups if output files exist + +Additional help is available on the following topics: + commands List of available commands +To access the help, use 'commandline-test help '. +For help on a command, use 'commandline-test help '. +]]> + diff --git a/src/gromacs/utility/file.cpp b/src/gromacs/utility/file.cpp index dbd809c38b..76147344aa 100644 --- a/src/gromacs/utility/file.cpp +++ b/src/gromacs/utility/file.cpp @@ -140,6 +140,12 @@ File::File(const std::string &filename, const char *mode) open(filename, mode); } +File::File(const FileInitializer &initializer) + : impl_(new Impl(NULL, true)) +{ + open(initializer.filename_, initializer.mode_); +} + File::File(FILE *fp, bool bClose) : impl_(new Impl(fp, bClose)) { diff --git a/src/gromacs/utility/file.h b/src/gromacs/utility/file.h index d1f354c9a4..5e45736076 100644 --- a/src/gromacs/utility/file.h +++ b/src/gromacs/utility/file.h @@ -52,6 +52,43 @@ namespace gmx { +class File; + +/*! \brief + * Parameters for creating a File object. + * + * This class (mostly) replaces the ability to return a File object from a + * function (since File is not copyable): returning a FileInitializer instead + * allows the caller to construct the File object. + * + * \inpublicapi + * \ingroup module_utility + */ +class FileInitializer +{ + public: + /*! \brief + * Creates the initializer with given parameters. + * + * The passed strings must remain valid until the initializer is used + * to construct a File object. + */ + FileInitializer(const char *filename, const char *mode) + : filename_(filename), mode_(mode) + { + } + + private: + const char *filename_; + const char *mode_; + + /*! \brief + * Needed to allow access to the parameters without otherwise + * unnecessary accessors. + */ + friend class File; +}; + /*! \brief * Basic file object. * @@ -90,6 +127,14 @@ class File File(const char *filename, const char *mode); //! \copydoc File(const char *, const char *) File(const std::string &filename, const char *mode); + /*! \brief + * Creates a file object and opens a file. + * + * \param[in] initializer Parameters to open the file. + * \throws std::bad_alloc if out of memory. + * \throws FileIOError on any I/O error. + */ + File(const FileInitializer &initializer); /*! \brief * Destroys the file object. * diff --git a/src/gromacs/utility/fileredirector.cpp b/src/gromacs/utility/fileredirector.cpp new file mode 100644 index 0000000000..9a1406122d --- /dev/null +++ b/src/gromacs/utility/fileredirector.cpp @@ -0,0 +1,90 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal \file + * \brief + * Implements classes and functions from fileredirector.h. + * + * \author Teemu Murtola + * \ingroup module_utility + */ +#include "gmxpre.h" + +#include "fileredirector.h" + +#include "gromacs/utility/file.h" + +namespace gmx +{ + +FileOutputRedirectorInterface::~FileOutputRedirectorInterface() +{ +} + +namespace +{ + +/*! \internal + * \brief + * Implements the redirector returned by defaultFileOutputRedirector(). + * + * Does not redirect anything, but instead opens the files exactly as + * requested. + * + * \ingroup module_utility + */ +class DefaultOutputRedirector : public FileOutputRedirectorInterface +{ + public: + virtual File &standardOutput() + { + return File::standardOutput(); + } + virtual FileInitializer openFileForWriting(const char *filename) + { + return FileInitializer(filename, "w"); + } +}; + +} // namespace + +//! \cond libapi +FileOutputRedirectorInterface &defaultFileOutputRedirector() +{ + static DefaultOutputRedirector instance; + return instance; +} +//! \endcond + +} // namespace gmx diff --git a/src/gromacs/utility/fileredirector.h b/src/gromacs/utility/fileredirector.h new file mode 100644 index 0000000000..f65ce72c78 --- /dev/null +++ b/src/gromacs/utility/fileredirector.h @@ -0,0 +1,107 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \libinternal \file + * \brief + * Declares gmx::FileOutputRedirectorInterface. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_FILEREDIRECTOR_H +#define GMX_UTILITY_FILEREDIRECTOR_H + +#include + +#include "gromacs/utility/file.h" + +namespace gmx +{ + +/*! \libinternal \brief + * Allows capturing `stdout` and file output from code that supports it. + * + * The calling code should take in this interface and use the File objects + * it returns for all output that needs to support this redirection. + * By default, the code can then use defaultFileOutputRedirector() in case no + * redirection is needed. + * + * This allows tests to capture the file output without duplicating the + * knowledge of which files are actually produced. With some further + * refactoring of the File class, this could support capturing the output into + * in-memory buffers as well, but for now the current capabilities are + * sufficient. + * + * \inlibraryapi + * \ingroup module_utility + */ +class FileOutputRedirectorInterface +{ + public: + virtual ~FileOutputRedirectorInterface(); + + /*! \brief + * Returns a File object to use for `stdout` output. + */ + virtual File &standardOutput() = 0; + /*! \brief + * Returns a File object to use for output to a given file. + * + * \param[in] filename Requested file name. + */ + virtual FileInitializer openFileForWriting(const char *filename) = 0; + + //! Convenience method to open a file using an std::string path. + FileInitializer openFileForWriting(const std::string &filename) + { + return openFileForWriting(filename.c_str()); + } +}; + +//! \cond libapi +/*! \brief + * Returns default implementation for FileOutputRedirectorInterface. + * + * The returned implementation does not redirect anything, but just opens the + * files at requested locations. + * + * \ingroup module_utility + */ +FileOutputRedirectorInterface &defaultFileOutputRedirector(); +//! \endcond + +} // namespace gmx + +#endif diff --git a/src/testutils/stringtest.cpp b/src/testutils/stringtest.cpp index 93b8bf9c75..7ce6008345 100644 --- a/src/testutils/stringtest.cpp +++ b/src/testutils/stringtest.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -43,10 +43,22 @@ #include "stringtest.h" +#include +#include +#include +#include + +#include + #include "gromacs/options/basicoptions.h" #include "gromacs/options/options.h" +#include "gromacs/utility/exceptions.h" #include "gromacs/utility/file.h" +#include "gromacs/utility/fileredirector.h" +#include "testutils/refdata.h" +#include "testutils/testexceptions.h" +#include "testutils/testfilemanager.h" #include "testutils/testoptions.h" namespace gmx @@ -58,6 +70,27 @@ namespace { //! Stores the -stdout flag value to print out values instead of checking them. bool g_bWriteToStdOut = false; + +/*! \brief + * Helper for checking a block of text, e.g., implementing the `-stdout` + * option. + * + * \ingroup module_testutils + */ +void checkTextImpl(TestReferenceChecker *checker, const std::string &text, + const char *id) +{ + if (g_bWriteToStdOut) + { + printf("%s:\n", id); + printf("%s[END]\n", text.c_str()); + } + else + { + checker->checkStringBlock(text, id); + } +} + } // TODO: Only add this option to those test binaries that actually need it @@ -73,7 +106,92 @@ GMX_TEST_OPTIONS(StringTestOptions, options) } //! \endcond +/******************************************************************** + * TestFileOutputRedirector + */ + +/*! \internal + * \brief + * Implementation of FileOutputRedirectorInterface for tests. + * + * This class redirects all output files to temporary files managed by a + * TestFileManager, and supports checking the contents of these files using the + * reference data framework. + * + * \ingroup module_testutils + */ +class TestFileOutputRedirector : public FileOutputRedirectorInterface +{ + public: + //! Initializes the redirector with the given file manager. + explicit TestFileOutputRedirector(TestFileManager *fileManager) + : fileManager_(*fileManager) + { + } + + virtual File &standardOutput() + { + if (!stdoutFile_) + { + const std::string path = fileManager_.getTemporaryFilePath("stdout.txt"); + stdoutFile_.reset(new File(path, "w")); + fileList_.push_back(FileListEntry("", path)); + } + return *stdoutFile_; + } + virtual FileInitializer openFileForWriting(const char *filename) + { + std::string suffix = filename; + std::replace(suffix.begin(), suffix.end(), '/', '_'); + const std::string path = fileManager_.getTemporaryFilePath(suffix); + fileList_.push_back(FileListEntry(filename, path)); + return FileInitializer(fileList_.back().second.c_str(), "w"); + } + + /*! \brief + * Checks the contents of all redirected files. + */ + void checkRedirectedFiles(TestReferenceChecker *checker) + { + if (stdoutFile_) + { + stdoutFile_->close(); + stdoutFile_.reset(); + } + std::vector::const_iterator i; + for (i = fileList_.begin(); i != fileList_.end(); ++i) + { + const std::string text = File::readToString(i->second); + checkTextImpl(checker, text, i->first.c_str()); + } + } + + private: + typedef std::pair FileListEntry; + + TestFileManager &fileManager_; + boost::scoped_ptr stdoutFile_; + std::vector fileList_; +}; + +/******************************************************************** + * StringTestBase::Impl + */ + +class StringTestBase::Impl +{ + public: + TestReferenceData data_; + boost::scoped_ptr checker_; + boost::scoped_ptr redirector_; +}; + +/******************************************************************** + * StringTestBase + */ + StringTestBase::StringTestBase() + : impl_(new Impl) { } @@ -81,36 +199,49 @@ StringTestBase::~StringTestBase() { } +FileOutputRedirectorInterface & +StringTestBase::initOutputRedirector(TestFileManager *fileManager) +{ + if (impl_->redirector_) + { + GMX_THROW(TestException("initOutputRedirector() called more than once")); + } + impl_->redirector_.reset(new TestFileOutputRedirector(fileManager)); + return *impl_->redirector_; +} + TestReferenceChecker & StringTestBase::checker() { - if (checker_.get() == NULL) + if (!impl_->checker_) { - checker_.reset(new TestReferenceChecker(data_.rootChecker())); + impl_->checker_.reset(new TestReferenceChecker(impl_->data_.rootChecker())); } - return *checker_; + return *impl_->checker_; } void StringTestBase::checkText(const std::string &text, const char *id) { - if (g_bWriteToStdOut) - { - printf("%s:\n", id); - printf("%s[END]\n", text.c_str()); - } - else - { - checker().checkStringBlock(text, id); - } + checkTextImpl(&checker(), text, id); } void StringTestBase::checkFileContents(const std::string &filename, const char *id) { - std::string text = File::readToString(filename); + const std::string text = File::readToString(filename); checkText(text, id); } +void +StringTestBase::checkRedirectedOutputFiles() +{ + if (!impl_->redirector_) + { + GMX_THROW(TestException("initOutputRedirector() not called")); + } + impl_->redirector_->checkRedirectedFiles(&checker()); +} + } // namespace test } // namespace gmx diff --git a/src/testutils/stringtest.h b/src/testutils/stringtest.h index 502f207bf6..67e89113a3 100644 --- a/src/testutils/stringtest.h +++ b/src/testutils/stringtest.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014,2015, by the GROMACS development team, led by * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, * and including many others, as listed in the AUTHORS file in the * top-level source directory and at http://www.gromacs.org. @@ -45,16 +45,21 @@ #include -#include #include -#include "testutils/refdata.h" +#include "gromacs/utility/classhelpers.h" namespace gmx { + +class FileOutputRedirectorInterface; + namespace test { +class TestFileManager; +class TestReferenceChecker; + /*! \libinternal \brief * Test fixture for tests that check string formatting. * @@ -72,6 +77,18 @@ class StringTestBase : public ::testing::Test StringTestBase(); ~StringTestBase(); + /*! \brief + * Creates a redirector that directs all output to temporary files. + * + * \param[in] fileManager File manager to use for temporary files. + * + * Can only be called once in a test. + * + * \see checkRedirectedOutputFiles() + */ + FileOutputRedirectorInterface & + initOutputRedirector(TestFileManager *fileManager); + /*! \brief * Returns the root checker for this test's reference data. * @@ -81,14 +98,14 @@ class StringTestBase : public ::testing::Test TestReferenceChecker &checker(); /*! \brief - * Check a string. + * Checks a string. * * \param[in] text String to check. * \param[in] id Unique (within a single test) id for the string. */ void checkText(const std::string &text, const char *id); /*! \brief - * Check contents of a file as a single string. + * Checks contents of a file as a single string. * * \param[in] filename Name of the file to check. * \param[in] id Unique (within a single test) id for the string. @@ -97,10 +114,25 @@ class StringTestBase : public ::testing::Test * single string and calls checkText(). */ void checkFileContents(const std::string &filename, const char *id); + /*! \brief + * Checks contents of all files redirected with initOutputRedirector(). + * + * Uses the same logic as checkFileContents() to check each file + * (including `stdout`) that has been created using the redirector + * returned by initOutputRedirector(). + * + * initOutputRedirector() must have been called. + * This method should not be called if the redirector will still be + * used for further output in the test. Behavior is not designed for + * checking in the middle of the test, although that could potentially + * be changed if necessary. + */ + void checkRedirectedOutputFiles(); private: - TestReferenceData data_; - boost::scoped_ptr checker_; + class Impl; + + PrivateImplPointer impl_; }; } // namespace test -- 2.22.0