Add input file options that accept missing files
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 16 Jan 2015 20:26:44 +0000 (22:26 +0200)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Thu, 29 Jan 2015 00:40:50 +0000 (01:40 +0100)
Add a flag to t_filenm and to FileNameOption that makes input options
not fail when the user-provided file does not exist.  Add tests for the
functionality.  Use the flag for -cpi option to support using the same
command line for initial and continuation runs.

Fixes #1672.

Change-Id: I62945266ff15449fb153d5153cd550b3a257df69

src/gromacs/commandline/pargs.cpp
src/gromacs/commandline/tests/pargs.cpp
src/gromacs/fileio/filenm.h
src/gromacs/options/filenameoption.cpp
src/gromacs/options/filenameoption.h
src/gromacs/options/filenameoptionmanager.cpp
src/gromacs/options/filenameoptionstorage.h
src/gromacs/options/tests/filenameoptionmanager.cpp
src/programs/mdrun/mdrun.cpp

index 83180178c5ba638464250b5faf153757f01999e7..0b3538f395cb29afbd229c532ff8a5abd2f3ff3f 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.
@@ -328,6 +328,7 @@ void OptionsAdapter::filenmToOptions(Options *options, t_filenm *fnm)
     const bool        bOptional = ((fnm->flag & ffOPT)   != 0);
     const bool        bLibrary  = ((fnm->flag & ffLIB)   != 0);
     const bool        bMultiple = ((fnm->flag & ffMULT)  != 0);
+    const bool        bMissing  = ((fnm->flag & ffALLOW_MISSING) != 0);
     const char *const name      = &fnm->opt[1];
     const char *      defName   = fnm->fn;
     int               defType   = -1;
@@ -349,6 +350,7 @@ void OptionsAdapter::filenmToOptions(Options *options, t_filenm *fnm)
                     .legacyType(fnm->ftp).legacyOptionalBehavior()
                     .readWriteFlags(bRead, bWrite).required(!bOptional)
                     .libraryFile(bLibrary).multiValue(bMultiple)
+                    .allowMissing(bMissing)
                     .description(ftp2desc(fnm->ftp)));
 }
 
index 55dc8b0c36cc163117f6c3f5e6f7b0641e6bacfa..d74f12f9877b437e37aa97e6dc8a13f98449ba0d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * 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.
@@ -439,6 +439,27 @@ TEST_F(ParseCommonArgsTest, HandlesNonExistentOptionalInputFiles)
     done_filenms(nfile(), fnm);
 }
 
