[core] Construct App once in the C++ unit test main (#21001)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Igor Stadnyk
2026-10-17 19:00:37 +00:00
committed by GitHub
co-authored by J. Nick Koston
parent 4324b7d084
commit e110613ef1
15 changed files with 16 additions and 39 deletions
@@ -4,7 +4,6 @@
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <new>
#include <vector>
#include "esphome/components/daikin/daikin.h"
@@ -124,8 +123,6 @@ struct Rig {
// A second fixture in the same test would otherwise restore what the first saved
if (global_preferences != nullptr)
global_preferences->reset();
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
static char name[] = "test_daikin_regression";
static char friendly_name[] = "";
App.pre_setup(name, sizeof(name) - 1, friendly_name, 0);
-4
View File
@@ -2,7 +2,6 @@
#include <array>
#include <cstdint>
#include <cstdio>
#include <new>
#include <string>
#include <vector>
@@ -146,9 +145,6 @@ struct Fixture {
// A second fixture in the same test would otherwise restore what the first saved
if (!keep_preferences && global_preferences != nullptr)
global_preferences->reset();
// The test main does not construct App as generated code does; the boost timer needs its scheduler
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
static char name[] = "test_daikin";
static char friendly_name[] = "";
App.pre_setup(name, sizeof(name) - 1, friendly_name, 0);
@@ -31,11 +31,6 @@ class TestCardDisplay : public Display {
namespace {
class TestCard : public ::testing::Test {
protected:
void SetUp() override {
// test_card() stops the poller, which needs the scheduler; the test main does not construct App.
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
}
int writer_calls_{0};
display_writer_t count_writer_() {
return [this](Display &) { this->writer_calls_++; };
-6
View File
@@ -44,12 +44,6 @@ class MockUARTComponent : public uart::UARTComponent {
#endif // defined(USE_ESP8266) || defined(USE_ESP32)
};
// The test main does not construct App as generated code does
inline void construct_app() {
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
}
inline void cancel_timers(EmonTx *emontx) {
for (uint32_t id = 0; id < 3; id++)
App.scheduler.cancel_timeout(emontx, id);
-1
View File
@@ -14,7 +14,6 @@ namespace {
class EmonTxTest : public ::testing::Test {
protected:
void SetUp() override {
construct_app();
this->sut_.set_uart_parent(&this->uart_);
this->sut_.add_on_data_callback([this](StringRef line) { this->lines_.emplace_back(line.c_str(), line.size()); });
this->sut_.add_on_json_callback([this](JsonObject, StringRef) { this->json_count_++; });
@@ -12,7 +12,6 @@ namespace {
class EmonTxVersionTest : public ::testing::Test {
protected:
void SetUp() override {
construct_app();
this->sut_.set_uart_parent(&this->uart_);
this->sut_.set_version_text_sensor(&this->version_);
}
@@ -39,7 +39,9 @@ class FilterLifetimeTest : public ::testing::Test {
unsigned publications_{0};
void SetUp() override {
new (&App) Application;
// The test main keeps App alive; this test replaces it with a fresh one it owns
App.~Application();
new (&App) Application();
App.pre_setup("filter-lifetime-tests", 21, "Filter lifetime tests", 21);
host::setup_preferences();
host::host_preferences->reset();
@@ -55,6 +57,7 @@ class FilterLifetimeTest : public ::testing::Test {
void TearDown() override {
host::host_preferences->reset();
App.~Application();
new (&App) Application(); // later tests and the test listener use App
}
void attach(FilterLifetime &filter) {
@@ -1,7 +1,6 @@
#include <gtest/gtest.h>
#include <array>
#include <deque>
#include <new>
#include <vector>
#include "esphome/components/haier_water_heater/hon_water_heater.h"
@@ -53,9 +52,6 @@ class TestableHeater : public HonWaterHeater {
class HonControllerTest : public ::testing::Test {
protected:
void SetUp() override {
// The test runner skips generated setup(), which normally constructs App and its scheduler.
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
this->heater_.set_uart_parent(&this->uart_);
this->heater_.set_network_pending(false);
this->set_clock_(100);
+3 -1
View File
@@ -33,7 +33,9 @@ class ChipDevice : public Component, public I2CDevice {};
class I2CChipIdTest : public ::testing::Test {
protected:
void SetUp() override {
new (&App) Application;
// The test main keeps App alive; this test replaces it with a fresh one it owns
App.~Application();
new (&App) Application();
static char name[] = "i2c-chip-id-tests";
static char friendly_name[] = "I2C chip ID tests";
App.pre_setup(name, sizeof(name) - 1, friendly_name, sizeof(friendly_name) - 1);
+5
View File
@@ -32,6 +32,11 @@ class ResetAppListener : public ::testing::EmptyTestEventListener {
};
void setup() {
// Construct App once, as generated code does, before the logger, the listener or any test touches it.
// A fixture that destroys App must construct it again before returning.
// Placement new needs <new>, which the generated include block already provides
new (&esphome::App) esphome::Application;
// Log functions call global_logger->log_vprintf_() without a null check,
// so we must set up a Logger before any test that triggers logging.
static esphome::logger::Logger test_logger(0);
@@ -84,9 +84,6 @@ struct Fixture {
// A second fixture in the same test would otherwise restore what the first saved
if (global_preferences != nullptr)
global_preferences->reset();
// The test main does not construct App as generated code does
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
static char name[] = "test_mistral_ir";
static char friendly_name[] = "";
App.pre_setup(name, sizeof(name) - 1, friendly_name, 0);
@@ -72,9 +72,6 @@ struct Fixture {
// A second fixture in the same test would otherwise restore what the first saved
if (global_preferences != nullptr)
global_preferences->reset();
// The test main does not construct App as generated code does
static const bool APP_CONSTRUCTED = (new (&App) Application(), true);
(void) APP_CONSTRUCTED;
static char name[] = "test_pd_pioneer_ir";
static char friendly_name[] = "";
App.pre_setup(name, sizeof(name) - 1, friendly_name, 0);
@@ -46,9 +46,6 @@ class SnapshotAnimationTest : public ::testing::Test {
fs::remove_all(this->dir_);
fs::create_directories(this->dir_);
setenv("ESPHOME_SNAPSHOT_DIR", this->dir_.c_str(), 1);
// The test main does not construct App as generated code does, and recording needs its scheduler.
static const bool app_constructed = (new (&App) Application(), true);
(void) app_constructed;
App.pre_setup("test_snapshot", 10, "", 0);
}
void TearDown() override {
+4 -1
View File
@@ -71,7 +71,9 @@ class TMP102Test : public ::testing::Test {
unsigned publications_{0};
void SetUp() override {
new (&App) Application;
// The test main keeps App alive; this test replaces it with a fresh one it owns
App.~Application();
new (&App) Application();
App.pre_setup("tmp102-regression-tests", 23, "TMP102 tests", 12);
host::setup_preferences();
host::host_preferences->reset();
@@ -93,6 +95,7 @@ class TMP102Test : public ::testing::Test {
App.scheduler.call(millis());
host::host_preferences->reset();
App.~Application();
new (&App) Application(); // later tests and the test listener use App
}
static void run_timers(uint32_t duration) {
@@ -52,9 +52,6 @@ inline ble_device_base::ESPBTDevice advert(uint64_t address, const Frame &frame)
struct Harness {
Harness(uint64_t address, const char *key) : scale(address, key) {
// The test main does not construct App as generated code does; the stabilized reset needs its scheduler
static const bool app_constructed = (new (&App) Application(), true);
(void) app_constructed;
App.pre_setup("test_scale", 10, "", 0);
this->scale.set_weight(&this->weight);
this->scale.set_impedance_low(&this->impedance_low);