blob: ab178440611eaa485a7dfac4723ad035fdaf8788 [file] [log] [blame]
// Copyright 2021 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 anyhow::Result;
use std::io::Write;
use crate::test_code::{convert_to_camel, copyright, CodeGenerator, TestCodeBuilder};
const MOCK_FUNC_TEMPLATE: &'static str = include_str!("templates/template_cpp_mock_function");
const TEST_FUNC_TEMPLATE: &'static str = include_str!("templates/template_cpp_test_function");
pub struct CppTestCodeGenerator<'a> {
pub code: &'a CppTestCode,
pub copyright: bool,
}
impl CodeGenerator for CppTestCodeGenerator<'_> {
fn write_file<W: Write>(&self, writer: &mut W) -> Result<()> {
let mut test_fixture = format!(
r#"
class {}Test: public ::gtest::RealLoopFixture {{
"#,
self.code.component_camel_name
);
if self.code.mock_functions.len() > 0 {
test_fixture.push_str(" public:\n");
for mock in &self.code.mock_functions {
test_fixture.push_str(
format!(" std::unique_ptr<{}> {};\n", mock.class_name, mock.handle_name)
.as_str(),
);
}
}
test_fixture.push_str(
"
protected:
std::unique_ptr<component_testing::RealmRoot> CreateRealm() {
",
);
for mock in &self.code.mock_functions {
// Adding code to create the mock handles in CreateRealm().
test_fixture.push_str(
format!(
" {} = std::make_unique<{}>(dispatcher());\n",
mock.handle_name, mock.class_name
)
.as_str(),
);
}
test_fixture.push_str(
r#"
auto realm_builder = component_testing::RealmBuilder::Create();
realm_builder
"#,
);
test_fixture.push_str(&self.code.realm_builder_snippets.join("\n"));
test_fixture.push_str(";\n\n");
test_fixture.push_str(
r#" return std::make_unique<component_testing::RealmRoot>(realm_builder.Build(dispatcher()));
}
};
"#,
);
// Add copyright
if self.copyright {
writer.write_all(&copyright("//").as_bytes())?;
}
// Add import statements
let mut all_imports = self.code.imports.clone();
all_imports.sort();
let mut imports = all_imports.join("\n");
imports.push_str("\n\n");
writer.write_all(&imports.as_bytes())?;
// Add constants, these are components urls
let mut constants = self.code.constants.join("\n");
constants.push_str("\n\n");
writer.write_all(&constants.as_bytes())?;
// Generate Test Fixture class() function
writer.write_all(&test_fixture.as_bytes())?;
// Add mock implementation functions, one per component
for mock in &self.code.mock_functions {
writer.write_all(mock.function_impl.as_bytes())?;
writer.write_all(b"\n")?;
}
// Add testcases, one per protocol
let mut test_cases = self.code.test_case.join("\n\n");
test_cases.push_str("\n");
writer.write_all(&test_cases.as_bytes())?;
Ok(())
}
}
pub struct MockService {
class_name: String,
handle_name: String,
function_impl: String,
}
pub struct CppTestCode {
/// library import strings
pub imports: Vec<String>,
/// constant strings
constants: Vec<String>,
/// RealmBuilder compatibility routing code
pub realm_builder_snippets: Vec<String>,
/// testcase functions
test_case: Vec<String>,
/// stores information on mock implementions
mock_functions: Vec<MockService>,
/// name used by RealmBuilder for the component-under-test
component_under_test: String,
/// CamelCase version of the component name used to name cpp class name
component_camel_name: String,
}
impl TestCodeBuilder for CppTestCode {
fn new(component_name: &str) -> Self {
let component_camel_name = convert_to_camel(component_name);
CppTestCode {
realm_builder_snippets: Vec::new(),
constants: Vec::new(),
imports: Vec::new(),
test_case: Vec::new(),
mock_functions: Vec::new(),
component_under_test: component_name.to_string(),
component_camel_name: component_camel_name,
}
}
fn add_import<'a>(&'a mut self, import_library: &str) -> &'a dyn TestCodeBuilder {
self.imports.push(format!(r#"#include {}"#, import_library));
self
}
fn add_component<'a>(
&'a mut self,
component_name: &str,
url: &str,
const_var: &str,
mock: bool,
) -> &'a dyn TestCodeBuilder {
if mock {
let mock_handle_name = format!("mock_{}", component_name);
self.realm_builder_snippets.push(format!(
r#" .AddLocalChild(
"{}",
&{})"#,
component_name, &mock_handle_name,
));
} else {
self.realm_builder_snippets.push(format!(
r#" .AddChild(
"{}",
{})"#,
component_name, const_var
));
self.constants
.push(format!(r#"constexpr char {}[] = "{}";"#, const_var, url).to_string());
}
self
}
fn add_mock_impl<'a>(
&'a mut self,
component_name: &str,
protocol: &str,
) -> &'a dyn TestCodeBuilder {
let mock_class_name = format!("Mock{}", component_name);
let mock_handle_name = format!("mock_{}", component_name);
self.mock_functions.push(MockService {
function_impl: MOCK_FUNC_TEMPLATE
.replace("CLASS_NAME", &mock_class_name)
.replace("COMPONENT_PROTOCOL_NAME", &protocol),
class_name: mock_class_name,
handle_name: mock_handle_name,
});
self
}
fn add_protocol<'a>(
&'a mut self,
protocol: &str,
source: &str,
targets: Vec<String>,
) -> &'a dyn TestCodeBuilder {
let source_code = match source {
"root" => "component_testing::ParentRef()".to_string(),
"self" => format!("component_testing::ChildRef{{\"{}\"}}", self.component_under_test),
_ => format!("component_testing::ChildRef{{\"{}\"}}", source),
};
let mut targets_code: String = "".to_string();
for i in 0..targets.len() {
let t = &targets[i];
match t.as_str() {
"root" => targets_code.push_str("component_testing::ParentRef(), "),
"self" => targets_code.push_str(
format!("component_testing::ChildRef{{\"{}\"}}, ", self.component_under_test)
.as_str(),
),
_ => targets_code
.push_str(format!("component_testing::ChildRef{{\"{}\"}}, ", t).as_str()),
}
}
self.realm_builder_snippets.push(format!(
r#" .AddRoute(component_testing::Route {{
.capabilities = {{component_testing::Protocol {{"{}"}}}},
.source = {},
.targets = {{{}}}}})"#,
protocol, source_code, targets_code
));
self
}
fn add_directory<'a>(
&'a mut self,
dir_name: &str,
dir_path: &str,
targets: Vec<String>,
) -> &'a dyn TestCodeBuilder {
let mut targets_code: String = "".to_string();
for i in 0..targets.len() {
let t = &targets[i];
match t.as_str() {
"root" => targets_code.push_str("component_testing::ParentRef(), "),
"self" => targets_code.push_str(
format!("component_testing::ChildRef{{\"{}\"}}, ", self.component_under_test)
.as_str(),
),
_ => targets_code
.push_str(format!("component_testing::ChildRef{{\"{}\"}}, ", t).as_str()),
}
}
self.realm_builder_snippets.push(format!(
r#" .AddRoute(component_testing::Route {{
.capabilities = {{component_testing::Directory {{
.name = "{}",
.path = "{}",
.rights = fuchsia::io::RW_STAR_DIR,}}}},
.source = component_testing::ParentRef(),
.targets = {{{}}}}})"#,
dir_name, dir_path, targets_code
));
self
}
fn add_storage<'a>(
&'a mut self,
storage_name: &str,
storage_path: &str,
targets: Vec<String>,
) -> &'a dyn TestCodeBuilder {
let mut targets_code: String = "".to_string();
for i in 0..targets.len() {
let t = &targets[i];
match t.as_str() {
"root" => targets_code.push_str("component_testing::ParentRef(), "),
"self" => targets_code.push_str(
format!("component_testing::ChildRef{{\"{}\"}}, ", self.component_under_test)
.as_str(),
),
_ => targets_code
.push_str(format!("component_testing::ChildRef{{\"{}\"}}, ", t).as_str()),
}
}
self.realm_builder_snippets.push(format!(
r#" .AddRoute(component_testing::Route {{
.capabilities = {{component_testing::Storage {{
.name = "{}",
.path = "{}",}}}},
.source = component_testing::ParentRef(),
.targets = {{{}}}}})"#,
storage_name, storage_path, targets_code
));
self
}
fn add_test_case<'a>(&'a mut self, protocol: &str) -> &'a dyn TestCodeBuilder {
let fields: Vec<&str> = protocol.rsplit("::").collect();
self.test_case.push(
TEST_FUNC_TEMPLATE
.replace("TEST_CLASS_NAME", format!("{}Test", self.component_camel_name).as_str())
.replace("TEST_NAME", fields[0])
.replace("PROTOCOL_TYPE", &protocol),
);
self
}
fn add_fidl_connect<'a>(&'a mut self, _protocol: &str) -> &'a dyn TestCodeBuilder {
// Not implemented
self
}
}