mirror of
https://github.com/apache/thrift.git
synced 2026-10-28 08:24:58 +08:00
Support C++ 11 enum class generation via pure_enums=enum_class
Adds C++ 11 `enum class` generation to the C++ code generator. When `pure_enums=enum_class` is specified, generates strongly-typed enums with proper scoping and type safety.
## Code Generator Changes
**Modified `t_cpp_generator.cc`:**
- Added `gen_enum_class_` flag parsed from `pure_enums=enum_class` option
- Modified `generate_enum()` to emit `enum class` when both `gen_pure_enums_` and `gen_enum_class_` are set
- Fixed enum value scoping: use `static_cast<int>(Color::RED)` for array initialization
- Fixed map operations: cast enum class to int in `find()` calls (no implicit conversion)
**Test infrastructure:**
- Created `t_cpp_generator_enum_class_tests.cc` with three test cases validating default, `pure_enums`, and `pure_enums=enum_class` behavior
- Extracted common utilities to `t_cpp_generator_test_utils.h` (shared with `private_optional` tests)
- Added expected output files: `expected_Color_{default,pure_enums,enum_class}.txt`
**Build system:**
- Added CMake compile-checks for generated code (both `private_optional` and `enum_class`)
- Unified compile-check blocks with shared Boost detection
- Ensures generated code compiles as part of standard test build
## Usage
```thrift
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3
}
```
```bash
thrift --gen cpp:pure_enums=enum_class example.thrift
```
Generates:
```cpp
enum class Color {
RED = 1,
GREEN = 2,
BLUE = 3
};
```
Backward compatible - existing behavior unchanged when option absent.
---
- [ ] Did you create an [Apache Jira](https://issues.apache.org/jira/projects/THRIFT/issues/) ticket? ([Request account here](https://selfserve.apache.org/jira-account.html), not required for trivial changes)
- [ ] If a ticket exists: Does your pull request title follow the pattern "THRIFT-NNNN: describe my issue"?
- [x] Did you squash your changes to a single commit? (not required, but preferred)
- [x] Did you do your best to avoid breaking changes? If one was needed, did you label the Jira ticket with "Breaking-Change"?
- [ ] If your change does not involve any code, include `[skip ci]` anywhere in the commit message to free up build resources.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zsy056 <1074382+zsy056@users.noreply.github.com>
This commit is contained in:
1 parent
55e86e396e
commit
ab178fbf24
10 files changed
+570
-185
No files matched your search
@@ -452,3 +452,4 @@ compiler/cpp/tests/*.cmake
|
||||
compiler/cpp/tests/Testing/
|
||||
compiler/cpp/tests/bin/
|
||||
compiler/cpp/tests/*.a
|
||||
compiler/cpp/tests/build/
|
||||
@@ -57,6 +57,7 @@ public:
|
||||
|
||||
|
||||
gen_pure_enums_ = false;
|
||||
gen_enum_class_ = false;
|
||||
use_include_prefix_ = false;
|
||||
gen_cob_style_ = false;
|
||||
gen_no_client_completion_ = false;
|
||||
@@ -73,6 +74,9 @@ public:
|
||||
for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) {
|
||||
if( iter->first.compare("pure_enums") == 0) {
|
||||
gen_pure_enums_ = true;
|
||||
if (iter->second.compare("enum_class") == 0) {
|
||||
gen_enum_class_ = true;
|
||||
}
|
||||
} else if( iter->first.compare("include_prefix") == 0) {
|
||||
use_include_prefix_ = true;
|
||||
} else if( iter->first.compare("cob_style") == 0) {
|
||||
@@ -336,6 +340,11 @@ private:
|
||||
*/
|
||||
bool gen_pure_enums_;
|
||||
|
||||
/**
|
||||
* True if we should generate C++ 11 enum class for Thrift enums.
|
||||
*/
|
||||
bool gen_enum_class_;
|
||||
|
||||
/**
|
||||
* True if we should generate templatized reader/writer methods.
|
||||
*/
|
||||
@@ -609,7 +618,11 @@ void t_cpp_generator::generate_enum(t_enum* tenum) {
|
||||
f_types_ << indent() << "struct " << tenum->get_name() << " {" << '\n';
|
||||
indent_up();
|
||||
}
|
||||
f_types_ << indent() << "enum " << enum_name;
|
||||
if (gen_pure_enums_ && gen_enum_class_) {
|
||||
f_types_ << indent() << "enum class " << enum_name;
|
||||
} else {
|
||||
f_types_ << indent() << "enum " << enum_name;
|
||||
}
|
||||
|
||||
generate_enum_constant_list(f_types_, constants, "", "", true);
|
||||
|
||||
@@ -624,12 +637,20 @@ void t_cpp_generator::generate_enum(t_enum* tenum) {
|
||||
Generate a character array of enum names for debugging purposes.
|
||||
*/
|
||||
std::string prefix = "";
|
||||
if (!gen_pure_enums_) {
|
||||
std::string int_value_prefix = "";
|
||||
std::string int_value_suffix = "";
|
||||
if (!gen_pure_enums_ || gen_enum_class_) {
|
||||
prefix = tenum->get_name() + "::";
|
||||
}
|
||||
if (gen_enum_class_) {
|
||||
int_value_prefix = "static_cast<int>(" + tenum->get_name() + "::";
|
||||
int_value_suffix = ")";
|
||||
} else if (!gen_pure_enums_) {
|
||||
int_value_prefix = tenum->get_name() + "::";
|
||||
}
|
||||
|
||||
f_types_impl_ << indent() << "int _k" << tenum->get_name() << "Values[] =";
|
||||
generate_enum_constant_list(f_types_impl_, constants, prefix.c_str(), "", false);
|
||||
generate_enum_constant_list(f_types_impl_, constants, int_value_prefix.c_str(), int_value_suffix.c_str(), false);
|
||||
|
||||
f_types_impl_ << indent() << "const char* _k" << tenum->get_name() << "Names[] =";
|
||||
generate_enum_constant_list(f_types_impl_, constants, "\"", "\"", false);
|
||||
@@ -680,7 +701,12 @@ void t_cpp_generator::generate_enum_ostream_operator(std::ostream& out, t_enum*
|
||||
scope_up(out);
|
||||
|
||||
out << indent() << "std::map<int, const char*>::const_iterator it = _"
|
||||
<< tenum->get_name() << "_VALUES_TO_NAMES.find(val);" << '\n';
|
||||
<< tenum->get_name() << "_VALUES_TO_NAMES.find(";
|
||||
if (gen_enum_class_) {
|
||||
out << "static_cast<int>(val));" << '\n';
|
||||
} else {
|
||||
out << "val);" << '\n';
|
||||
}
|
||||
out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << '\n';
|
||||
indent_up();
|
||||
out << indent() << "out << it->second;" << '\n';
|
||||
@@ -720,7 +746,12 @@ void t_cpp_generator::generate_enum_to_string_helper_function(std::ostream& out,
|
||||
scope_up(out);
|
||||
|
||||
out << indent() << "std::map<int, const char*>::const_iterator it = _"
|
||||
<< tenum->get_name() << "_VALUES_TO_NAMES.find(val);" << '\n';
|
||||
<< tenum->get_name() << "_VALUES_TO_NAMES.find(";
|
||||
if (gen_enum_class_) {
|
||||
out << "static_cast<int>(val));" << '\n';
|
||||
} else {
|
||||
out << "val);" << '\n';
|
||||
}
|
||||
out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << '\n';
|
||||
indent_up();
|
||||
out << indent() << "return std::string(it->second);" << '\n';
|
||||
@@ -4954,6 +4985,7 @@ THRIFT_REGISTER_GENERATOR(
|
||||
" Omits generation of default operators ==, != and <\n"
|
||||
" templates: Generate templatized reader/writer methods.\n"
|
||||
" pure_enums: Generate pure enums instead of wrapper classes.\n"
|
||||
" When 'pure_enums=enum_class', generate C++ 11 enum class.\n"
|
||||
" include_prefix: Use full include paths in generated files.\n"
|
||||
" moveable_types: Generate move constructors and assignment operators.\n"
|
||||
" no_ostream_operators:\n"
|
||||
|
||||
@@ -194,73 +194,117 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
# Compile-check generated C++ output for the fixture thrift file with private_optional enabled.
|
||||
# Compile-check generated C++ output for fixture thrift files.
|
||||
# This ensures the generator output is compileable (no link step required).
|
||||
# Note: These checks require Boost headers and are optional.
|
||||
if(TARGET thrift-compiler)
|
||||
# Generated C++ includes Thrift runtime headers which may require Boost.
|
||||
# Only enable the compile-check when Boost headers are available.
|
||||
set(_private_optional_boost_include_dirs "")
|
||||
# Try to find Boost for the compile checks
|
||||
find_package(Boost QUIET)
|
||||
set(_compile_check_boost_include_dirs "")
|
||||
if(Boost_FOUND)
|
||||
set(_private_optional_boost_include_dirs ${Boost_INCLUDE_DIRS})
|
||||
set(_compile_check_boost_include_dirs ${Boost_INCLUDE_DIRS})
|
||||
elseif(DEFINED BOOST_ROOT)
|
||||
if(EXISTS "${BOOST_ROOT}/include/boost")
|
||||
set(_private_optional_boost_include_dirs "${BOOST_ROOT}/include")
|
||||
set(_compile_check_boost_include_dirs "${BOOST_ROOT}/include")
|
||||
elseif(EXISTS "${BOOST_ROOT}/boost")
|
||||
set(_private_optional_boost_include_dirs "${BOOST_ROOT}")
|
||||
set(_compile_check_boost_include_dirs "${BOOST_ROOT}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(_private_optional_boost_include_dirs STREQUAL "")
|
||||
message(STATUS "Skipping generated private_optional compile-check (Boost headers not found)")
|
||||
# Only create compile-check targets if Boost is available
|
||||
if(NOT _compile_check_boost_include_dirs STREQUAL "")
|
||||
set(_private_optional_thrift
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cpp/test_private_optional.thrift"
|
||||
)
|
||||
set(_private_optional_gen_out_dir
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated-private-optional"
|
||||
)
|
||||
set(_private_optional_gen_cpp_dir
|
||||
"${_private_optional_gen_out_dir}/gen-cpp"
|
||||
)
|
||||
set(_private_optional_types_cpp
|
||||
"${_private_optional_gen_cpp_dir}/test_private_optional_types.cpp"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${_private_optional_types_cpp}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_private_optional_gen_out_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir "${_private_optional_gen_out_dir}"
|
||||
$<TARGET_FILE:thrift-compiler>
|
||||
--gen cpp:private_optional
|
||||
-o "${_private_optional_gen_out_dir}"
|
||||
"${_private_optional_thrift}"
|
||||
DEPENDS thrift-compiler "${_private_optional_thrift}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
"${_private_optional_types_cpp}"
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
add_library(thrift_compiler_generated_private_optional STATIC
|
||||
"${_private_optional_types_cpp}"
|
||||
)
|
||||
|
||||
target_include_directories(thrift_compiler_generated_private_optional PRIVATE
|
||||
"${_private_optional_gen_cpp_dir}"
|
||||
"${THRIFT_COMPILER_SOURCE_DIR}/../../lib/cpp/src"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
${_compile_check_boost_include_dirs}
|
||||
)
|
||||
|
||||
set(_enum_class_thrift
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cpp/test_enum_class.thrift"
|
||||
)
|
||||
set(_enum_class_gen_out_dir
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated-enum-class"
|
||||
)
|
||||
set(_enum_class_gen_cpp_dir
|
||||
"${_enum_class_gen_out_dir}/gen-cpp"
|
||||
)
|
||||
set(_enum_class_types_cpp
|
||||
"${_enum_class_gen_cpp_dir}/test_enum_class_types.cpp"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${_enum_class_types_cpp}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_enum_class_gen_out_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir "${_enum_class_gen_out_dir}"
|
||||
$<TARGET_FILE:thrift-compiler>
|
||||
--gen cpp:pure_enums=enum_class
|
||||
-o "${_enum_class_gen_out_dir}"
|
||||
"${_enum_class_thrift}"
|
||||
DEPENDS thrift-compiler "${_enum_class_thrift}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
"${_enum_class_types_cpp}"
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
add_library(thrift_compiler_generated_enum_class STATIC
|
||||
"${_enum_class_types_cpp}"
|
||||
)
|
||||
|
||||
target_include_directories(thrift_compiler_generated_enum_class PRIVATE
|
||||
"${_enum_class_gen_cpp_dir}"
|
||||
"${THRIFT_COMPILER_SOURCE_DIR}/../../lib/cpp/src"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
${_compile_check_boost_include_dirs}
|
||||
)
|
||||
|
||||
# Build the compile-check as part of the standard test build.
|
||||
add_dependencies(thrift_compiler_tests thrift_compiler_generated_private_optional)
|
||||
add_dependencies(thrift_compiler_tests thrift_compiler_generated_enum_class)
|
||||
else()
|
||||
set(_private_optional_thrift
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cpp/test_private_optional.thrift"
|
||||
)
|
||||
set(_private_optional_gen_out_dir
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated-private-optional"
|
||||
)
|
||||
set(_private_optional_gen_cpp_dir
|
||||
"${_private_optional_gen_out_dir}/gen-cpp"
|
||||
)
|
||||
set(_private_optional_types_cpp
|
||||
"${_private_optional_gen_cpp_dir}/test_private_optional_types.cpp"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${_private_optional_types_cpp}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_private_optional_gen_out_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir "${_private_optional_gen_out_dir}"
|
||||
$<TARGET_FILE:thrift-compiler>
|
||||
--gen cpp:private_optional
|
||||
-o "${_private_optional_gen_out_dir}"
|
||||
"${_private_optional_thrift}"
|
||||
DEPENDS thrift-compiler "${_private_optional_thrift}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
"${_private_optional_types_cpp}"
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
|
||||
add_library(thrift_compiler_generated_private_optional STATIC
|
||||
"${_private_optional_types_cpp}"
|
||||
)
|
||||
|
||||
target_include_directories(thrift_compiler_generated_private_optional PRIVATE
|
||||
"${_private_optional_gen_cpp_dir}"
|
||||
"${THRIFT_COMPILER_SOURCE_DIR}/../../lib/cpp/src"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
${_private_optional_boost_include_dirs}
|
||||
)
|
||||
|
||||
# Build the compile-check as part of the standard test build.
|
||||
add_dependencies(thrift_compiler_tests thrift_compiler_generated_private_optional)
|
||||
message(STATUS "Skipping generated code compile-checks (Boost headers not found)")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Skipping generated private_optional compile-check (no thrift-compiler target)")
|
||||
message(STATUS "Skipping generated code compile-checks (no thrift-compiler target)")
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.
|
||||
|
||||
struct Color {
|
||||
enum type {
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
enum class Color {
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
enum Color {
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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.
|
||||
|
||||
#include "t_cpp_generator_test_utils.h"
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
using cpp_generator_test_utils::read_file;
|
||||
using cpp_generator_test_utils::source_dir;
|
||||
using cpp_generator_test_utils::join_path;
|
||||
using cpp_generator_test_utils::normalize_for_compare;
|
||||
using cpp_generator_test_utils::parse_thrift_for_test;
|
||||
|
||||
// Helper function to extract enum definition from generated header
|
||||
// Handles struct Color { enum type { ... }; }; or enum Color { ... }; or enum class Color { ... };
|
||||
string extract_enum_definition(const string& content, const string& enum_name) {
|
||||
// First try to find struct wrapper (default behavior)
|
||||
size_t struct_start = content.find("struct " + enum_name + " {");
|
||||
if (struct_start != string::npos) {
|
||||
// Find the closing brace and semicolon for the struct
|
||||
size_t brace_count = 0;
|
||||
size_t pos = content.find("{", struct_start);
|
||||
if (pos == string::npos) return "";
|
||||
|
||||
brace_count = 1;
|
||||
pos++;
|
||||
|
||||
while (pos < content.length() && brace_count > 0) {
|
||||
if (content[pos] == '{') brace_count++;
|
||||
else if (content[pos] == '}') brace_count--;
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (brace_count == 0) {
|
||||
// Skip whitespace and semicolon
|
||||
while (pos < content.length() && (content[pos] == ' ' || content[pos] == '\n' || content[pos] == '\r')) pos++;
|
||||
if (pos < content.length() && content[pos] == ';') pos++;
|
||||
return content.substr(struct_start, pos - struct_start);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Try to find enum class
|
||||
size_t enum_start = content.find("enum class " + enum_name + " {");
|
||||
if (enum_start == string::npos) {
|
||||
// Try to find plain enum
|
||||
enum_start = content.find("enum " + enum_name + " {");
|
||||
if (enum_start == string::npos) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Find the closing brace and semicolon
|
||||
size_t brace_end = content.find("};", enum_start);
|
||||
if (brace_end == string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return content.substr(enum_start, brace_end - enum_start + 2);
|
||||
}
|
||||
|
||||
TEST_CASE("t_cpp_generator default behavior generates wrapper struct for enums", "[functional]")
|
||||
{
|
||||
string path = join_path(source_dir(), "test_enum_class.thrift");
|
||||
string name = "test_enum_class";
|
||||
map<string, string> parsed_options = {}; // No options
|
||||
string option_string = "";
|
||||
|
||||
std::unique_ptr<t_program> program(new t_program(path, name));
|
||||
parse_thrift_for_test(program.get());
|
||||
|
||||
std::unique_ptr<t_generator> gen(
|
||||
t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
// Generate code
|
||||
REQUIRE_NOTHROW(gen->generate_program());
|
||||
|
||||
// Read generated output
|
||||
string generated_file = "gen-cpp/test_enum_class_types.h";
|
||||
string generated_content = read_file(generated_file);
|
||||
REQUIRE(!generated_content.empty());
|
||||
|
||||
// Extract enum definition
|
||||
string enum_def = extract_enum_definition(generated_content, "Color");
|
||||
REQUIRE(!enum_def.empty());
|
||||
|
||||
// Compare generated enum definition against the expected fixture.
|
||||
string expected_path = join_path(source_dir(), "expected_Color_default.txt");
|
||||
string expected_content = read_file(expected_path);
|
||||
REQUIRE(!expected_content.empty());
|
||||
|
||||
REQUIRE(normalize_for_compare(enum_def) == normalize_for_compare(expected_content));
|
||||
}
|
||||
|
||||
TEST_CASE("t_cpp_generator with pure_enums generates plain enum", "[functional]")
|
||||
{
|
||||
string path = join_path(source_dir(), "test_enum_class.thrift");
|
||||
string name = "test_enum_class";
|
||||
map<string, string> parsed_options = {{"pure_enums", ""}};
|
||||
string option_string = "";
|
||||
|
||||
std::unique_ptr<t_program> program(new t_program(path, name));
|
||||
parse_thrift_for_test(program.get());
|
||||
|
||||
std::unique_ptr<t_generator> gen(
|
||||
t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
// Generate code
|
||||
REQUIRE_NOTHROW(gen->generate_program());
|
||||
|
||||
// Read generated output
|
||||
string generated_file = "gen-cpp/test_enum_class_types.h";
|
||||
string generated_content = read_file(generated_file);
|
||||
REQUIRE(!generated_content.empty());
|
||||
|
||||
// Extract enum definition
|
||||
string enum_def = extract_enum_definition(generated_content, "Color");
|
||||
REQUIRE(!enum_def.empty());
|
||||
|
||||
// Compare generated enum definition against the expected fixture.
|
||||
string expected_path = join_path(source_dir(), "expected_Color_pure_enums.txt");
|
||||
string expected_content = read_file(expected_path);
|
||||
REQUIRE(!expected_content.empty());
|
||||
|
||||
REQUIRE(normalize_for_compare(enum_def) == normalize_for_compare(expected_content));
|
||||
}
|
||||
|
||||
TEST_CASE("t_cpp_generator with pure_enums=enum_class generates C++ 11 enum class", "[functional]")
|
||||
{
|
||||
string path = join_path(source_dir(), "test_enum_class.thrift");
|
||||
string name = "test_enum_class";
|
||||
map<string, string> parsed_options = {{"pure_enums", "enum_class"}};
|
||||
string option_string = "";
|
||||
|
||||
std::unique_ptr<t_program> program(new t_program(path, name));
|
||||
parse_thrift_for_test(program.get());
|
||||
|
||||
std::unique_ptr<t_generator> gen(
|
||||
t_generator_registry::get_generator(program.get(), "cpp", parsed_options, option_string));
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
// Generate code
|
||||
REQUIRE_NOTHROW(gen->generate_program());
|
||||
|
||||
// Read generated output
|
||||
string generated_file = "gen-cpp/test_enum_class_types.h";
|
||||
string generated_content = read_file(generated_file);
|
||||
REQUIRE(!generated_content.empty());
|
||||
|
||||
// Extract enum definition
|
||||
string enum_def = extract_enum_definition(generated_content, "Color");
|
||||
REQUIRE(!enum_def.empty());
|
||||
|
||||
// Compare generated enum definition against the expected fixture.
|
||||
string expected_path = join_path(source_dir(), "expected_Color_enum_class.txt");
|
||||
string expected_content = read_file(expected_path);
|
||||
REQUIRE(!expected_content.empty());
|
||||
|
||||
REQUIRE(normalize_for_compare(enum_def) == normalize_for_compare(expected_content));
|
||||
}
|
||||
@@ -15,104 +15,15 @@
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "../catch/catch.hpp"
|
||||
#include <thrift/parse/t_program.h>
|
||||
#include <thrift/generate/t_generator.h>
|
||||
#include <thrift/generate/t_generator_registry.h>
|
||||
#include <thrift/globals.h>
|
||||
#include <thrift/main.h>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <algorithm>
|
||||
#include "t_cpp_generator_test_utils.h"
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::ostringstream;
|
||||
|
||||
// Provided by compiler/cpp/tests/thrift_test_parser_support.cc
|
||||
extern std::string g_curdir;
|
||||
extern std::string g_curpath;
|
||||
|
||||
// Helper function to read file content
|
||||
string read_file(const string& filename) {
|
||||
ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
return "";
|
||||
}
|
||||
ostringstream ss;
|
||||
ss << file.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static string source_dir() {
|
||||
string file = __FILE__;
|
||||
std::replace(file.begin(), file.end(), '\\', '/');
|
||||
size_t slash = file.rfind('/');
|
||||
return (slash == string::npos) ? string(".") : file.substr(0, slash);
|
||||
}
|
||||
|
||||
static string join_path(const string& a, const string& b) {
|
||||
if (a.empty()) {
|
||||
return b;
|
||||
}
|
||||
if (a.back() == '/' || a.back() == '\\') {
|
||||
return a + b;
|
||||
}
|
||||
return a + "/" + b;
|
||||
}
|
||||
|
||||
static string normalize_for_compare(string s) {
|
||||
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
|
||||
|
||||
std::istringstream in(s);
|
||||
std::ostringstream out;
|
||||
string line;
|
||||
bool in_block_comment = false;
|
||||
bool first = true;
|
||||
while (std::getline(in, line)) {
|
||||
while (!line.empty() && (line.back() == ' ' || line.back() == '\t')) {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
const auto first_non_ws = line.find_first_not_of(" \t");
|
||||
if (first_non_ws == string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const string trimmed = line.substr(first_non_ws);
|
||||
|
||||
if (in_block_comment) {
|
||||
if (trimmed.find("*/") != string::npos) {
|
||||
in_block_comment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.size() >= 2 && trimmed.compare(0, 2, "//") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.size() >= 2 && trimmed.compare(0, 2, "/*") == 0) {
|
||||
if (trimmed.find("*/") == string::npos) {
|
||||
in_block_comment = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
out << '\n';
|
||||
}
|
||||
first = false;
|
||||
out << line;
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
using cpp_generator_test_utils::read_file;
|
||||
using cpp_generator_test_utils::source_dir;
|
||||
using cpp_generator_test_utils::join_path;
|
||||
using cpp_generator_test_utils::normalize_for_compare;
|
||||
using cpp_generator_test_utils::parse_thrift_for_test;
|
||||
|
||||
// Helper function to extract class definition from generated header
|
||||
string extract_class_definition(const string& content, const string& class_name) {
|
||||
@@ -129,37 +40,6 @@ string extract_class_definition(const string& content, const string& class_name)
|
||||
return content.substr(class_start, class_end - class_start + 2);
|
||||
}
|
||||
|
||||
static void parse_thrift_for_test(t_program* program) {
|
||||
REQUIRE(program != nullptr);
|
||||
|
||||
// These globals are used by the parser; see thrift/globals.h.
|
||||
g_program = program;
|
||||
g_scope = program->scope();
|
||||
g_parent_scope = nullptr;
|
||||
g_parent_prefix = program->get_name() + ".";
|
||||
|
||||
g_curpath = program->get_path();
|
||||
g_curdir = directory_name(g_curpath);
|
||||
|
||||
// Pass 1: scan includes (even if none) to match the compiler behavior.
|
||||
g_parse_mode = INCLUDES;
|
||||
yylineno = 1;
|
||||
yyin = std::fopen(g_curpath.c_str(), "r");
|
||||
REQUIRE(yyin != nullptr);
|
||||
REQUIRE(yyparse() == 0);
|
||||
std::fclose(yyin);
|
||||
yyin = nullptr;
|
||||
|
||||
// Pass 2: parse program.
|
||||
g_parse_mode = PROGRAM;
|
||||
yylineno = 1;
|
||||
yyin = std::fopen(g_curpath.c_str(), "r");
|
||||
REQUIRE(yyin != nullptr);
|
||||
REQUIRE(yyparse() == 0);
|
||||
std::fclose(yyin);
|
||||
yyin = nullptr;
|
||||
}
|
||||
|
||||
TEST_CASE("t_cpp_generator default behavior generates all public fields", "[functional]")
|
||||
{
|
||||
string path = join_path(source_dir(), "test_private_optional.thrift");
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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.
|
||||
|
||||
#ifndef T_CPP_GENERATOR_TEST_UTILS_H
|
||||
#define T_CPP_GENERATOR_TEST_UTILS_H
|
||||
|
||||
#include "../catch/catch.hpp"
|
||||
#include <thrift/parse/t_program.h>
|
||||
#include <thrift/generate/t_generator.h>
|
||||
#include <thrift/generate/t_generator_registry.h>
|
||||
#include <thrift/globals.h>
|
||||
#include <thrift/main.h>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
// Provided by compiler/cpp/tests/thrift_test_parser_support.cc
|
||||
extern std::string g_curdir;
|
||||
extern std::string g_curpath;
|
||||
|
||||
namespace cpp_generator_test_utils {
|
||||
|
||||
// Helper function to read file content
|
||||
inline std::string read_file(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << file.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Get the source directory of the current test file
|
||||
inline std::string source_dir() {
|
||||
std::string file = __FILE__;
|
||||
std::replace(file.begin(), file.end(), '\\', '/');
|
||||
size_t slash = file.rfind('/');
|
||||
return (slash == std::string::npos) ? std::string(".") : file.substr(0, slash);
|
||||
}
|
||||
|
||||
// Join two path components
|
||||
inline std::string join_path(const std::string& a, const std::string& b) {
|
||||
if (a.empty()) {
|
||||
return b;
|
||||
}
|
||||
if (a.back() == '/' || a.back() == '\\') {
|
||||
return a + b;
|
||||
}
|
||||
return a + "/" + b;
|
||||
}
|
||||
|
||||
// Normalize generated code for comparison by removing comments and extra whitespace
|
||||
inline std::string normalize_for_compare(std::string s) {
|
||||
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
|
||||
|
||||
std::istringstream in(s);
|
||||
std::ostringstream out;
|
||||
std::string line;
|
||||
bool in_block_comment = false;
|
||||
bool first = true;
|
||||
while (std::getline(in, line)) {
|
||||
while (!line.empty() && (line.back() == ' ' || line.back() == '\t')) {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
const auto first_non_ws = line.find_first_not_of(" \t");
|
||||
if (first_non_ws == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string trimmed = line.substr(first_non_ws);
|
||||
|
||||
if (in_block_comment) {
|
||||
if (trimmed.find("*/") != std::string::npos) {
|
||||
in_block_comment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.size() >= 2 && trimmed.compare(0, 2, "//") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.size() >= 2 && trimmed.compare(0, 2, "/*") == 0) {
|
||||
if (trimmed.find("*/") == std::string::npos) {
|
||||
in_block_comment = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
out << '\n';
|
||||
}
|
||||
first = false;
|
||||
out << line;
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// Parse a thrift program for testing
|
||||
inline void parse_thrift_for_test(t_program* program) {
|
||||
REQUIRE(program != nullptr);
|
||||
|
||||
// These globals are used by the parser; see thrift/globals.h.
|
||||
g_program = program;
|
||||
g_scope = program->scope();
|
||||
g_parent_scope = nullptr;
|
||||
g_parent_prefix = program->get_name() + ".";
|
||||
|
||||
g_curpath = program->get_path();
|
||||
g_curdir = directory_name(g_curpath);
|
||||
|
||||
// Pass 1: scan includes (even if none) to match the compiler behavior.
|
||||
g_parse_mode = INCLUDES;
|
||||
yylineno = 1;
|
||||
yyin = std::fopen(g_curpath.c_str(), "r");
|
||||
REQUIRE(yyin != nullptr);
|
||||
REQUIRE(yyparse() == 0);
|
||||
std::fclose(yyin);
|
||||
yyin = nullptr;
|
||||
|
||||
// Pass 2: parse program.
|
||||
g_parse_mode = PROGRAM;
|
||||
yylineno = 1;
|
||||
yyin = std::fopen(g_curpath.c_str(), "r");
|
||||
REQUIRE(yyin != nullptr);
|
||||
REQUIRE(yyparse() == 0);
|
||||
std::fclose(yyin);
|
||||
yyin = nullptr;
|
||||
}
|
||||
|
||||
} // namespace cpp_generator_test_utils
|
||||
|
||||
#endif // T_CPP_GENERATOR_TEST_UTILS_H
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace cpp test.enum_class
|
||||
|
||||
enum Color {
|
||||
RED = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3
|
||||
}
|
||||
|
||||
enum Status {
|
||||
ACTIVE = 0,
|
||||
INACTIVE = 1,
|
||||
PENDING = 2
|
||||
}
|
||||
Reference in new issue
Block a user