/* Self tests for array_view for GDB, the GNU debugger.

   Copyright (C) 2017-2024 Free Software Foundation, Inc.

   This file is part of GDB.

   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 "defs.h"
#include "gdbsupport/selftest.h"
#include "gdbsupport/array-view.h"
#include <array>
#include <vector>

namespace selftests {
namespace array_view_tests {

/* Triviality checks.  */
#define CHECK_TRAIT(TRAIT)			\
  static_assert (std::TRAIT<gdb::array_view<gdb_byte>>::value, "")

#if HAVE_IS_TRIVIALLY_COPYABLE

CHECK_TRAIT (is_trivially_copyable);
CHECK_TRAIT (is_trivially_move_assignable);
CHECK_TRAIT (is_trivially_move_constructible);
CHECK_TRAIT (is_trivially_destructible);

#endif

#undef CHECK_TRAIT

/* Wrapper around std::is_convertible to make the code using it a bit
   shorter.  (With C++14 we'd use a variable template instead.)  */

template<typename From, typename To>
static constexpr bool
is_convertible ()
{
  return std::is_convertible<From, To>::value;
}

/* Check for implicit conversion to immutable and mutable views.  */

static constexpr bool
check_convertible ()
{
  using T = gdb_byte;
  using gdb::array_view;

  return (true
	  /* immutable array_view */
	  &&  is_convertible<const T (&) [1],	array_view<const T>> ()
	  &&  is_convertible<T (&) [1], 	array_view<const T>> ()
	  &&  is_convertible<const T, 		array_view<const T>> ()
	  &&  is_convertible<T, 		array_view<const T>> ()

	  /* mutable array_view */
	  &&  is_convertible<T (&) [1], 	array_view<T>> ()
	  && !is_convertible<const T (&) [1],	array_view<T>> ()
	  &&  is_convertible<T, 		array_view<T>> ()
	  && !is_convertible<const T,		array_view<T>> ()

	  /* While float is implicitly convertible to gdb_byte, we
	     don't want implicit float->array_view<gdb_byte>
	     conversion.  */
	  && !is_convertible<float, 		array_view<const T>> ()
	  && !is_convertible<float, 		array_view<T>> ());
}

static_assert (check_convertible (), "");

namespace no_slicing
{
struct A { int i; };
struct B : A { int j; };
struct C : A { int l; };

/* Check that there's no array->view conversion for arrays of derived types or
   subclasses.  */
static constexpr bool
check ()
{
  using gdb::array_view;

  return (true

	  /* array->view  */

	  &&  is_convertible <A (&)[1], array_view<A>> ()
	  && !is_convertible <B (&)[1], array_view<A>> ()
	  && !is_convertible <C (&)[1], array_view<A>> ()

	  && !is_convertible <A (&)[1], array_view<B>> ()
	  &&  is_convertible <B (&)[1], array_view<B>> ()
	  && !is_convertible <C (&)[1], array_view<B>> ()

	  /* elem->view  */

	  &&  is_convertible <A, array_view<A>> ()
	  && !is_convertible <B, array_view<A>> ()
	  && !is_convertible <C, array_view<A>> ()

	  && !is_convertible <A, array_view<B>> ()
	  &&  is_convertible <B, array_view<B>> ()
	  && !is_convertible <C, array_view<B>> ());
}

/* Check that there's no container->view conversion for containers of derived
   types or subclasses.  */

template<template<typename ...> class Container>
static constexpr bool
check_ctor_from_container ()
{
  using gdb::array_view;

  return (    is_convertible <Container<A>, array_view<A>> ()
	  && !is_convertible <Container<B>, array_view<A>> ()
	  && !is_convertible <Container<C>, array_view<A>> ()

	  && !is_convertible <Container<A>, array_view<B>> ()
	  &&  is_convertible <Container<B>, array_view<B>> ()
	  && !is_convertible <Container<C>, array_view<B>> ());
}

} /* namespace no_slicing */

/* std::array with only one template argument, so we can pass it to
   check_ctor_from_container.  */
template<typename T> using StdArray1 = std::array<T, 1>;

