Update from OSX
[m6w6/libmemcached] / libtest / test.cc
index a8b91094aed0de4b2d252476849972b65dfa0e9a..ef10028c5800160dabb687448ec433b0bc0ba321 100644 (file)
@@ -1,43 +1,25 @@
 /*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
  * 
- *  uTest, libtest
+ *  libtest
  *
  *  Copyright (C) 2011 Data Differential, http://datadifferential.com/
- *  Copyright (C) 2006-2009 Brian Aker
  *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are
- *  met:
+ *  This library 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 3 of the License, or (at your option) any later version.
  *
- *      * Redistributions of source code must retain the above copyright
- *  notice, this list of conditions and the following disclaimer.
- *
- *      * Redistributions in binary form must reproduce the above
- *  copyright notice, this list of conditions and the following disclaimer
- *  in the documentation and/or other materials provided with the
- *  distribution.
- *
- *      * The names of its contributors may not be used to endorse or
- *  promote products derived from this software without specific prior
- *  written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *  This library 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 this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-
-
-
+#include <config.h>
 #include <libtest/common.h>
 
 #include <cassert>
 
 #include <signal.h>
 
-#include <libtest/stats.h>
-
 #ifndef __INTEL_COMPILER
 #pragma GCC diagnostic ignored "-Wold-style-cast"
 #endif
 
-static in_port_t global_port= 0;
-static char global_socket[1024];
-
-in_port_t default_port()
-{
-  assert(global_port);
-  return global_port;
-}
-void set_default_port(in_port_t port)
-{
-  global_port= port;
-}
-
-const char *default_socket()
-{
-  assert(global_socket[0]);
-  return global_socket;
-}
-
-bool test_is_local()
-{
-  return (getenv("LIBTEST_LOCAL"));
-}
-
-void set_default_socket(const char *socket)
-{
-  strncpy(global_socket, socket, strlen(socket));
-}
+using namespace libtest;
 
 static void stats_print(Stats *stats)
 {
-  std::cout << "\tTotal Collections\t\t\t\t" << stats->collection_total << std::endl;
-  std::cout << "\tFailed Collections\t\t\t\t" << stats->collection_failed << std::endl;
-  std::cout << "\tSkipped Collections\t\t\t\t" << stats->collection_skipped << std::endl;
-  std::cout << "\tSucceeded Collections\t\t\t\t" << stats->collection_success << std::endl;
-  std::cout << std::endl;
-  std::cout << "Total\t\t\t\t" << stats->total << std::endl;
-  std::cout << "\tFailed\t\t\t" << stats->failed << std::endl;
-  std::cout << "\tSkipped\t\t\t" << stats->skipped << std::endl;
-  std::cout << "\tSucceeded\t\t" << stats->success << std::endl;
+  if (stats->collection_failed == 0 and stats->collection_success == 0)
+  {
+    return;
+  }
+
+  Out << "\tTotal Collections\t\t\t\t" << stats->collection_total;
+  Out << "\tFailed Collections\t\t\t\t" << stats->collection_failed;
+  Out << "\tSkipped Collections\t\t\t\t" << stats->collection_skipped;
+  Out << "\tSucceeded Collections\t\t\t\t" << stats->collection_success;
+  Outn();
+  Out << "Total\t\t\t\t" << stats->total;
+  Out << "\tFailed\t\t\t" << stats->failed;
+  Out << "\tSkipped\t\t\t" << stats->skipped;
+  Out << "\tSucceeded\t\t" << stats->success;
 }
 
 static long int timedif(struct timeval a, struct timeval b)
@@ -114,324 +71,409 @@ static long int timedif(struct timeval a, struct timeval b)
   return s + us;
 }
 
-const char *test_strerror(test_return_t code)
-{
-  switch (code) {
-  case TEST_SUCCESS:
-    return "ok";
-
-  case TEST_FAILURE:
-    return "failed";
-
-  case TEST_MEMORY_ALLOCATION_FAILURE:
-    return "memory allocation";
-
-  case TEST_SKIPPED:
-    return "skipped";
-
-  case TEST_FATAL:
-    break;
-  }
-
-  return "failed";
-}
+#include <getopt.h>
+#include <unistd.h>
 
-void create_core(void)
+int main(int argc, char *argv[])
 {
-  if (getenv("LIBMEMCACHED_NO_COREDUMP") == NULL)
-  {
-    pid_t pid= fork();
+  bool opt_massive= false;
+  unsigned long int opt_repeat= 1; // Run all tests once
+  bool opt_quiet= false;
+  std::string collection_to_run;
 
-    if (pid == 0)
+  // Options parsing
+  {
+    enum long_option_t {
+      OPT_LIBYATL_VERSION,
+      OPT_LIBYATL_MATCH_COLLECTION,
+      OPT_LIBYATL_MASSIVE,
+      OPT_LIBYATL_QUIET,
+      OPT_LIBYATL_REPEAT
+    };
+
+    static struct option long_options[]=
     {
-      abort();
-    }
-    else
+      { "version", no_argument, NULL, OPT_LIBYATL_VERSION },
+      { "quiet", no_argument, NULL, OPT_LIBYATL_QUIET },
+      { "repeat", no_argument, NULL, OPT_LIBYATL_REPEAT },
+      { "collection", required_argument, NULL, OPT_LIBYATL_MATCH_COLLECTION },
+      { "massive", no_argument, NULL, OPT_LIBYATL_MASSIVE },
+      { 0, 0, 0, 0 }
+    };
+
+    int option_index= 0;
+    while (1)
     {
-      while (waitpid(pid, NULL, 0) != pid) {};
-    }
-  }
-}
-
-enum shutdown_t {
-  SHUTDOWN_RUNNING,
-  SHUTDOWN_GRACEFUL,
-  SHUTDOWN_FORCED
-};
+      int option_rv= getopt_long(argc, argv, "", long_options, &option_index);
+      if (option_rv == -1)
+      {
+        break;
+      }
 
-static Framework *world= NULL;
-static volatile shutdown_t __shutdown= SHUTDOWN_RUNNING;
-pthread_mutex_t shutdown_mutex= PTHREAD_MUTEX_INITIALIZER;
+      switch (option_rv)
+      {
+      case OPT_LIBYATL_VERSION:
+        break;
 
-static bool is_shutdown()
-{
-  bool ret;
-  pthread_mutex_lock(&shutdown_mutex);
-  ret= bool(__shutdown != SHUTDOWN_RUNNING);
-  pthread_mutex_unlock(&shutdown_mutex);
+      case OPT_LIBYATL_QUIET:
+        opt_quiet= true;
+        break;
 
-  return ret;
-}
+      case OPT_LIBYATL_REPEAT:
+        opt_repeat= strtoul(optarg, (char **) NULL, 10);
+        break;
 
-static void set_shutdown(shutdown_t arg)
-{
-  pthread_mutex_lock(&shutdown_mutex);
-  __shutdown= arg;
-  pthread_mutex_unlock(&shutdown_mutex);
-}
+      case OPT_LIBYATL_MATCH_COLLECTION:
+        collection_to_run= optarg;
+        break;
 
-static void *sig_thread(void *arg)
-{   
-  sigset_t *set= (sigset_t *) arg;
+      case OPT_LIBYATL_MASSIVE:
+        opt_massive= true;
+        break;
 
-  while (is_shutdown())
-  {
-    int sig;
-    int error;
-    while ((error= sigwait(set, &sig)) == EINTR) ;
+      case '?':
+        /* getopt_long already printed an error message. */
+        Error << "unknown option to getopt_long()";
+        exit(EXIT_FAILURE);
 
