Skip to content

Commit 5cc30ae

Browse files
committed
Fix DynamoDB listing assertion
1 parent 2b9923c commit 5cc30ae

3 files changed

Lines changed: 136 additions & 8 deletions

File tree

.github/workflows/build.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ jobs:
4343
- name: Set up pinned Unity Catalog
4444
uses: ./.github/actions/setup-unitycatalog
4545

46+
- name: Run DynamoDB commit coordinator listing regression tests
47+
run: |
48+
cd spark/src/main/java/io/delta/dynamodbcommitcoordinator/integration_tests
49+
python _dynamodb_commitcoordinator_listing.py
50+
4651
- name: Run cross-Spark build test
4752
run: python project/tests/test_cross_spark_publish.py
4853

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#
2+
# Copyright (2024) The Delta Lake Project Authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import os
18+
19+
20+
def _is_matching_delta_file(file_name, version):
21+
expected_prefix = f"{version:020}."
22+
return (
23+
file_name.startswith(expected_prefix) and
24+
file_name.endswith(".json") and
25+
".tmp" not in file_name
26+
)
27+
28+
29+
def _find_matching_delta_files(items, version):
30+
matching_files = []
31+
for item in items:
32+
file_name = os.path.basename(item["Key"])
33+
if _is_matching_delta_file(file_name, version):
34+
matching_files.append(file_name)
35+
return matching_files
36+
37+
38+
def validate_delta_file_listing_response(response, listing_prefix, version, should_exist):
39+
if 'Contents' not in response:
40+
assert not should_exist, (
41+
f"Listing for prefix {listing_prefix} did not return any files even though it "
42+
f"should have."
43+
)
44+
return
45+
46+
expected_count = 1 if should_exist else 0
47+
matching_files = _find_matching_delta_files(response['Contents'], version)
48+
assert len(matching_files) == expected_count, (
49+
f"Expected {expected_count} matching files for version {version} under prefix "
50+
f"{listing_prefix}, but found {len(matching_files)}: {matching_files}"
51+
)
52+
53+
54+
def test_should_fail_if_should_exist_is_true_and_s3_listing_lacks_contents():
55+
test_name = "it('should fail if should_exist is true and S3 listing lacks Contents')"
56+
try:
57+
validate_delta_file_listing_response(
58+
response={},
59+
listing_prefix="tables/test/_delta_log/00000000000000000001.",
60+
version=1,
61+
should_exist=True
62+
)
63+
except AssertionError as error:
64+
assert "did not return any files" in str(error), (
65+
f"{test_name}: unexpected assertion message: {error}"
66+
)
67+
return
68+
69+
raise AssertionError(f"{test_name}: expected missing Contents to fail")
70+
71+
72+
def test_should_match_delta_json_files_if_version_has_uuid_suffix():
73+
test_name = "it('should match Delta JSON files if version has UUID suffix')"
74+
response = {
75+
'Contents': [
76+
{'Key': 'tables/test/_delta_log/_staged_commits/00000000000000000001.uuid.json'},
77+
{'Key': 'tables/test/_delta_log/_staged_commits/00000000000000000001.uuid.json.tmp'}
78+
]
79+
}
80+
81+
try:
82+
validate_delta_file_listing_response(
83+
response=response,
84+
listing_prefix="tables/test/_delta_log/_staged_commits/00000000000000000001.",
85+
version=1,
86+
should_exist=True
87+
)
88+
except AssertionError as error:
89+
raise AssertionError(f"{test_name}: unexpected assertion: {error}")
90+
91+
92+
def test_should_fail_if_should_exist_is_true_and_matching_file_has_non_json_suffix():
93+
test_name = (
94+
"it('should fail if should_exist is true and matching file has non-json suffix')"
95+
)
96+
try:
97+
validate_delta_file_listing_response(
98+
response={
99+
'Contents': [
100+
{'Key': 'tables/test/_delta_log/00000000000000000001.json.backup'}
101+
]
102+
},
103+
listing_prefix="tables/test/_delta_log/00000000000000000001.",
104+
version=1,
105+
should_exist=True
106+
)
107+
except AssertionError as error:
108+
assert "Expected 1 matching files for version 1" in str(error), (
109+
f"{test_name}: unexpected assertion message: {error}"
110+
)
111+
return
112+
113+
raise AssertionError(f"{test_name}: expected non-json suffix to fail")
114+
115+
116+
def run_listing_validation_self_tests():
117+
test_should_fail_if_should_exist_is_true_and_s3_listing_lacks_contents()
118+
test_should_match_delta_json_files_if_version_has_uuid_suffix()
119+
test_should_fail_if_should_exist_is_true_and_matching_file_has_non_json_suffix()
120+
121+
122+
if __name__ == "__main__":
123+
run_listing_validation_self_tests()

spark/src/main/java/io/delta/dynamodbcommitcoordinator/integration_tests/dynamodb_commitcoordinator_integration_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
import boto3
2626
import uuid
2727

28+
from _dynamodb_commitcoordinator_listing import (
29+
run_listing_validation_self_tests,
30+
validate_delta_file_listing_response
31+
)
32+
2833
"""
2934
3035
Run this script in root dir of repository:
@@ -100,6 +105,8 @@
100105
commit_coordinator_property_key = "coordinatedCommits.commitCoordinator"
101106
property_key_suffix = "-preview"
102107

108+
run_listing_validation_self_tests()
109+
103110
spark = SparkSession \
104111
.builder \
105112
.appName("utilities") \
@@ -197,14 +204,7 @@ def check_for_delta_file_in_filesystem(delta_table_path, version, is_backfilled,
197204
listing_prefix = os.path.join(relative_commit_folder_path, f"{version:020}.").lstrip("/")
198205
print(f"querying {listing_prefix} from bucket {s3_bucket} for version {version}")
199206
response = s3_client.list_objects_v2(Bucket=s3_bucket, Prefix=listing_prefix)
200-
if 'Contents' not in response:
201-
assert(not should_exist, f"Listing for prefix {listing_prefix} did not return any files even though it should have.")
202-
return
203-
items = response['Contents']
204-
commits = filter(lambda key: ".json" in key and ".tmp" not in key, map(lambda x: os.path.basename(x['Key']), items))
205-
expected_count = 1 if should_exist else 0
206-
matching_files = list(filter(lambda key: key.split('.')[0].endswith(f"{version:020}"), commits))
207-
assert(len(matching_files) == expected_count)
207+
validate_delta_file_listing_response(response, listing_prefix, version, should_exist)
208208

209209
def test_downgrades_and_upgrades(delta_table_path, delta_table_version):
210210
# Downgrade to filesystem based commits should work

0 commit comments

Comments
 (0)