static_assert (no_slicing::check (), "");
static_assert (no_slicing::check_ctor_from_container<std::vector> (), "");
static_assert (no_slicing::check_ctor_from_container<StdArray1> (), "");
static_assert (no_slicing::check_ctor_from_container<gdb::array_view> (), "");

/* Check that array_view implicitly converts from std::vector.  */

static constexpr bool
check_convertible_from_std_vector ()
{
  using gdb::array_view;
  using T = gdb_byte;

  /* Note there's no such thing as std::vector<const T>.  */

  return (true
	  &&  is_convertible <std::vector<T>, array_view<T>> ()
	  &&  is_convertible <std::vector<T>, array_view<const T>> ());
}

static_assert (check_convertible_from_std_vector (), "");

/* Check that array_view implicitly converts from std::array.  */

static constexpr bool
check_convertible_from_std_array ()
{
  using gdb::array_view;
  using T = gdb_byte;

  /* Note: a non-const T view can't refer to a const T array.  */

  return (true
	  &&  is_convertible <std::array<T, 1>,		array_view<T>> ()
	  &&  is_convertible <std::array<T, 1>,		array_view<const T>> ()
	  && !is_convertible <std::array<const T, 1>,	array_view<T>> ()
	  &&  is_convertible <std::array<const T, 1>,	array_view<const T>> ());
}

static_assert (check_convertible_from_std_array (), "");

/* Check that VIEW views C (a container like std::vector/std::array)
   correctly.  */

template<typename View, typename Container>
static bool
check_container_view (const View &view, const Container &c)
{
  if (view.empty ())
    return false;
  if (view.size () != c.size ())
    return false;
  if (view.data () != c.data ())
    return false;
  for (size_t i = 0; i < c.size (); i++)
    {
      if (&view[i] != &c[i])
	return false;
      if (view[i] != c[i])
	return false;
    }
  return true;
}

/* Check that VIEW views E (an object of the type of a view element)
   correctly.  */

template<typename View, typename Elem>
static bool
check_elem_view (const View &view, const Elem &e)
{
  if (view.empty ())
    return false;
  if (view.size () != 1)
    return false;
  if (view.data () != &e)
    return false;
  if (&view[0] != &e)
    return false;
  if (view[0] != e)
    return false;
  return true;
}

/* Check for operator[].  The first overload is taken iff
   'view<T>()[0] = T()' is a valid expression.  */

template<typename View,
	 typename = decltype (std::declval<View> ()[0]
			      = std::declval<typename View::value_type> ())>
static bool
check_op_subscript (const View &view)
{
  return true;
}

/* This overload is taken iff 'view<T>()[0] = T()' is not a valid
   expression.  */

static bool
check_op_subscript (...)
{
  return false;
}

/* Check construction with pointer + size.  This is a template in
   order to test both gdb_byte and const gdb_byte.  */

template<typename T>
static void
check_ptr_size_ctor ()
{
  T data[] = {0x11, 0x22, 0x33, 0x44};

  gdb::array_view<T> view (data + 1, 2);

  SELF_CHECK (!view.empty ());
  SELF_CHECK (view.size () == 2);
  SELF_CHECK (view.data () == &data[1]);
  SELF_CHECK (view[0] == data[1]);
  SELF_CHECK (view[1] == data[2]);

  gdb::array_view<const T> cview (data + 1, 2);
  SELF_CHECK (!cview.empty ());
  SELF_CHECK (cview.size () == 2);
  SELF_CHECK (cview.data () == &data[1]);
  SELF_CHECK (cview[0] == data[1]);
  SELF_CHECK (cview[1] == data[2]);
}

/* Asserts std::is_constructible.  */

template<typename T, typename... Args>
static constexpr bool
require_not_constructible ()
{
  static_assert (!std::is_constructible<T, Args...>::value, "");

  /* constexpr functions can't return void in C++11 (N3444).  */
  return true;
};

/* Check the array_view<T>(PTR, SIZE) ctor, when T is a pointer.  */

