Skip to content

Commit 1d51a23

Browse files
committed
🧪 tests: update testcase for making coverage.
1 parent 31859e9 commit 1d51a23

File tree

4 files changed

+152
-32
lines changed

4 files changed

+152
-32
lines changed

src/ddeutil/workflow/job.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,6 @@ def local_execute(
12731273

12741274
errors: DictData = {}
12751275
statuses: list[Status] = [WAIT] * len_strategy
1276-
fail_fast: bool = False
12771276

12781277
if not job.strategy.fail_fast:
12791278
done: Iterator[Future] = as_completed(futures)
@@ -1298,7 +1297,6 @@ def local_execute(
12981297
)
12991298
trace.debug(f"[JOB]: ... Job was set Fail-Fast{nd}")
13001299
done: Iterator[Future] = as_completed(futures)
1301-
fail_fast: bool = True
13021300

13031301
for i, future in enumerate(done, start=0):
13041302
try:
@@ -1313,19 +1311,10 @@ def local_execute(
13131311
pass
13141312

13151313
status: Status = validate_statuses(statuses)
1316-
1317-
# NOTE: Prepare status because it does not cancel from parent event but
1318-
# cancel from failed item execution.
1319-
if fail_fast and status == CANCEL:
1320-
status = FAILED
1321-
1322-
return Result(
1323-
run_id=run_id,
1324-
parent_run_id=parent_run_id,
1314+
return Result.from_trace(trace).catch(
13251315
status=status,
13261316
context=catch(context, status=status, updated=errors),
13271317
info={"execution_time": time.monotonic() - ts},
1328-
extras=job.extras,
13291318
)
13301319

13311320

src/ddeutil/workflow/result.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,12 @@
88
This module provides the core result and status management functionality for
99
workflow execution tracking. It includes the Status enumeration for execution
1010
states and the Result dataclass for context transfer between workflow components.
11-
12-
Classes:
13-
Status: Enumeration for execution status tracking
14-
Result: Dataclass for execution context and result management
15-
16-
Functions:
17-
validate_statuses: Determine final status from multiple status values
18-
get_status_from_error: Convert exception types to appropriate status
1911
"""
2012
from __future__ import annotations
2113

2214
from dataclasses import field
2315
from enum import Enum
24-
from typing import Optional, TypedDict, Union
16+
from typing import Any, Optional, TypedDict, Union
2517

2618
from pydantic import ConfigDict
2719
from pydantic.dataclasses import dataclass
@@ -327,16 +319,26 @@ def get_context_by_layer(
327319
key: str,
328320
layer: Layer,
329321
context_key: str,
330-
):
322+
*,
323+
default: Optional[Any] = None,
324+
) -> Any: # pragma: no cov
331325
if layer == Layer.WORKFLOW:
332-
return context.get("jobs", {}).get(key, {}).get(context_key, WAIT)
326+
return context.get("jobs", {}).get(key, {}).get(context_key, default)
333327
elif layer == Layer.JOB:
334-
return context.get("stages", {}).get(key, {}).get(context_key, WAIT)
328+
return context.get("stages", {}).get(key, {}).get(context_key, default)
335329
elif layer == Layer.STRATEGY:
336-
return context.get("strategies", {}).get(key, {}).get(context_key, WAIT)
337-
return context.get(key, {}).get(context_key, WAIT)
330+
return (
331+
context.get("strategies", {}).get(key, {}).get(context_key, default)
332+
)
333+
return context.get(key, {}).get(context_key, default)
338334

339335

340-
def get_status(context: DictData, key: str, layer: Layer):
336+
def get_status(
337+
context: DictData,
338+
key: str,
339+
layer: Layer,
340+
) -> Status: # pragma: no cov
341341
"""Get status from context by a specific key and context layer."""
342-
return get_context_by_layer(context, key, layer, context_key="status")
342+
return get_context_by_layer(
343+
context, key, layer, context_key="status", default=WAIT
344+
)

src/ddeutil/workflow/stages.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ def is_nested(self) -> bool:
587587
"""
588588
return False
589589

590-
def detail(self) -> DictData:
590+
def detail(self) -> DictData: # pragma: no cov
591591
"""Return the detail of this stage for generate markdown.
592592
593593
Returns:
@@ -3115,8 +3115,24 @@ class CaseStage(BaseNestedStage):
31153115
def __validate_match(
31163116
cls, match: list[Union[Match, Else]]
31173117
) -> list[Union[Match, Else]]:
3118-
if len([m for m in match if isinstance(m, Else)]) > 1:
3119-
raise ValueError("match field should contain only one Else model")
3118+
"""Validate the match field should contain only one Else model."""
3119+
c_else_case: int = 0
3120+
c_else_model: int = 0
3121+
for m in match:
3122+
if isinstance(m, Else):
3123+
if c_else_model:
3124+
raise ValueError(
3125+
"Match field should contain only one `Else` model."
3126+
)
3127+
c_else_model += 1
3128+
continue
3129+
if isinstance(m, Match) and m.case == "_":
3130+
if c_else_case:
3131+
raise ValueError(
3132+
"Match field should contain only one else, '_', case."
3133+
)
3134+
c_else_case += 1
3135+
continue
31203136
return match
31213137

31223138
def extract_stages_from_case(

tests/stages/test_stage_case.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,71 @@
1+
import pytest
12
from ddeutil.workflow import CANCEL, FAILED, SKIP, SUCCESS, Result
23
from ddeutil.workflow.stages import CaseStage, Stage
4+
from pydantic import ValidationError
35

46
from ..utils import MockEvent
57

68

9+
def test_case_stage_raise():
10+
# NOTE: Raise because it contains else case more than 1.
11+
with pytest.raises(ValidationError):
12+
CaseStage.model_validate(
13+
{
14+
"name": "Start run case-match stage",
15+
"id": "case-stage",
16+
"case": "${{ params.name }}",
17+
"match": [
18+
{
19+
"else": [
20+
{
21+
"name": "Else stage",
22+
"echo": "Not match any case.",
23+
},
24+
],
25+
},
26+
{
27+
"else": [
28+
{
29+
"name": "Else stage",
30+
"echo": "Not match any case.",
31+
},
32+
],
33+
},
34+
],
35+
}
36+
)
37+
38+
# NOTE: Raise because it contains else case more than 1.
39+
with pytest.raises(ValidationError):
40+
CaseStage.model_validate(
41+
{
42+
"name": "Start run case-match stage",
43+
"id": "case-stage",
44+
"case": "${{ params.name }}",
45+
"match": [
46+
{
47+
"case": "_",
48+
"stages": [
49+
{
50+
"name": "Else stage",
51+
"echo": "Not match any case.",
52+
},
53+
],
54+
},
55+
{
56+
"case": "_",
57+
"stages": [
58+
{
59+
"name": "Else stage",
60+
"echo": "Not match any case.",
61+
},
62+
],
63+
},
64+
],
65+
}
66+
)
67+
68+
769
def test_case_stage_exec():
870
stage: Stage = CaseStage.model_validate(
971
{
@@ -225,7 +287,7 @@ def test_case_stage_exec_cancel():
225287
"status": CANCEL,
226288
"errors": {
227289
"name": "StageCancelError",
228-
"message": ("Cancel before start case process."),
290+
"message": "Cancel before start case process.",
229291
},
230292
}
231293

@@ -244,6 +306,30 @@ def test_case_stage_exec_cancel():
244306
},
245307
}
246308

309+
event = MockEvent(n=2)
310+
rs: Result = stage.execute(
311+
{"params": {"name": "bar"}}, event=event, run_id="04"
312+
)
313+
assert rs.status == CANCEL
314+
assert rs.context == {
315+
"status": CANCEL,
316+
"case": "bar",
317+
"stages": {
318+
"3616274431": {
319+
"outputs": {},
320+
"errors": {
321+
"name": "StageCancelError",
322+
"message": "Cancel before start empty process.",
323+
},
324+
"status": CANCEL,
325+
}
326+
},
327+
"errors": {
328+
"name": "StageCancelError",
329+
"message": "Cancel case 'bar' after end nested process.",
330+
},
331+
}
332+
247333

248334
def test_case_stage_exec_skipped():
249335
stage: Stage = CaseStage.model_validate(
@@ -268,3 +354,30 @@ def test_case_stage_exec_skipped():
268354
rs: Result = stage.execute({"params": {"name": "test"}}, run_id="01")
269355
assert rs.status == SKIP
270356
assert rs.context == {"status": SKIP}
357+
358+
stage: Stage = CaseStage.model_validate(
359+
{
360+
"name": "Stage skip not has else condition",
361+
"id": "not-else",
362+
"case": "${{ params.name }}",
363+
"match": [
364+
{
365+
"case": "bar",
366+
"stages": [
367+
{
368+
"name": "Match name with Bar",
369+
"if": "'${{ params.name }}' != 'bar'",
370+
"echo": "Hello ${{ params.name }}",
371+
}
372+
],
373+
}
374+
],
375+
}
376+
)
377+
rs: Result = stage.execute({"params": {"name": "bar"}}, run_id="02")
378+
assert rs.status == SKIP
379+
assert rs.context == {
380+
"status": SKIP,
381+
"case": "bar",
382+
"stages": {"3616274431": {"outputs": {}, "status": SKIP}},
383+
}

0 commit comments

Comments
 (0)