<commit_before>/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#define SUITE type

#include "vast/test/test.hpp"
#include "type_test.hpp"
#include "vast/test/fixtures/actor_system.hpp"

#include "vast/data.hpp"
#include "vast/json.hpp"
#include "vast/load.hpp"
#include "vast/save.hpp"
#include "vast/type.hpp"

#include "vast/concept/hashable/uhash.hpp"
#include "vast/concept/hashable/xxhash.hpp"
#include "vast/concept/parseable/vast/type.hpp"
#include "vast/concept/printable/stream.hpp"
#include "vast/concept/printable/to_string.hpp"
#include "vast/concept/printable/vast/json.hpp"
#include "vast/concept/printable/vast/offset.hpp"
#include "vast/concept/printable/vast/type.hpp"

using caf::get;
using caf::get_if;
using caf::holds_alternative;

using namespace std::string_literals;
using namespace vast;

FIXTURE_SCOPE(type_tests, fixtures::deterministic_actor_system)

TEST(default construction) {
  type t;
  CHECK(!t);
  CHECK(!holds_alternative<boolean_type>(t));
}

TEST(construction) {
  auto s = string_type{};
  auto t = type{s};
  CHECK(t);
  CHECK(holds_alternative<string_type>(t));
  CHECK(get_if<string_type>(&t) != nullptr);
}

TEST(assignment) {
  auto t = type{string_type{}};
  CHECK(t);
  CHECK(holds_alternative<string_type>(t));
  t = real_type{};
  CHECK(t);
  CHECK(holds_alternative<real_type>(t));
  t = {};
  CHECK(!t);
  CHECK(!holds_alternative<real_type>(t));
}

TEST(copying) {
  auto t = type{string_type{}};
  auto u = t;
  CHECK(holds_alternative<string_type>(u));
}

TEST(names) {
  type t;
  t.name("foo");
  CHECK(t.name().empty());
  t = type{string_type{}};
  t.name("foo");
  CHECK_EQUAL(t.name(), "foo");
}

TEST(attributes) {
  auto attrs = std::vector<attribute>{{"key", "value"}};
  type t;
  t.attributes(attrs);
  CHECK(t.attributes().empty());
  t = string_type{};
  t.attributes({{"key", "value"}});
  CHECK_EQUAL(t.attributes(), attrs);
}

TEST(equality comparison) {
  MESSAGE("type-erased comparison");
  CHECK(type{} == type{});
  CHECK(type{boolean_type{}} != type{});
  CHECK(type{boolean_type{}} == type{boolean_type{}});
  CHECK(type{boolean_type{}} != type{real_type{}});
  auto x = type{string_type{}};
  auto y = type{string_type{}};
  x.name("foo");
  CHECK(x != y);
  y.name("foo");
  CHECK(x == y);
  MESSAGE("concrete type comparison");
  CHECK(real_type{} == real_type{});
  CHECK(real_type{}.name("foo") != real_type{});
  CHECK(real_type{}.name("foo") == real_type{}.name("foo"));
  auto attrs = std::vector<attribute>{{"key", "value"}};
  CHECK(real_type{}.attributes(attrs) != real_type{});
  CHECK(real_type{}.attributes(attrs) == real_type{}.attributes(attrs));
}

TEST(less-than comparison) {
  CHECK(!(type{} < type{}));
  CHECK(!(real_type{} < real_type{}));
  CHECK(string_type{}.name("a") < string_type{}.name("b"));
  CHECK(record_type{}.name("a") < record_type{}.name("b"));
}

TEST(strict weak ordering) {
  std::vector<type> xs{string_type{}, address_type{}, pattern_type{}};
  std::vector<type> ys{string_type{}, pattern_type{}, address_type{}};
  std::sort(xs.begin(), xs.end());
  std::sort(ys.begin(), ys.end());
  CHECK(xs == ys);
}

TEST(introspection) {
  CHECK(is_complex(enumeration_type{}));
  CHECK(!is_basic(enumeration_type{}));
  CHECK(is_complex(vector_type{}));
  CHECK(is_container(vector_type{}));
  CHECK(is_recursive(vector_type{}));
  CHECK(is_complex(set_type{}));
  CHECK(is_container(set_type{}));
  CHECK(is_recursive(set_type{}));
  CHECK(is_complex(map_type{}));
  CHECK(is_container(map_type{}));
  CHECK(is_recursive(map_type{}));
  CHECK(is_recursive(record_type{}));
  CHECK(!is_container(record_type{}));
  CHECK(is_recursive(alias_type{}));
  CHECK(!is_container(alias_type{}));
}

TEST(type/data compatibility) {
  CHECK(compatible(address_type{}, in, subnet_type{}));
  CHECK(compatible(address_type{}, in, subnet{}));
  CHECK(compatible(subnet_type{}, in, subnet_type{}));
  CHECK(compatible(subnet_type{}, in, subnet{}));
}

TEST(serialization) {
  CHECK_ROUNDTRIP(type{});
  CHECK_ROUNDTRIP(none_type{});
  CHECK_ROUNDTRIP(boolean_type{});
  CHECK_ROUNDTRIP(integer_type{});
  CHECK_ROUNDTRIP(count_type{});
  CHECK_ROUNDTRIP(real_type{});
  CHECK_ROUNDTRIP(timespan_type{});
  CHECK_ROUNDTRIP(timestamp_type{});
  CHECK_ROUNDTRIP(string_type{});
  CHECK_ROUNDTRIP(pattern_type{});
  CHECK_ROUNDTRIP(address_type{});
  CHECK_ROUNDTRIP(subnet_type{});
  CHECK_ROUNDTRIP(port_type{});
  CHECK_ROUNDTRIP(enumeration_type{});
  CHECK_ROUNDTRIP(vector_type{});
  CHECK_ROUNDTRIP(set_type{});
  CHECK_ROUNDTRIP(map_type{});
  CHECK_ROUNDTRIP(record_type{});
  CHECK_ROUNDTRIP(alias_type{});
  CHECK_ROUNDTRIP(type{none_type{}});
  CHECK_ROUNDTRIP(type{boolean_type{}});
  CHECK_ROUNDTRIP(type{integer_type{}});
  CHECK_ROUNDTRIP(type{count_type{}});
  CHECK_ROUNDTRIP(type{real_type{}});
  CHECK_ROUNDTRIP(type{timespan_type{}});
  CHECK_ROUNDTRIP(type{timestamp_type{}});
  CHECK_ROUNDTRIP(type{string_type{}});
  CHECK_ROUNDTRIP(type{pattern_type{}});
  CHECK_ROUNDTRIP(type{address_type{}});
  CHECK_ROUNDTRIP(type{subnet_type{}});
  CHECK_ROUNDTRIP(type{port_type{}});
  CHECK_ROUNDTRIP(type{enumeration_type{}});
  CHECK_ROUNDTRIP(type{vector_type{}});
  CHECK_ROUNDTRIP(type{set_type{}});
  CHECK_ROUNDTRIP(type{map_type{}});
  CHECK_ROUNDTRIP(type{record_type{}});
  CHECK_ROUNDTRIP(type{alias_type{}});
  auto r = record_type{
    {"x", integer_type{}},
    {"y", address_type{}},
    {"z", real_type{}.attributes({{"key", "value"}})}
  };
  // Make it recursive.
  r = {
    {"a", map_type{string_type{}, port_type{}}},
    {"b", vector_type{boolean_type{}}.name("foo")},
    {"c", r}
  };
  r.name("foo");
  CHECK_ROUNDTRIP(r);
}

TEST(record range) {
  auto r = record_type{
    {"x", record_type{
            {"y", record_type{
                    {"z", integer_type{}},
                    {"k", boolean_type{}}
                  }},
            {"m", record_type{
                    {"y", record_type{
                            {"a", address_type{}}}},
                    {"f", real_type{}}
                  }},
            {"b", boolean_type{}}
          }},
    {"y", record_type{
            {"b", boolean_type{}}}}
  };
  MESSAGE("check types of record r");
  auto record_index = r.index();
  CHECK_EQUAL(at(r, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 0, 0), integer_type{});
  CHECK_EQUAL(at(r, 0, 0, 1), boolean_type{});
  CHECK_EQUAL(at(r, 0, 1)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 1, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 1, 0, 0), address_type{});
  CHECK_EQUAL(at(r, 0, 1, 1), real_type{});
  CHECK_EQUAL(at(r, 0, 2), boolean_type{});
  CHECK_EQUAL(at(r, 1)->index(), record_index);
  CHECK_EQUAL(at(r, 1, 0), boolean_type{});
  MESSAGE("check keys of record r");
  std::vector<std::string> keys;
  for (auto& i : record_type::each{r})
    keys.emplace_back(i.key());
  std::vector<std::string> expected_keys{"x.y.z", "x.y.k", "x.m.y.a",
                                         "x.m.f", "x.b",   "y.b"};
  CHECK_EQUAL(keys, expected_keys);
}

