From 67ebd75bb4680b07138b518978e8608a79cfbf80 Mon Sep 17 00:00:00 2001 From: Teemu Murtola Date: Sun, 28 Jun 2015 22:21:52 +0300 Subject: [PATCH] Make some unit tests use mock file output Introduce TextOutputStream interface that can be used instead of direct file output. Make unit tests for help output use an in-memory stream instead of a real file using this mechanism. TextWriter wraps the raw stream and provides more convenient line-oriented writing capabilities (and provides a natural place to implement more of the same, making them immediately available independent of the stream used). Most of the touched files only contain mechanical File -> TextWriter/TextOutputStream and related documentation replacements. The main non-trivial changes are introduction of the streams and TextWriter in utility/, and reorganization of the way the tests use TestOutputRedirector. Further work follows to allow full removal of the old File class and to extend the use of the streams. Change-Id: I7700d0d8af5f44b304b940797a4834993e4738fb --- .../commandline/cmdlinehelpcontext.cpp | 13 +- src/gromacs/commandline/cmdlinehelpcontext.h | 4 +- src/gromacs/commandline/cmdlinehelpmodule.cpp | 64 ++++--- src/gromacs/commandline/cmdlinehelpwriter.cpp | 8 +- src/gromacs/commandline/shellcompletions.cpp | 20 +- src/gromacs/commandline/shellcompletions.h | 6 +- .../commandline/tests/cmdlinehelpmodule.cpp | 11 +- .../commandline/tests/cmdlinehelpwriter.cpp | 13 +- .../tests/cmdlinemodulemanagertest.cpp | 9 +- .../tests/cmdlinemodulemanagertest.h | 9 +- src/gromacs/onlinehelp/helptopic.cpp | 4 +- src/gromacs/onlinehelp/helpwritercontext.cpp | 27 +-- src/gromacs/onlinehelp/helpwritercontext.h | 17 +- src/gromacs/onlinehelp/tests/helpmanager.cpp | 11 +- src/gromacs/selection/selectioncollection.cpp | 3 +- src/gromacs/selection/selhelp.cpp | 4 +- src/gromacs/utility.h | 35 ++-- src/gromacs/utility/file.cpp | 20 -- src/gromacs/utility/file.h | 59 +----- src/gromacs/utility/fileredirector.cpp | 9 +- src/gromacs/utility/fileredirector.h | 46 +++-- src/gromacs/utility/filestream.cpp | 171 ++++++++++++++++++ src/gromacs/utility/filestream.h | 116 ++++++++++++ src/gromacs/utility/nodelete.h | 71 ++++++++ src/gromacs/utility/stringstream.cpp | 60 ++++++ src/gromacs/utility/stringstream.h | 80 ++++++++ src/gromacs/utility/textstream.h | 104 +++++++++++ src/gromacs/utility/textwriter.cpp | 125 +++++++++++++ src/gromacs/utility/textwriter.h | 144 +++++++++++++++ src/testutils/cmdlinetest.cpp | 5 +- src/testutils/stringtest.cpp | 135 ++------------ src/testutils/stringtest.h | 37 +--- src/testutils/testfileredirector.cpp | 66 +++++++ src/testutils/testfileredirector.h | 36 ++++ src/testutils/testinit.cpp | 4 +- src/testutils/testutils-doc.h | 4 + 36 files changed, 1175 insertions(+), 375 deletions(-) create mode 100644 src/gromacs/utility/filestream.cpp create mode 100644 src/gromacs/utility/filestream.h create mode 100644 src/gromacs/utility/nodelete.h create mode 100644 src/gromacs/utility/stringstream.cpp create mode 100644 src/gromacs/utility/stringstream.h create mode 100644 src/gromacs/utility/textstream.h create mode 100644 src/gromacs/utility/textwriter.cpp create mode 100644 src/gromacs/utility/textwriter.h diff --git a/src/gromacs/commandline/cmdlinehelpcontext.cpp b/src/gromacs/commandline/cmdlinehelpcontext.cpp index cfe32fbf38..d1af037ef5 100644 --- a/src/gromacs/commandline/cmdlinehelpcontext.cpp +++ b/src/gromacs/commandline/cmdlinehelpcontext.cpp @@ -74,8 +74,9 @@ class CommandLineHelpContext::Impl { public: //! Creates the implementation class and the low-level context. - Impl(File *file, HelpOutputFormat format, const HelpLinks *links) - : writerContext_(file, format, links), moduleDisplayName_("gmx"), + Impl(TextOutputStream *stream, HelpOutputFormat format, + const HelpLinks *links) + : writerContext_(stream, format, links), moduleDisplayName_("gmx"), completionWriter_(NULL), bHidden_(false) { } @@ -97,9 +98,9 @@ class CommandLineHelpContext::Impl }; CommandLineHelpContext::CommandLineHelpContext( - File *file, HelpOutputFormat format, const HelpLinks *links, - const std::string &programName) - : impl_(new Impl(file, format, links)) + TextOutputStream *stream, HelpOutputFormat format, + const HelpLinks *links, const std::string &programName) + : impl_(new Impl(stream, format, links)) { impl_->writerContext_.setReplacement("[PROGRAM]", programName); } @@ -112,7 +113,7 @@ CommandLineHelpContext::CommandLineHelpContext( CommandLineHelpContext::CommandLineHelpContext( ShellCompletionWriter *writer) - : impl_(new Impl(writer->outputFile(), eHelpOutputFormat_Other, NULL)) + : impl_(new Impl(&writer->outputStream(), eHelpOutputFormat_Other, NULL)) { impl_->completionWriter_ = writer; } diff --git a/src/gromacs/commandline/cmdlinehelpcontext.h b/src/gromacs/commandline/cmdlinehelpcontext.h index 330f20aa6f..be59e70cbb 100644 --- a/src/gromacs/commandline/cmdlinehelpcontext.h +++ b/src/gromacs/commandline/cmdlinehelpcontext.h @@ -76,8 +76,8 @@ class CommandLineHelpContext * * Wraps the constructor of HelpWriterContext. */ - CommandLineHelpContext(File *file, HelpOutputFormat format, - const HelpLinks *links, + CommandLineHelpContext(TextOutputStream *stream, + HelpOutputFormat format, const HelpLinks *links, const std::string &programName); //! Creates a context for a particular HelpWriterContext. explicit CommandLineHelpContext(const HelpWriterContext &writerContext); diff --git a/src/gromacs/commandline/cmdlinehelpmodule.cpp b/src/gromacs/commandline/cmdlinehelpmodule.cpp index 2a2959cbf8..1261c14259 100644 --- a/src/gromacs/commandline/cmdlinehelpmodule.cpp +++ b/src/gromacs/commandline/cmdlinehelpmodule.cpp @@ -65,6 +65,7 @@ #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/programcontext.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "shellcompletions.h" @@ -376,7 +377,7 @@ void CommandsHelpTopic::writeHelp(const HelpWriterContext &context) const context.writeTextBlock( "Usage: [PROGRAM] [] [][PAR]" "Available commands:"); - File &file = context.outputFile(); + TextWriter &file = context.outputFile(); TextTableFormatter formatter; formatter.addColumn(NULL, maxNameLength + 1, false); formatter.addColumn(NULL, 72 - maxNameLength, true); @@ -511,8 +512,8 @@ class HelpExportReStructuredText : public HelpExportInterface FileOutputRedirectorInterface *outputRedirector_; const std::string &binaryName_; HelpLinks links_; - boost::scoped_ptr indexFile_; - boost::scoped_ptr manPagesFile_; + boost::scoped_ptr indexFile_; + boost::scoped_ptr manPagesFile_; }; HelpExportReStructuredText::HelpExportReStructuredText( @@ -537,12 +538,14 @@ HelpExportReStructuredText::HelpExportReStructuredText( void HelpExportReStructuredText::startModuleExport() { indexFile_.reset( - new File(outputRedirector_->openFileForWriting("fragments/byname.rst"))); + new TextWriter( + outputRedirector_->openTextOutputFile("fragments/byname.rst"))); indexFile_->writeLine(formatString("* :doc:`%s ` - %s", binaryName_.c_str(), binaryName_.c_str(), RootHelpText::title)); manPagesFile_.reset( - new File(outputRedirector_->openFileForWriting("conf-man.py"))); + new TextWriter( + outputRedirector_->openTextOutputFile("conf-man.py"))); manPagesFile_->writeLine("man_pages = ["); } @@ -553,31 +556,33 @@ void HelpExportReStructuredText::exportModuleHelp( { // TODO: Ideally, the file would only be touched if it really changes. // This would make Sphinx reruns much faster. - File file(outputRedirector_->openFileForWriting("onlinehelp/" + tag + ".rst")); - file.writeLine(formatString(".. _%s:", displayName.c_str())); + TextOutputStreamPointer file + = outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst"); + TextWriter writer(file); + writer.writeLine(formatString(".. _%s:", displayName.c_str())); if (0 == displayName.compare(binaryName_ + " mdrun")) { // Make an extra link target for the convenience of // MPI-specific documentation - file.writeLine(".. _mdrun_mpi:"); + writer.writeLine(".. _mdrun_mpi:"); } - file.writeLine(); + writer.writeLine(); - CommandLineHelpContext context(&file, eHelpOutputFormat_Rst, &links_, binaryName_); + CommandLineHelpContext context(file.get(), eHelpOutputFormat_Rst, &links_, binaryName_); context.enterSubSection(displayName); context.setModuleDisplayName(displayName); module.writeHelp(context); - file.writeLine(); - file.writeLine(".. only:: man"); - file.writeLine(); - file.writeLine(" See also"); - file.writeLine(" --------"); - file.writeLine(); - file.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str())); - file.writeLine(); - file.writeLine(" More information about |Gromacs| is available at ."); - file.close(); + writer.writeLine(); + writer.writeLine(".. only:: man"); + writer.writeLine(); + writer.writeLine(" See also"); + writer.writeLine(" --------"); + writer.writeLine(); + writer.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str())); + writer.writeLine(); + writer.writeLine(" More information about |Gromacs| is available at ."); + file->close(); indexFile_->writeLine(formatString("* :doc:`%s ` - %s", displayName.c_str(), tag.c_str(), @@ -604,9 +609,11 @@ void HelpExportReStructuredText::finishModuleExport() void HelpExportReStructuredText::startModuleGroupExport() { indexFile_.reset( - new File(outputRedirector_->openFileForWriting("fragments/bytopic.rst"))); + new TextWriter( + outputRedirector_->openTextOutputFile("fragments/bytopic.rst"))); manPagesFile_.reset( - new File(outputRedirector_->openFileForWriting("fragments/bytopic-man.rst"))); + new TextWriter( + outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"))); } void HelpExportReStructuredText::exportModuleGroup( @@ -650,12 +657,13 @@ void HelpExportReStructuredText::finishModuleGroupExport() void HelpExportReStructuredText::exportTopic(const HelpTopicInterface &topic) { - const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst"); - File file(outputRedirector_->openFileForWriting(path)); - CommandLineHelpContext context(&file, eHelpOutputFormat_Rst, &links_, - binaryName_); - HelpManager manager(topic, context.writerContext()); + const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst"); + TextOutputStreamPointer file(outputRedirector_->openTextOutputFile(path)); + CommandLineHelpContext context(file.get(), eHelpOutputFormat_Rst, &links_, + binaryName_); + HelpManager manager(topic, context.writerContext()); manager.writeCurrentTopic(); + file->close(); } /******************************************************************** @@ -850,7 +858,7 @@ int CommandLineHelpModule::run(int argc, char *argv[]) return 0; } - File &outputFile = impl_->outputRedirector_->standardOutput(); + TextOutputStream &outputFile = impl_->outputRedirector_->standardOutput(); HelpLinks links(eHelpOutputFormat_Console); initProgramLinks(&links, *impl_); CommandLineHelpContext context(&outputFile, eHelpOutputFormat_Console, &links, diff --git a/src/gromacs/commandline/cmdlinehelpwriter.cpp b/src/gromacs/commandline/cmdlinehelpwriter.cpp index addc8c8361..1241c66755 100644 --- a/src/gromacs/commandline/cmdlinehelpwriter.cpp +++ b/src/gromacs/commandline/cmdlinehelpwriter.cpp @@ -59,8 +59,8 @@ #include "gromacs/options/timeunitmanager.h" #include "gromacs/utility/arrayref.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "shellcompletions.h" @@ -413,7 +413,7 @@ void SynopsisFormatter::start(const char *name) { currentLength_ = std::strlen(name) + 1; indent_ = std::min(currentLength_, 13); - File &file = context_.outputFile(); + TextWriter &file = context_.outputFile(); switch (context_.outputFormat()) { case eHelpOutputFormat_Console: @@ -436,7 +436,7 @@ void SynopsisFormatter::start(const char *name) void SynopsisFormatter::finish() { - File &file = context_.outputFile(); + TextWriter &file = context_.outputFile(); file.writeLine(); file.writeLine(); } @@ -456,7 +456,7 @@ void SynopsisFormatter::formatOption(const OptionInfo &option) } fullOptionText.append(bFormatted_ ? "`]" : "]"); - File &file = context_.outputFile(); + TextWriter &file = context_.outputFile(); currentLength_ += totalLength; if (currentLength_ >= lineLength_) { diff --git a/src/gromacs/commandline/shellcompletions.cpp b/src/gromacs/commandline/shellcompletions.cpp index 1eefeda1cf..a0ef63e4c6 100644 --- a/src/gromacs/commandline/shellcompletions.cpp +++ b/src/gromacs/commandline/shellcompletions.cpp @@ -3,7 +3,7 @@ * * Copyright (c) 1991-2000, University of Groningen, The Netherlands. * Copyright (c) 2001-2004, The GROMACS development team. - * Copyright (c) 2013,2014, by the GROMACS development team, led by + * Copyright (c) 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. @@ -59,9 +59,9 @@ #include "gromacs/options/optionsvisitor.h" #include "gromacs/utility/arrayref.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" namespace gmx { @@ -107,7 +107,7 @@ class OptionsListWriter : public OptionsVisitor class OptionCompletionWriter : public OptionsVisitor { public: - explicit OptionCompletionWriter(File *out) : out_(*out) {} + explicit OptionCompletionWriter(TextWriter *out) : out_(*out) {} virtual void visitSubSection(const Options §ion) { @@ -121,7 +121,7 @@ class OptionCompletionWriter : public OptionsVisitor void writeOptionCompletion(const OptionInfo &option, const std::string &completion); - File &out_; + TextWriter &out_; }; void OptionCompletionWriter::visitOption(const OptionInfo &option) @@ -196,8 +196,8 @@ class ShellCompletionWriter::Impl return formatString("_%s_%s_compl", binaryName_.c_str(), moduleName); } - std::string binaryName_; - boost::scoped_ptr file_; + std::string binaryName_; + boost::scoped_ptr file_; }; ShellCompletionWriter::ShellCompletionWriter(const std::string &binaryName, @@ -210,14 +210,14 @@ ShellCompletionWriter::~ShellCompletionWriter() { } -File *ShellCompletionWriter::outputFile() +TextOutputStream &ShellCompletionWriter::outputStream() { - return impl_->file_.get(); + return impl_->file_->stream(); } void ShellCompletionWriter::startCompletions() { - impl_->file_.reset(new File(impl_->binaryName_ + "-completion.bash", "w")); + impl_->file_.reset(new TextWriter(impl_->binaryName_ + "-completion.bash")); impl_->file_->writeLine("shopt -s extglob"); } @@ -225,7 +225,7 @@ void ShellCompletionWriter::writeModuleCompletions( const char *moduleName, const Options &options) { - File &out = *impl_->file_; + TextWriter &out = *impl_->file_; out.writeLine(formatString("%s() {", impl_->completionFunctionName(moduleName).c_str())); out.writeLine("local IFS=$'\\n'"); out.writeLine("local c=${COMP_WORDS[COMP_CWORD]}"); diff --git a/src/gromacs/commandline/shellcompletions.h b/src/gromacs/commandline/shellcompletions.h index 8843547543..f8f11906b6 100644 --- a/src/gromacs/commandline/shellcompletions.h +++ b/src/gromacs/commandline/shellcompletions.h @@ -3,7 +3,7 @@ * * Copyright (c) 1991-2000, University of Groningen, The Netherlands. * Copyright (c) 2001-2004, The GROMACS development team. - * Copyright (c) 2013,2014, by the GROMACS development team, led by + * Copyright (c) 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. @@ -53,8 +53,8 @@ namespace gmx { class CommandLineHelpContext; -class File; class Options; +class TextOutputStream; //! \cond internal //! \addtogroup module_commandline @@ -78,7 +78,7 @@ class ShellCompletionWriter ShellCompletionFormat format); ~ShellCompletionWriter(); - File *outputFile(); + TextOutputStream &outputStream(); void startCompletions(); void writeModuleCompletions(const char *moduleName, diff --git a/src/gromacs/commandline/tests/cmdlinehelpmodule.cpp b/src/gromacs/commandline/tests/cmdlinehelpmodule.cpp index b8cb8eae28..7be3fb5ed7 100644 --- a/src/gromacs/commandline/tests/cmdlinehelpmodule.cpp +++ b/src/gromacs/commandline/tests/cmdlinehelpmodule.cpp @@ -74,14 +74,13 @@ TEST_F(CommandLineHelpModuleTest, PrintsGeneralHelp) }; CommandLine args(cmdline); initManager(args, "test"); - redirectManagerOutput(); addModule("module", "First module"); addModule("other", "Second module"); addHelpTopic("topic", "Test topic"); int rc = 0; ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv())); ASSERT_EQ(0, rc); - checkRedirectedOutputFiles(); + checkRedirectedOutput(); } TEST_F(CommandLineHelpModuleTest, PrintsHelpOnTopic) @@ -91,7 +90,6 @@ TEST_F(CommandLineHelpModuleTest, PrintsHelpOnTopic) }; CommandLine args(cmdline); initManager(args, "test"); - redirectManagerOutput(); addModule("module", "First module"); MockHelpTopic &topic = addHelpTopic("topic", "Test topic"); topic.addSubTopic("sub1", "Subtopic 1", ""); @@ -101,7 +99,7 @@ TEST_F(CommandLineHelpModuleTest, PrintsHelpOnTopic) int rc = 0; ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv())); ASSERT_EQ(0, rc); - checkRedirectedOutputFiles(); + checkRedirectedOutput(); } /*! \brief @@ -126,9 +124,8 @@ TEST_F(CommandLineHelpModuleTest, ExportsHelp) }; // TODO: Find a more elegant solution, or get rid of the links.dat altogether. gmx::File::writeFileFromString("links.dat", ""); - CommandLine args(cmdline); + CommandLine args(cmdline); initManager(args, "test"); - redirectManagerOutput(); MockOptionsModule &mod1 = addOptionsModule("module", "First module"); MockOptionsModule &mod2 = addOptionsModule("other", "Second module"); { @@ -156,7 +153,7 @@ TEST_F(CommandLineHelpModuleTest, ExportsHelp) int rc = 0; ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv())); ASSERT_EQ(0, rc); - checkRedirectedOutputFiles(); + checkRedirectedOutput(); std::remove("links.dat"); } diff --git a/src/gromacs/commandline/tests/cmdlinehelpwriter.cpp b/src/gromacs/commandline/tests/cmdlinehelpwriter.cpp index 317cc4c9ab..7d3a282e2e 100644 --- a/src/gromacs/commandline/tests/cmdlinehelpwriter.cpp +++ b/src/gromacs/commandline/tests/cmdlinehelpwriter.cpp @@ -56,10 +56,9 @@ #include "gromacs/options/basicoptions.h" #include "gromacs/options/filenameoption.h" #include "gromacs/options/options.h" -#include "gromacs/utility/file.h" +#include "gromacs/utility/stringstream.h" #include "testutils/stringtest.h" -#include "testutils/testfilemanager.h" namespace { @@ -71,21 +70,19 @@ class CommandLineHelpWriterTest : public ::gmx::test::StringTestBase void checkHelp(gmx::CommandLineHelpWriter *writer); - gmx::test::TestFileManager tempFiles_; bool bHidden_; }; void CommandLineHelpWriterTest::checkHelp(gmx::CommandLineHelpWriter *writer) { - std::string filename = tempFiles_.getTemporaryFilePath("helptext.txt"); - gmx::File file(filename, "w"); - gmx::CommandLineHelpContext context(&file, gmx::eHelpOutputFormat_Console, + gmx::StringOutputStream stream; + gmx::CommandLineHelpContext context(&stream, gmx::eHelpOutputFormat_Console, NULL, "test"); context.setShowHidden(bHidden_); writer->writeHelp(context); - file.close(); + stream.close(); - checkFileContents(filename, "HelpText"); + checkText(stream.toString(), "HelpText"); } diff --git a/src/gromacs/commandline/tests/cmdlinemodulemanagertest.cpp b/src/gromacs/commandline/tests/cmdlinemodulemanagertest.cpp index f85cabe057..5c33490f3a 100644 --- a/src/gromacs/commandline/tests/cmdlinemodulemanagertest.cpp +++ b/src/gromacs/commandline/tests/cmdlinemodulemanagertest.cpp @@ -56,7 +56,7 @@ #include "gromacs/onlinehelp/tests/mock_helptopic.h" #include "testutils/cmdlinetest.h" -#include "testutils/testfilemanager.h" +#include "testutils/testfileredirector.h" namespace gmx { @@ -131,9 +131,9 @@ MockOptionsModule::~MockOptionsModule() class CommandLineModuleManagerTestBase::Impl { public: + TestFileOutputRedirector redirector_; boost::scoped_ptr programContext_; boost::scoped_ptr manager_; - TestFileManager fileManager_; }; CommandLineModuleManagerTestBase::CommandLineModuleManagerTestBase() @@ -154,6 +154,7 @@ void CommandLineModuleManagerTestBase::initManager( impl_->manager_.reset(new gmx::CommandLineModuleManager( realBinaryName, impl_->programContext_.get())); impl_->manager_->setQuiet(true); + impl_->manager_->setOutputRedirector(&impl_->redirector_); } MockModule & @@ -186,9 +187,9 @@ CommandLineModuleManager &CommandLineModuleManagerTestBase::manager() return *impl_->manager_; } -void CommandLineModuleManagerTestBase::redirectManagerOutput() +void CommandLineModuleManagerTestBase::checkRedirectedOutput() { - impl_->manager_->setOutputRedirector(&initOutputRedirector(&impl_->fileManager_)); + impl_->redirector_.checkRedirectedFiles(&checker()); } } // namespace test diff --git a/src/gromacs/commandline/tests/cmdlinemodulemanagertest.h b/src/gromacs/commandline/tests/cmdlinemodulemanagertest.h index c156561606..ecb77a4f66 100644 --- a/src/gromacs/commandline/tests/cmdlinemodulemanagertest.h +++ b/src/gromacs/commandline/tests/cmdlinemodulemanagertest.h @@ -60,6 +60,7 @@ namespace test class CommandLine; class MockHelpTopic; +class TestFileOutputRedirector; /*! \internal \brief * Mock implementation of gmx::CommandLineModuleInterface. @@ -140,16 +141,14 @@ class CommandLineModuleManagerTestBase : public gmx::test::StringTestBase CommandLineModuleManager &manager(); /*! \brief - * Redirects all manager output to files. + * Checks all output from the manager using reference data. * - * Can be used to silence tests that would otherwise print out - * something, and/or checkRedirectedFileContents() from the base class - * can be used to check the output. + * Both output to `stdout` and to files is checked. * * The manager is put into quiet mode by default, so the manager will * only print out information if, e.g., help is explicitly requested. */ - void redirectManagerOutput(); + void checkRedirectedOutput(); private: class Impl; diff --git a/src/gromacs/onlinehelp/helptopic.cpp b/src/gromacs/onlinehelp/helptopic.cpp index 58be839ba3..6a3678ff6c 100644 --- a/src/gromacs/onlinehelp/helptopic.cpp +++ b/src/gromacs/onlinehelp/helptopic.cpp @@ -49,9 +49,9 @@ #include "gromacs/onlinehelp/helpformat.h" #include "gromacs/onlinehelp/helpwritercontext.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" namespace gmx { @@ -180,7 +180,7 @@ AbstractCompositeHelpTopic::writeSubTopicList(const HelpWriterContext &context, { return false; } - File &file = context.outputFile(); + TextWriter &file = context.outputFile(); TextTableFormatter formatter; formatter.addColumn(NULL, maxNameLength + 1, false); formatter.addColumn(NULL, 72 - maxNameLength, true); diff --git a/src/gromacs/onlinehelp/helpwritercontext.cpp b/src/gromacs/onlinehelp/helpwritercontext.cpp index f4deb79186..07d2f29401 100644 --- a/src/gromacs/onlinehelp/helpwritercontext.cpp +++ b/src/gromacs/onlinehelp/helpwritercontext.cpp @@ -53,10 +53,10 @@ #include "gromacs/onlinehelp/helpformat.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/programcontext.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "rstparser.h" @@ -438,9 +438,9 @@ class HelpWriterContext::Impl { public: //! Initializes the state with the given parameters. - SharedState(File *file, HelpOutputFormat format, + SharedState(TextOutputStream *stream, HelpOutputFormat format, const HelpLinks *links) - : file_(*file), format_(format), links_(links) + : file_(stream), format_(format), links_(links) { } @@ -466,8 +466,8 @@ class HelpWriterContext::Impl return *consoleOptionsFormatter_; } - //! Output file to which the help is written. - File &file_; + //! Writer for writing the help. + TextWriter file_; //! Output format for the help output. HelpOutputFormat format_; //! Links to use. @@ -598,14 +598,14 @@ void HelpWriterContext::Impl::processMarkup(const std::string &text, * HelpWriterContext */ -HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format) - : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL)), 0)) +HelpWriterContext::HelpWriterContext(TextOutputStream *stream, HelpOutputFormat format) + : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(stream, format, NULL)), 0)) { } -HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format, +HelpWriterContext::HelpWriterContext(TextOutputStream *stream, HelpOutputFormat format, const HelpLinks *links) - : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links)), 0)) + : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(stream, format, links)), 0)) { if (links != NULL) { @@ -639,9 +639,10 @@ HelpOutputFormat HelpWriterContext::outputFormat() const return impl_->state_->format_; } -File &HelpWriterContext::outputFile() const +TextWriter &HelpWriterContext::outputFile() const { - return impl_->state_->file_; + // TODO: Consider how to deal with the const/non-const difference better. + return const_cast(impl_->state_->file_); } void HelpWriterContext::enterSubSection(const std::string &title) @@ -676,7 +677,7 @@ void HelpWriterContext::writeTitle(const std::string &title) const { return; } - File &file = outputFile(); + TextWriter &file = outputFile(); switch (outputFormat()) { case eHelpOutputFormat_Console: @@ -714,7 +715,7 @@ void HelpWriterContext::writeOptionItem(const std::string &name, const std::string &info, const std::string &description) const { - File &file = outputFile(); + TextWriter &file = outputFile(); switch (outputFormat()) { case eHelpOutputFormat_Console: diff --git a/src/gromacs/onlinehelp/helpwritercontext.h b/src/gromacs/onlinehelp/helpwritercontext.h index 1cbce8e6b5..95cac6d739 100644 --- a/src/gromacs/onlinehelp/helpwritercontext.h +++ b/src/gromacs/onlinehelp/helpwritercontext.h @@ -51,8 +51,9 @@ namespace gmx { -class File; class TextLineWrapperSettings; +class TextOutputStream; +class TextWriter; /*! \cond libapi */ //! \libinternal Output format for help writing. @@ -132,13 +133,13 @@ class HelpWriterContext { public: /*! \brief - * Initializes a context with the given output file and format. + * Initializes a context with the given output stream and format. * * \throws std::bad_alloc if out of memory. */ - HelpWriterContext(File *file, HelpOutputFormat format); + HelpWriterContext(TextOutputStream *stream, HelpOutputFormat format); /*! \brief - * Initializes a context with the given output file, format and links. + * Initializes a context with the given output stream, format and links. * * \throws std::bad_alloc if out of memory. * @@ -146,7 +147,7 @@ class HelpWriterContext * is destructed. The caller is responsible for ensuring that the * links object remains valid long enough. */ - HelpWriterContext(File *file, HelpOutputFormat format, + HelpWriterContext(TextOutputStream *stream, HelpOutputFormat format, const HelpLinks *links); //! Creates a copy of the context. HelpWriterContext(const HelpWriterContext &other); @@ -174,15 +175,15 @@ class HelpWriterContext */ HelpOutputFormat outputFormat() const; /*! \brief - * Returns the raw output file for writing the help. + * Returns the raw writer for writing the help. * - * Using this file directly should be avoided, as it requires one to + * Using this writer directly should be avoided, as it requires one to * have different code for each output format. * Using other methods in this class should be preferred. * * Does not throw. */ - File &outputFile() const; + TextWriter &outputFile() const; /*! \brief * Creates a subsection in the output help. diff --git a/src/gromacs/onlinehelp/tests/helpmanager.cpp b/src/gromacs/onlinehelp/tests/helpmanager.cpp index f28b0f8e12..05dfb24382 100644 --- a/src/gromacs/onlinehelp/tests/helpmanager.cpp +++ b/src/gromacs/onlinehelp/tests/helpmanager.cpp @@ -51,12 +51,11 @@ #include "gromacs/onlinehelp/helptopic.h" #include "gromacs/onlinehelp/helpwritercontext.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" +#include "gromacs/utility/stringstream.h" #include "gromacs/onlinehelp/tests/mock_helptopic.h" #include "testutils/stringtest.h" #include "testutils/testasserts.h" -#include "testutils/testfilemanager.h" namespace { @@ -68,18 +67,14 @@ class HelpTestBase : public gmx::test::StringTestBase public: HelpTestBase(); - gmx::test::TestFileManager tempFiles_; MockHelpTopic rootTopic_; - std::string filename_; - gmx::File helpFile_; + gmx::StringOutputStream helpFile_; gmx::HelpWriterContext context_; gmx::HelpManager manager_; }; HelpTestBase::HelpTestBase() : rootTopic_("", NULL, "Root topic text"), - filename_(tempFiles_.getTemporaryFilePath("helptext.txt")), - helpFile_(filename_, "w"), context_(&helpFile_, gmx::eHelpOutputFormat_Console), manager_(rootTopic_, context_) { @@ -158,7 +153,7 @@ void HelpTopicFormattingTest::checkHelpFormatting() ASSERT_NO_THROW_GMX(manager_.writeCurrentTopic()); helpFile_.close(); - checkFileContents(filename_, "HelpText"); + checkText(helpFile_.toString(), "HelpText"); } TEST_F(HelpTopicFormattingTest, FormatsSimpleTopic) diff --git a/src/gromacs/selection/selectioncollection.cpp b/src/gromacs/selection/selectioncollection.cpp index e18078814b..1e0726dbbd 100644 --- a/src/gromacs/selection/selectioncollection.cpp +++ b/src/gromacs/selection/selectioncollection.cpp @@ -62,6 +62,7 @@ #include "gromacs/topology/topology.h" #include "gromacs/utility/exceptions.h" #include "gromacs/utility/file.h" +#include "gromacs/utility/filestream.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/smalloc.h" #include "gromacs/utility/stringutil.h" @@ -292,7 +293,7 @@ void printHelp(gmx_ana_selcollection_t *sc, const std::string &line) { sc->rootHelp = createSelectionHelpTopic(); } - HelpWriterContext context(&File::standardError(), + HelpWriterContext context(&TextOutputFile::standardError(), eHelpOutputFormat_Console); HelpManager manager(*sc->rootHelp, context); try diff --git a/src/gromacs/selection/selhelp.cpp b/src/gromacs/selection/selhelp.cpp index e96a955f71..131e9d25e8 100644 --- a/src/gromacs/selection/selhelp.cpp +++ b/src/gromacs/selection/selhelp.cpp @@ -53,9 +53,9 @@ #include "gromacs/onlinehelp/helptopic.h" #include "gromacs/onlinehelp/helpwritercontext.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "selmethod.h" #include "symrec.h" @@ -708,7 +708,7 @@ void KeywordsHelpTopic::printKeywordList(const HelpWriterContext &context, e_selvalue_t type, bool bModifiers) const { - File &file = context.outputFile(); + TextWriter &file = context.outputFile(); MethodList::const_iterator iter; for (iter = methods_.begin(); iter != methods_.end(); ++iter) { diff --git a/src/gromacs/utility.h b/src/gromacs/utility.h index 213f698280..b06d2d9104 100644 --- a/src/gromacs/utility.h +++ b/src/gromacs/utility.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by + * Copyright (c) 2010,2011,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. @@ -42,17 +42,17 @@ * containers to simplify implementation of other code. Contents of the module * are discussed in more details under the different headings below. * Some of the code in installed headers in the module is intended for use - * directly from code outside the Gromacs library, but a significant portion is - * exposed only because other public headers depend on it. + * directly from code outside the \Gromacs library, but a significant portion + * is exposed only because other public headers depend on it. * * Since this module implements error handling, it should be at the lowest * level: it should not depend on other modules. Any functionality needed by * the error handling code should also be kept in this module. * - *

Error Handling

+ *

Error handling

* * Exception classes used in the library are declared in the exceptions.h header - * file. Most Gromacs-specific exceptions derive from gmx::GromacsException. + * file. Most \Gromacs-specific exceptions derive from gmx::GromacsException. * * This header also declares a ::GMX_THROW macro that should be used for * throwing exceptions. ::GMX_THROW_WITH_ERRNO is also provided for reporting @@ -87,17 +87,28 @@ * \endif * * - *

Basic %File Handling

+ * \if libapi + * + *

Basic file handling and streams

* - * The header file.h declares a gmx::File class for basic I/O support. + * The header textstream.h declares interfaces for simple text format streams. + * Headers filestream.h and stringstream.h provide implementations for these + * streams for reading/writing files and for writing to in-memory strings. * - * The header path.h declares helpers for manipulating paths and for managing - * directories. + * The header fileredirector.h provides interfaces for redirecting file input + * and/or output to alternative streams, for use in testing, as well as default + * implementations for these interfaces that just use the file system. * - * The fate of these headers depends on what is decided in Redmine issue #950. + * The header textwriter.h provides gmx::TextWriter for more formatting support + * when writing to a text stream. * + * The header path.h declares helpers for manipulating paths as strings and for + * managing directories and files. + * The fate of this header depends on what is decided in Redmine issue #950. + * + * \endif * - *

Implementation Helpers

+ *

Implementation helpers

* * The header basedefinitions.h contains common definitions and macros used * throughout \Gromacs. It includes fixed-width integer types (`gmx_int64_t` @@ -114,7 +125,7 @@ * safety when using bit flag fields. * * - *

Other Functionality

+ *

Other functionality

* * The header init.h declares gmx::init() and gmx::finalize() for initializing * and deinitializing the \Gromacs library. diff --git a/src/gromacs/utility/file.cpp b/src/gromacs/utility/file.cpp index 76147344aa..fcca1650b2 100644 --- a/src/gromacs/utility/file.cpp +++ b/src/gromacs/utility/file.cpp @@ -140,12 +140,6 @@ 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)) { @@ -333,20 +327,6 @@ File &File::standardInput() return stdinObject; } -// static -File &File::standardOutput() -{ - static File stdoutObject(stdout, false); - return stdoutObject; -} - -// static -File &File::standardError() -{ - static File stderrObject(stderr, false); - return stderrObject; -} - // static std::string File::readToString(const char *filename) { diff --git a/src/gromacs/utility/file.h b/src/gromacs/utility/file.h index 5e45736076..3be92f3b7c 100644 --- a/src/gromacs/utility/file.h +++ b/src/gromacs/utility/file.h @@ -52,43 +52,6 @@ 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. * @@ -127,14 +90,6 @@ 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. * @@ -279,18 +234,6 @@ class File * \throws std::bad_alloc if out of memory (only on first call). */ static File &standardInput(); - /*! \brief - * Returns a File object for accessing stdout. - * - * \throws std::bad_alloc if out of memory (only on first call). - */ - static File &standardOutput(); - /*! \brief - * Returns a File object for accessing stderr. - * - * \throws std::bad_alloc if out of memory (only on first call). - */ - static File &standardError(); /*! \brief * Reads contents of a file to a std::string. @@ -323,7 +266,7 @@ class File * \param[in] bClose Whether this object should close its file handle. * \throws std::bad_alloc if out of memory. * - * Used internally to implement standardOutput() and standardError(). + * Used internally to implement standardInput(). */ File(FILE *fp, bool bClose); diff --git a/src/gromacs/utility/fileredirector.cpp b/src/gromacs/utility/fileredirector.cpp index 405702bd72..054d7c6495 100644 --- a/src/gromacs/utility/fileredirector.cpp +++ b/src/gromacs/utility/fileredirector.cpp @@ -44,6 +44,7 @@ #include "fileredirector.h" #include "gromacs/utility/file.h" +#include "gromacs/utility/filestream.h" namespace gmx { @@ -88,13 +89,13 @@ class DefaultInputRedirector : public FileInputRedirectorInterface class DefaultOutputRedirector : public FileOutputRedirectorInterface { public: - virtual File &standardOutput() + virtual TextOutputStream &standardOutput() { - return File::standardOutput(); + return TextOutputFile::standardOutput(); } - virtual FileInitializer openFileForWriting(const char *filename) + virtual TextOutputStreamPointer openTextOutputFile(const char *filename) { - return FileInitializer(filename, "w"); + return TextOutputStreamPointer(new TextOutputFile(filename)); } }; diff --git a/src/gromacs/utility/fileredirector.h b/src/gromacs/utility/fileredirector.h index d0a7c6e47e..407d57b924 100644 --- a/src/gromacs/utility/fileredirector.h +++ b/src/gromacs/utility/fileredirector.h @@ -45,7 +45,7 @@ #include -#include "gromacs/utility/file.h" +#include "gromacs/utility/textstream.h" namespace gmx { @@ -55,11 +55,10 @@ namespace gmx * * The calling code should take in this interface and use the methods in it * all file system operations that need to support this redirection. - * By default, the code can then use defaultFileInputRedirector() in case no - * redirection is needed. * * This allows tests to override the file existence checks without actually - * using the file system. + * using the file system. See FileOutputRedirectorInterface for notes on + * a typical usage pattern. * * With some further refactoring of the File class, this could also support * redirecting input files from in-memory buffers as well, but for now the @@ -88,16 +87,25 @@ class FileInputRedirectorInterface /*! \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 + * The calling code should take in this interface and use the stream 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. + * Currently, the (nearly) only purpose for this interface is for unit tests to + * capture the file output without duplicating the knowledge of which files are + * actually produced. The tests can also replace actual files with in-memory + * streams (e.g., a StringOutputStream), and test the output without actually + * accessing the file system and managing actual files. + * + * As the main user for non-default implementation of this interface is tests, + * code using this interface generally uses a pattern where the redirector is + * initialized to defaultFileOutputRedirector(), and a separate setter is + * provided for tests to change the default. This allows code outside the + * tests (and outside the code actually calling the redirector) to be written + * as if this interface did not exist (i.e., they do not need to pass the + * default instance). + * + * Also, the interface only supports text files, but can be generalized if/when + * there is a need for binary streams (see also TextOutputStream). * * \inlibraryapi * \ingroup module_utility @@ -108,20 +116,20 @@ class FileOutputRedirectorInterface virtual ~FileOutputRedirectorInterface(); /*! \brief - * Returns a File object to use for `stdout` output. + * Returns a stream to use for `stdout` output. */ - virtual File &standardOutput() = 0; + virtual TextOutputStream &standardOutput() = 0; /*! \brief - * Returns a File object to use for output to a given file. + * Returns a stream to use for output to a file at a given path. * * \param[in] filename Requested file name. */ - virtual FileInitializer openFileForWriting(const char *filename) = 0; + virtual TextOutputStreamPointer openTextOutputFile(const char *filename) = 0; - //! Convenience method to open a file using an std::string path. - FileInitializer openFileForWriting(const std::string &filename) + //! Convenience method to open a stream using an std::string path. + TextOutputStreamPointer openTextOutputFile(const std::string &filename) { - return openFileForWriting(filename.c_str()); + return openTextOutputFile(filename.c_str()); } }; diff --git a/src/gromacs/utility/filestream.cpp b/src/gromacs/utility/filestream.cpp new file mode 100644 index 0000000000..08d975421f --- /dev/null +++ b/src/gromacs/utility/filestream.cpp @@ -0,0 +1,171 @@ +/* + * 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 from filestream.h. + * + * \author Teemu Murtola + * \ingroup module_utility + */ +#include "gmxpre.h" + +#include "filestream.h" + +#include +#include + +#include "gromacs/utility/exceptions.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/stringutil.h" + +namespace gmx +{ + +namespace internal +{ + +/******************************************************************** + * FileStreamImpl + */ + +class FileStreamImpl +{ + public: + explicit FileStreamImpl(FILE *fp) + : fp_(fp), bClose_(false) + { + } + FileStreamImpl(const char *filename, const char *mode) + : fp_(NULL), bClose_(true) + { + fp_ = std::fopen(filename, mode); + if (fp_ == NULL) + { + GMX_THROW_WITH_ERRNO( + FileIOError(formatString("Could not open file '%s'", filename)), + "fopen", errno); + } + } + ~FileStreamImpl() + { + if (fp_ != NULL && bClose_) + { + if (std::fclose(fp_) != 0) + { + // TODO: Log the error somewhere + } + } + } + + FILE *handle() + { + GMX_RELEASE_ASSERT(fp_ != NULL, + "Attempted to access a file object that is not open"); + return fp_; + } + + void close() + { + GMX_RELEASE_ASSERT(fp_ != NULL, + "Attempted to close a file object that is not open"); + GMX_RELEASE_ASSERT(bClose_, + "Attempted to close a file object that should not be"); + const bool bOk = (std::fclose(fp_) == 0); + fp_ = NULL; + if (!bOk) + { + GMX_THROW_WITH_ERRNO( + FileIOError("Error while closing file"), "fclose", errno); + } + } + + private: + //! File handle for this object (NULL if the stream has been closed). + FILE *fp_; + //! Whether \p fp_ should be closed by this object. + bool bClose_; +}; + +} // namespace internal + +using internal::FileStreamImpl; + +/******************************************************************** + * TextOutputFile + */ + +TextOutputFile::TextOutputFile(const std::string &filename) + : impl_(new FileStreamImpl(filename.c_str(), "w")) +{ +} + +TextOutputFile::TextOutputFile(FILE *fp) + : impl_(new FileStreamImpl(fp)) +{ +} + +TextOutputFile::~TextOutputFile() +{ +} + +void TextOutputFile::write(const char *str) +{ + if (std::fprintf(impl_->handle(), "%s", str) < 0) + { + GMX_THROW_WITH_ERRNO(FileIOError("Writing to file failed"), + "fprintf", errno); + } +} + +void TextOutputFile::close() +{ + impl_->close(); +} + +// static +TextOutputFile &TextOutputFile::standardOutput() +{ + static TextOutputFile stdoutObject(stdout); + return stdoutObject; +} + +// static +TextOutputFile &TextOutputFile::standardError() +{ + static TextOutputFile stderrObject(stderr); + return stderrObject; +} + +} // namespace gmx diff --git a/src/gromacs/utility/filestream.h b/src/gromacs/utility/filestream.h new file mode 100644 index 0000000000..8a2d2a379f --- /dev/null +++ b/src/gromacs/utility/filestream.h @@ -0,0 +1,116 @@ +/* + * 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 implementations for textstream.h interfaces for file input/output. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_FILESTREAM_H +#define GMX_UTILITY_FILESTREAM_H + +#include + +#include + +#include "gromacs/utility/classhelpers.h" +#include "gromacs/utility/textstream.h" + +namespace gmx +{ + +namespace internal +{ +class FileStreamImpl; +} + +/*! \libinternal \brief + * Text output stream implementation for writing to a file. + * + * Implementations for the TextOutputStream methods throw FileIOError on any + * I/O error. + * + * \inlibraryapi + * \ingroup module_utility + */ +class TextOutputFile : public TextOutputStream +{ + public: + /*! \brief + * Opens a text file as a stream. + * + * \param[in] filename Path to the file to open. + * \throws std::bad_alloc if out of memory. + * \throws FileIOError on any I/O error. + */ + explicit TextOutputFile(const std::string &filename); + /*! \brief + * Initializes file object from an existing file handle. + * + * \param[in] fp File handle to use. + * \throws std::bad_alloc if out of memory. + * + * The caller is responsible of closing the file; close() does nothing + * for an object constructed this way. + */ + explicit TextOutputFile(FILE *fp); + virtual ~TextOutputFile(); + + // From TextOutputStream + virtual void write(const char *text); + virtual void close(); + + /*! \brief + * Returns a stream for accessing `stdout`. + * + * \throws std::bad_alloc if out of memory (only on first call). + */ + static TextOutputFile &standardOutput(); + /*! \brief + * Returns a stream for accessing `stderr`. + * + * \throws std::bad_alloc if out of memory (only on first call). + */ + static TextOutputFile &standardError(); + + private: + PrivateImplPointer impl_; +}; + +} // namespace gmx + +#endif diff --git a/src/gromacs/utility/nodelete.h b/src/gromacs/utility/nodelete.h new file mode 100644 index 0000000000..7f9fe0c454 --- /dev/null +++ b/src/gromacs/utility/nodelete.h @@ -0,0 +1,71 @@ +/* + * 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 no_delete deleter for boost::shared_ptr. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_NODELETE_H +#define GMX_UTILITY_NODELETE_H + +namespace gmx +{ + +/*! \libinternal \brief + * Deleter for boost::shared_ptr that does nothing. + * + * This is useful for cases where a class needs to keep a reference to another + * class, and optionally also manage the lifetime of that other class. + * The simplest construct (that does not force all callers to use heap + * allocation and boost::shared_ptr for the referenced class) is to use a + * single boost::shared_ptr to hold that reference, and use no_delete as the + * deleter if the lifetime is managed externally. + * + * \inlibraryapi + * \ingroup module_utility + */ +template +struct no_delete +{ + //! Deleter that does nothing. + void operator()(T *) {} +}; + +} // namespace gmx + +#endif diff --git a/src/gromacs/utility/stringstream.cpp b/src/gromacs/utility/stringstream.cpp new file mode 100644 index 0000000000..7a93d3472c --- /dev/null +++ b/src/gromacs/utility/stringstream.cpp @@ -0,0 +1,60 @@ +/* + * 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 from stringstream.h. + * + * \author Teemu Murtola + * \ingroup module_utility + */ +#include "gmxpre.h" + +#include "stringstream.h" + +#include + +namespace gmx +{ + +void StringOutputStream::write(const char *str) +{ + str_.append(str); +} + +void StringOutputStream::close() +{ +} + +} // namespace gmx diff --git a/src/gromacs/utility/stringstream.h b/src/gromacs/utility/stringstream.h new file mode 100644 index 0000000000..899a304a12 --- /dev/null +++ b/src/gromacs/utility/stringstream.h @@ -0,0 +1,80 @@ +/* + * 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 implementations for textstream.h interfaces for input/output to + * in-memory strings. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_STRINGSTREAM_H +#define GMX_UTILITY_STRINGSTREAM_H + +#include + +#include "gromacs/utility/classhelpers.h" +#include "gromacs/utility/textstream.h" + +namespace gmx +{ + +/*! \libinternal \brief + * Text output stream implementation for writing to an in-memory string. + * + * Implementations for the TextOutputStream methods throw std::bad_alloc if + * reallocation of the string fails. + * + * \inlibraryapi + * \ingroup module_utility + */ +class StringOutputStream : public TextOutputStream +{ + public: + //! Returns the text written to the stream so far. + const std::string &toString() const { return str_; } + + // From TextOutputStream + virtual void write(const char *text); + virtual void close(); + + private: + std::string str_; +}; + +} // namespace gmx + +#endif diff --git a/src/gromacs/utility/textstream.h b/src/gromacs/utility/textstream.h new file mode 100644 index 0000000000..be4bc69fc0 --- /dev/null +++ b/src/gromacs/utility/textstream.h @@ -0,0 +1,104 @@ +/* + * 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 interfaces for simple input/output streams. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_TEXTSTREAM_H +#define GMX_UTILITY_TEXTSTREAM_H + +#include + +namespace gmx +{ + +/*! \libinternal \brief + * Interface for writing text. + * + * Concrete implementations can write the text to, e.g., a file or an in-memory + * string. The main use is to allow unit tests to inject in-memory buffers + * instead of reading in files produced by the code under test, but there are + * also use cases outside the tests where it is useful to abstract out whether + * the output is into a real file or something else. + * + * To use more advanced formatting than writing plain strings, use TextWriter. + * + * The current implementation assumes text-only output in several places, but + * this interface could possibly be generalized also for binary files. + * However, since all binary files currently written by \Gromacs are either + * XDR- or TNG-based, they may require a different approach. Also, it is worth + * keeping the distinction between text and binary files clear, since Windows + * does transparent `LF`-`CRLF` newline translation for text files, so mixing + * modes when reading and/or writing the same file can cause subtle issues. + * + * Both methods in the interface can throw std::bad_alloc or other exceptions + * that indicate failures to write to the stream. + * + * \inlibraryapi + * \ingroup module_utility + */ +class TextOutputStream +{ + public: + virtual ~TextOutputStream() {} + + /*! \brief + * Writes a given string to the stream. + */ + virtual void write(const char *text) = 0; + /*! \brief + * Closes the stream. + * + * It is not allowed to write to a stream after it has been closed. + * A method separate from the destructor is provided such that errors + * that occur while closing the stream (e.g., when closing the file) + * can be handled using exceptions. + * The destructor is not allowed to throw, so code that wants to + * observe such errors needs to call close() after it has finished + * writing to the stream. + */ + virtual void close() = 0; +}; + +//! Shorthand for a smart pointer to a TextOutputStream. +typedef boost::shared_ptr TextOutputStreamPointer; + +} // namespace gmx + +#endif diff --git a/src/gromacs/utility/textwriter.cpp b/src/gromacs/utility/textwriter.cpp new file mode 100644 index 0000000000..aef0d5cc7b --- /dev/null +++ b/src/gromacs/utility/textwriter.cpp @@ -0,0 +1,125 @@ +/* + * 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 gmx::TextWriter. + * + * \author Teemu Murtola + * \ingroup module_utility + */ +#include "gmxpre.h" + +#include "textwriter.h" + +#include + +#include "gromacs/utility/filestream.h" +#include "gromacs/utility/nodelete.h" +#include "gromacs/utility/textstream.h" + +namespace gmx +{ + +class TextWriter::Impl +{ + public: + explicit Impl(const TextOutputStreamPointer &stream) + : stream_(stream) + { + } + + TextOutputStreamPointer stream_; +}; + +TextWriter::TextWriter(const std::string &filename) + : impl_(new Impl(TextOutputStreamPointer(new TextOutputFile(filename)))) +{ +} + +TextWriter::TextWriter(TextOutputStream *stream) + : impl_(new Impl(TextOutputStreamPointer(stream, no_delete()))) +{ +} + +TextWriter::TextWriter(const TextOutputStreamPointer &stream) + : impl_(new Impl(stream)) +{ +} + +TextWriter::~TextWriter() +{ +} + +TextOutputStream &TextWriter::stream() +{ + return *impl_->stream_; +} + +void TextWriter::writeString(const char *str) +{ + impl_->stream_->write(str); +} + +void TextWriter::writeString(const std::string &str) +{ + impl_->stream_->write(str.c_str()); +} + +void TextWriter::writeLine(const char *line) +{ + const size_t length = std::strlen(line); + writeString(line); + if (length == 0 || line[length-1] != '\n') + { + writeLine(); + } +} + +void TextWriter::writeLine(const std::string &line) +{ + writeLine(line.c_str()); +} + +void TextWriter::writeLine() +{ + writeString("\n"); +} + +void TextWriter::close() +{ + impl_->stream_->close(); +} + +} // namespace gmx diff --git a/src/gromacs/utility/textwriter.h b/src/gromacs/utility/textwriter.h new file mode 100644 index 0000000000..c473b8c279 --- /dev/null +++ b/src/gromacs/utility/textwriter.h @@ -0,0 +1,144 @@ +/* + * 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::TextWriter. + * + * \author Teemu Murtola + * \inlibraryapi + * \ingroup module_utility + */ +#ifndef GMX_UTILITY_TEXTWRITER_H +#define GMX_UTILITY_TEXTWRITER_H + +#include + +#include "gromacs/utility/classhelpers.h" +#include "gromacs/utility/textstream.h" + +namespace gmx +{ + +/*! \libinternal \brief + * Writes text into a TextOutputStream. + * + * This class provides more formatting and line-oriented writing capabilities + * than writing raw strings into the stream. + * + * All methods that write to the stream can throw any exceptions that the + * underlying stream throws. + * + * \inlibraryapi + * \ingroup module_utility + */ +class TextWriter +{ + public: + /*! \brief + * Creates a writer that writes to specified file. + * + * \param[in] filename Path to the file to open. + * \throws std::bad_alloc if out of memory. + * \throws FileIOError on any I/O error. + * + * This constructor is provided for convenience for writing directly to + * a file, without the need to construct multiple objects. + */ + explicit TextWriter(const std::string &filename); + /*! \brief + * Creates a writer that writes to specified stream. + * + * \param[in] stream Stream to write to. + * \throws std::bad_alloc if out of memory. + * + * The caller is responsible of the lifetime of the stream (should + * remain in existence as long as the writer exists). + * + * This constructor is provided for convenience for cases where the + * stream is not allocated with `new` and/or not managed by a + * boost::shared_ptr (e.g., if the stream is an object on the stack). + */ + explicit TextWriter(TextOutputStream *stream); + /*! \brief + * Creates a writer that writes to specified stream. + * + * \param[in] stream Stream to write to. + * \throws std::bad_alloc if out of memory. + * + * The writer keeps a reference to the stream, so the caller can pass + * in a temporary if necessary. + */ + explicit TextWriter(const TextOutputStreamPointer &stream); + ~TextWriter(); + + //! Returns the underlying stream for this writer. + TextOutputStream &stream(); + + /*! \brief + * Writes a string to the stream. + * + * \param[in] str String to write. + */ + void writeString(const char *str); + //! \copydoc writeString(const char *) + void writeString(const std::string &str); + /*! \brief + * Writes a line to the stream. + * + * \param[in] line Line to write. + * + * If \p line does not end in a newline, one newline is appended. + * Otherwise, works as writeString(). + */ + void writeLine(const char *line); + //! \copydoc writeLine(const char *) + void writeLine(const std::string &line); + //! Writes a newline to the stream. + void writeLine(); + + /*! \brief + * Closes the underlying stream. + */ + void close(); + + private: + class Impl; + + PrivateImplPointer impl_; +}; + +} // namespace gmx + +#endif diff --git a/src/testutils/cmdlinetest.cpp b/src/testutils/cmdlinetest.cpp index 3d5a956fca..af2e5b2390 100644 --- a/src/testutils/cmdlinetest.cpp +++ b/src/testutils/cmdlinetest.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. @@ -58,6 +58,7 @@ #include "gromacs/utility/file.h" #include "gromacs/utility/gmxassert.h" #include "gromacs/utility/stringutil.h" +#include "gromacs/utility/textwriter.h" #include "testutils/refdata.h" #include "testutils/testfilemanager.h" @@ -308,7 +309,7 @@ void CommandLineTestHelper::setInputFileContents( GMX_ASSERT(extension[0] != '.', "Extension should not contain a dot"); std::string fullFilename = impl_->fileManager_.getTemporaryFilePath( formatString("%d.%s", args->argc(), extension)); - File file(fullFilename, "w"); + TextWriter file(fullFilename); ConstArrayRef::const_iterator i; for (i = contents.begin(); i != contents.end(); ++i) { diff --git a/src/testutils/stringtest.cpp b/src/testutils/stringtest.cpp index 7ce6008345..8c005295e0 100644 --- a/src/testutils/stringtest.cpp +++ b/src/testutils/stringtest.cpp @@ -43,22 +43,15 @@ #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 @@ -70,27 +63,6 @@ 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 @@ -106,74 +78,6 @@ 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 */ @@ -183,31 +87,34 @@ class StringTestBase::Impl public: TestReferenceData data_; boost::scoped_ptr checker_; - boost::scoped_ptr redirector_; }; /******************************************************************** * StringTestBase */ -StringTestBase::StringTestBase() - : impl_(new Impl) +// static +void StringTestBase::checkText(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); + } } -StringTestBase::~StringTestBase() +StringTestBase::StringTestBase() + : impl_(new Impl) { } -FileOutputRedirectorInterface & -StringTestBase::initOutputRedirector(TestFileManager *fileManager) +StringTestBase::~StringTestBase() { - if (impl_->redirector_) - { - GMX_THROW(TestException("initOutputRedirector() called more than once")); - } - impl_->redirector_.reset(new TestFileOutputRedirector(fileManager)); - return *impl_->redirector_; } TestReferenceChecker & @@ -223,7 +130,7 @@ StringTestBase::checker() void StringTestBase::checkText(const std::string &text, const char *id) { - checkTextImpl(&checker(), text, id); + checkText(&checker(), text, id); } void @@ -233,15 +140,5 @@ StringTestBase::checkFileContents(const std::string &filename, const char *id) 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 67e89113a3..ce0f00af86 100644 --- a/src/testutils/stringtest.h +++ b/src/testutils/stringtest.h @@ -52,12 +52,9 @@ namespace gmx { -class FileOutputRedirectorInterface; - namespace test { -class TestFileManager; class TestReferenceChecker; /*! \libinternal \brief @@ -74,20 +71,18 @@ class TestReferenceChecker; class StringTestBase : public ::testing::Test { public: - StringTestBase(); - ~StringTestBase(); - /*! \brief - * Creates a redirector that directs all output to temporary files. + * Checks a block of text. * - * \param[in] fileManager File manager to use for temporary files. - * - * Can only be called once in a test. - * - * \see checkRedirectedOutputFiles() + * This static method is provided for code that does not derive from + * StringTestBase to use the same functionality, e.g., implementing the + * `-stdout` option. */ - FileOutputRedirectorInterface & - initOutputRedirector(TestFileManager *fileManager); + static void checkText(TestReferenceChecker *checker, + const std::string &text, const char *id); + + StringTestBase(); + ~StringTestBase(); /*! \brief * Returns the root checker for this test's reference data. @@ -114,20 +109,6 @@ 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: class Impl; diff --git a/src/testutils/testfileredirector.cpp b/src/testutils/testfileredirector.cpp index 62ea64812b..eebcca434f 100644 --- a/src/testutils/testfileredirector.cpp +++ b/src/testutils/testfileredirector.cpp @@ -45,12 +45,24 @@ #include #include +#include +#include + +#include + +#include "gromacs/utility/stringstream.h" + +#include "testutils/stringtest.h" namespace gmx { namespace test { +/******************************************************************** + * TestFileInputRedirector + */ + TestFileInputRedirector::TestFileInputRedirector() { } @@ -69,5 +81,59 @@ bool TestFileInputRedirector::fileExists(const char *filename) const return existingFiles_.count(filename) > 0; } +/******************************************************************** + * TestFileOutputRedirector::Impl + */ + +class TestFileOutputRedirector::Impl +{ + public: + typedef boost::shared_ptr StringStreamPointer; + typedef std::pair FileListEntry; + + StringStreamPointer stdoutStream_; + std::vector fileList_; +}; + +/******************************************************************** + * TestFileOutputRedirector + */ + +TestFileOutputRedirector::TestFileOutputRedirector() + : impl_(new Impl) +{ +} + +TestFileOutputRedirector::~TestFileOutputRedirector() +{ +} + +TextOutputStream &TestFileOutputRedirector::standardOutput() +{ + if (!impl_->stdoutStream_) + { + impl_->stdoutStream_.reset(new StringOutputStream); + impl_->fileList_.push_back(Impl::FileListEntry("", impl_->stdoutStream_)); + } + return *impl_->stdoutStream_; +} + +TextOutputStreamPointer +TestFileOutputRedirector::openTextOutputFile(const char *filename) +{ + Impl::StringStreamPointer stream(new StringOutputStream); + impl_->fileList_.push_back(Impl::FileListEntry(filename, stream)); + return stream; +} + +void TestFileOutputRedirector::checkRedirectedFiles(TestReferenceChecker *checker) +{ + std::vector::const_iterator i; + for (i = impl_->fileList_.begin(); i != impl_->fileList_.end(); ++i) + { + StringTestBase::checkText(checker, i->second->toString(), i->first.c_str()); + } +} + } // namespace test } // namespace gmx diff --git a/src/testutils/testfileredirector.h b/src/testutils/testfileredirector.h index 711c6c8ac1..ccb3a69490 100644 --- a/src/testutils/testfileredirector.h +++ b/src/testutils/testfileredirector.h @@ -54,6 +54,8 @@ namespace gmx namespace test { +class TestReferenceChecker; + /*! \libinternal \brief * In-memory implementation for FileInputRedirectorInterface for tests. * @@ -87,6 +89,40 @@ class TestFileInputRedirector : public FileInputRedirectorInterface GMX_DISALLOW_COPY_AND_ASSIGN(TestFileInputRedirector); }; +/*! \libinternal \brief + * In-memory implementation of FileOutputRedirectorInterface for tests. + * + * This class redirects all output files to in-memory buffers, and supports + * checking the contents of these files using the reference data framework. + * + * \ingroup module_testutils + */ +class TestFileOutputRedirector : public FileOutputRedirectorInterface +{ + public: + TestFileOutputRedirector(); + virtual ~TestFileOutputRedirector(); + + /*! \brief + * Checks contents of all redirected files (including stdout). + * + * 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 checkRedirectedFiles(TestReferenceChecker *checker); + + // From FileOutputRedirectorInterface + virtual TextOutputStream &standardOutput(); + virtual TextOutputStreamPointer openTextOutputFile(const char *filename); + + private: + class Impl; + + PrivateImplPointer impl_; +}; + } // namespace test } // namespace gmx diff --git a/src/testutils/testinit.cpp b/src/testutils/testinit.cpp index 0fd8d1100b..a17ed2309d 100644 --- a/src/testutils/testinit.cpp +++ b/src/testutils/testinit.cpp @@ -60,7 +60,7 @@ #include "gromacs/options/options.h" #include "gromacs/utility/errorcodes.h" #include "gromacs/utility/exceptions.h" -#include "gromacs/utility/file.h" +#include "gromacs/utility/filestream.h" #include "gromacs/utility/futil.h" #include "gromacs/utility/path.h" #include "gromacs/utility/programcontext.h" @@ -142,7 +142,7 @@ void printHelp(const Options &options) std::fprintf(stderr, "\nYou can use the following GROMACS-specific command-line flags\n" "to control the behavior of the tests:\n\n"); - CommandLineHelpContext context(&File::standardError(), + CommandLineHelpContext context(&TextOutputFile::standardError(), eHelpOutputFormat_Console, NULL, program); context.setModuleDisplayName(program); CommandLineHelpWriter(options).writeHelp(context); diff --git a/src/testutils/testutils-doc.h b/src/testutils/testutils-doc.h index a99fccd9da..0188dff2b4 100644 --- a/src/testutils/testutils-doc.h +++ b/src/testutils/testutils-doc.h @@ -54,6 +54,10 @@ * - gmx::test::TestFileInputRedirector (in testfileredirector.h) provides * functionality for capturing file existence checks in code that uses * gmx::FileInputRedirectorInterface. + * - gmx::test::TestFileOutputRedirector (in testfileredirector.h) provides + * functionality for capturing file output (including `stdout`) from code + * that uses gmx::FileOutputRedirectorInterface, and checking that output + * against reference data. * - #GMX_TEST_OPTIONS macro provides facilities for adding custom command * line options for the test binary. * - testasserts.h provides several custom test assertions for better -- 2.22.0