@@ -3280,6 +3280,46 @@ def test_dx_run_extra_args(self):
32803280 with self.assertSubprocessFailure(stderr_regexp='JSON', exit_code=3):
32813281 run("dx run " + applet_id + " --extra-args not-a-JSON-string")
32823282
3283+ def test_dx_run_extra_args_instanceTypeSelector_rejected(self):
3284+ # Validate that instanceTypeSelector cannot be specified in runtime extra-args
3285+ applet_id = dxpy.api.applet_new({"project": self.project,
3286+ "dxapi": "1.0.0",
3287+ "runSpec": {"interpreter": "bash",
3288+ "distribution": "Ubuntu",
3289+ "release": "14.04",
3290+ "code": "echo 'hello'"}
3291+ })['id']
3292+
3293+ # Test with wildcard entry point
3294+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3295+ run('dx run ' + applet_id + ' --extra-args ' +
3296+ '\'{"systemRequirements": {"*": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}\'')
3297+
3298+ # Test with specific entry point
3299+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3300+ run('dx run ' + applet_id + ' --extra-args ' +
3301+ '\'{"systemRequirements": {"main": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}\'')
3302+
3303+ # Test with regionalOptions
3304+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3305+ run('dx run ' + applet_id + ' --extra-args ' +
3306+ '\'{"regionalOptions": {"aws:us-east-1": {"systemRequirements": {"*": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}}}\'')
3307+
3308+ # Test with systemRequirementsByExecutable
3309+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3310+ run('dx run ' + applet_id + ' --extra-args ' +
3311+ '\'{"systemRequirementsByExecutable": {"' + applet_id + '": {"main": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}}\'')
3312+
3313+ # Test with systemRequirementsByExecutable with wildcard entry point
3314+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3315+ run('dx run ' + applet_id + ' --extra-args ' +
3316+ '\'{"systemRequirementsByExecutable": {"' + applet_id + '": {"*": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}}\'')
3317+
3318+ # Test with regionalOptions.systemRequirementsByExecutable
3319+ with self.assertSubprocessFailure(stderr_regexp='instanceTypeSelector cannot be specified in runtime', exit_code=3):
3320+ run('dx run ' + applet_id + ' --extra-args ' +
3321+ '\'{"regionalOptions": {"aws:us-east-1": {"systemRequirementsByExecutable": {"' + applet_id + '": {"main": {"instanceTypeSelector": {"allowedInstanceTypes": ["mem2_ssd1_v2_x2"]}}}}}}}\'')
3322+
32833323 def test_dx_run_sys_reqs(self):
32843324 app_spec = {"project": self.project,
32853325 "dxapi": "1.0.0",
@@ -3378,6 +3418,73 @@ def test_dx_run_clone_nvidia_driver(self):
33783418 cloned_job_desc = dxpy.api.job_describe(cloned_job_id_nvidia_override)
33793419 assert cloned_job_desc["systemRequirements"]["*"]["nvidiaDriver"] == build_nvidia_version
33803420
3421+ def test_dx_run_clone_with_instance_type_selector(self):
3422+ """
3423+ Test that when cloning a job from an applet that uses instanceTypeSelector:
3424+ 1. Cloning without runtime args works - instanceTypeSelector is preserved
3425+ 2. Cloning with --instance-type works - runtime instanceType overrides instanceTypeSelector
3426+ 3. Cloning with --instance-count fails - clusterSpec and instanceTypeSelector are mutually exclusive
3427+ 4. Job description contains instanceTypeSelector in systemRequirements
3428+ """
3429+ # Create an applet with instanceTypeSelector
3430+ applet_id = dxpy.api.applet_new({"project": self.project,
3431+ "dxapi": "1.0.0",
3432+ "runSpec": {"interpreter": "bash",
3433+ "distribution": "Ubuntu",
3434+ "release": "20.04",
3435+ "version": "0",
3436+ "code": "echo 'hello'",
3437+ "systemRequirements": {
3438+ "*": {
3439+ "instanceTypeSelector": {
3440+ "allowedInstanceTypes": ["mem1_ssd1_x2", "mem2_ssd1_x2"]
3441+ }
3442+ }
3443+ }}
3444+ })['id']
3445+
3446+ # Run the applet to create the origin job
3447+ # The API will resolve instanceTypeSelector to an actual instanceType
3448+ origin_job_id = run(f"dx run {applet_id} --brief -y").strip().split('\n')[-1]
3449+
3450+ # Verify the origin job description contains instanceTypeSelector
3451+ origin_job_desc = dxpy.api.job_describe(origin_job_id)
3452+ assert "systemRequirements" in origin_job_desc
3453+ assert "*" in origin_job_desc["systemRequirements"]
3454+ assert "instanceTypeSelector" in origin_job_desc["systemRequirements"]["*"]
3455+ assert "allowedInstanceTypes" in origin_job_desc["systemRequirements"]["*"]["instanceTypeSelector"]
3456+
3457+ # Clone without runtime args - should work, instanceTypeSelector is preserved
3458+ cloned_job_id_1 = run(f"dx run --clone {origin_job_id} --brief -y").strip()
3459+ assert cloned_job_id_1.startswith("job-")
3460+ cloned_job_desc_1 = dxpy.api.job_describe(cloned_job_id_1)
3461+ # Verify instanceTypeSelector is in the cloned job's systemRequirements
3462+ assert "instanceTypeSelector" in cloned_job_desc_1["systemRequirements"]["*"]
3463+
3464+ # Clone with --instance-type - should work, runtime instanceType overrides instanceTypeSelector
3465+ cloned_job_id_2 = run(f"dx run --clone {origin_job_id} --instance-type mem2_hdd2_x4 --brief -y").strip()
3466+ assert cloned_job_id_2.startswith("job-")
3467+ cloned_job_desc_2 = dxpy.api.job_describe(cloned_job_id_2)
3468+ # Verify instanceType is used instead of instanceTypeSelector
3469+ assert "instanceType" in cloned_job_desc_2["systemRequirements"]["*"]
3470+ assert cloned_job_desc_2["systemRequirements"]["*"]["instanceType"] == "mem2_hdd2_x4"
3471+ # instanceTypeSelector should not be in the runtime systemRequirements when overridden
3472+ # TODO uncomment later - for now this is not included in the backend behaviour
3473+ # assert "instanceTypeSelector" not in cloned_job_desc_2["systemRequirements"]["*"]
3474+
3475+ # Clone with --instance-count alone - should fail (clusterSpec and instanceTypeSelector are mutually exclusive)
3476+ with self.assertSubprocessFailure(stderr_regexp='Cannot specify --instance-count.*instanceTypeSelector.*without providing --instance-type', exit_code=3):
3477+ run(f"dx run --clone {origin_job_id} --instance-count 3 --brief -y")
3478+
3479+ # Clone with both --instance-count and --instance-type - should work (instanceType overrides instanceTypeSelector)
3480+ cloned_job_id_3 = run(f"dx run --clone {origin_job_id} --instance-count 3 --instance-type mem2_hdd2_x4 --brief -y").strip()
3481+ assert cloned_job_id_3.startswith("job-")
3482+ cloned_job_desc_3 = dxpy.api.job_describe(cloned_job_id_3)
3483+ # Verify instanceType override is applied
3484+ assert "instanceType" in cloned_job_desc_3["systemRequirements"]["*"]
3485+ assert cloned_job_desc_3["systemRequirements"]["*"]["instanceType"] == "mem2_hdd2_x4"
3486+ # Note: When the applet's runSpec doesn't have a clusterSpec defined (only instanceTypeSelector)
3487+
33813488 def test_dx_run_clone(self):
33823489 applet_id = dxpy.api.applet_new({"project": self.project,
33833490 "dxapi": "1.0.0",
0 commit comments