blob: 3d397a26b4e7b0754b803542133e467c6b5af3d7 [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed 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.
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/flat_hash_map.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "flatbuffers/base.h"
#include "flatbuffers/buffer.h"
#include "flatbuffers/flatbuffer_builder.h"
#include "flatbuffers/reflection_generated.h"
#include "flatbuffers/string.h"
#include "flatbuffers/vector.h"
#include "./fuzztest/domain.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/flatbuffers.h"
#include "./fuzztest/internal/meta.h"
#include "./fuzztest/internal/test_flatbuffers_generated.h"
namespace fuzztest {
namespace {
using ::fuzztest::internal::BoolTable;
using ::fuzztest::internal::DefaultTable;
using ::fuzztest::internal::OptionalTable;
using ::fuzztest::internal::RecursiveTable;
using ::fuzztest::internal::RequiredTable;
using ::fuzztest::internal::UnsupportedTypesTable;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Each;
using ::testing::HasSubstr;
using ::testing::IsFalse;
using ::testing::IsNull;
using ::testing::IsTrue;
using ::testing::NotNull;
using ::testing::Pair;
using ::testing::ResultOf;
template <typename T>
inline bool Eq(const T& lhs, const T& rhs) {
return rhs == lhs;
}
template <typename T>
inline bool Eq(const T* lhs, const T* rhs) {
if (lhs == nullptr && rhs == nullptr) return true;
if (lhs == nullptr || rhs == nullptr) return false;
return Eq(*lhs, *rhs);
}
template <>
inline bool Eq<flatbuffers::String>(const flatbuffers::String& lhs,
const flatbuffers::String& rhs) {
if (lhs.size() != rhs.size()) return false;
return memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
template <>
inline bool Eq<BoolTable>(const BoolTable& lhs, const BoolTable& rhs) {
return lhs.b() == rhs.b();
}
template <>
inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) {
const bool eq_b = lhs.b() == rhs.b();
const bool eq_i8 = lhs.i8() == rhs.i8();
const bool eq_i16 = lhs.i16() == rhs.i16();
const bool eq_i32 = lhs.i32() == rhs.i32();
const bool eq_i64 = lhs.i64() == rhs.i64();
const bool eq_u8 = lhs.u8() == rhs.u8();
const bool eq_u16 = lhs.u16() == rhs.u16();
const bool eq_u32 = lhs.u32() == rhs.u32();
const bool eq_u64 = lhs.u64() == rhs.u64();
const bool eq_f = lhs.f() == rhs.f();
const bool eq_d = lhs.d() == rhs.d();
const bool eq_str = Eq(lhs.str(), rhs.str());
const bool eq_ei8 = lhs.ei8() == rhs.ei8();
const bool eq_ei16 = lhs.ei16() == rhs.ei16();
const bool eq_ei32 = lhs.ei32() == rhs.ei32();
const bool eq_ei64 = lhs.ei64() == rhs.ei64();
const bool eq_eu8 = lhs.eu8() == rhs.eu8();
const bool eq_eu16 = lhs.eu16() == rhs.eu16();
const bool eq_eu32 = lhs.eu32() == rhs.eu32();
const bool eq_eu64 = lhs.eu64() == rhs.eu64();
const bool eq_t = Eq(lhs.t(), rhs.t());
return eq_b && eq_i8 && eq_i16 && eq_i32 && eq_i64 && eq_u8 && eq_u16 &&
eq_u32 && eq_u64 && eq_f && eq_d && eq_str && eq_ei8 && eq_ei16 &&
eq_ei32 && eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && eq_t;
}
const internal::DefaultTable* CreateDefaultTable(
flatbuffers::FlatBufferBuilder& fbb) {
auto bool_table_offset = internal::CreateBoolTable(fbb, true);
auto table_offset =
internal::CreateDefaultTableDirect(fbb,
/*b=*/true,
/*i8=*/1,
/*i16=*/2,
/*i32=*/3,
/*i64=*/4,
/*u8=*/5,
/*u16=*/6,
/*u32=*/7,
/*u64=*/8,
/*f=*/9.0,
/*d=*/10.0,
/*str=*/"foo bar baz",
/*ei8=*/internal::ByteEnum_Second,
/*ei16=*/internal::ShortEnum_Second,
/*ei32=*/internal::IntEnum_Second,
/*ei64=*/internal::LongEnum_Second,
/*eu8=*/internal::UByteEnum_Second,
/*eu16=*/internal::UShortEnum_Second,
/*eu32=*/internal::UIntEnum_Second,
/*eu64=*/internal::ULongEnum_Second,
/*t=*/bool_table_offset);
fbb.Finish(table_offset);
return flatbuffers::GetRoot<DefaultTable>(fbb.GetBufferPointer());
}
// TODO: b/430818627 - Remove and replace usages with GenerateNonUniqueValues.
template <typename Domain>
std::vector<typename Domain::corpus_type> GenerateNonUniqueCorpusValues(
Domain domain, int num_seeds = 10, int num_mutations = 100,
const domain_implementor::MutationMetadata& metadata = {},
bool only_shrink = false) {
using CorpusT = typename Domain::corpus_type;
absl::BitGen bitgen;
std::vector<CorpusT> seeds;
seeds.reserve(num_seeds);
while (seeds.size() < num_seeds) {
seeds.push_back(domain.Init(bitgen));
}
std::vector<CorpusT> values = seeds;
for (const auto& seed : seeds) {
CorpusT value = seed;
std::vector<CorpusT> mutations;
mutations.reserve(num_mutations);
while (mutations.size() < num_mutations) {
domain.Mutate(value, bitgen, metadata, only_shrink);
mutations.push_back(value);
}
values.insert(values.end(), mutations.begin(), mutations.end());
}
return values;
};
// TODO: b/430818627 - Remove and replace usages with GenerateInitialValues.
template <typename Domain>
std::vector<typename Domain::corpus_type> GenerateInitialCorpusValues(
Domain domain, int n) {
std::vector<typename Domain::corpus_type> values;
absl::BitGen bitgen;
values.reserve(n);
for (int i = 0; i < n; ++i) {
values.push_back(domain.Init(bitgen));
}
return values;
}
TEST(FlatbuffersEnumDomainImplTest, ExcludedValuesAreNotGenerated) {
const reflection::Schema* schema =
reflection::GetSchema(DefaultTable::BinarySchema::data());
const reflection::Enum* enum_def =
schema->enums()->LookupByKey("fuzztest.internal.ByteEnum");
auto domain =
internal::FlatbuffersEnumDomainImpl<uint8_t>(enum_def).WithExcludedValues(
{internal::ByteEnum_Second});
EXPECT_THAT(
GenerateInitialCorpusValues(
domain, IterationsToHitAll(
internal::ByteEnum_MAX - internal::ByteEnum_MIN,
1.0 / (internal::ByteEnum_MAX - internal::ByteEnum_MIN))),
Each(ResultOf(
[&domain](const auto& corpus) {
return domain.GetValue(corpus) != internal::ByteEnum_Second;
},
IsTrue())));
}
TEST(FlatbuffersEnumDomainImplTest, InvalidEnumValuesAreRejected) {
const reflection::Schema* schema =
reflection::GetSchema(DefaultTable::BinarySchema::data());
const reflection::Enum* enum_def =
schema->enums()->LookupByKey("fuzztest.internal.ByteEnum");
auto domain =
internal::FlatbuffersEnumDomainImpl<uint8_t>(enum_def).WithExcludedValues(
{internal::ByteEnum_First});
{
auto invalid_value =
static_cast<internal::FlatbuffersEnumDomainImpl<uint8_t>::corpus_type>(
internal::ByteEnum_MIN - 1);
EXPECT_THAT(domain.ValidateCorpusValue(invalid_value),
StatusIs(absl::StatusCode::kInvalidArgument));
}
{
auto invalid_value =
static_cast<internal::FlatbuffersEnumDomainImpl<uint8_t>::corpus_type>(
internal::ByteEnum_MAX + 1);
EXPECT_THAT(domain.ValidateCorpusValue(invalid_value),
StatusIs(absl::StatusCode::kInvalidArgument));
}
{
auto invalid_value =
static_cast<internal::FlatbuffersEnumDomainImpl<uint8_t>::corpus_type>(
internal::ByteEnum_First);
EXPECT_THAT(domain.ValidateCorpusValue(invalid_value),
StatusIs(absl::StatusCode::kInvalidArgument));
}
}
TEST(FlatbuffersMetaTest, IsFlatbuffersTable) {
static_assert(internal::is_flatbuffers_table_v<DefaultTable>);
static_assert(!internal::is_flatbuffers_table_v<int>);
static_assert(!internal::is_flatbuffers_table_v<std::optional<bool>>);
}
TEST(FlatbuffersTableDomainImplTest, DefaultTableValueRoundTrip) {
flatbuffers::FlatBufferBuilder fbb;
auto table = CreateDefaultTable(fbb);
auto domain = Arbitrary<const DefaultTable*>();
auto corpus = domain.FromValue(table);
ASSERT_TRUE(corpus.has_value());
ASSERT_OK(domain.ValidateCorpusValue(*corpus));
auto ir = domain.SerializeCorpus(corpus.value());
auto new_corpus = domain.ParseCorpus(ir);
ASSERT_TRUE(new_corpus.has_value());
ASSERT_OK(domain.ValidateCorpusValue(*new_corpus));
auto new_table = domain.GetValue(*new_corpus);
EXPECT_EQ(new_table->b(), true);
EXPECT_EQ(new_table->i8(), 1);
EXPECT_EQ(new_table->i16(), 2);
EXPECT_EQ(new_table->i32(), 3);
EXPECT_EQ(new_table->i64(), 4);
EXPECT_EQ(new_table->u8(), 5);
EXPECT_EQ(new_table->u16(), 6);
EXPECT_EQ(new_table->u32(), 7);
EXPECT_EQ(new_table->u64(), 8);
EXPECT_EQ(new_table->f(), 9.0);
EXPECT_EQ(new_table->d(), 10.0);
EXPECT_EQ(new_table->str()->str(), "foo bar baz");
EXPECT_EQ(new_table->ei8(), internal::ByteEnum_Second);
EXPECT_EQ(new_table->ei16(), internal::ShortEnum_Second);
EXPECT_EQ(new_table->ei32(), internal::IntEnum_Second);
EXPECT_EQ(new_table->ei64(), internal::LongEnum_Second);
EXPECT_EQ(new_table->eu8(), internal::UByteEnum_Second);
EXPECT_EQ(new_table->eu16(), internal::UShortEnum_Second);
EXPECT_EQ(new_table->eu32(), internal::UIntEnum_Second);
EXPECT_EQ(new_table->eu64(), internal::ULongEnum_Second);
ASSERT_THAT(new_table->t(), NotNull());
EXPECT_EQ(new_table->t()->b(), true);
}
TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) {
flatbuffers::FlatBufferBuilder fbb;
auto table = CreateDefaultTable(fbb);
auto domain = Arbitrary<const DefaultTable*>().WithSeeds({table});
EXPECT_THAT(GenerateInitialCorpusValues(domain, IterationsToHitAll(1, 0.5)),
Contains(ResultOf(
[table, &domain](
const typename decltype(domain)::corpus_type& corpus) {
return Eq(domain.GetValue(corpus), table);
},
IsTrue())));
}
TEST(FlatbuffersTableDomainImplTest, CanMutateAnyTableField) {
absl::flat_hash_map<std::string, bool> mutated_fields{
{"b", false}, {"i8", false}, {"i16", false}, {"i32", false},
{"i64", false}, {"u8", false}, {"u16", false}, {"u32", false},
{"u64", false}, {"f", false}, {"d", false}, {"str", false},
{"ei8", false}, {"ei16", false}, {"ei32", false}, {"ei64", false},
{"eu8", false}, {"eu16", false}, {"eu32", false}, {"eu64", false},
{"t", false},
};
auto domain = Arbitrary<const DefaultTable*>();
absl::BitGen bitgen;
for (size_t i = 0; i < IterationsToHitAll(mutated_fields.size(),
1.0 / mutated_fields.size());
++i) {
Value initial_val(domain, bitgen);
Value val(initial_val);
val.Mutate(domain, bitgen, {}, false);
const auto& mut = val.user_value;
const auto& init = initial_val.user_value;
mutated_fields["b"] |= mut->b() != init->b();
mutated_fields["i8"] |= mut->i8() != init->i8();
mutated_fields["i16"] |= mut->i16() != init->i16();
mutated_fields["i32"] |= mut->i32() != init->i32();
mutated_fields["i64"] |= mut->i64() != init->i64();
mutated_fields["u8"] |= mut->u8() != init->u8();
mutated_fields["u16"] |= mut->u16() != init->u16();
mutated_fields["u32"] |= mut->u32() != init->u32();
mutated_fields["u64"] |= mut->u64() != init->u64();
mutated_fields["f"] |= mut->f() != init->f();
mutated_fields["d"] |= mut->d() != init->d();
mutated_fields["str"] |= !Eq(mut->str(), init->str());
mutated_fields["ei8"] |= mut->ei8() != init->ei8();
mutated_fields["ei16"] |= mut->ei16() != init->ei16();
mutated_fields["ei32"] |= mut->ei32() != init->ei32();
mutated_fields["ei64"] |= mut->ei64() != init->ei64();
mutated_fields["eu8"] |= mut->eu8() != init->eu8();
mutated_fields["eu16"] |= mut->eu16() != init->eu16();
mutated_fields["eu32"] |= mut->eu32() != init->eu32();
mutated_fields["eu64"] |= mut->eu64() != init->eu64();
mutated_fields["t"] |= !Eq(mut->t(), init->t());
if (std::all_of(mutated_fields.begin(), mutated_fields.end(),
[](const auto& p) { return p.second; })) {
break;
}
}
EXPECT_THAT(mutated_fields, Each(Pair(_, true)));
}
TEST(FlatbuffersTableDomainImplTest, OptionalTableEventuallyBecomeEmpty) {
flatbuffers::FlatBufferBuilder fbb;
auto bool_table_offset = internal::CreateBoolTable(fbb, true);
auto table_offset =
internal::CreateOptionalTableDirect(fbb,
true, // b
1, // i8
2, // i16
3, // i32
4, // i64
5, // u8
6, // u16
7, // u32
8, // u64
9.0, // f
10.0, // d
"foo bar baz", // str
internal::ByteEnum_Second, // ei8
internal::ShortEnum_Second, // ei16
internal::IntEnum_Second, // ei32
internal::LongEnum_Second, // ei64
internal::UByteEnum_Second, // eu8
internal::UShortEnum_Second, // eu16
internal::UIntEnum_Second, // eu32
internal::ULongEnum_Second, // eu64
bool_table_offset // t
);
fbb.Finish(table_offset);
auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer());
auto domain = Arbitrary<const OptionalTable*>();
Value val(domain, table);
absl::BitGen bitgen;
absl::flat_hash_map<std::string, bool> null_fields{
{"b", false}, {"i8", false}, {"i16", false}, {"i32", false},
{"i64", false}, {"u8", false}, {"u16", false}, {"u32", false},
{"u64", false}, {"f", false}, {"d", false}, {"str", false},
{"ei8", false}, {"ei16", false}, {"ei32", false}, {"ei64", false},
{"eu8", false}, {"eu16", false}, {"eu32", false}, {"eu64", false},
{"t", false},
};
// Optional fields are mutated to null with probability 1/100.
const int iterations =
IterationsToHitAll(null_fields.size(), .01 / null_fields.size());
for (size_t i = 0; i < iterations; ++i) {
val.Mutate(domain, bitgen, /*metadata=*/{}, /*only_shrink=*/true);
const auto& v = val.user_value;
null_fields["b"] |= !v->b().has_value();
null_fields["i8"] |= !v->i8().has_value();
null_fields["i16"] |= !v->i16().has_value();
null_fields["i32"] |= !v->i32().has_value();
null_fields["i64"] |= !v->i64().has_value();
null_fields["u8"] |= !v->u8().has_value();
null_fields["u16"] |= !v->u16().has_value();
null_fields["u32"] |= !v->u32().has_value();
null_fields["u64"] |= !v->u64().has_value();
null_fields["f"] |= !v->f().has_value();
null_fields["d"] |= !v->d().has_value();
null_fields["str"] |= v->str() == nullptr;
null_fields["ei8"] |= !v->ei8().has_value();
null_fields["ei16"] |= !v->ei16().has_value();
null_fields["ei32"] |= !v->ei32().has_value();
null_fields["ei64"] |= !v->ei64().has_value();
null_fields["eu8"] |= !v->eu8().has_value();
null_fields["eu16"] |= !v->eu16().has_value();
null_fields["eu32"] |= !v->eu32().has_value();
null_fields["eu64"] |= !v->eu64().has_value();
null_fields["t"] |= v->t() == nullptr;
if (std::all_of(null_fields.begin(), null_fields.end(),
[](const auto& p) { return p.second; })) {
break;
}
}
EXPECT_THAT(null_fields, Each(Pair(_, true)));
}
TEST(FlatbuffersTableDomainImplTest, RequiredTableFieldsAlwaysSet) {
auto domain = Arbitrary<const RequiredTable*>();
EXPECT_THAT(GenerateNonUniqueCorpusValues(
domain,
/*num_seeds=*/1,
/*num_mutations=*/IterationsToHitAll(1, 1.0 / 100), {},
/*only_shrink=*/true),
Each(ResultOf(
[&](const typename decltype(domain)::corpus_type& corpus) {
auto value = domain.GetValue(corpus);
return value->str() == nullptr && value->t() == nullptr;
},
IsFalse())));
}
TEST(FlatbuffersTableDomainImplTest, Printer) {
flatbuffers::FlatBufferBuilder fbb;
auto table = CreateDefaultTable(fbb);
auto domain = Arbitrary<const DefaultTable*>();
auto corpus = domain.FromValue(table);
ASSERT_TRUE(corpus.has_value());
auto printer = domain.GetPrinter();
std::string out;
printer.PrintCorpusValue(*corpus, &out,
domain_implementor::PrintMode::kHumanReadable);
EXPECT_THAT(out, AllOf(HasSubstr("b: (true)"), // b
HasSubstr("i8: (1)"), // i8
HasSubstr("i16: (2)"), // i16
HasSubstr("i32: (3)"), // i32
HasSubstr("i64: (4)"), // i64
HasSubstr("u8: (5)"), // u8
HasSubstr("u16: (6)"), // u16
HasSubstr("u32: (7)"), // u32
HasSubstr("u64: (8)"), // u64
HasSubstr("f: (9.f)"), // f
HasSubstr("d: (10.)"), // d
HasSubstr("str: (\"foo bar baz\")"), // str
HasSubstr("ei8: (Second)"), // ei8
HasSubstr("ei16: (Second)"), // ei16
HasSubstr("ei32: (Second)"), // ei32
HasSubstr("ei64: (Second)"), // ei64
HasSubstr("eu8: (Second)"), // eu8
HasSubstr("eu16: (Second)"), // eu16
HasSubstr("eu32: (Second)"), // eu32
HasSubstr("eu64: (Second)"), // eu64
HasSubstr("t: ({b: (true)})") // t
));
}
TEST(FlatbuffersTableDomainImplTest, UnsupportedTypesRemainNull) {
absl::flat_hash_map<std::string, bool> null_fields{
{"u", true}, {"s", true}, {"v_b", true}, {"v_i8", true},
{"v_i16", true}, {"v_i32", true}, {"v_i64", true}, {"v_u8", true},
{"v_u16", true}, {"v_u32", true}, {"v_u64", true}, {"v_f", true},
{"v_d", true}, {"v_str", true}, {"v_ei8", true}, {"v_ei16", true},
{"v_ei32", true}, {"v_ei64", true}, {"v_eu8", true}, {"v_eu16", true},
{"v_eu32", true}, {"v_eu64", true}, {"v_t", true}, {"v_u", true},
{"v_s", true}};
auto domain = Arbitrary<const UnsupportedTypesTable*>();
absl::BitGen bitgen;
for (size_t i = 0;
i < IterationsToHitAll(null_fields.size(), 1.0 / null_fields.size());
++i) {
Value val(domain, bitgen);
val.Mutate(domain, bitgen, {}, false);
const auto& mut = val.user_value;
null_fields["u"] &= mut->u() == nullptr;
null_fields["s"] &= mut->s() == nullptr;
null_fields["v_b"] &= mut->v_b() == nullptr;
null_fields["v_i8"] &= mut->v_i8() == nullptr;
null_fields["v_i16"] &= mut->v_i16() == nullptr;
null_fields["v_i32"] &= mut->v_i32() == nullptr;
null_fields["v_i64"] &= mut->v_i64() == nullptr;
null_fields["v_u8"] &= mut->v_u8() == nullptr;
null_fields["v_u16"] &= mut->v_u16() == nullptr;
null_fields["v_u32"] &= mut->v_u32() == nullptr;
null_fields["v_u64"] &= mut->v_u64() == nullptr;
null_fields["v_f"] &= mut->v_f() == nullptr;
null_fields["v_d"] &= mut->v_d() == nullptr;
null_fields["v_str"] &= mut->v_str() == nullptr;
null_fields["v_ei8"] &= mut->v_ei8() == nullptr;
null_fields["v_ei16"] &= mut->v_ei16() == nullptr;
null_fields["v_ei32"] &= mut->v_ei32() == nullptr;
null_fields["v_ei64"] &= mut->v_ei64() == nullptr;
null_fields["v_eu8"] &= mut->v_eu8() == nullptr;
null_fields["v_eu16"] &= mut->v_eu16() == nullptr;
null_fields["v_eu32"] &= mut->v_eu32() == nullptr;
null_fields["v_eu64"] &= mut->v_eu64() == nullptr;
null_fields["v_t"] &= mut->v_t() == nullptr;
null_fields["v_u"] &= mut->v_u() == nullptr;
null_fields["v_s"] &= mut->v_s() == nullptr;
if (std::any_of(null_fields.begin(), null_fields.end(),
[](const auto& p) { return !p.second; })) {
break;
}
}
EXPECT_THAT(null_fields, Each(Pair(_, true)));
}
TEST(FlatbuffersTableDomainImplTest, MutateAlwaysChangesValues) {
auto domain = Arbitrary<const DefaultTable*>();
const reflection::Schema* schema =
reflection::GetSchema(DefaultTable::BinarySchema::data());
const reflection::Object* object =
schema->objects()->LookupByKey(DefaultTable::GetFullyQualifiedName());
absl::BitGen bitgen;
size_t iterations = IterationsToHitAll(object->fields()->size(),
1.0 / object->fields()->size());
typename decltype(domain)::corpus_type corpus = domain.Init(bitgen);
for (size_t i = 0; i < iterations; ++i) {
auto mutated_corpus = corpus;
domain.Mutate(mutated_corpus, bitgen, {}, false);
EXPECT_FALSE(Eq(domain.GetValue(mutated_corpus), domain.GetValue(corpus)));
corpus = mutated_corpus;
}
}
TEST(FlatbuffersTableDomainImplTest, UnsupportedFieldsCountIsZero) {
auto domain = Arbitrary<const UnsupportedTypesTable*>();
auto corpus = domain.Init(absl::BitGen());
EXPECT_EQ(domain.CountNumberOfFields(corpus), 0);
}
TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) {
flatbuffers::FlatBufferBuilder fbb;
auto table_offset = internal::CreateOptionalTableDirect(fbb);
fbb.Finish(table_offset);
auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer());
auto domain = Arbitrary<const OptionalTable*>();
auto corpus = domain.FromValue(table);
ASSERT_TRUE(corpus.has_value());
EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 21);
}
TEST(FlatbuffersTableDomainImplTest, RecursiveTable) {
flatbuffers::FlatBufferBuilder fbb;
flatbuffers::Offset<RecursiveTable> root_offset;
const int kDepth = 10;
for (int i = 0; i < kDepth; ++i) {
auto nested_table_offset =
internal::CreateNestedRecursiveTable(fbb, root_offset);
root_offset = internal::CreateRecursiveTable(fbb, nested_table_offset);
}
fbb.Finish(root_offset);
auto table = flatbuffers::GetRoot<RecursiveTable>(fbb.GetBufferPointer());
auto domain = Arbitrary<const RecursiveTable*>();
auto corpus = domain.FromValue(table);
ASSERT_TRUE(corpus.has_value());
ASSERT_OK(domain.ValidateCorpusValue(corpus.value()));
auto new_table = domain.GetValue(corpus.value());
for (int i = 0; i < kDepth; ++i) {
auto nested_table = new_table->t();
ASSERT_THAT(nested_table, NotNull()) << "Depth: " << i;
new_table = nested_table->t();
}
ASSERT_THAT(new_table, IsNull());
}
} // namespace
} // namespace fuzztest