TEST(record resolving) {
  auto r = record_type{
    {"a", integer_type{}},
    {"b", count_type{}},
    {"c", record_type{
      {"x", integer_type{}},
      {"y", address_type{}},
      {"z", real_type{}}
    }}
  };
  MESSAGE("top-level offset resolve");
  auto o = r.resolve("c");
  REQUIRE(o);
  CHECK_EQUAL(o->size(), 1u);
  CHECK_EQUAL(o->front(), 2u);
  MESSAGE("nested offset resolve");
  o = r.resolve("c.x");
  REQUIRE(o);
  CHECK_EQUAL(o->size(), 2u);
  CHECK_EQUAL(o->front(), 2u);
  CHECK_EQUAL(o->back(), 0u);
  o = r.resolve("c.x.absent");
  CHECK(!o);
  MESSAGE("top-level offset resolve");
  auto k = r.resolve(offset{2});
  REQUIRE(k);
  CHECK_EQUAL(*k, "c");
  MESSAGE("nested offset resolve");
  k = r.resolve(offset{2, 0});
  REQUIRE(k);
  CHECK_EQUAL(*k, "c.x");
}

TEST(record flattening/unflattening) {
  auto x = record_type{
    {"x", record_type{
      {"y", record_type{
        {"z", integer_type{}},
        {"k", boolean_type{}}
      }},
      {"m", record_type{
        {"y", record_type{{"a", address_type{}}}},
        {"f", real_type{}}
      }},
      {"b", boolean_type{}}
    }},
    {"y", record_type{
      {"b", boolean_type{}}
    }}
  };
  auto y = record_type{{
    {"x.y.z", integer_type{}},
    {"x.y.k", boolean_type{}},
    {"x.m.y.a", address_type{}},
    {"x.m.f", real_type{}},
    {"x.b", boolean_type{}},
    {"y.b", boolean_type{}}
  }};
  auto f = flatten(x);
  CHECK(f == y);
  auto u = unflatten(f);
  CHECK(u == x);
}

TEST(record flat index computation) {
  auto x = record_type{
    {"x", record_type{
      {"y", record_type{
        {"z", integer_type{}}, // 0: x.y.z [0, 0, 0]
        {"k", boolean_type{}}  // 1: x.y.k [0, 0, 1]
      }},
      {"m", record_type{
        {"y", record_type{
          {"a", address_type{}}} // 2: x.m.y.a [0, 1, 0, 0]
        },
        {"f", real_type{}} // 3: x.m.f [0, 1, 1]
      }},
      {"b", boolean_type{}} // 4: x.b [0, 2]
    }},
    {"y", record_type{
      {"b", boolean_type{}} // 5: y.b [1, 0]
    }}
  };
  using os = caf::optional<size_t>;
  static const os invalid;
  CHECK_EQUAL(flat_size(x), 6u);
  CHECK_EQUAL(x.flat_index_at(offset({0, 0, 0})), os(0u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 0, 1})), os(1u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 1, 0, 0})), os(2u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 1, 1})), os(3u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 2})), os(4u));
  CHECK_EQUAL(x.flat_index_at(offset({1, 0})), os(5u));
  CHECK_EQUAL(x.flat_index_at(offset({0})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({0, 0})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({1})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({2})), invalid);
}