-    switch (sig)
-    {
-    case SIGSEGV:
-    case SIGINT:
-    case SIGABRT:
-      Error << "Signal handling thread got signal " <<  strsignal(sig);
-      set_shutdown(SHUTDOWN_FORCED);
-      break;
-
-    default:
-      Error << "Signal handling thread got unexpected signal " <<  strsignal(sig);
-    case SIGUSR1:
-      break;
+      default:
+        break;
+      }
     }
   }
 
-  return NULL;
-}
-
-
-static void setup_signals(pthread_t& thread)
-{
-  sigset_t set;
-
-  sigemptyset(&set);
-  sigaddset(&set, SIGSEGV);
-  sigaddset(&set, SIGABRT);
-  sigaddset(&set, SIGINT);
-  sigaddset(&set, SIGUSR1);
+  srandom((unsigned int)time(NULL));
 
-  int error;
-  if ((error= pthread_sigmask(SIG_BLOCK, &set, NULL)) != 0)
+  if (bool(getenv("YATL_REPEAT")) and (strtoul(getenv("YATL_REPEAT"), (char **) NULL, 10) > 1))
   {
-    Error << " died during pthread_sigmask(" << strerror(error) << ")";
-    exit(EXIT_FAILURE);
+    opt_repeat= strtoul(getenv("YATL_REPEAT"), (char **) NULL, 10);
   }
 
-  if ((error= pthread_create(&thread, NULL, &sig_thread, (void *) &set)) != 0)
+  if ((bool(getenv("YATL_QUIET")) and (strcmp(getenv("YATL_QUIET"), "0") == 0)) or opt_quiet)
   {
-    Error << " died during pthread_create(" << strerror(error) << ")";
-    exit(EXIT_FAILURE);
+    opt_quiet= true;
   }
-}
-
-
-int main(int argc, char *argv[])
-{
-  world= new Framework();
-
-  if (not world)
+  else if (getenv("JENKINS_URL"))
   {
-    return EXIT_FAILURE;
+    if (bool(getenv("YATL_QUIET")) and (strcmp(getenv("YATL_QUIET"), "1") == 0))
+    { }
+    else
+    {
+      opt_quiet= true;
+    }
   }
 
-  pthread_t thread;
-  setup_signals(thread);
-
-  Stats stats;
-
-  get_world(world);
-
-  test_return_t error;
-  void *creators_ptr= world->create(error);
-  if (test_failed(error))
+  if (opt_quiet)
   {
-    Error << "create() failed";
-    return EXIT_FAILURE;
+    close(STDOUT_FILENO);
   }
 
-  char *collection_to_run= NULL;
-  if (argc > 1)
+  char buffer[1024];
+  if (getenv("LIBTEST_TMP"))
   {
-    collection_to_run= argv[1];
+    snprintf(buffer, sizeof(buffer), "%s", getenv("LIBTEST_TMP"));
   }
-  else if (getenv("TEST_COLLECTION"))
+  else
   {
-    collection_to_run= getenv("TEST_COLLECTION");
+    snprintf(buffer, sizeof(buffer), "%s", LIBTEST_TEMP);
   }
 
-  if (collection_to_run)
+  if (chdir(buffer) == -1)
   {
-    std::cout << "Only testing " <<  collection_to_run << std::endl;
+    char getcwd_buffer[1024];
+    char *dir= getcwd(getcwd_buffer, sizeof(getcwd_buffer));
+
+    Error << "Unable to chdir() from " << dir << " to " << buffer << " errno:" << strerror(errno);
+    return EXIT_FAILURE;
   }
 
-  char *wildcard= NULL;
-  if (argc == 3)
+  if (libtest::libtool() == NULL)
   {
-    wildcard= argv[2];
+    Error << "Failed to locate libtool";
+    return EXIT_FAILURE;
   }
 
-  for (collection_st *next= world->collections; next->name and (not is_shutdown()); next++)
-  {
-    test_return_t collection_rc= TEST_SUCCESS;
-    bool failed= false;
-    bool skipped= false;
+  int exit_code;
 
-    if (collection_to_run && fnmatch(collection_to_run, next->name, 0))
-      continue;
+  try {
+    do {
+      exit_code= EXIT_SUCCESS;
+      Framework world;
 
-    stats.collection_total++;
+      fatal_assert(sigignore(SIGPIPE) == 0);
 
-    collection_rc= world->startup(creators_ptr);
+      libtest::SignalThread signal;
+      if (signal.setup() == false)
+      {
+        Error << "Failed to setup signals";
+        return EXIT_FAILURE;
+      }
 
-    if (collection_rc == TEST_SUCCESS and next->pre)
-    {
-      collection_rc= world->runner->pre(next->pre, creators_ptr);
-    }
+      Stats stats;
 
-    switch (collection_rc)
-    {
-    case TEST_SUCCESS:
-      break;
-
-    case TEST_FATAL:
-    case TEST_FAILURE:
-      Error << next->name << " [ failed ]";
-      stats.collection_failed++;
-      goto cleanup;
-
-    case TEST_SKIPPED:
-      Log << next->name << " [ skipping ]";
-      stats.collection_skipped++;
-      goto cleanup;
-
-    case TEST_MEMORY_ALLOCATION_FAILURE:
-      test_assert(0, "Allocation failure, or unknown return");
-    }
+      get_world(&world);
 
-    Log << "Collection: " << next->name;
+      test_return_t error;
+      void *creators_ptr= world.create(error);
 
-    for (test_st *run= next->tests; run->name; run++)
-    {
-      struct timeval start_time, end_time;
-      long int load_time= 0;
+      switch (error)
+      {
+      case TEST_SUCCESS:
+        break;
+
+      case TEST_SKIPPED:
+        Out << "SKIP " << argv[0];
+        return EXIT_SUCCESS;
+
+      case TEST_FAILURE:
+        return EXIT_FAILURE;
+      }
+
+      if (getenv("YATL_COLLECTION_TO_RUN"))
+      {
+        if (strlen(getenv("YATL_COLLECTION_TO_RUN")))
+        {
+          collection_to_run= getenv("YATL_COLLECTION_TO_RUN");
+        }
+      }
 
-      if (wildcard && fnmatch(wildcard, run->name, 0))
+      if (collection_to_run.compare("none") == 0)
       {
-        continue;
+        return EXIT_SUCCESS;
       }
 
-      test_return_t return_code;
-      if (test_success(return_code= world->item.startup(creators_ptr)))
+      if (collection_to_run.empty() == false)
       {
-        if (test_success(return_code= world->item.flush(creators_ptr, run)))
+        Out << "Only testing " <<  collection_to_run;
+      }
+
+      char *wildcard= NULL;
+      if (argc == 3)
+      {
+        wildcard= argv[2];
+      }
+
+      for (collection_st *next= world.collections; next and next->name and (not signal.is_shutdown()); next++)
+      {
+        bool failed= false;
+        bool skipped= false;
+
+        if (collection_to_run.empty() == false and fnmatch(collection_to_run.c_str(), next->name, 0))
         {
-          // @note pre will fail is SKIPPED is returned
-          if (test_success(return_code= world->item.pre(creators_ptr)))
+          continue;
+        }
+
+        stats.collection_total++;
+
+        test_return_t collection_rc= world.startup(creators_ptr);
+
+        if (collection_rc == TEST_SUCCESS and next->pre)
+        {
+          collection_rc= world.runner()->pre(next->pre, creators_ptr);
+        }
+
+        switch (collection_rc)
+        {
+        case TEST_SUCCESS:
+          break;
+
+        case TEST_FAILURE:
+          Out << next->name << " [ failed ]";
+          failed= true;
+          signal.set_shutdown(SHUTDOWN_GRACEFUL);
+          goto cleanup;
+
+        case TEST_SKIPPED:
+          Out << next->name << " [ skipping ]";
+          skipped= true;
+          goto cleanup;
+
+        default:
+          fatal_message("invalid return code");
+        }
+
+        Out << "Collection: " << next->name;
+
+        for (test_st *run= next->tests; run->name; run++)
+        {
+          struct timeval start_time, end_time;
+          long int load_time= 0;
+
+          if (wildcard && fnmatch(wildcard, run->name, 0))
           {
-            { // Runner Code
-              gettimeofday(&start_time, NULL);
-              return_code= world->runner->run(run->test_fn, creators_ptr);
-              gettimeofday(&end_time, NULL);
-              load_time= timedif(end_time, start_time);
+            continue;
+          }
+
+          test_return_t return_code;
+          try {
+            if (test_success(return_code= world.item.startup(creators_ptr)))
+            {
+              if (test_success(return_code= world.item.flush(creators_ptr, run)))
+              {
+                // @note pre will fail is SKIPPED is returned
+                if (test_success(return_code= world.item.pre(creators_ptr)))
+                {
+                  { // Runner Code
+                    gettimeofday(&start_time, NULL);
+                    assert(world.runner());
+                    assert(run->test_fn);
+                    try 
+                    {
+                      return_code= world.runner()->run(run->test_fn, creators_ptr);
+                    }
+                    // Special case where check for the testing of the exception
+                    // system.
+                    catch (libtest::fatal &e)
+                    {
+                      if (fatal::is_disabled())
+                      {
+                        fatal::increment_disabled_counter();
+                        return_code= TEST_SUCCESS;
+                      }
+                      else
+                      {
+                        throw;
+                      }
+                    }
+
+                    gettimeofday(&end_time, NULL);
+                    load_time= timedif(end_time, start_time);
+                  }
+                }
+
+                // @todo do something if post fails
+                (void)world.item.post(creators_ptr);
+              }
+              else if (return_code == TEST_SKIPPED)
+              { }
+              else if (return_code == TEST_FAILURE)
+              {
+                Error << " item.flush(failure)";
+                signal.set_shutdown(SHUTDOWN_GRACEFUL);
+              }
+            }
+            else if (return_code == TEST_SKIPPED)
+            { }
+            else if (return_code == TEST_FAILURE)
+            {
+              Error << " item.startup(failure)";
+              signal.set_shutdown(SHUTDOWN_GRACEFUL);
             }
           }
 
-          // @todo do something if post fails
-          (void)world->item.post(creators_ptr);
-        }
-        else
-        {
-          Error << " item.flush(failure)";
+          catch (libtest::fatal &e)
+          {
+            Error << "Fatal exception was thrown: " << e.what();
+            return_code= TEST_FAILURE;
+          }
+          catch (std::exception &e)
+          {
+            Error << "Exception was thrown: " << e.what();
+            return_code= TEST_FAILURE;
+          }
+          catch (...)
+          {
+            Error << "Unknown exception occurred";
+            return_code= TEST_FAILURE;
+          }
+
+          stats.total++;
+
+          switch (return_code)
+          {
+          case TEST_SUCCESS:
+            Out << "\tTesting " << run->name <<  "\t\t\t\t\t" << load_time / 1000 << "." << load_time % 1000 << "[ " << test_strerror(return_code) << " ]";
+            stats.success++;
+            break;
+
+          case TEST_FAILURE:
+            stats.failed++;
+            failed= true;
+            Out << "\tTesting " << run->name <<  "\t\t\t\t\t" << "[ " << test_strerror(return_code) << " ]";
+            break;
+
+          case TEST_SKIPPED:
+            stats.skipped++;
+            skipped= true;
+            Out << "\tTesting " << run->name <<  "\t\t\t\t\t" << "[ " << test_strerror(return_code) << " ]";
+            break;
+
+          default:
+            fatal_message("invalid return code");
+          }
+
+          if (test_failed(world.on_error(return_code, creators_ptr)))
+          {
+            Error << "Failed while running on_error()";
+            signal.set_shutdown(SHUTDOWN_GRACEFUL);
+            break;
+          }
         }
-      }
-      else
-      {
-        Error << " item.startup(failure)";
-      }
 
-      stats.total++;
+        (void) world.runner()->post(next->post, creators_ptr);
 
-      switch (return_code)
-      {
-      case TEST_SUCCESS:
-        Log << "\tTesting " << run->name <<  "\t\t\t\t\t" << load_time / 1000 << "." << load_time % 1000 << "[ " << test_strerror(return_code) << " ]";
-        stats.success++;
-        break;
+cleanup:
+        if (failed == false and skipped == false)
+        {
+          stats.collection_success++;
+        }
 
-      case TEST_FATAL:
-      case TEST_FAILURE:
-        stats.failed++;
-        failed= true;
-        Error << "\tTesting " << run->name <<  "\t\t\t\t\t" << "[ " << test_strerror(return_code) << " ]";
-        break;
+        if (failed)
+        {
+          stats.collection_failed++;
+        }
 
-      case TEST_SKIPPED:
-        stats.skipped++;
-        skipped= true;
-        Log << "\tTesting " << run->name <<  "\t\t\t\t\t" << "[ " << test_strerror(return_code) << " ]";
-        break;
+        if (skipped)
+        {
+          stats.collection_skipped++;
+        }
 
-      case TEST_MEMORY_ALLOCATION_FAILURE:
-        test_assert(0, "Memory Allocation Error");
+        world.shutdown(creators_ptr);
+        Outn();
       }
 
-      if (test_failed(world->on_error(return_code, creators_ptr)))
+      if (not signal.is_shutdown())
       {
-        Error << "Failed while running on_error()";
-        break;
+        signal.set_shutdown(SHUTDOWN_GRACEFUL);
       }
-    }
-
-    if (next->post and world->runner->post)
-    {
-      (void) world->runner->post(next->post, creators_ptr);
-    }
 
-    if (failed == 0 and skipped == 0)
-    {
-      stats.collection_success++;
-    }
-cleanup:
+      shutdown_t status= signal.get_shutdown();
+      if (status == SHUTDOWN_FORCED)
+      {
+        Out << "Tests were aborted.";
+        exit_code= EXIT_FAILURE;
+      }
+      else if (stats.collection_failed)
+      {
+        Out << "Some test failed.";
+        exit_code= EXIT_FAILURE;
+      }
+      else if (stats.collection_skipped and stats.collection_failed and stats.collection_success)
+      {
+        Out << "Some tests were skipped.";
+      }
+      else if (stats.collection_success and stats.collection_failed == 0)
+      {
+        Out << "All tests completed successfully.";
+      }
 
-    world->shutdown(creators_ptr);
-    Logn();
-  }
+      stats_print(&stats);
 
-  if (not is_shutdown())
-  {
-    set_shutdown(SHUTDOWN_GRACEFUL);
-    pthread_kill(thread, SIGUSR1);
+      Outn(); // Generate a blank to break up the messages if make check/test has been run
+    } while (exit_code == EXIT_SUCCESS and --opt_repeat);
   }
-
-  if (__shutdown == SHUTDOWN_FORCED)
+  catch (libtest::fatal& e)
   {
-    Error << "Tests were aborted.";
+    std::cerr << e.what() << std::endl;
   }
-  else if (stats.collection_failed or stats.collection_skipped)
+  catch (std::exception& e)
   {
-    Error << "Some test failures and/or skipped test occurred.";
+    std::cerr << e.what() << std::endl;
   }
-  else
+  catch (...)
   {
-    Log << "All tests completed successfully.";
+    std::cerr << "Unknown exception halted execution." << std::endl;
   }
 
-  stats_print(&stats);
-
-  void *retval;
-  pthread_join(thread, &retval);
-
-  delete world;
-
-  return stats.failed == 0 and __shutdown == SHUTDOWN_GRACEFUL ? EXIT_SUCCESS : EXIT_FAILURE;
+  return exit_code;
 }