From 024aef47119bb9258d5b855506865344e6da2b62 Mon Sep 17 00:00:00 2001
From: Steven Murray <Steven.Murray@cern.ch>
Date: Fri, 13 Dec 2019 17:58:08 +0100
Subject: [PATCH] Added cta-immutable-file-test

---
 cta.spec.in                            |   1 +
 tests/CMakeLists.txt                   |  20 +-
 tests/ImmutableFileTest.cpp            | 284 +++++++++++++++++++++++++
 tests/ImmutableFileTest.hpp            | 118 ++++++++++
 tests/ImmutableFileTestCmdLineArgs.cpp | 116 ++++++++++
 tests/ImmutableFileTestCmdLineArgs.hpp |  58 +++++
 tests/ImmutableFileTestMain.cpp        |  30 +++
 7 files changed, 626 insertions(+), 1 deletion(-)
 create mode 100644 tests/ImmutableFileTest.cpp
 create mode 100644 tests/ImmutableFileTest.hpp
 create mode 100644 tests/ImmutableFileTestCmdLineArgs.cpp
 create mode 100644 tests/ImmutableFileTestCmdLineArgs.hpp
 create mode 100644 tests/ImmutableFileTestMain.cpp

diff --git a/cta.spec.in b/cta.spec.in
index a5c2e9ceb1..5324abe3e5 100644
--- a/cta.spec.in
+++ b/cta.spec.in
@@ -245,6 +245,7 @@ Unit tests and system tests with virtual tape drives
 %defattr(0755,root,root,-)
 %{_libdir}/libsystemTestHelperTests.so*
 %{_libdir}/libcta-tapedSystemTests.so*
+%{_bindir}/cta-immutable-file-test
 %{_bindir}/cta-rdbmsUnitTests
 %{_bindir}/cta-rdbmsUnitTests-oracle.sh
 %{_bindir}/cta-unitTests
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 9b9ceae41d..3feef2cdbb 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -114,7 +114,25 @@ target_link_libraries(cta-systemTests
   pthread
   ${PROTOBUF3_LIBRARIES})
 
-install(TARGETS cta-rdbmsUnitTests cta-unitTests cta-unitTests-multiProcess cta-systemTests DESTINATION usr/bin)
+add_executable(cta-immutable-file-test
+  ImmutableFileTest.cpp
+  ImmutableFileTestMain.cpp
+  ImmutableFileTestCmdLineArgs.cpp)
+
+set_property (TARGET cta-immutable-file-test APPEND PROPERTY INCLUDE_DIRECTORIES "/usr/include/xrootd")
+
+target_link_libraries(cta-immutable-file-test
+  ctacommon)
+
+install(
+  TARGETS
+    cta-rdbmsUnitTests
+    cta-unitTests
+    cta-unitTests-multiProcess
+    cta-systemTests
+    cta-immutable-file-test
+  DESTINATION
+    usr/bin)
 
 install(TARGETS systemTestHelperTests DESTINATION usr/${CMAKE_INSTALL_LIBDIR})
 