TEST(record symbol finding) {
  auto r = record_type{
    {"a", integer_type{}},
    {"b", record_type{
      {"a", integer_type{}},
      {"b", count_type{}},
      {"c", record_type{
        {"x", integer_type{}},
        {"y", address_type{}},
        {"z", real_type{}}
      }}
    }},
    {"c", count_type{}}
  };
  r = r.name("foo");
  auto f = flatten(r);
  MESSAGE("record access by key");
  auto first = r.at("a");
  REQUIRE(first);
  CHECK(holds_alternative<integer_type>(*first));
  first = f.at("a");
  REQUIRE(first);
  CHECK(holds_alternative<integer_type>(*first));
  auto deep = r.at("b.c.y");
  REQUIRE(deep);
  CHECK(holds_alternative<address_type>(*deep));
  deep = f.at("b.c.y");
  REQUIRE(deep);
  CHECK(holds_alternative<address_type>(*deep));
  auto rec = r.at("b");
  REQUIRE(rec);
  CHECK(holds_alternative<record_type>(*rec));
  rec = f.at("b");
  // A flat record has no longer an internal record that can be accessed
  // directly. Hence the access fails.
  CHECK(!rec);
  rec = r.at("b.c");
  REQUIRE(rec);
  CHECK(holds_alternative<record_type>(*rec));
  rec = f.at("b.c");
  CHECK(!rec);
  MESSAGE("prefix finding");
  // Since the type has a name, the prefix has the form "name.first.second".
  // E.g., a full key is foo.a for field 0 or foo.b.c.z for a nested field.
  using offset_keys = std::vector<std::pair<offset, std::string>>;
  CHECK_EQUAL(r.find_prefix("a"), (offset_keys{{{0}, "a"}}));
  CHECK_EQUAL(f.find_prefix("a"), (offset_keys{{{0}, "a"}}));
  CHECK_EQUAL(r.find_prefix("b.a"), (offset_keys{{{1,0}, "b.a"}}));
  CHECK_EQUAL(f.find_prefix("b.a"), (offset_keys{{{1}, "b.a"}}));
  auto b = offset_keys{
    {{1}, "b"},
    {{1, 0}, "b.a"},
    {{1, 1}, "b.b"},
    {{1, 2}, "b.c"},
    {{1, 2, 0}, "b.c.x"},
    {{1, 2, 1}, "b.c.y"},
    {{1, 2, 2}, "b.c.z"}
  };
  auto b_flat = offset_keys{
    {{1}, "b.a"},
    {{2}, "b.b"},
    {{3}, "b.c.x"},
    {{4}, "b.c.y"},
    {{5}, "b.c.z"}
  };
  CHECK_EQUAL(r.find_prefix("b"), b);
  CHECK_EQUAL(f.find_prefix("b"), b_flat);
  MESSAGE("suffix finding");
  // Find a single deep field.
  CHECK_EQUAL(r.find_suffix("c.y"), (offset_keys{{{1, 2, 1}, "b.c.y"}}));
  CHECK_EQUAL(f.find_suffix("c.y"), (offset_keys{{{4}, "b.c.y"}}));
  CHECK_EQUAL(r.find_suffix("z"), (offset_keys{{{1, 2, 2}, "b.c.z"}}));
  CHECK_EQUAL(f.find_suffix("z"), (offset_keys{{{5}, "b.c.z"}}));
  // Find multiple record fields.
  auto a = offset_keys{
    {{0}, "a"},
    {{1, 0}, "b.a"},
  };
  auto a_flat = offset_keys{
    {{0}, "a"},
    {{1}, "b.a"},
  };
  CHECK_EQUAL(r.find_suffix("a"), a);
  CHECK_EQUAL(f.find_suffix("a"), a_flat);
  // Use a glob expression.
  auto c = offset_keys{
    {{1, 2, 0}, "b.c.x"},
    {{1, 2, 1}, "b.c.y"},
    {{1, 2, 2}, "b.c.z"}
  };
  auto c_flat = offset_keys{
    {{3}, "b.c.x"},
    {{4}, "b.c.y"},
    {{5}, "b.c.z"}
  };
  CHECK_EQUAL(r.find_suffix("c.*"), c);
  CHECK_EQUAL(f.find_suffix("c.*"), c_flat);
  // Find a field that is also a record.
  CHECK_EQUAL(r.find_suffix("b"), (offset_keys{{{1, 1}, "b.b"}}));
  CHECK_EQUAL(f.find_suffix("b"), (offset_keys{{{2}, "b.b"}}));
  MESSAGE("arbitrary finding");
  auto any_c = offset_keys{
    {{1, 2}, "b.c"},
    {{1, 2, 0}, "b.c.x"},
    {{1, 2, 1}, "b.c.y"},
    {{1, 2, 2}, "b.c.z"},
    {{2}, "c"}
  };
  auto any_c_flat = offset_keys{
    {{3}, "b.c.x"},
    {{4}, "b.c.y"},
    {{5}, "b.c.z"},
    {{6}, "c"}
  };
  CHECK_EQUAL(r.find("c"), any_c);
  CHECK_EQUAL(f.find("c"), any_c_flat);
}

TEST(congruence) {
  MESSAGE("basic");
  auto i = integer_type{};
  auto j = integer_type{};
  CHECK(i == j);
  i = i.name("i");
  j = j.name("j");
  CHECK(i != j);<commit_msg>Refactor record key lookup unit test<commit_after>/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#define SUITE type

#include "vast/test/test.hpp"
#include "type_test.hpp"
#include "vast/test/fixtures/actor_system.hpp"

#include "vast/data.hpp"
#include "vast/json.hpp"
#include "vast/load.hpp"
#include "vast/save.hpp"
#include "vast/type.hpp"

#include "vast/concept/hashable/uhash.hpp"
#include "vast/concept/hashable/xxhash.hpp"
#include "vast/concept/parseable/vast/type.hpp"
#include "vast/concept/printable/stream.hpp"
#include "vast/concept/printable/to_string.hpp"
#include "vast/concept/printable/vast/json.hpp"
#include "vast/concept/printable/vast/offset.hpp"
#include "vast/concept/printable/vast/type.hpp"

using caf::get;
using caf::get_if;
using caf::holds_alternative;

using namespace std::string_literals;
using namespace vast;

FIXTURE_SCOPE(type_tests, fixtures::deterministic_actor_system)

TEST(default construction) {
  type t;
  CHECK(!t);
  CHECK(!holds_alternative<boolean_type>(t));
}

TEST(construction) {
  auto s = string_type{};
  auto t = type{s};
  CHECK(t);
  CHECK(holds_alternative<string_type>(t));
  CHECK(get_if<string_type>(&t) != nullptr);
}

TEST(assignment) {
  auto t = type{string_type{}};
  CHECK(t);
  CHECK(holds_alternative<string_type>(t));
  t = real_type{};
  CHECK(t);
  CHECK(holds_alternative<real_type>(t));
  t = {};
  CHECK(!t);
  CHECK(!holds_alternative<real_type>(t));
}

TEST(copying) {
  auto t = type{string_type{}};
  auto u = t;
  CHECK(holds_alternative<string_type>(u));
}

TEST(names) {
  type t;
  t.name("foo");
  CHECK(t.name().empty());
  t = type{string_type{}};
  t.name("foo");
  CHECK_EQUAL(t.name(), "foo");
}

TEST(attributes) {
  auto attrs = std::vector<attribute>{{"key", "value"}};
  type t;
  t.attributes(attrs);
  CHECK(t.attributes().empty());
  t = string_type{};
  t.attributes({{"key", "value"}});
  CHECK_EQUAL(t.attributes(), attrs);
}

TEST(equality comparison) {
  MESSAGE("type-erased comparison");
  CHECK(type{} == type{});
  CHECK(type{boolean_type{}} != type{});
  CHECK(type{boolean_type{}} == type{boolean_type{}});
  CHECK(type{boolean_type{}} != type{real_type{}});
  auto x = type{string_type{}};
  auto y = type{string_type{}};
  x.name("foo");
  CHECK(x != y);
  y.name("foo");
  CHECK(x == y);
  MESSAGE("concrete type comparison");
  CHECK(real_type{} == real_type{});
  CHECK(real_type{}.name("foo") != real_type{});
  CHECK(real_type{}.name("foo") == real_type{}.name("foo"));
  auto attrs = std::vector<attribute>{{"key", "value"}};
  CHECK(real_type{}.attributes(attrs) != real_type{});
  CHECK(real_type{}.attributes(attrs) == real_type{}.attributes(attrs));
}

TEST(less-than comparison) {
  CHECK(!(type{} < type{}));
  CHECK(!(real_type{} < real_type{}));
  CHECK(string_type{}.name("a") < string_type{}.name("b"));
  CHECK(record_type{}.name("a") < record_type{}.name("b"));
}

TEST(strict weak ordering) {
  std::vector<type> xs{string_type{}, address_type{}, pattern_type{}};
  std::vector<type> ys{string_type{}, pattern_type{}, address_type{}};
  std::sort(xs.begin(), xs.end());
  std::sort(ys.begin(), ys.end());
  CHECK(xs == ys);
}

TEST(introspection) {
  CHECK(is_complex(enumeration_type{}));
  CHECK(!is_basic(enumeration_type{}));
  CHECK(is_complex(vector_type{}));
  CHECK(is_container(vector_type{}));
  CHECK(is_recursive(vector_type{}));
  CHECK(is_complex(set_type{}));
  CHECK(is_container(set_type{}));
  CHECK(is_recursive(set_type{}));
  CHECK(is_complex(map_type{}));
  CHECK(is_container(map_type{}));
  CHECK(is_recursive(map_type{}));
  CHECK(is_recursive(record_type{}));
  CHECK(!is_container(record_type{}));
  CHECK(is_recursive(alias_type{}));
  CHECK(!is_container(alias_type{}));
}

TEST(type/data compatibility) {
  CHECK(compatible(address_type{}, in, subnet_type{}));
  CHECK(compatible(address_type{}, in, subnet{}));
  CHECK(compatible(subnet_type{}, in, subnet_type{}));
  CHECK(compatible(subnet_type{}, in, subnet{}));
}

TEST(serialization) {
  CHECK_ROUNDTRIP(type{});
  CHECK_ROUNDTRIP(none_type{});
  CHECK_ROUNDTRIP(boolean_type{});
  CHECK_ROUNDTRIP(integer_type{});
  CHECK_ROUNDTRIP(count_type{});
  CHECK_ROUNDTRIP(real_type{});
  CHECK_ROUNDTRIP(timespan_type{});
  CHECK_ROUNDTRIP(timestamp_type{});
  CHECK_ROUNDTRIP(string_type{});
  CHECK_ROUNDTRIP(pattern_type{});
  CHECK_ROUNDTRIP(address_type{});
  CHECK_ROUNDTRIP(subnet_type{});
  CHECK_ROUNDTRIP(port_type{});
  CHECK_ROUNDTRIP(enumeration_type{});
  CHECK_ROUNDTRIP(vector_type{});
  CHECK_ROUNDTRIP(set_type{});
  CHECK_ROUNDTRIP(map_type{});
  CHECK_ROUNDTRIP(record_type{});
  CHECK_ROUNDTRIP(alias_type{});
  CHECK_ROUNDTRIP(type{none_type{}});
  CHECK_ROUNDTRIP(type{boolean_type{}});
  CHECK_ROUNDTRIP(type{integer_type{}});
  CHECK_ROUNDTRIP(type{count_type{}});
  CHECK_ROUNDTRIP(type{real_type{}});
  CHECK_ROUNDTRIP(type{timespan_type{}});
  CHECK_ROUNDTRIP(type{timestamp_type{}});
  CHECK_ROUNDTRIP(type{string_type{}});
  CHECK_ROUNDTRIP(type{pattern_type{}});
  CHECK_ROUNDTRIP(type{address_type{}});
  CHECK_ROUNDTRIP(type{subnet_type{}});
  CHECK_ROUNDTRIP(type{port_type{}});
  CHECK_ROUNDTRIP(type{enumeration_type{}});
  CHECK_ROUNDTRIP(type{vector_type{}});
  CHECK_ROUNDTRIP(type{set_type{}});
  CHECK_ROUNDTRIP(type{map_type{}});
  CHECK_ROUNDTRIP(type{record_type{}});
  CHECK_ROUNDTRIP(type{alias_type{}});
  auto r = record_type{
    {"x", integer_type{}},
    {"y", address_type{}},
    {"z", real_type{}.attributes({{"key", "value"}})}
  };
  // Make it recursive.
  r = {
    {"a", map_type{string_type{}, port_type{}}},
    {"b", vector_type{boolean_type{}}.name("foo")},
    {"c", r}
  };
  r.name("foo");
  CHECK_ROUNDTRIP(r);
}

TEST(record range) {
  auto r = record_type{
    {"x", record_type{
            {"y", record_type{
                    {"z", integer_type{}},
                    {"k", boolean_type{}}
                  }},
            {"m", record_type{
                    {"y", record_type{
                            {"a", address_type{}}}},
                    {"f", real_type{}}
                  }},
            {"b", boolean_type{}}
          }},
    {"y", record_type{
            {"b", boolean_type{}}}}
  };
  MESSAGE("check types of record r");
  auto record_index = r.index();
  CHECK_EQUAL(at(r, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 0, 0), integer_type{});
  CHECK_EQUAL(at(r, 0, 0, 1), boolean_type{});
  CHECK_EQUAL(at(r, 0, 1)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 1, 0)->index(), record_index);
  CHECK_EQUAL(at(r, 0, 1, 0, 0), address_type{});
  CHECK_EQUAL(at(r, 0, 1, 1), real_type{});
  CHECK_EQUAL(at(r, 0, 2), boolean_type{});
  CHECK_EQUAL(at(r, 1)->index(), record_index);
  CHECK_EQUAL(at(r, 1, 0), boolean_type{});
  MESSAGE("check keys of record r");
  std::vector<std::string> keys;
  for (auto& i : record_type::each{r})
    keys.emplace_back(i.key());
  std::vector<std::string> expected_keys{"x.y.z", "x.y.k", "x.m.y.a",
                                         "x.m.f", "x.b",   "y.b"};
  CHECK_EQUAL(keys, expected_keys);
}

