Add FNV-1a hash functions (#12502)

This commit is contained in:
David Woodhouse
2025-12-15 20:23:54 +00:00
committed by GitHub
parent 24d7e9dd23
commit 839139df36
4 changed files with 158 additions and 2 deletions

View File

@@ -0,0 +1,60 @@
esphome:
name: fnv1a-hash-test
platformio_options:
build_flags:
- "-DDEBUG"
on_boot:
- lambda: |-
using esphome::fnv1a_hash;
using esphome::fnv1a_hash_extend;
// Test empty string (should return offset basis)
uint32_t hash_empty = fnv1a_hash("");
if (hash_empty == 0x811c9dc5) {
ESP_LOGI("FNV1A", "empty PASSED");
} else {
ESP_LOGE("FNV1A", "empty FAILED: 0x%08x != 0x811c9dc5", hash_empty);
}
// Test known FNV-1a hashes
uint32_t hash_hello = fnv1a_hash("hello");
if (hash_hello == 0x4f9f2cab) {
ESP_LOGI("FNV1A", "known_hello PASSED");
} else {
ESP_LOGE("FNV1A", "known_hello FAILED: 0x%08x != 0x4f9f2cab", hash_hello);
}
uint32_t hash_helloworld = fnv1a_hash("helloworld");
if (hash_helloworld == 0x3b9f5c61) {
ESP_LOGI("FNV1A", "known_helloworld PASSED");
} else {
ESP_LOGE("FNV1A", "known_helloworld FAILED: 0x%08x != 0x3b9f5c61", hash_helloworld);
}
// Test fnv1a_hash_extend consistency
uint32_t hash1 = fnv1a_hash("hello");
hash1 = fnv1a_hash_extend(hash1, "world");
uint32_t hash2 = fnv1a_hash("helloworld");
if (hash1 == hash2) {
ESP_LOGI("FNV1A", "extend PASSED");
} else {
ESP_LOGE("FNV1A", "extend FAILED: 0x%08x != 0x%08x", hash1, hash2);
}
// Test with std::string
std::string str1 = "foo";
std::string str2 = "bar";
uint32_t hash3 = fnv1a_hash(str1);
hash3 = fnv1a_hash_extend(hash3, str2);
uint32_t hash4 = fnv1a_hash("foobar");
if (hash3 == hash4) {
ESP_LOGI("FNV1A", "string PASSED");
} else {
ESP_LOGE("FNV1A", "string FAILED: 0x%08x != 0x%08x", hash3, hash4);
}
host:
api:
logger:

View File

@@ -0,0 +1,69 @@
"""Integration test for FNV-1a hash functions."""
from __future__ import annotations
import asyncio
import re
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_fnv1a_hash(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test that FNV-1a hash functions work correctly."""
test_results = {}
all_tests_complete = asyncio.Event()
expected_tests = {"empty", "known_hello", "known_helloworld", "extend", "string"}
def on_log_line(line: str) -> None:
"""Capture log lines with test results."""
# Strip ANSI escape codes
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
# Look for our test result messages
# Format: "[timestamp][level][FNV1A:line]: test_name PASSED"
match = re.search(r"\[FNV1A:\d+\]:\s+(\w+)\s+(PASSED|FAILED)", clean_line)
if match:
test_name = match.group(1)
result = match.group(2)
test_results[test_name] = result
if set(test_results.keys()) >= expected_tests:
all_tests_complete.set()
async with (
run_compiled(yaml_config, line_callback=on_log_line),
api_client_connected() as client,
):
device_info = await client.device_info()
assert device_info is not None
assert device_info.name == "fnv1a-hash-test"
# Wait for all tests to complete or timeout
try:
await asyncio.wait_for(all_tests_complete.wait(), timeout=2.0)
except TimeoutError:
pytest.fail(f"Tests timed out. Got results for: {set(test_results.keys())}")
# Verify all tests passed
assert "empty" in test_results, "empty string test not found"
assert test_results["empty"] == "PASSED", "empty string test failed"
assert "known_hello" in test_results, "known_hello test not found"
assert test_results["known_hello"] == "PASSED", "known_hello test failed"
assert "known_helloworld" in test_results, "known_helloworld test not found"
assert test_results["known_helloworld"] == "PASSED", (
"known_helloworld test failed"
)
assert "extend" in test_results, "fnv1a_hash_extend test not found"
assert test_results["extend"] == "PASSED", "fnv1a_hash_extend test failed"
assert "string" in test_results, "std::string test not found"
assert test_results["string"] == "PASSED", "std::string test failed"