blob: f9f0042ff4c2fd951c2ceaa453a7431af0d882dd [file] [log] [blame] [edit]
// Copyright 2024 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.
use super::problems::ProblemPattern;
use super::problems::StringPattern::*;
use super::test::*;
#[test]
fn protocol_openness() {
assert!(compare_fidl_protocol(
"Foo",
"
@available(replaced=2)
@discoverable
closed protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
@available(added=2)
@discoverable
open protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
"
)
.is_compatible());
assert!(compare_fidl_protocol(
"Foo",
"
@available(replaced=2)
@discoverable
ajar protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
@available(added=2)
@discoverable
open protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
"
)
.is_compatible());
assert!(compare_fidl_protocol(
"Foo",
"
@available(replaced=2)
@discoverable
closed protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
@available(added=2)
@discoverable
ajar protocol Foo {
strict OneWay();
strict TwoWay() -> ();
strict -> Event();
};
"
)
.is_compatible());
}
mod method {
use super::*;
/// Helper to generate protocols with changing methods.
fn protocol(discoverable: &str, change: &str, openness: &str, flexibility: &str) -> String {
let discoverable = if discoverable == "" {
"@discoverable".to_string()
} else {
format!("@discoverable({discoverable})")
};
"
DISCOVERABLE
OPENNESS protocol Foo {
strict Bar() -> ();
@available(CHANGE)
FLEXIBILITY Baz() -> ();
};"
.replace("DISCOVERABLE", &discoverable)
.replace("OPENNESS", openness)
.replace("CHANGE", change)
.replace("FLEXIBILITY", flexibility)
}
#[test]
fn server_platform_add_remove_method() {
// With server=platform it's safe to add and remove methods because the
// platform will support the superset of all stable and unstable levels'
// methods.
for change in vec!["added=2", "added=HEAD", "removed=2", "removed=HEAD"] {
for (openness, flexibility) in
vec![("closed", "strict"), ("open", "strict"), ("open", "flexible")]
{
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol("server=\"platform\"", change, openness, flexibility)
)
.is_compatible());
}
}
}
#[test]
fn server_external_add_method() {
let changes = vec!["added=2", "added=HEAD"];
let discoverable = "server=\"external\"";
// For strict methods this should fail.
for change in &changes {
for (openness, flexibility) in vec![("closed", "strict"), ("open", "strict")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.has_problems(vec![
ProblemPattern::error().message(Begins("Server(@1) missing method"))
]));
}
}
// For flexible methods this should succeed.
for change in changes {
for (openness, flexibility) in vec![("open", "flexible")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
#[test]
fn remove_method() {
// It's always safe to remove methods because the platform will support the
// superset of all stable and unstable levels' methods.
let discoverables = ["", "server=\"platform\"", "server=\"external\""];
for discoverable in discoverables {
for change in vec!["removed=2", "removed=HEAD"] {
for (openness, flexibility) in
vec![("closed", "strict"), ("open", "strict"), ("open", "flexible")]
{
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
}
#[test]
fn server_anywhere_add_method() {
let changes = vec!["added=2", "added=HEAD"];
let discoverable = "name=\"fuchsia.example.Foo\"";
// For strict methods this should fail.
for change in &changes {
for (openness, flexibility) in vec![("closed", "strict"), ("open", "strict")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.has_problems(vec![
ProblemPattern::error().message(Begins("Server(@1) missing method"))
]));
}
}
// For flexible methods this should succeed.
for change in changes {
for (openness, flexibility) in vec![("open", "flexible")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
}
mod event {
use super::*;
/// Helper to generate protocols with changing events.
fn protocol(discoverable: &str, change: &str, openness: &str, flexibility: &str) -> String {
let discoverable = if discoverable == "" {
"@discoverable".to_string()
} else {
format!("@discoverable({discoverable})")
};
"
DISCOVERABLE
OPENNESS protocol Foo {
strict Bar() -> ();
@available(CHANGE)
FLEXIBILITY -> Baz();
};"
.replace("DISCOVERABLE", &discoverable)
.replace("OPENNESS", openness)
.replace("CHANGE", change)
.replace("FLEXIBILITY", flexibility)
}
#[test]
fn server_external_add_remove_event() {
// With server=external it's safe to add and remove protocols because the
// platform will support the superset of all stable and unstable levels'
// protocols.
for change in vec!["added=2", "added=HEAD", "removed=2", "removed=HEAD"] {
for (openness, flexibility) in
vec![("closed", "strict"), ("open", "strict"), ("open", "flexible")]
{
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol("server=\"external\"", change, openness, flexibility)
)
.is_compatible());
}
}
}
#[test]
fn server_platform_add_event() {
let changes = vec!["added=2", "added=HEAD"];
let discoverable = "server=\"platform\"";
// For strict events this should fail.
for change in &changes {
for (openness, flexibility) in vec![("closed", "strict"), ("open", "strict")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.has_problems(vec![
ProblemPattern::error().message(Begins("Client(@1) missing event"))
]));
}
}
// For flexible events this should succeed.
for change in changes {
for (openness, flexibility) in vec![("open", "flexible")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
#[test]
fn remove_event() {
// It's safe to remove events because the platform will support the
// superset of all stable and unstable levels' events.
let discoverables = ["", "server=\"platform\"", "server=\"external\""];
for discoverable in discoverables {
for change in vec!["removed=2", "removed=HEAD"] {
for (openness, flexibility) in
vec![("closed", "strict"), ("open", "strict"), ("open", "flexible")]
{
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
}
#[test]
fn client_anywhere_add_event() {
let changes = vec!["added=2", "added=HEAD"];
let discoverable = "name=\"fuchsia.example.Foo\"";
// For strict clients this should fail.
for change in &changes {
for (openness, flexibility) in vec![("closed", "strict"), ("open", "strict")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.has_problems(vec![
ProblemPattern::error().message(Begins("Client(@1) missing event"))
]));
}
}
// For flexible clients this should succeed.
for change in changes {
for (openness, flexibility) in vec![("open", "flexible")] {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
protocol(discoverable, change, openness, flexibility)
)
.is_compatible());
}
}
}
}
mod protocol {
use super::*;
#[test]
fn add_protocol() {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
r#"
@discoverable
@available(added=2)
protocol Example {};
"#
)
.is_compatible());
}
}
#[test]
fn tear_off() {
let error_message = "Server(@1) missing method fuchsia.compat.test/TearOff.Added";
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
r#"
// Server @1 will be incompatible with clients @PLATFORM
closed protocol TearOff {
@available(added=2)
strict Added();
};
@discoverable(server="external")
protocol ServerExternal {
// Ok - TearOff server in ServerExternal client.
ClientEndInRequest(resource struct { t client_end:TearOff; });
// Error - TearOff server in ServerExternal server.
ClientEndInResponse() -> (resource struct { t client_end:TearOff; });
// Error - TearOff server in ServerExternal server.
ServerEndInRequest(resource struct { t server_end:TearOff; });
// Ok - TearOff server in ServerExternal client.
ServerEndInResponse() -> (resource struct { t server_end:TearOff; });
};
@discoverable(server="platform")
protocol ServerPlatform {
// Error - TearOff server in ServerPlatform server.
ClientEndInRequest(resource struct { t client_end:TearOff; });
// Ok - TearOff server in ServerPlatform client.
ClientEndInResponse() -> (resource struct { t client_end:TearOff; });
// Ok - TearOff server in ServerPlatform client.
ServerEndInRequest(resource struct { t server_end:TearOff; });
// Error - TearOff server in ServerPlatform server.
ServerEndInResponse() -> (resource struct { t server_end:TearOff; });
};
"#
)
.has_problems(vec![
// ServerExternal.ClientEndInResponse
ProblemPattern::error()
.path(Contains("ServerExternal.ClientEndInResponse"))
.message(error_message),
ProblemPattern::error()
.path(Contains("ServerExternal.ClientEndInResponse"))
.message(Contains("Incompatible response")),
// ServerExternal.ServerEndInRequest
ProblemPattern::error()
.path(Contains("ServerExternal.ServerEndInRequest"))
.message(error_message),
ProblemPattern::error()
.path(Contains("ServerExternal.ServerEndInRequest"))
.message(Contains("Incompatible request")),
// ServerPlatform.ClientEndInRequest
ProblemPattern::error()
.path(Contains("ServerPlatform.ClientEndInRequest"))
.message(error_message),
ProblemPattern::error()
.path(Contains("ServerPlatform.ClientEndInRequest"))
.message(Contains("Incompatible request")),
// ServerPlatform.ServerEndInResponse
ProblemPattern::error()
.path(Contains("ServerPlatform.ServerEndInResponse"))
.message(error_message),
ProblemPattern::error()
.path(Contains("ServerPlatform.ServerEndInResponse"))
.message(Contains("Incompatible response")),
]));
}
#[test]
fn discoverable_contradiction() {
assert!(compare_fidl_library(
["1", "1,2,NEXT,HEAD"],
r#"
@discoverable(client="external", server="platform")
protocol ExternalClient {};
@discoverable(client="platform", server="external")
protocol ExternalServer {
TakeServerEnd(resource struct{endpoint server_end:ExternalClient;}); // Bad
TakeClientEnd(resource struct{endpoint client_end:ExternalClient;});
ReturnServerEnd() -> (resource struct{endpoint server_end:ExternalClient;});
ReturnClientEnd() -> (resource struct{endpoint client_end:ExternalClient;}); // Bad
};
"#
)
.has_problems(vec![
ProblemPattern::warning()
.path(Contains("ExternalServer.TakeServerEnd"))
.message(Contains("ExternalClient(@1) used as a external server")),
ProblemPattern::warning()
.path(Contains("ExternalServer.ReturnClientEnd"))
.message(Contains("ExternalClient(@1) used as a external server")),
ProblemPattern::warning()
.path(Contains("ExternalServer.TakeServerEnd"))
.message(Contains("ExternalClient(@1,2,NEXT,HEAD) used as a platform client")),
ProblemPattern::warning()
.path(Contains("ExternalServer.ReturnClientEnd"))
.message(Contains("ExternalClient(@1,2,NEXT,HEAD) used as a platform client")),
]));
}