blob: 1986d31ec6b311f401b0a0b1fcb2ba83c87350df [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 <zircon/assert.h>
#include <random>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tools/fidl/fidlc/src/flat_ast.h"
#include "tools/fidl/fidlc/tests/test_library.h"
namespace fidlc {
namespace {
const int kRepeatTestCount = 10;
struct DeclarationOrderTests : public testing::Test {
public:
// Adds random prefixes to names surrounded by "#" characters. For example,
// "#Foo#" might become "W__Foo". This helps to eliminate the effect of
// lexicographical name ordering and exclusively test dependency ordering.
std::string Mangle(std::string input) {
size_t start = 0;
std::map<std::string, char> letters;
std::uniform_int_distribution<size_t> distribution(0, 25);
while ((start = input.find_first_of('#', start)) != std::string::npos) {
auto end = input.find_first_of('#', start + 1);
auto key = input.substr(start + 1, end - start - 1);
auto [it, inserted] = letters.emplace(key, 0);
if (inserted)
it->second = static_cast<char>('A' + distribution(rng_));
char letter = it->second;
input.replace(start, end - start + 1, std::string(1, letter) + "__" + key);
}
return input;
}
// Given a vector of decls, returns a vector of their unmangled names.
std::vector<std::string_view> Unmangle(const std::vector<const Decl*>& decls) {
std::vector<std::string_view> result;
for (auto& decl : decls) {
auto name = decl->name.decl_name();
auto idx = name.find("__");
if (decl->name.as_anonymous()) {
// Anonymous type names are converted to CamelCase with underscores removed.
ZX_ASSERT_MSG(idx == std::string::npos, "%s", std::string(name).c_str());
result.push_back(name.substr(1));
} else {
ZX_ASSERT_MSG(idx == 1, "%s", std::string(name).c_str());
result.push_back(name.substr(idx + 2));
}
}
return result;
}
private:
static const size_t kSeed = 1337;
std::default_random_engine rng_{kSeed};
};
// Checks if an order (vector of strings) is the union of a set of suborders.
MATCHER_P(IsUnionOf, suborders, "union of suborders " + testing::PrintToString(suborders)) {
std::vector<bool> used(arg.size(), false);
for (auto& suborder : suborders) {
std::optional<std::string_view> prev;
size_t prev_index = 0;
for (auto& name : suborder) {
auto it = std::find(arg.begin(), arg.end(), name);
if (it == arg.end()) {
*result_listener << "'" << name << "' not found";
return false;
}
size_t index = it - arg.begin();
if (used[index]) {
*result_listener << "'" << name << "' used twice";
return false;
}
used[index] = true;
if (prev && index < prev_index) {
*result_listener << "'" << name << "' came before '" << *prev << "'";
return false;
}
prev = name;
prev_index = index;
}
}
for (size_t i = 0; i < arg.size(); i++) {
if (!used[i]) {
*result_listener << "unexpected '" << arg[i] << "'";
return false;
}
}
return true;
}
// This test ensures that there are no unused anonymous structs in the
// declaration order output.
TEST_F(DeclarationOrderTests, GoodNoUnusedAnonymousNames) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
protocol #Protocol# {
strict Method() -> ();
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Protocol"};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodNonnullableRef) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Request# = struct {
req array<#Element#, 4>;
};
type #Element# = struct {};
protocol #Protocol# {
SomeMethod(struct { req #Request#; });
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {
"Element",
"Request",
"ProtocolSomeMethodRequest",
"Protocol",
};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodNullableRefBreaksDependency) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Request# = resource struct {
req array<box<#Element#>, 4>;
};
type #Element# = resource struct {
prot client_end:#Protocol#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Request#; });
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::vector<std::string_view>> expected_suborders = {
{"Element"},
{"Request", "ProtocolSomeMethodRequest", "Protocol"},
};
ASSERT_THAT(Unmangle(library.declaration_order()), IsUnionOf(expected_suborders));
}
}
TEST_F(DeclarationOrderTests, GoodRequestTypeBreaksDependencyGraph) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Request# = resource struct {
req server_end:#Protocol#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Request#; });
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Request", "ProtocolSomeMethodRequest", "Protocol"};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodNonnullableUnion) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Union# = resource union {
1: req server_end:#Protocol#;
2: foo #Payload#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Union#; });
};
type #Payload# = struct {
a int32;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {
"Payload",
"Union",
"ProtocolSomeMethodRequest",
"Protocol",
};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodNullableUnion) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Union# = resource union {
1: req server_end:#Protocol#;
2: foo #Payload#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Union#:optional; });
};
type #Payload# = struct {
a int32;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::vector<std::string_view>> expected_suborders = {
{"Payload", "Union"},
{"ProtocolSomeMethodRequest", "Protocol"},
};
ASSERT_THAT(Unmangle(library.declaration_order()), IsUnionOf(expected_suborders));
}
}
TEST_F(DeclarationOrderTests, GoodNonnullableUnionInStruct) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Payload# = struct {
a int32;
};
protocol #Protocol# {
SomeMethod(struct { req #Request#; });
};
type #Request# = struct {
u #Union#;
};
type #Union# = union {
1: foo #Payload#;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {
"Payload", "Union", "Request", "ProtocolSomeMethodRequest", "Protocol",
};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodNullableUnionInStruct) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Payload# = struct {
a int32;
};
protocol #Protocol# {
SomeMethod(struct { req #Request#; });
};
type #Request# = struct {
u #Union#:optional;
};
type #Union# = union {
1: foo #Payload#;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::vector<std::string_view>> expected_suborders = {
{"Payload", "Union"},
{"Request", "ProtocolSomeMethodRequest", "Protocol"},
};
ASSERT_THAT(Unmangle(library.declaration_order()), IsUnionOf(expected_suborders));
}
}
TEST_F(DeclarationOrderTests, GoodMultipleLibraries) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto sources = Mangle(R"FIDL(
// dependency.fidl
library dependency;
type #Decl1# = struct {};
// example.fidl
library example;
using dependency;
type #Decl0# = struct {};
type #Decl2# = struct {};
protocol #Decl1# {
Method(struct { arg dependency.#Decl1#; });
};
)FIDL");
auto index = sources.find("// example.fidl");
SharedAmongstLibraries shared;
TestLibrary dependency(&shared, "dependency.fidl", sources.substr(0, index));
ASSERT_COMPILED(dependency);
TestLibrary library(&shared, "example.fidl", sources.substr(index));
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Decl1"};
ASSERT_EQ(Unmangle(dependency.declaration_order()), expected);
std::vector<std::vector<std::string_view>> expected_suborders = {
{"Decl0"},
{"Decl2"},
{"Decl1MethodRequest", "Decl1"},
};
ASSERT_THAT(Unmangle(library.declaration_order()), IsUnionOf(expected_suborders));
}
}
TEST_F(DeclarationOrderTests, GoodConstTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
const #Constant# #Alias# = 42;
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Alias", "Constant"};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodEnumOrdinalTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Enum# = enum : #Alias# { A = 1; };
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Alias", "Enum"};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
TEST_F(DeclarationOrderTests, GoodBitsOrdinalTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
auto source = Mangle(R"FIDL(
library example;
type #Bits# = bits : #Alias# { A = 1; };
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
std::vector<std::string_view> expected = {"Alias", "Bits"};
ASSERT_EQ(Unmangle(library.declaration_order()), expected);
}
}
} // namespace
} // namespace fidlc