Skip to content

Commit c847cd8

Browse files
authored
Merge pull request NixOS#14297 from lovesegfault/nix-s3-test-public
test(nixos/s3-binary-cache-store): misc improvements
2 parents dbbdae9 + d9c808f commit c847cd8

File tree

1 file changed

+102
-39
lines changed

1 file changed

+102
-39
lines changed

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

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ in
8383
ENDPOINT = 'http://server:9000'
8484
REGION = 'eu-west-1'
8585
86-
PKG_A = '${pkgA}'
87-
PKG_B = '${pkgB}'
88-
PKG_C = '${pkgC}'
86+
PKGS = {
87+
'A': '${pkgA}',
88+
'B': '${pkgB}',
89+
'C': '${pkgC}',
90+
}
8991
9092
ENV_WITH_CREDS = f"AWS_ACCESS_KEY_ID={ACCESS_KEY} AWS_SECRET_ACCESS_KEY={SECRET_KEY}"
9193
@@ -101,10 +103,6 @@ in
101103
bucket_and_path = f"{bucket}{path}" if path else bucket
102104
return f"s3://{bucket_and_path}?{query}"
103105
104-
def make_http_url(path):
105-
"""Build HTTP URL for direct S3 access"""
106-
return f"{ENDPOINT}/{path}"
107-
108106
def get_package_hash(pkg_path):
109107
"""Extract store hash from package path"""
110108
return pkg_path.split("/")[-1].split("-")[0]
@@ -133,42 +131,65 @@ in
133131
print(output)
134132
raise Exception(f"{error_msg}: expected {expected}, got {actual}")
135133
136-
def with_test_bucket(populate_with=[]):
134+
def verify_packages_in_store(machine, pkg_paths, should_exist=True):
135+
"""
136+
Verify whether packages exist in the store.
137+
138+
Args:
139+
machine: The machine to check on
140+
pkg_paths: List of package paths to check (or single path)
141+
should_exist: If True, verify packages exist; if False, verify they don't
142+
"""
143+
paths = [pkg_paths] if isinstance(pkg_paths, str) else pkg_paths
144+
for pkg in paths:
145+
if should_exist:
146+
machine.succeed(f"nix path-info {pkg}")
147+
else:
148+
machine.fail(f"nix path-info {pkg}")
149+
150+
def setup_s3(populate_bucket=[], public=False):
137151
"""
138152
Decorator that creates/destroys a unique bucket for each test.
139153
Optionally pre-populates bucket with specified packages.
154+
Cleans up client store after test completion.
140155
141156
Args:
142-
populate_with: List of packages to upload before test runs
157+
populate_bucket: List of packages to upload before test runs
158+
public: If True, make the bucket publicly accessible
143159
"""
144160
def decorator(test_func):
145161
def wrapper():
146162
bucket = str(uuid.uuid4())
147163
server.succeed(f"mc mb minio/{bucket}")
164+
if public:
165+
server.succeed(f"mc anonymous set download minio/{bucket}")
148166
try:
149-
if populate_with:
167+
if populate_bucket:
150168
store_url = make_s3_url(bucket)
151-
for pkg in populate_with:
169+
for pkg in populate_bucket:
152170
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {pkg}")
153171
test_func(bucket)
154172
finally:
155173
server.succeed(f"mc rb --force minio/{bucket}")
174+
# Clean up client store - only delete if path exists
175+
for pkg in PKGS.values():
176+
client.succeed(f"[ ! -e {pkg} ] || nix store delete --ignore-liveness {pkg}")
156177
return wrapper
157178
return decorator
158179
159180
# ============================================================================
160181
# Test Functions
161182
# ============================================================================
162183
163-
@with_test_bucket()
184+
@setup_s3()
164185
def test_credential_caching(bucket):
165186
"""Verify credential providers are cached and reused"""
166187
print("\n=== Testing Credential Caching ===")
167188
168189
store_url = make_s3_url(bucket)
169190
output = server.succeed(
170191
f"{ENV_WITH_CREDS} nix copy --debug --to '{store_url}' "
171-
f"{PKG_A} {PKG_B} {PKG_C} 2>&1"
192+
f"{PKGS['A']} {PKGS['B']} {PKGS['C']} 2>&1"
172193
)
173194
174195
assert_count(
@@ -180,7 +201,7 @@ in
180201
181202
print("✓ Credential provider created once and cached")
182203
183-
@with_test_bucket(populate_with=[PKG_A])
204+
@setup_s3(populate_bucket=[PKGS['A']])
184205
def test_fetchurl_basic(bucket):
185206
"""Test builtins.fetchurl works with s3:// URLs"""
186207
print("\n=== Testing builtins.fetchurl ===")
@@ -196,13 +217,13 @@ in
196217
197218
print("✓ builtins.fetchurl works with s3:// URLs")
198219
199-
@with_test_bucket()
220+
@setup_s3()
200221
def test_error_message_formatting(bucket):
201222
"""Verify error messages display URLs correctly"""
202223
print("\n=== Testing Error Message Formatting ===")
203224
204225
nonexistent_url = make_s3_url(bucket, path="/foo-that-does-not-exist")
205-
expected_http_url = make_http_url(f"{bucket}/foo-that-does-not-exist")
226+
expected_http_url = f"{ENDPOINT}/{bucket}/foo-that-does-not-exist"
206227
207228
error_msg = client.fail(
208229
f"{ENV_WITH_CREDS} nix eval --impure --expr "
@@ -216,7 +237,7 @@ in
216237
217238
print("✓ Error messages format URLs correctly")
218239
219-
@with_test_bucket(populate_with=[PKG_A])
240+
@setup_s3(populate_bucket=[PKGS['A']])
220241
def test_fork_credential_preresolution(bucket):
221242
"""Test credential pre-resolution in forked processes"""
222243
print("\n=== Testing Fork Credential Pre-resolution ===")
@@ -246,7 +267,7 @@ in
246267
""".format(id=test_id, url=test_url, hash=cache_info_hash)
247268
248269
output = client.succeed(
249-
f"{ENV_WITH_CREDS} nix build --debug --impure --expr '{fetchurl_expr}' 2>&1"
270+
f"{ENV_WITH_CREDS} nix build --debug --impure --no-link --expr '{fetchurl_expr}' 2>&1"
250271
)
251272
252273
# Verify fork behavior
@@ -296,7 +317,7 @@ in
296317
297318
print(" ✓ Child uses pre-resolved credentials (no new providers)")
298319
299-
@with_test_bucket(populate_with=[PKG_A, PKG_B, PKG_C])
320+
@setup_s3(populate_bucket=[PKGS['A'], PKGS['B'], PKGS['C']])
300321
def test_store_operations(bucket):
301322
"""Test nix store info and copy operations"""
302323
print("\n=== Testing Store Operations ===")
@@ -316,11 +337,11 @@ in
316337
print(f" ✓ Store URL: {store_info['url']}")
317338
318339
# Test copy from store
319-
client.fail(f"nix path-info {PKG_A}")
340+
verify_packages_in_store(client, PKGS['A'], should_exist=False)
320341
321342
output = client.succeed(
322343
f"{ENV_WITH_CREDS} nix copy --debug --no-check-sigs "
323-
f"--from '{store_url}' {PKG_A} {PKG_B} {PKG_C} 2>&1"
344+
f"--from '{store_url}' {PKGS['A']} {PKGS['B']} {PKGS['C']} 2>&1"
324345
)
325346
326347
assert_count(
@@ -330,12 +351,46 @@ in
330351
"Client credential provider caching failed"
331352
)
332353
333-
client.succeed(f"nix path-info {PKG_A}")
354+
verify_packages_in_store(client, [PKGS['A'], PKGS['B'], PKGS['C']])
334355
335356
print(" ✓ nix copy works")
336357
print(" ✓ Credentials cached on client")
337358
338-
@with_test_bucket(populate_with=[PKG_A])
359+
@setup_s3(populate_bucket=[PKGS['A'], PKGS['B']], public=True)
360+
def test_public_bucket_operations(bucket):
361+
"""Test store operations on public bucket without credentials"""
362+
print("\n=== Testing Public Bucket Operations ===")
363+
364+
store_url = make_s3_url(bucket)
365+
366+
# Verify store info works without credentials
367+
client.succeed(f"nix store info --store '{store_url}' >&2")
368+
print(" ✓ nix store info works without credentials")
369+
370+
# Get and validate store info JSON
371+
info_json = client.succeed(f"nix store info --json --store '{store_url}'")
372+
store_info = json.loads(info_json)
373+
374+
if not store_info.get("url"):
375+
raise Exception("Store should have a URL")
376+
377+
print(f" ✓ Store URL: {store_info['url']}")
378+
379+
# Verify packages are not yet in client store
380+
verify_packages_in_store(client, [PKGS['A'], PKGS['B']], should_exist=False)
381+
382+
# Test copy from public bucket without credentials
383+
client.succeed(
384+
f"nix copy --debug --no-check-sigs "
385+
f"--from '{store_url}' {PKGS['A']} {PKGS['B']} 2>&1"
386+
)
387+
388+
# Verify packages were copied successfully
389+
verify_packages_in_store(client, [PKGS['A'], PKGS['B']])
390+
391+
print(" ✓ nix copy from public bucket works without credentials")
392+
393+
@setup_s3(populate_bucket=[PKGS['A']])
339394
def test_url_format_variations(bucket):
340395
"""Test different S3 URL parameter combinations"""
341396
print("\n=== Testing URL Format Variations ===")
@@ -350,7 +405,7 @@ in
350405
client.succeed(f"{ENV_WITH_CREDS} nix store info --store '{url2}' >&2")
351406
print(" ✓ Parameter order: endpoint before region works")
352407
353-
@with_test_bucket(populate_with=[PKG_A])
408+
@setup_s3(populate_bucket=[PKGS['A']])
354409
def test_concurrent_fetches(bucket):
355410
"""Validate thread safety with concurrent S3 operations"""
356411
print("\n=== Testing Concurrent Fetches ===")
@@ -386,12 +441,12 @@ in
386441
387442
try:
388443
output = client.succeed(
389-
f"{ENV_WITH_CREDS} nix build --debug --impure "
444+
f"{ENV_WITH_CREDS} nix build --debug --impure --no-link "
390445
f"--expr '{concurrent_expr}' --max-jobs 5 2>&1"
391446
)
392447
except:
393448
output = client.fail(
394-
f"{ENV_WITH_CREDS} nix build --debug --impure "
449+
f"{ENV_WITH_CREDS} nix build --debug --impure --no-link "
395450
f"--expr '{concurrent_expr}' --max-jobs 5 2>&1"
396451
)
397452
@@ -412,26 +467,33 @@ in
412467
f"Expected 5 FileTransfer instances for 5 concurrent fetches, got {transfers_created}"
413468
)
414469
415-
@with_test_bucket()
470+
if providers_created != 1:
471+
print("Debug output:")
472+
print(output)
473+
raise Exception(
474+
f"Expected 1 credential provider for concurrent fetches, got {providers_created}"
475+
)
476+
477+
@setup_s3()
416478
def test_compression_narinfo_gzip(bucket):
417479
"""Test narinfo compression with gzip"""
418480
print("\n=== Testing Compression: narinfo (gzip) ===")
419481
420482
store_url = make_s3_url(bucket, **{'narinfo-compression': 'gzip'})
421-
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKG_B}")
483+
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKGS['B']}")
422484
423-
pkg_hash = get_package_hash(PKG_B)
485+
pkg_hash = get_package_hash(PKGS['B'])
424486
verify_content_encoding(server, bucket, f"{pkg_hash}.narinfo", "gzip")
425487
426488
print(" ✓ .narinfo has Content-Encoding: gzip")
427489
428490
# Verify client can download and decompress
429-
client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' --no-check-sigs {PKG_B}")
430-
client.succeed(f"nix path-info {PKG_B}")
491+
client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' --no-check-sigs {PKGS['B']}")
492+
verify_packages_in_store(client, PKGS['B'])
431493
432494
print(" ✓ Client decompressed .narinfo successfully")
433495
434-
@with_test_bucket()
496+
@setup_s3()
435497
def test_compression_mixed(bucket):
436498
"""Test mixed compression (narinfo=xz, ls=gzip)"""
437499
print("\n=== Testing Compression: mixed (narinfo=xz, ls=gzip) ===")
@@ -441,9 +503,9 @@ in
441503
**{'narinfo-compression': 'xz', 'write-nar-listing': 'true', 'ls-compression': 'gzip'}
442504
)
443505
444-
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKG_C}")
506+
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKGS['C']}")
445507
446-
pkg_hash = get_package_hash(PKG_C)
508+
pkg_hash = get_package_hash(PKGS['C'])
447509
448510
# Verify .narinfo has xz compression
449511
verify_content_encoding(server, bucket, f"{pkg_hash}.narinfo", "xz")
@@ -454,20 +516,20 @@ in
454516
print(" ✓ .ls has Content-Encoding: gzip")
455517
456518
# Verify client can download with mixed compression
457-
client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' --no-check-sigs {PKG_C}")
458-
client.succeed(f"nix path-info {PKG_C}")
519+
client.succeed(f"{ENV_WITH_CREDS} nix copy --from '{store_url}' --no-check-sigs {PKGS['C']}")
520+
verify_packages_in_store(client, PKGS['C'])
459521
460522
print(" ✓ Client downloaded package with mixed compression")
461523
462-
@with_test_bucket()
524+
@setup_s3()
463525
def test_compression_disabled(bucket):
464526
"""Verify no compression by default"""
465527
print("\n=== Testing Compression: disabled (default) ===")
466528
467529
store_url = make_s3_url(bucket)
468-
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKG_A}")
530+
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {PKGS['A']}")
469531
470-
pkg_hash = get_package_hash(PKG_A)
532+
pkg_hash = get_package_hash(PKGS['A'])
471533
verify_no_compression(server, bucket, f"{pkg_hash}.narinfo")
472534
473535
print(" ✓ No compression applied by default")
@@ -494,6 +556,7 @@ in
494556
test_error_message_formatting()
495557
test_fork_credential_preresolution()
496558
test_store_operations()
559+
test_public_bucket_operations()
497560
test_url_format_variations()
498561
test_concurrent_fetches()
499562
test_compression_narinfo_gzip()

0 commit comments

Comments
 (0)