blobstore: Add the conformance test for large file upload with 16mb - #465
blobstore: Add the conformance test for large file upload with 16mb#465LihaoLiuXs wants to merge 6 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #465 +/- ##
============================================
- Coverage 81.67% 81.58% -0.10%
Complexity 652 652
============================================
Files 204 204
Lines 13477 13523 +46
Branches 1774 1791 +17
============================================
+ Hits 11008 11033 +25
- Misses 1686 1700 +14
- Partials 783 790 +7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
We shouldn't commit large files. It should use regex pattern similar to other files.
There was a problem hiding this comment.
Removed the large 16mb file from commit.
… binaries
Per Sandeep's review: switch from random-looking deterministic content
(byte = i % 251) to a single repeating byte ('A'), matching the convention
used by testMultipartUpload_multipleParts. This makes the recorded WireMock
fixtures compress trivially in git pack:
- Replace bodyFileName + 16 MiB binary in __files/ with inline body strings
in the mapping JSON. Two binary files removed.
- AWS GET-1: inline 16 MiB of 'A's as response.body, drop the
x-amz-transfer-encoding=append-md5 header (no MD5 trailer in fixture),
recompute ETag.
- GCP GET-2: inline 16 MiB of 'A's as response.body.
- GCP PUT-4 and PUT-5 retain WireMock-recorded bodyPatterns (regex prefix)
and scenario-state ordering for the resumable-upload chunk → finalize
sequence.
- AWS PUT-2: bodyPatterns omitted because WireMock records binary AWS
upload bodies as base64 and cannot reverse-encode them in replay
matching. This is consistent with how AWS test fixtures handle large
binary uploads.
On-disk fixture footprint is ~32 MB (mostly the inlined 16 MiB 'A' bodies),
but git-pack compressed size is ~36 KB total.
Both AWS and GCP replay verified passing.
Per Sandeep's review — avoid committing large files to git. Instead of inlining the full 16MB 'A'-filled response body in the WireMock mapping JSON, use a compact [GENERATED:A:16777216] marker that is expanded at serve-time by a new GeneratedResponseBodyTransformer. This reduces the AWS/GCP GET mapping files from 16MB to under 2KB each while preserving full byte-for-byte validation in replay mode.
| * {@code [GENERATED:A:16777216]}) will be expanded into a body filled with the specified | ||
| * character repeated {@code size} times. | ||
| */ | ||
| public static class GeneratedResponseBodyTransformer extends ResponseDefinitionTransformer { |
There was a problem hiding this comment.
little confused on why we need new transformer ? our MPU tests already have >5MB blobs and regex are already getting used through transformer
There was a problem hiding this comment.
The existing MPU regex in bodyPatterns handles request body matching (validates the upload payload in the PUT). This transformer handles response body generation on the GET/download side — for 16 MiB, storing the literal response body in the mapping JSON would blow up the repo, so the [GENERATED:A:16777216] marker keeps the mapping tiny and expands at serve-time. Different concern from the upload-path regex.
There was a problem hiding this comment.
why do we care but response body > 16mb, this is the upload task
iamabhilaksh
left a comment
There was a problem hiding this comment.
A couple of things before this lands — none are blocking the test logic itself.
Scope creep in .gitignore: the .agents/ and .gus/ lines look unrelated to the 16MiB conformance test. Could you drop those into a separate PR (or just from this one)? Easier to revert the test-only change later if needed.
Re: Sandeep's transformer question — [Thinking out loud] I think the existing MPU regex in the WireMock setup matches the upload request path, while the new Authorization-stripping transformer is rewriting the GET response body on download. Different concerns, so I don't think they conflict — but worth a one-liner reply from you to confirm I'm reading it right.
SriramGuduri
left a comment
There was a problem hiding this comment.
Clean, well-documented conformance test that guards against the GCP large byte[] upload truncation regression. The GeneratedResponseBodyTransformer is a clever pattern to keep WireMock fixtures small in git while still exercising the full 16 MiB payload path. Approve — no blocking issues.
| public ResponseDefinition transform( | ||
| Request request, | ||
| ResponseDefinition responseDefinition, | ||
| FileSource files, |
There was a problem hiding this comment.
nitpick: Integer.parseInt will throw NumberFormatException if the size value overflows int (>2^31-1). For a test utility this is fine since fixture content is developer-authored, but a descriptive error message might save debugging time vs. a raw NFE.
| Request request, | ||
| ResponseDefinition responseDefinition, | ||
| FileSource files, | ||
| Parameters parameters) { |
There was a problem hiding this comment.
suggestion: Allocating a char[] of 16 MiB (32 MB on heap since char is 2 bytes) + converting to String (another copy) means ~64 MB of transient heap per response expansion. Consider using byte[] and withBody(byte[]) if WireMock supports it — halves the allocation. Not blocking since tests typically have generous heap, but worth noting for future larger payloads.
| content.length, | ||
| downloaded.length, | ||
| testName + ": Content-Length did not match (truncation regression)"); | ||
| Assertions.assertArrayEquals( |
There was a problem hiding this comment.
nitpick: try (outputStream) around a ByteArrayOutputStream is unnecessary since its close() is a no-op and toByteArray() is called after the block anyway. Removing the try-with-resources would simplify the code without changing behavior.
| .claude/* | ||
| !.claude/skills/ | ||
| .agents/ | ||
| .gus/ |
There was a problem hiding this comment.
nitpick: Adding .agents/ and .gus/ is unrelated to the blobstore test change. Consider splitting into a separate commit for cleaner history, or mention in PR description.
|
@sandeepvinayak Per your review feedback — the 16MB inline response bodies have been replaced with compact Changes in this revision:
The pattern follows the same spirit as the existing |
- Use byte[] instead of char[]/String in GeneratedResponseBodyTransformer to halve heap allocation (~32MB vs ~64MB for 16MiB payloads) - Add descriptive error for size overflow in GENERATED body marker - Remove unnecessary try-with-resources around ByteArrayOutputStream - Remove unrelated .agents/ and .gus/ entries from .gitignore
| /** | ||
| * Regression test for the GCP large byte[] upload truncation bug. Uploads a 16 MiB byte array via | ||
| * {@link BucketClient#upload(UploadRequest, byte[])} with only the fields that callers like | ||
| * CoreStash use (key + contentLength + base64 CRC32C checksum), then downloads and verifies the | ||
| * full payload round-trips byte-for-byte. Before the fix, the GCP byte[] path used | ||
| * WriteChannel#write(ByteBuffer) which violates the resumable-upload chunking contract above | ||
| * ~6 MiB and silently truncated the object. | ||
| * | ||
| * <p>Scoped to AWS and GCP — the original bug was provider-specific to GCP's resumable-upload | ||
| * implementation, and AWS coverage guards the byte[] path on S3 against a similar regression. | ||
| * Alibaba is excluded because recording 16 MiB fixtures for every provider triples test-resource | ||
| * size with no additional bug coverage. | ||
| * | ||
| * <p>Fixture convention: payload is filled with a single repeating byte ({@code 'A'}) so | ||
| * that the recorded WireMock {@code bodyPatterns} compress trivially in git pack (matching | ||
| * the {@code testMultipartUpload_multipleParts} convention). Response body mappings use the | ||
| * {@code [GENERATED:A:16777216]} marker expanded at serve-time by | ||
| * {@code TestsUtil.GeneratedResponseBodyTransformer} to avoid committing large files. This | ||
| * keeps recorded fixtures small in the git object store while preserving full body-match | ||
| * validation in replay mode. | ||
| */ |
There was a problem hiding this comment.
what's this paragraph, it was local AI not meant to retain ? :D Let's remove
| if (!matcher.matches()) { | ||
| return responseDefinition; | ||
| } | ||
| byte fillByte = (byte) matcher.group(1).charAt(0); |
There was a problem hiding this comment.
[Thinking out loud] The regex at L249 matches (.) — any single character — but the cast (byte) matcher.group(1).charAt(0) here silently truncates anything outside ASCII to its low 8 bits. For example [GENERATED:é:1024] would compile fine, regex-match fine, then quietly fill the body with (byte) 0xE9. Our only real caller is A, so this is purely theoretical today, but I'm wondering if we should either tighten the regex to [ -~] (printable ASCII) or note in the Javadoc that the marker is ASCII-only — keeps a future contributor from getting a confusing fixture mismatch. Happy either way. What's your take?
iamabhilaksh
left a comment
There was a problem hiding this comment.
Single non-blocking observation inline on the new [GENERATED:c:N] body transformer — the regex accepts any character but the byte cast is ASCII-only, so worth either tightening the regex or noting the ASCII contract in the Javadoc. Otherwise the post-review changes look clean.
Summary
< Provide a brief description of the changes in this PR >
Some conventions to follow
docstore:for document store module,blobstorefor Blob Store moduletest:perf: