/*
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#ifndef SVN_TEST_H
#define SVN_TEST_H

#ifndef SVN_ENABLE_DEPRECATION_WARNINGS_IN_TESTS
#undef SVN_DEPRECATED
#define SVN_DEPRECATED
#endif /* ! SVN_ENABLE_DEPRECATION_WARNINGS_IN_TESTS */

#include <stdio.h>

#include <apr_pools.h>

#include "svn_delta.h"
#include "svn_path.h"
#include "svn_types.h"
#include "svn_error.h"
#include "svn_string.h"
#include "svn_auth.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */


/** Handy macro to test a condition, returning SVN_ERR_TEST_FAILED if FALSE
 *
 * This macro should be used in place of SVN_ERR_ASSERT() since we don't
 * want to core-dump the test.
 */
#define SVN_TEST_ASSERT(expr)                                     \
  do {                                                            \
    if (!(expr))                                                  \
      return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,         \
                               "assertion '%s' failed at %s:%d",  \
                               #expr, __FILE__, __LINE__);        \
  } while (0)

/**
 * Macro for testing assumptions when the context does not allow
 * returning an svn_error_t*.
 *
 * Will write to stderr and cause a segfault if EXPR is false.
 */
#define SVN_TEST_ASSERT_NO_RETURN(expr)                           \
  do {                                                            \
    if (!(expr))                                                  \
      {                                                           \
        unsigned int z_e_r_o_p_a_g_e__;                           \
        fprintf(stderr, "TEST ASSERTION FAILED: %s\n", #expr);    \
        z_e_r_o_p_a_g_e__ = *(volatile unsigned int*)0;           \
        *(volatile unsigned int*)0 = z_e_r_o_p_a_g_e__;           \
      }                                                           \
  } while (0)

/** Handy macro for testing an expected svn_error_t return value.
 * EXPECTED must be a real error (neither SVN_NO_ERROR nor APR_SUCCESS).
 * The error returned by EXPR will be cleared.
 */
#define SVN_TEST_ASSERT_ERROR(expr, expected)                             \
  do {                                                                    \
    svn_error_t *err__ = (expr);                                          \
    SVN_ERR_ASSERT((expected));                                           \
    if (err__ == SVN_NO_ERROR || err__->apr_err != (expected))            \
      return err__ ? svn_error_createf(SVN_ERR_TEST_FAILED, err__,        \
                                       "Expected error %s but got %s",    \
                                       svn_error_symbolic_name(expected), \
                                       svn_error_symbolic_name(           \
                                         err__->apr_err))                 \
                   : svn_error_createf(SVN_ERR_TEST_FAILED, err__,        \
                                       "Expected error %s but got %s",    \
                                       svn_error_symbolic_name(expected), \
                                        "SVN_NO_ERROR");                  \
    svn_error_clear(err__);                                               \
  } while (0)

/** Handy macro for testing that an svn_error_t is returned.
 * The result must be neither SVN_NO_ERROR nor SVN_ERR_ASSERTION_FAIL.
 * The error returned by EXPR will be cleared.
 */
#define SVN_TEST_ASSERT_ANY_ERROR(expr)                                   \
  do {                                                                    \
    svn_error_t *err__ = (expr);                                          \
    if (err__ == SVN_NO_ERROR || err__->apr_err == SVN_ERR_ASSERTION_FAIL)\
      return err__ ? svn_error_createf(SVN_ERR_TEST_FAILED, err__,        \
                                       "Expected error but got %s",       \
                                       "SVN_ERR_ASSERTION_FAIL")          \
                   : svn_error_createf(SVN_ERR_TEST_FAILED, err__,        \
                                       "Expected error but got %s",       \
                                       "SVN_NO_ERROR");                   \
    svn_error_clear(err__);                                               \
  } while (0)

/** Handy macro for testing string equality.
 *
 * EXPR and/or EXPECTED_EXPR may be NULL which compares equal to NULL and
 * not equal to any non-NULL string.
 */
#define SVN_TEST_STRING_ASSERT(expr, expected_expr)                 \
  do {                                                              \
    const char *tst_str1 = (expr);                                  \
    const char *tst_str2 = (expected_expr);                         \
                                                                    \
    if (tst_str2 == NULL && tst_str1 == NULL)                       \
      break;                                                        \
    if ((tst_str1 == NULL) || (tst_str2 == NULL)                    \
        || (strcmp(tst_str2, tst_str1) != 0))                       \
      return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,           \
          "Strings not equal\n  Expected: '%s'\n  Found:    '%s'"   \
          "\n  at %s:%d",                                           \
          tst_str2, tst_str1, __FILE__, __LINE__);                  \
  } while(0)

 /** Handy macro for testing integer equality.
  */