TEST(record resolving) {
  auto r = record_type{
    {"a", integer_type{}},
    {"b", count_type{}},
    {"c", record_type{
      {"x", integer_type{}},
      {"y", address_type{}},
      {"z", real_type{}}
    }}
  };
  MESSAGE("top-level offset resolve");
  auto o = r.resolve("c");
  REQUIRE(o);
  CHECK_EQUAL(o->size(), 1u);
  CHECK_EQUAL(o->front(), 2u);
  MESSAGE("nested offset resolve");
  o = r.resolve("c.x");
  REQUIRE(o);
  CHECK_EQUAL(o->size(), 2u);
  CHECK_EQUAL(o->front(), 2u);
  CHECK_EQUAL(o->back(), 0u);
  o = r.resolve("c.x.absent");
  CHECK(!o);
  MESSAGE("top-level offset resolve");
  auto k = r.resolve(offset{2});
  REQUIRE(k);
  CHECK_EQUAL(*k, "c");
  MESSAGE("nested offset resolve");
  k = r.resolve(offset{2, 0});
  REQUIRE(k);
  CHECK_EQUAL(*k, "c.x");
}

TEST(record flattening/unflattening) {
  auto x = record_type{
    {"x", record_type{
      {"y", record_type{
        {"z", integer_type{}},
        {"k", boolean_type{}}
      }},
      {"m", record_type{
        {"y", record_type{{"a", address_type{}}}},
        {"f", real_type{}}
      }},
      {"b", boolean_type{}}
    }},
    {"y", record_type{
      {"b", boolean_type{}}
    }}
  };
  auto y = record_type{{
    {"x.y.z", integer_type{}},
    {"x.y.k", boolean_type{}},
    {"x.m.y.a", address_type{}},
    {"x.m.f", real_type{}},
    {"x.b", boolean_type{}},
    {"y.b", boolean_type{}}
  }};
  auto f = flatten(x);
  CHECK(f == y);
  auto u = unflatten(f);
  CHECK(u == x);
}

