Skip to content

Commit 495d1b8

Browse files
authored
Merge pull request #14393 from lovesegfault/s3-multipart-tests
test(nixos): add S3 multipart upload integration tests
2 parents 66d7b8f + 94965a3 commit 495d1b8

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

tests/nixos/s3-binary-cache-store.nix

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ in
3434
pkgA
3535
pkgB
3636
pkgC
37+
pkgs.coreutils
3738
];
3839
environment.systemPackages = [ pkgs.minio-client ];
40+
nix.nixPath = [ "nixpkgs=${pkgs.path}" ];
3941
nix.extraOptions = ''
4042
experimental-features = nix-command
4143
substituters =
@@ -639,6 +641,129 @@ in
639641
)
640642
print(" ✓ Fetch with versionId parameter works")
641643
644+
@setup_s3()
645+
def test_multipart_upload_basic(bucket):
646+
"""Test basic multipart upload with a large file"""
647+
print("\n--- Test: Multipart Upload Basic ---")
648+
649+
large_file_size = 10 * 1024 * 1024
650+
large_pkg = server.succeed(
651+
"nix-store --add $(dd if=/dev/urandom of=/tmp/large-file bs=1M count=10 2>/dev/null && echo /tmp/large-file)"
652+
).strip()
653+
654+
chunk_size = 5 * 1024 * 1024
655+
expected_parts = 3 # 10 MB raw becomes ~10.5 MB compressed (NAR + xz overhead)
656+
657+
store_url = make_s3_url(
658+
bucket,
659+
**{
660+
"multipart-upload": "true",
661+
"multipart-threshold": str(5 * 1024 * 1024),
662+
"multipart-chunk-size": str(chunk_size),
663+
}
664+
)
665+
666+
print(f" Uploading {large_file_size} byte file (expect {expected_parts} parts)")
667+
output = server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {large_pkg} --debug 2>&1")
668+
669+
if "using S3 multipart upload" not in output:
670+
raise Exception("Expected multipart upload to be used")
671+
672+
expected_msg = f"{expected_parts} parts uploaded"
673+
if expected_msg not in output:
674+
print("Debug output:")
675+
print(output)
676+
raise Exception(f"Expected '{expected_msg}' in output")
677+
678+
print(f" ✓ Multipart upload used with {expected_parts} parts")
679+
680+
client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' {large_pkg} --no-check-sigs")
681+
verify_packages_in_store(client, large_pkg, should_exist=True)
682+
683+
print(" ✓ Large file downloaded and verified")
684+
685+
@setup_s3()
686+
def test_multipart_threshold(bucket):
687+
"""Test that files below threshold use regular upload"""
688+
print("\n--- Test: Multipart Threshold Behavior ---")
689+
690+
store_url = make_s3_url(
691+
bucket,
692+
**{
693+
"multipart-upload": "true",
694+
"multipart-threshold": str(1024 * 1024 * 1024),
695+
}
696+
)
697+
698+
print(" Uploading small file with high threshold")
699+
output = server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKGS['A']} --debug 2>&1")
700+
701+
if "using S3 multipart upload" in output:
702+
raise Exception("Should not use multipart for file below threshold")
703+
704+
if "using S3 regular upload" not in output:
705+
raise Exception("Expected regular upload to be used")
706+
707+
print(" ✓ Regular upload used for file below threshold")
708+
709+
client.succeed(f"{ENV_WITH_CREDS} nix copy --no-check-sigs --from '{store_url}' {PKGS['A']}")
710+
verify_packages_in_store(client, PKGS['A'], should_exist=True)
711+
712+
print(" ✓ Small file uploaded and verified")
713+
714+
@setup_s3()
715+
def test_multipart_with_log_compression(bucket):
716+
"""Test multipart upload with compressed build logs"""
717+
print("\n--- Test: Multipart Upload with Log Compression ---")
718+
719+
# Create a derivation that produces a large text log (12 MB of base64 output)
720+
drv_path = server.succeed(
721+
"""
722+
nix-instantiate --expr '
723+
let pkgs = import <nixpkgs> {};
724+
in derivation {
725+
name = "large-log-builder";
726+
builder = "/bin/sh";
727+
args = ["-c" "$coreutils/bin/dd if=/dev/urandom bs=1M count=12 | $coreutils/bin/base64; echo success > $out"];
728+
coreutils = pkgs.coreutils;
729+
system = builtins.currentSystem;
730+
}
731+
'
732+
"""
733+
).strip()
734+
735+
print(" Building derivation to generate large log")
736+
server.succeed(f"nix-store --realize {drv_path} &>/dev/null")
737+
738+
# Upload logs with compression and multipart
739+
store_url = make_s3_url(
740+
bucket,
741+
**{
742+
"multipart-upload": "true",
743+
"multipart-threshold": str(5 * 1024 * 1024),
744+
"multipart-chunk-size": str(5 * 1024 * 1024),
745+
"log-compression": "xz",
746+
}
747+
)
748+
749+
print(" Uploading build log with compression and multipart")
750+
output = server.succeed(
751+
f"{ENV_WITH_CREDS} nix store copy-log --to '{store_url}' {drv_path} --debug 2>&1"
752+
)
753+
754+
# Should use multipart for the compressed log
755+
if "using S3 multipart upload" not in output or "log/" not in output:
756+
print("Debug output:")
757+
print(output)
758+
raise Exception("Expected multipart upload to be used for compressed log")
759+
760+
if "parts uploaded" not in output:
761+
print("Debug output:")
762+
print(output)
763+
raise Exception("Expected multipart completion message")
764+
765+
print(" ✓ Compressed log uploaded with multipart")
766+
642767
# ============================================================================
643768
# Main Test Execution
644769
# ============================================================================
@@ -669,6 +794,10 @@ in
669794
test_compression_disabled()
670795
test_nix_prefetch_url()
671796
test_versioned_urls()
797+
# FIXME: enable when multipart fully lands
798+
# test_multipart_upload_basic()
799+
# test_multipart_threshold()
800+
# test_multipart_with_log_compression()
672801
673802
print("\n" + "="*80)
674803
print("✓ All S3 Binary Cache Store Tests Passed!")

0 commit comments

Comments
 (0)