#define SVN_TEST_INT_ASSERT(expr, expected_expr)                  \
  do {                                                            \
    apr_int64_t tst_int1 = (expr);                                \
    apr_int64_t tst_int2 = (expected_expr);                       \
                                                                  \
    if (tst_int1 != tst_int2)                                     \
      return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,         \
          "Integers not equal\n"                                  \
          "  Expected: %" APR_INT64_T_FMT "\n"                    \
          "     Found: %" APR_INT64_T_FMT "\n"                    \
          "  at %s:%d",                                           \
          tst_int2, tst_int1, __FILE__, __LINE__);                \
  } while(0)


/* Baton for any arguments that need to be passed from main() to svn
 * test functions.
 */
typedef struct svn_test_opts_t
{
  /* The name of the application (to generate unique names) */
  const char *prog_name;
  /* Description of the fs backend that should be used for testing. */
  const char *fs_type;
  /* Config file. */
  const char *config_file;
  /* Source dir. */
  const char *srcdir;
  /* Repository dir: temporary directory to create repositories in as subdir */
  const char *repos_dir;
  /* Repository url: The url to access REPOS_DIR as */
  const char *repos_url;
  /* Memcached server. */
  const char *memcached_server;
  /* Repository template: pre-created repository to copy for tests */
  const char *repos_template;
  /* Minor version to use for servers and FS backends, or zero to use
     the current latest version. */
  int server_minor_version;
  svn_boolean_t verbose;
  /* Add future "arguments" here. */
} svn_test_opts_t;

/* Prototype for test driver functions. */
typedef svn_error_t* (*svn_test_driver2_t)(apr_pool_t *pool);

/* Prototype for test driver functions which need options. */
typedef svn_error_t* (*svn_test_driver_opts_t)(const svn_test_opts_t *opts,
                                               apr_pool_t *pool);

/* Prototype for test predicate functions. */
typedef svn_boolean_t (*svn_test_predicate_func_t)(const svn_test_opts_t *opts,
                                                   const char *predicate_value,
                                                   apr_pool_t *pool);

/* Test modes. */
enum svn_test_mode_t
  {
    svn_test_pass,
    svn_test_xfail,
    svn_test_skip,
    svn_test_all
  };

/* Structure for runtime test predicates. */
struct svn_test_predicate_t
{
  /* The predicate function. */
  svn_test_predicate_func_t func;

  /* The value that the predicate function tests. */
  const char *value;

  /* The test mode that's used if the predicate matches. */
  enum svn_test_mode_t alternate_mode;

  /* Description for the test log */
  const char *description;
};


/* Each test gets a test descriptor, holding the function and other
 * associated data.
 */
struct svn_test_descriptor_t
{
  /* Is the test marked XFAIL? */
  enum svn_test_mode_t mode;

  /* A pointer to the test driver function. */
  svn_test_driver2_t func2;

  /* A pointer to the test driver function. */
  svn_test_driver_opts_t func_opts;

  /* A descriptive message for this test. */
  const char *msg;

  /* An optional description of a work-in-progress test. */
  const char *wip;

  /* An optional runtiume predicate. */
  struct svn_test_predicate_t predicate;
};

/* All Subversion test programs include an array of svn_test_descriptor_t's
 * (all of our sub-tests) that begins and ends with a SVN_TEST_NULL entry.
 * This descriptor must be passed to the svn_test_main function.
 *
 * MAX_THREADS is the number of concurrent tests to run.  Set to 1 if
 * all tests must be executed serially.  Numbers less than 1 mean
 * "unbounded".
 */
int svn_test_main(int argc, const char *argv[], int max_threads,
                  struct svn_test_descriptor_t *test_funcs);

/* Boilerplate for the main function for each test program. */
#define SVN_TEST_MAIN                                 \
  int main(int argc, const char *argv[])              \
    {                                                 \
      return svn_test_main(argc, argv,                \
                           max_threads, test_funcs);  \
    }

/* A null initializer for the test descriptor. */
#define SVN_TEST_NULL  {0}

/* Initializer for PASS tests */
#define SVN_TEST_PASS2(func, msg)  {svn_test_pass, func, NULL, msg}

/* Initializer for XFAIL tests */
#define SVN_TEST_XFAIL2(func, msg) {svn_test_xfail, func, NULL, msg}

/* Initializer for conditional XFAIL tests */
#define SVN_TEST_XFAIL_COND2(func, p, msg) \
  {(p) ? svn_test_xfail : svn_test_pass, func, NULL, msg}