TEST(record flat index computation) {
  auto x = record_type{
    {"x", record_type{
      {"y", record_type{
        {"z", integer_type{}}, // 0: x.y.z [0, 0, 0]
        {"k", boolean_type{}}  // 1: x.y.k [0, 0, 1]
      }},
      {"m", record_type{
        {"y", record_type{
          {"a", address_type{}}} // 2: x.m.y.a [0, 1, 0, 0]
        },
        {"f", real_type{}} // 3: x.m.f [0, 1, 1]
      }},
      {"b", boolean_type{}} // 4: x.b [0, 2]
    }},
    {"y", record_type{
      {"b", boolean_type{}} // 5: y.b [1, 0]
    }}
  };
  using os = caf::optional<size_t>;
  static const os invalid;
  CHECK_EQUAL(flat_size(x), 6u);
  CHECK_EQUAL(x.flat_index_at(offset({0, 0, 0})), os(0u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 0, 1})), os(1u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 1, 0, 0})), os(2u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 1, 1})), os(3u));
  CHECK_EQUAL(x.flat_index_at(offset({0, 2})), os(4u));
  CHECK_EQUAL(x.flat_index_at(offset({1, 0})), os(5u));
  CHECK_EQUAL(x.flat_index_at(offset({0})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({0, 0})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({1})), invalid);
  CHECK_EQUAL(x.flat_index_at(offset({2})), invalid);
}

record_type make_record() {
  auto r = record_type{
    {"a", integer_type{}},
    {"b", record_type{
      {"a", integer_type{}},
      {"b", count_type{}},
      {"c", record_type{
        {"x", integer_type{}},
        {"y", address_type{}},
        {"z", real_type{}}
      }}
    }},
    {"c", count_type{}}
  };
  r = r.name("foo");
  return r;
}

TEST(record symbol finding - exact) {
  const auto r = make_record();
  const auto f = flatten(r);
  auto first = r.at("a");
  REQUIRE(first);
  CHECK(holds_alternative<integer_type>(*first));
  first = f.at("a");
  REQUIRE(first);
  CHECK(holds_alternative<integer_type>(*first));
  auto deep = r.at("b.c.y");
  REQUIRE(deep);
  CHECK(holds_alternative<address_type>(*deep));
  deep = f.at("b.c.y");
  REQUIRE(deep);
  CHECK(holds_alternative<address_type>(*deep));
  auto rec = r.at("b");
  REQUIRE(rec);
  CHECK(holds_alternative<record_type>(*rec));
  rec = f.at("b");
  // A flat record has no longer an internal record that can be accessed
  // directly. Hence the access fails.
  CHECK(!rec);
  rec = r.at("b.c");
  REQUIRE(rec);
  CHECK(holds_alternative<record_type>(*rec));
  rec = f.at("b.c");
  CHECK(!rec);
}

using offset_keys = std::vector<std::pair<offset, std::string>>;

