This is an automated email from the ASF dual-hosted git repository.
gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 52b9c5e refactor(test): reorganize table metadata test suite (#343)
52b9c5e is described below
commit 52b9c5ea875580060c14d947a797fa1308211206
Author: Li Feiyang <[email protected]>
AuthorDate: Wed Nov 26 11:04:01 2025 +0800
refactor(test): reorganize table metadata test suite (#343)
---
src/iceberg/table_requirements.cc | 1 -
src/iceberg/test/CMakeLists.txt | 4 +-
src/iceberg/test/meson.build | 2 +
src/iceberg/test/table_metadata_builder_test.cc | 405 +++---------------------
src/iceberg/test/table_requirement_test.cc | 203 ++++++++++++
src/iceberg/test/table_update_test.cc | 89 ++++++
6 files changed, 346 insertions(+), 358 deletions(-)
diff --git a/src/iceberg/table_requirements.cc
b/src/iceberg/table_requirements.cc
index aae874e..fcb744a 100644
--- a/src/iceberg/table_requirements.cc
+++ b/src/iceberg/table_requirements.cc
@@ -19,7 +19,6 @@
#include "iceberg/table_requirements.h"
-#include "iceberg/exception.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_update.h"
diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt
index 302789e..f0c6027 100644
--- a/src/iceberg/test/CMakeLists.txt
+++ b/src/iceberg/test/CMakeLists.txt
@@ -82,8 +82,10 @@ add_iceberg_test(table_test
json_internal_test.cc
pending_update_test.cc
schema_json_test.cc
- table_metadata_builder_test.cc
table_test.cc
+ table_metadata_builder_test.cc
+ table_requirement_test.cc
+ table_update_test.cc
test_common.cc)
add_iceberg_test(expression_test
diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build
index 6fbe82d..4e5d30a 100644
--- a/src/iceberg/test/meson.build
+++ b/src/iceberg/test/meson.build
@@ -48,7 +48,9 @@ iceberg_tests = {
'json_internal_test.cc',
'schema_json_test.cc',
'table_metadata_builder_test.cc',
+ 'table_requirement_test.cc',
'table_test.cc',
+ 'table_update_test.cc',
'test_common.cc',
),
},
diff --git a/src/iceberg/test/table_metadata_builder_test.cc
b/src/iceberg/test/table_metadata_builder_test.cc
index 9b98048..f49a60d 100644
--- a/src/iceberg/test/table_metadata_builder_test.cc
+++ b/src/iceberg/test/table_metadata_builder_test.cc
@@ -20,62 +20,40 @@
#include <memory>
#include <string>
-#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include "iceberg/partition_spec.h"
+#include "iceberg/snapshot.h"
#include "iceberg/sort_order.h"
#include "iceberg/table_metadata.h"
-#include "iceberg/table_requirement.h"
-#include "iceberg/table_requirements.h"
#include "iceberg/table_update.h"
#include "iceberg/test/matchers.h"
namespace iceberg {
-// Helper functions to reduce test boilerplate
namespace {
-// Generate requirements and return them
-std::vector<std::unique_ptr<TableRequirement>> GenerateRequirements(
- const TableUpdate& update, const TableMetadata* base) {
- TableUpdateContext context(base, /*is_replace=*/false);
- EXPECT_THAT(update.GenerateRequirements(context), IsOk());
-
- auto requirements = context.Build();
- EXPECT_THAT(requirements, IsOk());
- return std::move(requirements.value());
+// Helper function to create base metadata for tests
+std::unique_ptr<TableMetadata> CreateBaseMetadata() {
+ auto metadata = std::make_unique<TableMetadata>();
+ metadata->format_version = 2;
+ metadata->table_uuid = "test-uuid-1234";
+ metadata->location = "s3://bucket/test";
+ metadata->last_sequence_number = 0;
+ metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)};
+ metadata->last_column_id = 0;
+ metadata->default_spec_id = PartitionSpec::kInitialSpecId;
+ metadata->last_partition_id = 0;
+ metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
+ metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
+ metadata->next_row_id = TableMetadata::kInitialRowId;
+ return metadata;
}
} // namespace
-// Test fixture for TableMetadataBuilder tests
-class TableMetadataBuilderTest : public ::testing::Test {
- protected:
- void SetUp() override {
- // Create a base metadata for update tests
- base_metadata_ = std::make_unique<TableMetadata>();
- base_metadata_->format_version = 2;
- base_metadata_->table_uuid = "test-uuid-1234";
- base_metadata_->location = "s3://bucket/test";
- base_metadata_->last_sequence_number = 0;
- base_metadata_->last_updated_ms =
TimePointMs{std::chrono::milliseconds(1000)};
- base_metadata_->last_column_id = 0;
- base_metadata_->default_spec_id = PartitionSpec::kInitialSpecId;
- base_metadata_->last_partition_id = 0;
- base_metadata_->current_snapshot_id = Snapshot::kInvalidSnapshotId;
- base_metadata_->default_sort_order_id = SortOrder::kInitialSortOrderId;
- base_metadata_->next_row_id = TableMetadata::kInitialRowId;
- }
-
- std::unique_ptr<TableMetadata> base_metadata_;
-};
-
-// ============================================================================
-// TableMetadataBuilder - Basic Construction Tests
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest, BuildFromEmpty) {
+// test construction of TableMetadataBuilder
+TEST(TableMetadataBuilderTest, BuildFromEmpty) {
auto builder = TableMetadataBuilder::BuildFromEmpty(2);
ASSERT_NE(builder, nullptr);
@@ -91,8 +69,9 @@ TEST_F(TableMetadataBuilderTest, BuildFromEmpty) {
EXPECT_EQ(metadata->current_snapshot_id, Snapshot::kInvalidSnapshotId);
}
-TEST_F(TableMetadataBuilderTest, BuildFromExisting) {
- auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get());
+TEST(TableMetadataBuilderTest, BuildFromExisting) {
+ auto base = CreateBaseMetadata();
+ auto builder = TableMetadataBuilder::BuildFrom(base.get());
ASSERT_NE(builder, nullptr);
ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
@@ -103,345 +82,59 @@ TEST_F(TableMetadataBuilderTest, BuildFromExisting) {
EXPECT_EQ(metadata->location, "s3://bucket/test");
}
-// ============================================================================
-// TableMetadataBuilder - AssignUUID Tests
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest, AssignUUIDForNewTable) {
+// Test AssignUUID method
+TEST(TableMetadataBuilderTest, AssignUUID) {
+ // Assign UUID for new table
auto builder = TableMetadataBuilder::BuildFromEmpty(2);
builder->AssignUUID("new-uuid-5678");
-
ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
EXPECT_EQ(metadata->table_uuid, "new-uuid-5678");
-}
-TEST_F(TableMetadataBuilderTest, AssignUUIDAndUpdateExisting) {
- auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get());
+ // Update existing table's UUID
+ auto base = CreateBaseMetadata();
+ builder = TableMetadataBuilder::BuildFrom(base.get());
builder->AssignUUID("updated-uuid-9999");
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
+ ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build());
EXPECT_EQ(metadata->table_uuid, "updated-uuid-9999");
-}
-TEST_F(TableMetadataBuilderTest, AssignUUIDWithEmptyUUID) {
- auto builder = TableMetadataBuilder::BuildFromEmpty(2);
+ // Empty UUID should fail
+ builder = TableMetadataBuilder::BuildFromEmpty(2);
builder->AssignUUID("");
-
ASSERT_THAT(builder->Build(), HasErrorMessage("Cannot assign empty UUID"));
-}
-TEST_F(TableMetadataBuilderTest, AssignUUIDWithSameUUID) {
- auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get());
- builder->AssignUUID("test-uuid-1234"); // Same UUID
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
+ // Assign same UUID (no-op)
+ base = CreateBaseMetadata();
+ builder = TableMetadataBuilder::BuildFrom(base.get());
+ builder->AssignUUID("test-uuid-1234");
+ ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build());
EXPECT_EQ(metadata->table_uuid, "test-uuid-1234");
-}
-TEST_F(TableMetadataBuilderTest, AssignUUIDWithAutoGenerate) {
- auto builder = TableMetadataBuilder::BuildFromEmpty(2);
- builder->AssignUUID(); // Auto-generate
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
+ // Auto-generate UUID
+ builder = TableMetadataBuilder::BuildFromEmpty(2);
+ builder->AssignUUID();
+ ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build());
EXPECT_FALSE(metadata->table_uuid.empty());
-}
-TEST_F(TableMetadataBuilderTest, AssignUUIDAndCaseInsensitiveComparison) {
- base_metadata_->table_uuid = "TEST-UUID-ABCD";
- auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get());
+ // Case insensitive comparison
+ base = CreateBaseMetadata();
+ base->table_uuid = "TEST-UUID-ABCD";
+ builder = TableMetadataBuilder::BuildFrom(base.get());
builder->AssignUUID("test-uuid-abcd"); // Different case - should be no-op
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
+ ICEBERG_UNWRAP_OR_FAIL(metadata, builder->Build());
EXPECT_EQ(metadata->table_uuid, "TEST-UUID-ABCD"); // Original case
preserved
}
-// ============================================================================
-// TableUpdate - ApplyTo Tests
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest, TableUpdateWithAssignUUID) {
+// Test applying TableUpdate to builder
+TEST(TableMetadataBuilderTest, ApplyUpdate) {
+ // Apply AssignUUID update
auto builder = TableMetadataBuilder::BuildFromEmpty(2);
-
table::AssignUUID update("apply-uuid");
update.ApplyTo(*builder);
+ // TODO(Li Feiyang): Add more update and `apply` once other build methods are
+ // implemented
ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
EXPECT_EQ(metadata->table_uuid, "apply-uuid");
}
-// ============================================================================
-// TableUpdate - GenerateRequirements Tests
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest,
- TableUpdateWithAssignUUIDAndGenerateRequirementsForNewTable) {
- table::AssignUUID update("new-uuid");
-
- auto requirements = GenerateRequirements(update, nullptr);
- EXPECT_TRUE(requirements.empty()); // No requirements for new table
-}
-
-TEST_F(TableMetadataBuilderTest,
- TableUpdateWithAssignUUIDAndGenerateRequirementsForExistingTable) {
- table::AssignUUID update("new-uuid");
-
- auto requirements = GenerateRequirements(update, base_metadata_.get());
- EXPECT_EQ(requirements.size(), 1); // Should generate AssertUUID requirement
-}
-
-TEST_F(TableMetadataBuilderTest,
- TableUpdateWithAssignUUIDAndGenerateRequirementsWithEmptyUUID) {
- base_metadata_->table_uuid = "";
- table::AssignUUID update("new-uuid");
-
- auto requirements = GenerateRequirements(update, base_metadata_.get());
- EXPECT_TRUE(requirements.empty()); // No requirement when base has no UUID
-}
-
-// ============================================================================
-// TableRequirement - Validate Tests
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDSuccess) {
- table::AssertUUID requirement("test-uuid-1234");
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDMismatch) {
- table::AssertUUID requirement("wrong-uuid");
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("UUID does not match"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDNullBase) {
- table::AssertUUID requirement("any-uuid");
-
- auto status = requirement.Validate(nullptr);
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertUUIDCaseInsensitive) {
- base_metadata_->table_uuid = "TEST-UUID-1234";
- table::AssertUUID requirement("test-uuid-1234");
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDSuccess)
{
- base_metadata_->current_schema_id = 5;
- table::AssertCurrentSchemaID requirement(5);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertCurrentSchemaIDMismatch) {
- base_metadata_->current_schema_id = 5;
- table::AssertCurrentSchemaID requirement(10);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("schema ID does not match"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertCurrentSchemaIDNullBase) {
- table::AssertCurrentSchemaID requirement(5);
-
- auto status = requirement.Validate(nullptr);
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertCurrentSchemaIDNotSet) {
- base_metadata_->current_schema_id = std::nullopt;
- table::AssertCurrentSchemaID requirement(5);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("schema ID is not set"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertDoesNotExistSuccess) {
- table::AssertDoesNotExist requirement;
-
- ASSERT_THAT(requirement.Validate(nullptr), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertDoesNotExistTableExists) {
- table::AssertDoesNotExist requirement;
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("table already exists"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertRefSnapshotIDSuccess) {
- auto ref = std::make_shared<SnapshotRef>();
- ref->snapshot_id = 100;
- ref->retention = SnapshotRef::Branch{};
- base_metadata_->refs["main"] = ref;
-
- table::AssertRefSnapshotID requirement("main", 100);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertRefSnapshotIDMismatch) {
- auto ref = std::make_shared<SnapshotRef>();
- ref->snapshot_id = 100;
- ref->retention = SnapshotRef::Branch{};
- base_metadata_->refs["main"] = ref;
-
- table::AssertRefSnapshotID requirement("main", 200);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("has changed"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertRefSnapshotIDRefMissing) {
- table::AssertRefSnapshotID requirement("missing-ref", 100);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("is missing"));
-}
-
-// Removed TableRequirementAssertRefSnapshotIDNullBase test
-// Java implementation doesn't check for null base, so passing nullptr would
cause
-// undefined behavior This matches Java's assumption that base is never null
when Validate
-// is called
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertRefSnapshotIDNulloptSuccess) {
- // Ref should not exist, and it doesn't
- table::AssertRefSnapshotID requirement("nonexistent", std::nullopt);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertRefSnapshotIDNulloptButExists) {
- auto ref = std::make_shared<SnapshotRef>();
- ref->snapshot_id = 100;
- ref->retention = SnapshotRef::Branch{};
- base_metadata_->refs["main"] = ref;
-
- table::AssertRefSnapshotID requirement("main", std::nullopt);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("created concurrently"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedFieldIdSuccess) {
- base_metadata_->last_column_id = 10;
- table::AssertLastAssignedFieldId requirement(10);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedFieldIdMismatch) {
- base_metadata_->last_column_id = 10;
- table::AssertLastAssignedFieldId requirement(15);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("last assigned field ID does not
match"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedFieldIdNullBase) {
- table::AssertLastAssignedFieldId requirement(10);
-
- EXPECT_THAT(requirement.Validate(nullptr), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedPartitionIdSuccess) {
- base_metadata_->last_partition_id = 5;
- table::AssertLastAssignedPartitionId requirement(5);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedPartitionIdMismatch) {
- base_metadata_->last_partition_id = 5;
- table::AssertLastAssignedPartitionId requirement(8);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("last assigned partition ID does not
match"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertLastAssignedPartitionIdNullBase) {
- table::AssertLastAssignedPartitionId requirement(5);
-
- auto status = requirement.Validate(nullptr);
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertDefaultSpecIDSuccess) {
- base_metadata_->default_spec_id = 3;
- table::AssertDefaultSpecID requirement(3);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest, TableRequirementAssertDefaultSpecIDMismatch) {
- base_metadata_->default_spec_id = 3;
- table::AssertDefaultSpecID requirement(7);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("spec changed"));
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertDefaultSortOrderIDSuccess) {
- base_metadata_->default_sort_order_id = 2;
- table::AssertDefaultSortOrderID requirement(2);
-
- ASSERT_THAT(requirement.Validate(base_metadata_.get()), IsOk());
-}
-
-TEST_F(TableMetadataBuilderTest,
TableRequirementAssertDefaultSortOrderIDMismatch) {
- base_metadata_->default_sort_order_id = 2;
- table::AssertDefaultSortOrderID requirement(4);
-
- auto status = requirement.Validate(base_metadata_.get());
- EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
- EXPECT_THAT(status, HasErrorMessage("sort order changed"));
-}
-
-// ============================================================================
-// Integration Tests - End-to-End Workflow
-// ============================================================================
-
-TEST_F(TableMetadataBuilderTest, IntegrationCreateTableWithUUID) {
- auto builder = TableMetadataBuilder::BuildFromEmpty(2);
- builder->AssignUUID("integration-test-uuid");
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
- EXPECT_EQ(metadata->table_uuid, "integration-test-uuid");
- EXPECT_EQ(metadata->format_version, 2);
-}
-
-TEST_F(TableMetadataBuilderTest, IntegrationOptimisticConcurrencyControl) {
- table::AssignUUID update("new-uuid");
-
- // Generate and validate requirements
- auto requirements = GenerateRequirements(update, base_metadata_.get());
- for (const auto& req : requirements) {
- auto val_status = req->Validate(base_metadata_.get());
- ASSERT_THAT(val_status, IsOk()) << "Requirement validation failed";
- }
-
- // Apply update and build
- auto builder = TableMetadataBuilder::BuildFrom(base_metadata_.get());
- update.ApplyTo(*builder);
-
- ICEBERG_UNWRAP_OR_FAIL(auto metadata, builder->Build());
- ASSERT_NE(metadata, nullptr);
-}
-
} // namespace iceberg
diff --git a/src/iceberg/test/table_requirement_test.cc
b/src/iceberg/test/table_requirement_test.cc
new file mode 100644
index 0000000..24af87d
--- /dev/null
+++ b/src/iceberg/test/table_requirement_test.cc
@@ -0,0 +1,203 @@
+/*
+ * 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 "iceberg/table_requirement.h"
+
+#include <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "iceberg/snapshot.h"
+#include "iceberg/table_metadata.h"
+#include "iceberg/test/matchers.h"
+
+namespace iceberg {
+
+TEST(TableRequirementTest, AssertUUID) {
+ auto base = std::make_unique<TableMetadata>();
+ base->table_uuid = "test-uuid-1234";
+
+ // Success - UUID matches
+ table::AssertUUID requirement("test-uuid-1234");
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // UUID mismatch
+ table::AssertUUID wrong_uuid("wrong-uuid");
+ auto status = wrong_uuid.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("UUID does not match"));
+
+ // Null base metadata
+ table::AssertUUID any_uuid("any-uuid");
+ status = any_uuid.Validate(nullptr);
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
+
+ // Case insensitive UUID comparison
+ base->table_uuid = "TEST-UUID-1234";
+ table::AssertUUID lowercase_uuid("test-uuid-1234");
+ ASSERT_THAT(lowercase_uuid.Validate(base.get()), IsOk());
+}
+
+TEST(TableRequirementTest, AssertCurrentSchemaID) {
+ auto base = std::make_unique<TableMetadata>();
+ base->current_schema_id = 5;
+
+ // Success - schema ID matches
+ table::AssertCurrentSchemaID requirement(5);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Schema ID mismatch
+ table::AssertCurrentSchemaID wrong_id(10);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("schema ID does not match"));
+
+ // Null base metadata
+ table::AssertCurrentSchemaID req_for_null(5);
+ status = req_for_null.Validate(nullptr);
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
+
+ // Schema ID not set
+ base->current_schema_id = std::nullopt;
+ table::AssertCurrentSchemaID req_for_nullopt(5);
+ status = req_for_nullopt.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("schema ID is not set"));
+}
+
+TEST(TableRequirementTest, AssertDoesNotExist) {
+ // Success - table does not exist (null metadata)
+ table::AssertDoesNotExist requirement;
+ ASSERT_THAT(requirement.Validate(nullptr), IsOk());
+
+ // Table already exists
+ auto base = std::make_unique<TableMetadata>();
+ auto status = requirement.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("table already exists"));
+}
+
+TEST(TableRequirementTest, AssertRefSnapshotID) {
+ auto base = std::make_unique<TableMetadata>();
+ auto ref = std::make_shared<SnapshotRef>();
+ ref->snapshot_id = 100;
+ ref->retention = SnapshotRef::Branch{};
+ base->refs["main"] = ref;
+
+ // Success - ref snapshot ID matches
+ table::AssertRefSnapshotID requirement("main", 100);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Snapshot ID mismatch
+ table::AssertRefSnapshotID wrong_id("main", 200);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("has changed"));
+
+ // Ref missing
+ table::AssertRefSnapshotID missing_ref("missing-ref", 100);
+ status = missing_ref.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("is missing"));
+
+ // Ref should not exist and doesn't (nullopt snapshot ID)
+ table::AssertRefSnapshotID nonexistent("nonexistent", std::nullopt);
+ ASSERT_THAT(nonexistent.Validate(base.get()), IsOk());
+
+ // Ref should not exist but does (nullopt snapshot ID but ref exists)
+ table::AssertRefSnapshotID exists_but_shouldnt("main", std::nullopt);
+ status = exists_but_shouldnt.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("created concurrently"));
+}
+
+TEST(TableRequirementTest, AssertLastAssignedFieldId) {
+ auto base = std::make_unique<TableMetadata>();
+ base->last_column_id = 10;
+
+ // Success - field ID matches
+ table::AssertLastAssignedFieldId requirement(10);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Field ID mismatch
+ table::AssertLastAssignedFieldId wrong_id(15);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("last assigned field ID does not
match"));
+
+ // Null base metadata (should succeed)
+ table::AssertLastAssignedFieldId req_for_null(10);
+ EXPECT_THAT(req_for_null.Validate(nullptr), IsOk());
+}
+
+TEST(TableRequirementTest, AssertLastAssignedPartitionId) {
+ auto base = std::make_unique<TableMetadata>();
+ base->last_partition_id = 5;
+
+ // Success - partition ID matches
+ table::AssertLastAssignedPartitionId requirement(5);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Partition ID mismatch
+ table::AssertLastAssignedPartitionId wrong_id(8);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("last assigned partition ID does not
match"));
+
+ // Null base metadata
+ table::AssertLastAssignedPartitionId req_for_null(5);
+ status = req_for_null.Validate(nullptr);
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("metadata is missing"));
+}
+
+TEST(TableRequirementTest, AssertDefaultSpecID) {
+ auto base = std::make_unique<TableMetadata>();
+ base->default_spec_id = 3;
+
+ // Success - spec ID matches
+ table::AssertDefaultSpecID requirement(3);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Spec ID mismatch
+ table::AssertDefaultSpecID wrong_id(7);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("spec changed"));
+}
+
+TEST(TableRequirementTest, AssertDefaultSortOrderID) {
+ auto base = std::make_unique<TableMetadata>();
+ base->default_sort_order_id = 2;
+
+ // Success - sort order ID matches
+ table::AssertDefaultSortOrderID requirement(2);
+ ASSERT_THAT(requirement.Validate(base.get()), IsOk());
+
+ // Sort order ID mismatch
+ table::AssertDefaultSortOrderID wrong_id(4);
+ auto status = wrong_id.Validate(base.get());
+ EXPECT_THAT(status, IsError(ErrorKind::kCommitFailed));
+ EXPECT_THAT(status, HasErrorMessage("sort order changed"));
+}
+
+} // namespace iceberg
diff --git a/src/iceberg/test/table_update_test.cc
b/src/iceberg/test/table_update_test.cc
new file mode 100644
index 0000000..9607db5
--- /dev/null
+++ b/src/iceberg/test/table_update_test.cc
@@ -0,0 +1,89 @@
+/*
+ * 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 "iceberg/table_update.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "iceberg/partition_spec.h"
+#include "iceberg/snapshot.h"
+#include "iceberg/sort_order.h"
+#include "iceberg/table_metadata.h"
+#include "iceberg/table_requirement.h"
+#include "iceberg/table_requirements.h"
+#include "iceberg/test/matchers.h"
+
+namespace iceberg {
+
+namespace {
+
+// Helper function to generate requirements
+std::vector<std::unique_ptr<TableRequirement>> GenerateRequirements(
+ const TableUpdate& update, const TableMetadata* base) {
+ TableUpdateContext context(base, /*is_replace=*/false);
+ EXPECT_THAT(update.GenerateRequirements(context), IsOk());
+
+ auto requirements = context.Build();
+ EXPECT_THAT(requirements, IsOk());
+ return std::move(requirements.value());
+}
+
+// Helper function to create base metadata for tests
+std::unique_ptr<TableMetadata> CreateBaseMetadata() {
+ auto metadata = std::make_unique<TableMetadata>();
+ metadata->format_version = 2;
+ metadata->table_uuid = "test-uuid-1234";
+ metadata->location = "s3://bucket/test";
+ metadata->last_sequence_number = 0;
+ metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds(1000)};
+ metadata->last_column_id = 0;
+ metadata->default_spec_id = PartitionSpec::kInitialSpecId;
+ metadata->last_partition_id = 0;
+ metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId;
+ metadata->default_sort_order_id = SortOrder::kInitialSortOrderId;
+ metadata->next_row_id = TableMetadata::kInitialRowId;
+ return metadata;
+}
+
+} // namespace
+
+// Test GenerateRequirements for AssignUUID update
+TEST(TableUpdateTest, AssignUUIDGenerateRequirements) {
+ table::AssignUUID update("new-uuid");
+
+ // New table - no requirements
+ auto new_table_reqs = GenerateRequirements(update, nullptr);
+ EXPECT_TRUE(new_table_reqs.empty());
+
+ // Existing table - should generate AssertUUID requirement
+ auto base = CreateBaseMetadata();
+ auto existing_table_reqs = GenerateRequirements(update, base.get());
+ EXPECT_EQ(existing_table_reqs.size(), 1);
+
+ // Existing table with empty UUID - no requirements
+ base->table_uuid = "";
+ auto empty_uuid_reqs = GenerateRequirements(update, base.get());
+ EXPECT_TRUE(empty_uuid_reqs.empty());
+}
+
+} // namespace iceberg