static void
check_ptr_size_ctor2 ()
{
  struct A {};
  A an_a;

  A *array[] = { &an_a };
  const A * const carray[] = { &an_a };

  gdb::array_view<A *> v1 = {array, ARRAY_SIZE (array)};
  gdb::array_view<A *> v2 = {array, (char) ARRAY_SIZE (array)};
  gdb::array_view<A * const> v3 = {array, ARRAY_SIZE (array)};
  gdb::array_view<const A * const> cv1 = {carray, ARRAY_SIZE (carray)};

  require_not_constructible<gdb::array_view<A *>, decltype (carray), size_t> ();

  SELF_CHECK (v1[0] == array[0]);
  SELF_CHECK (v2[0] == array[0]);
  SELF_CHECK (v3[0] == array[0]);

  SELF_CHECK (!v1.empty ());
  SELF_CHECK (v1.size () == 1);
  SELF_CHECK (v1.data () == &array[0]);

  SELF_CHECK (cv1[0] == carray[0]);

  SELF_CHECK (!cv1.empty ());
  SELF_CHECK (cv1.size () == 1);
  SELF_CHECK (cv1.data () == &carray[0]);
}

/* Check construction with a pair of pointers.  This is a template in
   order to test both gdb_byte and const gdb_byte.  */

template<typename T>
static void
check_ptr_ptr_ctor ()
{
  T data[] = {0x11, 0x22, 0x33, 0x44};

  gdb::array_view<T> view (data + 1, data + 3);

  SELF_CHECK (!view.empty ());
  SELF_CHECK (view.size () == 2);
  SELF_CHECK (view.data () == &data[1]);
  SELF_CHECK (view[0] == data[1]);
  SELF_CHECK (view[1] == data[2]);

  gdb_byte array[] = {0x11, 0x22, 0x33, 0x44};
  const gdb_byte *p1 = array;
  gdb_byte *p2 = array + ARRAY_SIZE (array);
  gdb::array_view<const gdb_byte> view2 (p1, p2);
}

/* Check construction with a pair of pointers of mixed constness.  */

static void
check_ptr_ptr_mixed_cv ()
{
  gdb_byte array[] = {0x11, 0x22, 0x33, 0x44};
  const gdb_byte *cp = array;
  gdb_byte *p = array;
  gdb::array_view<const gdb_byte> view1 (cp, p);
  gdb::array_view<const gdb_byte> view2 (p, cp);
  SELF_CHECK (view1.empty ());
  SELF_CHECK (view2.empty ());
}

/* Check range-for support (i.e., begin()/end()).  This is a template
   in order to test both gdb_byte and const gdb_byte.  */

template<typename T>
static void
check_range_for ()
{
  T data[] = {1, 2, 3, 4};
  gdb::array_view<T> view (data);

  typename std::decay<T>::type sum = 0;
  for (auto &elem : view)
    sum += elem;
  SELF_CHECK (sum == 1 + 2 + 3 + 4);
}

/* Entry point.  */

