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
#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"
const CommandLineModuleInterface *moduleOverride_;
bool bHidden_;
- File *outputOverride_;
+ FileOutputRedirectorInterface *outputRedirector_;
GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
};
// 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 <topic>'.");
context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
}
virtual void finishModuleGroupExport();
private:
- HelpLinks links_;
- boost::scoped_ptr<File> indexFile_;
- boost::scoped_ptr<File> manPagesFile_;
+ FileOutputRedirectorInterface *outputRedirector_;
+ HelpLinks links_;
+ boost::scoped_ptr<File> indexFile_;
+ boost::scoped_ptr<File> manPagesFile_;
};
HelpExportReStructuredText::HelpExportReStructuredText(
const CommandLineHelpModuleImpl &helpModule)
- : links_(eHelpOutputFormat_Rst)
+ : outputRedirector_(helpModule.outputRedirector_),
+ links_(eHelpOutputFormat_Rst)
{
File linksFile("links.dat", "r");
std::string line;
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 = [");
}
{
// 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"))
{
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(
: rootTopic_(new RootHelpTopic(*this)), programContext_(programContext),
binaryName_(binaryName), modules_(modules), groups_(groups),
context_(NULL), moduleOverride_(NULL), bHidden_(false),
- outputOverride_(NULL)
+ outputRedirector_(&defaultFileOutputRedirector())
{
}
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[])
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<CommandLineHelpContext> context(
- new CommandLineHelpContext(outputFile,
+ new CommandLineHelpContext(&outputFile,
eHelpOutputFormat_Console, &links));
context->setShowHidden(impl_->bHidden_);
if (impl_->moduleOverride_ != NULL)
/*
* 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.
{
class CommandLineHelpContext;
-class File;
+class FileOutputRedirectorInterface;
class ProgramContextInterface;
class CommandLineHelpModuleImpl;
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
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)
/*
* 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.
class CommandLineModuleGroupData;
class CommandLineModuleInterface;
class CommandLineProgramContext;
-class File;
+class FileOutputRedirectorInterface;
//! \addtogroup module_commandline
//! \{
/*! \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.
/*
* 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.
: 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_; }
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);
}
void CommandLineOptionsModule::writeHelp(const CommandLineHelpContext &context) const
{
- boost::scoped_ptr<CommandLineOptionsModuleInterface> module(factory_());
+ boost::scoped_ptr<CommandLineOptionsModuleInterface> 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)
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
/*
* 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.
* 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 *
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();
#include "gromacs/commandline/cmdlinemodulemanager.h"
-#include <vector>
+#include <cstdio>
#include <gmock/gmock.h>
#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"
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
*/
}
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;
{
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
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<gmx::CommandLineProgramContext> programContext_;
boost::scoped_ptr<gmx::CommandLineModuleManager> manager_;
gmx::test::TestFileManager fileManager_;
- boost::scoped_ptr<gmx::File> outputFile_;
};
void CommandLineModuleManagerTest::initManager(
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)
{
return *topic;
}
-void CommandLineModuleManagerTest::ignoreManagerOutput()
-{
- outputFile_.reset(
- new gmx::File(fileManager_.getTemporaryFilePath("out.txt"), "w"));
- manager().setOutputRedirect(outputFile_.get());
-}
-
/********************************************************************
* Actual tests
*/
};
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)
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
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="programs/byname.rst"><![CDATA[
+Tools by Name
+=============
+* :doc:`test help <test-help>` - Print help information
+* :doc:`test module <test-module>` - First module
+* :doc:`test other <test-other>` - Second module
+]]></String>
+ <String Name="conf-man.py"><![CDATA[
+man_pages = [
+ ('programs/test-help', 'test-help', "Print help information", '', 1),
+ ('programs/test-module', 'test-module', "First module", '', 1),
+ ('programs/test-other', 'test-other', "Second module", '', 1),
+ ('man/gromacs.7', 'gromacs', 'molecular dynamics simulation suite', '', 7)
+]
+]]></String>
+ <String Name="programs/test-help.rst"><![CDATA[
+.. _test help:
+
+test help
+=========
+
+.. only:: man
+
+ See also
+ --------
+
+ :manpage:`gromacs(7)`
+
+ More information about |Gromacs| is available at <http://www.gromacs.org/>.
+]]></String>
+ <String Name="programs/test-module.rst"><![CDATA[
+.. _test module:
+
+test module
+===========
+Synopsis
+--------
+::
+
+ test module [-int <int>]
+
+Description
+-----------
+Sample description
+for testing ``test module``.
+
+Options
+-------
+Other options:
+
+``-int`` <int>
+
+
+
+.. only:: man
+
+ See also
+ --------
+
+ :manpage:`gromacs(7)`
+
+ More information about |Gromacs| is available at <http://www.gromacs.org/>.
+]]></String>
+ <String Name="programs/test-other.rst"><![CDATA[
+.. _test other:
+
+test other
+==========
+Synopsis
+--------
+::
+
+ test other
+
+
+.. only:: man
+
+ See also
+ --------
+
+ :manpage:`gromacs(7)`
+
+ More information about |Gromacs| is available at <http://www.gromacs.org/>.
+]]></String>
+ <String Name="programs/bytopic.rst"><![CDATA[
+Tools by Topic
+==============
+Group 1
+-------
+| :doc:`test module <test-module>` - First module
+
+Group 2
+-------
+| :doc:`test other <test-other>` - Second module
+
+]]></String>
+ <String Name="man/bytopic.rst"><![CDATA[
+Group 1
++++++++
+| ``test module`` - First module
+
+Group 2
++++++++
+| ``test other`` - Second module
+
+]]></String>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="<stdout>"><![CDATA[
+SYNOPSIS
+
+test [-[no]h] [-[no]quiet] [-[no]version] [-[no]copyright] [-nice <int>]
+ [-[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 <int> (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 <topic>'.
+For help on a command, use 'commandline-test help <command>'.
+]]></String>
+</ReferenceData>
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))
{
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.
*
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.
*
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \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
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_FILEREDIRECTOR_H
+#define GMX_UTILITY_FILEREDIRECTOR_H
+
+#include <string>
+
+#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
/*
* 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.
#include "stringtest.h"
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/scoped_ptr.hpp>
+
#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
{
//! 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
}
//! \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("<stdout>", 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<FileListEntry>::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<std::string, std::string> FileListEntry;
+
+ TestFileManager &fileManager_;
+ boost::scoped_ptr<File> stdoutFile_;
+ std::vector<FileListEntry> fileList_;
+};
+
+/********************************************************************
+ * StringTestBase::Impl
+ */
+
+class StringTestBase::Impl
+{
+ public:
+ TestReferenceData data_;
+ boost::scoped_ptr<TestReferenceChecker> checker_;
+ boost::scoped_ptr<TestFileOutputRedirector> redirector_;
+};
+
+/********************************************************************
+ * StringTestBase
+ */
+
StringTestBase::StringTestBase()
+ : impl_(new Impl)
{
}
{
}
+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
/*
* 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.
#include <string>
-#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
-#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.
*
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.
*
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.
* 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<TestReferenceChecker> checker_;
+ class Impl;
+
+ PrivateImplPointer<Impl> impl_;
};
} // namespace test