/* Initializer for SKIP tests */
#define SVN_TEST_SKIP2(func, p, msg) \
  {(p) ? svn_test_skip : svn_test_pass, func, NULL, msg}

/* Similar macros, but for tests needing options.  */
#define SVN_TEST_OPTS_PASS(func, msg)  {svn_test_pass, NULL, func, msg}
#define SVN_TEST_OPTS_XFAIL(func, msg) {svn_test_xfail, NULL, func, msg}
#define SVN_TEST_OPTS_XFAIL_COND(func, p, msg) \
  {(p) ? svn_test_xfail : svn_test_pass, NULL, func, msg}
#define SVN_TEST_OPTS_XFAIL_OTOH(func, msg, predicate) \
  {svn_test_xfail, NULL, func, msg, NULL, predicate}
#define SVN_TEST_OPTS_SKIP(func, p, msg) \
  {(p) ? svn_test_skip : svn_test_pass, NULL, func, msg}

/* Initializer for XFAIL tests for works-in-progress. */
#define SVN_TEST_WIMP(func, msg, wip) \
  {svn_test_xfail, func, NULL, msg, wip}
#define SVN_TEST_WIMP_COND(func, p, msg, wip) \
  {(p) ? svn_test_xfail : svn_test_pass, func, NULL, msg, wip}
#define SVN_TEST_OPTS_WIMP(func, msg, wip) \
  {svn_test_xfail, NULL, func, msg, wip}
#define SVN_TEST_OPTS_WIMP_COND(func, p, msg, wip) \
  {(p) ? svn_test_xfail : svn_test_pass, NULL, func, msg, wip}


/* Return a pseudo-random number based on SEED, and modify SEED.
 *
 * This is a "good" pseudo-random number generator, intended to replace
 * all those "bad" rand() implementations out there.
 */
apr_uint32_t svn_test_rand(apr_uint32_t *seed);


/* Add PATH to the test cleanup list.  */
void svn_test_add_dir_cleanup(const char *path);


/* A simple representation for a tree node. */
typedef struct svn_test__tree_entry_t
{
  const char *path;     /* relpath of this node */
  const char *contents; /* text contents, or NULL for a directory */
}
svn_test__tree_entry_t;

/* Wrapper for an array of svn_test__tree_entry_t's. */
typedef struct svn_test__tree_t
{
  svn_test__tree_entry_t *entries;
  int num_entries;
}
svn_test__tree_t;


/* The standard Greek tree, terminated by a node with path=NULL. */
extern const svn_test__tree_entry_t svn_test__greek_tree_nodes[21];


/* Returns a path to BASENAME within the transient data area for the
   current test. */
const char *
svn_test_data_path(const char* basename, apr_pool_t *result_pool);


/* Some tests require the --srcdir option and should use this function
 * to get it. If not provided, print a warning and attempt to run the
 * tests under the assumption that --srcdir is the current directory. */
svn_error_t *
svn_test_get_srcdir(const char **srcdir,
                    const svn_test_opts_t *opts,
                    apr_pool_t *pool);

/* Initializes a standard auth baton for accessing the repositories */
svn_error_t *
svn_test__init_auth_baton(svn_auth_baton_t **baton,
                          apr_pool_t *result_pool);

/* Create a temp folder for test & schedule it for automatic cleanup.
 * Uses POOL for all allocations. */
svn_error_t *
svn_test_make_sandbox_dir(const char **sb_dir_p,
                          const char *sb_name,
                          apr_pool_t *pool);

/*
 * Test predicates
 */

#define SVN_TEST_PASS_IF_FS_TYPE_IS(fs_type) \
  { svn_test__fs_type_is, fs_type, svn_test_pass, \
    "PASS if fs-type = " fs_type }

#define SVN_TEST_PASS_IF_FS_TYPE_IS_NOT(fs_type) \
  { svn_test__fs_type_not, fs_type, svn_test_pass, \
    "PASS if fs-type != " fs_type }

/* Return TRUE if the fs-type in OPTS matches PREDICATE_VALUE. */
svn_boolean_t
svn_test__fs_type_is(const svn_test_opts_t *opts,
                     const char *predicate_value,
                     apr_pool_t *pool);


/* Return TRUE if the fs-type in OPTS does not matches PREDICATE_VALUE. */
svn_boolean_t
svn_test__fs_type_not(const svn_test_opts_t *opts,
                      const char *predicate_value,
                      apr_pool_t *pool);


#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* SVN_TEST_H */
