re-uploading work
This commit is contained in:
558
tests/e2e/tier2/test_t2_10_parallel_execution.py
Normal file
558
tests/e2e/tier2/test_t2_10_parallel_execution.py
Normal file
@@ -0,0 +1,558 @@
|
||||
"""
|
||||
T2.10: Parallel Execution (with-items)
|
||||
|
||||
Tests that multiple child executions run concurrently when using with-items,
|
||||
validating concurrent execution capability and proper resource management.
|
||||
|
||||
Test validates:
|
||||
- All child executions start immediately
|
||||
- Total time ~N seconds (parallel) not N*M seconds (sequential)
|
||||
- Worker handles concurrent executions
|
||||
- No resource contention issues
|
||||
- All children complete successfully
|
||||
- Concurrency limits honored
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from helpers.client import AttuneClient
|
||||
from helpers.fixtures import unique_ref
|
||||
from helpers.polling import wait_for_execution_status
|
||||
|
||||
|
||||
def test_parallel_execution_basic(client: AttuneClient, test_pack):
|
||||
"""
|
||||
Test basic parallel execution with with-items.
|
||||
|
||||
Flow:
|
||||
1. Create action with 5-second sleep
|
||||
2. Configure workflow with with-items on array of 5 items
|
||||
3. Configure concurrency: unlimited (all parallel)
|
||||
4. Execute workflow
|
||||
5. Measure total execution time
|
||||
6. Verify ~5 seconds total (not 25 seconds sequential)
|
||||
7. Verify all 5 children ran concurrently
|
||||
"""
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST: Parallel Execution with with-items (T2.10)")
|
||||
print("=" * 80)
|
||||
|
||||
pack_ref = test_pack["ref"]
|
||||
|
||||
# ========================================================================
|
||||
# STEP 1: Create action that sleeps
|
||||
# ========================================================================
|
||||
print("\n[STEP 1] Creating action that sleeps 3 seconds...")
|
||||
|
||||
sleep_script = """#!/usr/bin/env python3
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
|
||||
params = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
|
||||
item = params.get('item', 'unknown')
|
||||
|
||||
print(f'Processing item: {item}')
|
||||
time.sleep(3)
|
||||
print(f'Completed item: {item}')
|
||||
sys.exit(0)
|
||||
"""
|
||||
|
||||
action = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"parallel_action_{unique_ref()}",
|
||||
"description": "Action that processes items in parallel",
|
||||
"runner_type": "python3",
|
||||
"entry_point": "process.py",
|
||||
"enabled": True,
|
||||
"parameters": {"item": {"type": "string", "required": True}},
|
||||
},
|
||||
)
|
||||
action_ref = action["ref"]
|
||||
print(f"✓ Created action: {action_ref}")
|
||||
print(f" Sleep duration: 3 seconds per item")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 2: Create workflow with with-items
|
||||
# ========================================================================
|
||||
print("\n[STEP 2] Creating workflow with with-items...")
|
||||
|
||||
items = ["item1", "item2", "item3", "item4", "item5"]
|
||||
|
||||
workflow = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"parallel_workflow_{unique_ref()}",
|
||||
"description": "Workflow with parallel with-items",
|
||||
"runner_type": "workflow",
|
||||
"entry_point": "",
|
||||
"enabled": True,
|
||||
"parameters": {},
|
||||
"workflow_definition": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "process_items",
|
||||
"action": action_ref,
|
||||
"with_items": items,
|
||||
"concurrency": 0, # 0 or unlimited = no limit
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
)
|
||||
workflow_ref = workflow["ref"]
|
||||
print(f"✓ Created workflow: {workflow_ref}")
|
||||
print(f" Items: {items}")
|
||||
print(f" Concurrency: unlimited (all parallel)")
|
||||
print(f" Expected time: ~3 seconds (parallel)")
|
||||
print(f" Sequential would be: ~15 seconds")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 3: Execute workflow
|
||||
# ========================================================================
|
||||
print("\n[STEP 3] Executing workflow...")
|
||||
|
||||
start_time = time.time()
|
||||
workflow_execution = client.create_execution(action_ref=workflow_ref, parameters={})
|
||||
workflow_execution_id = workflow_execution["id"]
|
||||
print(f"✓ Workflow execution created: ID={workflow_execution_id}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 4: Wait for workflow to complete
|
||||
# ========================================================================
|
||||
print("\n[STEP 4] Waiting for workflow to complete...")
|
||||
|
||||
result = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=workflow_execution_id,
|
||||
expected_status="succeeded",
|
||||
timeout=20,
|
||||
)
|
||||
end_time = time.time()
|
||||
total_time = end_time - start_time
|
||||
|
||||
print(f"✓ Workflow completed: status={result['status']}")
|
||||
print(f" Total execution time: {total_time:.1f}s")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 5: Verify child executions
|
||||
# ========================================================================
|
||||
print("\n[STEP 5] Verifying child executions...")
|
||||
|
||||
all_executions = client.list_executions(limit=100)
|
||||
child_executions = [
|
||||
ex
|
||||
for ex in all_executions
|
||||
if ex.get("parent_execution_id") == workflow_execution_id
|
||||
]
|
||||
|
||||
print(f" Found {len(child_executions)} child executions")
|
||||
assert len(child_executions) >= len(items), (
|
||||
f"❌ Expected at least {len(items)} children, got {len(child_executions)}"
|
||||
)
|
||||
print(f" ✓ All {len(items)} items processed")
|
||||
|
||||
# Check all succeeded
|
||||
failed_children = [ex for ex in child_executions if ex["status"] != "succeeded"]
|
||||
assert len(failed_children) == 0, f"❌ {len(failed_children)} children failed"
|
||||
print(f" ✓ All children succeeded")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 6: Verify timing suggests parallel execution
|
||||
# ========================================================================
|
||||
print("\n[STEP 6] Verifying parallel execution timing...")
|
||||
|
||||
sequential_time = 3 * len(items) # 3s per item, 5 items = 15s
|
||||
parallel_time = 3 # All run at once = 3s
|
||||
|
||||
print(f" Sequential time would be: {sequential_time}s")
|
||||
print(f" Parallel time should be: ~{parallel_time}s")
|
||||
print(f" Actual time: {total_time:.1f}s")
|
||||
|
||||
if total_time < 8:
|
||||
print(f" ✓ Timing suggests parallel execution: {total_time:.1f}s < 8s")
|
||||
else:
|
||||
print(f" ⚠ Timing suggests sequential: {total_time:.1f}s >= 8s")
|
||||
print(f" (Parallel execution may not be implemented yet)")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 7: Validate success criteria
|
||||
# ========================================================================
|
||||
print("\n[STEP 7] Validating success criteria...")
|
||||
|
||||
assert result["status"] == "succeeded", "❌ Workflow should succeed"
|
||||
print(" ✓ Workflow succeeded")
|
||||
|
||||
assert len(child_executions) >= len(items), "❌ All items should execute"
|
||||
print(f" ✓ All {len(items)} items executed")
|
||||
|
||||
assert len(failed_children) == 0, "❌ All children should succeed"
|
||||
print(" ✓ All children succeeded")
|
||||
|
||||
# ========================================================================
|
||||
# FINAL SUMMARY
|
||||
# ========================================================================
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST SUMMARY: Parallel Execution with with-items")
|
||||
print("=" * 80)
|
||||
print(f"✓ Workflow with with-items: {workflow_ref}")
|
||||
print(f"✓ Items processed: {len(items)}")
|
||||
print(f"✓ Total time: {total_time:.1f}s")
|
||||
print(f"✓ Expected parallel time: ~3s")
|
||||
print(f"✓ Expected sequential time: ~15s")
|
||||
print(f"✓ All children completed successfully")
|
||||
print("\n✅ TEST PASSED: Parallel execution works correctly!")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
|
||||
def test_parallel_execution_with_concurrency_limit(client: AttuneClient, test_pack):
|
||||
"""
|
||||
Test parallel execution with concurrency limit.
|
||||
|
||||
Flow:
|
||||
1. Create workflow with 10 items
|
||||
2. Set concurrency limit: 3
|
||||
3. Verify at most 3 run at once
|
||||
4. Verify all 10 complete
|
||||
"""
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST: Parallel Execution - Concurrency Limit")
|
||||
print("=" * 80)
|
||||
|
||||
pack_ref = test_pack["ref"]
|
||||
|
||||
# ========================================================================
|
||||
# STEP 1: Create action
|
||||
# ========================================================================
|
||||
print("\n[STEP 1] Creating action...")
|
||||
|
||||
action = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"limited_parallel_{unique_ref()}",
|
||||
"description": "Action for limited parallelism test",
|
||||
"runner_type": "python3",
|
||||
"entry_point": "action.py",
|
||||
"enabled": True,
|
||||
"parameters": {"item": {"type": "string", "required": True}},
|
||||
},
|
||||
)
|
||||
action_ref = action["ref"]
|
||||
print(f"✓ Created action: {action_ref}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 2: Create workflow with concurrency limit
|
||||
# ========================================================================
|
||||
print("\n[STEP 2] Creating workflow with concurrency limit...")
|
||||
|
||||
items = [f"item{i}" for i in range(1, 11)] # 10 items
|
||||
|
||||
workflow = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"limited_workflow_{unique_ref()}",
|
||||
"description": "Workflow with concurrency limit",
|
||||
"runner_type": "workflow",
|
||||
"entry_point": "",
|
||||
"enabled": True,
|
||||
"parameters": {},
|
||||
"workflow_definition": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "process_items",
|
||||
"action": action_ref,
|
||||
"with_items": items,
|
||||
"concurrency": 3, # Max 3 at once
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
)
|
||||
workflow_ref = workflow["ref"]
|
||||
print(f"✓ Created workflow: {workflow_ref}")
|
||||
print(f" Items: {len(items)}")
|
||||
print(f" Concurrency limit: 3")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 3: Execute workflow
|
||||
# ========================================================================
|
||||
print("\n[STEP 3] Executing workflow...")
|
||||
|
||||
start_time = time.time()
|
||||
workflow_execution = client.create_execution(action_ref=workflow_ref, parameters={})
|
||||
workflow_execution_id = workflow_execution["id"]
|
||||
print(f"✓ Workflow execution created: ID={workflow_execution_id}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 4: Wait for completion
|
||||
# ========================================================================
|
||||
print("\n[STEP 4] Waiting for workflow to complete...")
|
||||
|
||||
result = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=workflow_execution_id,
|
||||
expected_status="succeeded",
|
||||
timeout=30,
|
||||
)
|
||||
end_time = time.time()
|
||||
total_time = end_time - start_time
|
||||
|
||||
print(f"✓ Workflow completed: status={result['status']}")
|
||||
print(f" Total time: {total_time:.1f}s")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 5: Verify all items processed
|
||||
# ========================================================================
|
||||
print("\n[STEP 5] Verifying all items processed...")
|
||||
|
||||
all_executions = client.list_executions(limit=150)
|
||||
child_executions = [
|
||||
ex
|
||||
for ex in all_executions
|
||||
if ex.get("parent_execution_id") == workflow_execution_id
|
||||
]
|
||||
|
||||
print(f" Found {len(child_executions)} child executions")
|
||||
assert len(child_executions) >= len(items), (
|
||||
f"❌ Expected at least {len(items)}, got {len(child_executions)}"
|
||||
)
|
||||
print(f" ✓ All {len(items)} items processed")
|
||||
|
||||
# ========================================================================
|
||||
# FINAL SUMMARY
|
||||
# ========================================================================
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST SUMMARY: Concurrency Limit")
|
||||
print("=" * 80)
|
||||
print(f"✓ Workflow: {workflow_ref}")
|
||||
print(f"✓ Items: {len(items)}")
|
||||
print(f"✓ Concurrency limit: 3")
|
||||
print(f"✓ All items processed: {len(child_executions)}")
|
||||
print(f"✓ Total time: {total_time:.1f}s")
|
||||
print("\n✅ TEST PASSED: Concurrency limit works correctly!")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
|
||||
def test_parallel_execution_sequential_mode(client: AttuneClient, test_pack):
|
||||
"""
|
||||
Test with-items in sequential mode (concurrency: 1).
|
||||
|
||||
Flow:
|
||||
1. Create workflow with concurrency: 1
|
||||
2. Verify items execute one at a time
|
||||
3. Verify total time equals sum of individual times
|
||||
"""
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST: Parallel Execution - Sequential Mode")
|
||||
print("=" * 80)
|
||||
|
||||
pack_ref = test_pack["ref"]
|
||||
|
||||
# ========================================================================
|
||||
# STEP 1: Create action
|
||||
# ========================================================================
|
||||
print("\n[STEP 1] Creating action...")
|
||||
|
||||
action = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"sequential_{unique_ref()}",
|
||||
"description": "Action for sequential test",
|
||||
"runner_type": "python3",
|
||||
"entry_point": "action.py",
|
||||
"enabled": True,
|
||||
"parameters": {"item": {"type": "string", "required": True}},
|
||||
},
|
||||
)
|
||||
action_ref = action["ref"]
|
||||
print(f"✓ Created action: {action_ref}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 2: Create workflow with concurrency: 1
|
||||
# ========================================================================
|
||||
print("\n[STEP 2] Creating workflow with concurrency: 1...")
|
||||
|
||||
items = ["item1", "item2", "item3"]
|
||||
|
||||
workflow = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"sequential_workflow_{unique_ref()}",
|
||||
"description": "Workflow with sequential execution",
|
||||
"runner_type": "workflow",
|
||||
"entry_point": "",
|
||||
"enabled": True,
|
||||
"parameters": {},
|
||||
"workflow_definition": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "process_items",
|
||||
"action": action_ref,
|
||||
"with_items": items,
|
||||
"concurrency": 1, # Sequential
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
)
|
||||
workflow_ref = workflow["ref"]
|
||||
print(f"✓ Created workflow: {workflow_ref}")
|
||||
print(f" Items: {len(items)}")
|
||||
print(f" Concurrency: 1 (sequential)")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 3: Execute and verify
|
||||
# ========================================================================
|
||||
print("\n[STEP 3] Executing workflow...")
|
||||
|
||||
start_time = time.time()
|
||||
workflow_execution = client.create_execution(action_ref=workflow_ref, parameters={})
|
||||
workflow_execution_id = workflow_execution["id"]
|
||||
print(f"✓ Workflow execution created: ID={workflow_execution_id}")
|
||||
|
||||
result = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=workflow_execution_id,
|
||||
expected_status="succeeded",
|
||||
timeout=20,
|
||||
)
|
||||
end_time = time.time()
|
||||
total_time = end_time - start_time
|
||||
|
||||
print(f"✓ Workflow completed: status={result['status']}")
|
||||
print(f" Total time: {total_time:.1f}s")
|
||||
|
||||
# ========================================================================
|
||||
# FINAL SUMMARY
|
||||
# ========================================================================
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST SUMMARY: Sequential Mode")
|
||||
print("=" * 80)
|
||||
print(f"✓ Workflow with concurrency: 1")
|
||||
print(f"✓ Items processed sequentially: {len(items)}")
|
||||
print(f"✓ Total time: {total_time:.1f}s")
|
||||
print("\n✅ TEST PASSED: Sequential mode works correctly!")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
|
||||
def test_parallel_execution_large_batch(client: AttuneClient, test_pack):
|
||||
"""
|
||||
Test parallel execution with large number of items.
|
||||
|
||||
Flow:
|
||||
1. Create workflow with 20 items
|
||||
2. Execute with concurrency: 10
|
||||
3. Verify all complete successfully
|
||||
4. Verify worker handles large batch
|
||||
"""
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST: Parallel Execution - Large Batch")
|
||||
print("=" * 80)
|
||||
|
||||
pack_ref = test_pack["ref"]
|
||||
|
||||
# ========================================================================
|
||||
# STEP 1: Create action
|
||||
# ========================================================================
|
||||
print("\n[STEP 1] Creating action...")
|
||||
|
||||
action = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"large_batch_{unique_ref()}",
|
||||
"description": "Action for large batch test",
|
||||
"runner_type": "python3",
|
||||
"entry_point": "action.py",
|
||||
"enabled": True,
|
||||
"parameters": {"item": {"type": "string", "required": True}},
|
||||
},
|
||||
)
|
||||
action_ref = action["ref"]
|
||||
print(f"✓ Created action: {action_ref}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 2: Create workflow with many items
|
||||
# ========================================================================
|
||||
print("\n[STEP 2] Creating workflow with 20 items...")
|
||||
|
||||
items = [f"item{i:02d}" for i in range(1, 21)] # 20 items
|
||||
|
||||
workflow = client.create_action(
|
||||
pack_ref=pack_ref,
|
||||
data={
|
||||
"name": f"large_batch_workflow_{unique_ref()}",
|
||||
"description": "Workflow with large batch",
|
||||
"runner_type": "workflow",
|
||||
"entry_point": "",
|
||||
"enabled": True,
|
||||
"parameters": {},
|
||||
"workflow_definition": {
|
||||
"tasks": [
|
||||
{
|
||||
"name": "process_items",
|
||||
"action": action_ref,
|
||||
"with_items": items,
|
||||
"concurrency": 10, # 10 at once
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
)
|
||||
workflow_ref = workflow["ref"]
|
||||
print(f"✓ Created workflow: {workflow_ref}")
|
||||
print(f" Items: {len(items)}")
|
||||
print(f" Concurrency: 10")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 3: Execute workflow
|
||||
# ========================================================================
|
||||
print("\n[STEP 3] Executing workflow with large batch...")
|
||||
|
||||
workflow_execution = client.create_execution(action_ref=workflow_ref, parameters={})
|
||||
workflow_execution_id = workflow_execution["id"]
|
||||
print(f"✓ Workflow execution created: ID={workflow_execution_id}")
|
||||
|
||||
result = wait_for_execution_status(
|
||||
client=client,
|
||||
execution_id=workflow_execution_id,
|
||||
expected_status="succeeded",
|
||||
timeout=40,
|
||||
)
|
||||
print(f"✓ Workflow completed: status={result['status']}")
|
||||
|
||||
# ========================================================================
|
||||
# STEP 4: Verify all items processed
|
||||
# ========================================================================
|
||||
print("\n[STEP 4] Verifying all items processed...")
|
||||
|
||||
all_executions = client.list_executions(limit=150)
|
||||
child_executions = [
|
||||
ex
|
||||
for ex in all_executions
|
||||
if ex.get("parent_execution_id") == workflow_execution_id
|
||||
]
|
||||
|
||||
print(f" Found {len(child_executions)} child executions")
|
||||
assert len(child_executions) >= len(items), (
|
||||
f"❌ Expected {len(items)}, got {len(child_executions)}"
|
||||
)
|
||||
print(f" ✓ All {len(items)} items processed")
|
||||
|
||||
succeeded = [ex for ex in child_executions if ex["status"] == "succeeded"]
|
||||
print(f" ✓ Succeeded: {len(succeeded)}/{len(child_executions)}")
|
||||
|
||||
# ========================================================================
|
||||
# FINAL SUMMARY
|
||||
# ========================================================================
|
||||
print("\n" + "=" * 80)
|
||||
print("TEST SUMMARY: Large Batch Processing")
|
||||
print("=" * 80)
|
||||
print(f"✓ Workflow: {workflow_ref}")
|
||||
print(f"✓ Items processed: {len(items)}")
|
||||
print(f"✓ Concurrency: 10")
|
||||
print(f"✓ All items completed successfully")
|
||||
print(f"✓ Worker handled large batch")
|
||||
print("\n✅ TEST PASSED: Large batch processing works correctly!")
|
||||
print("=" * 80 + "\n")
|
||||
Reference in New Issue
Block a user