Make some unit tests use mock file output
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 28 Jun 2015 19:21:52 +0000 (22:21 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Sat, 4 Jul 2015 10:22:39 +0000 (12:22 +0200)
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

36 files changed:
src/gromacs/commandline/cmdlinehelpcontext.cpp
src/gromacs/commandline/cmdlinehelpcontext.h
src/gromacs/commandline/cmdlinehelpmodule.cpp
src/gromacs/commandline/cmdlinehelpwriter.cpp
src/gromacs/commandline/shellcompletions.cpp
src/gromacs/commandline/shellcompletions.h
src/gromacs/commandline/tests/cmdlinehelpmodule.cpp
src/gromacs/commandline/tests/cmdlinehelpwriter.cpp
src/gromacs/commandline/tests/cmdlinemodulemanagertest.cpp
src/gromacs/commandline/tests/cmdlinemodulemanagertest.h
src/gromacs/onlinehelp/helptopic.cpp
src/gromacs/onlinehelp/helpwritercontext.cpp
src/gromacs/onlinehelp/helpwritercontext.h
src/gromacs/onlinehelp/tests/helpmanager.cpp
src/gromacs/selection/selectioncollection.cpp
src/gromacs/selection/selhelp.cpp
src/gromacs/utility.h
src/gromacs/utility/file.cpp
src/gromacs/utility/file.h
src/gromacs/utility/fileredirector.cpp
src/gromacs/utility/fileredirector.h
src/gromacs/utility/filestream.cpp [new file with mode: 0644]
src/gromacs/utility/filestream.h [new file with mode: 0644]
src/gromacs/utility/nodelete.h [new file with mode: 0644]
src/gromacs/utility/stringstream.cpp [new file with mode: 0644]
src/gromacs/utility/stringstream.h [new file with mode: 0644]
src/gromacs/utility/textstream.h [new file with mode: 0644]
src/gromacs/utility/textwriter.cpp [new file with mode: 0644]
src/gromacs/utility/textwriter.h [new file with mode: 0644]
src/testutils/cmdlinetest.cpp
src/testutils/stringtest.cpp
src/testutils/stringtest.h
src/testutils/testfileredirector.cpp
src/testutils/testfileredirector.h
src/testutils/testinit.cpp
src/testutils/testutils-doc.h

index cfe32fbf38905227976a8f86633fd6fdb9ace6d6..d1af037ef5898431e6d590d52f98676823d32245 100644 (file)
@@ -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;
 }
index 330f20aa6f486572d71bbf11c57abd27698c4876..be59e70cbb9d7a444108fff3e4503f7442a53a61 100644 (file)
@@ -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);
index 2a2959cbf86e19b22c25d284f47e7a0d88b339b0..1261c142595e20c463ee38b5ae3599de9e977c00 100644 (file)
@@ -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] [<options>] <command> [<args>][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<File>         indexFile_;
-        boost::scoped_ptr<File>         manPagesFile_;
+        boost::scoped_ptr<TextWriter>   indexFile_;
+        boost::scoped_ptr<TextWriter>   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 </onlinehelp/%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 <http://www.gromacs.org/>.");
-    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 <http://www.gromacs.org/>.");
+    file->close();
 
     indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%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,
index addc8c8361c0e01a1c100a0d35990088cbd7cd4b..1241c667550539d71c5ea9c3302be89d1aebe8dd 100644 (file)
@@ -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_)
     {
index 1eefeda1cf6fd282965b63511aab391030368ca4..a0ef63e4c607e8917d6adb61ca97bb34d0ec1164 100644 (file)
@@ -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 &section)
         {
@@ -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> file_;
+        std::string                   binaryName_;
+        boost::scoped_ptr<TextWriter> 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]}");
index 88435475436e8a4951eaf6e893ebbfccb4f3b15b..f8f11906b6c09f3690baaa3eaf5ebd52c81397b1 100644 (file)
@@ -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,
index b8cb8eae28e2bc4e658bebe3925ffdc61571a8e6..7be3fb5ed7e738c8b02d5605beeaca477390a70d 100644 (file)
@@ -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");
 }
 
index 317cc4c9ab8c5dec62e564fb9c3278bd85c7a0a8..7d3a282e2e4ec6650a61bb8a52f69082caedd8be 100644 (file)
 #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");
 }
 
 
index f85cabe057ec0cabe853a08aa1c26f97c32a4843..5c33490f3a9e789997c673add39e030737d224b9 100644 (file)
@@ -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<CommandLineProgramContext> programContext_;
         boost::scoped_ptr<CommandLineModuleManager>  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
index c156561606b01a59fecd095c6c88743e23042bdc..ecb77a4f66e67c6ac8495646b8e8cabf23249649 100644 (file)
@@ -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;
index 58be839ba3222b2400c08a6551fe92605a80cf9f..6a3678ff6c5c708085237a4ed693b43aa543fc70 100644 (file)
@@ -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);
index f4deb7918619be1e1dd3cb568a7312a1d80cdb4c..07d2f29401e29023d20f6f9fa5e25662cfcac391 100644 (file)
 
 #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<TextWriter &>(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:
index 1cbce8e6b5e5533eaeb4af8e7b8565f15bcc4d9d..95cac6d739a1d347ce540bad92b459c2c97b6faf 100644 (file)
@@ -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.
index f28b0f8e12cdca9a5bc176cd78eeaf1968c0522c..05dfb2438202bc87f0774dcc69adee9053572f83 100644 (file)
 #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)
