|
9 | 9 | Real-contract facts the fake reproduces: |
10 | 10 |
|
11 | 11 | * `run` parses argv with the real flag table: `--pool` / `--user` / |
12 | | - `--task-id` are required and there is NO `--workspace` flag — |
13 | | - violations exit 2 with an argparse-style usage error, exactly like |
14 | | - the real parser. |
| 12 | + `--task-id` are required and there is NO `--workspace` and NO |
| 13 | + `--gpu-mode` flag (removed upstream; requests inherit |
| 14 | + exclusive/shared from the target pool) — violations exit 2 with an |
| 15 | + argparse-style usage error, exactly like the real parser. |
15 | 16 | * Request ids look like the real ones (`req-%06d`). |
16 | 17 | * `logs` returns EMPTY stdout/stderr while the request is running |
17 | 18 | (CAPE has no streaming logs); output appears only once the request |
|
34 | 35 | `FAKE_CAPE_SERVER_STATE` overrides the server request's state. |
35 | 36 |
|
36 | 37 | The fake keeps the status JSON minimal ({state, request_id, |
37 | | -exit_code}) — the provider only ever reads `state` and the |
38 | | -`request_id` echo. `cape wait` is deliberately not modeled: the |
39 | | -provider must never call it (its exit code is untrustworthy). |
| 38 | +exit_code}) plus the new upstream cotenancy fields (`gpu_mode`, |
| 39 | +`gpu_cotenant_count`, `gpu_cotenant_counts`) which the provider must |
| 40 | +tolerate — it only ever reads `state` and the `request_id` echo. |
| 41 | +`cape wait` is deliberately not modeled: the provider must never call |
| 42 | +it (its exit code is untrustworthy). |
40 | 43 | """ |
41 | 44 |
|
42 | 45 | from __future__ import annotations |
@@ -130,7 +133,9 @@ def build_parser(): |
130 | 133 | run.add_argument("--hard-gpu-enforcement", action="store_true") |
131 | 134 | run.add_argument("--port", type=int, action="append", default=[]) |
132 | 135 | run.add_argument("--locality") |
133 | | - run.add_argument("--gpu-mode", default="whole") |
| 136 | + # NO --gpu-mode: upstream removed the flag (requests inherit |
| 137 | + # exclusive/shared from the target pool), so sending it makes |
| 138 | + # argparse exit 2 with a usage error, like any unknown flag. |
134 | 139 | run.add_argument("--isolation-policy", default="default") |
135 | 140 | run.add_argument("--cwd") |
136 | 141 | run.add_argument("--env", action="append", default=[]) |
@@ -204,6 +209,12 @@ def do_status(args): |
204 | 209 | record["exit_code"] = 124 |
205 | 210 | elif state in TERMINAL: |
206 | 211 | record["exit_code"] = 0 |
| 212 | + # New upstream cotenancy fields: gpu_mode is DERIVED from the target |
| 213 | + # pool (null | "exclusive" | "shared"); the provider must tolerate |
| 214 | + # (ignore) all three. |
| 215 | + record["gpu_mode"] = None |
| 216 | + record["gpu_cotenant_count"] = 0 |
| 217 | + record["gpu_cotenant_counts"] = [] |
207 | 218 | print(json.dumps(record)) |
208 | 219 |
|
209 | 220 |
|
@@ -397,7 +408,9 @@ async def test_create_returns_sandbox_and_emits_real_run_face(cape_env: dict[str |
397 | 408 | assert "--cwd" not in argv |
398 | 409 | assert _flag(argv, "--image") == "docker://task-image:1" |
399 | 410 | assert _flag(argv, "--gpus") == "2" |
400 | | - assert _flag(argv, "--gpu-mode") == "whole" |
| 411 | + # Upstream removed --gpu-mode (pool-level exclusive/shared); |
| 412 | + # sending it would make the real CLI exit 2 before submitting. |
| 413 | + assert "--gpu-mode" not in argv |
401 | 414 | assert _flag(argv, "--isolation-policy") == "default" |
402 | 415 | assert _flag(argv, "--max-duration-seconds") == "14400" |
403 | 416 | assert _flags(argv, "--bind") == [ |
@@ -506,6 +519,37 @@ def test_fake_cape_rejects_workspace_flag_and_requires_pool_user_task_id( |
506 | 519 | assert flag in proc.stderr |
507 | 520 |
|
508 | 521 |
|
| 522 | +def test_fake_cape_rejects_dropped_gpu_mode_flag(cape_env: dict[str, Any]) -> None: |
| 523 | + # Upstream removed `cape run --gpu-mode` entirely (requests now |
| 524 | + # inherit exclusive/shared from the target pool). Sending the flag |
| 525 | + # is an unknown-argument error: argparse exits 2 with usage on |
| 526 | + # stderr, so a provider that still emits it never submits anything. |
| 527 | + binary = str(cape_env["binary"]) |
| 528 | + legacy = [ |
| 529 | + binary, "run", "--pool", "p", "--user", "u", "--task-id", "t", |
| 530 | + "--gpu-mode", "whole", "--image", "img", "--", "sh", "-c", "true", |
| 531 | + ] # fmt: skip |
| 532 | + proc = subprocess.run(legacy, capture_output=True, text=True) |
| 533 | + assert proc.returncode == 2 |
| 534 | + assert "--gpu-mode" in proc.stderr |
| 535 | + |
| 536 | + |
| 537 | +async def test_run_face_never_emits_dropped_gpu_mode_flag(cape_env: dict[str, Any]) -> None: |
| 538 | + # Regression for the upstream flag removal: the run argv must not |
| 539 | + # contain --gpu-mode for any GPU count (0 or N). The fake parser |
| 540 | + # would exit 2 on it, but assert explicitly so the failure reads as |
| 541 | + # a contract violation rather than a create() error. |
| 542 | + provider = _provider(cape_env) |
| 543 | + for resource, gpus in ((None, 0), (SandboxResource(gpu=4), 4)): |
| 544 | + sandbox = await provider.create(_sandbox_config(resource=resource)) |
| 545 | + try: |
| 546 | + argv = _log_entries(cape_env, verb="run")[-1]["argv"] |
| 547 | + assert "--gpu-mode" not in argv |
| 548 | + assert _flag(argv, "--gpus") == str(gpus) |
| 549 | + finally: |
| 550 | + await provider.delete(sandbox.sandbox_id) |
| 551 | + |
| 552 | + |
509 | 553 | async def test_fake_cape_logs_empty_while_running_populated_when_terminal( |
510 | 554 | cape_env: dict[str, Any], |
511 | 555 | ) -> None: |
|
0 commit comments