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>diff --git a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
index f6a15b3..4c427b1 100644
--- a/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_cpp_generator.cc
@@ -57,6 +57,7 @@
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 @@
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) {
@@ -337,6 +341,11 @@
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.
*/
bool gen_templates_;
@@ -609,7 +618,11 @@
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 @@
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 @@
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 @@
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 @@
" 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"
diff --git a/compiler/cpp/tests/CMakeLists.txt b/compiler/cpp/tests/CMakeLists.txt
index 77c1524..468de6e 100644
--- a/compiler/cpp/tests/CMakeLists.txt
+++ b/compiler/cpp/tests/CMakeLists.txt
@@ -194,73 +194,117 @@
)
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()
diff --git a/compiler/cpp/tests/cpp/expected_Color_default.txt b/compiler/cpp/tests/cpp/expected_Color_default.txt
new file mode 100644
index 0000000..94f3226
--- /dev/null
+++ b/compiler/cpp/tests/cpp/expected_Color_default.txt
@@ -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
+ };
+};
diff --git a/compiler/cpp/tests/cpp/expected_Color_enum_class.txt b/compiler/cpp/tests/cpp/expected_Color_enum_class.txt
new file mode 100644
index 0000000..f21bc8e
--- /dev/null
+++ b/compiler/cpp/tests/cpp/expected_Color_enum_class.txt
@@ -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
+};
diff --git a/compiler/cpp/tests/cpp/expected_Color_pure_enums.txt b/compiler/cpp/tests/cpp/expected_Color_pure_enums.txt
new file mode 100644
index 0000000..2294c62
--- /dev/null
+++ b/compiler/cpp/tests/cpp/expected_Color_pure_enums.txt
@@ -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
+};
diff --git a/compiler/cpp/tests/cpp/t_cpp_generator_enum_class_tests.cc b/compiler/cpp/tests/cpp/t_cpp_generator_enum_class_tests.cc
new file mode 100644
index 0000000..56404ad
--- /dev/null
+++ b/compiler/cpp/tests/cpp/t_cpp_generator_enum_class_tests.cc
@@ -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));
+}
diff --git a/compiler/cpp/tests/cpp/t_cpp_generator_private_optional_tests.cc b/compiler/cpp/tests/cpp/t_cpp_generator_private_optional_tests.cc
index 9f48229..2d63cb4 100644
--- a/compiler/cpp/tests/cpp/t_cpp_generator_private_optional_tests.cc
+++ b/compiler/cpp/tests/cpp/t_cpp_generator_private_optional_tests.cc
@@ -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 @@
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");
diff --git a/compiler/cpp/tests/cpp/t_cpp_generator_test_utils.h b/compiler/cpp/tests/cpp/t_cpp_generator_test_utils.h
new file mode 100644
index 0000000..5cea69a
--- /dev/null
+++ b/compiler/cpp/tests/cpp/t_cpp_generator_test_utils.h
@@ -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
diff --git a/compiler/cpp/tests/cpp/test_enum_class.thrift b/compiler/cpp/tests/cpp/test_enum_class.thrift
new file mode 100644
index 0000000..bf85e32
--- /dev/null
+++ b/compiler/cpp/tests/cpp/test_enum_class.thrift
@@ -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
+}