static void
run_tests ()
{
  /* Empty views.  */
  {
    constexpr gdb::array_view<gdb_byte> view1;
    constexpr gdb::array_view<const gdb_byte> view2;

    static_assert (view1.empty (), "");
    static_assert (view1.data () == nullptr, "");
    static_assert (view1.size () == 0, "");
    static_assert (view2.empty (), "");
    static_assert (view2.size () == 0, "");
    static_assert (view2.data () == nullptr, "");
  }

  std::vector<gdb_byte> vec = {0x11, 0x22, 0x33, 0x44 };
  std::array<gdb_byte, 4> array = {{0x11, 0x22, 0x33, 0x44}};

  /* Various tests of views over std::vector.  */
  {
    gdb::array_view<gdb_byte> view = vec;
    SELF_CHECK (check_container_view (view, vec));
    gdb::array_view<const gdb_byte> cview = vec;
    SELF_CHECK (check_container_view (cview, vec));
  }

  /* Likewise, over std::array.  */
  {
    gdb::array_view<gdb_byte> view = array;
    SELF_CHECK (check_container_view (view, array));
    gdb::array_view<gdb_byte> cview = array;
    SELF_CHECK (check_container_view (cview, array));
  }

  /* op=(std::vector/std::array/elem) */
  {
    gdb::array_view<gdb_byte> view;

    view = vec;
    SELF_CHECK (check_container_view (view, vec));
    view = std::move (vec);
    SELF_CHECK (check_container_view (view, vec));

    view = array;
    SELF_CHECK (check_container_view (view, array));
    view = std::move (array);
    SELF_CHECK (check_container_view (view, array));

    gdb_byte elem = 0;
    view = elem;
    SELF_CHECK (check_elem_view (view, elem));
    view = std::move (elem);
    SELF_CHECK (check_elem_view (view, elem));
  }

  /* Test copy/move ctor and mutable->immutable conversion.  */
  {
    gdb_byte data[] = {0x11, 0x22, 0x33, 0x44};
    gdb::array_view<gdb_byte> view1 = data;
    gdb::array_view<gdb_byte> view2 = view1;
    gdb::array_view<gdb_byte> view3 = std::move (view1);
    gdb::array_view<const gdb_byte> cview1 = data;
    gdb::array_view<const gdb_byte> cview2 = cview1;
    gdb::array_view<const gdb_byte> cview3 = std::move (cview1);
    SELF_CHECK (view1[0] == data[0]);
    SELF_CHECK (view2[0] == data[0]);
    SELF_CHECK (view3[0] == data[0]);
    SELF_CHECK (cview1[0] == data[0]);
    SELF_CHECK (cview2[0] == data[0]);
    SELF_CHECK (cview3[0] == data[0]);
  }

  /* Same, but op=(view).  */
  {
    gdb_byte data[] = {0x55, 0x66, 0x77, 0x88};
    gdb::array_view<gdb_byte> view1;
    gdb::array_view<gdb_byte> view2;
    gdb::array_view<gdb_byte> view3;
    gdb::array_view<const gdb_byte> cview1;
    gdb::array_view<const gdb_byte> cview2;
    gdb::array_view<const gdb_byte> cview3;

    view1 = data;
    view2 = view1;
    view3 = std::move (view1);
    cview1 = data;
    cview2 = cview1;
    cview3 = std::move (cview1);
    SELF_CHECK (view1[0] == data[0]);
    SELF_CHECK (view2[0] == data[0]);
    SELF_CHECK (view3[0] == data[0]);
    SELF_CHECK (cview1[0] == data[0]);
    SELF_CHECK (cview2[0] == data[0]);
    SELF_CHECK (cview3[0] == data[0]);
  }

  /* op[] */
  {
    std::vector<gdb_byte> vec2 = {0x11, 0x22};
    gdb::array_view<gdb_byte> view = vec2;
    gdb::array_view<const gdb_byte> cview = vec2;

    /* Check that op[] on a non-const view of non-const T returns a
       mutable reference.  */
    view[0] = 0x33;
    SELF_CHECK (vec2[0] == 0x33);

    /* OTOH, check that assigning through op[] on a view of const T
       wouldn't compile.  */
    SELF_CHECK (!check_op_subscript (cview));
    /* For completeness.  */
    SELF_CHECK (check_op_subscript (view));
  }

  check_ptr_size_ctor<const gdb_byte> ();
  check_ptr_size_ctor<gdb_byte> ();
  check_ptr_size_ctor2 ();
  check_ptr_ptr_ctor<const gdb_byte> ();
  check_ptr_ptr_ctor<gdb_byte> ();
  check_ptr_ptr_mixed_cv ();

  check_range_for<gdb_byte> ();
  check_range_for<const gdb_byte> ();

  /* Check that the right ctor overloads are taken when the element is
     a container.  */
  {
    using Vec = std::vector<gdb_byte>;
    Vec vecs[3];

    gdb::array_view<Vec> view_array = vecs;
    SELF_CHECK (view_array.size () == 3);

    Vec elem;
    gdb::array_view<Vec> view_elem = elem;
    SELF_CHECK (view_elem.size () == 1);
  }

  /* gdb::make_array_view, int length.  */
  {
    gdb_byte data[] = {0x55, 0x66, 0x77, 0x88};
    int len = sizeof (data) / sizeof (data[0]);
    auto view = gdb::make_array_view (data, len);

    SELF_CHECK (view.data () == data);
    SELF_CHECK (view.size () == len);

    for (size_t i = 0; i < len; i++)
      SELF_CHECK (view[i] == data[i]);
  }

  /* Test slicing.  */
  {
    gdb_byte data[] = {0x55, 0x66, 0x77, 0x88, 0x99};
    gdb::array_view<gdb_byte> view = data;

    {
      auto slc = view.slice (1, 3);
      SELF_CHECK (slc.data () == data + 1);
      SELF_CHECK (slc.size () == 3);
      SELF_CHECK (slc[0] == data[1]);
      SELF_CHECK (slc[0] == view[1]);
    }

    {
      auto slc = view.slice (2);
      SELF_CHECK (slc.data () == data + 2);
      SELF_CHECK (slc.size () == 3);
      SELF_CHECK (slc[0] == view[2]);
      SELF_CHECK (slc[0] == data[2]);
    }
  }
}

