mirror of
https://github.com/esphome/esphome.git
synced 2026-10-13 17:52:51 +00:00
Merge branch 'neutral-ble-client' into radon-eye-single-node
This commit is contained in:
@@ -4,3 +4,4 @@ output:
|
||||
gate_pin: ${gate_pin}
|
||||
zero_cross_pin: ${zero_cross_pin}
|
||||
zero_cross_interrupt_type: ANY
|
||||
min_power: 0%
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import CORE
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# USE_API compiles every api source, so emit what they need. No socket
|
||||
# override: an __init__.py there makes pytest import its conftest as socket.conftest.
|
||||
async def to_code_testing(config):
|
||||
cg.add_define("USE_API")
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("API_MAX_SEND_QUEUE", 8)
|
||||
cg.add_define("MAX_API_CONNECTIONS", 1)
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
CORE.register_controller() # api_server registers with the controller registry
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
|
||||
namespace esphome::api::testing {
|
||||
|
||||
// Pointer plus two 16 bit sizes
|
||||
static_assert(sizeof(APIBuffer) <= 2 * sizeof(void *));
|
||||
|
||||
TEST(APIBuffer, RefusesSizesAbove16Bits) {
|
||||
APIBuffer buf;
|
||||
ASSERT_TRUE(buf.resize(16));
|
||||
EXPECT_FALSE(buf.reserve(UINT16_MAX + 1));
|
||||
EXPECT_EQ(buf.size(), 16u);
|
||||
EXPECT_EQ(buf.capacity(), 16u);
|
||||
EXPECT_TRUE(buf.reserve(UINT16_MAX));
|
||||
EXPECT_EQ(buf.capacity(), UINT16_MAX);
|
||||
}
|
||||
|
||||
static const uint8_t BYTES[] = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
TEST(APIBuffer, AppendReturnsTheNewBytes) {
|
||||
APIBuffer buf;
|
||||
ASSERT_TRUE(buf.reserve(8));
|
||||
uint8_t *first = buf.append(3);
|
||||
ASSERT_NE(first, nullptr);
|
||||
std::memcpy(first, BYTES, 3);
|
||||
EXPECT_EQ(buf.size(), 3u);
|
||||
EXPECT_EQ(buf.capacity(), 8u);
|
||||
|
||||
// Grows through realloc and keeps what was there
|
||||
uint8_t *second = buf.append(6);
|
||||
ASSERT_EQ(second, buf.data() + 3);
|
||||
std::memcpy(second, BYTES + 3, 3);
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
EXPECT_EQ(buf.capacity(), 9u);
|
||||
EXPECT_EQ(std::memcmp(buf.data(), BYTES, 6), 0);
|
||||
}
|
||||
|
||||
TEST(APIBuffer, DropFrontSlidesTheRestDown) {
|
||||
APIBuffer buf;
|
||||
uint8_t *bytes = buf.append(6);
|
||||
ASSERT_NE(bytes, nullptr);
|
||||
std::memcpy(bytes, BYTES, 6);
|
||||
|
||||
buf.drop_front(2);
|
||||
EXPECT_EQ(buf.size(), 4u);
|
||||
EXPECT_EQ(buf.capacity(), 6u);
|
||||
EXPECT_EQ(std::memcmp(buf.data(), BYTES + 2, 4), 0);
|
||||
|
||||
// Growing afterwards keeps the slid bytes
|
||||
ASSERT_TRUE(buf.reserve(64));
|
||||
EXPECT_EQ(buf.size(), 4u);
|
||||
EXPECT_EQ(std::memcmp(buf.data(), BYTES + 2, 4), 0);
|
||||
|
||||
// Dropping everything leaves an empty buffer with its capacity
|
||||
buf.drop_front(4);
|
||||
EXPECT_EQ(buf.size(), 0u);
|
||||
EXPECT_EQ(buf.capacity(), 64u);
|
||||
}
|
||||
|
||||
} // namespace esphome::api::testing
|
||||
@@ -0,0 +1,510 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/api/api_overflow_buffer.h"
|
||||
|
||||
#ifdef USE_HOST
|
||||
namespace esphome::api::testing {
|
||||
|
||||
// Idle cost is the buffer plus one word of bookkeeping
|
||||
static_assert(sizeof(APIOverflowBuffer) <= sizeof(APIBuffer) + sizeof(void *));
|
||||
|
||||
// Exposes storage so tests can check it is reused, not reallocated
|
||||
class TestOverflowBuffer : public APIOverflowBuffer {
|
||||
public:
|
||||
using APIOverflowBuffer::LEN_PREFIX;
|
||||
using APIOverflowBuffer::MAX_BYTES;
|
||||
using APIOverflowBuffer::MAX_LONE_BYTES;
|
||||
struct Storage {
|
||||
size_t capacity;
|
||||
const uint8_t *data;
|
||||
bool operator==(const Storage &) const = default;
|
||||
};
|
||||
size_t capacity() const { return this->buf_.capacity(); }
|
||||
Storage storage() const { return {this->buf_.capacity(), this->buf_.data()}; }
|
||||
uint8_t count() const { return this->count_; }
|
||||
size_t live() const { return this->buf_.size() - this->head_; }
|
||||
/// Simulates a socket write inside try_drain() re-entering the send path
|
||||
void set_draining(bool draining) { this->draining_ = draining; }
|
||||
};
|
||||
|
||||
static std::vector<uint8_t> make_message(size_t len, uint8_t seed) {
|
||||
std::vector<uint8_t> msg(len);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
msg[i] = static_cast<uint8_t>(seed + i);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static bool enqueue(TestOverflowBuffer &buf, const std::vector<uint8_t> &msg, uint16_t skip = 0) {
|
||||
struct iovec iov = {const_cast<uint8_t *>(msg.data()), msg.size()};
|
||||
return buf.enqueue_iov(&iov, 1, static_cast<uint16_t>(msg.size()), skip);
|
||||
}
|
||||
|
||||
static void append(std::vector<uint8_t> &dst, const std::vector<uint8_t> &src, size_t skip = 0) {
|
||||
dst.insert(dst.end(), src.begin() + skip, src.end());
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> concat(std::initializer_list<std::vector<uint8_t>> parts) {
|
||||
std::vector<uint8_t> out;
|
||||
for (const auto &part : parts)
|
||||
append(out, part);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The pipe delivers the filler first, then the drained messages.
|
||||
static void expect_after_filler(const std::vector<uint8_t> &received, size_t filler,
|
||||
const std::vector<uint8_t> &expected) {
|
||||
ASSERT_EQ(received.size(), filler + expected.size());
|
||||
EXPECT_TRUE(std::equal(expected.begin(), expected.end(), received.begin() + filler));
|
||||
}
|
||||
|
||||
// Non-blocking socket pair with small buffers, so the writer fills like a stalled TCP connection
|
||||
class OverflowBufferTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
int fds[2];
|
||||
ASSERT_EQ(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0);
|
||||
int size = 4096;
|
||||
ASSERT_EQ(::setsockopt(fds[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)), 0);
|
||||
ASSERT_EQ(::setsockopt(fds[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)), 0);
|
||||
ASSERT_EQ(::fcntl(fds[1], F_SETFL, O_NONBLOCK), 0);
|
||||
this->reader_ = fds[1];
|
||||
this->sock_ = std::make_unique<socket::Socket>(fds[0]);
|
||||
ASSERT_EQ(this->sock_->setblocking(false), 0);
|
||||
}
|
||||
void TearDown() override { ::close(this->reader_); }
|
||||
|
||||
/// Write filler until the socket refuses; returns the bytes accepted
|
||||
size_t fill_pipe_() {
|
||||
uint8_t junk[512];
|
||||
std::memset(junk, 0xEE, sizeof(junk));
|
||||
size_t total = 0;
|
||||
for (;;) {
|
||||
ssize_t written = this->sock_->write(junk, sizeof(junk));
|
||||
if (written <= 0)
|
||||
break;
|
||||
total += static_cast<size_t>(written);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Append whatever the pipe currently holds.
|
||||
void read_into_(std::vector<uint8_t> &out) {
|
||||
uint8_t tmp[1024];
|
||||
for (;;) {
|
||||
ssize_t n = ::read(this->reader_, tmp, sizeof(tmp));
|
||||
if (n <= 0)
|
||||
break;
|
||||
out.insert(out.end(), tmp, tmp + n);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain once; a refusal must be a would-block, never a hard error.
|
||||
ssize_t drain_(TestOverflowBuffer &buf) {
|
||||
ssize_t sent = buf.try_drain(this->sock_.get());
|
||||
if (sent == -1) {
|
||||
EXPECT_TRUE(errno == EWOULDBLOCK || errno == EAGAIN);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
/// Read and drain until the backlog is empty; returns all bytes received
|
||||
std::vector<uint8_t> drain_all_(TestOverflowBuffer &buf) {
|
||||
std::vector<uint8_t> received;
|
||||
for (int i = 0; i < 10000 && !buf.empty(); i++) {
|
||||
this->read_into_(received);
|
||||
// A hard socket error would never clear the backlog; stop instead of spinning
|
||||
if (this->drain_(buf) == -1 && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||
break;
|
||||
}
|
||||
EXPECT_TRUE(buf.empty());
|
||||
this->read_into_(received);
|
||||
return received;
|
||||
}
|
||||
|
||||
struct Stall {
|
||||
size_t filler;
|
||||
std::vector<uint8_t> first, second, received;
|
||||
TestOverflowBuffer::Storage before;
|
||||
};
|
||||
/// Park two messages, then drain the first fully and the second part way
|
||||
void stall_mid_message_(TestOverflowBuffer &buf, Stall &s) {
|
||||
s.filler = this->fill_pipe_();
|
||||
s.first = make_message(1500, 20);
|
||||
ASSERT_GT(s.filler, s.first.size()); // the first message must drain in one go
|
||||
// Larger than the whole pipe, so a drain always stops inside it
|
||||
s.second = make_message(std::max<size_t>(s.filler + 1, std::min<size_t>(s.filler * 3, 12000)), 60);
|
||||
ASSERT_GT(s.second.size(), s.filler);
|
||||
ASSERT_TRUE(enqueue(buf, s.first));
|
||||
ASSERT_TRUE(enqueue(buf, s.second));
|
||||
s.before = buf.storage();
|
||||
this->read_into_(s.received);
|
||||
ASSERT_GT(this->drain_(buf), 0);
|
||||
ASSERT_EQ(buf.count(), 1);
|
||||
}
|
||||
|
||||
int reader_{-1};
|
||||
std::unique_ptr<socket::Socket> sock_;
|
||||
};
|
||||
|
||||
TEST_F(OverflowBufferTest, IdleBufferOwnsNoStorage) {
|
||||
TestOverflowBuffer buf;
|
||||
EXPECT_TRUE(buf.empty());
|
||||
EXPECT_EQ(buf.capacity(), 0u);
|
||||
EXPECT_EQ(buf.storage().data, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, StorageIsReusedAcrossStalls) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(1000, 1);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
const auto storage = buf.storage();
|
||||
EXPECT_GE(storage.capacity, msg.size() + TestOverflowBuffer::LEN_PREFIX);
|
||||
|
||||
for (int stall = 0; stall < 5; stall++) {
|
||||
expect_after_filler(this->drain_all_(buf), filler, msg);
|
||||
EXPECT_TRUE(buf.empty());
|
||||
// Same allocation every time: no free, no new allocation
|
||||
EXPECT_EQ(buf.storage(), storage);
|
||||
|
||||
filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
EXPECT_EQ(buf.storage(), storage);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, ReleaseWhileQueuedFreesOnceDrained) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(1000, 7);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
const size_t capacity = buf.capacity();
|
||||
|
||||
// Requested while the backlog still holds data: storage must stay until sent
|
||||
buf.release();
|
||||
EXPECT_FALSE(buf.empty());
|
||||
EXPECT_EQ(buf.capacity(), capacity);
|
||||
|
||||
expect_after_filler(this->drain_all_(buf), filler, msg);
|
||||
EXPECT_TRUE(buf.empty());
|
||||
EXPECT_EQ(buf.capacity(), 0u);
|
||||
EXPECT_EQ(buf.storage().data, nullptr);
|
||||
|
||||
// A later stall allocates again and keeps it, since nobody asked for a release
|
||||
filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
EXPECT_GT(buf.capacity(), 0u);
|
||||
this->drain_all_(buf);
|
||||
EXPECT_GT(buf.capacity(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, ReleaseWhenEmptyFreesImmediately) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(100, 3);
|
||||
|
||||
this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
this->drain_all_(buf);
|
||||
EXPECT_GT(buf.capacity(), 0u);
|
||||
|
||||
buf.release();
|
||||
EXPECT_EQ(buf.capacity(), 0u);
|
||||
EXPECT_EQ(buf.storage().data, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, PreservesOrderAndSkipsSentPrefix) {
|
||||
TestOverflowBuffer buf;
|
||||
auto first = make_message(700, 10);
|
||||
auto second_a = make_message(300, 50);
|
||||
auto second_b = make_message(400, 90);
|
||||
auto third = make_message(200, 130);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
// 100 bytes of the first message were already accepted by the socket
|
||||
ASSERT_TRUE(enqueue(buf, first, 100));
|
||||
// Two iovecs with the skip covering all of the first one plus part of the second
|
||||
struct iovec iov[2] = {{second_a.data(), second_a.size()}, {second_b.data(), second_b.size()}};
|
||||
const uint16_t second_skip = static_cast<uint16_t>(second_a.size() + 5);
|
||||
ASSERT_TRUE(buf.enqueue_iov(iov, 2, static_cast<uint16_t>(second_a.size() + second_b.size()), second_skip));
|
||||
ASSERT_TRUE(enqueue(buf, third));
|
||||
EXPECT_EQ(buf.count(), 3);
|
||||
|
||||
// Nothing can go out while the pipe is full
|
||||
EXPECT_EQ(this->drain_(buf), -1);
|
||||
EXPECT_EQ(buf.count(), 3);
|
||||
|
||||
std::vector<uint8_t> expected;
|
||||
append(expected, first, 100);
|
||||
append(expected, second_b, 5);
|
||||
append(expected, third);
|
||||
expect_after_filler(this->drain_all_(buf), filler, expected);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, RefusesWhenQueueIsFull) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(16, 1);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
for (int i = 0; i < API_MAX_SEND_QUEUE; i++) {
|
||||
ASSERT_TRUE(enqueue(buf, msg)) << "message " << i;
|
||||
}
|
||||
EXPECT_FALSE(enqueue(buf, msg));
|
||||
EXPECT_EQ(buf.count(), API_MAX_SEND_QUEUE);
|
||||
|
||||
// Draining frees the slots again
|
||||
std::vector<uint8_t> expected;
|
||||
for (int i = 0; i < API_MAX_SEND_QUEUE; i++)
|
||||
append(expected, msg);
|
||||
expect_after_filler(this->drain_all_(buf), filler, expected);
|
||||
this->fill_pipe_();
|
||||
EXPECT_TRUE(enqueue(buf, msg));
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, SkipAtIovecBoundary) {
|
||||
TestOverflowBuffer buf;
|
||||
auto sent = make_message(300, 50);
|
||||
auto unsent = make_message(400, 90);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
// The skip covers the first iovec exactly, so only the second is copied
|
||||
struct iovec iov[2] = {{sent.data(), sent.size()}, {unsent.data(), unsent.size()}};
|
||||
ASSERT_TRUE(
|
||||
buf.enqueue_iov(iov, 2, static_cast<uint16_t>(sent.size() + unsent.size()), static_cast<uint16_t>(sent.size())));
|
||||
EXPECT_EQ(buf.live(), unsent.size() + TestOverflowBuffer::LEN_PREFIX);
|
||||
expect_after_filler(this->drain_all_(buf), filler, unsent);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, AppendsBehindSentPrefixWhenItFits) {
|
||||
TestOverflowBuffer buf;
|
||||
size_t filler = this->fill_pipe_();
|
||||
auto first = make_message(200, 20);
|
||||
// Size the second message so the two land half way into a 256 byte step,
|
||||
// leaving exactly 128 bytes of slack whatever the pipe accepted
|
||||
const size_t base = std::max<size_t>(filler + 1, std::min<size_t>(filler * 3, 12000));
|
||||
const size_t second_len = (base / 256 + 1) * 256 + 128 - first.size() - 2 * TestOverflowBuffer::LEN_PREFIX;
|
||||
auto second = make_message(second_len, 60);
|
||||
ASSERT_GT(second.size(), filler);
|
||||
ASSERT_TRUE(enqueue(buf, first));
|
||||
ASSERT_TRUE(enqueue(buf, second));
|
||||
const auto storage = buf.storage();
|
||||
const size_t slack = storage.capacity - first.size() - second.size() - 2 * TestOverflowBuffer::LEN_PREFIX;
|
||||
ASSERT_EQ(slack, 128u);
|
||||
auto third = make_message(slack - TestOverflowBuffer::LEN_PREFIX, 200);
|
||||
|
||||
std::vector<uint8_t> received;
|
||||
this->read_into_(received);
|
||||
ASSERT_GT(this->drain_(buf), 0);
|
||||
ASSERT_EQ(buf.count(), 1);
|
||||
const size_t live = buf.live();
|
||||
|
||||
// Fits in the tail, so the sent prefix is left alone
|
||||
ASSERT_TRUE(enqueue(buf, third));
|
||||
EXPECT_EQ(buf.storage(), storage);
|
||||
EXPECT_EQ(buf.live(), live + third.size() + TestOverflowBuffer::LEN_PREFIX);
|
||||
|
||||
append(received, this->drain_all_(buf));
|
||||
expect_after_filler(received, filler, concat({first, second, third}));
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, ReleaseSurvivesFurtherEnqueues) {
|
||||
TestOverflowBuffer buf;
|
||||
auto first = make_message(300, 7);
|
||||
auto second = make_message(300, 70);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, first));
|
||||
buf.release();
|
||||
ASSERT_TRUE(enqueue(buf, second));
|
||||
EXPECT_GT(buf.capacity(), 0u);
|
||||
|
||||
expect_after_filler(this->drain_all_(buf), filler, concat({first, second}));
|
||||
EXPECT_EQ(buf.capacity(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, RefusesWhenByteLimitIsExceeded) {
|
||||
TestOverflowBuffer buf;
|
||||
// Two of these fill the byte budget exactly, well before the slot count is reached
|
||||
static_assert(API_MAX_SEND_QUEUE >= 3);
|
||||
auto msg = make_message(TestOverflowBuffer::MAX_BYTES / 2 - TestOverflowBuffer::LEN_PREFIX, 1);
|
||||
|
||||
this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
EXPECT_FALSE(enqueue(buf, msg));
|
||||
EXPECT_EQ(buf.count(), 2);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, LoneMessageMayExceedByteLimit) {
|
||||
TestOverflowBuffer buf;
|
||||
// The oversized message must still fit under the lone message ceiling
|
||||
static_assert(TestOverflowBuffer::MAX_BYTES + 100 + TestOverflowBuffer::LEN_PREFIX <=
|
||||
TestOverflowBuffer::MAX_LONE_BYTES);
|
||||
auto big = make_message(TestOverflowBuffer::MAX_BYTES + 100, 5);
|
||||
auto small = make_message(16, 9);
|
||||
|
||||
// Refusing the only message would drop the connection for nothing
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, big));
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
// With a backlog present the byte limit applies again
|
||||
EXPECT_FALSE(enqueue(buf, small));
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
|
||||
expect_after_filler(this->drain_all_(buf), filler, big);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, LoneMessageAboveOffsetLimitIsRefused) {
|
||||
TestOverflowBuffer buf;
|
||||
// Payload plus prefix is past the lone message ceiling
|
||||
auto msg = make_message(TestOverflowBuffer::MAX_LONE_BYTES, 3);
|
||||
|
||||
this->fill_pipe_();
|
||||
EXPECT_FALSE(enqueue(buf, msg));
|
||||
EXPECT_TRUE(buf.empty());
|
||||
EXPECT_EQ(buf.capacity(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, HardSocketErrorLeavesBacklogIntact) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(300, 40);
|
||||
|
||||
this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
// A closed socket fails every write outright, unlike a full one
|
||||
ASSERT_EQ(this->sock_->close(), 0);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(buf.try_drain(this->sock_.get()), -1);
|
||||
EXPECT_NE(errno, EWOULDBLOCK);
|
||||
EXPECT_NE(errno, EAGAIN);
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
EXPECT_EQ(buf.live(), msg.size() + TestOverflowBuffer::LEN_PREFIX);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, GrowsWhileReclaimingSentPrefix) {
|
||||
TestOverflowBuffer buf;
|
||||
Stall s;
|
||||
ASSERT_NO_FATAL_FAILURE(this->stall_mid_message_(buf, s));
|
||||
|
||||
// One byte too many to fit even after the sent prefix is reclaimed: grows in one copy
|
||||
auto third = make_message(s.before.capacity - buf.live() + 1, 200);
|
||||
ASSERT_TRUE(enqueue(buf, third));
|
||||
EXPECT_GT(buf.capacity(), s.before.capacity);
|
||||
EXPECT_EQ(buf.count(), 2);
|
||||
|
||||
append(s.received, this->drain_all_(buf));
|
||||
expect_after_filler(s.received, s.filler, concat({s.first, s.second, third}));
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, NestedDrainMakesNoProgress) {
|
||||
TestOverflowBuffer buf;
|
||||
auto msg = make_message(300, 40);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, msg));
|
||||
std::vector<uint8_t> received;
|
||||
this->read_into_(received);
|
||||
|
||||
// Room is available, but a nested drain must leave the outer one's message alone
|
||||
buf.set_draining(true);
|
||||
EXPECT_EQ(this->drain_(buf), 0);
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
std::vector<uint8_t> nothing;
|
||||
this->read_into_(nothing);
|
||||
EXPECT_TRUE(nothing.empty());
|
||||
|
||||
buf.set_draining(false);
|
||||
append(received, this->drain_all_(buf));
|
||||
expect_after_filler(received, filler, msg);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, NestedEnqueueAppendsWithinCapacity) {
|
||||
TestOverflowBuffer buf;
|
||||
auto first = make_message(500, 10);
|
||||
auto second = make_message(4, 90);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, first));
|
||||
const auto storage = buf.storage();
|
||||
ASSERT_GE(storage.capacity, first.size() + second.size() + 2 * TestOverflowBuffer::LEN_PREFIX);
|
||||
|
||||
buf.set_draining(true);
|
||||
EXPECT_TRUE(enqueue(buf, second));
|
||||
EXPECT_EQ(buf.count(), 2);
|
||||
EXPECT_EQ(buf.storage(), storage);
|
||||
buf.set_draining(false);
|
||||
|
||||
expect_after_filler(this->drain_all_(buf), filler, concat({first, second}));
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, NestedEnqueueRefusesToGrow) {
|
||||
TestOverflowBuffer buf;
|
||||
auto first = make_message(500, 10);
|
||||
auto second = make_message(100, 90);
|
||||
|
||||
size_t filler = this->fill_pipe_();
|
||||
ASSERT_TRUE(enqueue(buf, first));
|
||||
const auto storage = buf.storage();
|
||||
ASSERT_LT(storage.capacity, first.size() + second.size() + 2 * TestOverflowBuffer::LEN_PREFIX);
|
||||
|
||||
// Growing would free the bytes the outer write() is sending from
|
||||
buf.set_draining(true);
|
||||
EXPECT_FALSE(enqueue(buf, second));
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
EXPECT_EQ(buf.storage(), storage);
|
||||
buf.set_draining(false);
|
||||
|
||||
expect_after_filler(this->drain_all_(buf), filler, first);
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, NestedEnqueueRefusesToCompact) {
|
||||
TestOverflowBuffer buf;
|
||||
Stall s;
|
||||
ASSERT_NO_FATAL_FAILURE(this->stall_mid_message_(buf, s));
|
||||
auto third = make_message(1000, 200);
|
||||
|
||||
// Sliding the remainder down would move the bytes the outer write() points at
|
||||
buf.set_draining(true);
|
||||
EXPECT_FALSE(enqueue(buf, third));
|
||||
EXPECT_EQ(buf.count(), 1);
|
||||
EXPECT_EQ(buf.storage(), s.before);
|
||||
buf.set_draining(false);
|
||||
|
||||
// Once the drain is over the same enqueue compacts and succeeds
|
||||
ASSERT_TRUE(enqueue(buf, third));
|
||||
EXPECT_EQ(buf.storage(), s.before);
|
||||
append(s.received, this->drain_all_(buf));
|
||||
expect_after_filler(s.received, s.filler, concat({s.first, s.second, third}));
|
||||
}
|
||||
|
||||
TEST_F(OverflowBufferTest, CompactsInsteadOfGrowingAfterPartialDrain) {
|
||||
TestOverflowBuffer buf;
|
||||
Stall s;
|
||||
ASSERT_NO_FATAL_FAILURE(this->stall_mid_message_(buf, s));
|
||||
auto third = make_message(1000, 200);
|
||||
|
||||
// The sent first message is reclaimed by sliding the remainder down, not by reallocating
|
||||
ASSERT_TRUE(enqueue(buf, third));
|
||||
EXPECT_EQ(buf.storage(), s.before);
|
||||
|
||||
append(s.received, this->drain_all_(buf));
|
||||
expect_after_filler(s.received, s.filler, concat({s.first, s.second, third}));
|
||||
}
|
||||
|
||||
} // namespace esphome::api::testing
|
||||
#endif // USE_HOST
|
||||
@@ -0,0 +1,5 @@
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
manifest.dependencies = manifest.dependencies + ["sensor", "spi"]
|
||||
@@ -0,0 +1,62 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/atm90e32/atm90e32.h"
|
||||
|
||||
namespace esphome::atm90e32::testing {
|
||||
|
||||
TEST(ATM90E32OffsetRegisterVerification, AcceptsExactSignedReadback) {
|
||||
EXPECT_TRUE(offset_register_value_matches(0x007B, 123));
|
||||
EXPECT_TRUE(offset_register_value_matches(0xFF85, -123));
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetRegisterVerification, RejectsMismatchedReadback) {
|
||||
EXPECT_FALSE(offset_register_value_matches(0x007C, 123));
|
||||
EXPECT_FALSE(offset_register_value_matches(0xFF84, -123));
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetRestoreState, ReportsVerifiedStoredValuesAsRestored) {
|
||||
const auto state = resolve_offset_restore_state(true, true, false);
|
||||
|
||||
EXPECT_TRUE(state.restored);
|
||||
EXPECT_TRUE(state.values_verified);
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetRestoreState, ReportsVerifiedConfigFallbackAsNotRestored) {
|
||||
const auto state = resolve_offset_restore_state(true, false, true);
|
||||
|
||||
EXPECT_FALSE(state.restored);
|
||||
EXPECT_TRUE(state.values_verified);
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetRestoreState, ReportsFailedConfigFallbackAsUnverified) {
|
||||
const auto state = resolve_offset_restore_state(true, false, false);
|
||||
|
||||
EXPECT_FALSE(state.restored);
|
||||
EXPECT_FALSE(state.values_verified);
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetRestoreState, ReportsConfigWithoutStoredValuesAsNotRestored) {
|
||||
const auto state = resolve_offset_restore_state(false, true, false);
|
||||
|
||||
EXPECT_FALSE(state.restored);
|
||||
EXPECT_TRUE(state.values_verified);
|
||||
}
|
||||
|
||||
TEST(ATM90E32OffsetPersistence, RollsBackStoredValuesOrZeroSentinel) {
|
||||
const OffsetCalibration previous[3]{{1, -1}, {2, -2}, {3, -3}};
|
||||
OffsetCalibration rollback[3]{};
|
||||
|
||||
prepare_offset_rollback(previous, true, rollback);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
EXPECT_EQ(rollback[phase].first_offset, previous[phase].first_offset);
|
||||
EXPECT_EQ(rollback[phase].second_offset, previous[phase].second_offset);
|
||||
}
|
||||
|
||||
prepare_offset_rollback(previous, false, rollback);
|
||||
for (const auto &phase : rollback) {
|
||||
EXPECT_EQ(phase.first_offset, 0);
|
||||
EXPECT_EQ(phase.second_offset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::atm90e32::testing
|
||||
@@ -6,15 +6,14 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# close_service_batch compiles only under USE_BLUETOOTH_PROXY_CONNECTIONS;
|
||||
# emit the backend define so the host build exercises it.
|
||||
async def to_code_testing(config):
|
||||
# These defines are global to the merged host test binary; safe
|
||||
# because no co-compiled test observes them.
|
||||
# These defines are global to the merged host test binary. The api sources are
|
||||
# compiled in it too (the api tests define USE_API), and USE_BLUETOOTH_PROXY would make
|
||||
# them include and call bluetooth_proxy, which has no host build without a BLE hub.
|
||||
cg.add_define("USE_BLE_GATT_CLIENT")
|
||||
cg.add_define("USE_BLE_GATT_CLIENT_STUB_BACKEND")
|
||||
cg.add_define("USE_BLUETOOTH_PROXY")
|
||||
# Gates the connection half of the API surface, which is what
|
||||
# close_service_batch and the GATT response types live behind.
|
||||
cg.add_define("USE_BLUETOOTH_PROXY_CONNECTIONS")
|
||||
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", 1)
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Compile the gated filter path; no external component is in-tree to call
|
||||
# enable_advertisement_filter(), so the define is forced here.
|
||||
<<: !include common.yaml
|
||||
|
||||
esphome:
|
||||
build_flags:
|
||||
- "-DUSE_BLUETOOTH_PROXY_ADVERTISEMENT_FILTER"
|
||||
|
||||
esp32_ble_tracker:
|
||||
|
||||
bluetooth_proxy:
|
||||
active: true
|
||||
@@ -0,0 +1,18 @@
|
||||
tinyusb:
|
||||
id: tinyusb_test
|
||||
usb_lang_id: 0x0123
|
||||
usb_manufacturer_str: ESPHomeTestManufacturer
|
||||
usb_product_id: 0x1234
|
||||
usb_product_str: ESPHomeTestProduct
|
||||
usb_serial_str: ESPHomeTestSerialNumber
|
||||
usb_vendor_id: 0x2345
|
||||
|
||||
uart:
|
||||
- id: uart_0
|
||||
tx_pin: 14
|
||||
rx_pin: 13
|
||||
baud_rate: 115200
|
||||
|
||||
usb_cdc_acm:
|
||||
interfaces:
|
||||
- id: cdc_acm_1
|
||||
@@ -0,0 +1,12 @@
|
||||
# Second UART/CDC pair for a two-bridge setup. Kept out of common.yaml because the
|
||||
# ESP32-S2 has only two UART controllers and the logger occupies one, so a second
|
||||
# uart there would fail at runtime.
|
||||
uart:
|
||||
- id: uart_1
|
||||
tx_pin: 15
|
||||
rx_pin: 16
|
||||
baud_rate: 115200
|
||||
|
||||
usb_cdc_acm:
|
||||
interfaces:
|
||||
- id: cdc_acm_2
|
||||
@@ -0,0 +1,15 @@
|
||||
packages:
|
||||
cdc_acm_uart: !include common.yaml
|
||||
cdc_acm_uart_dual: !include common_dual.yaml
|
||||
|
||||
bridge:
|
||||
- platform: cdc_acm_uart
|
||||
uart_id: uart_0
|
||||
usb_cdc_acm_id: cdc_acm_1
|
||||
dtr_pin: 40
|
||||
rts_pin: 41
|
||||
- platform: cdc_acm_uart
|
||||
uart_id: uart_1
|
||||
usb_cdc_acm_id: cdc_acm_2
|
||||
dtr_pin: 20
|
||||
rts_pin: 21
|
||||
@@ -0,0 +1,14 @@
|
||||
# ESP32-S2 has no USB_SERIAL_JTAG, so the logger defaults to USB_CDC, which shares
|
||||
# the USB OTG peripheral with tinyusb. Use a hardware UART for logging instead.
|
||||
logger:
|
||||
hardware_uart: UART0
|
||||
|
||||
packages:
|
||||
cdc_acm_uart: !include common.yaml
|
||||
|
||||
bridge:
|
||||
- platform: cdc_acm_uart
|
||||
uart_id: uart_0
|
||||
usb_cdc_acm_id: cdc_acm_1
|
||||
dtr_pin: 40
|
||||
rts_pin: 41
|
||||
@@ -0,0 +1,17 @@
|
||||
packages:
|
||||
cdc_acm_uart: !include common.yaml
|
||||
cdc_acm_uart_dual: !include common_dual.yaml
|
||||
|
||||
bridge:
|
||||
- platform: cdc_acm_uart
|
||||
uart_id: uart_0
|
||||
usb_cdc_acm_id: cdc_acm_1
|
||||
dtr_pin: 40
|
||||
rts_pin: 41
|
||||
- platform: cdc_acm_uart
|
||||
uart_id: uart_1
|
||||
usb_cdc_acm_id: cdc_acm_2
|
||||
# GPIO19/20 are USB D-/D+ on the S3 (which the CDC side itself uses); use
|
||||
# unrelated free pins here.
|
||||
dtr_pin: 17
|
||||
rts_pin: 18
|
||||
@@ -30,8 +30,7 @@ climate:
|
||||
- switch.turn_on: climate_heater_switch
|
||||
- switch.turn_off: climate_cooler_switch
|
||||
# Thermostat-based climate so climate.control: action variants get build
|
||||
# coverage (bang_bang doesn't support fan modes, presets, etc.). Climate
|
||||
# has no template platform, so thermostat is the right vehicle.
|
||||
# coverage (bang_bang doesn't support fan modes, presets, etc.).
|
||||
- platform: thermostat
|
||||
id: climate_test_thermostat
|
||||
name: Test Thermostat
|
||||
|
||||
@@ -348,4 +348,75 @@ TEST(StepToAccuracyDecimals, NonFiniteAndZero) {
|
||||
EXPECT_EQ(step_to_accuracy_decimals(-INFINITY), 0);
|
||||
}
|
||||
|
||||
// --- FixedVector::try_init() ---
|
||||
|
||||
// Keeps the block observable, else the compiler may drop the malloc and free pair and fold the check
|
||||
static void escape(const void *p) { asm volatile("" : : "g"(p) : "memory"); }
|
||||
|
||||
TEST(FixedVectorTryInit, ReportsExhaustionAndStaysEmpty) {
|
||||
FixedVector<uint32_t> v;
|
||||
const bool ok = v.try_init(SIZE_MAX / sizeof(uint32_t));
|
||||
escape(&v);
|
||||
EXPECT_FALSE(ok);
|
||||
EXPECT_EQ(v.capacity(), 0u);
|
||||
EXPECT_FALSE(v.try_init(SIZE_MAX / sizeof(uint32_t) + 1)); // byte count would wrap
|
||||
EXPECT_EQ(v.capacity(), 0u);
|
||||
EXPECT_TRUE(v.try_init(0));
|
||||
EXPECT_TRUE(v.try_init(4));
|
||||
v.push_back(7);
|
||||
EXPECT_EQ(v.size(), 1u);
|
||||
}
|
||||
|
||||
// --- RAMAllocator::make_unique() ---
|
||||
|
||||
namespace {
|
||||
struct Probe {
|
||||
static inline int live = 0;
|
||||
int a;
|
||||
int b;
|
||||
Probe(int a, int b) : a(a), b(b) { live++; }
|
||||
~Probe() { live--; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static_assert(sizeof(RAMUniquePtr<Probe>) == sizeof(Probe *), "the deleter must not add storage");
|
||||
|
||||
TEST(RAMAllocatorMakeUnique, ForwardsArgsAndDestroysOnce) {
|
||||
auto p = RAMAllocator<Probe>().make_unique(3, 4);
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(p->a, 3);
|
||||
EXPECT_EQ(p->b, 4);
|
||||
EXPECT_EQ(Probe::live, 1);
|
||||
p.reset();
|
||||
EXPECT_EQ(Probe::live, 0);
|
||||
}
|
||||
|
||||
TEST(RAMAllocatorMakeUnique, ValueInitializesLikeMakeUnique) {
|
||||
struct Plain {
|
||||
uint32_t words[8];
|
||||
};
|
||||
// Dirty a block of the same size first so a recycled allocation is not zero by chance
|
||||
auto dirty = RAMAllocator<uint8_t>().make_unique_array_for_overwrite(sizeof(Plain));
|
||||
std::memset(dirty.get(), 0xFF, sizeof(Plain));
|
||||
dirty.reset();
|
||||
auto p = RAMAllocator<Plain>().make_unique();
|
||||
ASSERT_NE(p, nullptr);
|
||||
// Under ASan fresh blocks are filled with 0xbe, so this holds even when the dirtied block is not reused
|
||||
EXPECT_TRUE(std::all_of(std::begin(p->words), std::end(p->words), [](uint32_t w) { return w == 0; }));
|
||||
}
|
||||
|
||||
TEST(RAMAllocatorMakeUnique, ArrayFormRejectsOverflowAndZero) {
|
||||
EXPECT_EQ(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(SIZE_MAX / sizeof(uint32_t) + 1), nullptr);
|
||||
EXPECT_EQ(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(0), nullptr);
|
||||
EXPECT_NE(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(1), nullptr);
|
||||
}
|
||||
|
||||
TEST(RAMAllocatorMakeUnique, ArrayFormAllocatesElements) {
|
||||
RAMUniquePtr<uint8_t[]> buf = RAMAllocator<uint8_t>().make_unique_array_for_overwrite(256);
|
||||
ASSERT_NE(buf, nullptr);
|
||||
std::memset(buf.get(), 0xA5, 256);
|
||||
EXPECT_EQ(buf[0], 0xA5);
|
||||
EXPECT_EQ(buf[255], 0xA5);
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO14
|
||||
|
||||
packages:
|
||||
uart_38400: !include ../../test_build_components/common/uart_38400/esp32-idf.yaml
|
||||
uart_38400_even: !include ../../test_build_components/common/uart_38400_even/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO3
|
||||
|
||||
packages:
|
||||
uart_38400: !include ../../test_build_components/common/uart_38400/esp8266-ard.yaml
|
||||
uart_38400_even: !include ../../test_build_components/common/uart_38400_even/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart_38400: !include ../../test_build_components/common/uart_38400/rp2040-ard.yaml
|
||||
uart_38400_even: !include ../../test_build_components/common/uart_38400_even/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -17,6 +17,8 @@ esp32:
|
||||
disable_dev_null_vfs: true
|
||||
disable_mbedtls_peer_cert: true
|
||||
disable_mbedtls_pkcs7: true
|
||||
disable_mbedtls_tls_server: true
|
||||
disable_mbedtls_tls_extras: true
|
||||
disable_regi2c_in_iram: true
|
||||
disable_fatfs: true
|
||||
sram1_as_iram: true
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Exercises the ESP-NOW-over-hosted shim: on the ESP32-P4 host, esp32_hosted
|
||||
# supplies the esp_now_* symbols that the espnow component links against.
|
||||
packages:
|
||||
esp32_hosted: !include common.yaml
|
||||
espnow: !include ../espnow/common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
substitutions:
|
||||
verify_ssl: "true"
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_1200: !include ../../test_build_components/common/uart_1200/esp32-idf.yaml
|
||||
uart_1200_none_2stopbits: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
uart_rx_pin: GPIO3
|
||||
|
||||
packages:
|
||||
uart_1200: !include ../../test_build_components/common/uart_1200/esp8266-ard.yaml
|
||||
uart_1200_none_2stopbits: !include ../../test_build_components/common/uart_1200_none_2stopbits/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -30,6 +30,18 @@ binary_sensor:
|
||||
widget: button_button
|
||||
state: pressed
|
||||
|
||||
globals:
|
||||
- id: counter
|
||||
type: int
|
||||
|
||||
script:
|
||||
- id: add_row
|
||||
then:
|
||||
- lvgl.list.add:
|
||||
id: test_list_id
|
||||
label:
|
||||
text: row
|
||||
|
||||
lvgl:
|
||||
id: lvgl_id
|
||||
rotation: 90
|
||||
@@ -1291,7 +1303,7 @@ lvgl:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "table selected row %u col %u"
|
||||
args: [row, column]
|
||||
args: [(unsigned)row, (unsigned)column]
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.table.cell.update:
|
||||
@@ -1347,10 +1359,12 @@ lvgl:
|
||||
- logger.log:
|
||||
format: "list entry added at %d"
|
||||
args: [list_index]
|
||||
- lambda: "id(counter)++;"
|
||||
on_remove:
|
||||
- logger.log:
|
||||
format: "list entry removed at %d"
|
||||
args: [list_index]
|
||||
- lambda: "id(counter)--;"
|
||||
on_click:
|
||||
- lvgl.list.add_text:
|
||||
id: test_list_id
|
||||
|
||||
@@ -68,6 +68,14 @@ class Initiator {
|
||||
|
||||
static const uint8_t PROLOGUE[] = {'t', 'e', 's', 't', 'p', 'r', 'o', 'l', 'o', 'g', 'u', 'e'};
|
||||
|
||||
// The context only points at the key and init() copies it before returning,
|
||||
// so a temporary context over a temporary key is safe within one call
|
||||
static NoiseContext ctx_for(const psk_t &psk) {
|
||||
NoiseContext ctx;
|
||||
ctx.set_psk(psk.data());
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static psk_t make_psk(uint8_t seed) {
|
||||
psk_t psk;
|
||||
for (size_t i = 0; i < psk.size(); i++) {
|
||||
@@ -102,7 +110,7 @@ TEST(NoiseResponderHandshakeTest, MessageMethodsErrorBeforeInit) {
|
||||
TEST(NoiseResponderHandshakeTest, FullHandshakeAndTransportRoundTrip) {
|
||||
const psk_t psk = make_psk(7);
|
||||
NoiseResponderHandshake responder;
|
||||
ASSERT_EQ(responder.init(psk, PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
EXPECT_EQ(responder.action(), Action::ACTION_READ);
|
||||
|
||||
Initiator initiator(psk, PROLOGUE, sizeof(PROLOGUE));
|
||||
@@ -155,8 +163,8 @@ TEST(NoiseResponderHandshakeTest, ReInitRestartsHandshake) {
|
||||
// proves the restart took effect; the old state surviving would fail the
|
||||
// MAC here.
|
||||
NoiseResponderHandshake responder;
|
||||
ASSERT_EQ(responder.init(make_psk(7), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(make_psk(9), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(ctx_for(make_psk(7)), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(ctx_for(make_psk(9)), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
EXPECT_EQ(responder.action(), Action::ACTION_READ);
|
||||
|
||||
Initiator initiator(make_psk(9), PROLOGUE, sizeof(PROLOGUE));
|
||||
@@ -168,7 +176,7 @@ TEST(NoiseResponderHandshakeTest, ReInitRestartsHandshake) {
|
||||
|
||||
TEST(NoiseResponderHandshakeTest, WrongPskFailsWithMacFailure) {
|
||||
NoiseResponderHandshake responder;
|
||||
ASSERT_EQ(responder.init(make_psk(7), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(ctx_for(make_psk(7)), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
|
||||
Initiator initiator(make_psk(200), PROLOGUE, sizeof(PROLOGUE));
|
||||
uint8_t msg[MAX_HANDSHAKE_SIZE];
|
||||
@@ -185,7 +193,7 @@ TEST(NoiseResponderHandshakeTest, MismatchedPrologueFailsWithMacFailure) {
|
||||
// tampered preamble must fail even with the right key.
|
||||
const psk_t psk = make_psk(7);
|
||||
NoiseResponderHandshake responder;
|
||||
ASSERT_EQ(responder.init(psk, PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
|
||||
static const uint8_t TAMPERED[] = {'x'};
|
||||
Initiator initiator(psk, TAMPERED, sizeof(TAMPERED));
|
||||
|
||||
@@ -17,12 +17,17 @@ TEST(NoiseContextTest, AllZerosPskIsReserved) {
|
||||
EXPECT_FALSE(NoiseContext::is_all_zeros(psk));
|
||||
|
||||
NoiseContext ctx;
|
||||
psk_t loaded;
|
||||
EXPECT_FALSE(ctx.has_psk());
|
||||
ctx.set_psk(zeros);
|
||||
EXPECT_FALSE(ctx.has_psk());
|
||||
ctx.set_psk(psk);
|
||||
ctx.load_psk(loaded);
|
||||
EXPECT_EQ(loaded, zeros);
|
||||
ctx.set_psk(psk.data());
|
||||
EXPECT_TRUE(ctx.has_psk());
|
||||
EXPECT_EQ(ctx.get_psk(), psk);
|
||||
ctx.load_psk(loaded);
|
||||
EXPECT_EQ(loaded, psk);
|
||||
// Callers map the reserved key to nullptr; the context just stores what it is given
|
||||
ctx.set_psk(nullptr);
|
||||
EXPECT_FALSE(ctx.has_psk());
|
||||
}
|
||||
|
||||
TEST(WireFormatTest, FrameHeaderIsIndicatorPlusBigEndianLength) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
port: 3290
|
||||
password: "superlongpasswordthatnoonewillknow"
|
||||
@@ -0,0 +1,10 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
encryption:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
port: 3291
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
ota: !include api_key_offer.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
ota: !include api_key_offer.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
ota: !include api_runtime_key.yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
ota: !include api_runtime_key.yaml
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
uart_115200: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO2
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
uart_115200: !include ../../test_build_components/common/uart_115200/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
uart_115200: !include ../../test_build_components/common/uart_115200/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# A receiver with no dumpers and no listeners compiles both lists out.
|
||||
# Only built while remote_receiver is tested in isolation: the counts are global defines,
|
||||
# so this variant cannot be merged with configs that register any.
|
||||
remote_receiver:
|
||||
- id: rcvr_bare
|
||||
pin: ${pin}
|
||||
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
|
||||
packages:
|
||||
bare: !include bare-common.yaml
|
||||
@@ -2,7 +2,7 @@ remote_transmitter:
|
||||
id: xmitr
|
||||
pin: GPIO26
|
||||
carrier_duty_percent: 50%
|
||||
# non_blocking is bk7231n/bk7238-only; the CI board is a BK7252
|
||||
# non_blocking is bk7238-only; the CI board is a BK7252, so this builds the bit-bang path
|
||||
|
||||
packages:
|
||||
buttons: !include common-buttons.yaml
|
||||
|
||||
@@ -14,6 +14,15 @@ display:
|
||||
position:
|
||||
x: 100
|
||||
y: 100
|
||||
snapshot_key: SDLK_F12
|
||||
|
||||
- platform: sdl
|
||||
id: headless_display
|
||||
headless: true
|
||||
show_test_card: true
|
||||
dimensions:
|
||||
width: 320
|
||||
height: 240
|
||||
|
||||
- platform: sdl
|
||||
id: second_display
|
||||
@@ -46,3 +55,21 @@ binary_sensor:
|
||||
sdl_id: sdl_sdl_display
|
||||
id: key_enter
|
||||
key: SDLK_RETURN
|
||||
|
||||
esphome:
|
||||
# A name of your own is only good for one snapshot - a second one under the same name fails
|
||||
# rather than writing over the first - so these run once rather than on a repeating interval.
|
||||
on_boot:
|
||||
- delay: 2s
|
||||
- snapshot.take:
|
||||
id: headless_display
|
||||
filename: test_card.bmp
|
||||
- snapshot.take:
|
||||
id: headless_display
|
||||
filename: !lambda 'return "shot.bmp";'
|
||||
|
||||
interval:
|
||||
# A generated name has the time in it, so this one can repeat.
|
||||
- interval: 10s
|
||||
then:
|
||||
- snapshot.take: sdl_sdl_display
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Config-only test for the headless and screenshot options. The combinations that must be
|
||||
# rejected are covered by tests/component_tests/sdl/test_sdl.py; this file checks that the
|
||||
# accepted forms validate together.
|
||||
host:
|
||||
mac_address: "62:23:45:AF:B3:DD"
|
||||
|
||||
display:
|
||||
- platform: sdl
|
||||
id: headless_display
|
||||
headless: true
|
||||
dimensions: 320x240
|
||||
|
||||
- platform: sdl
|
||||
id: windowed_display
|
||||
dimensions: 320x240
|
||||
snapshot_key: SDLK_F12
|
||||
|
||||
binary_sensor:
|
||||
- platform: sdl
|
||||
sdl_id: windowed_display
|
||||
id: key_up
|
||||
key: SDLK_UP
|
||||
|
||||
interval:
|
||||
- interval: 10s
|
||||
then:
|
||||
- snapshot.take:
|
||||
id: headless_display
|
||||
filename: periodic.bmp
|
||||
@@ -4,3 +4,6 @@ psram:
|
||||
sendspin:
|
||||
id: sendspin_hub_id
|
||||
task_stack_in_psram: true
|
||||
manufacturer: Test Manufacturer
|
||||
model: Test Model
|
||||
firmware_version: 1.2.3
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
# Compile the tap code paths; no tap is attached, so this exercises the
|
||||
# null-tap branches that a normal build never defines.
|
||||
esphome:
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DUSE_SERIAL_PROXY_TAP"
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
serial_proxy: !include common.yaml
|
||||
@@ -0,0 +1,34 @@
|
||||
display:
|
||||
- platform: snapshot
|
||||
id: snapshot_display
|
||||
update_interval: 1s
|
||||
show_test_card: true
|
||||
# An odd width exercises the row padding in the BMP writer
|
||||
dimensions:
|
||||
width: 101
|
||||
height: 64
|
||||
|
||||
- platform: snapshot
|
||||
id: snapshot_rotated
|
||||
rotation: 90
|
||||
dimensions: 320x240
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, 40, 20, Color(0xFF, 0x80, 0x00));
|
||||
|
||||
esphome:
|
||||
# A name of your own is only good for one snapshot - a second one under the same name fails
|
||||
# rather than writing over the first - so these run once rather than on a repeating interval.
|
||||
on_boot:
|
||||
- delay: 2s
|
||||
- snapshot.take:
|
||||
id: snapshot_display
|
||||
filename: test_card.bmp
|
||||
- snapshot.take:
|
||||
id: snapshot_rotated
|
||||
filename: !lambda 'return "rotated.bmp";'
|
||||
|
||||
interval:
|
||||
# A generated name has the time in it, so this one can repeat.
|
||||
- interval: 10s
|
||||
then:
|
||||
- snapshot.take: snapshot_display
|
||||
@@ -0,0 +1,5 @@
|
||||
host:
|
||||
mac_address: "62:23:45:AF:B3:DE"
|
||||
|
||||
packages:
|
||||
snapshot: !include common.yaml
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
uart_1200_even_7bits: !include ../../test_build_components/common/uart_1200_even_7bits/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO2
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
uart_1200_even_7bits: !include ../../test_build_components/common/uart_1200_even_7bits/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
uart_1200_even_7bits: !include ../../test_build_components/common/uart_1200_even_7bits/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
packages:
|
||||
uart_9600_even_7bits: !include ../../test_build_components/common/uart_9600_even_7bits/esp32-idf.yaml
|
||||
|
||||
teleinfo:
|
||||
id: test_teleinfo_standard
|
||||
historical_mode: false
|
||||
update_interval: 60s
|
||||
|
||||
sensor:
|
||||
- platform: teleinfo
|
||||
name: sinsts
|
||||
tag_name: SINSTS
|
||||
teleinfo_id: test_teleinfo_standard
|
||||
unit_of_measurement: VA
|
||||
@@ -25,6 +25,27 @@ esphome:
|
||||
away: !lambda "return true;"
|
||||
is_on: !lambda "return false;"
|
||||
|
||||
- climate.template.publish:
|
||||
id: template_climate
|
||||
current_temperature: 21.0
|
||||
mode: HEAT
|
||||
fan_mode: AUTO
|
||||
swing_mode: "OFF"
|
||||
preset: NONE
|
||||
target_temperature: 22.0
|
||||
|
||||
# Templated
|
||||
- climate.template.publish:
|
||||
id: template_climate
|
||||
current_temperature: !lambda "return 21.5f;"
|
||||
mode: !lambda "return climate::CLIMATE_MODE_COOL;"
|
||||
target_temperature: !lambda "return 23.0f;"
|
||||
|
||||
- climate.template.publish:
|
||||
id: template_climate_custom_modes
|
||||
custom_fan_mode: "turbo"
|
||||
custom_preset: "eco_plus"
|
||||
|
||||
# Test C++ API: set_template() with stateless lambda (no captures)
|
||||
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
||||
- lambda: |-
|
||||
@@ -513,6 +534,98 @@ alarm_control_panel:
|
||||
codes:
|
||||
- "1234"
|
||||
|
||||
climate:
|
||||
- platform: template
|
||||
id: template_climate
|
||||
name: "Template Climate"
|
||||
optimistic: true
|
||||
sensor: template_template_sens
|
||||
supports_action: true
|
||||
supports_current_humidity: true
|
||||
restore_mode: NO_RESTORE
|
||||
initial_state:
|
||||
mode: HEAT
|
||||
target_temperature: 21.0
|
||||
fan_mode: LOW
|
||||
supported_modes:
|
||||
- "OFF"
|
||||
- HEAT
|
||||
- COOL
|
||||
supported_fan_modes:
|
||||
- AUTO
|
||||
- LOW
|
||||
- HIGH
|
||||
supported_swing_modes:
|
||||
- "OFF"
|
||||
- VERTICAL
|
||||
supported_presets:
|
||||
- NONE
|
||||
- ECO
|
||||
visual:
|
||||
min_temperature: 16.0
|
||||
max_temperature: 30.0
|
||||
temperature_step: 0.5
|
||||
set_mode_action:
|
||||
- logger.log:
|
||||
format: "set_mode_action %d"
|
||||
args: ["(int) x"]
|
||||
set_target_temperature_action:
|
||||
- logger.log:
|
||||
format: "set_target_temperature_action %.1f"
|
||||
args: ["x"]
|
||||
set_target_humidity_action:
|
||||
- logger.log:
|
||||
format: "set_target_humidity_action %.1f"
|
||||
args: ["x"]
|
||||
set_fan_mode_action:
|
||||
- logger.log:
|
||||
format: "set_fan_mode_action %d"
|
||||
args: ["(int) x"]
|
||||
set_swing_mode_action:
|
||||
- logger.log:
|
||||
format: "set_swing_mode_action %d"
|
||||
args: ["(int) x"]
|
||||
set_preset_action:
|
||||
- logger.log:
|
||||
format: "set_preset_action %d"
|
||||
args: ["(int) x"]
|
||||
on_control:
|
||||
- logger.log: "on_control fired"
|
||||
on_state:
|
||||
- logger.log: "on_state fired"
|
||||
|
||||
- platform: template
|
||||
id: template_climate_custom_modes
|
||||
name: "Template Climate Custom Modes"
|
||||
optimistic: true
|
||||
sensor: template_template_sens
|
||||
supported_modes:
|
||||
- "OFF"
|
||||
- HEAT
|
||||
custom_fan_modes:
|
||||
- turbo
|
||||
- silent
|
||||
- eco
|
||||
custom_presets:
|
||||
- eco_plus
|
||||
- power_save
|
||||
- max
|
||||
set_custom_fan_mode_action:
|
||||
- logger.log:
|
||||
format: "set_custom_fan_mode_action %s"
|
||||
args: ["x.c_str()"]
|
||||
set_custom_preset_action:
|
||||
- logger.log:
|
||||
format: "set_custom_preset_action %s"
|
||||
args: ["x.c_str()"]
|
||||
initial_state:
|
||||
custom_fan_mode: eco
|
||||
custom_preset: max
|
||||
visual:
|
||||
min_temperature: 16.0
|
||||
max_temperature: 30.0
|
||||
temperature_step: 0.5
|
||||
|
||||
water_heater:
|
||||
- platform: template
|
||||
id: template_water_heater
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Tuya without any network component (no wifi/ethernet/api), as used on
|
||||
# serial-only or BLE-only Tuya MCU boards. Regression test for
|
||||
# https://github.com/esphome/esphome/issues/18942
|
||||
substitutions:
|
||||
status_pin: P6
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/bk72xx-ard.yaml
|
||||
|
||||
tuya:
|
||||
status_pin: ${status_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: tuya
|
||||
id: tuya_presence
|
||||
sensor_datapoint: 101
|
||||
|
||||
sensor:
|
||||
- platform: tuya
|
||||
id: tuya_light_intensity
|
||||
sensor_datapoint: 103
|
||||
|
||||
number:
|
||||
- platform: tuya
|
||||
id: tuya_far_detection
|
||||
number_datapoint: 109
|
||||
min_value: 0
|
||||
max_value: 600
|
||||
step: 1
|
||||
@@ -14,6 +14,7 @@ esphome:
|
||||
condition: wifi.ap_active
|
||||
then:
|
||||
- logger.log: "WiFi AP is active!"
|
||||
- wifi.roam
|
||||
|
||||
wifi:
|
||||
networks:
|
||||
|
||||
Reference in New Issue
Block a user