+TEST_F(ParseCommonArgsTest, AcceptsNonExistentInputFilesIfSpecified)
+{
+    t_filenm          fnm[] = {
+        { efCPT, "-c",  "file1", ffOPTRD | ffALLOW_MISSING },
+        { efCPT, "-c2", "file2", ffOPTRD | ffALLOW_MISSING },
+        { efCPT, "-c3", "file3", ffOPTRD | ffALLOW_MISSING },
+        { efCPT, "-c4", "file4", ffOPTRD | ffALLOW_MISSING },
+        { efTRX, "-f",  "trj",   ffOPTRD | ffALLOW_MISSING }
+    };
+    const char *const cmdline[] = {
+        "test", "-c2", "-c3", "nonexistent", "-c4", "nonexistent.cpt", "-f", "nonexistent"
+    };
+    parseFromArray(cmdline, 0, fnm, gmx::EmptyArrayRef());
+    EXPECT_STREQ("file1.cpt", opt2fn("-c", nfile(), fnm));
+    EXPECT_STREQ("file2.cpt", opt2fn("-c2", nfile(), fnm));
+    EXPECT_STREQ("nonexistent.cpt", opt2fn("-c3", nfile(), fnm));
+    EXPECT_STREQ("nonexistent.cpt", opt2fn("-c4", nfile(), fnm));
+    EXPECT_STREQ("nonexistent.xtc", opt2fn("-f", nfile(), fnm));
+    done_filenms(nfile(), fnm);
+}
+
 TEST_F(ParseCommonArgsTest, HandlesCompressedFiles)
 {
     t_filenm          fnm[] = {
index 0dcfd73b9d57fed2ad9f624724de42c0f28f3561..dbd2bf730b3d3c044736482b8a4e0ad28e57c7a0 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.
@@ -82,6 +82,7 @@ typedef struct {
 #define ffOPT   1<<3
 #define ffLIB   1<<4
 #define ffMULT  1<<5
+#define ffALLOW_MISSING 1<<6
 #define ffRW    (ffREAD | ffWRITE)
 #define ffOPTRD (ffREAD | ffOPT)
 #define ffOPTWR (ffWRITE| ffOPT)
index 1f136417867b10d02b56cd975675f40acc25a886..68843686536900ddce5b178f46de23cc4fd360ac 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.
@@ -204,7 +204,7 @@ FileNameOptionStorage::FileNameOptionStorage(const FileNameOption  &settings,
                                              FileNameOptionManager *manager)
     : MyBase(settings), info_(this), manager_(manager), fileType_(-1),
       defaultExtension_(""), bRead_(settings.bRead_), bWrite_(settings.bWrite_),
-      bLibrary_(settings.bLibrary_)
+      bLibrary_(settings.bLibrary_), bAllowMissing_(settings.bAllowMissing_)
 {
     GMX_RELEASE_ASSERT(!hasFlag(efOption_MultipleTimes),
                        "allowMultiple() is not supported for file name options");
@@ -479,6 +479,11 @@ bool FileNameOptionInfo::isLibraryFile() const
     return option().isLibraryFile();
 }
 
+bool FileNameOptionInfo::allowMissing() const
+{
+    return option().allowMissing();
+}
+
 bool FileNameOptionInfo::isDirectoryOption() const
 {
     return option().isDirectoryOption();
index 2c1536fc8c683a6c54a48c6f2a42b4a743beaac7..07fefeb3c805fe332e04d0995bf38ac6979b48af 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.
@@ -76,7 +76,8 @@ class FileNameOption : public OptionTemplate<std::string, FileNameOption>
             : MyBase(name), optionType_(eftUnknown), legacyType_(-1),
               defaultBasename_(NULL), defaultType_(-1),
               bLegacyOptionalBehavior_(false),
-              bRead_(false), bWrite_(false), bLibrary_(false)
+              bRead_(false), bWrite_(false), bLibrary_(false),
+              bAllowMissing_(false)
         {
         }
 
@@ -134,6 +135,20 @@ class FileNameOption : public OptionTemplate<std::string, FileNameOption>
          */
         MyClass &libraryFile(bool bLibrary = true)
         { bLibrary_ = bLibrary; return me(); }
+        /*! \brief
+         * Tells that missing file names explicitly provided by the user are
+         * valid for this input option.
+         *
+         * If this method is not called, an error will be raised if the user
+         * explicitly provides a file name that does not name an existing file,
+         * or if the default value does not resolve to a valid file name for a
+         * required option that the user has not set.
+         *
+         * This method only has effect with input files, and only if a
+         * FileNameOptionManager is being used.
+         */
+        MyClass &allowMissing(bool bAllow = true)
+        { bAllowMissing_ = bAllow; return me(); }
         /*! \brief
          * Sets a default basename for the file option.
          *
@@ -194,6 +209,7 @@ class FileNameOption : public OptionTemplate<std::string, FileNameOption>
         bool                    bRead_;
         bool                    bWrite_;
         bool                    bLibrary_;
+        bool                    bAllowMissing_;
 
         /*! \brief
          * Needed to initialize FileNameOptionStorage from this class without
@@ -229,6 +245,8 @@ class FileNameOptionInfo : public OptionInfo
          * \see FileNameOption::libraryFile()
          */
         bool isLibraryFile() const;
+        //! Whether the (input) option allows missing files to be provided.
+        bool allowMissing() const;
 
         //! Whether the option specifies directories.
         bool isDirectoryOption() const;
index 834ec2cc78e2ce250448aac5ddb8cd02a21da05c..fc630dfa25613435419e8dc505094bf2a29d2e1d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2014, by the GROMACS development team, led by
+ * Copyright (c) 2014,2015, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -146,13 +146,16 @@ void FileNameOptionManager::addDefaultFileNameOption(
 std::string FileNameOptionManager::completeFileName(
         const std::string &value, const FileNameOptionInfo &option)
 {
-    const bool bInput = option.isInputFile() || option.isInputOutputFile();
+    const bool bAllowMissing = option.allowMissing();
+    const bool bInput
+        = option.isInputFile() || option.isInputOutputFile();
     // Currently, directory options are simple, and don't need any
     // special processing.
     // TODO: Consider splitting them into a separate DirectoryOption.
     if (option.isDirectoryOption())
     {
-        if (!impl_->bInputCheckingDisabled_ && bInput && !Directory::exists(value))
+        if (!impl_->bInputCheckingDisabled_ && bInput && !bAllowMissing
+            && !Directory::exists(value))
         {
             std::string message
                 = formatString("Directory '%s' does not exist or is not accessible.",
@@ -198,7 +201,11 @@ std::string FileNameOptionManager::completeFileName(
             {
                 return processedValue;
             }
-            if (option.isLibraryFile())
+            if (bAllowMissing)
+            {
+                return value + option.defaultExtension();
+            }
+            else if (option.isLibraryFile())
             {
                 // TODO: Treat also library files here.
                 return value + option.defaultExtension();
@@ -218,7 +225,7 @@ std::string FileNameOptionManager::completeFileName(
             {
                 // TODO: Treat also library files.
             }
-            else if (!File::exists(value))
+            else if (!bAllowMissing && !File::exists(value))
             {
                 std::string message
                     = formatString("File '%s' does not exist or is not accessible.",
@@ -254,6 +261,7 @@ std::string FileNameOptionManager::completeDefaultFileName(
     const bool        bInput = option.isInputFile() || option.isInputOutputFile();
     const std::string realPrefix
         = !impl_->defaultFileName_.empty() ? impl_->defaultFileName_ : prefix;
+    const bool        bAllowMissing = option.allowMissing();
     if (bInput)
     {
         std::string completedName = findExistingExtension(realPrefix, option);
@@ -261,7 +269,11 @@ std::string FileNameOptionManager::completeDefaultFileName(
         {
             return completedName;
         }
-        if (option.isLibraryFile())
+        if (bAllowMissing)
+        {
+            return realPrefix + option.defaultExtension();
+        }
+        else if (option.isLibraryFile())
         {
             // TODO: Treat also library files here.
             return realPrefix + option.defaultExtension();
index 7628a379d1a0f90e7baa7afa74f38cbc8699c04d..8e799687dd4e2de2157a5032fa8af7c7f96c6016 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.
@@ -83,6 +83,8 @@ class FileNameOptionStorage : public OptionStorageTemplate<std::string>
         bool isInputOutputFile() const { return bRead_ && bWrite_; }
         //! \copydoc FileNameOptionInfo::isLibraryFile()
         bool isLibraryFile() const { return bLibrary_; }
+        //! \copydoc FileNameOptionInfo::allowMissing()
+        bool allowMissing() const { return bAllowMissing_; }
 
         //! \copydoc FileNameOptionInfo::isDirectoryOption()
         bool isDirectoryOption() const;
@@ -108,6 +110,7 @@ class FileNameOptionStorage : public OptionStorageTemplate<std::string>
         bool                    bRead_;
         bool                    bWrite_;
         bool                    bLibrary_;
+        bool                    bAllowMissing_;
 };
 
 } // namespace gmx
index 8e2e5238834b00239cf5b0583b421e9370bb7558..bb01302e8629ef62e00e298f70615066759efeaa 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2014, by the GROMACS development team, led by
+ * Copyright (c) 2014,2015, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -192,6 +192,63 @@ TEST_F(FileNameOptionManagerTest, GivesErrorOnMissingRequiredInputFile)
     EXPECT_THROW_GMX(options_.finish(), gmx::InvalidInputError);
 }
 
+TEST_F(FileNameOptionManagerTest, AcceptsMissingInputFileIfSpecified)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftIndex).inputFile()
+                                    .allowMissing()));
+    EXPECT_TRUE(value.empty());
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.appendValue("missing.ndx"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ("missing.ndx", value);
+}
+
+TEST_F(FileNameOptionManagerTest, AcceptsMissingDefaultInputFileIfSpecified)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value)
+                                    .filetype(gmx::eftIndex).inputFile()
+                                    .defaultBasename("missing")
+                                    .allowMissing()));
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.startOption("f"));
+    EXPECT_NO_THROW_GMX(assigner.finishOption());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ("missing.ndx", value);
+}
+
+TEST_F(FileNameOptionManagerTest, AcceptsMissingRequiredInputFileIfSpecified)
+{
+    std::string value;
+    ASSERT_NO_THROW_GMX(options_.addOption(
+                                FileNameOption("f").store(&value).required()
+                                    .filetype(gmx::eftIndex).inputFile()
+                                    .defaultBasename("missing")
+                                    .allowMissing()));
+    EXPECT_EQ("missing.ndx", value);
+
+    gmx::OptionsAssigner assigner(&options_);
+    EXPECT_NO_THROW_GMX(assigner.start());
+    EXPECT_NO_THROW_GMX(assigner.finish());
+    EXPECT_NO_THROW_GMX(options_.finish());
+
+    EXPECT_EQ("missing.ndx", value);
+}
+
 TEST_F(FileNameOptionManagerTest, AddsMissingExtensionBasedOnExistingFile)
 {
     std::string filename(createDummyFile(".trr"));
index 6f398157219934aa37cc09b7aea6d0d01cb3908d..fa91f5250606724f1b72dd51a82506b6bb8705fe 100644 (file)
@@ -408,7 +408,7 @@ int gmx_mdrun(int argc, char *argv[])
         { efTPR, NULL,      NULL,       ffREAD },
         { efTRN, "-o",      NULL,       ffWRITE },
         { efCOMPRESSED, "-x", NULL,     ffOPTWR },
-        { efCPT, "-cpi",    NULL,       ffOPTRD },
+        { efCPT, "-cpi",    NULL,       ffOPTRD | ffALLOW_MISSING },
         { efCPT, "-cpo",    NULL,       ffOPTWR },
         { efSTO, "-c",      "confout",  ffWRITE },
         { efEDR, "-e",      "ener",     ffWRITE },