diff --git a/tests/ImmutableFileTest.cpp b/tests/ImmutableFileTest.cpp
new file mode 100644
index 0000000000..87e18e691a
--- /dev/null
+++ b/tests/ImmutableFileTest.cpp
@@ -0,0 +1,284 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2019  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/exception/CommandLineNotParsed.hpp"
+#include "common/exception/Exception.hpp"
+#include "tests/ImmutableFileTest.hpp"
+#include "tests/ImmutableFileTestCmdLineArgs.hpp"
+
+
+#include <iostream>
+#include <list>
+#include <sstream>
+#include <utility>
+#include <XrdCl/XrdClFile.hh>
+
+namespace cta {
+
+//------------------------------------------------------------------------------
+// constructor
+//------------------------------------------------------------------------------
+ImmutableFileTest::ImmutableFileTest(
+  std::istream &inStream,
+  std::ostream &outStream,
+  std::ostream &errStream):
+  m_in(inStream),
+  m_out(outStream),
+  m_err(errStream) {
+}
+
+//------------------------------------------------------------------------------
+// main
+//------------------------------------------------------------------------------
+int ImmutableFileTest::main(const int argc, char *const *const argv) {
+  bool cmdLineNotParsed = false;
+  std::string errorMessage;
+
+  try {
+    return exceptionThrowingMain(argc, argv);
+  } catch(exception::CommandLineNotParsed &ue) {
+    errorMessage = ue.getMessage().str();
+    cmdLineNotParsed = true;
+  } catch(exception::Exception &ex) {
+    errorMessage = ex.getMessage().str();
+  } catch(std::exception &se) {
+    errorMessage = se.what();
+  } catch(...) {
+    errorMessage = "An unknown exception was thrown";
+  }
+
+  // Reaching this point means the command has failed, an exception was throw
+  // and errorMessage has been set accordingly
+
+  m_err << errorMessage << std::endl;
+  if(cmdLineNotParsed) {
+    m_err << std::endl;
+    ImmutableFileTestCmdLineArgs::printUsage(m_err);
+  }
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+// exceptionThrowingMain
+//------------------------------------------------------------------------------
+int ImmutableFileTest::exceptionThrowingMain(const int argc, char *const *const argv) {
+  const ImmutableFileTestCmdLineArgs cmdLine(argc, argv);
+
+  if(cmdLine.help) {
+    ImmutableFileTestCmdLineArgs::printUsage(m_out);
+    return 0;
+  }
+
+  if(cmdLine.fileUrl.IsLocalFile()) {
+    exception::Exception ex;
+    ex.getMessage() << cmdLine.fileUrl.GetURL() << " is local";
+    throw ex;
+  }
+  if(fileExists(cmdLine.fileUrl)) {
+    m_out << cmdLine.fileUrl.GetURL() << " already exists" << std::endl;
+    deleteFile(cmdLine.fileUrl);
+    m_out << "Deleted " << cmdLine.fileUrl.GetURL() << std::endl;
+  } else {
+    m_out << cmdLine.fileUrl.GetURL() << " does not exist yet" << std::endl;
+  }
+
+  m_out << "Ready to create " << cmdLine.fileUrl.GetURL()  << std::endl;
+  m_out << std::endl;
+  try {
+    testOpenCloseFile(cmdLine.fileUrl, XrdCl::OpenFlags::New, 0);
+    testOpenCloseFile(cmdLine.fileUrl, XrdCl::OpenFlags::Delete | XrdCl::OpenFlags::Write, 0);
+    testOpenCloseFile(cmdLine.fileUrl, XrdCl::OpenFlags::Write, kXR_NotAuthorized);
+    testOpenCloseFile(cmdLine.fileUrl, XrdCl::OpenFlags::Update, kXR_NotAuthorized);
+    testOpenCloseFile(cmdLine.fileUrl, XrdCl::OpenFlags::Append, kXR_NotAuthorized);
+  } catch(...) {
+    try {
+      deleteFile(cmdLine.fileUrl);
+    } catch(...) {
+      // Do nothing
+    }
+    throw;
+  }
+
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// fileExists
+//------------------------------------------------------------------------------
+bool ImmutableFileTest::fileExists(const XrdCl::URL &url) {
+  XrdCl::FileSystem fs(url);
+  XrdCl::StatInfo *info = 0;
+  const XrdCl::XRootDStatus statStatus = fs.Stat(url.GetPath(), info);
+  if(!statStatus.IsOK()) m_out << statStatus.ToStr() << std::endl;
+  return statStatus.IsOK();
+}
+
+//------------------------------------------------------------------------------
+// deleteFile
+//------------------------------------------------------------------------------
+void ImmutableFileTest::deleteFile(const XrdCl::URL &url) {
+  XrdCl::FileSystem fs(url);
+  const XrdCl::XRootDStatus rmStatus = fs.Rm(url.GetPath());
+
+  if (!rmStatus.IsOK()) {
+    exception::Exception ex;
+    ex.getMessage() << "Failed to rm file: URL=" << url.GetURL() << " :" << rmStatus.ToStr();
+    throw ex;
+  }
+}
+
+//------------------------------------------------------------------------------
+// testOpenCloseFile
+//------------------------------------------------------------------------------
+void ImmutableFileTest::testOpenCloseFile(const XrdCl::URL &url,
+  const XrdCl::OpenFlags::Flags openFlags, const uint32_t expectedOpenErrNo) {
+  bool testPassed = false;
+  std::ostringstream msg;
+  const bool expectedSuccess = 0 == expectedOpenErrNo;
+  XrdCl::File file;
+
+  m_out << "START OF TEST: Opening " << url.GetURL() << " with flags \"" << openFlagsToString(openFlags) << "\"" <<
+    " expecting " << xErrorCodeToString(expectedOpenErrNo) << std::endl;
+
+  const XrdCl::Access::Mode openMode = XrdCl::Access::UR | XrdCl::Access::UW;
+  const XrdCl::XRootDStatus openStatus = file.Open(url.GetURL(), openFlags, openMode);
+  {
+    if (expectedSuccess) {
+      if(openStatus.IsOK()) {
+        testPassed = true;
+        msg << "Succeeded to open file as expected";
+      } else {
+        testPassed = false;
+        msg << "Failure when success was expected: Failed to open file: " << openStatus.ToStr();
+      }
+    } else {
+      if (openStatus.IsOK()) {
+        testPassed = false;
+        msg << "Success when failure was expected: Successfully opened file";
+      } else {
+        if (expectedOpenErrNo == openStatus.errNo) {
+          testPassed = true;
+          msg << "Successfully got " << xErrorCodeToString(expectedOpenErrNo);
+        } else {
+          testPassed = false;
+          msg << "Unexpectedly got " << xErrorCodeToString(openStatus.errNo);
+        }
+      }
+    }
+  }
+
+  if (openStatus.IsOK()){
+    const XrdCl::XRootDStatus closeStatus = file.Close();
+    if (!closeStatus.IsOK()) {
+      exception::Exception ex;
+      ex.getMessage() << "Failed to close \"" << url.GetURL() << "\" :" << closeStatus.ToStr();
+      throw ex;
+    }
+  }
+
+  if(testPassed) {
+    m_out << "END   OF TEST: PASSED: " << msg.str() << std::endl;
+  } else {
+    exception::Exception ex;
+    ex.getMessage() << "END   OF TEST: FAILED: " << msg.str();
+    throw ex;
+  }
+}
+
+//------------------------------------------------------------------------------
+// openFlagsToString
+//------------------------------------------------------------------------------
+std::string ImmutableFileTest::openFlagsToString(XrdCl::OpenFlags::Flags flags) {
+  typedef std::pair<XrdCl::OpenFlags::Flags, std::string> FlagAndName;
+  std::list<FlagAndName> allFlags;
+  allFlags.emplace_back(XrdCl::OpenFlags::Compress, "Compress");
+  allFlags.emplace_back(XrdCl::OpenFlags::Delete, "Delete");
+  allFlags.emplace_back(XrdCl::OpenFlags::Force, "Force");
+  allFlags.emplace_back(XrdCl::OpenFlags::MakePath, "MakePath");
+  allFlags.emplace_back(XrdCl::OpenFlags::New, "New");
+  allFlags.emplace_back(XrdCl::OpenFlags::NoWait, "NoWait");
+  allFlags.emplace_back(XrdCl::OpenFlags::Append, "Append");
+  allFlags.emplace_back(XrdCl::OpenFlags::Read, "Read");
+  allFlags.emplace_back(XrdCl::OpenFlags::Update, "Update");
+  allFlags.emplace_back(XrdCl::OpenFlags::Write, "Write");
+  allFlags.emplace_back(XrdCl::OpenFlags::POSC, "POSC");
+  allFlags.emplace_back(XrdCl::OpenFlags::Refresh, "Refresh");
+  allFlags.emplace_back(XrdCl::OpenFlags::Replica, "Replica");
+  allFlags.emplace_back(XrdCl::OpenFlags::SeqIO, "SeqIO");
+  allFlags.emplace_back(XrdCl::OpenFlags::PrefName, "PrefName");
+
+  std::ostringstream result;
+  for(const auto &flagAndName : allFlags) {
+    const XrdCl::OpenFlags::Flags flag = flagAndName.first;
+    const std::string &name = flagAndName.second;
+
+    if(flags & flag) {
+      if(!result.str().empty()) {
+        result << " | ";
+      }
+      result << name;
+      flags &= ~flag;
+    }
+  }
+
+  if(0 != flags) {
+    if(!result.str().empty()) {
+      result << " | ";
+    }
+    result << "ONE OR MORE UNKNOWN FLAGS";
+  }
+
+  return result.str();
+}
+
+//------------------------------------------------------------------------------
+// xErrorCodeToString
+//------------------------------------------------------------------------------
+std::string ImmutableFileTest::xErrorCodeToString(const uint32_t code) {
+  switch(code) {
+  case 0: return "SUCCESS";
+  case kXR_ArgInvalid: return "kXR_ArgInvalid";
+  case kXR_ArgMissing: return "kXR_ArgMissing";
+  case kXR_ArgTooLong: return "kXR_ArgTooLong";
+  case kXR_FileLocked: return "kXR_FileLocked";
+  case kXR_FileNotOpen: return "kXR_FileNotOpen";
+  case kXR_FSError: return "kXR_FSError";
+  case kXR_InvalidRequest: return "kXR_InvalidRequest";
+  case kXR_IOError: return "kXR_IOError";
+  case kXR_NoMemory: return "kXR_NoMemory";
+  case kXR_NoSpace: return "kXR_NoSpace";
+  case kXR_NotAuthorized: return "kXR_NotAuthorized";
+  case kXR_NotFound: return "kXR_NotFound";
+  case kXR_ServerError: return "kXR_ServerError";
+  case kXR_Unsupported: return "kXR_Unsupported";
+  case kXR_noserver: return "kXR_noserver";
+  case kXR_NotFile: return "kXR_NotFile";
+  case kXR_isDirectory: return "kXR_isDirectory";
+  case kXR_Cancelled: return "kXR_Cancelled";
+  case kXR_ChkLenErr: return "kXR_ChkLenErr";
+  case kXR_ChkSumErr: return "kXR_ChkSumErr";
+  case kXR_inProgress: return "kXR_inProgress";
+  case kXR_overQuota: return "kXR_overQuota";
+  case kXR_SigVerErr: return "kXR_SigVerErr";
+  case kXR_DecryptErr: return "kXR_DecryptErr";
+  case kXR_Overloaded: return "kXR_Overloaded";
+  default: return "UNKNOWN";
+  }
+}
+
+} // namespace cta
diff --git a/tests/ImmutableFileTest.hpp b/tests/ImmutableFileTest.hpp
new file mode 100644
index 0000000000..425e9a85e1
--- /dev/null
+++ b/tests/ImmutableFileTest.hpp
@@ -0,0 +1,118 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <iostream>
+#include <XrdCl/XrdClFile.hh>
+
+namespace cta {
+
+/**
+ * Command-line tool that modifies a file using the XRootD client API.
+ */
+class ImmutableFileTest {
+public:
+
+  /**
+   * Constructor.
+   *
+   * @param inStream Standard input stream.
+   * @param outStream Standard output stream.
+   * @param errStream Standard error stream.
+   */
+  ImmutableFileTest(std::istream &inStream, std::ostream &outStream, std::ostream &errStream);
+
+  /**
+   * The object's implementation of main() that should be called from the main()
+   * of the program.
+   *
+   * @param argc The number of command-line arguments including the program name.
+   * @param argv The command-line arguments.
+   * @return The exit value of the program.
+   */
+  int main(const int argc, char *const *const argv);
+
+private:
+
+  /**
+   * Standard input stream.
+   */
+  std::istream &m_in;
+
+  /**
+   * Standard output stream.
+   */
+  std::ostream &m_out;
+
+  /**
+   * Standard error stream.
+   */
+  std::ostream &m_err;
+
+  /**
+   * An exception throwing version of main().
+   *
+   * @param argc The number of command-line arguments including the program name.
+   * @param argv The command-line arguments.
+   * @return The exit value of the program.
+   */
+  int exceptionThrowingMain(const int argc, char *const *const argv);
+
+  /**
+   * @return True if the specified file exists
+   * @param url The XRootD URL of the file to be tested.
+   */
+  bool fileExists(const XrdCl::URL &url);
+
+  /**
+   * Deletes the specified file.
+   *
+   * @param url The XRootD URL of the file to be deleted.
+   */
+  void deleteFile(const XrdCl::URL &url);
+
+  /**
+   * Tests the opening and closing of the specified file.
+   *
+   * WARNING: The specified file can be destroyed depending on the flags used to
+   * open it.
+   *
+   * @param url The XRootD URL of the file to be tested.
+   * @param openFlags The XRootD flags to be used when opening the file.
+   * @param expectecErrNo The expected errNo result of opening the file.
+   */
+  void testOpenCloseFile(const XrdCl::URL &url, const XrdCl::OpenFlags::Flags openFlags,
+    const uint32_t expectedOpenErrNo);
+
+  /**
+   * @return The string representation of the specified XRootD "open file"
+   * flags.
+   * @param flags The XRootD "open file" flags
+   */
+  std::string openFlagsToString(XrdCl::OpenFlags::Flags flags);
+
+  /**
+   * @return The string representation of the specified XErrorCode.
+   * @param code The XErrorCode.
+   */
+  std::string xErrorCodeToString(uint32_t code);
+
+}; // class ImmutableFileTest
+
+} // namespace cta
diff --git a/tests/ImmutableFileTestCmdLineArgs.cpp b/tests/ImmutableFileTestCmdLineArgs.cpp
new file mode 100644
index 0000000000..5b69526c81
--- /dev/null
+++ b/tests/ImmutableFileTestCmdLineArgs.cpp
@@ -0,0 +1,116 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/exception/CommandLineNotParsed.hpp"
+#include "tests/ImmutableFileTestCmdLineArgs.hpp"
+
+#include <getopt.h>
+#include <ostream>
+
+namespace cta {
+
+//------------------------------------------------------------------------------
+// constructor
+//------------------------------------------------------------------------------
+ImmutableFileTestCmdLineArgs::ImmutableFileTestCmdLineArgs(const int argc, char *const *const argv):
+  help(false) {
+
+  static struct option longopts[] = {
+    {"help", no_argument, NULL, 'h'},
+    {NULL  ,           0, NULL,   0}
+  };
+
+  // Prevent getopt() from printing an error message if it does not recognize
+  // an option character
+  opterr = 0;
+
+  int opt = 0;
+  while((opt = getopt_long(argc, argv, ":h", longopts, NULL)) != -1) {
+    switch(opt) {
+    case 'h':
+      help = true;
+      break;
+    case ':': // Missing parameter
+      {
+        exception::CommandLineNotParsed ex;
+        ex.getMessage() << "The -" << (char)opt << " option requires a parameter";
+        throw ex;
+      }
+    case '?': // Unknown option
+      {
+        exception::CommandLineNotParsed ex;
+        if(0 == optopt) {
+          ex.getMessage() << "Unknown command-line option";
+        } else {
+          ex.getMessage() << "Unknown command-line option: -" << (char)optopt;
+        }
+        throw ex;
+      }
+    default:
+      {
+        exception::CommandLineNotParsed ex;
+        ex.getMessage() <<
+          "getopt_long returned the following unknown value: 0x" <<
+          std::hex << (int)opt;
+        throw ex;
+      }
+    } // switch(opt)
+  } // while getopt_long()
+
+  // There is no need to continue parsing when the help option is set
+  if(help) {
+    return;
+  }
+
+  // Calculate the number of non-option ARGV-elements
+  const int nbArgs = argc - optind;
+
+  // Check the number of arguments
+  if(nbArgs != 1) {
+    exception::CommandLineNotParsed ex;
+    ex.getMessage() << "Wrong number of command-line arguments: expected=1 actual=" << nbArgs;
+    throw ex;
+  }
+
+  const std::string fileUrlString = argv[optind];
+
+  fileUrl.FromString(fileUrlString);
+
+  if(!fileUrl.IsValid()) {
+    throw exception::Exception(std::string("File URL is not a valid XRootD URL: URL=") + fileUrlString);
+  }
+}
+
+//------------------------------------------------------------------------------
+// printUsage
+//------------------------------------------------------------------------------
+void ImmutableFileTestCmdLineArgs::printUsage(std::ostream &os) {
+  os <<
+    "Usage:"                                            "\n"
+    "    cta-immutable-file-test URL"                   "\n"
+    "Where:"                                            "\n"
+    "    URL is the XRootD URL of the destination file" "\n"
+    "Options:"                                          "\n"
+    "    -h,--help"                                     "\n"
+    "        Prints this usage message"                 "\n"
+    "WARNING:"                                          "\n"
+    "    This command will destroy the destination file!";
+  os << std::endl;
+}
+
+} // namespace cta
diff --git a/tests/ImmutableFileTestCmdLineArgs.hpp b/tests/ImmutableFileTestCmdLineArgs.hpp
new file mode 100644
index 0000000000..e546b74a93
--- /dev/null
+++ b/tests/ImmutableFileTestCmdLineArgs.hpp
@@ -0,0 +1,58 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <ostream>
+#include <string>
+#include <XrdCl/XrdClURL.hh>
+
+namespace cta {
+
+/**
+ * Structure to store command-line arguments.
+ */
+struct ImmutableFileTestCmdLineArgs {
+  /**
+   * True if the usage message should be printed.
+   */
+  bool help;
+
+  /**
+   * The XRootd URL of the file to be modified.
+   */
+  XrdCl::URL fileUrl;
+
+  /**
+   * Constructor that parses the specified command-line arguments.
+   *
+   * @param argc The number of command-line arguments including the name of the
+   * executable.
+   * @param argv The vector of command-line arguments.
+   */
+  ImmutableFileTestCmdLineArgs(const int argc, char *const *const argv);
+
+  /**
+   * Prints the usage message of the command-line tool.
+   *
+   * @param os The output stream to which the usage message is to be printed.
+   */
+  static void printUsage(std::ostream &os);
+};
+
+} // namespace cta
diff --git a/tests/ImmutableFileTestMain.cpp b/tests/ImmutableFileTestMain.cpp
new file mode 100644
index 0000000000..f7d2c973f7
--- /dev/null
+++ b/tests/ImmutableFileTestMain.cpp
@@ -0,0 +1,30 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tests/ImmutableFileTest.hpp"
+
+#include <iostream>
+
+//------------------------------------------------------------------------------
+// main
+//------------------------------------------------------------------------------
+int main(const int argc, char *const *const argv) {
+  cta::ImmutableFileTest cmd(std::cin, std::cout, std::cerr);
+
+  return cmd.main(argc, argv);
+}
-- 
GitLab