async def test_inspect_job_bad_context(client: Client) -> None: expr = StrExpr(POS, POS, "${{ inspect_job(22) }}") with pytest.raises( EvalError, match=r"inspect_job\(\) is only available inside a job definition" ): await expr.eval(Root({}, client))
async def test_inspect_job_bad_second_arg( client: Client, live_context_factory: LiveContextFactory) -> None: expr = StrExpr(POS, POS, "${{ inspect_job('foo', 22) }}") ctx = live_context_factory(client, set()) with pytest.raises( EvalError, match=r"inspect_job\(\) suffix argument should be a str, got 22"): await expr.eval(ctx)
def test_expr_validation_set_indexing() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ tags['flow_id'] }}", ) errors = validate_expr(expr, BatchContext) assert errors[0].args[0] == "'TagsCtx' is not subscriptable"
def test_expr_validation_list_comp_ok() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ [x * x for x in range(5) ] }}", ) errors = validate_expr(expr, BatchContext) assert errors == []
def test_expr_validation_ok() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ flow.flow_id }}", ) errors = validate_expr(expr, BatchContext) assert errors == []
def test_expr_validation_unknown_context() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ something_new }}", ) errors = validate_expr(expr, BatchContext) assert errors[0].args[0] == "Unknown context 'something_new'"
def test_expr_validation_ok_for_property_access() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ volumes.volume.ref_rw }}", ) errors = validate_expr(expr, BatchContext) assert errors == []
def test_expr_validation_not_context_field() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ flow.foo }}", ) errors = validate_expr(expr, BatchContext) assert errors[0].args[0] == "'BatchFlowCtx' has no attribute 'foo'"
def test_expr_validation_bad_set_attr() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ tags.anything }}", ) errors = validate_expr(expr, BatchContext) assert errors[0].args[0] == "'TagsCtx' has no attribute 'anything'"
def test_expr_validation_bad_subcontext_lookup() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ needs.task.foo }}", ) errors = validate_expr(expr, BatchTaskContext, known_needs={"task"}) assert errors[0].args[0] == "'DepCtx' has no attribute 'foo'"
def test_expr_validation_invalid_need() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ needs.fff }}", ) errors = validate_expr(expr, BatchTaskContext, known_needs=set()) assert errors[0].args[ 0] == "Task 'fff' is not available under needs context"
def test_expr_validation_invalid_input() -> None: expr = StrExpr( Pos(0, 0, LocalPath("<default>")), Pos(0, 0, LocalPath("<default>")), pattern="${{ inputs.fff }}", ) errors = validate_expr(expr, BatchActionContext[EmptyRoot], known_inputs=set()) assert errors[0].args[0] == "Input 'fff' is undefined"
async def test_inspect_job_no_jobs_errors( client: Client, live_context_factory: LiveContextFactory) -> None: async def fake_list(*args: Any, **kwargs: Any) -> AsyncIterator[Any]: # make it async iterator: for _ in range(0): yield None client.jobs.list = fake_list # type: ignore expr = StrExpr(POS, POS, "${{ inspect_job('foo') }}") expr_with_suffix = StrExpr(POS, POS, "${{ inspect_job('foo', 'bar') }}") ctx = live_context_factory(client, set()) with pytest.raises( EvalError, match=r"inspect_job\(\) did not found running job with name foo"): await expr.eval(ctx) with pytest.raises( EvalError, match=r"inspect_job\(\) did not found running job with name foo" r" and suffix bar", ): await expr_with_suffix.eval(ctx)
async def test_inspect_job_correct_set_of_tags_multi( client: Client, live_context_factory: LiveContextFactory) -> None: called = False async def fake_list(*args: Any, tags: AbstractSet[str], **kwargs: Any) -> AsyncIterator[Any]: nonlocal called called = True assert tags == {"test_tag", "job:foo", "multi:bar"} yield {"id": "test_id"} client.jobs.list = fake_list # type: ignore expr = StrExpr(POS, POS, "${{ inspect_job('foo', 'bar').id }}") ctx = live_context_factory(client, {"test_tag"}) result = await expr.eval(ctx) assert result == "test_id" assert called
async def test_upload_dry_run_mode_prints_commands( client: Client, live_context_factory: LiveContextFactory, capsys: CaptureFixture[str], ) -> None: expr = StrExpr( POS, POS, "${{ upload(volumes.test) }}", ) ctx = live_context_factory( client, set(), dry_run=True, volumes={ "test": VolumeCtx( id="test", full_local_path=LocalPath("/test/local"), local=LocalPath("local"), remote=URL("storage://cluster/user/somedir"), read_only=False, mount=RemotePath("/mnt"), ) }, ) await expr.eval(ctx) capture = capsys.readouterr() assert "neuro mkdir --parents storage://cluster/user\n" in capture.out if sys.platform == "win32": assert ("neuro cp --recursive --update --no-target-directory" " '\\test\\local' storage://cluster/user/somedir\n" in capture.out) else: assert ("neuro cp --recursive --update --no-target-directory" " /test/local storage://cluster/user/somedir\n" in capture.out)
async def test_fmt(client: Client) -> None: expr = StrExpr(POS, POS, "${{ fmt('{} {}', 1, 'a') }}") ret = await expr.eval(Root({}, client)) assert ret == "1 a"
async def test_hash_files(client: Client) -> None: expr = StrExpr(POS, POS, "${{ hash_files('Dockerfile', 'requirements/*.txt') }}") folder = LocalPath(__file__).parent / "hash_files" ret = await expr.eval(Root({"flow": {"workspace": folder}}, client)) assert ret == "081fde04651e1184890a0470501bff3db8e0014260224e07acf5688e70e0edbe"
async def test_lower(client: Client) -> None: expr = StrExpr(POS, POS, "${{ lower('aBcDeF') }}") ret = await expr.eval(Root({}, client)) assert ret == "abcdef"
async def test_upper(client: Client) -> None: expr = StrExpr(POS, POS, "${{ upper('aBcDeF') }}") ret = await expr.eval(Root({}, client)) assert ret == "ABCDEF"
async def test_parse_volume_remote(client: Client) -> None: expr = StrExpr( POS, POS, "${{ parse_volume('storage:path/to:/mnt/path:rw').remote }}") ret = await expr.eval(Root({}, client)) assert Matches("storage:[^:]+/path/to") == ret
async def test_parse_volume_mount(client: Client) -> None: expr = StrExpr( POS, POS, "${{ parse_volume('storage:path/to:/mnt/path:rw').mount }}") ret = await expr.eval(Root({}, client)) assert ret == "/mnt/path"
async def test_parse_volume_read_only(client: Client) -> None: expr = StrExpr( POS, POS, "${{ parse_volume('storage:path/to:/mnt/path:rw').read_only }}") ret = await expr.eval(Root({}, client)) assert ret == "False"
async def test_parse_volume_local_full(client: Client) -> None: expr = StrExpr( POS, POS, "${{ parse_volume('storage:path/to:/mnt/path:rw').full_local_path }}") ret = await expr.eval(Root({}, client)) assert ret == "None"
async def test_str(client: Client, pattern: str, result: str) -> None: expr = StrExpr(POS, POS, "${{ str(" + pattern + ") }}") ret = await expr.eval(Root({}, client)) assert ret == result
async def test_values(client: Client) -> None: expr = StrExpr(POS, POS, "${{ values(dct) }}") ret = await expr.eval(Root({"dct": {"a": 1, "b": 2}}, client)) assert ret == "[1, 2]"
def test_parse_full_exprs(assets: pathlib.Path) -> None: workspace = assets config_file = workspace / "live-full-exprs.yml" flow = parse_live(workspace, config_file) assert flow == ast.LiveFlow( Pos(0, 0, config_file), Pos(48, 0, config_file), id=SimpleOptIdExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None, ), kind=ast.FlowKind.LIVE, title=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Global title", ), images={ "image_a": ast.Image( Pos(4, 4, config_file), Pos(11, 0, config_file), ref=StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "image:banana" ), context=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir" ), dockerfile=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir/Dockerfile" ), build_args=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ ['--arg1', 'val1', '--arg2=val2'] }}", type2str, ), env=MappingExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ {'SECRET_ENV': 'secret:key' } }}", type2str, ), volumes=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ ['secret:key:/var/secret/key.txt'] }}", type2str, ), build_preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "gpu-small" ), force_rebuild=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), ) }, volumes={ "volume_a": ast.Volume( Pos(13, 4, config_file), Pos(17, 2, config_file), remote=URIExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:dir" ), mount=RemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/var/dir" ), read_only=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), local=OptLocalPathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir" ), ), "volume_b": ast.Volume( Pos(18, 4, config_file), Pos(20, 0, config_file), remote=URIExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:other" ), mount=RemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/var/other" ), read_only=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), local=OptLocalPathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), ), }, defaults=ast.FlowDefaults( Pos(21, 2, config_file), Pos(28, 0, config_file), _specified_fields={ "env", "volumes", "life_span", "schedule_timeout", "preset", "workdir", "tags", }, tags=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ ['tag-a', 'tag-b'] }}", type2str, ), env=MappingExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ {'global_a': 'val-a', 'global_b': 'val-b'} }}", type2str, ), volumes=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ ['storage:common:/mnt/common:rw'] }}", type2str, ), workdir=OptRemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/global/dir" ), life_span=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "1d4h" ), preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "cpu-large" ), schedule_timeout=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "24d23h22m21s" ), ), mixins=None, jobs={ "test_a": ast.Job( Pos(30, 4, config_file), Pos(48, 0, config_file), _specified_fields={ "pass_config", "image", "life_span", "http_port", "title", "cmd", "schedule_timeout", "tags", "preset", "http_auth", "env", "browse", "workdir", "name", "entrypoint", "port_forward", "volumes", "detach", }, mixins=None, name=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "job-name" ), image=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ images.image_a.ref }}", ), preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "cpu-micro" ), schedule_timeout=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "1d2h3m4s" ), entrypoint=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "bash" ), cmd=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "echo abc" ), workdir=OptRemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/local/dir" ), env=MappingExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ {'local_a': 'val-1', 'local_b': 'val-2'} }}", type2str, ), volumes=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ [volumes.volume_a.ref, 'storage:dir:/var/dir:ro'] }}", type2str, ), tags=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ ['tag-1', 'tag-2'] }}", type2str, ), life_span=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "2h55m" ), title=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Job title" ), detach=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), browse=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), http_port=OptIntExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), 8080 ), http_auth=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), False ), pass_config=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), port_forward=SequenceExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), '${{ ["2211:22"] }}', port_pair_item, ), multi=SimpleOptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), params=None, ) }, )
def test_parse_full(assets: pathlib.Path) -> None: workspace = assets config_file = workspace / "live-full.yml" flow = parse_live(workspace, config_file) assert flow == ast.LiveFlow( Pos(0, 0, config_file), Pos(69, 0, config_file), id=SimpleOptIdExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None, ), kind=ast.FlowKind.LIVE, title=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Global title", ), images={ "image_a": ast.Image( Pos(4, 4, config_file), Pos(16, 0, config_file), ref=StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "image:banana" ), context=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir" ), dockerfile=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir/Dockerfile" ), build_args=SequenceItemsExpr( [ StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "--arg1" ), StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "val1"), StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "--arg2=val2", ), ] ), env=MappingItemsExpr( { "SECRET_ENV": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "secret:key" ), } ), volumes=SequenceItemsExpr( [ OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "secret:key:/var/secret/key.txt", ), ] ), build_preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "gpu-small" ), force_rebuild=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), ) }, volumes={ "volume_a": ast.Volume( Pos(18, 4, config_file), Pos(22, 2, config_file), remote=URIExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:dir" ), mount=RemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/var/dir" ), read_only=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), local=OptLocalPathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "dir" ), ), "volume_b": ast.Volume( Pos(23, 4, config_file), Pos(25, 0, config_file), remote=URIExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:other" ), mount=RemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/var/other" ), read_only=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), local=OptLocalPathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), ), }, defaults=ast.FlowDefaults( Pos(26, 2, config_file), Pos(36, 0, config_file), _specified_fields={ "env", "volumes", "life_span", "schedule_timeout", "preset", "workdir", "tags", }, tags=SequenceItemsExpr( [ StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "tag-a"), StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "tag-b"), ] ), env=MappingItemsExpr( { "global_a": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-a" ), "global_b": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-b" ), } ), volumes=SequenceItemsExpr( [ OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:common:/mnt/common:rw", ), ] ), workdir=OptRemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/global/dir" ), life_span=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "1d4h" ), preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "cpu-large" ), schedule_timeout=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "24d23h22m21s" ), ), mixins={ "envs": ast.JobMixin( Pos(38, 4, config_file), Pos(41, 0, config_file), _specified_fields={"env"}, mixins=None, name=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), image=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None, ), preset=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), schedule_timeout=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), entrypoint=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), cmd=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), workdir=OptRemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), env=MappingItemsExpr( { "local_b": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-mixin-2", ), "local_c": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-mixin-3", ), } ), volumes=None, tags=None, life_span=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), title=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), detach=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), browse=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), http_port=OptIntExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), http_auth=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), pass_config=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), port_forward=None, multi=SimpleOptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), params=None, ) }, jobs={ "test_a": ast.Job( Pos(43, 4, config_file), Pos(69, 0, config_file), _specified_fields={ "workdir", "http_auth", "preset", "port_forward", "life_span", "title", "http_port", "cmd", "schedule_timeout", "env", "pass_config", "detach", "name", "tags", "image", "mixins", "browse", "volumes", "entrypoint", }, mixins=[ StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "envs") ], name=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "job-name" ), image=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ images.image_a.ref }}", ), preset=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "cpu-micro" ), schedule_timeout=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "1d2h3m4s" ), entrypoint=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "bash" ), cmd=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "echo abc" ), workdir=OptRemotePathExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "/local/dir" ), env=MappingItemsExpr( { "local_a": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-1" ), "local_b": StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "val-2" ), } ), volumes=SequenceItemsExpr( [ OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ volumes.volume_a.ref }}", ), OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "storage:dir:/var/dir:ro", ), OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "", ), OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None, ), ] ), tags=SequenceItemsExpr( [ StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "tag-1" ), StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "tag-2" ), ] ), life_span=OptTimeDeltaExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "2h55m" ), title=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Job title" ), detach=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), browse=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), http_port=OptIntExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), 8080 ), http_auth=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), False ), pass_config=OptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), True ), port_forward=SequenceItemsExpr( [ PortPairExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "2211:22" ) ] ), multi=SimpleOptBoolExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), None ), params=None, ) }, )
async def test_len(client: Client) -> None: expr = StrExpr(POS, POS, "${{ len('abc') }}") ret = await expr.eval(Root({}, client)) assert ret == "3"
def test_parse_batch_action(assets: LocalPath) -> None: config_file = assets / "batch-action-with-image.yml" action = parse_action(config_file) assert action == ast.BatchAction( Pos(0, 0, config_file), Pos(43, 0, config_file), kind=ast.ActionKind.BATCH, name=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Test batch Action", ), author=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "Andrew Svetlov", ), descr=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "description of test action", ), inputs={ "arg1": ast.Input( Pos(6, 4, config_file), Pos(7, 2, config_file), descr=SimpleOptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "param 1"), default=SimpleOptPrimitiveExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), ), "arg2": ast.Input( Pos(8, 4, config_file), Pos(10, 0, config_file), descr=SimpleOptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "param 2"), default=SimpleOptPrimitiveExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "value 2"), ), }, outputs=BatchActionOutputs( Pos(ANY, ANY, config_file), Pos(ANY, ANY, config_file), values={ "res1": ast.Output( Pos(ANY, ANY, config_file), Pos(ANY, ANY, config_file), descr=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "action result 1", ), value=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ needs.task_1.outputs.task1 }}", ), ), "res2": ast.Output( Pos(ANY, ANY, config_file), Pos(ANY, ANY, config_file), descr=SimpleOptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "action result 2", ), value=OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ needs.task_2.outputs.task2 }}", ), ), }, ), cache=ast.Cache( Pos(19, 2, config_file), Pos(21, 0, config_file), strategy=ast.CacheStrategy.INHERIT, life_span=OptTimeDeltaExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "30m"), ), images={ "image_a": ast.Image( _start=Pos(23, 4, config_file), _end=Pos(35, 0, config_file), ref=StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "image:banana"), context=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "dir"), dockerfile=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "dir/Dockerfile"), build_args=SequenceItemsExpr([ StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "--arg1"), StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "val1"), StrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "--arg2=val2", ), ]), env=MappingItemsExpr({ "SECRET_ENV": StrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "secret:key"), }), volumes=SequenceItemsExpr([ OptStrExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "secret:key:/var/secret/key.txt", ), ]), build_preset=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "gpu-small"), force_rebuild=OptBoolExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), ) }, tasks=[ ast.Task( Pos(36, 2, config_file), Pos(40, 0, config_file), _specified_fields={"needs", "image", "cmd", "id"}, mixins=None, title=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), name=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), image=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "image:banana"), preset=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), schedule_timeout=OptTimeDeltaExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), entrypoint=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), cmd=OptBashExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "echo ::set-output name=task1::Task 1 ${{ inputs.arg1 }}", ), workdir=OptRemotePathExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), env=None, volumes=None, tags=None, life_span=OptTimeDeltaExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), http_port=OptIntExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), http_auth=OptBoolExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), pass_config=OptBoolExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), id=OptIdExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "task_1"), needs={}, strategy=None, cache=None, enable=EnableExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ success() }}", ), ), ast.Task( Pos(40, 2, config_file), Pos(43, 0, config_file), _specified_fields={"image", "cmd", "id"}, mixins=None, title=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), name=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), image=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "ubuntu"), preset=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), schedule_timeout=OptTimeDeltaExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), entrypoint=OptStrExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), cmd=OptBashExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "echo ::set-output name=task2::Task 2 ${{ inputs.arg2 }}", ), workdir=OptRemotePathExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), env=None, volumes=None, tags=None, life_span=OptTimeDeltaExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), http_port=OptIntExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), http_auth=OptBoolExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), pass_config=OptBoolExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), None), id=OptIdExpr(Pos(0, 0, config_file), Pos(0, 0, config_file), "task_2"), needs=None, strategy=None, cache=None, enable=EnableExpr( Pos(0, 0, config_file), Pos(0, 0, config_file), "${{ success() }}", ), ), ], )
async def test_join(client: Client) -> None: expr = StrExpr(POS, POS, "${{ join('_', ['x', 'y', 'z']) }}") ret = await expr.eval(Root({}, client)) assert ret == "x_y_z"