blob: 39db9fc8b549ef4e4c0af49aa3ee6d470a9a9f7a [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
if(NOT DEFINED CXX_STANDARD)
set(CXX_STANDARD 17)
endif()
separate_arguments(compiler_flags NATIVE_COMMAND "${CXX_FLAGS}")
message(STATUS "Codegen compiler: ${CXX}; flags: ${CXX_FLAGS}; C++${CXX_STANDARD}")
file(READ "${SCHEMA}" schema_text)
file(MAKE_DIRECTORY "${WORK}")
function(expect_rejected name replacement)
set(directory "${WORK}/${name}")
file(MAKE_DIRECTORY "${directory}")
string(REPLACE "(id: 7)" "${replacement}" text "${schema_text}")
file(WRITE "${directory}/echo.fbs" "${text}")
execute_process(COMMAND "${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if("${result}" STREQUAL "0" OR error STREQUAL "")
message(FATAL_ERROR "${name}: invalid schema was accepted or lacked diagnostic")
endif()
if(EXISTS "${directory}/echo.brpc.fb.h" OR EXISTS "${directory}/echo.brpc.fb.cpp")
message(FATAL_ERROR "${name}: invalid schema emitted service files")
endif()
message(STATUS "Rejected ${name}: ${error}")
endfunction()
function(expect_schema_rejected name text diagnostic)
set(directory "${WORK}/${name}")
file(REMOVE_RECURSE "${directory}")
file(MAKE_DIRECTORY "${directory}")
file(WRITE "${directory}/echo.fbs" "${text}")
execute_process(COMMAND "${FLATC}" --cpp -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE flatc_result ERROR_VARIABLE flatc_error)
if(NOT "${flatc_result}" STREQUAL "0")
message(FATAL_ERROR "${name}: not a valid official flatc input: ${flatc_error}")
endif()
execute_process(COMMAND "${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
string(FIND "${error}" "${diagnostic}" diagnostic_position)
if("${result}" STREQUAL "0" OR diagnostic_position LESS 0)
message(FATAL_ERROR "${name}: schema was accepted or lacked diagnostic: ${error}")
endif()
if(EXISTS "${directory}/echo.brpc.fb.h" OR EXISTS "${directory}/echo.brpc.fb.cpp")
message(FATAL_ERROR "${name}: rejected schema emitted service files")
endif()
file(WRITE "${directory}/echo.brpc.fb.h" "previous header\n")
file(WRITE "${directory}/echo.brpc.fb.cpp" "previous source\n")
execute_process(COMMAND "${GENERATOR}" -o "${directory}"
"${directory}/echo.fbs" RESULT_VARIABLE result ERROR_VARIABLE error)
string(FIND "${error}" "${diagnostic}" diagnostic_position)
file(READ "${directory}/echo.brpc.fb.h" old_header)
file(READ "${directory}/echo.brpc.fb.cpp" old_source)
if("${result}" STREQUAL "0" OR diagnostic_position LESS 0 OR
NOT "${old_header}" STREQUAL "previous header\n" OR
NOT "${old_source}" STREQUAL "previous source\n")
message(FATAL_ERROR "${name}: rejection changed existing output files")
endif()
message(STATUS "Rejected ${name} without changing outputs: ${error}")
endfunction()
function(prepare_output_pair directory)
file(REMOVE_RECURSE "${directory}")
file(MAKE_DIRECTORY "${directory}")
file(WRITE "${directory}/echo.fbs" "${schema_text}")
file(WRITE "${directory}/echo.brpc.fb.h" "previous header\n")
file(WRITE "${directory}/echo.brpc.fb.cpp" "previous source\n")
endfunction()
function(expect_output_pair_restored name failure)
set(directory "${WORK}/${name}")
prepare_output_pair("${directory}")
execute_process(COMMAND "${CMAKE_COMMAND}" -E env
"BRPC_FLATC_TEST_FAILURE=${failure}"
"${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if("${result}" STREQUAL "0" OR error STREQUAL "")
message(FATAL_ERROR "${name}: injected failure was not reported")
endif()
file(READ "${directory}/echo.brpc.fb.h" current_header)
file(READ "${directory}/echo.brpc.fb.cpp" current_source)
if(NOT current_header STREQUAL "previous header\n" OR
NOT current_source STREQUAL "previous source\n")
message(FATAL_ERROR "${name}: previous output pair was not restored")
endif()
file(GLOB transaction_files "${directory}/echo.brpc.fb.*.tmp"
"${directory}/echo.brpc.fb.*.bak")
if(transaction_files)
message(FATAL_ERROR "${name}: transaction files remained after rollback")
endif()
message(STATUS "Restored output pair after ${failure}: ${error}")
endfunction()
function(expect_output_pair_removed name failure remaining_backup)
set(directory "${WORK}/${name}")
prepare_output_pair("${directory}")
execute_process(COMMAND "${CMAKE_COMMAND}" -E env
"BRPC_FLATC_TEST_FAILURE=${failure}"
"${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if("${result}" STREQUAL "0" OR error STREQUAL "")
message(FATAL_ERROR "${name}: injected restore failure was not reported")
endif()
if(EXISTS "${directory}/echo.brpc.fb.h" OR EXISTS "${directory}/echo.brpc.fb.cpp")
message(FATAL_ERROR "${name}: partial final output remained")
endif()
if(NOT EXISTS "${directory}/echo.brpc.fb.${remaining_backup}.bak")
message(FATAL_ERROR "${name}: recoverable backup was not retained")
endif()
file(GLOB temporary_outputs "${directory}/echo.brpc.fb.*.tmp")
if(temporary_outputs)
message(FATAL_ERROR "${name}: temporary files remained")
endif()
message(STATUS "Removed final pair after ${failure}: ${error}")
endfunction()
function(expect_concurrent_output_pair)
set(directory "${WORK}/concurrent_output_pair")
set(output "${directory}/output")
file(REMOVE_RECURSE "${directory}")
file(MAKE_DIRECTORY "${directory}/first" "${directory}/second" "${output}")
set(first_schema
"namespace codegen.concurrent;\ntable Request {}\ntable Response {}\nrpc_service Echo { First(Request):Response (id: 1); }\n")
set(second_schema
"namespace codegen.concurrent;\ntable Request {}\ntable Response {}\nrpc_service Echo { Second(Request):Response (id: 2); }\n")
file(WRITE "${directory}/first/echo.fbs" "${first_schema}")
file(WRITE "${directory}/second/echo.fbs" "${second_schema}")
file(WRITE "${directory}/run.sh"
"#!/bin/sh\n\"${GENERATOR}\" -o \"${output}\" \"${directory}/first/echo.fbs\" &\nfirst=\$!\n\"${GENERATOR}\" -o \"${output}\" \"${directory}/second/echo.fbs\" &\nsecond=\$!\nwait \$first\nfirst_result=\$?\nwait \$second\nsecond_result=\$?\ntest \$first_result -eq 0 -a \$second_result -eq 0\n")
execute_process(COMMAND /bin/sh "${directory}/run.sh"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "concurrent_output_pair: generation failed: ${error}")
endif()
execute_process(COMMAND "${FLATC}" --cpp -o "${output}"
"${directory}/first/echo.fbs" RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "concurrent_output_pair: official flatc failed: ${error}")
endif()
set(includes "-I${output}")
foreach(include_dir IN LISTS INCLUDE_DIRS)
list(APPEND includes "-I${include_dir}")
endforeach()
execute_process(COMMAND "${CXX}" ${compiler_flags} "-std=c++${CXX_STANDARD}" ${includes}
-c "${output}/echo.brpc.fb.cpp" -o "${directory}/echo.o"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "concurrent_output_pair: mixed generated files: ${error}")
endif()
file(GLOB transaction_files "${output}/echo.brpc.fb.*.tmp"
"${output}/echo.brpc.fb.*.bak")
if(transaction_files)
message(FATAL_ERROR "concurrent_output_pair: transaction files remained")
endif()
message(STATUS "Serialized concurrent output publication")
endfunction()
function(expect_rejected_rpc_name name)
set(directory "${WORK}/reserved_${name}")
file(MAKE_DIRECTORY "${directory}")
file(REMOVE "${directory}/echo.brpc.fb.h" "${directory}/echo.brpc.fb.cpp")
string(REPLACE "Inspect(" "${name}(" text "${schema_text}")
file(WRITE "${directory}/echo.fbs" "${text}")
execute_process(COMMAND "${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if("${result}" STREQUAL "0")
message(SEND_ERROR "RPC ${name}: generator incorrectly accepted a fixed Stub field name")
return()
endif()
if(NOT error MATCHES "method name collides with generated API: ${name}")
message(FATAL_ERROR "RPC ${name}: expected a name-collision diagnostic: ${error}")
endif()
if(EXISTS "${directory}/echo.brpc.fb.h" OR EXISTS "${directory}/echo.brpc.fb.cpp")
message(FATAL_ERROR "RPC ${name}: rejected schema emitted service files")
endif()
message(STATUS "Rejected RPC ${name}: ${error}")
endfunction()
function(expect_compiles name text)
set(standard "${CXX_STANDARD}")
if(ARGC GREATER 2)
set(standard "${ARGV2}")
endif()
set(directory "${WORK}/${name}")
file(MAKE_DIRECTORY "${directory}")
file(WRITE "${directory}/echo.fbs" "${text}")
execute_process(COMMAND "${FLATC}" --cpp -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: official flatc failed: ${error}")
endif()
execute_process(COMMAND "${GENERATOR}" -o "${directory}" "${directory}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: brpc_flatc failed: ${error}")
endif()
set(includes "-I${directory}")
set(diagnostics)
foreach(include_dir IN LISTS INCLUDE_DIRS)
if(ARGC GREATER 3)
list(FIND IMPLICIT_INCLUDE_DIRS "${include_dir}" implicit_index)
if(implicit_index GREATER_EQUAL 0)
continue()
endif()
list(APPEND includes -isystem "${include_dir}")
else()
list(APPEND includes "-I${include_dir}")
endif()
endforeach()
if(ARGC GREATER 3)
set(diagnostics ${ARGV3})
endif()
execute_process(COMMAND "${CXX}" ${compiler_flags} ${diagnostics} "-std=c++${standard}" ${includes}
-c "${directory}/echo.brpc.fb.cpp" -o "${directory}/echo.o"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: generated code did not compile: ${error}")
endif()
# The generated header must also compile without incidental prior includes.
file(WRITE "${directory}/header.cpp" "#include \"echo.brpc.fb.h\"\n")
execute_process(COMMAND "${CXX}" ${compiler_flags} ${diagnostics} "-std=c++${standard}" ${includes}
-c "${directory}/header.cpp" -o "${directory}/header.o"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: generated header is not self-contained: ${error}")
endif()
foreach(suffix brpc.fb.h brpc.fb.cpp)
file(READ "${directory}/echo.${suffix}" generated)
if(NOT generated MATCHES "Licensed to the Apache Software Foundation")
message(FATAL_ERROR "${name}: ${suffix} lacks Apache license")
endif()
if(generated MATCHES "FLATBUFFERS_VERSION")
message(FATAL_ERROR "${name}: service output pins FlatBuffers version")
endif()
endforeach()
message(STATUS "Compiled ${name}")
endfunction()
function(expect_distinct_headers name first_stem second_stem)
set(directory "${WORK}/header_guards_${name}")
set(includes)
foreach(include_dir IN LISTS INCLUDE_DIRS)
list(APPEND includes "-I${include_dir}")
endforeach()
foreach(side first second)
set(stem "${${side}_stem}")
set(output "${directory}/${side}")
file(MAKE_DIRECTORY "${output}")
string(REPLACE "namespace codegen.example;" "namespace guard_${name}.${side};"
text "${schema_text}")
# Isolate the fixture's global helper table as well as its services.
string(PREPEND text "namespace guard_${name}.${side};\n")
if(side STREQUAL "first")
set(first_text "${text}")
endif()
file(WRITE "${output}/${stem}.fbs" "${text}")
execute_process(COMMAND "${FLATC}" --cpp -o "${output}" "${output}/${stem}.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}/${side}: official flatc failed: ${error}")
endif()
execute_process(COMMAND "${GENERATOR}" -o "${output}" "${output}/${stem}.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}/${side}: brpc_flatc failed: ${error}")
endif()
file(WRITE "${output}/single.cpp"
"#include \"${stem}.brpc.fb.h\"\n::guard_${name}::${side}::Echo* service = nullptr;\n")
execute_process(COMMAND "${CXX}" ${compiler_flags} "-std=c++${CXX_STANDARD}" ${includes}
-c "${output}/single.cpp" -o "${output}/single.o"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}/${side}: standalone header failed: ${error}")
endif()
endforeach()
foreach(reverse FALSE TRUE)
set(first "#include \"first/${first_stem}.brpc.fb.h\"\n")
set(second "#include \"second/${second_stem}.brpc.fb.h\"\n")
if(reverse)
set(headers "${second}${first}")
else()
set(headers "${first}${second}")
endif()
file(WRITE "${directory}/combined_${reverse}.cpp"
"${headers}::guard_${name}::first::Echo* first_echo = nullptr;\n::guard_${name}::second::Echo* second_echo = nullptr;\n")
execute_process(COMMAND "${CXX}" ${compiler_flags} "-std=c++${CXX_STANDARD}" ${includes}
-c "${directory}/combined_${reverse}.cpp"
-o "${directory}/combined_${reverse}.o"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: combined headers (reverse=${reverse}) failed: ${error}")
endif()
endforeach()
# A checkout/output directory change must not alter generated identifiers.
set(relocated "${directory}/relocated")
file(MAKE_DIRECTORY "${relocated}")
file(WRITE "${relocated}/${first_stem}.fbs" "${first_text}")
execute_process(COMMAND "${GENERATOR}" -o . "${first_stem}.fbs"
WORKING_DIRECTORY "${relocated}"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${name}: relocated generation failed: ${error}")
endif()
foreach(suffix brpc.fb.h brpc.fb.cpp)
file(READ "${directory}/first/${first_stem}.${suffix}" original)
file(READ "${relocated}/${first_stem}.${suffix}" regenerated)
if(NOT "${original}" STREQUAL "${regenerated}")
message(FATAL_ERROR "${name}: ${suffix} depends on checkout/output paths")
endif()
endforeach()
message(STATUS "Distinct, relocatable header guards: ${name}")
endfunction()
# Exercise escaped table names as RPC types, not only collision candidates.
foreach(type_name module import synchronized atomic_cancel atomic_commit atomic_noexcept)
set(type_schema "table ${type_name} { value:int; }\ntable Response {}\n")
set(service_schema "rpc_service Echo { Call(${type_name}):Response (id: 1); Reverse(Response):${type_name} (id: 2); }\n")
expect_compiles(escaped_rpc_global_${type_name}
"${type_schema}${service_schema}")
expect_compiles(escaped_rpc_nested_${type_name}
"namespace api.module;\n${type_schema}${service_schema}")
set(imported_name escaped_rpc_imported_${type_name})
set(imported_dir "${WORK}/${imported_name}")
file(MAKE_DIRECTORY "${imported_dir}")
file(WRITE "${imported_dir}/types.fbs" "namespace payload;\n${type_schema}")
execute_process(COMMAND "${FLATC}" --cpp -o "${imported_dir}"
"${imported_dir}/types.fbs" RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "${imported_name}: official flatc failed (${result}): ${error}")
endif()
expect_compiles(${imported_name}
"include \"types.fbs\"; namespace api; rpc_service Echo { Call(payload.${type_name}):payload.Response (id: 1); Reverse(payload.Response):payload.${type_name} (id: 2); }")
endforeach()
expect_compiles(escaped_rpc_near_name [=[
namespace api;
table module_ { value:int; }
rpc_service Echo { Call(module_):module_ (id: 1); }
]=])
expect_compiles(escaped_type_preserves_service_name [=[
namespace api;
table module { value:int; }
rpc_service module { Call(module):module (id: 1); }
]=])
# Exercise the names emitted by official flatc, not only schema type names.
foreach(scope "" "api.")
if(scope STREQUAL "")
set(namespace_decl "")
set(qualified "::")
set(scope_id global)
else()
set(namespace_decl "namespace api;\n")
set(qualified "::api::")
set(scope_id nested)
endif()
set(tables "table Request {}\ntable Response {}\n")
foreach(kind enum_value enum_min enum_any enum_values enum_names enum_name
union_traits union_verify union_vector table_create table_direct)
if(kind MATCHES "^enum_")
set(definitions "enum Collision: byte { Echo_Stub = 0 }\n")
if(kind STREQUAL enum_value)
set(service Collision_Echo)
set(symbol Collision_Echo_Stub)
elseif(kind STREQUAL enum_min)
set(service Collision_MIN)
set(symbol Collision_MIN)
elseif(kind STREQUAL enum_any)
set(definitions "enum Collision: byte (bit_flags) { Flag = 0 }\n")
set(service Collision_ANY)
set(symbol Collision_ANY)
elseif(kind STREQUAL enum_values)
set(service EnumValuesCollision)
set(symbol EnumValuesCollision)
elseif(kind STREQUAL enum_names)
set(service EnumNamesCollision)
set(symbol EnumNamesCollision)
else()
set(service EnumNameCollision)
set(symbol EnumNameCollision)
endif()
elseif(kind MATCHES "^union_")
set(definitions "union Collision { Request }\n")
if(kind STREQUAL union_traits)
set(service CollisionTraits)
elseif(kind STREQUAL union_verify)
set(service VerifyCollision)
else()
set(service VerifyCollisionVector)
endif()
set(symbol "${service}")
elseif(kind STREQUAL table_create)
set(definitions "table Echo_Stub {}\n")
set(service CreateEcho)
set(symbol CreateEcho_Stub)
else()
set(definitions "table Echo { text:string; }\n")
set(service CreateEchoDirect)
set(symbol CreateEchoDirect)
endif()
expect_schema_rejected(${scope_id}_${kind}
"${namespace_decl}${tables}${definitions}rpc_service ${service} { Call(Request):Response (id: 1); }\n"
"generated service class name collides: ${qualified}${symbol}")
endforeach()
endforeach()
# Pair each root/helper declaration with actual flatc output and legal names.
foreach(symbol GetRoot_Stub GetSizePrefixedRoot_Stub VerifyRoot_StubBuffer
VerifySizePrefixedRoot_StubBuffer FinishRoot_StubBuffer
FinishSizePrefixedRoot_StubBuffer Root_StubIdentifier
Root_StubBufferHasIdentifier SizePrefixedRoot_StubBufferHasIdentifier
Root_StubExtension)
expect_schema_rejected(root_helper_${symbol}
"table Request {} table Response {} table Root_Stub {} root_type Root_Stub; file_identifier \"TEST\"; file_extension \"bin\"; rpc_service ${symbol} { Call(Request):Response (id: 1); }"
"generated service class name collides: ::${symbol}")
endforeach()
file(WRITE "${WORK}/root_types.fbs"
"namespace data; table Request {} table Response {} table Root_Stub {} root_type Root_Stub; file_identifier \"TEST\"; file_extension \"bin\";")
expect_schema_rejected(imported_root_helper
"include \"../root_types.fbs\"; namespace data; rpc_service GetRoot { Call(Request):Response (id: 1); }"
"generated service class name collides: ::data::GetRoot_Stub")
expect_schema_rejected(escaped_table_builder
"table Request {} table Response {} table class {} rpc_service class_Builder { Call(Request):Response (id: 1); }"
"generated service class name collides: ::class_Builder")
expect_compiles(unescaped_table_builder_available
"table Request {} table Response {} table class {} rpc_service classBuilder { Call(Request):Response (id: 1); }")
expect_compiles(escaped_enum_value_available
"table Request {} table Response {} enum E:byte { module = 0 } rpc_service E_module { Call(Request):Response (id: 1); }")
expect_compiles(sparse_enum_no_names_table
"table Request {} table Response {} enum E:int { A = 0, B = 10 } rpc_service EnumNamesE { Call(Request):Response (id: 1); }")
expect_schema_rejected(dense_enum_names_table
"table Request {} table Response {} enum E:int { A = 0, B = 9 } rpc_service EnumNamesE { Call(Request):Response (id: 1); }"
"generated service class name collides: ::EnumNamesE")
expect_compiles(deprecated_string_no_direct
"table Request {} table Response {} table Echo { text:string (deprecated); } rpc_service CreateEchoDirect { Call(Request):Response (id: 1); }")
expect_compiles(union_alias_no_traits
"table Request {} table Response {} union E { A:Request, B:Request } rpc_service ETraits { Call(Request):Response (id: 1); }")
expect_compiles(nonroot_helper_available
"table Request {} table Response {} table Root_Stub {} rpc_service GetRoot { Call(Request):Response (id: 1); }")
foreach(name __Echo Echo__Name _Echo _echo Echo_ channel_ owned_channel_)
expect_schema_rejected(reserved_service_${name}
"table Request {} table Response {} rpc_service ${name} { Call(Request):Response (id: 1); }"
"reserved C++ identifier")
endforeach()
foreach(name __Call _Call Call__Name)
expect_schema_rejected(reserved_method_${name}
"table Request {} table Response {} rpc_service Echo { ${name}(Request):Response (id: 1); }"
"reserved C++ identifier")
endforeach()
foreach(name __api _Api _api _)
expect_schema_rejected(reserved_namespace_${name}
"namespace ${name}; table Request {} table Response {} rpc_service Echo { Call(Request):Response (id: 1); }"
"reserved C++ identifier")
endforeach()
expect_compiles(global_helper_table [=[
table BrpcFlatbuffersFail { value:int; }
table Request {} table Response {}
rpc_service Echo { Call(Request):Response (id: 1); }
]=])
expect_compiles(global_helper_service [=[
table Request {} table Response {}
rpc_service BrpcFlatbuffersFail { Call(Request):Response (id: 1); }
]=])
expect_compiles(legal_underscore_names [=[
namespace api._internal;
table Request {} table Response {}
rpc_service _echo { _call(Request):Response (id: 1); call_(Request):Response (id: 2); }
]=])
if(CXX_ID MATCHES "Clang")
# Integer-only tables avoid unrelated reserved locals in upstream flatc.
expect_compiles(strict_generated_identifiers
"namespace api; table Request { value:int; } table Response { value:int; } rpc_service Echo { Call(Request):Response (id: 1); }"
"${CXX_STANDARD}" "-Wreserved-identifier;-Werror=reserved-identifier")
else()
message(STATUS "Reserved-name rejection tested; strict diagnostics require Clang")
endif()
expect_compiles(absent_direct_helper [=[
namespace api;
table Request {} table Response {} table Echo { value:int; }
rpc_service CreateEchoDirect { Call(Request):Response (id: 1); }
]=])
expect_compiles(cross_namespace_flatc_symbols [=[
namespace data;
table Request {} table Response {} enum Collision:byte { Echo_Stub = 0 }
namespace api;
rpc_service Collision_Echo { Call(data.Request):data.Response (id: 1); }
]=])
foreach(name Stub descriptor GetDescriptor FBCallMethod)
string(CONCAT reserved_service_schema
"table Request {}\ntable Response {}\n"
"rpc_service ${name} { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(service_member_${name} "${reserved_service_schema}"
"service name collides with generated API: ${name}")
expect_schema_rejected(nested_service_member_${name}
"namespace api;\n${reserved_service_schema}"
"service name collides with generated API: ${name}")
endforeach()
foreach(name Echo Echo_Stub)
foreach(depth "" ".nested")
string(CONCAT namespace_collision_schema
"namespace api.${name}${depth};\n"
"table Request {}\ntable Response {}\n"
"namespace api;\n"
"rpc_service Echo {\n"
" Call(api.${name}${depth}.Request):"
"api.${name}${depth}.Response (id: 1);\n}\n")
expect_schema_rejected(namespace_collision_${name}${depth}
"${namespace_collision_schema}"
"generated service class name collides: ::api::${name}")
endforeach()
endforeach()
file(WRITE "${WORK}/namespace_types.fbs"
"namespace api.Echo; table Request {} table Response {}\n")
expect_schema_rejected(imported_namespace_collision
"include \"../namespace_types.fbs\"; namespace api;
rpc_service Echo {
Call(api.Echo.Request):api.Echo.Response (id: 1);
}"
"generated service class name collides: ::api::Echo")
expect_distinct_headers(punctuation foo-bar foo_bar)
expect_distinct_headers(letter_case FooBar foobar)
expect_distinct_headers(same_basename echo echo)
expect_rejected_rpc_name(channel_)
expect_rejected_rpc_name(owned_channel_)
string(REPLACE "rpc_service Echo {" "rpc_service char8_t {"
char8_t_schema "${schema_text}")
expect_schema_rejected(cpp20_char8_t "${char8_t_schema}"
"C++ keyword is not supported: char8_t")
foreach(name brpc butil flatbuffers google std)
set(global_namespace_collision_schema
"table Request {}\ntable Response {}\nrpc_service ${name} { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(global_namespace_${name}
"${global_namespace_collision_schema}"
"generated service class name collides: ::${name}")
endforeach()
set(service_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\nrpc_service Echo { Call(Request):Response (id: 1); }\nrpc_service Echo_Stub { Call(Request):Response (id: 2); }\n")
expect_schema_rejected(service_stub_collision "${service_collision_schema}"
"generated service class name collides: ::codegen::collision::Echo_Stub")
set(stub_service_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\nrpc_service Echo_Stub { Call(Request):Response (id: 2); }\nrpc_service Echo { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(stub_service_collision "${stub_service_collision_schema}"
"generated service class name collides: ::codegen::collision::Echo_Stub")
set(table_service_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\ntable Echo {}\nrpc_service Echo { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(table_service_collision "${table_service_collision_schema}"
"generated service class name collides: ::codegen::collision::Echo")
set(table_stub_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\ntable Echo_Stub {}\nrpc_service Echo { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(table_stub_collision "${table_stub_collision_schema}"
"generated service class name collides: ::codegen::collision::Echo_Stub")
set(table_builder_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\ntable Echo {}\nrpc_service EchoBuilder { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(table_builder_collision "${table_builder_collision_schema}"
"generated service class name collides: ::codegen::collision::EchoBuilder")
set(enum_service_collision_schema
"namespace codegen.collision;\ntable Request {}\ntable Response {}\nenum Echo : byte { Value = 0 }\nrpc_service Echo { Call(Request):Response (id: 1); }\n")
expect_schema_rejected(enum_service_collision "${enum_service_collision_schema}"
"generated service class name collides: ::codegen::collision::Echo")
expect_output_pair_restored(stage_source_failure stage_source)
expect_output_pair_restored(preserve_source_failure preserve_source)
expect_output_pair_restored(publish_header_failure publish_header)
expect_output_pair_restored(publish_source_failure publish_source)
expect_output_pair_removed(restore_header_failure
"publish_source,restore_header" h)
expect_output_pair_removed(restore_source_failure
"publish_source,restore_source" cpp)
expect_concurrent_output_pair()
expect_rejected(missing_id "")
expect_rejected(duplicate_id "(id: 41)")
expect_rejected(negative_id "(id: -1)")
expect_rejected(overflow_id "(id: 2147483648)")
expect_rejected(string_id "(id: \"7\")")
expect_rejected(streaming "(id: 7, streaming: \"server\")")
expect_compiles(sparse_ids "${schema_text}")
file(WRITE "${WORK}/cxx20_probe.cpp" "int main() { return 0; }\n")
execute_process(COMMAND "${CXX}" ${compiler_flags} -std=c++20 -fsyntax-only "${WORK}/cxx20_probe.cpp"
RESULT_VARIABLE cxx20_result ERROR_QUIET)
if("${cxx20_result}" STREQUAL "0")
expect_compiles(cxx20 "${schema_text}" 20)
else()
message(STATUS "Skipping C++20 compile smoke test: compiler lacks -std=c++20")
endif()
set(cross_namespace_schema
"namespace codegen.common;\ntable Request {}\ntable Response {}\nnamespace codegen.first;\nrpc_service Echo { Call(codegen.common.Request):codegen.common.Response (id: 1); }\nnamespace codegen.second;\nrpc_service Echo_Stub { Call(codegen.common.Request):codegen.common.Response (id: 2); }\n")
expect_compiles(cross_namespace_names "${cross_namespace_schema}")
set(cross_namespace_type_schema
"namespace codegen.common;\ntable Request {}\ntable Response {}\ntable Echo {}\nnamespace codegen.api;\nrpc_service Echo { Call(codegen.common.Request):codegen.common.Response (id: 1); }\n")
expect_compiles(cross_namespace_type_names "${cross_namespace_type_schema}")
expect_compiles(namespace_sibling [=[
namespace api.other.Echo;
table Request {}
table Response {}
namespace api;
rpc_service Echo {
Call(api.other.Echo.Request):api.other.Echo.Response (id: 1);
}
]=])
expect_compiles(service_member_near_names [=[
namespace api;
table Request {}
table Response {}
rpc_service channel { Call(Request):Response (id: 1); }
rpc_service channel_handle { Call(Request):Response (id: 1); }
rpc_service owned_channel_handle { Call(Request):Response (id: 1); }
rpc_service StubService { Call(Request):Response (id: 1); }
rpc_service ChannelOwnership { Call(Request):Response (id: 1); }
]=])
set(shadow_names request response controller done method std fail BrpcFlatbuffersFail
STUB_OWNS_CHANNEL STUB_DOESNT_OWN_CHANNEL ChannelOwnership)
set(shadow_schema "namespace codegen.names;\ntable Request { text:string; }\ntable Response { text:string; }\nrpc_service Names {\n")
set(method_id 0)
foreach(name IN LISTS shadow_names)
math(EXPR method_id "${method_id} + 7")
string(APPEND shadow_schema " ${name}(Request):Response (id: ${method_id});\n")
endforeach()
string(APPEND shadow_schema "}\nrpc_service ChannelOwnership { Ping(Request):Response (id: 3); }\n")
expect_compiles(shadowed_names "${shadow_schema}")
# Supply RUNTIME_LIBRARIES when invoking this script directly to exercise the
# new names against an existing FlatBuffers-enabled bRPC library as well.
if(RUNTIME_LIBRARIES)
set(directory "${WORK}/shadowed_names")
set(runtime_source [=[
#include "echo.brpc.fb.h"
#include <iostream>
using ::brpc::flatbuffers::Message;
using ::google::protobuf::Closure;
using ::google::protobuf::RpcController;
class Completion : public Closure {
public:
void Run() override { ++runs; }
int runs = 0;
};
class Implementation : public ::codegen::names::Names {
public:
int selected = 0;
]=])
set(method_id 0)
foreach(name IN LISTS shadow_names)
math(EXPR method_id "${method_id} + 7")
string(APPEND runtime_source
" void ${name}(RpcController*, const Message*, Message*, Closure* completion) override { selected = ${method_id}; completion->Run(); }\n")
endforeach()
string(APPEND runtime_source [=[
};
class LocalChannel : public ::brpc::flatbuffers::RpcChannel {
public:
explicit LocalChannel(::brpc::flatbuffers::Service* service) : service_(service) {}
void FBCallMethod(const ::brpc::flatbuffers::MethodDescriptor* method,
RpcController* controller, const Message* request,
Message* response, Closure* done) override {
service_->FBCallMethod(method, controller, request, response, done);
}
private:
::brpc::flatbuffers::Service* service_;
};
int main() {
::brpc::flatbuffers::MessageBuilder builder;
builder.Finish(::codegen::names::CreateRequest(builder));
Message request = builder.ReleaseMessage();
Message response;
Implementation implementation;
LocalChannel channel(&implementation);
::codegen::names::Names::Stub stub(&channel);
::codegen::names::Names defaults;
LocalChannel default_channel(&defaults);
::codegen::names::Names::Stub default_stub(&default_channel);
::codegen::names::Names::Stub disconnected(nullptr);
Completion completion;
int expected_runs = 0;
]=])
set(method_id 0)
foreach(name IN LISTS shadow_names)
math(EXPR method_id "${method_id} + 7")
string(APPEND runtime_source
" stub.${name}(nullptr, &request, &response, &completion);\n"
" if (implementation.selected != ${method_id} || completion.runs != ++expected_runs) return 1;\n"
" default_stub.${name}(nullptr, &request, &response, &completion);\n"
" if (completion.runs != ++expected_runs) return 2;\n"
" disconnected.${name}(nullptr, &request, &response, &completion);\n"
" if (completion.runs != ++expected_runs) return 3;\n")
endforeach()
string(APPEND runtime_source [=[
::codegen::names::ChannelOwnership service;
LocalChannel service_channel(&service);
::codegen::names::ChannelOwnership::Stub named_stub(&service_channel,
::brpc::flatbuffers::Service::STUB_DOESNT_OWN_CHANNEL);
named_stub.Ping(nullptr, &request, &response, &completion);
if (completion.runs != ++expected_runs) return 4;
::codegen::names::Names::Stub owned_stub(new LocalChannel(&implementation),
::brpc::flatbuffers::Service::STUB_OWNS_CHANNEL);
owned_stub.request(nullptr, &request, &response, &completion);
if (implementation.selected != 7 || completion.runs != ++expected_runs) return 5;
std::cout << "Shadowed-name dispatch, failures, and constructors passed\n";
return 0;
}
]=])
file(WRITE "${directory}/runtime.cpp" "${runtime_source}")
set(includes "-I${directory}")
foreach(include_dir IN LISTS INCLUDE_DIRS)
list(APPEND includes "-I${include_dir}")
endforeach()
execute_process(COMMAND "${CXX}" ${compiler_flags} "-std=c++${CXX_STANDARD}" ${includes}
"${directory}/runtime.cpp" "${directory}/echo.o" ${RUNTIME_LIBRARIES}
-o "${directory}/runtime"
RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "Shadowed-name runtime link failed: ${error}")
endif()
execute_process(COMMAND "${directory}/runtime"
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "Shadowed-name runtime failed: ${output}${error}")
endif()
message(STATUS "${output}")
endif()
string(REPLACE
" Repeat(Request):Response (id: 41);\n Inspect(Request):Response (id: 7);"
" Inspect(Request):Response (id: 7);\n Repeat(Request):Response (id: 41);"
reordered "${schema_text}")
expect_compiles(reordered "${reordered}")
file(READ "${WORK}/reordered/echo.brpc.fb.cpp" reordered_source)
if(NOT reordered_source MATCHES "case 41:" OR NOT reordered_source MATCHES "case 7:")
message(FATAL_ERROR "Reordering changed wire IDs")
endif()
string(FIND "${reordered_source}" "void Echo_Stub::Repeat(" repeat_begin)
if(repeat_begin LESS 0)
message(FATAL_ERROR "Reordered stub lacks Repeat")
endif()
string(SUBSTRING "${reordered_source}" ${repeat_begin} -1 repeat_body)
string(FIND "${repeat_body}" "\n}\n" repeat_end)
string(SUBSTRING "${repeat_body}" 0 ${repeat_end} repeat_body)
if(NOT repeat_body MATCHES "method\\(1\\)")
message(FATAL_ERROR "Reordered stub does not use its dense method position")
endif()
string(REPLACE "namespace codegen.example;" "" global_schema "${schema_text}")
expect_compiles(global_namespace "${global_schema}")
string(REPLACE "(id: 7)" "(id: 0)" zero_schema "${schema_text}")
expect_compiles(zero_id "${zero_schema}")
string(REPLACE "(id: 7)" "(id: 2147483647)" maximum_schema "${schema_text}")
expect_compiles(maximum_id "${maximum_schema}")
# Include schemas are read through the official Parser and not re-emitted.
file(MAKE_DIRECTORY "${WORK}/included")
file(WRITE "${WORK}/included/types.fbs"
"namespace shared; table Input { note:string; } table Output { note:string; }\n"
"rpc_service Imported { Ping(Input):Output (id: 3); }\n")
execute_process(COMMAND "${FLATC}" --cpp -o "${WORK}/included"
"${WORK}/included/types.fbs" RESULT_VARIABLE result)
if(NOT "${result}" STREQUAL "0")
message(FATAL_ERROR "Cannot generate included table definitions")
endif()
expect_compiles(included
"include \"types.fbs\"; namespace api; rpc_service Local { Call(shared.Input):shared.Output (id: 17); }")
file(READ "${WORK}/included/echo.brpc.fb.h" included_header)
if(included_header MATCHES "class Imported")
message(FATAL_ERROR "Included service was emitted twice")
endif()
set(imported_collision_dir "${WORK}/imported_service_collision")
file(REMOVE_RECURSE "${imported_collision_dir}")
file(MAKE_DIRECTORY "${imported_collision_dir}")
file(WRITE "${imported_collision_dir}/types.fbs"
"namespace shared; table Input {} table Output {}\n"
"rpc_service Imported { Ping(Input):Output (id: 3); }\n")
file(WRITE "${imported_collision_dir}/echo.fbs"
"include \"types.fbs\"; namespace shared;\n"
"rpc_service Imported_Stub { Call(Input):Output (id: 7); }\n")
execute_process(COMMAND "${GENERATOR}" -o "${imported_collision_dir}"
"${imported_collision_dir}/echo.fbs"
RESULT_VARIABLE result ERROR_VARIABLE error)
if("${result}" STREQUAL "0" OR
NOT error MATCHES "generated service class name collides: ::shared::Imported_Stub")
message(FATAL_ERROR
"imported_service_collision: schema was accepted or lacked diagnostic: ${error}")
endif()
if(EXISTS "${imported_collision_dir}/echo.brpc.fb.h" OR
EXISTS "${imported_collision_dir}/echo.brpc.fb.cpp")
message(FATAL_ERROR "imported_service_collision: rejected schema emitted files")
endif()
message(STATUS "Rejected imported service collision: ${error}")