// vi:set ft=cpp: -*- Mode: C++ -*-
/* SPDX-License-Identifier: GPL-2.0-only or License-Ref-kk-custom */
/*
 * Copyright (C) 2020 Kernkonzept GmbH.
 * Author(s): Manuel Thieme <manuel.thieme@kernkonzept.com>
 */

/**
 * \file
 * Directives for skipping tests because of given run tags.
 */
#pragma once

#include <string>
#include <vector>

#include <l4/atkins/tap/cmdline>
#include <l4/atkins/l4_assert>


/**
 * Check whether this test run is called with a certain run tag.
 *
 * \param tag  Name of the run tag to check for.
 *
 * \retval true  Tag is set.
 * \retval false  Tag is not set.
 */
static bool run_tag_set(std::string const &tag)
{
    std::vector<std::string> const run_tags =
      Atkins::Cmdline::cmdline()->run_tags();
    return std::find(run_tags.cbegin(),
                     run_tags.cend(),
                     tag) != run_tags.cend();
}

/**
 * Check whether an array contains a given value.
 *
 * \param array  Array to find value in.
 * \param value  Value to find in array.
 *
 * \retval true  `array` contains `value`.
 * \retval false  `array` does not contain `value`.
 */
template <typename T, unsigned N>
static bool contains(T const (&array)[N], T const &value)
{
  for (unsigned i = 0; i < N; ++i)
    if (array[i] == value)
      return true;

  return false;
}

/**
 * Directive to skip a test when a certain tag is **not** explicitly given or
 * set to 1.
 * This is the single test equivalent to `TAG`.
 * Can only be used in test bodies.
 *
 * `TAG` has to be a string literal.
 */
#define TAG_PLAIN(TAG) \
  do { \
    if (!(run_tag_set(TAG "=1") || run_tag_set(TAG))) \
      SKIP("run-tag " TAG " not explicitly set to 1"); \
  } while (0)

/**
 * Directive to skip a test when a certain run tag is explicitly given or set
 * to 1.
 * This is the single test equivalent to `!TAG`.
 * Can only be used in test bodies.
 *
 * `TAG` has to be a string literal.
 */
#define TAG_NOT(TAG) \
  do { \
    if (run_tag_set(TAG "=1") || run_tag_set(TAG)) \
      SKIP("run-tag " TAG " existent but not explicitly set to 0"); \
  } while (0)

/**
 * Directive to skip a test when a certain run_tag is explicitly set to 0.
 * This is the single test equivalent to `+TAG`.
 * Can only be used in test bodies.
 *
 * `TAG` has to be a string literal.
 */
#define TAG_PLUS(TAG) \
  do { \
    if (run_tag_set(TAG "=0")) \
      SKIP("run-tag " TAG " explicitly set to 0"); \
  } while (0)

/**
 * Directive to skip a test when a certain run_tag is **not** explicitly set to
 * 0.
 * This is the single test equivalent to `-TAG`.
 * Can only be used in test bodies.
 *
 * `TAG` has to be a string literal.
 */
#define TAG_MINUS(TAG) \
  do { \
    if (!run_tag_set(TAG "=0")) \
      SKIP("run-tag " TAG " not explicitly set to 0"); \
  } while (0)


/**
 * `TAG_PLAIN` for value-parameterized tests.
 * Can only be used in test bodies.
 *
 * `TAG` is only required on tests whose parameters are in `PARAMS`.
 * `TAG` has to be a string literal.
 * `PARAMS` has to be a const array.
 *
 * Example:
 * ```
 * std::tuple<std::string> const params[] = {{"foo"}, {"bar"}};
 * TAG_PLAIN_FOR("impl-def", params);
 * ```
 */
#define TAG_PLAIN_FOR(TAG, PARAMS) \
  do { \
    if (contains(PARAMS, GetParam())) \
      TAG_PLAIN(TAG); \
  } while (0)

/**
 * `TAG_NOT` for value-parameterized tests.
 * Can only be used in test bodies.
 *
 * `TAG` is only prohibited on tests whose parameters are in `PARAMS`.
 * `TAG` has to be a string literal.
 * `PARAMS` has to be a const array.
 *
 * Example:
 * ```
 * std::tuple<std::string> const params[] = {{"foo"}, {"bar"}};
 * TAG_NOT_FOR("impl-def", params);
 * ```
 */
#define TAG_NOT_FOR(TAG, PARAMS) \
  do { \
    if (contains(PARAMS, GetParam())) \
      TAG_NOT(TAG); \
  } while (0)

/**
 * `TAG_PLUS` for value-parameterized tests.
 * Can only be used in test bodies.
 *
 * `TAG` is only required on tests whose parameters are in `PARAMS`.
 * `TAG` has to be a string literal.
 * `PARAMS` has to be a const array.
 *
 * Example:
 * ```
 * std::tuple<std::string> const params[] = {{"foo"}, {"bar"}};
 * TAG_PLUS_FOR("impl-def", params);
 * ```
 */
#define TAG_PLUS_FOR(TAG, PARAMS) \
  do { \
    if (contains(PARAMS, GetParam())) \
      TAG_PLUS(TAG); \
  } while (0)

/**
 * `TAG_MINUS` for value-parameterized tests.
 * Can only be used in test bodies.
 *
 * `TAG` is only required on tests whose parameters are in `PARAMS`.
 * `TAG` has to be a string literal.
 * `PARAMS` has to be a const array.
 *
 * Example:
 * ```
 * std::tuple<std::string> const params[] = {{"foo"}, {"bar"}};
 * TAG_MINUS_FOR("impl-def", params);
 * ```
 */
#define TAG_MINUS_FOR(TAG, PARAMS) \
  do { \
    if (contains(PARAMS, GetParam())) \
      TAG_MINUS(TAG); \
  } while (0)


/**
 * Shortcut for `TAG_PLUS("impl-def");`
 * Can only be used in test bodies.
 */
#define TEST_CAT_IMPL_DEF() \
  TAG_PLUS("impl-def")

/**
 * Shortcut for `TAG_PLUS_FOR("impl-def", PARAMS);`
 * Can only be used in test bodies.
 *
 * `PARAMS` has to be a const array.
 */
#define TEST_CAT_IMPL_DEF_FOR(PARAMS) \
  TAG_PLUS_FOR("impl-def", PARAMS)
