mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
esphome:
 | 
						|
  name: scheduler-string-test
 | 
						|
  on_boot:
 | 
						|
    priority: -100
 | 
						|
    then:
 | 
						|
      - logger.log: "Starting scheduler string tests"
 | 
						|
  debug_scheduler: true  # Enable scheduler debug logging
 | 
						|
 | 
						|
host:
 | 
						|
api:
 | 
						|
logger:
 | 
						|
  level: VERBOSE
 | 
						|
 | 
						|
globals:
 | 
						|
  - id: timeout_counter
 | 
						|
    type: int
 | 
						|
    initial_value: '0'
 | 
						|
  - id: interval_counter
 | 
						|
    type: int
 | 
						|
    initial_value: '0'
 | 
						|
  - id: dynamic_counter
 | 
						|
    type: int
 | 
						|
    initial_value: '0'
 | 
						|
  - id: static_tests_done
 | 
						|
    type: bool
 | 
						|
    initial_value: 'false'
 | 
						|
  - id: dynamic_tests_done
 | 
						|
    type: bool
 | 
						|
    initial_value: 'false'
 | 
						|
  - id: results_reported
 | 
						|
    type: bool
 | 
						|
    initial_value: 'false'
 | 
						|
  - id: edge_tests_done
 | 
						|
    type: bool
 | 
						|
    initial_value: 'false'
 | 
						|
  - id: empty_cancel_failed
 | 
						|
    type: bool
 | 
						|
    initial_value: 'false'
 | 
						|
 | 
						|
