blob: f87447e2d225f8d15028e7a3c315bdb06f15e267 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <chrono>
#include <map>
#include <random>
#include <string>
#include <unittest/unittest.h>
#include <fidl/flat_ast.h>
#include <fidl/lexer.h>
#include <fidl/parser.h>
#include <fidl/source_file.h>
#include "test_library.h"
#define DECL_NAME(D) \
static_cast<const std::string>(D->name.name_part()).c_str()
#define ASSERT_DECL_NAME(D, N) \
ASSERT_STR_EQ(N, DECL_NAME(D));
namespace {
// The calculated declaration order is a product of both the inter-type dependency relationships,
// and an ordering among the type names. To eliminate the effect of name ordering and exclusively
// test dependency ordering, this utility manufactures random names for the types tested.
class Namer {
public:
Namer() : vars_() {}
std::string mangle(std::string input) {
std::size_t start_pos = 0;
std::size_t max_length = 0;
while ((start_pos = input.find_first_of('#', start_pos)) != std::string::npos) {
std::size_t end_pos = input.find_first_of('#', start_pos + 1);
assert(end_pos != std::string::npos);
std::size_t key_len = end_pos - start_pos;
max_length = std::max(max_length, key_len);
start_pos = end_pos + 1;
}
std::size_t normalize_length = max_length + 5;
while ((start_pos = input.find_first_of('#')) != std::string::npos) {
std::size_t end_pos = input.find_first_of('#', start_pos + 1);
auto key = input.substr(start_pos + 1, end_pos - start_pos - 1);
if (vars_.find(key) == vars_.end()) {
vars_[key] = random_prefix(key, normalize_length);
}
auto replacement = vars_.at(key);
input.replace(start_pos, end_pos - start_pos + 1, replacement);
}
return input;
}
const char* of(const std::string& key) const {
return vars_.at(key).c_str();
}
private:
std::string random_prefix(std::string label, std::size_t up_to) {
// normalize any name to at least |up_to| characters, by adding random prefix
static std::string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static unsigned int seed = static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count());
static std::default_random_engine gen(seed);
static std::uniform_int_distribution<size_t> distribution(0, characters.size() - 1);
if (label.size() < up_to - 1) {
label = "_" + label;
}
while (label.size() < up_to) {
label.insert(0, 1, characters[distribution(gen)]);
}
return label;
}
std::map<std::string, std::string> vars_;
};
constexpr int kRepeatTestCount = 100;
bool nonnullable_ref() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
struct #Request# {
array<#Element#>:4 req;
};
struct #Element# {};
protocol #Protocol# {
SomeMethod(#Request# req);
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(4, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Element"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[2], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
}
END_TEST;
}
bool nullable_ref_breaks_dependency() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
struct #Request# {
array<#Element#?>:4 req;
};
struct #Element# {
#Protocol# prot;
};
protocol #Protocol# {
SomeMethod(#Request# req);
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(4, decl_order.size());
// Since the Element struct contains a Protocol handle, it does not
// have any dependencies, and we therefore have two independent
// declaration sub-graphs:
// a. Element
// b. Request <- SomeLongAnonymousPrefix0 <- Protocol
// Because of random prefixes, either (a) or (b) will be selected to
// be first in the declaration order.
bool element_is_first = strcmp(
DECL_NAME(decl_order[0]), namer.of("Element")) == 0;
if (element_is_first) {
ASSERT_DECL_NAME(decl_order[0], namer.of("Element"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[2], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
} else {
ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[1], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[2], namer.of("Protocol"));
ASSERT_DECL_NAME(decl_order[3], namer.of("Element"));
}
}
END_TEST;
}
bool request_type_breaks_dependency_graph() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
struct #Request# {
request<#Protocol#> req;
};
protocol #Protocol# {
SomeMethod(#Request# req);
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(3, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[1], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[2], namer.of("Protocol"));
}
END_TEST;
}
// A xunion has the same effect dependency-wise, be it nullable or nonnullable.
bool nonnullable_xunion() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
xunion #Xunion# {
request<#Protocol#> req;
#Payload# foo;
};
protocol #Protocol# {
SomeMethod(#Xunion# req);
};
struct #Payload# {
int32 a;
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(4, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Xunion"));
ASSERT_DECL_NAME(decl_order[2], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
}
END_TEST;
}
bool nullable_xunion() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
xunion #Xunion# {
request<#Protocol#> req;
#Payload# foo;
};
protocol #Protocol# {
SomeMethod(#Xunion#? req);
};
struct #Payload# {
int32 a;
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(4, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Xunion"));
ASSERT_DECL_NAME(decl_order[2], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
}
END_TEST;
}
bool nonnullable_xunion_in_struct() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
struct #Payload# {
int32 a;
};
protocol #Protocol# {
SomeMethod(#Request# req);
};
struct #Request# {
#Xunion# xu;
};
xunion #Xunion# {
#Payload# foo;
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(5, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Xunion"));
ASSERT_DECL_NAME(decl_order[2], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[3], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[4], namer.of("Protocol"));
}
END_TEST;
}
bool nullable_xunion_in_struct() {
BEGIN_TEST;
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
struct #Payload# {
int32 a;
};
protocol #Protocol# {
SomeMethod(#Request# req);
};
struct #Request# {
#Xunion#? xu;
};
xunion #Xunion# {
#Payload# foo;
};
)FIDL");
TestLibrary library(source);
ASSERT_TRUE(library.Compile());
auto decl_order = library.declaration_order();
ASSERT_EQ(5, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Xunion"));
ASSERT_DECL_NAME(decl_order[2], namer.of("Request"));
ASSERT_DECL_NAME(decl_order[3], "SomeLongAnonymousPrefix0");
ASSERT_DECL_NAME(decl_order[4], namer.of("Protocol"));
}
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(declaration_order_test)
RUN_TEST(nonnullable_ref)
RUN_TEST(nullable_ref_breaks_dependency)
RUN_TEST(request_type_breaks_dependency_graph)
RUN_TEST(nonnullable_xunion)
RUN_TEST(nullable_xunion)
RUN_TEST(nonnullable_xunion_in_struct)
RUN_TEST(nullable_xunion_in_struct)
END_TEST_CASE(declaration_order_test)