TEST(record symbol finding - prefix) {
  const auto r = make_record();
  const auto f = flatten(r);
  // Since the type has a name, the prefix has the form "name.first.second".
  // E.g., a full key is foo.a for field 0 or foo.b.c.z for a nested field.
  CHECK_EQUAL(r.find_prefix("a"), (offset_keys{{{0}, "a"}}));
  CHECK_EQUAL(f.find_prefix("a"), (offset_keys{{{0}, "a"}}));
  CHECK_EQUAL(r.find_prefix("b.a"), (offset_keys{{{1,0}, "b.a"}}));
  CHECK_EQUAL(f.find_prefix("b.a"), (offset_keys{{{1}, "b.a"}}));
  const auto b = offset_keys{
    // clang-format off
    {{1}, "b"},
    {{1, 0}, "b.a"},
    {{1, 1}, "b.b"},
    {{1, 2}, "b.c"},
    {{1, 2, 0}, "b.c.x"},
    {{1, 2, 1}, "b.c.y"},
    {{1, 2, 2}, "b.c.z"}
    // clang-format on
  };
  const auto b_flat = offset_keys{{{1}, "b.a"},
                                  {{2}, "b.b"},
                                  {{3}, "b.c.x"},
                                  {{4}, "b.c.y"},
                                  {{5}, "b.c.z"}};
  CHECK_EQUAL(r.find_prefix("b"), b);
  CHECK_EQUAL(f.find_prefix("b"), b_flat);
}

TEST(record symbol finding - suffix) {
  const auto r = make_record();
  const auto f = flatten(r);
  MESSAGE("single deep field");
  CHECK_EQUAL(r.find_suffix("c.y"), (offset_keys{{{1, 2, 1}, "b.c.y"}}));
  CHECK_EQUAL(f.find_suffix("c.y"), (offset_keys{{{4}, "b.c.y"}}));
  CHECK_EQUAL(r.find_suffix("z"), (offset_keys{{{1, 2, 2}, "b.c.z"}}));
  CHECK_EQUAL(f.find_suffix("z"), (offset_keys{{{5}, "b.c.z"}}));
  MESSAGE("multiple record fields");
  const auto a = offset_keys{
    {{0}, "a"},
    {{1, 0}, "b.a"},
  };
  const auto a_flat = offset_keys{
    {{0}, "a"},
    {{1}, "b.a"},
  };
  CHECK_EQUAL(r.find_suffix("a"), a);
  CHECK_EQUAL(f.find_suffix("a"), a_flat);
  MESSAGE("glob expression");
  const auto c = offset_keys{{{1, 2, 0}, "b.c.x"},
                             {{1, 2, 1}, "b.c.y"},
                             {{1, 2, 2}, "b.c.z"}};
  const auto c_flat = offset_keys{{{3}, "b.c.x"},
                                  {{4}, "b.c.y"},
                                  {{5}, "b.c.z"}};
  CHECK_EQUAL(r.find_suffix("c.*"), c);
  CHECK_EQUAL(f.find_suffix("c.*"), c_flat);
  MESSAGE("field that is also a record");
  CHECK_EQUAL(r.find_suffix("b"), (offset_keys{{{1, 1}, "b.b"}}));
  CHECK_EQUAL(f.find_suffix("b"), (offset_keys{{{2}, "b.b"}}));
}

TEST(record symbol finding - no anchor) {
  const auto r = make_record();
  const auto f = flatten(r);
  auto any_c = offset_keys{
    {{1, 2}, "b.c"},
    {{1, 2, 0}, "b.c.x"},
    {{1, 2, 1}, "b.c.y"},
    {{1, 2, 2}, "b.c.z"},
    {{2}, "c"}
  };
  auto any_c_flat = offset_keys{
    {{3}, "b.c.x"},
    {{4}, "b.c.y"},
    {{5}, "b.c.z"},
    {{6}, "c"}
  };
  CHECK_EQUAL(r.find("c"), any_c);
  CHECK_EQUAL(f.find("c"), any_c_flat);
}

TEST(congruence) {
  MESSAGE("basic");
  auto i = integer_type{};
  auto j = integer_type{};
  CHECK(i == j);
  i = i.name("i");
  j = j.name("j");
  CHECK(i != j);
{'fraction_non_alphanumeric': 0.22366260774387742, 'fraction_numerical': 0.010740183335613628, 'mean_word_length': 3.517459826946848, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7110864', 'n_tokens_mistral': 11587, 'n_tokens_neox': 11140, 'n_words': 1965}