| // 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 { |
| super::{util::*, *}, |
| crate::fidl::*, |
| anyhow::Error, |
| std::{io, iter}, |
| }; |
| |
| pub struct CppMockBackend<'a, W: io::Write> { |
| w: &'a mut W, |
| } |
| |
| impl<'a, W: io::Write> CppMockBackend<'a, W> { |
| pub fn new(w: &'a mut W) -> Self { |
| CppMockBackend { w } |
| } |
| } |
| |
| fn get_mock_out_param_types(m: &Method, ir: &FidlIr) -> Result<String, Error> { |
| if !m.has_response || m.maybe_response.as_ref().unwrap().is_empty() { |
| Ok("void".to_string()) |
| } else { |
| Ok(format!( |
| "std::tuple<{}>", |
| m.maybe_response |
| .as_ref() |
| .unwrap() |
| .iter() |
| .map(|param| { |
| if let Some(arg_type) = get_base_type_from_alias( |
| ¶m.experimental_maybe_from_type_alias.as_ref().map(|t| &t.name), |
| ) { |
| return format!("{}", arg_type); |
| } |
| match param._type { |
| Type::Str { .. } => "std::string".to_string(), |
| Type::Vector { ref element_type, .. } => format!( |
| "std::vector<{}>", |
| type_to_cpp_str(element_type, false, ir).unwrap() |
| ), |
| _ => type_to_cpp_str(¶m._type, true, ir).unwrap(), |
| } |
| }) |
| .collect::<Vec<_>>() |
| .join(", "), |
| )) |
| } |
| } |
| |
| fn get_mock_params(m: &Method, ir: &FidlIr) -> Result<String, Error> { |
| // If async, put all output parameters last and add callback and cookie. Otherwise put first |
| // output param first, then all input params, then the rest of the output params. |
| let (skip, return_param) = get_first_param(m, ir)?; |
| let has_return_value = skip && !m.maybe_attributes.has("Async"); |
| |
| let mut params = Vec::new(); |
| if has_return_value { |
| params.push(format!( |
| "{} out_{}", |
| return_param, |
| m.maybe_response.as_ref().unwrap()[0].name.0 |
| )); |
| } |
| |
| Ok(params |
| .into_iter() |
| .chain(m.maybe_request.as_ref().unwrap_or(&Vec::new()).iter().map(|param| { |
| let name = &to_c_name(¶m.name.0); |
| if let Some(arg_type) = get_base_type_from_alias( |
| ¶m.experimental_maybe_from_type_alias.as_ref().map(|t| &t.name), |
| ) { |
| return format!("{} {}", arg_type, name); |
| } |
| match param._type { |
| Type::Handle { .. } => { |
| format!("const {}& {}", type_to_cpp_str(¶m._type, true, ir).unwrap(), name) |
| } |
| Type::Str { .. } => format!("std::string {}", name), |
| Type::Vector { ref element_type, .. } => format!( |
| "std::vector<{ty}> {name}", |
| ty = type_to_cpp_str(element_type, false, ir).unwrap(), |
| name = name, |
| ), |
| _ => format!("{} {}", type_to_cpp_str(¶m._type, true, ir).unwrap(), name), |
| } |
| })) |
| .chain( |
| m.maybe_response |
| .as_ref() |
| .unwrap_or(&Vec::new()) |
| .iter() |
| .skip(if has_return_value { 1 } else { 0 }) |
| .map(|param| { |
| let name = &to_c_name(¶m.name.0); |
| if let Some(arg_type) = get_base_type_from_alias( |
| ¶m.experimental_maybe_from_type_alias.as_ref().map(|t| &t.name), |
| ) { |
| return format!("{} out_{}", arg_type, name); |
| } |
| match param._type { |
| Type::Str { .. } => format!("std::string {}", name), |
| Type::Vector { ref element_type, .. } => format!( |
| "std::vector<{ty}> out_{name}", |
| ty = type_to_cpp_str(element_type, false, ir).unwrap(), |
| name = name, |
| ), |
| _ => format!( |
| "{} out_{}", |
| type_to_cpp_str(¶m._type, true, ir).unwrap(), |
| name |
| ), |
| } |
| }), |
| ) |
| .collect::<Vec<_>>() |
| .join(", ")) |
| } |
| |
| fn get_mock_expect_args(m: &Method) -> Result<String, Error> { |
| let mut args = Vec::new(); |
| if m.has_response && !m.maybe_response.as_ref().unwrap().is_empty() { |
| args.push(format!( |
| "{{{}}}", |
| m.maybe_response |
| .as_ref() |
| .unwrap() |
| .iter() |
| .map(|param| { |
| let name = &to_c_name(¶m.name.0); |
| match param._type { |
| Type::Handle { .. } => format!("std::move(out_{})", name), |
| Type::Str { .. } => format!("std::move(out_{})", name), |
| Type::Vector { .. } => format!("std::move(out_{})", name), |
| _ => format!("out_{}", name), |
| } |
| }) |
| .collect::<Vec<_>>() |
| .join(", "), |
| )); |
| } |
| |
| Ok(args |
| .into_iter() |
| .chain(m.maybe_request.as_ref().unwrap_or(&Vec::new()).iter().map(|param| { |
| let name = &to_c_name(¶m.name.0); |
| match param._type { |
| Type::Handle { .. } => format!("{}.get()", name), |
| Type::Str { .. } => format!("std::move({})", name), |
| Type::Vector { .. } => format!("std::move({})", name), |
| _ => name.to_string(), |
| } |
| })) |
| .collect::<Vec<_>>() |
| .join(", ")) |
| } |
| |
| fn get_mock_param_types(m: &Method, ir: &FidlIr) -> Result<String, Error> { |
| Ok(iter::once(get_mock_out_param_types(m, ir)?) |
| .chain(m.maybe_request.as_ref().unwrap_or(&Vec::new()).iter().map(|param| { |
| if let Some(arg_type) = get_base_type_from_alias( |
| ¶m.experimental_maybe_from_type_alias.as_ref().map(|t| &t.name), |
| ) { |
| return format!("{}", arg_type); |
| } |
| match param._type { |
| Type::Str { .. } => "std::string".to_string(), |
| Type::Vector { ref element_type, .. } => { |
| format!("std::vector<{}>", type_to_cpp_str(element_type, false, ir).unwrap()) |
| } |
| _ => type_to_cpp_str(¶m._type, true, ir).unwrap(), |
| } |
| })) |
| .collect::<Vec<_>>() |
| .join(", ")) |
| } |
| |
| impl<'a, W: io::Write> CppMockBackend<'a, W> { |
| fn codegen_mock_accessors(&self, methods: &Vec<Method>, ir: &FidlIr) -> Result<String, Error> { |
| let text = methods |
| .iter() |
| .map(|m| { |
| Ok(format!( |
| " mock_function::MockFunction<{param_types}>& mock_{name}() \ |
| {{ return mock_{name}_; }}", |
| param_types = get_mock_param_types(&m, ir)?, |
| name = to_c_name(&m.name.0).as_str(), |
| )) |
| }) |
| .collect::<Result<Vec<_>, Error>>() |
| .map(|x| x.join("\n"))?; |
| Ok(if text.len() > 0 { "\n".to_string() + &text } else { "".to_string() }) |
| } |
| |
| fn codegen_mock_definitions( |
| &self, |
| methods: &Vec<Method>, |
| ir: &FidlIr, |
| ) -> Result<String, Error> { |
| methods |
| .iter() |
| .map(|m| { |
| Ok(format!( |
| " mock_function::MockFunction<{param_types}> mock_{name}_;", |
| param_types = get_mock_param_types(&m, ir)?, |
| name = to_c_name(&m.name.0).as_str(), |
| )) |
| }) |
| .collect::<Result<Vec<_>, Error>>() |
| .map(|x| x.join("\n")) |
| } |
| |
| fn codegen_mock_expects( |
| &self, |
| name: &str, |
| methods: &Vec<Method>, |
| ir: &FidlIr, |
| ) -> Result<String, Error> { |
| let text = methods |
| .iter() |
| .map(|m| { |
| Ok(format!( |
| include_str!("templates/cpp/mock_expect.h"), |
| protocol_name = to_cpp_name(name), |
| method_name = m.name.0, |
| params = get_mock_params(&m, ir)?, |
| method_name_snake = to_c_name(&m.name.0).as_str(), |
| args = get_mock_expect_args(&m)?, |
| )) |
| }) |
| .collect::<Result<Vec<_>, Error>>() |
| .map(|x| x.join("\n"))?; |
| Ok(if text.len() > 0 { "\n".to_string() + &text } else { "".to_string() }) |
| } |
| |
| fn codegen_mock_verify(&self, methods: &Vec<Method>) -> Result<String, Error> { |
| Ok(methods |
| .iter() |
| .map(|m| format!(" mock_{}_.VerifyAndClear();", to_c_name(&m.name.0))) |
| .collect::<Vec<_>>() |
| .join("\n")) |
| } |
| |
| fn codegen_mock_protocol_out_args(&self, m: &Method, ir: &FidlIr) -> Result<String, Error> { |
| let (skip, _) = get_first_param(m, ir)?; |
| let skip_amt = if skip { 1 } else { 0 }; |
| |
| let mut accum = String::new(); |
| |
| if m.has_response { |
| let response = m.maybe_response.as_ref().unwrap(); |
| for i in skip_amt..response.len() { |
| let name = to_c_name(&response[i].name.0); |
| match &response[i]._type { |
| Type::Handle { .. } => { |
| accum.push_str( |
| format!( |
| " *out_{name} = std::move(std::get<{index}>(ret));\n", |
| name = name, |
| index = i, |
| ) |
| .as_str(), |
| ); |
| } |
| Type::Str { .. } => { |
| accum.push_str( |
| format!( |
| " strncpy(out_{name}, std::get<{index}>(ret).c_str(), \ |
| {name}_capacity));\n", |
| name = name, |
| index = i, |
| ) |
| .as_str(), |
| ); |
| } |
| Type::Vector { .. } => { |
| accum.push_str( |
| format!( |
| " *out_{name}_actual = std::min<size_t>(\ |
| std::get<{index}>(ret).size(), {name}_{size});\n", |
| name = name, |
| size = name_size(&response[i].maybe_attributes), |
| index = i, |
| ) |
| .as_str(), |
| ); |
| accum.push_str( |
| format!( |
| " std::move(std::get<{index}>(ret).begin(), \ |
| std::get<{index}>(ret).begin() + *out_{name}_actual, \ |
| out_{name}_{buffer});\n", |
| name = name, |
| buffer = name_buffer(&response[i].maybe_attributes), |
| index = i, |
| ) |
| .as_str(), |
| ); |
| } |
| _ => { |
| accum.push_str( |
| format!( |
| " *out_{name} = std::get<{index}>(ret);\n", |
| name = name, |
| index = i, |
| ) |
| .as_str(), |
| ); |
| } |
| } |
| } |
| } |
| |
| if skip { |
| accum.push_str(" return std::get<0>(ret);\n"); |
| } |
| |
| Ok(accum) |
| } |
| |
| fn codegen_mock_protocol_async_out_args( |
| &self, |
| m: &Method, |
| ir: &FidlIr, |
| ) -> Result<String, Error> { |
| let mut out_args = Vec::new(); |
| out_args.push("cookie".to_string()); |
| |
| if m.has_response { |
| let response = m.maybe_response.as_ref().unwrap(); |
| for i in 0..response.len() { |
| match &response[i]._type { |
| Type::Handle { .. } => { |
| out_args.push(format!("std::move(std::get<{}>(ret))", i)) |
| } |
| Type::Str { .. } => out_args.push(format!("std::get<{}>(ret).c_str()", i)), |
| Type::Vector { .. } => { |
| out_args.push(format!("std::get<{}>(ret).data()", i)); |
| out_args.push(format!("std::get<{}>(ret).size()", i)); |
| } |
| Type::Identifier { identifier, .. } => { |
| if identifier.is_base_type() { |
| out_args.push(format!("std::get<{}>(ret)", i)) |
| } else { |
| match ir.get_declaration(identifier).unwrap() { |
| Declaration::Struct | Declaration::Union => { |
| out_args.push(format!("&std::get<{}>(ret)", i)) |
| } |
| _ => out_args.push(format!("std::get<{}>(ret)", i)), |
| } |
| } |
| } |
| _ => out_args.push(format!("std::get<{}>(ret)", i)), |
| } |
| } |
| } |
| |
| Ok(format!(" callback({});\n", out_args.join(", "))) |
| } |
| |
| fn codegen_mock_protocol_defs( |
| &self, |
| name: &str, |
| methods: &Vec<Method>, |
| ir: &FidlIr, |
| ) -> Result<String, Error> { |
| let text = methods |
| .iter() |
| .map(|m| { |
| let (out_params, return_param) = get_out_params(&m, name, true, ir)?; |
| let in_params = get_in_params(&m, true, true, ir)?; |
| |
| let params = in_params.into_iter().chain(out_params).collect::<Vec<_>>().join(", "); |
| |
| let mut accum = String::new(); |
| |
| accum.push_str( |
| format!( |
| " virtual {return_param} {protocol_name}{function_name}({params}) {{\n", |
| return_param = return_param, |
| protocol_name = to_cpp_name(name), |
| params = params, |
| function_name = to_cpp_name(&m.name.0), |
| ) |
| .as_str(), |
| ); |
| |
| let in_args = m |
| .maybe_request |
| .as_ref() |
| .unwrap_or(&Vec::new()) |
| .iter() |
| .map(|param| { |
| let name = to_c_name(¶m.name.0); |
| match ¶m._type { |
| Type::Handle { .. } => format!("std::move({})", name), |
| Type::Str { .. } => format!("std::string({})", name), |
| Type::Vector { .. } => { |
| let ty_name = type_to_cpp_str(¶m._type, false, ir).unwrap(); |
| format!( |
| "std::vector<{ty}>({name}_{buffer}, \ |
| {name}_{buffer} + {name}_{size})", |
| ty = ty_name, |
| name = name, |
| buffer = name_buffer(¶m.maybe_attributes), |
| size = name_size(¶m.maybe_attributes), |
| ) |
| } |
| Type::Identifier { identifier, .. } => { |
| if identifier.is_base_type() { |
| name |
| } else { |
| match ir.get_declaration(identifier).unwrap() { |
| Declaration::Interface => { |
| if not_callback(identifier, ir).unwrap() { |
| name |
| } else { |
| format!("*{}", name) |
| } |
| } |
| Declaration::Struct | Declaration::Union => { |
| format!("*{}", name) |
| } |
| _ => name, |
| } |
| } |
| } |
| _ => name, |
| } |
| }) |
| .collect::<Vec<_>>() |
| .join(", "); |
| |
| accum.push_str(" "); |
| |
| if m.has_response && !m.maybe_response.as_ref().unwrap().is_empty() { |
| accum |
| .push_str(format!("{} ret = ", get_mock_out_param_types(&m, ir)?).as_str()); |
| } |
| |
| accum.push_str( |
| format!( |
| "mock_{name}_.Call({args});\n", |
| name = to_c_name(&m.name.0), |
| args = in_args.as_str(), |
| ) |
| .as_str(), |
| ); |
| |
| if m.maybe_attributes.has("Async") { |
| accum.push_str(self.codegen_mock_protocol_async_out_args(&m, ir)?.as_str()); |
| } else { |
| accum.push_str(self.codegen_mock_protocol_out_args(&m, ir)?.as_str()); |
| } |
| |
| accum.push_str(" }"); |
| Ok(accum) |
| }) |
| .collect::<Result<Vec<_>, Error>>() |
| .map(|x| x.join("\n\n"))?; |
| Ok(if text.len() > 0 { "\n".to_string() + &text } else { "".to_string() }) |
| } |
| |
| fn codegen_mock(&self, declarations: &Vec<Decl<'_>>, ir: &FidlIr) -> Result<String, Error> { |
| declarations |
| .iter() |
| .filter_map(filter_protocol) |
| .map(|data| { |
| Ok(format!( |
| include_str!("templates/cpp/mock.h"), |
| protocol_name = to_cpp_name(data.name.get_name()), |
| protocol_name_snake = to_c_name(data.name.get_name()).as_str(), |
| mock_expects = |
| self.codegen_mock_expects(data.name.get_name(), &data.methods, ir)?, |
| mock_verify = self.codegen_mock_verify(&data.methods)?, |
| protocol_definitions = |
| self.codegen_mock_protocol_defs(data.name.get_name(), &data.methods, ir)?, |
| mock_accessors = self.codegen_mock_accessors(&data.methods, ir)?, |
| mock_definitions = self.codegen_mock_definitions(&data.methods, ir)?, |
| )) |
| }) |
| .collect::<Result<Vec<_>, Error>>() |
| .map(|x| x.join("")) |
| } |
| |
| fn codegen_mock_includes( |
| &self, |
| declarations: &Vec<Decl<'_>>, |
| ir: &FidlIr, |
| ) -> Result<String, Error> { |
| let mut need_c_string_header = false; |
| let mut need_cpp_string_header = false; |
| let mut need_cpp_tuple_header = false; |
| let mut need_cpp_vector_header = false; |
| |
| declarations.iter().filter_map(filter_protocol).for_each(|data| { |
| data.methods.iter().for_each(|method| { |
| if method.has_request { |
| method.maybe_request.as_ref().unwrap().iter().for_each(|param| { |
| match param._type { |
| Type::Str { .. } => need_cpp_string_header = true, |
| Type::Vector { .. } => need_cpp_vector_header = true, |
| _ => {} |
| } |
| }); |
| } |
| |
| if method.has_response && !method.maybe_response.as_ref().unwrap().is_empty() { |
| need_cpp_tuple_header = true; |
| method.maybe_response.as_ref().unwrap().iter().for_each(|param| { |
| match param._type { |
| Type::Str { .. } => { |
| need_cpp_string_header = true; |
| if !method.maybe_attributes.has("Async") { |
| need_c_string_header = true; |
| } |
| } |
| Type::Vector { .. } => need_cpp_vector_header = true, |
| _ => {} |
| } |
| }); |
| } |
| }) |
| }); |
| |
| let mut accum = String::new(); |
| if need_c_string_header { |
| accum.push_str("#include <string.h>\n\n"); |
| } |
| if need_cpp_string_header { |
| accum.push_str("#include <string>\n"); |
| } |
| if need_cpp_tuple_header { |
| accum.push_str("#include <tuple>\n"); |
| } |
| if need_cpp_vector_header { |
| accum.push_str("#include <vector>\n"); |
| } |
| if need_cpp_string_header || need_cpp_tuple_header || need_cpp_vector_header { |
| accum.push_str("\n"); |
| } |
| |
| let mut base = vec!["lib/mock-function/mock-function".to_string()]; |
| base.push(ir.name.0.replace('.', "/") + "/cpp/banjo"); |
| let mut includes = base |
| .into_iter() |
| .chain( |
| ir.library_dependencies |
| .iter() |
| .map(|l| &l.name.0) |
| .filter(|n| *n != "zx") |
| .map(|n| n.replace('.', "/") + "/cpp/banjo"), |
| ) |
| .map(|n| format!("#include <{}.h>", n)) |
| .collect::<Vec<_>>(); |
| includes.sort(); |
| accum.push_str(&includes.join("\n")); |
| |
| Ok(accum) |
| } |
| } |
| |
| impl<'a, W: io::Write> Backend<'a, W> for CppMockBackend<'a, W> { |
| fn codegen(&mut self, ir: FidlIr) -> Result<(), Error> { |
| let decl_order = get_declarations(&ir)?; |
| validate_declarations(&decl_order)?; |
| self.w.write_fmt(format_args!( |
| include_str!("templates/cpp/mock_header.h"), |
| includes = self.codegen_mock_includes(&decl_order, &ir)?, |
| namespace = ir.name.0, |
| ))?; |
| self.w.write_fmt(format_args!("{}", self.codegen_mock(&decl_order, &ir)?))?; |
| self.w.write_fmt(format_args!(include_str!("templates/cpp/footer.h")))?; |
| Ok(()) |
| } |
| } |