// =============================================================================
//! \file
//! \brief Test framework
//!
//! Copyright (c) 2012-2020 by the developers. See the LICENSE file for details.


// =============================================================================
// Include headers

// C++98
#include <cstdlib>
#include <iostream>

// Local
extern "C"
{
#include "conf.h"
#include "config.h"
#include "digest.h"
#include "hmac.h"
#include "test.h"
#include "test_base64.h"
#include "test_cancellock.h"
#include "test_compression.h"
#include "test_inet_pton.h"
#include "test_regex.h"
#include "test_snprintf.h"
#include "test_strcasecmp_l.h"
#include "test_timestamp.h"
#include "test_unicode.h"
}
#include "compression.hxx"
#include "tls.hxx"


// =============================================================================
//! \defgroup TEST TEST: Testsuite
//!
//! The tests can be executed with "make test".
//! @{


// =============================================================================
// Macros

//! \brief Check return value of test
#define CHECK_RV(rv) \
{ \
   std::clog << std::flush; \
   if(rv) \
   { \
      std::cout << "A problem was detected!" << std::endl << std::flush; \
   } \
   else  { std::cout << "OK" << std::endl << std::flush; } \
   if(!res && rv)  { res = rv; } \
}


//! \brief Skip test and print info message
#define SKIP_TEST(s) \
{ \
   std::cout << "Skipped (" << s << ")" << std::endl << std::flush; \
}


// =============================================================================
// Variables

extern "C"
{
//! Enable additional debug output if nonzero
int  main_debug = 0;

//! Configuration directory path from command line option (always \c NULL )
const char*  main_confprefix = NULL;
}

static int  first_error = 1;


// =============================================================================
// Forward declarations (do not include main.h)

extern "C"
{
void  ts_environ_init(void);
void  ts_environ_exit(void);
}


// =============================================================================
//! \brief Print error message
//!
//! Exported as C style function
//!
//! \param[in] msg  Message to display on stderr

void  print_error(const char*  msg)
{
   std::clog << std::flush;
   std::cout << std::flush;

   if(first_error)
   {
      std::cout << "Failed" << std::endl << std::flush;
      first_error = 0;
   }

   std::clog << TEST_TAB << msg << std::endl << std::flush;
}


// =============================================================================
//! \brief Test entry point
//!
//! \param[in] argc  Number of command line arguments
//! \param[in] argv  Array containing command line argument strings
//!
//! Exit status of program:
//! - \c EXIT_SUCCESS on success
//! - \c EXIT_FAILURE on error

int  main(int  argc, char**  argv)
{
   int  res = EXIT_SUCCESS;
   int  rv;

   ts_environ_init();

   // Load configuration
   first_error = 1;
   std::cout << "Load configuration ... " << std::flush;
   rv = conf_load(config);
   CHECK_RV(rv);
   if(res)
   {
      goto exit;
   }

   // Initialize modules
#if !CFG_CMPR_DISABLE
   if(0 > cmpr_init())  { res = EXIT_FAILURE; }
#endif  // !CFG_CMPR_DISABLE
#if CFG_USE_TLS
   if(0 > tls_init())  { res = EXIT_FAILURE; }
#endif  // CFG_USE_TLS
   digest_init();
   hmac_init();
   if(EXIT_FAILURE == res)
   {
      std::clog << "Initialization of modules failed" << std::endl;
   }
   else
   {
      std::cout << "========================================"
                   "========================================" << std::endl;

      // Test strcasecmp_l()
      first_error = 1;
      std::cout << "Test 'posix_strcasecmp_l()' ... " << std::flush;
      rv = test_strcasecmp_l();
      CHECK_RV(rv);

      // Test snprintf()
      first_error = 1;
      std::cout << "Test 'posix_snprintf()' ... " << std::flush;
      rv = test_snprintf();
      CHECK_RV(rv);

      // Test inet_pton()
      first_error = 1;
      std::cout << "Test 'posix_inet_pton()' ... " << std::flush;
      rv = test_inet_pton();
      CHECK_RV(rv);

      // Test regcomp() and regexec()
      first_error = 1;
      std::cout << "Test 'posix_regcomp()' and 'posix_regexec()' ... "
                << std::flush;
#if CFG_USE_CLB || CFG_USE_XSI
      rv = test_regex();
      CHECK_RV(rv);
#else  // CFG_USE_CLB || CFG_USE_XSI
      SKIP_TEST("no CLB or X/Open XSI");
#endif  // CFG_USE_CLB || CFG_USE_XSI

      // Test Cancel-Lock implementation
      // Note: Test base64 encoder first because it is used here
      first_error = 1;
      std::cout << "Test 'core_get_cancel_lock()' ... " << std::flush;
#if CFG_USE_TLS
      rv = test_cancellock();
      CHECK_RV(rv);
#else  // CFG_USE_TLS
      // Cryptographic functions are shared with TLS module
      SKIP_TEST("no TLS support");
#endif  // CFG_USE_TLS

      // Test compression
      first_error = 1;
      std::cout << "Test 'cmpr_send()' and 'cmpr_recv()' ... " << std::flush;
#if !CFG_CMPR_DISABLE
#  if CFG_USE_ZLIB && (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
      rv = test_compression();
      CHECK_RV(rv);
#  else  // CFG_USE_ZLIB && (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
#     if ! (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
      SKIP_TEST("no POSIX.1-2001 or X/Open XSI");
#     else  // ! (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
      SKIP_TEST("no ZLIB support");
#     endif  // ! (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
#  endif  // CFG_USE_ZLIB && (CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI)
#else  // ! CFG_CMPR_DISABLE
      SKIP_TEST("disabled by configuration");
#endif  // ! CFG_CMPR_DISABLE

      // Test timestamp decode
      first_error = 1;
      std::cout << "Test 'enc_timestamp_decode()' ... " << std::flush;
      rv = test_timestamp();
      CHECK_RV(rv);

      // Test base64 encoder
      first_error = 1;
      std::cout << "Test 'enc_mime_encode_base64()' ... " << std::flush;
      rv = test_base64();
      CHECK_RV(rv);

      // Test Unicode normalization (this is slow => execute at the end)
      first_error = 1;
      std::cout << "Test 'enc_convert_to_utf8_nfc()' ... " << std::flush;
      rv = test_unicode();
      CHECK_RV(rv);

      std::cout << "========================================"
                   "========================================" << std::endl;
   }

   // Shutdown modules
   hmac_exit();
   digest_exit();
#if CFG_USE_TLS
   tls_exit();
#endif  // CFG_USE_TLS
#if CFG_USE_ZLIB
   cmpr_exit();
#endif  // CFG_USE_ZLIB

   // Destroy configuration data
   conf_delete(config);

   // Exit
   if(res)
   {
      std::clog << "Error: One or more tests have failed!" << std::endl;
   }
exit:
   ts_environ_exit();
   exit(res);
}


//! @}


// =============================================================================
// Lock UI thread stub
//
// Exported as C style function
//
// The UI is not used for testing.
//
// \return
// - 0 (NOP and success)

int  ts_lock_ui(void)
{
   return(0);
}


// =============================================================================
// Unlock UI thread stub
//
// Exported as C style function
//
// The UI is not used for testing.
//
// \return
// - 0 (NOP and success)

int  ts_unlock_ui(void)
{
   return(0);
}


// =============================================================================
// Wakeup UI thread stub
//
// Exported as C style function
//
// The UI is not used for testing.

void  ui_wakeup(unsigned int)
{
   return;
}


// EOF