index e18078814b489a5bfdfc32878001dd145bad0b7a..1e0726dbbd86f345290787e0a5287c0eabc46bf1 100644 (file)
@@ -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
index e96a955f716b7198179e1e9b130efd1b3918c217..131e9d25e8b55f1f742b0d0c6925a42ee616f1ec 100644 (file)
@@ -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)
     {
index 213f698280ab6760a42773a19d6d95e314bb8439..b06d2d9104061a0bac85da4aad49f0aa73060388 100644 (file)
@@ -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.
  * 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.
  *
- * <H3>Error Handling</H3>
+ * <H3>Error handling</H3>
  *
  * 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
  * \endif
  *
  *
- * <H3>Basic %File Handling</H3>
+ * \if libapi
+ *
+ * <H3>Basic file handling and streams</H3>
  *
- * 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
  *
- * <H3>Implementation Helpers</H3>
+ * <H3>Implementation helpers</H3>
  *
  * The header basedefinitions.h contains common definitions and macros used
  * throughout \Gromacs.  It includes fixed-width integer types (`gmx_int64_t`
  * safety when using bit flag fields.
  *
  *
- * <H3>Other Functionality</H3>
+ * <H3>Other functionality</H3>
  *
  * The header init.h declares gmx::init() and gmx::finalize() for initializing
  * and deinitializing the \Gromacs library.
index 76147344aa7dbbcccde11c132b842aa92e651140..fcca1650b2194cf1465b05edae7f8cd02dc8d019 100644 (file)
@@ -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)
 {
index 5e457360760098321180d4f8c3665dc0b115d44e..3be92f3b7c73281f0d9f2ad5093aef9fef6cb16e 100644 (file)
 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);
 
index 405702bd7280e180f17002a0abd6713d32adab96..054d7c649507bad3012fde869f5458a3c2d0259a 100644 (file)
@@ -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));
         }
 };
 
index d0a7c6e47edf55ffaac4a34be22d3f100ce53acb..407d57b924e65e958d3bc4404971dd8a60bd84c3 100644 (file)
@@ -45,7 +45,7 @@
 
 #include <string>
 
-#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 (file)
index 0000000..08d9754
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \ingroup module_utility
+ */
+#include "gmxpre.h"
+
+#include "filestream.h"
+
+#include <cerrno>
+#include <cstdio>
+
+#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 (file)
index 0000000..8a2d2a3
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_FILESTREAM_H
+#define GMX_UTILITY_FILESTREAM_H
+
+#include <cstdio>
+
+#include <string>
+
+#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<internal::FileStreamImpl> impl_;
+};
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/utility/nodelete.h b/src/gromacs/utility/nodelete.h
new file mode 100644 (file)
index 0000000..7f9fe0c
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \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 <class T>
+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 (file)
index 0000000..7a93d34
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \ingroup module_utility
+ */
+#include "gmxpre.h"
+
+#include "stringstream.h"
+
+#include <string>
+
+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 (file)
index 0000000..899a304
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_STRINGSTREAM_H
+#define GMX_UTILITY_STRINGSTREAM_H
+
+#include <string>
+
+#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 (file)
index 0000000..be4bc69
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_TEXTSTREAM_H
+#define GMX_UTILITY_TEXTSTREAM_H
+
+#include <boost/shared_ptr.hpp>
+
+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<TextOutputStream> TextOutputStreamPointer;
+
+} // namespace gmx
+
+#endif
diff --git a/src/gromacs/utility/textwriter.cpp b/src/gromacs/utility/textwriter.cpp
new file mode 100644 (file)
index 0000000..aef0d5c
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \ingroup module_utility
+ */
+#include "gmxpre.h"
+
+#include "textwriter.h"
+
+#include <cstring>
+
+#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<TextOutputStream>())))
+{
+}
+
+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 (file)
index 0000000..c473b8c
--- /dev/null
@@ -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 <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_UTILITY_TEXTWRITER_H
+#define GMX_UTILITY_TEXTWRITER_H
+
+#include <string>
+
+#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> impl_;
+};
+
+} // namespace gmx
+
+#endif
index 3d5a956fca8c09fd79a121b81dddc618572cb3f8..af2e5b23908886a13bbc53a71936bd9c8aba9ba1 100644 (file)
@@ -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 char *>::const_iterator i;
     for (i = contents.begin(); i != contents.end(); ++i)
     {
index 7ce6008345f5ee323fcd8ee11bf075a04f09d39c..8c005295e04c1e764b5e06950d768e27924abe7a 100644 (file)
 
 #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
@@ -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("<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
  */
@@ -183,31 +87,34 @@ class StringTestBase::Impl
     public:
         TestReferenceData                           data_;
         boost::scoped_ptr<TestReferenceChecker>     checker_;
-        boost::scoped_ptr<TestFileOutputRedirector> 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
index 67e89113a3b7e6dc0d2b06275ae0b53eae0c241c..ce0f00af86084ab4b25caeef6283a45be62a1787 100644 (file)
 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;
index 62ea64812b3d056c6e82da0aa1e57986329f63b2..eebcca434fa37645c1f27f06930c69a323a335f6 100644 (file)
 
 #include <set>
 #include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#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<StringOutputStream> StringStreamPointer;
+        typedef std::pair<std::string, StringStreamPointer> FileListEntry;
+
+        StringStreamPointer         stdoutStream_;
+        std::vector<FileListEntry>  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("<stdout>", 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<Impl::FileListEntry>::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
index 711c6c8ac14e735cc573e100b912c270b10ef87d..ccb3a69490e43780c3356f01b6fc2d1388ba375e 100644 (file)
@@ -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> impl_;
+};
+
 } // namespace test
 } // namespace gmx
 
index 0fd8d1100b1784407475280e25f382c6930a47f9..a17ed2309df7d2906f046f887641fcd2264ceebf 100644 (file)
@@ -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);
index a99fccd9dac05911488d62b1c6d4d85fa27b274a..0188dff2b4e793e335e4d400daadd318cd3b90d4 100644 (file)
  *  - 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