mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	test
This commit is contained in:
		
							
								
								
									
										167
									
								
								tests/integration/fixtures/script_queued.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								tests/integration/fixtures/script_queued.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| esphome: | ||||
|   name: test-script-queued | ||||
|  | ||||
| host: | ||||
| api: | ||||
|   actions: | ||||
|     # Test 1: Queue depth with default max_runs=5 | ||||
|     - action: test_queue_depth | ||||
|       then: | ||||
|         - logger.log: "=== TEST 1: Queue depth (should process 1-5, reject 6) ===" | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 1 | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 2 | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 3 | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 4 | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 5 | ||||
|         - script.execute: | ||||
|             id: queue_depth_script | ||||
|             value: 6 | ||||
|  | ||||
|     # Test 2: Ring buffer wrap test | ||||
|     - action: test_ring_buffer | ||||
|       then: | ||||
|         - logger.log: "=== TEST 2: Ring buffer wrap (should process A, B, C in order) ===" | ||||
|         - script.execute: | ||||
|             id: wrap_script | ||||
|             msg: "A" | ||||
|         - script.execute: | ||||
|             id: wrap_script | ||||
|             msg: "B" | ||||
|         - script.execute: | ||||
|             id: wrap_script | ||||
|             msg: "C" | ||||
|  | ||||
|     # Test 3: Stop clears queue | ||||
|     - action: test_stop_clears | ||||
|       then: | ||||
|         - logger.log: "=== TEST 3: Stop clears queue (should only see 1, then 'STOPPED') ===" | ||||
|         - script.execute: | ||||
|             id: stop_script | ||||
|             num: 1 | ||||
|         - script.execute: | ||||
|             id: stop_script | ||||
|             num: 2 | ||||
|         - script.execute: | ||||
|             id: stop_script | ||||
|             num: 3 | ||||
|         - delay: 50ms | ||||
|         - logger.log: "STOPPING script now" | ||||
|         - script.stop: stop_script | ||||
|  | ||||
|     # Test 4: Verify rejection (max_runs=3) | ||||
|     - action: test_rejection | ||||
|       then: | ||||
|         - logger.log: "=== TEST 4: Verify rejection (max_runs=3, try 8) ===" | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 1 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 2 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 3 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 4 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 5 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 6 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 7 | ||||
|         - script.execute: | ||||
|             id: rejection_script | ||||
|             val: 8 | ||||
|  | ||||
|     # Test 5: No parameters test | ||||
|     - action: test_no_params | ||||
|       then: | ||||
|         - logger.log: "=== TEST 5: No params (should process 3 times) ===" | ||||
|         - script.execute: no_params_script | ||||
|         - script.execute: no_params_script | ||||
|         - script.execute: no_params_script | ||||
|  | ||||
| logger: | ||||
|   level: DEBUG | ||||
|  | ||||
| script: | ||||
|   # Test script 1: Queue depth test (default max_runs=5) | ||||
|   - id: queue_depth_script | ||||
|     mode: queued | ||||
|     parameters: | ||||
|       value: int | ||||
|     then: | ||||
|       - logger.log: | ||||
|           format: "Queue test: START item %d" | ||||
|           args: ['value'] | ||||
|       - delay: 100ms | ||||
|       - logger.log: | ||||
|           format: "Queue test: END item %d" | ||||
|           args: ['value'] | ||||
|  | ||||
|   # Test script 2: Ring buffer wrap test (max_runs=3) | ||||
|   - id: wrap_script | ||||
|     mode: queued | ||||
|     max_runs: 3 | ||||
|     parameters: | ||||
|       msg: string | ||||
|     then: | ||||
|       - logger.log: | ||||
|           format: "Ring buffer: START '%s'" | ||||
|           args: ['msg.c_str()'] | ||||
|       - delay: 50ms | ||||
|       - logger.log: | ||||
|           format: "Ring buffer: END '%s'" | ||||
|           args: ['msg.c_str()'] | ||||
|  | ||||
|   # Test script 3: Stop test | ||||
|   - id: stop_script | ||||
|     mode: queued | ||||
|     max_runs: 5 | ||||
|     parameters: | ||||
|       num: int | ||||
|     then: | ||||
|       - logger.log: | ||||
|           format: "Stop test: START %d" | ||||
|           args: ['num'] | ||||
|       - delay: 100ms | ||||
|       - logger.log: | ||||
|           format: "Stop test: END %d" | ||||
|           args: ['num'] | ||||
|  | ||||
|   # Test script 4: Rejection test (max_runs=3) | ||||
|   - id: rejection_script | ||||
|     mode: queued | ||||
|     max_runs: 3 | ||||
|     parameters: | ||||
|       val: int | ||||
|     then: | ||||
|       - logger.log: | ||||
|           format: "Rejection test: START %d" | ||||
|           args: ['val'] | ||||
|       - delay: 200ms | ||||
|       - logger.log: | ||||
|           format: "Rejection test: END %d" | ||||
|           args: ['val'] | ||||
|  | ||||
|   # Test script 5: No parameters | ||||
|   - id: no_params_script | ||||
|     mode: queued | ||||
|     then: | ||||
|       - logger.log: "No params: START" | ||||
|       - delay: 50ms | ||||
|       - logger.log: "No params: END" | ||||
							
								
								
									
										207
									
								
								tests/integration/test_script_queued.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								tests/integration/test_script_queued.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| """Test ESPHome queued script functionality.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
| import re | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_script_queued( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test comprehensive queued script functionality.""" | ||||
|     loop = asyncio.get_running_loop() | ||||
|  | ||||
|     # Track all test results | ||||
|     test_results = { | ||||
|         "queue_depth": {"processed": [], "rejections": 0}, | ||||
|         "ring_buffer": {"start_order": [], "end_order": []}, | ||||
|         "stop": {"processed": [], "stop_logged": False}, | ||||
|         "rejection": {"processed": [], "rejections": 0}, | ||||
|         "no_params": {"executions": 0}, | ||||
|     } | ||||
|  | ||||
|     # Patterns for Test 1: Queue depth | ||||
|     queue_start = re.compile(r"Queue test: START item (\d+)") | ||||
|     queue_end = re.compile(r"Queue test: END item (\d+)") | ||||
|     queue_reject = re.compile( | ||||
|         r"Script 'queue_depth_script' maximum number of queued runs exceeded!" | ||||
|     ) | ||||
|  | ||||
|     # Patterns for Test 2: Ring buffer | ||||
|     ring_start = re.compile(r"Ring buffer: START '([A-Z])'") | ||||
|     ring_end = re.compile(r"Ring buffer: END '([A-Z])'") | ||||
|  | ||||
|     # Patterns for Test 3: Stop | ||||
|     stop_start = re.compile(r"Stop test: START (\d+)") | ||||
|     stop_log = re.compile(r"STOPPING script now") | ||||
|  | ||||
|     # Patterns for Test 4: Rejection | ||||
|     reject_start = re.compile(r"Rejection test: START (\d+)") | ||||
|     reject_end = re.compile(r"Rejection test: END (\d+)") | ||||
|     reject_reject = re.compile( | ||||
|         r"Script 'rejection_script' maximum number of queued runs exceeded!" | ||||
|     ) | ||||
|  | ||||
|     # Patterns for Test 5: No params | ||||
|     no_params_end = re.compile(r"No params: END") | ||||
|  | ||||
|     # Test completion futures | ||||
|     test1_complete = loop.create_future() | ||||
|     test2_complete = loop.create_future() | ||||
|     test3_complete = loop.create_future() | ||||
|     test4_complete = loop.create_future() | ||||
|     test5_complete = loop.create_future() | ||||
|  | ||||
|     def check_output(line: str) -> None: | ||||
|         """Check log output for all test messages.""" | ||||
|         # Test 1: Queue depth | ||||
|         if match := queue_start.search(line): | ||||
|             item = int(match.group(1)) | ||||
|             if item not in test_results["queue_depth"]["processed"]: | ||||
|                 test_results["queue_depth"]["processed"].append(item) | ||||
|  | ||||
|         if match := queue_end.search(line): | ||||
|             item = int(match.group(1)) | ||||
|             if item == 5 and not test1_complete.done(): | ||||
|                 test1_complete.set_result(True) | ||||
|  | ||||
|         if queue_reject.search(line): | ||||
|             test_results["queue_depth"]["rejections"] += 1 | ||||
|  | ||||
|         # Test 2: Ring buffer | ||||
|         if match := ring_start.search(line): | ||||
|             msg = match.group(1) | ||||
|             test_results["ring_buffer"]["start_order"].append(msg) | ||||
|  | ||||
|         if match := ring_end.search(line): | ||||
|             msg = match.group(1) | ||||
|             test_results["ring_buffer"]["end_order"].append(msg) | ||||
|             if ( | ||||
|                 len(test_results["ring_buffer"]["end_order"]) == 3 | ||||
|                 and not test2_complete.done() | ||||
|             ): | ||||
|                 test2_complete.set_result(True) | ||||
|  | ||||
|         # Test 3: Stop | ||||
|         if match := stop_start.search(line): | ||||
|             item = int(match.group(1)) | ||||
|             if item not in test_results["stop"]["processed"]: | ||||
|                 test_results["stop"]["processed"].append(item) | ||||
|  | ||||
|         if stop_log.search(line): | ||||
|             test_results["stop"]["stop_logged"] = True | ||||
|             # Give time for any queued items to be cleared | ||||
|             if not test3_complete.done(): | ||||
|                 loop.call_later( | ||||
|                     0.3, | ||||
|                     lambda: test3_complete.set_result(True) | ||||
|                     if not test3_complete.done() | ||||
|                     else None, | ||||
|                 ) | ||||
|  | ||||
|         # Test 4: Rejection | ||||
|         if match := reject_start.search(line): | ||||
|             item = int(match.group(1)) | ||||
|             if item not in test_results["rejection"]["processed"]: | ||||
|                 test_results["rejection"]["processed"].append(item) | ||||
|  | ||||
|         if match := reject_end.search(line): | ||||
|             item = int(match.group(1)) | ||||
|             if item == 3 and not test4_complete.done(): | ||||
|                 test4_complete.set_result(True) | ||||
|  | ||||
|         if reject_reject.search(line): | ||||
|             test_results["rejection"]["rejections"] += 1 | ||||
|  | ||||
|         # Test 5: No params | ||||
|         if no_params_end.search(line): | ||||
|             test_results["no_params"]["executions"] += 1 | ||||
|             if ( | ||||
|                 test_results["no_params"]["executions"] == 3 | ||||
|                 and not test5_complete.done() | ||||
|             ): | ||||
|                 test5_complete.set_result(True) | ||||
|  | ||||
|     async with ( | ||||
|         run_compiled(yaml_config, line_callback=check_output), | ||||
|         api_client_connected() as client, | ||||
|     ): | ||||
|         # Get services | ||||
|         entities, services = await client.list_entities_services() | ||||
|  | ||||
|         # Test 1: Queue depth limit | ||||
|         test_service = next((s for s in services if s.name == "test_queue_depth"), None) | ||||
|         assert test_service is not None, "test_queue_depth service not found" | ||||
|         client.execute_service(test_service, {}) | ||||
|         await asyncio.wait_for(test1_complete, timeout=2.0) | ||||
|         await asyncio.sleep(0.1)  # Give time for rejections | ||||
|  | ||||
|         # Verify Test 1 | ||||
|         assert sorted(test_results["queue_depth"]["processed"]) == [1, 2, 3, 4, 5], ( | ||||
|             f"Test 1: Expected to process items 1-5, got {sorted(test_results['queue_depth']['processed'])}" | ||||
|         ) | ||||
|         assert test_results["queue_depth"]["rejections"] > 0, ( | ||||
|             "Test 1: Expected at least one rejection warning" | ||||
|         ) | ||||
|  | ||||
|         # Test 2: Ring buffer order | ||||
|         test_service = next((s for s in services if s.name == "test_ring_buffer"), None) | ||||
|         assert test_service is not None, "test_ring_buffer service not found" | ||||
|         client.execute_service(test_service, {}) | ||||
|         await asyncio.wait_for(test2_complete, timeout=2.0) | ||||
|  | ||||
|         # Verify Test 2 | ||||
|         assert test_results["ring_buffer"]["start_order"] == ["A", "B", "C"], ( | ||||
|             f"Test 2: Expected start order [A, B, C], got {test_results['ring_buffer']['start_order']}" | ||||
|         ) | ||||
|         assert test_results["ring_buffer"]["end_order"] == ["A", "B", "C"], ( | ||||
|             f"Test 2: Expected end order [A, B, C], got {test_results['ring_buffer']['end_order']}" | ||||
|         ) | ||||
|  | ||||
|         # Test 3: Stop clears queue | ||||
|         test_service = next((s for s in services if s.name == "test_stop_clears"), None) | ||||
|         assert test_service is not None, "test_stop_clears service not found" | ||||
|         client.execute_service(test_service, {}) | ||||
|         await asyncio.wait_for(test3_complete, timeout=2.0) | ||||
|  | ||||
|         # Verify Test 3 | ||||
|         assert test_results["stop"]["stop_logged"], ( | ||||
|             "Test 3: Stop command was not logged" | ||||
|         ) | ||||
|         assert test_results["stop"]["processed"] == [1], ( | ||||
|             f"Test 3: Expected only item 1 to process, got {test_results['stop']['processed']}" | ||||
|         ) | ||||
|  | ||||
|         # Test 4: Rejection enforcement (max_runs=3) | ||||
|         test_service = next((s for s in services if s.name == "test_rejection"), None) | ||||
|         assert test_service is not None, "test_rejection service not found" | ||||
|         client.execute_service(test_service, {}) | ||||
|         await asyncio.wait_for(test4_complete, timeout=2.0) | ||||
|         await asyncio.sleep(0.1)  # Give time for rejections | ||||
|  | ||||
|         # Verify Test 4 | ||||
|         assert sorted(test_results["rejection"]["processed"]) == [1, 2, 3], ( | ||||
|             f"Test 4: Expected to process items 1-3, got {sorted(test_results['rejection']['processed'])}" | ||||
|         ) | ||||
|         assert test_results["rejection"]["rejections"] == 5, ( | ||||
|             f"Test 4: Expected 5 rejections (items 4-8), got {test_results['rejection']['rejections']}" | ||||
|         ) | ||||
|  | ||||
|         # Test 5: No parameters | ||||
|         test_service = next((s for s in services if s.name == "test_no_params"), None) | ||||
|         assert test_service is not None, "test_no_params service not found" | ||||
|         client.execute_service(test_service, {}) | ||||
|         await asyncio.wait_for(test5_complete, timeout=2.0) | ||||
|  | ||||
|         # Verify Test 5 | ||||
|         assert test_results["no_params"]["executions"] == 3, ( | ||||
|             f"Test 5: Expected 3 executions, got {test_results['no_params']['executions']}" | ||||
|         ) | ||||
		Reference in New Issue
	
	Block a user