script:
 | 
						|
  - id: test_static_strings
 | 
						|
    then:
 | 
						|
      - logger.log: "Testing static string timeouts and intervals"
 | 
						|
      - lambda: |-
 | 
						|
          auto *component1 = id(test_sensor1);
 | 
						|
          // Test 1: Static string literals with set_timeout
 | 
						|
          App.scheduler.set_timeout(component1, "static_timeout_1", 50, []() {
 | 
						|
            ESP_LOGI("test", "Static timeout 1 fired");
 | 
						|
            id(timeout_counter) += 1;
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 2: Static const char* with set_timeout
 | 
						|
          static const char* TIMEOUT_NAME = "static_timeout_2";
 | 
						|
          App.scheduler.set_timeout(component1, TIMEOUT_NAME, 100, []() {
 | 
						|
            ESP_LOGI("test", "Static timeout 2 fired");
 | 
						|
            id(timeout_counter) += 1;
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 3: Static string literal with set_interval
 | 
						|
          App.scheduler.set_interval(component1, "static_interval_1", 200, []() {
 | 
						|
            ESP_LOGI("test", "Static interval 1 fired, count: %d", id(interval_counter));
 | 
						|
            id(interval_counter) += 1;
 | 
						|
            if (id(interval_counter) >= 3) {
 | 
						|
              App.scheduler.cancel_interval(id(test_sensor1), "static_interval_1");
 | 
						|
              ESP_LOGI("test", "Cancelled static interval 1");
 | 
						|
            }
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 4: Empty string (should be handled safely)
 | 
						|
          App.scheduler.set_timeout(component1, "", 150, []() {
 | 
						|
            ESP_LOGI("test", "Empty string timeout fired");
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 5: Cancel timeout with const char* literal
 | 
						|
          App.scheduler.set_timeout(component1, "cancel_static_timeout", 5000, []() {
 | 
						|
            ESP_LOGI("test", "This static timeout should be cancelled");
 | 
						|
          });
 | 
						|
          // Cancel using const char* directly
 | 
						|
          App.scheduler.cancel_timeout(component1, "cancel_static_timeout");
 | 
						|
          ESP_LOGI("test", "Cancelled static timeout using const char*");
 | 
						|
 | 
						|
          // Test 6 & 7: Test defer with const char* overload using a test component
 | 
						|
          class TestDeferComponent : public Component {
 | 
						|
          public:
 | 
						|
            void test_static_defer() {
 | 
						|
              // Test 6: Static string literal with defer (const char* overload)
 | 
						|
              this->defer("static_defer_1", []() {
 | 
						|
                ESP_LOGI("test", "Static defer 1 fired");
 | 
						|
                id(timeout_counter) += 1;
 | 
						|
              });
 | 
						|
 | 
						|
              // Test 7: Static const char* with defer
 | 
						|
              static const char* DEFER_NAME = "static_defer_2";
 | 
						|
              this->defer(DEFER_NAME, []() {
 | 
						|
                ESP_LOGI("test", "Static defer 2 fired");
 | 
						|
                id(timeout_counter) += 1;
 | 
						|
              });
 | 
						|
            }
 | 
						|
          };
 | 
						|
 | 
						|
          static TestDeferComponent test_defer_component;
 | 
						|
          test_defer_component.test_static_defer();
 | 
						|
 | 
						|
  - id: test_dynamic_strings
 | 
						|
    then:
 | 
						|
      - logger.log: "Testing dynamic string timeouts and intervals"
 | 
						|
      - lambda: |-
 | 
						|
          auto *component2 = id(test_sensor2);
 | 
						|
 | 
						|
          // Test 8: Dynamic string with set_timeout (std::string)
 | 
						|
          std::string dynamic_name = "dynamic_timeout_" + std::to_string(id(dynamic_counter)++);
 | 
						|
          App.scheduler.set_timeout(component2, dynamic_name, 100, []() {
 | 
						|
            ESP_LOGI("test", "Dynamic timeout fired");
 | 
						|
            id(timeout_counter) += 1;
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 9: Dynamic string with set_interval
 | 
						|
          std::string interval_name = "dynamic_interval_" + std::to_string(id(dynamic_counter)++);
 | 
						|
          App.scheduler.set_interval(component2, interval_name, 250, [interval_name]() {
 | 
						|
            ESP_LOGI("test", "Dynamic interval fired: %s", interval_name.c_str());
 | 
						|
            id(interval_counter) += 1;
 | 
						|
            if (id(interval_counter) >= 6) {
 | 
						|
              App.scheduler.cancel_interval(id(test_sensor2), interval_name);
 | 
						|
              ESP_LOGI("test", "Cancelled dynamic interval");
 | 
						|
            }
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 10: Cancel with different string object but same content
 | 
						|
          std::string cancel_name = "cancel_test";
 | 
						|
          App.scheduler.set_timeout(component2, cancel_name, 2000, []() {
 | 
						|
            ESP_LOGI("test", "This should be cancelled");
 | 
						|
          });
 | 
						|
 | 
						|
          // Cancel using a different string object
 | 
						|
          std::string cancel_name_2 = "cancel_test";
 | 
						|
          App.scheduler.cancel_timeout(component2, cancel_name_2);
 | 
						|
          ESP_LOGI("test", "Cancelled timeout using different string object");
 | 
						|
 | 
						|
          // Test 11: Dynamic string with defer (using std::string overload)
 | 
						|
          class TestDynamicDeferComponent : public Component {
 | 
						|
          public:
 | 
						|
            void test_dynamic_defer() {
 | 
						|
              std::string defer_name = "dynamic_defer_" + std::to_string(id(dynamic_counter)++);
 | 
						|
              this->defer(defer_name, [defer_name]() {
 | 
						|
                ESP_LOGI("test", "Dynamic defer fired: %s", defer_name.c_str());
 | 
						|
                id(timeout_counter) += 1;
 | 
						|
              });
 | 
						|
            }
 | 
						|
          };
 | 
						|
 | 
						|
          static TestDynamicDeferComponent test_dynamic_defer_component;
 | 
						|
          test_dynamic_defer_component.test_dynamic_defer();
 | 
						|
 | 
						|
  - id: test_cancellation_edge_cases
 | 
						|
    then:
 | 
						|
      - logger.log: "Testing cancellation edge cases"
 | 
						|
      - lambda: |-
 | 
						|
          auto *component1 = id(test_sensor1);
 | 
						|
          // Use a different component for empty string tests to avoid interference
 | 
						|
          auto *component2 = id(test_sensor2);
 | 
						|
 | 
						|
          // Test 12: Cancel with empty string - regression test for issue #9599
 | 
						|
          // First create a timeout with empty name on component2 to avoid interference
 | 
						|
          App.scheduler.set_timeout(component2, "", 500, []() {
 | 
						|
            ESP_LOGE("test", "ERROR: Empty name timeout fired - it should have been cancelled!");
 | 
						|
            id(empty_cancel_failed) = true;
 | 
						|
          });
 | 
						|
 | 
						|
          // Now cancel it - this should work after our fix
 | 
						|
          bool cancelled_empty = App.scheduler.cancel_timeout(component2, "");
 | 
						|
          ESP_LOGI("test", "Cancel empty string result: %s (should be true)", cancelled_empty ? "true" : "false");
 | 
						|
          if (!cancelled_empty) {
 | 
						|
            ESP_LOGE("test", "ERROR: Failed to cancel empty string timeout!");
 | 
						|
            id(empty_cancel_failed) = true;
 | 
						|
          }
 | 
						|
 | 
						|
          // Test 13: Cancel non-existent timeout
 | 
						|
          bool cancelled_nonexistent = App.scheduler.cancel_timeout(component1, "does_not_exist");
 | 
						|
          ESP_LOGI("test", "Cancel non-existent timeout result: %s",
 | 
						|
                   cancelled_nonexistent ? "true (unexpected!)" : "false (expected)");
 | 
						|
 | 
						|
          // Test 14: Multiple timeouts with same name - only last should execute
 | 
						|
          for (int i = 0; i < 5; i++) {
 | 
						|
            App.scheduler.set_timeout(component1, "duplicate_timeout", 200 + i*10, [i]() {
 | 
						|
              ESP_LOGI("test", "Duplicate timeout %d fired", i);
 | 
						|
              id(timeout_counter) += 1;
 | 
						|
            });
 | 
						|
          }
 | 
						|
          ESP_LOGI("test", "Created 5 timeouts with same name 'duplicate_timeout'");
 | 
						|
 | 
						|
          // Test 15: Multiple intervals with same name - only last should run
 | 
						|
          for (int i = 0; i < 3; i++) {
 | 
						|
            App.scheduler.set_interval(component1, "duplicate_interval", 300, [i]() {
 | 
						|
              ESP_LOGI("test", "Duplicate interval %d fired", i);
 | 
						|
              id(interval_counter) += 10; // Large increment to detect multiple
 | 
						|
              // Cancel after first execution
 | 
						|
              App.scheduler.cancel_interval(id(test_sensor1), "duplicate_interval");
 | 
						|
            });
 | 
						|
          }
 | 
						|
          ESP_LOGI("test", "Created 3 intervals with same name 'duplicate_interval'");
 | 
						|
 | 
						|
          // Test 16: Cancel with nullptr protection (via empty const char*)
 | 
						|
          const char* null_name = "";
 | 
						|
          App.scheduler.set_timeout(component2, null_name, 600, []() {
 | 
						|
            ESP_LOGE("test", "ERROR: Const char* empty timeout fired - should have been cancelled!");
 | 
						|
            id(empty_cancel_failed) = true;
 | 
						|
          });
 | 
						|
          bool cancelled_const_empty = App.scheduler.cancel_timeout(component2, null_name);
 | 
						|
          ESP_LOGI("test", "Cancel const char* empty result: %s (should be true)",
 | 
						|
                   cancelled_const_empty ? "true" : "false");
 | 
						|
          if (!cancelled_const_empty) {
 | 
						|
            ESP_LOGE("test", "ERROR: Failed to cancel const char* empty timeout!");
 | 
						|
            id(empty_cancel_failed) = true;
 | 
						|
          }
 | 
						|
 | 
						|
          // Test 17: Rapid create/cancel/create with same name
 | 
						|
          App.scheduler.set_timeout(component1, "rapid_test", 5000, []() {
 | 
						|
            ESP_LOGI("test", "First rapid timeout - should not fire");
 | 
						|
            id(timeout_counter) += 100;
 | 
						|
          });
 | 
						|
          App.scheduler.cancel_timeout(component1, "rapid_test");
 | 
						|
          App.scheduler.set_timeout(component1, "rapid_test", 250, []() {
 | 
						|
            ESP_LOGI("test", "Second rapid timeout - should fire");
 | 
						|
            id(timeout_counter) += 1;
 | 
						|
          });
 | 
						|
 | 
						|
          // Test 18: Cancel all with a specific name (multiple instances)
 | 
						|
          // Create multiple with same name
 | 
						|
          App.scheduler.set_timeout(component1, "multi_cancel", 300, []() {
 | 
						|
            ESP_LOGI("test", "Multi-cancel timeout 1");
 | 
						|
          });
 | 
						|
          App.scheduler.set_timeout(component1, "multi_cancel", 350, []() {
 | 
						|
            ESP_LOGI("test", "Multi-cancel timeout 2");
 | 
						|
          });
 | 
						|
          App.scheduler.set_timeout(component1, "multi_cancel", 400, []() {
 | 
						|
            ESP_LOGI("test", "Multi-cancel timeout 3 - only this should fire");
 | 
						|
            id(timeout_counter) += 1;
 | 
						|
          });
 | 
						|
          // Note: Each set_timeout with same name cancels the previous one automatically
 | 
						|
 | 
						|
  - id: report_results
 | 
						|
    then:
 | 
						|
      - lambda: |-
 | 
						|
          ESP_LOGI("test", "Final results - Timeouts: %d, Intervals: %d",
 | 
						|
                   id(timeout_counter), id(interval_counter));
 | 
						|
 | 
						|
          // Check if empty string cancellation test passed
 | 
						|
          if (id(empty_cancel_failed)) {
 | 
						|
            ESP_LOGE("test", "ERROR: Empty string cancellation test FAILED!");
 | 
						|
          } else {
 | 
						|
            ESP_LOGI("test", "Empty string cancellation test PASSED");
 | 
						|
          }
 | 
						|
 | 
						|
sensor:
 | 
						|
  - platform: template
 | 
						|
    name: Test Sensor 1
 | 
						|
    id: test_sensor1
 | 
						|
    lambda: return 1.0;
 | 
						|
    update_interval: never
 | 
						|
 | 
						|
  - platform: template
 | 
						|
    name: Test Sensor 2
 | 
						|
    id: test_sensor2
 | 
						|
    lambda: return 2.0;
 | 
						|
    update_interval: never
 | 
						|
 | 
						|
interval:
 | 
						|
  # Run static string tests after boot - using script to run once
 | 
						|
  - interval: 0.1s
 | 
						|
    then:
 | 
						|
      - if:
 | 
						|
          condition:
 | 
						|
            lambda: 'return id(static_tests_done) == false;'
 | 
						|
          then:
 | 
						|
            - lambda: 'id(static_tests_done) = true;'
 | 
						|
            - script.execute: test_static_strings
 | 
						|
            - logger.log: "Started static string tests"
 | 
						|
 | 
						|
  # Run dynamic string tests after static tests
 | 
						|
  - interval: 0.2s
 | 
						|
    then:
 | 
						|
      - if:
 | 
						|
          condition:
 | 
						|
            lambda: 'return id(static_tests_done) && !id(dynamic_tests_done);'
 | 
						|
          then:
 | 
						|
            - lambda: 'id(dynamic_tests_done) = true;'
 | 
						|
            - delay: 0.2s
 | 
						|
            - script.execute: test_dynamic_strings
 | 
						|
 | 
						|
  # Run cancellation edge case tests after dynamic tests
 | 
						|
  - interval: 0.2s
 | 
						|
    then:
 | 
						|
      - if:
 | 
						|
          condition:
 | 
						|
            lambda: 'return id(dynamic_tests_done) && !id(edge_tests_done);'
 | 
						|
          then:
 | 
						|
            - lambda: 'id(edge_tests_done) = true;'
 | 
						|
            - delay: 0.5s
 | 
						|
            - script.execute: test_cancellation_edge_cases
 | 
						|
 | 
						|
  # Report results after all tests
 | 
						|
  - interval: 0.2s
 | 
						|
    then:
 | 
						|
      - if:
 | 
						|
          condition:
 | 
						|
            lambda: 'return id(edge_tests_done) && !id(results_reported);'
 | 
						|
          then:
 | 
						|
            - lambda: 'id(results_reported) = true;'
 | 
						|
            - delay: 1s
 | 
						|
            - script.execute: report_results
 |