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

   Copyright (C) 2017-2020 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>

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>> ());
}

} /* namespace no_slicing */

static_assert (no_slicing::check (), "");

/* 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]);
    }
  }
}

} /* 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);
}
