Skip to content

Commit f03ebf1

Browse files
MisterRaindropclaudehappy-otter
committed
fix: use GTest Environment to finalize Arrow S3 after tests
Arrow's static destructor detects when S3 was initialized but not finalized, causing a non-zero exit under sanitizers. The previous atexit approach caused segfaults because Arrow internals were already partially destroyed. Using a GTest Environment::TearDown() is the correct solution: it runs after all tests complete but before static destructors, making FinalizeS3() safe to call. Also fix std::unexpected double-brace init for GCC 14 compatibility. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
1 parent 519878f commit f03ebf1

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

src/iceberg/arrow/arrow_s3_file_io.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ Status EnsureS3Initialized() {
4848
init_status = ::arrow::fs::InitializeS3(options);
4949
});
5050
if (!init_status.ok()) {
51-
return std::unexpected<Error>{{.kind = ::iceberg::arrow::ToErrorKind(init_status),
52-
.message = init_status.ToString()}};
51+
return std::unexpected(Error{.kind = ::iceberg::arrow::ToErrorKind(init_status),
52+
.message = init_status.ToString()});
5353
}
5454
return {};
5555
#else

src/iceberg/test/arrow_s3_file_io_test.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,47 @@
1818
*/
1919

2020
#include <cstdlib>
21+
#include <iostream>
2122
#include <string>
2223
#include <unordered_map>
2324

2425
#include <gtest/gtest.h>
2526

27+
#ifdef ICEBERG_S3_ENABLED
28+
# include <arrow/filesystem/s3fs.h>
29+
#endif
30+
2631
#include "iceberg/arrow/arrow_file_io.h"
2732
#include "iceberg/arrow/s3_properties.h"
2833
#include "iceberg/test/matchers.h"
2934

35+
#ifdef ICEBERG_S3_ENABLED
36+
namespace {
37+
38+
/// \brief GTest environment that finalizes Arrow S3 after all tests complete.
39+
///
40+
/// Arrow's S3 initialization creates global state that must be cleaned up via
41+
/// FinalizeS3() before the process exits. Without this, Arrow's static destructor
42+
/// detects the missing finalization and causes a non-zero exit (which fails under
43+
/// sanitizers). GTest Environment::TearDown() runs after all tests but before
44+
/// static destructors, making it the safe place to finalize.
45+
class ArrowS3TestEnvironment : public ::testing::Environment {
46+
public:
47+
void TearDown() override {
48+
auto status = ::arrow::fs::FinalizeS3();
49+
if (!status.ok()) {
50+
std::cerr << "Warning: FinalizeS3 failed: " << status.ToString() << std::endl;
51+
}
52+
}
53+
};
54+
55+
// Register before main() runs. GTest takes ownership of the pointer.
56+
[[maybe_unused]] auto* const kS3Env =
57+
::testing::AddGlobalTestEnvironment(new ArrowS3TestEnvironment);
58+
59+
} // namespace
60+
#endif
61+
3062
namespace iceberg::arrow {
3163

3264
TEST(ArrowS3FileIOTest, RejectsNonS3Uri) {

0 commit comments

Comments
 (0)