template <typename T>
void
run_copy_test ()
{
  /* Test non-overlapping copy.  */
  {
    const std::vector<T> src_v = {1, 2, 3, 4};
    std::vector<T> dest_v (4, -1);

    SELF_CHECK (dest_v != src_v);
    copy (gdb::array_view<const T> (src_v), gdb::array_view<T> (dest_v));
    SELF_CHECK (dest_v == src_v);
  }

  /* Test overlapping copy, where the source is before the destination.  */
  {
    std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8};
    gdb::array_view<T> v = vec;

    copy (v.slice (1, 4),
	  v.slice (2, 4));

    std::vector<T> expected = {1, 2, 2, 3, 4, 5, 7, 8};
    SELF_CHECK (vec == expected);
  }

  /* Test overlapping copy, where the source is after the destination.  */
  {
    std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8};
    gdb::array_view<T> v = vec;

    copy (v.slice (2, 4),
	  v.slice (1, 4));

    std::vector<T> expected = {1, 3, 4, 5, 6, 6, 7, 8};
    SELF_CHECK (vec == expected);
  }

  /* Test overlapping copy, where the source is the same as the destination.  */
  {
    std::vector<T> vec = {1, 2, 3, 4, 5, 6, 7, 8};
    gdb::array_view<T> v = vec;

    copy (v.slice (2, 4),
	  v.slice (2, 4));

    std::vector<T> expected = {1, 2, 3, 4, 5, 6, 7, 8};
    SELF_CHECK (vec == expected);
  }
}

/* Class with a non-trivial copy assignment operator, used to test the
   array_view copy function.  */
struct foo
{
  /* Can be implicitly constructed from an int, such that we can use the same
     templated test function to test against array_view<int> and
     array_view<foo>.  */
  foo (int n)
    : n (n)
  {}

  /* Needed to avoid -Wdeprecated-copy-with-user-provided-copy error with
     Clang.  */
  foo (const foo &other) = default;

  void operator= (const foo &other)
  {
    this->n = other.n;
    this->n_assign_op_called++;
  }

  bool operator==(const foo &other) const
  {
    return this->n == other.n;
  }

  int n;

  /* Number of times the assignment operator has been called.  */
  static int n_assign_op_called;
};

int foo::n_assign_op_called = 0;

/* Test the array_view copy free function.  */

static void
run_copy_tests ()
{
  /* Test with a trivial type.  */
  run_copy_test<int> ();

  /* Test with a non-trivial type.  */
  foo::n_assign_op_called = 0;
  run_copy_test<foo> ();

  /* Make sure that for the non-trivial type foo, the assignment operator was
     called an amount of times that makes sense.  */
  SELF_CHECK (foo::n_assign_op_called == 12);
}

} /* namespace array_view_tests */
} /* namespace selftests */

void _initialize_array_view_selftests ();
void
_initialize_array_view_selftests ()
{
  selftests::register_test ("array_view",
			    selftests::array_view_tests::run_tests);
  selftests::register_test ("array_view-copy",
			    selftests::array_view_tests::run_copy_tests);
}