blob: 3a308c9014cbef4554bf927f919a7a43a1b8392d [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 <fidl/flat_ast.h>
#include <fidl/lexer.h>
#include <fidl/names.h>
#include <fidl/parser.h>
#include <fidl/source_file.h>
#include <zircon/assert.h>
#include <algorithm>
#include <chrono>
#include <map>
#include <random>
#include <string>
#include <zxtest/zxtest.h>
#include "error_test.h"
#include "test_library.h"
#define DECL_NAME(D) static_cast<const std::string>(D->name.decl_name()).c_str()
#define ASSERT_DECL_NAME(D, N) ASSERT_STREQ(N, DECL_NAME(D));
#define ASSERT_MANGLED_DECL_NAME(D, N) ASSERT_SUBSTR(DECL_NAME(D), N);
#define ASSERT_DECL_FQ_NAME(D, N) ASSERT_STREQ(N, fidl::NameFlatName(D->name).c_str());
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);
ZX_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(std::string_view key) const { return vars_.find(key)->second.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";
constexpr size_t kSeed = 1337;
static std::default_random_engine gen(kSeed);
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;
}
// Use transparent comparator std::less<> to allow std::string_view lookups.
std::map<std::string, std::string, std::less<>> vars_;
};
constexpr int kRepeatTestCount = 100;
// This test ensures that there are no unused anonymous structs in the
// declaration order output.
TEST(DeclarationOrderTest, GoodNoUnusedAnonymousNames) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
protocol #Protocol# {
Method() -> ();
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(1, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Protocol"));
}
}
TEST(DeclarationOrderTest, GoodNonnullableRef) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.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);
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_MANGLED_DECL_NAME(decl_order[2], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
}
}
TEST(DeclarationOrderTest, GoodNullableRefBreaksDependency) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.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);
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 <- ProtocolSomeMethodRequest <- 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_MANGLED_DECL_NAME(decl_order[2], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
} else {
ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
ASSERT_MANGLED_DECL_NAME(decl_order[1], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[2], namer.of("Protocol"));
ASSERT_DECL_NAME(decl_order[3], namer.of("Element"));
}
}
}
TEST(DeclarationOrderTest, GoodRequestTypeBreaksDependencyGraph) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.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);
auto decl_order = library.declaration_order();
ASSERT_EQ(3, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
ASSERT_MANGLED_DECL_NAME(decl_order[1], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[2], namer.of("Protocol"));
}
}
TEST(DeclarationOrderTest, GoodNonnullableUnion) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Xunion# = resource union {
1: req server_end:#Protocol#;
2: foo #Payload#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Xunion#; });
};
type #Payload# = struct {
a int32;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
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_MANGLED_DECL_NAME(decl_order[2], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
}
}
TEST(DeclarationOrderTest, GoodNullableUnion) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Xunion# = resource union {
1: req server_end:#Protocol#;
2: foo #Payload#;
};
protocol #Protocol# {
SomeMethod(resource struct { req #Xunion#:optional; });
};
type #Payload# = struct {
a int32;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(4, decl_order.size());
// Since the Xunion argument is nullable, Protocol does not have any
// dependencies, and we therefore have two independent declaration
// sub-graphs:
// a. Payload <- Xunion
// b. ProtocolSomeMethodRequest <- Protocol
// Because of random prefixes, either (a) or (b) will be selected to
// be first in the declaration order.
bool payload_is_first = strcmp(DECL_NAME(decl_order[0]), namer.of("Payload")) == 0;
if (payload_is_first) {
ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Xunion"));
ASSERT_MANGLED_DECL_NAME(decl_order[2], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[3], namer.of("Protocol"));
} else {
ASSERT_MANGLED_DECL_NAME(decl_order[0], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[1], namer.of("Protocol"));
ASSERT_DECL_NAME(decl_order[2], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[3], namer.of("Xunion"));
}
}
}
TEST(DeclarationOrderTest, GoodNonnullableUnionInStruct) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Payload# = struct {
a int32;
};
protocol #Protocol# {
SomeMethod(struct { req #Request#; });
};
type #Request# = struct {
xu #Xunion#;
};
type #Xunion# = union {
1: foo #Payload#;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
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_MANGLED_DECL_NAME(decl_order[3], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[4], namer.of("Protocol"));
}
}
TEST(DeclarationOrderTest, GoodNullableUnionInStruct) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Payload# = struct {
a int32;
};
protocol #Protocol# {
SomeMethod(struct { req #Request#; });
};
type #Request# = struct {
xu #Xunion#:optional;
};
type #Xunion# = union {
1: foo #Payload#;
};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(5, decl_order.size());
// Since the Xunion field is nullable, Request does not have any
// dependencies, and we therefore have two independent declaration
// sub-graphs:
// a. Payload <- Xunion
// b. Request <- ProtocolSomeMethodRequest <- Protocol
// Because of random prefixes, either (a) or (b) will be selected to
// be first in the declaration order.
bool payload_is_first = strcmp(DECL_NAME(decl_order[0]), namer.of("Payload")) == 0;
if (payload_is_first) {
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_MANGLED_DECL_NAME(decl_order[3], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[4], namer.of("Protocol"));
} else {
ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
ASSERT_MANGLED_DECL_NAME(decl_order[1], "ProtocolSomeMethodRequest");
ASSERT_DECL_NAME(decl_order[2], namer.of("Protocol"));
ASSERT_DECL_NAME(decl_order[3], namer.of("Payload"));
ASSERT_DECL_NAME(decl_order[4], namer.of("Xunion"));
}
}
}
TEST(DeclarationOrderTest, GoodAllLibrariesOrderSingle) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Foo# = struct {
bar vector<#Bar#>;
};
type #Bar# = struct {};
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
ASSERT_EQ(library.declaration_order(), library.all_libraries_declaration_order());
}
}
TEST(DeclarationOrderTest, GoodAllLibrariesOrderMultiple) {
for (int i = 0; i < kRepeatTestCount; i++) {
SharedAmongstLibraries shared;
TestLibrary dependency(&shared, "dependency.fidl", R"FIDL(
library dependency;
type ExampleDecl1 = struct {};
)FIDL");
ASSERT_COMPILED(dependency);
TestLibrary library(&shared, "example.fidl", R"FIDL(
library example;
using dependency;
type ExampleDecl0 = struct {};
type ExampleDecl2 = struct {};
protocol ExampleDecl1 {
Method(struct { arg dependency.ExampleDecl1; });
};
)FIDL");
ASSERT_COMPILED(library);
auto dependency_decl_order = dependency.declaration_order();
ASSERT_EQ(1, dependency_decl_order.size());
ASSERT_DECL_FQ_NAME(dependency_decl_order[0], "dependency/ExampleDecl1");
auto library_decl_order = library.declaration_order();
ASSERT_EQ(4, library_decl_order.size());
ASSERT_DECL_FQ_NAME(library_decl_order[0], "example/ExampleDecl2");
ASSERT_DECL_FQ_NAME(library_decl_order[1], "example/ExampleDecl1MethodRequest");
ASSERT_DECL_FQ_NAME(library_decl_order[2], "example/ExampleDecl1");
ASSERT_DECL_FQ_NAME(library_decl_order[3], "example/ExampleDecl0");
auto all_decl_order = library.all_libraries_declaration_order();
ASSERT_EQ(5, all_decl_order.size());
ASSERT_DECL_FQ_NAME(all_decl_order[0], "dependency/ExampleDecl1");
ASSERT_DECL_FQ_NAME(all_decl_order[1], "example/ExampleDecl2");
ASSERT_DECL_FQ_NAME(all_decl_order[2], "example/ExampleDecl1MethodRequest");
ASSERT_DECL_FQ_NAME(all_decl_order[3], "example/ExampleDecl1");
ASSERT_DECL_FQ_NAME(all_decl_order[4], "example/ExampleDecl0");
}
}
TEST(DeclarationOrderTest, GoodConstTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
const #Constant# #Alias# = 42;
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(2, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Constant"));
}
}
TEST(DeclarationOrderTest, GoodEnumOrdinalTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Enum# = enum : #Alias# { A = 1; };
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(2, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Enum"));
}
}
TEST(DeclarationOrderTest, GoodBitsOrdinalTypeComesFirst) {
for (int i = 0; i < kRepeatTestCount; i++) {
Namer namer;
auto source = namer.mangle(R"FIDL(
library example;
type #Bits# = bits : #Alias# { A = 1; };
alias #Alias# = uint32;
)FIDL");
TestLibrary library(source);
ASSERT_COMPILED(library);
auto decl_order = library.declaration_order();
ASSERT_EQ(2, decl_order.size());
ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
ASSERT_DECL_NAME(decl_order[1], namer.of("Bits"));
}
}
} // namespace