first commit
This commit is contained in:
commit
5893b00dd2
1669 changed files with 1982740 additions and 0 deletions
278
libraries/FastLED/tests/CMakeLists.txt
Normal file
278
libraries/FastLED/tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# Note that we are using the zig compiler as a drop-in replacement for
|
||||
# gcc. This allows the unit tests to be compiled across different platforms
|
||||
# without having to worry about the underlying compiler.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(FastLED_Tests)
|
||||
|
||||
# Enforce C++17 globally for all targets.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Force creation of thin archives (instead of full .a files) only for non apple builds
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> rcT <TARGET> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> rT <TARGET> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> <TARGET>")
|
||||
message(STATUS "Using thin archives for Clang build")
|
||||
endif()
|
||||
|
||||
# Enable parallel compilation
|
||||
include(ProcessorCount)
|
||||
ProcessorCount(CPU_COUNT)
|
||||
if(CPU_COUNT)
|
||||
set(CMAKE_BUILD_PARALLEL_LEVEL ${CPU_COUNT})
|
||||
endif()
|
||||
|
||||
# Check if mold linker is available
|
||||
find_program(MOLD_EXECUTABLE mold)
|
||||
|
||||
if(MOLD_EXECUTABLE)
|
||||
# Set mold as the default linker
|
||||
message(STATUS "Using mold linker: ${MOLD_EXECUTABLE}")
|
||||
|
||||
# Add mold linker flags to the common flags
|
||||
list(APPEND COMMON_COMPILE_FLAGS "-fuse-ld=mold")
|
||||
|
||||
# Set linker flags globally
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=mold")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=mold")
|
||||
else()
|
||||
find_program(LLDLINK_EXECUTABLE lld-link)
|
||||
if(LLDLINK_EXECUTABLE)
|
||||
message(STATUS "Using lld-link linker: ${LLDLINK_EXECUTABLE}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
|
||||
else()
|
||||
message(STATUS "Neither mold nor lld-link found. Using system default linker.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Set build type to Debug
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
|
||||
|
||||
# Output the current build type
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
# Define common compiler flags and definitions
|
||||
set(COMMON_COMPILE_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
#-Wpedantic
|
||||
-funwind-tables
|
||||
-g3
|
||||
-ggdb
|
||||
-fno-omit-frame-pointer
|
||||
-O0
|
||||
-fno-inline
|
||||
-Werror=return-type
|
||||
-Werror=missing-declarations
|
||||
-Werror=redundant-decls
|
||||
-Werror=init-self
|
||||
-Werror=missing-field-initializers
|
||||
-Werror=pointer-arith
|
||||
-Werror=write-strings
|
||||
-Werror=format=2
|
||||
-Werror=implicit-fallthrough
|
||||
-Werror=missing-include-dirs
|
||||
-Werror=date-time
|
||||
-Werror=unused-parameter
|
||||
-Werror=unused-variable
|
||||
-Werror=unused-value
|
||||
-Werror=cast-align
|
||||
-DFASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8
|
||||
-Wno-comment
|
||||
# ignore Arduino/PlatformIO-specific PROGMEM macro
|
||||
-DPROGMEM=
|
||||
)
|
||||
|
||||
# C++-specific compiler flags
|
||||
set(CXX_SPECIFIC_FLAGS
|
||||
-Werror=suggest-override
|
||||
-Werror=non-virtual-dtor
|
||||
-Werror=reorder
|
||||
-Werror=sign-compare
|
||||
-Werror=float-equal
|
||||
-Werror=mismatched-tags
|
||||
-Werror=switch-enum
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND CXX_SPECIFIC_FLAGS -Werror=self-assign -Werror=infinite-recursion -Werror=extra-tokens -Werror=unused-private-field -Wglobal-constructors -Werror=global-constructors)
|
||||
endif()
|
||||
|
||||
|
||||
set(UNIT_TEST_COMPILE_FLAGS
|
||||
-Wall
|
||||
#-Wextra
|
||||
#-Wpedantic
|
||||
-funwind-tables
|
||||
-g3
|
||||
-ggdb
|
||||
-fno-omit-frame-pointer
|
||||
-O0
|
||||
-Werror=return-type
|
||||
-Werror=missing-declarations
|
||||
#-Werror=redundant-decls
|
||||
-Werror=init-self
|
||||
#-Werror=missing-field-initializers
|
||||
#-Werror=pointer-arith
|
||||
#-Werror=write-strings
|
||||
#-Werror=format=2
|
||||
#-Werror=implicit-fallthrough
|
||||
#-Werror=missing-include-dirs
|
||||
-Werror=date-time
|
||||
#-Werror=unused-parameter
|
||||
#-Werror=unused-variable
|
||||
#-Werror=unused-value
|
||||
|
||||
# Not supported in gcc.
|
||||
#-Werror=infinite-recursion
|
||||
#-v
|
||||
-Wno-comment
|
||||
)
|
||||
|
||||
set(UNIT_TEST_CXX_FLAGS
|
||||
-Werror=suggest-override
|
||||
-Werror=non-virtual-dtor
|
||||
-Werror=switch-enum
|
||||
#-Werror=reorder
|
||||
#-Werror=sign-compare
|
||||
#-Werror=float-equal
|
||||
#-Werror=conversion
|
||||
)
|
||||
|
||||
set(COMMON_COMPILE_DEFINITIONS
|
||||
DEBUG
|
||||
FASTLED_FORCE_NAMESPACE=1
|
||||
FASTLED_NO_AUTO_NAMESPACE
|
||||
FASTLED_TESTING
|
||||
ENABLE_CRASH_HANDLER
|
||||
FASTLED_STUB_IMPL
|
||||
FASTLED_NO_PINMAP
|
||||
HAS_HARDWARE_PIN_SUPPORT
|
||||
_GLIBCXX_DEBUG
|
||||
_GLIBCXX_DEBUG_PEDANTIC
|
||||
)
|
||||
|
||||
|
||||
# Set output directories
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.build/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.build/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.build/bin)
|
||||
|
||||
# Set binary directory
|
||||
set(CMAKE_BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/.build/bin)
|
||||
|
||||
# Set path to FastLED source directory
|
||||
add_compile_definitions(${COMMON_COMPILE_DEFINITIONS})
|
||||
set(FASTLED_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
# Include FastLED source directory
|
||||
include_directories(${FASTLED_SOURCE_DIR}/src)
|
||||
|
||||
# Delegate source file computation to src/CMakeLists.txt
|
||||
add_subdirectory(${FASTLED_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/fastled)
|
||||
|
||||
if(NOT APPLE)
|
||||
target_link_options(fastled PRIVATE -static-libgcc -static-libstdc++)
|
||||
endif()
|
||||
|
||||
# Try to find libunwind, but make it optional
|
||||
find_package(LibUnwind QUIET)
|
||||
|
||||
# Define a variable to check if we should use libunwind
|
||||
set(USE_LIBUNWIND ${LibUnwind_FOUND})
|
||||
|
||||
if(USE_LIBUNWIND)
|
||||
message(STATUS "LibUnwind found. Using it for better stack traces.")
|
||||
else()
|
||||
message(STATUS "LibUnwind not found. Falling back to basic stack traces.")
|
||||
endif()
|
||||
|
||||
# Enable testing
|
||||
enable_testing()
|
||||
|
||||
# Create doctest main library
|
||||
add_library(doctest_main STATIC doctest_main.cpp)
|
||||
target_include_directories(doctest_main PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_options(doctest_main PRIVATE ${UNIT_TEST_COMPILE_FLAGS})
|
||||
target_compile_options(doctest_main PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${UNIT_TEST_CXX_FLAGS}>)
|
||||
target_compile_definitions(doctest_main PRIVATE ${COMMON_COMPILE_DEFINITIONS})
|
||||
|
||||
# Find all test source files
|
||||
file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp")
|
||||
|
||||
# Find test executables (only actual test executables, not libraries)
|
||||
file(GLOB TEST_BINARIES "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_*${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
|
||||
# Process source files
|
||||
foreach(TEST_SOURCE ${TEST_SOURCES})
|
||||
get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE)
|
||||
add_executable(${TEST_NAME} ${TEST_SOURCE})
|
||||
target_link_libraries(${TEST_NAME} fastled doctest_main)
|
||||
|
||||
# Set the correct subsystem for Windows
|
||||
if(WIN32)
|
||||
if(MSVC)
|
||||
set_target_properties(${TEST_NAME} PROPERTIES
|
||||
WIN32_EXECUTABLE FALSE
|
||||
LINK_FLAGS "/SUBSYSTEM:CONSOLE")
|
||||
else()
|
||||
# For MinGW/Clang on Windows
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set_target_properties(${TEST_NAME} PROPERTIES
|
||||
WIN32_EXECUTABLE FALSE
|
||||
LINK_FLAGS "-Xlinker /subsystem:console")
|
||||
else()
|
||||
set_target_properties(${TEST_NAME} PROPERTIES
|
||||
WIN32_EXECUTABLE FALSE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
# Add C++-specific flags only for C++ files
|
||||
target_compile_options(fastled PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${CXX_SPECIFIC_FLAGS}>)
|
||||
if(USE_LIBUNWIND)
|
||||
target_link_libraries(${TEST_NAME} ${LIBUNWIND_LIBRARIES})
|
||||
endif()
|
||||
target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set_target_properties(${TEST_NAME} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
# Add static linking flags and debug flags for test executables
|
||||
if(NOT APPLE AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
target_link_options(${TEST_NAME} PRIVATE -static-libgcc -static-libstdc++)
|
||||
endif()
|
||||
target_compile_options(${TEST_NAME} PRIVATE ${UNIT_TEST_COMPILE_FLAGS})
|
||||
# Add C++-specific flags only for C++ files
|
||||
target_compile_options(${TEST_NAME} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${UNIT_TEST_CXX_FLAGS}>)
|
||||
# Add C++-specific flags only for C++ files
|
||||
target_compile_options(${TEST_NAME} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${UNIT_TEST_CXX_FLAGS}>)
|
||||
target_compile_definitions(${TEST_NAME} PRIVATE
|
||||
${COMMON_COMPILE_DEFINITIONS}
|
||||
$<$<BOOL:${USE_LIBUNWIND}>:USE_LIBUNWIND>
|
||||
)
|
||||
|
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||
endforeach()
|
||||
|
||||
# Process remaining binaries (those without corresponding source files)
|
||||
option(CLEAN_ORPHANED_BINARIES "Remove orphaned test binaries" ON)
|
||||
if(CLEAN_ORPHANED_BINARIES)
|
||||
foreach(ORPHANED_BINARY ${TEST_BINARIES})
|
||||
get_filename_component(BINARY_NAME ${ORPHANED_BINARY} NAME_WE)
|
||||
get_filename_component(BINARY_DIR ${ORPHANED_BINARY} DIRECTORY)
|
||||
get_filename_component(PARENT_DIR ${BINARY_DIR} DIRECTORY)
|
||||
get_filename_component(GRANDPARENT_DIR ${PARENT_DIR} DIRECTORY)
|
||||
set(CORRESPONDING_SOURCE "${GRANDPARENT_DIR}/${BINARY_NAME}.cpp")
|
||||
if(NOT EXISTS "${CORRESPONDING_SOURCE}")
|
||||
message(STATUS "Found orphaned binary without source: ${ORPHANED_BINARY}")
|
||||
file(REMOVE "${ORPHANED_BINARY}")
|
||||
message(STATUS "Deleted orphaned binary: ${ORPHANED_BINARY}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add verbose output for tests
|
||||
set(CMAKE_CTEST_ARGUMENTS "--output-on-failure")
|
||||
49
libraries/FastLED/tests/crash_handler.h
Normal file
49
libraries/FastLED/tests/crash_handler.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef CRASH_HANDLER_H
|
||||
#define CRASH_HANDLER_H
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#include <libunwind.h>
|
||||
|
||||
void print_stacktrace() {
|
||||
unw_cursor_t cursor;
|
||||
unw_context_t context;
|
||||
|
||||
unw_getcontext(&context);
|
||||
unw_init_local(&cursor, &context);
|
||||
|
||||
int depth = 0;
|
||||
while (unw_step(&cursor) > 0) {
|
||||
unw_word_t offset, pc;
|
||||
char sym[256];
|
||||
|
||||
unw_get_reg(&cursor, UNW_REG_IP, &pc);
|
||||
if (pc == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
printf("#%-2d 0x%lx:", depth++, pc);
|
||||
|
||||
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
|
||||
printf(" (%s+0x%lx)\n", sym, offset);
|
||||
} else {
|
||||
printf(" -- symbol not found\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crash_handler(int sig) {
|
||||
fprintf(stderr, "Error: signal %d:\n", sig);
|
||||
print_stacktrace();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void setup_crash_handler() {
|
||||
signal(SIGSEGV, crash_handler);
|
||||
signal(SIGABRT, crash_handler);
|
||||
}
|
||||
|
||||
#endif // CRASH_HANDLER_H
|
||||
7106
libraries/FastLED/tests/doctest.h
Normal file
7106
libraries/FastLED/tests/doctest.h
Normal file
File diff suppressed because it is too large
Load diff
25
libraries/FastLED/tests/doctest_main.cpp
Normal file
25
libraries/FastLED/tests/doctest_main.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#define DOCTEST_CONFIG_IMPLEMENT
|
||||
#include "doctest.h"
|
||||
|
||||
// This file contains the main function for doctest
|
||||
// It will be compiled once and linked to all test executables
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows-specific entry point
|
||||
#include <windows.h>
|
||||
|
||||
// Define the main entry point for Windows
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) {
|
||||
return doctest::Context().run();
|
||||
}
|
||||
|
||||
// Also provide a standard main for compatibility
|
||||
int main(int argc, char** argv) {
|
||||
return doctest::Context(argc, argv).run();
|
||||
}
|
||||
#else
|
||||
// Standard entry point for non-Windows platforms
|
||||
int main(int argc, char** argv) {
|
||||
return doctest::Context(argc, argv).run();
|
||||
}
|
||||
#endif
|
||||
3
libraries/FastLED/tests/readme
Normal file
3
libraries/FastLED/tests/readme
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
To run tests use
|
||||
|
||||
`uv run ci/cpp_test_run.py`
|
||||
91
libraries/FastLED/tests/test.h
Normal file
91
libraries/FastLED/tests/test.h
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/lut.h"
|
||||
#include "fl/xypath.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/strstream.h"
|
||||
#include "fl/hash_set.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
namespace doctest {
|
||||
template<> struct StringMaker<CRGB> {
|
||||
static String convert(const CRGB& value) {
|
||||
fl::Str out = value.toString();
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct StringMaker<Str> {
|
||||
static String convert(const Str& value) {
|
||||
return value.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct StringMaker<vec2<T>> {
|
||||
static String convert(const vec2<T>& value) {
|
||||
fl::Str out;
|
||||
out += "vec2(";
|
||||
out += value.x;
|
||||
out += ", ";
|
||||
out += value.y;
|
||||
out += ")";
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct StringMaker<Tile2x2_u8> {
|
||||
static String convert(const Tile2x2_u8& value) {
|
||||
fl::StrStream out;
|
||||
out << "Tile2x2_u8(" << value.origin() << ")";
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct StringMaker<rect<T>> {
|
||||
static String convert(const rect<T>& value) {
|
||||
fl::Str out;
|
||||
out += "rect(";
|
||||
out += " (";
|
||||
out += value.mMin.x;
|
||||
out += ",";
|
||||
out += value.mMin.y;
|
||||
out += "), (";
|
||||
out += value.mMax.x;
|
||||
out += ",";
|
||||
out += value.mMax.y;
|
||||
out += "))";
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Key, typename Hash, typename KeyEqual> struct StringMaker<fl::hash_set<Key, Hash, KeyEqual>> {
|
||||
static String convert(const fl::hash_set<Key, Hash, KeyEqual>& value) {
|
||||
fl::Str out;
|
||||
out.append(value);
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct StringMaker<fl::vector<T>> {
|
||||
static String convert(const fl::vector<T>& value) {
|
||||
fl::Str out;
|
||||
out.append(value);
|
||||
return out.c_str();
|
||||
}
|
||||
};
|
||||
}
|
||||
27
libraries/FastLED/tests/test_algorithm.cpp
Normal file
27
libraries/FastLED/tests/test_algorithm.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/algorithm.h"
|
||||
#include "fl/dbg.h"
|
||||
#include "fl/vector.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("reverse an int list") {
|
||||
fl::vector<int> vec;
|
||||
vec.push_back(1);
|
||||
vec.push_back(2);
|
||||
vec.push_back(3);
|
||||
vec.push_back(4);
|
||||
vec.push_back(5);
|
||||
|
||||
fl::reverse(vec.begin(), vec.end());
|
||||
CHECK_EQ(vec[0], 5);
|
||||
CHECK_EQ(vec[1], 4);
|
||||
CHECK_EQ(vec[2], 3);
|
||||
CHECK_EQ(vec[3], 2);
|
||||
CHECK_EQ(vec[4], 1);
|
||||
}
|
||||
283
libraries/FastLED/tests/test_apa102_hd.cpp
Normal file
283
libraries/FastLED/tests/test_apa102_hd.cpp
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "FastLED.h"
|
||||
#include "fl/five_bit_hd_gamma.h"
|
||||
#include "assert.h"
|
||||
#include "math.h"
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("five_bit_bitshift") {
|
||||
const uint16_t test_data[][2][4] = {
|
||||
{ // test case
|
||||
//r g b brightness
|
||||
{0, 0, 0, 0}, // input
|
||||
{0, 0, 0, 0}, // output
|
||||
},
|
||||
{ // 0 brightness brings all colors down to 0
|
||||
{0xffff, 0xffff, 0xffff, 0},
|
||||
{0, 0, 0, 0},
|
||||
},
|
||||
{ // color values below 8 become 0 at max brightness
|
||||
{8, 7, 0, 255},
|
||||
{1, 0, 0, 1},
|
||||
},
|
||||
{
|
||||
{0xffff, 0x00f0, 0x000f, 0x01},
|
||||
{0x11, 0x00, 0x00, 0x01},
|
||||
},
|
||||
{
|
||||
{0x0100, 0x00f0, 0x000f, 0xff},
|
||||
{0x08, 0x08, 0x00, 0x03},
|
||||
},
|
||||
{
|
||||
{0x2000, 0x1000, 0x0f00, 0x20},
|
||||
{0x20, 0x10, 0x0f, 0x03},
|
||||
},
|
||||
{
|
||||
{0xffff, 0x8000, 0x4000, 0x40},
|
||||
{0x81, 0x41, 0x20, 0x0f},
|
||||
},
|
||||
{
|
||||
{0xffff, 0x8000, 0x4000, 0x80},
|
||||
{0x81, 0x41, 0x20, 0x1f},
|
||||
},
|
||||
{
|
||||
{0xffff, 0xffff, 0xffff, 0xff},
|
||||
{0xff, 0xff, 0xff, 0x1f},
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& data : test_data) {
|
||||
CRGB out_color;
|
||||
uint8_t out_brightness;
|
||||
five_bit_bitshift(data[0][0], data[0][1], data[0][2], data[0][3], &out_color, &out_brightness);
|
||||
INFO("input red ", data[0][0], " green ", data[0][1], " blue ", data[0][2], " brightness ", data[0][3]);
|
||||
INFO("output red ", out_color.r, " green ", out_color.g, " blue ", out_color.b, " brightness ", out_brightness);
|
||||
CHECK_EQ(out_color.r, data[1][0]);
|
||||
CHECK_EQ(out_color.g, data[1][1]);
|
||||
CHECK_EQ(out_color.b, data[1][2]);
|
||||
CHECK_EQ(out_brightness, data[1][3]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("__builtin_five_bit_hd_gamma_bitshift") {
|
||||
// NOTE: FASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8 is defined for this test in CMakeLists.txt
|
||||
|
||||
const uint8_t test_data[][2][4] = {
|
||||
{ // test case
|
||||
//r g b brightness
|
||||
{0, 0, 0, 0}, // input
|
||||
{0, 0, 0, 0}, // output
|
||||
},
|
||||
{ // 0 brightness brings all colors down to 0
|
||||
{255, 255, 255, 0},
|
||||
{0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
{16, 16, 16, 16},
|
||||
{0, 0, 0, 1},
|
||||
},
|
||||
{
|
||||
{64, 64, 64, 8},
|
||||
{4, 4, 4, 1},
|
||||
},
|
||||
{
|
||||
{255, 127, 43, 1},
|
||||
{17, 3, 0, 1},
|
||||
},
|
||||
{
|
||||
{255, 127, 43, 1},
|
||||
{17, 3, 0, 1},
|
||||
},
|
||||
{
|
||||
{255, 127, 43, 64},
|
||||
{129, 21, 1, 15},
|
||||
},
|
||||
{
|
||||
{255, 127, 43, 255},
|
||||
{255, 42, 3, 31},
|
||||
},
|
||||
{
|
||||
{255, 255, 255, 255},
|
||||
{255, 255, 255, 31},
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& data : test_data) {
|
||||
CRGB out_color;
|
||||
uint8_t out_brightness;
|
||||
__builtin_five_bit_hd_gamma_bitshift(CRGB(data[0][0], data[0][1], data[0][2]), CRGB(255, 255, 255), data[0][3], &out_color, &out_brightness);
|
||||
INFO("input red ", data[0][0], " green ", data[0][1], " blue ", data[0][2], " brightness ", data[0][3]);
|
||||
INFO("output red ", out_color.r, " green ", out_color.g, " blue ", out_color.b, " brightness ", out_brightness);
|
||||
CHECK_EQ(out_color.r, data[1][0]);
|
||||
CHECK_EQ(out_color.g, data[1][1]);
|
||||
CHECK_EQ(out_color.b, data[1][2]);
|
||||
CHECK_EQ(out_brightness, data[1][3]);
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK_NEAR(a, b, c) CHECK_LT(abs(a - b), c)
|
||||
|
||||
|
||||
#define STRESS_TEST 1
|
||||
|
||||
#define PROBLEMATIC_TEST 0
|
||||
|
||||
// Testing allows upto 21% error between power output of WS2812 and APA102 in HD mode.
|
||||
// This is mostly due to the rounding errors for WS2812 when one of the channels is small
|
||||
// and the rest are fairly large. One component will get rounded down to 0, while in
|
||||
// apa mode it will bitshift to relevance.
|
||||
const static float TOLERANCE = 0.21;
|
||||
const static int NUM_TESTS = 10000;
|
||||
const static size_t MAX_FAILURES = 30;
|
||||
const static int CUTOFF = 11; // WS2812 will give bad results when one of the components is less than 10.
|
||||
struct Power {
|
||||
float power;
|
||||
float power_5bit;
|
||||
uint8_t power_5bit_u8;
|
||||
};
|
||||
|
||||
static float power_diff(Power power) {
|
||||
return abs(power.power - power.power_5bit);
|
||||
}
|
||||
|
||||
|
||||
static float power_rgb(CRGB color, uint8_t brightness) {
|
||||
color *= brightness;
|
||||
float out = color.r / 255.f + color.g / 255.f + color.b / 255.f;
|
||||
return out / 3.0f;
|
||||
}
|
||||
|
||||
static float compute_power_5bit(CRGB color, uint8_t power_5bit, uint8_t brightness) {
|
||||
assert(power_5bit <= 31);
|
||||
float rgb_pow = power_rgb(color, brightness);
|
||||
float brightness_pow = (power_5bit) / 31.0f;
|
||||
|
||||
float out = rgb_pow * brightness_pow;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
static float compute_power_apa102(CRGB color, uint8_t brightness, uint8_t* power_5bit) {
|
||||
uint16_t r16 = map8_to_16(color.r);
|
||||
uint16_t g16 = map8_to_16(color.g);
|
||||
uint16_t b16 = map8_to_16(color.b);
|
||||
CRGB out_colors;
|
||||
uint8_t v5 = 31;
|
||||
uint8_t post_brightness_scale = five_bit_bitshift(r16, g16, b16, brightness, &out_colors, power_5bit);
|
||||
float power = compute_power_5bit(out_colors, v5, post_brightness_scale);
|
||||
return power;
|
||||
}
|
||||
|
||||
|
||||
static float compute_power_ws2812(CRGB color, uint8_t brightness) {
|
||||
float power = power_rgb(color, brightness);
|
||||
return power;
|
||||
}
|
||||
|
||||
static Power compute_power(uint8_t brightness8, CRGB color) {
|
||||
uint8_t power_5bit_u8;
|
||||
float power_5bit = compute_power_apa102(color, brightness8, &power_5bit_u8);
|
||||
float power_rgb = compute_power_ws2812(color, brightness8);
|
||||
return {power_rgb, power_5bit, power_5bit_u8};
|
||||
}
|
||||
|
||||
static void make_random(CRGB* color, uint8_t* brightness) {
|
||||
color->r = rand() % 256;
|
||||
color->g = rand() % 256;
|
||||
color->b = rand() % 256;
|
||||
*brightness = rand() % 256;
|
||||
}
|
||||
|
||||
struct Data {
|
||||
CRGB color;
|
||||
uint8_t brightness;
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE("five_bit_hd_gamma_bitshift functionality") {
|
||||
|
||||
SUBCASE("Sanity test for defines") {
|
||||
CHECK_EQ(FASTLED_HD_COLOR_MIXING, 1);
|
||||
}
|
||||
|
||||
#if PROBLEMATIC_TEST
|
||||
|
||||
SUBCASE("problematic test2") {
|
||||
// Failure, diff is 0.580777 brightness: 249 color: R: 103 G: 125 B: 236 power: 0 power_5bit: 31
|
||||
CRGB color = {103, 125, 236};
|
||||
uint8_t brightness = 249;
|
||||
problematic_test(color, brightness);
|
||||
FAIL("Problematic test failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if STRESS_TEST
|
||||
SUBCASE("Randomized Power Matching Test for 5 bit power") {
|
||||
srand(0); // Seed the random number generator so we get consitent results.
|
||||
std::vector<Data> failures;
|
||||
for (int i = 0; i < NUM_TESTS; i++) {
|
||||
CRGB color;
|
||||
uint8_t brightness;
|
||||
make_random(&color, &brightness);
|
||||
// if one ore more of the compoents is less than 10 then skip.
|
||||
if (color.r < CUTOFF || color.g < CUTOFF || color.b < CUTOFF || brightness < CUTOFF) {
|
||||
// WS2812 does badly at this.
|
||||
continue;
|
||||
}
|
||||
Power result = compute_power(brightness, color);
|
||||
float diff = power_diff(result);
|
||||
if (diff > TOLERANCE) {
|
||||
failures.push_back({color, brightness});
|
||||
while (failures.size() > MAX_FAILURES) {
|
||||
// failures.pop_back();
|
||||
// select smallest power difference and remove it.
|
||||
auto it = std::min_element(failures.begin(), failures.end(), [](const Data& a, const Data& b) {
|
||||
Power p1 = compute_power(a.brightness, a.color);
|
||||
Power p2 = compute_power(b.brightness, b.color);
|
||||
return power_diff(p1) < power_diff(p2);
|
||||
});
|
||||
failures.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failures.size()) {
|
||||
|
||||
// sort by the power difference
|
||||
|
||||
std::sort(failures.begin(), failures.end(), [](const Data& a, const Data& b) {
|
||||
Power p1 = compute_power(a.brightness, a.color);
|
||||
Power p2 = compute_power(b.brightness, b.color);
|
||||
return abs(p1.power - p1.power_5bit) > abs(p2.power - p2.power_5bit);
|
||||
});
|
||||
|
||||
std::cout << "Failures:" << std::endl;
|
||||
for (auto& failure : failures) {
|
||||
Power p = compute_power(failure.brightness, failure.color);
|
||||
std::string color_str = "R: " + std::to_string(failure.color.r) + " G: " + std::to_string(failure.color.g) + " B: " + std::to_string(failure.color.b);
|
||||
std::cout << "Failure, diff is " << power_diff(p) << " brightness: " << int(failure.brightness) << " color: " << color_str << " power: " << p.power << " power_5bit: " << int(p.power_5bit_u8) << std::endl;
|
||||
}
|
||||
// FAIL("Failures found");
|
||||
// make a oostream object
|
||||
std::ostringstream oss;
|
||||
oss << __FILE__ << ":" << __LINE__ << " Failures found";
|
||||
FAIL(oss.str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
270
libraries/FastLED/tests/test_bitset.cpp
Normal file
270
libraries/FastLED/tests/test_bitset.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/bitset.h"
|
||||
#include "fl/bitset_dynamic.h"
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("test bitset") {
|
||||
// default‐constructed bitset is empty
|
||||
bitset_fixed<10> bs;
|
||||
REQUIRE_EQ(bs.none(), true);
|
||||
REQUIRE_EQ(bs.count(), 0);
|
||||
REQUIRE_EQ(bs.size(), 10);
|
||||
|
||||
// set a bit
|
||||
bs.set(3);
|
||||
REQUIRE_EQ(bs.test(3), true);
|
||||
REQUIRE_EQ(bs[3], true);
|
||||
REQUIRE_EQ(bs.any(), true);
|
||||
REQUIRE_EQ(bs.count(), 1);
|
||||
|
||||
// reset that bit
|
||||
bs.reset(3);
|
||||
REQUIRE_EQ(bs.test(3), false);
|
||||
REQUIRE_EQ(bs.none(), true);
|
||||
|
||||
// toggle a bit
|
||||
bs.flip(2);
|
||||
REQUIRE_EQ(bs.test(2), true);
|
||||
bs.flip(2);
|
||||
REQUIRE_EQ(bs.test(2), false);
|
||||
|
||||
// flip all bits
|
||||
bitset_fixed<5> bs2;
|
||||
for (size_t i = 0; i < 5; ++i)
|
||||
bs2.set(i, (i % 2) == 0);
|
||||
auto bs2_flipped = ~bs2;
|
||||
for (size_t i = 0; i < 5; ++i)
|
||||
REQUIRE_EQ(bs2_flipped.test(i), !bs2.test(i));
|
||||
|
||||
// all() and count()
|
||||
bitset_fixed<4> bs3;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
bs3.set(i);
|
||||
REQUIRE_EQ(bs3.all(), true);
|
||||
REQUIRE_EQ(bs3.count(), 4);
|
||||
|
||||
// check that the count can auto expand
|
||||
bs3.set(100);
|
||||
REQUIRE_EQ(bs3.count(), 4);
|
||||
|
||||
// bitwise AND, OR, XOR
|
||||
bitset_fixed<4> a, b;
|
||||
a.set(0); a.set(2);
|
||||
b.set(1); b.set(2);
|
||||
|
||||
auto or_ab = a | b;
|
||||
REQUIRE_EQ(or_ab.test(0), true);
|
||||
REQUIRE_EQ(or_ab.test(1), true);
|
||||
REQUIRE_EQ(or_ab.test(2), true);
|
||||
REQUIRE_EQ(or_ab.test(3), false);
|
||||
|
||||
auto and_ab = a & b;
|
||||
REQUIRE_EQ(and_ab.test(2), true);
|
||||
REQUIRE_EQ(and_ab.test(0), false);
|
||||
|
||||
auto xor_ab = a ^ b;
|
||||
REQUIRE_EQ(xor_ab.test(0), true);
|
||||
REQUIRE_EQ(xor_ab.test(1), true);
|
||||
REQUIRE_EQ(xor_ab.test(2), false);
|
||||
|
||||
// reset and none()
|
||||
a.reset(); b.reset();
|
||||
REQUIRE_EQ(a.none(), true);
|
||||
|
||||
// Test expected size of bitset_fixed
|
||||
REQUIRE_EQ(bitset_fixed<8>().size(), 8);
|
||||
REQUIRE_EQ(bitset_fixed<16>().size(), 16);
|
||||
REQUIRE_EQ(bitset_fixed<32>().size(), 32);
|
||||
REQUIRE_EQ(bitset_fixed<64>().size(), 64);
|
||||
REQUIRE_EQ(bitset_fixed<100>().size(), 100);
|
||||
REQUIRE_EQ(bitset_fixed<1000>().size(), 1000);
|
||||
|
||||
// Test memory size of bitset_fixed class (sizeof)
|
||||
// For bitset_fixed<8>, we expect 1 uint64_t block (8 bytes)
|
||||
REQUIRE_EQ(sizeof(bitset_fixed<8>), 8);
|
||||
|
||||
// For bitset_fixed<64>, we expect 1 uint64_t block (8 bytes)
|
||||
REQUIRE_EQ(sizeof(bitset_fixed<64>), 8);
|
||||
|
||||
// For bitset_fixed<65>, we expect 2 uint64_t blocks (16 bytes)
|
||||
REQUIRE_EQ(sizeof(bitset_fixed<65>), 16);
|
||||
|
||||
// For bitset_fixed<128>, we expect 2 uint64_t blocks (16 bytes)
|
||||
REQUIRE_EQ(sizeof(bitset_fixed<128>), 16);
|
||||
|
||||
// For bitset_fixed<129>, we expect 3 uint64_t blocks (24 bytes)
|
||||
REQUIRE_EQ(sizeof(bitset_fixed<129>), 24);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("compare fixed and dynamic bitsets") {
|
||||
// Test that fixed and dynamic bitsets behave the same
|
||||
bitset_fixed<10> fixed_bs;
|
||||
fl::bitset_dynamic dynamic_bs(10);
|
||||
|
||||
// Set the same bits in both
|
||||
fixed_bs.set(1);
|
||||
fixed_bs.set(5);
|
||||
fixed_bs.set(9);
|
||||
|
||||
dynamic_bs.set(1);
|
||||
dynamic_bs.set(5);
|
||||
dynamic_bs.set(9);
|
||||
|
||||
// Verify they have the same state
|
||||
REQUIRE_EQ(fixed_bs.size(), dynamic_bs.size());
|
||||
REQUIRE_EQ(fixed_bs.count(), dynamic_bs.count());
|
||||
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
REQUIRE_EQ(fixed_bs.test(i), dynamic_bs.test(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("test bitset_dynamic") {
|
||||
// default-constructed bitset is empty
|
||||
bitset_dynamic bs;
|
||||
REQUIRE_EQ(bs.size(), 0);
|
||||
REQUIRE_EQ(bs.none(), true);
|
||||
REQUIRE_EQ(bs.count(), 0);
|
||||
|
||||
// resize and test
|
||||
bs.resize(10);
|
||||
REQUIRE_EQ(bs.size(), 10);
|
||||
REQUIRE_EQ(bs.none(), true);
|
||||
|
||||
// set a bit
|
||||
bs.set(3);
|
||||
REQUIRE_EQ(bs.test(3), true);
|
||||
REQUIRE_EQ(bs[3], true);
|
||||
REQUIRE_EQ(bs.any(), true);
|
||||
REQUIRE_EQ(bs.count(), 1);
|
||||
|
||||
// reset that bit
|
||||
bs.reset(3);
|
||||
REQUIRE_EQ(bs.test(3), false);
|
||||
REQUIRE_EQ(bs.none(), true);
|
||||
|
||||
// toggle a bit
|
||||
bs.flip(2);
|
||||
REQUIRE_EQ(bs.test(2), true);
|
||||
bs.flip(2);
|
||||
REQUIRE_EQ(bs.test(2), false);
|
||||
|
||||
// resize larger
|
||||
bs.set(5);
|
||||
bs.resize(20);
|
||||
REQUIRE_EQ(bs.size(), 20);
|
||||
REQUIRE_EQ(bs.test(5), true);
|
||||
REQUIRE_EQ(bs.count(), 1);
|
||||
|
||||
// resize smaller (truncate)
|
||||
bs.resize(4);
|
||||
REQUIRE_EQ(bs.size(), 4);
|
||||
REQUIRE_EQ(bs.test(5), false); // out of range now
|
||||
REQUIRE_EQ(bs.count(), 0);
|
||||
|
||||
// test with larger sizes that span multiple blocks
|
||||
bitset_dynamic large_bs(100);
|
||||
large_bs.set(0);
|
||||
large_bs.set(63);
|
||||
large_bs.set(64);
|
||||
large_bs.set(99);
|
||||
REQUIRE_EQ(large_bs.count(), 4);
|
||||
REQUIRE_EQ(large_bs.test(0), true);
|
||||
REQUIRE_EQ(large_bs.test(63), true);
|
||||
REQUIRE_EQ(large_bs.test(64), true);
|
||||
REQUIRE_EQ(large_bs.test(99), true);
|
||||
|
||||
// flip all bits
|
||||
bitset_dynamic bs2(5);
|
||||
for (size_t i = 0; i < 5; ++i)
|
||||
bs2.set(i, (i % 2) == 0);
|
||||
|
||||
bs2.flip();
|
||||
for (size_t i = 0; i < 5; ++i)
|
||||
REQUIRE_EQ(bs2.test(i), !((i % 2) == 0));
|
||||
|
||||
// all() and count()
|
||||
bitset_dynamic bs3(4);
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
bs3.set(i);
|
||||
REQUIRE_EQ(bs3.all(), true);
|
||||
REQUIRE_EQ(bs3.count(), 4);
|
||||
|
||||
// out-of-range ops are no-ops
|
||||
bs3.set(100);
|
||||
REQUIRE_EQ(bs3.count(), 4);
|
||||
|
||||
// bitwise AND, OR, XOR
|
||||
bitset_dynamic a(4), b(4);
|
||||
a.set(0); a.set(2);
|
||||
b.set(1); b.set(2);
|
||||
|
||||
auto or_ab = a | b;
|
||||
REQUIRE_EQ(or_ab.test(0), true);
|
||||
REQUIRE_EQ(or_ab.test(1), true);
|
||||
REQUIRE_EQ(or_ab.test(2), true);
|
||||
REQUIRE_EQ(or_ab.test(3), false);
|
||||
|
||||
auto and_ab = a & b;
|
||||
REQUIRE_EQ(and_ab.test(2), true);
|
||||
REQUIRE_EQ(and_ab.test(0), false);
|
||||
|
||||
auto xor_ab = a ^ b;
|
||||
REQUIRE_EQ(xor_ab.test(0), true);
|
||||
REQUIRE_EQ(xor_ab.test(1), true);
|
||||
REQUIRE_EQ(xor_ab.test(2), false);
|
||||
|
||||
// reset and none()
|
||||
a.reset(); b.reset();
|
||||
REQUIRE_EQ(a.none(), true);
|
||||
REQUIRE_EQ(b.none(), true);
|
||||
|
||||
// copy constructor
|
||||
bitset_dynamic original(10);
|
||||
original.set(3);
|
||||
original.set(7);
|
||||
|
||||
bitset_dynamic copy(original);
|
||||
REQUIRE_EQ(copy.size(), 10);
|
||||
REQUIRE_EQ(copy.test(3), true);
|
||||
REQUIRE_EQ(copy.test(7), true);
|
||||
REQUIRE_EQ(copy.count(), 2);
|
||||
|
||||
// move constructor
|
||||
bitset_dynamic moved(fl::move(copy));
|
||||
REQUIRE_EQ(moved.size(), 10);
|
||||
REQUIRE_EQ(moved.test(3), true);
|
||||
REQUIRE_EQ(moved.test(7), true);
|
||||
REQUIRE_EQ(moved.count(), 2);
|
||||
REQUIRE_EQ(copy.size(), 0); // moved from should be empty
|
||||
|
||||
// assignment operator
|
||||
bitset_dynamic assigned = original;
|
||||
REQUIRE_EQ(assigned.size(), 10);
|
||||
REQUIRE_EQ(assigned.test(3), true);
|
||||
REQUIRE_EQ(assigned.test(7), true);
|
||||
|
||||
// clear
|
||||
assigned.clear();
|
||||
REQUIRE_EQ(assigned.size(), 0);
|
||||
REQUIRE_EQ(assigned.none(), true);
|
||||
|
||||
// Test memory size changes with resize
|
||||
bitset_dynamic small_bs(8);
|
||||
bitset_dynamic medium_bs(65);
|
||||
bitset_dynamic large_bs2(129);
|
||||
|
||||
// These sizes should match the fixed bitset tests
|
||||
REQUIRE_EQ(small_bs.size(), 8);
|
||||
REQUIRE_EQ(medium_bs.size(), 65);
|
||||
REQUIRE_EQ(large_bs2.size(), 129);
|
||||
}
|
||||
|
||||
|
||||
177
libraries/FastLED/tests/test_brightness_bitshifter.cpp
Normal file
177
libraries/FastLED/tests/test_brightness_bitshifter.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "lib8tion/brightness_bitshifter.h"
|
||||
#include <iostream>
|
||||
#include <bitset>
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("brightness_bitshifter8") {
|
||||
SUBCASE("random test to check that the product is the same") {
|
||||
int count = 0;
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
uint8_t brightness_src = 0b10000000 >> rand() % 6;
|
||||
uint8_t brightness_dst = rand() % 256;
|
||||
uint16_t product = uint16_t(brightness_src) * brightness_dst;
|
||||
uint8_t shifts = brightness_bitshifter8(&brightness_src, &brightness_dst, 7);
|
||||
uint16_t new_product = uint16_t(brightness_src) * brightness_dst;
|
||||
CHECK_EQ(product, new_product);
|
||||
if (shifts) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
CHECK_GT(count, 0);
|
||||
}
|
||||
|
||||
SUBCASE("fixed test data mimicing actual use") {
|
||||
const uint8_t test_data[][2][2] = {
|
||||
// brightness_bitshifter8 is always called with brightness_src = 0b00010000
|
||||
{ // test case
|
||||
// src dst
|
||||
{0b00010000, 0b00000000}, // input
|
||||
{0b00010000, 0b00000000}, // output
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b00000001},
|
||||
{0b00000001, 0b00010000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b00000100},
|
||||
{0b00000001, 0b01000000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b00010000},
|
||||
{0b00000010, 0b10000000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b00001010},
|
||||
{0b00000001, 0b10100000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b00101010},
|
||||
{0b00000100, 0b10101000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b11101010},
|
||||
{0b00010000, 0b11101010},
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& data : test_data) {
|
||||
uint8_t brightness_src = data[0][0];
|
||||
uint8_t brightness_dst = data[0][1];
|
||||
uint8_t shifts = brightness_bitshifter8(&brightness_src, &brightness_dst, 4);
|
||||
INFO("input brightness_src: ", data[0][0], " ; input brightness_dst: ", data[0][1]);
|
||||
INFO("output brightness_src: ", brightness_src, " ; output brightness_dst: ", brightness_dst);
|
||||
INFO("shifts : ", shifts);
|
||||
CHECK_EQ(brightness_src, data[1][0]);
|
||||
CHECK_EQ(brightness_dst, data[1][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("brightness_bitshifter16") {
|
||||
SUBCASE("simple with steps=2") {
|
||||
uint8_t brightness_src = 0x1 << 1;
|
||||
uint16_t brightness_dst = 0x1 << 2;
|
||||
uint8_t max_shifts = 8;
|
||||
|
||||
uint8_t shifts = brightness_bitshifter16(&brightness_src, &brightness_dst, max_shifts, 2);
|
||||
|
||||
CHECK_EQ(shifts, 1);
|
||||
CHECK_EQ(brightness_src, 1);
|
||||
CHECK_EQ(brightness_dst, 0x1 << 4);
|
||||
}
|
||||
|
||||
SUBCASE("simple with steps=1") {
|
||||
uint8_t brightness_src = 0x1 << 1;
|
||||
uint16_t brightness_dst = 0x1 << 1;
|
||||
uint8_t max_shifts = 8;
|
||||
|
||||
uint8_t shifts = brightness_bitshifter16(&brightness_src, &brightness_dst, max_shifts, 1);
|
||||
|
||||
CHECK_EQ(shifts, 1);
|
||||
CHECK_EQ(brightness_src, 1);
|
||||
CHECK_EQ(brightness_dst, 0x1 << 2);
|
||||
}
|
||||
|
||||
SUBCASE("random test to check that the product is the same") {
|
||||
int count = 0;
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
uint8_t brightness_src = 0b10000000 >> (rand() % 8);
|
||||
uint16_t brightness_dst = rand() % uint32_t(65536);
|
||||
uint32_t product = uint32_t(brightness_src >> 8) * brightness_dst;
|
||||
uint8_t max_shifts = 8;
|
||||
uint8_t steps = 2;
|
||||
|
||||
uint8_t shifts = brightness_bitshifter16(&brightness_src, &brightness_dst, max_shifts, steps);
|
||||
|
||||
uint32_t new_product = uint32_t(brightness_src >> 8) * brightness_dst;
|
||||
CHECK_EQ(product, new_product);
|
||||
if (shifts) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
CHECK_GT(count, 0);
|
||||
}
|
||||
|
||||
SUBCASE("fixed test data mimicing actual use") {
|
||||
const uint16_t test_data[][2][2] = {
|
||||
// brightness_bitshifter16 is always called with brightness_src between 0b00000001 - 0b00010000
|
||||
{ // test case
|
||||
// src dst
|
||||
{0b00000001, 0b0000000000000000}, // input
|
||||
{0b00000001, 0b0000000000000000}, // output
|
||||
},
|
||||
{
|
||||
{0b00000001, 0b0000000000000001},
|
||||
{0b00000001, 0b0000000000000001},
|
||||
},
|
||||
{
|
||||
{0b00000001, 0b0000000000000010},
|
||||
{0b00000001, 0b0000000000000010},
|
||||
},
|
||||
{
|
||||
{0b00000010, 0b0000000000000001},
|
||||
{0b00000001, 0b0000000000000100},
|
||||
},
|
||||
{
|
||||
{0b00001010, 0b0000000000001010},
|
||||
{0b00000101, 0b0000000000101000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b0000111000100100},
|
||||
{0b00000100, 0b1110001001000000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b0011100010010010},
|
||||
{0b00001000, 0b1110001001001000},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b0110001001001110},
|
||||
{0b00010000, 0b0110001001001110},
|
||||
},
|
||||
{
|
||||
{0b00010000, 0b1110001001001110},
|
||||
{0b00010000, 0b1110001001001110},
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto& data : test_data) {
|
||||
uint8_t brightness_src = static_cast<uint8_t>(data[0][0]);
|
||||
uint16_t brightness_dst = data[0][1];
|
||||
uint8_t shifts = brightness_bitshifter16(&brightness_src, &brightness_dst, 4, 2);
|
||||
INFO("input brightness_src: ", data[0][0], " ; input brightness_dst: ", data[0][1]);
|
||||
INFO("output brightness_src: ", brightness_src, " ; output brightness_dst: ", brightness_dst);
|
||||
INFO("shifts (by 2 bits): ", shifts);
|
||||
CHECK_EQ(brightness_src, data[1][0]);
|
||||
CHECK_EQ(brightness_dst, data[1][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
261
libraries/FastLED/tests/test_bytestream.cpp
Normal file
261
libraries/FastLED/tests/test_bytestream.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
// Compile with: g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/bytestreammemory.h"
|
||||
#include "fx/video/pixel_stream.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("ByteStreamMemory basic operations") {
|
||||
|
||||
SUBCASE("Write and read single byte") {
|
||||
ByteStreamMemory stream(10); // Stream with 10 bytes capacity
|
||||
uint8_t testByte = 42;
|
||||
CHECK(stream.write(&testByte, 1) == 1);
|
||||
|
||||
uint8_t readByte = 0;
|
||||
CHECK(stream.read(&readByte, 1) == 1);
|
||||
CHECK(readByte == testByte);
|
||||
|
||||
// Next read should fail since the stream is empty
|
||||
CHECK(stream.read(&readByte, 1) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Write and read multiple bytes") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[] = {1, 2, 3, 4, 5};
|
||||
CHECK(stream.write(testData, 5) == 5);
|
||||
|
||||
uint8_t readData[5] = {0};
|
||||
CHECK(stream.read(readData, 5) == 5);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
CHECK(readData[i] == testData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Attempt to read from empty stream") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t readByte = 0;
|
||||
CHECK(stream.read(&readByte, 1) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Attempt to write beyond capacity") {
|
||||
ByteStreamMemory stream(5);
|
||||
uint8_t testData[] = {1, 2, 3, 4, 5, 6};
|
||||
CHECK(stream.write(testData, 6) == 5); // Should write up to capacity
|
||||
}
|
||||
|
||||
SUBCASE("Attempt to read more than available data") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[] = {1, 2, 3};
|
||||
CHECK(stream.write(testData, 3) == 3);
|
||||
|
||||
uint8_t readData[5] = {0};
|
||||
CHECK_FALSE(stream.read(readData, 5) == 3); // Should read only available data
|
||||
//CHECK(readData[0] == 1);
|
||||
//CHECK(readData[1] == 2);
|
||||
//CHECK(readData[2] == 3);
|
||||
}
|
||||
|
||||
SUBCASE("Multiple write and read operations") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData1[] = {1, 2, 3};
|
||||
uint8_t testData2[] = {4, 5};
|
||||
CHECK(stream.write(testData1, 3) == 3);
|
||||
CHECK(stream.write(testData2, 2) == 2);
|
||||
|
||||
uint8_t readData[5] = {0};
|
||||
CHECK(stream.read(readData, 5) == 5);
|
||||
CHECK(readData[0] == 1);
|
||||
CHECK(readData[1] == 2);
|
||||
CHECK(readData[2] == 3);
|
||||
CHECK(readData[3] == 4);
|
||||
CHECK(readData[4] == 5);
|
||||
}
|
||||
|
||||
|
||||
SUBCASE("Write after partial read") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[] = {1, 2, 3, 4, 5};
|
||||
CHECK(stream.write(testData, 5) == 5);
|
||||
|
||||
uint8_t readData[2] = {0};
|
||||
CHECK(stream.read(readData, 2) == 2);
|
||||
CHECK(readData[0] == 1);
|
||||
CHECK(readData[1] == 2);
|
||||
|
||||
uint8_t newTestData[] = {6, 7};
|
||||
CHECK(stream.write(newTestData, 2) == 2);
|
||||
|
||||
uint8_t remainingData[5] = {0};
|
||||
CHECK(stream.read(remainingData, 5) == 5);
|
||||
CHECK(remainingData[0] == 3);
|
||||
CHECK(remainingData[1] == 4);
|
||||
CHECK(remainingData[2] == 5);
|
||||
CHECK(remainingData[3] == 6);
|
||||
CHECK(remainingData[4] == 7);
|
||||
}
|
||||
|
||||
SUBCASE("Fill and empty stream multiple times") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[10];
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
testData[i] = i;
|
||||
}
|
||||
|
||||
// First cycle
|
||||
CHECK(stream.write(testData, 10) == 10);
|
||||
uint8_t readData[10] = {0};
|
||||
CHECK(stream.read(readData, 10) == 10);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
CHECK(readData[i] == i);
|
||||
}
|
||||
|
||||
// Second cycle
|
||||
CHECK(stream.write(testData, 10) == 10);
|
||||
CHECK(stream.read(readData, 10) == 10);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
CHECK(readData[i] == i);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Zero-length write and read") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[] = {1, 2, 3};
|
||||
CHECK(stream.write(testData, 0) == 0);
|
||||
|
||||
uint8_t readData[3] = {0};
|
||||
CHECK(stream.read(readData, 0) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Write and read with null pointers") {
|
||||
ByteStreamMemory stream(10);
|
||||
CHECK(stream.write(static_cast<uint8_t*>(nullptr), 5) == 0);
|
||||
CHECK(stream.read(nullptr, 5) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Boundary conditions") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[10];
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
testData[i] = i;
|
||||
}
|
||||
CHECK(stream.write(testData, 10) == 10);
|
||||
|
||||
uint8_t readData[10] = {0};
|
||||
CHECK(stream.read(readData, 10) == 10);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
CHECK(readData[i] == i);
|
||||
}
|
||||
|
||||
// Try to write again to full capacity
|
||||
CHECK(stream.write(testData, 10) == 10);
|
||||
}
|
||||
|
||||
SUBCASE("Write with partial capacity") {
|
||||
ByteStreamMemory stream(5);
|
||||
uint8_t testData[] = {1, 2, 3, 4, 5};
|
||||
CHECK(stream.write(testData, 5) == 5);
|
||||
|
||||
uint8_t moreData[] = {6, 7};
|
||||
CHECK(stream.write(moreData, 2) == 0); // Should not write since capacity is full
|
||||
|
||||
uint8_t readData[5] = {0};
|
||||
CHECK(stream.read(readData, 5) == 5);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
CHECK(readData[i] == testData[i]);
|
||||
}
|
||||
|
||||
// Now buffer is empty, try writing again
|
||||
CHECK(stream.write(moreData, 2) == 2);
|
||||
CHECK(stream.read(readData, 2) == 2);
|
||||
CHECK(readData[0] == 6);
|
||||
CHECK(readData[1] == 7);
|
||||
}
|
||||
|
||||
SUBCASE("Read after buffer is reset") {
|
||||
ByteStreamMemory stream(10);
|
||||
uint8_t testData[] = {1, 2, 3};
|
||||
CHECK(stream.write(testData, 3) == 3);
|
||||
|
||||
stream.clear(); // Assuming reset clears the buffer
|
||||
|
||||
uint8_t readData[3] = {0};
|
||||
CHECK(stream.read(readData, 3) == 0); // Should read nothing
|
||||
}
|
||||
|
||||
SUBCASE("Write zero bytes when buffer is full") {
|
||||
ByteStreamMemory stream(0); // Zero capacity
|
||||
uint8_t testByte = 42;
|
||||
CHECK(stream.write(&testByte, 1) == 0); // Cannot write to zero-capacity buffer
|
||||
}
|
||||
|
||||
SUBCASE("Sequential writes and reads") {
|
||||
ByteStreamMemory stream(10);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
CHECK(stream.write(&i, 1) == 1);
|
||||
}
|
||||
|
||||
uint8_t readByte = 0;
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
CHECK(stream.read(&readByte, 1) == 1);
|
||||
CHECK(readByte == i);
|
||||
}
|
||||
|
||||
// Stream should now be empty
|
||||
CHECK(stream.read(&readByte, 1) == 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("byte stream memory basic operations") {
|
||||
const int BYTES_PER_FRAME = 3 * 10 * 10; // Assuming a 10x10 RGB video
|
||||
|
||||
// Create a ByteStreamMemory
|
||||
const uint32_t BUFFER_SIZE = BYTES_PER_FRAME * 10; // Enough for 10 frames
|
||||
ByteStreamMemoryPtr memoryStream = ByteStreamMemoryPtr::New(BUFFER_SIZE);
|
||||
|
||||
// Fill the ByteStreamMemory with test data
|
||||
uint8_t testData[BUFFER_SIZE];
|
||||
for (uint32_t i = 0; i < BUFFER_SIZE; ++i) {
|
||||
testData[i] = static_cast<uint8_t>(i % 256);
|
||||
}
|
||||
memoryStream->write(testData, BUFFER_SIZE);
|
||||
|
||||
// Create and initialize PixelStream
|
||||
PixelStreamPtr stream = PixelStreamPtr::New(BYTES_PER_FRAME);
|
||||
bool initSuccess = stream->beginStream(memoryStream);
|
||||
REQUIRE(initSuccess);
|
||||
|
||||
// Test basic properties
|
||||
CHECK(stream->getType() == PixelStream::kStreaming);
|
||||
CHECK(stream->bytesPerFrame() == BYTES_PER_FRAME);
|
||||
|
||||
// Read a pixel
|
||||
CRGB pixel;
|
||||
bool readSuccess = stream->readPixel(&pixel);
|
||||
REQUIRE(readSuccess);
|
||||
CHECK(pixel.r == 0);
|
||||
CHECK(pixel.g == 1);
|
||||
CHECK(pixel.b == 2);
|
||||
|
||||
// Read some bytes
|
||||
uint8_t buffer[10];
|
||||
size_t bytesRead = stream->readBytes(buffer, 10);
|
||||
CHECK(bytesRead == 10);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
CHECK(buffer[i] == static_cast<uint8_t>((i + 3) % 256));
|
||||
}
|
||||
|
||||
// Check frame counting - streaming mode doesn't support this.
|
||||
//CHECK(PixelStream->framesDisplayed() == 0);
|
||||
//CHECK(PixelStream->framesRemaining() == 10); // We have 10 frames of data
|
||||
|
||||
// Close the stream
|
||||
stream->close();
|
||||
}
|
||||
297
libraries/FastLED/tests/test_circular_buffer.cpp
Normal file
297
libraries/FastLED/tests/test_circular_buffer.cpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/circular_buffer.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("circular_buffer basic operations") {
|
||||
CircularBuffer<int> buffer(5);
|
||||
|
||||
CHECK(buffer.empty());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
buffer.push_back(3);
|
||||
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
CHECK_FALSE(buffer.empty());
|
||||
CHECK_FALSE(buffer.full());
|
||||
|
||||
CHECK_EQ(buffer.front(), 1);
|
||||
CHECK_EQ(buffer.back(), 3);
|
||||
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 1);
|
||||
CHECK_EQ(buffer.size(), 2);
|
||||
CHECK_EQ(buffer.front(), 2);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer operator[]") {
|
||||
CircularBuffer<int> buffer(5);
|
||||
|
||||
CHECK(buffer.empty());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
CHECK_EQ(buffer.size(), 2);
|
||||
CHECK_EQ(buffer[0], 1);
|
||||
CHECK_EQ(buffer[1], 2);
|
||||
buffer.pop_front(nullptr);
|
||||
CHECK_EQ(2, buffer[0]);
|
||||
buffer.push_back(3);
|
||||
CHECK_EQ(2, buffer[0]);
|
||||
CHECK_EQ(3, buffer[1]);
|
||||
buffer.pop_back(nullptr);
|
||||
CHECK_EQ(2, buffer[0]);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer overflow behavior") {
|
||||
CircularBuffer<int> buffer(3);
|
||||
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
buffer.push_back(3);
|
||||
CHECK(buffer.full());
|
||||
|
||||
buffer.push_back(4);
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 2);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 3);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 4);
|
||||
CHECK(buffer.empty());
|
||||
|
||||
// CHECK_EQ(buffer.pop_front(), 0); // Returns default-constructed int (0) when empty
|
||||
CHECK_EQ(buffer.pop_front(&value), false);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer edge cases") {
|
||||
CircularBuffer<int> buffer(1);
|
||||
|
||||
CHECK(buffer.empty());
|
||||
CHECK_FALSE(buffer.full());
|
||||
|
||||
buffer.push_back(42);
|
||||
CHECK_FALSE(buffer.empty());
|
||||
CHECK(buffer.full());
|
||||
|
||||
buffer.push_back(43);
|
||||
CHECK_EQ(buffer.front(), 43);
|
||||
CHECK_EQ(buffer.back(), 43);
|
||||
|
||||
int value;
|
||||
bool ok = buffer.pop_front(&value);
|
||||
CHECK(ok);
|
||||
CHECK_EQ(value, 43);
|
||||
CHECK(buffer.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer clear operation") {
|
||||
CircularBuffer<int> buffer(5);
|
||||
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
buffer.push_back(3);
|
||||
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
|
||||
buffer.clear();
|
||||
|
||||
CHECK(buffer.empty());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
|
||||
buffer.push_back(4);
|
||||
CHECK_EQ(buffer.front(), 4);
|
||||
CHECK_EQ(buffer.back(), 4);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer indexing") {
|
||||
CircularBuffer<int> buffer(5);
|
||||
|
||||
buffer.push_back(10);
|
||||
buffer.push_back(20);
|
||||
buffer.push_back(30);
|
||||
|
||||
CHECK_EQ(buffer[0], 10);
|
||||
CHECK_EQ(buffer[1], 20);
|
||||
CHECK_EQ(buffer[2], 30);
|
||||
|
||||
buffer.pop_front(nullptr);
|
||||
buffer.push_back(40);
|
||||
|
||||
CHECK_EQ(buffer[0], 20);
|
||||
CHECK_EQ(buffer[1], 30);
|
||||
CHECK_EQ(buffer[2], 40);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer with custom type") {
|
||||
struct CustomType {
|
||||
int value;
|
||||
CustomType(int v = 0) : value(v) {}
|
||||
bool operator==(const CustomType& other) const { return value == other.value; }
|
||||
};
|
||||
|
||||
CircularBuffer<CustomType> buffer(3);
|
||||
|
||||
buffer.push_back(CustomType(1));
|
||||
buffer.push_back(CustomType(2));
|
||||
buffer.push_back(CustomType(3));
|
||||
|
||||
CHECK_EQ(buffer.front().value, 1);
|
||||
CHECK_EQ(buffer.back().value, 3);
|
||||
|
||||
buffer.push_back(CustomType(4));
|
||||
|
||||
CustomType value;
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value.value, 2);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value.value, 3);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value.value, 4);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer writing to full buffer") {
|
||||
CircularBuffer<int> buffer(3);
|
||||
|
||||
// Fill the buffer
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
buffer.push_back(3);
|
||||
CHECK(buffer.full());
|
||||
|
||||
// Write to full buffer
|
||||
buffer.push_back(4);
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
|
||||
// Check that the oldest element was overwritten
|
||||
CHECK_EQ(buffer[0], 2);
|
||||
CHECK_EQ(buffer[1], 3);
|
||||
CHECK_EQ(buffer[2], 4);
|
||||
|
||||
// Write multiple elements to full buffer
|
||||
buffer.push_back(5);
|
||||
buffer.push_back(6);
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
|
||||
// Check that the buffer contains only the most recent elements
|
||||
CHECK_EQ(buffer[0], 4);
|
||||
CHECK_EQ(buffer[1], 5);
|
||||
CHECK_EQ(buffer[2], 6);
|
||||
|
||||
// Verify front() and back()
|
||||
CHECK_EQ(buffer.front(), 4);
|
||||
CHECK_EQ(buffer.back(), 6);
|
||||
|
||||
// Pop all elements and verify
|
||||
//CHECK_EQ(buffer.pop_front(), 4);
|
||||
//CHECK_EQ(buffer.pop_front(), 5);
|
||||
//CHECK_EQ(buffer.pop_front(), 6);
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 4);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 5);
|
||||
CHECK_EQ(buffer.pop_front(&value), true);
|
||||
CHECK_EQ(value, 6);
|
||||
CHECK(buffer.empty());
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
TEST_CASE("circular_buffer zero capacity") {
|
||||
CircularBuffer<int> buffer(0);
|
||||
|
||||
CHECK(buffer.empty());
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
|
||||
// Attempt to push an element
|
||||
buffer.push_back(1);
|
||||
|
||||
// Buffer should now contain one element
|
||||
CHECK(buffer.empty());
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
|
||||
// Attempt to pop an element
|
||||
// CHECK_EQ(buffer.pop_front(), 0);
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_front(&value), false);
|
||||
|
||||
// Buffer should be empty again
|
||||
CHECK(buffer.empty());
|
||||
CHECK(buffer.full());
|
||||
CHECK_EQ(buffer.size(), 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
TEST_CASE("circular_buffer pop_back operation") {
|
||||
CircularBuffer<int> buffer(5);
|
||||
|
||||
buffer.push_back(1);
|
||||
buffer.push_back(2);
|
||||
buffer.push_back(3);
|
||||
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 3);
|
||||
CHECK_EQ(buffer.size(), 2);
|
||||
CHECK_EQ(buffer.back(), 2);
|
||||
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 2);
|
||||
CHECK_EQ(buffer.size(), 1);
|
||||
CHECK_EQ(buffer.front(), 1);
|
||||
CHECK_EQ(buffer.back(), 1);
|
||||
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 1);
|
||||
CHECK(buffer.empty());
|
||||
|
||||
CHECK_EQ(buffer.pop_back(&value), false);
|
||||
}
|
||||
|
||||
TEST_CASE("circular_buffer push_front operation") {
|
||||
CircularBuffer<int> buffer(3);
|
||||
|
||||
buffer.push_front(1);
|
||||
buffer.push_front(2);
|
||||
buffer.push_front(3);
|
||||
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
CHECK_EQ(buffer.front(), 3);
|
||||
CHECK_EQ(buffer.back(), 1);
|
||||
|
||||
buffer.push_front(4);
|
||||
CHECK_EQ(buffer.size(), 3);
|
||||
CHECK_EQ(buffer.front(), 4);
|
||||
CHECK_EQ(buffer.back(), 2);
|
||||
|
||||
int value;
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 2);
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 3);
|
||||
CHECK_EQ(buffer.pop_back(&value), true);
|
||||
CHECK_EQ(value, 4);
|
||||
CHECK(buffer.empty());
|
||||
}
|
||||
139
libraries/FastLED/tests/test_corkscrew.cpp
Normal file
139
libraries/FastLED/tests/test_corkscrew.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "fl/math_macros.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/sstream.h"
|
||||
|
||||
#include "fl/corkscrew.h"
|
||||
|
||||
|
||||
#define NUM_LEDS 288
|
||||
|
||||
|
||||
#define TWO_PI (PI * 2.0)
|
||||
|
||||
// Define an improved CHECK_CLOSE macro that provides better error messages
|
||||
#define CHECK_CLOSE(a, b, epsilon) \
|
||||
do { \
|
||||
float _a = (a); \
|
||||
float _b = (b); \
|
||||
float _diff = fabsf(_a - _b); \
|
||||
bool _result = _diff <= (epsilon); \
|
||||
if (!_result) { \
|
||||
printf("CHECK_CLOSE failed: |%f - %f| = %f > %f\n", (float)_a, \
|
||||
(float)_b, _diff, (float)(epsilon)); \
|
||||
} \
|
||||
CHECK(_result); \
|
||||
} while (0)
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Corkscrew generateMap") {
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = 10.0f;
|
||||
input.totalAngle = TWO_PI;
|
||||
input.offsetCircumference = 0.0f;
|
||||
input.numLeds = 10;
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
|
||||
CHECK_EQ(output.width, 10);
|
||||
CHECK_EQ(output.height, 1); // One vertical segment for one turn
|
||||
CHECK_EQ(output.mapping.size(), 10); // 10 LEDs around the corkscrew
|
||||
|
||||
CHECK_GE(output.mapping[0].x, 0.0f);
|
||||
CHECK_LE(output.mapping[0].x, 10.0f);
|
||||
CHECK_GE(output.mapping[0].y, 0.0f);
|
||||
CHECK_LE(output.mapping[0].y, 1.0f); // 1 vertical segment for 2π angle
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew to Frame Buffer Mapping") {
|
||||
// Define the corkscrew input parameters
|
||||
|
||||
|
||||
|
||||
const int kCorkscrewTotalHeight = 1; // cm
|
||||
//const int CORKSCREW_WIDTH = 1; // Width of the corkscrew in pixels
|
||||
//const int CORKSCREW_HEIGHT = 1; // Height of the corkscrew in pixels
|
||||
const int kCorkscrewTurns = 2; // Default to 19 turns
|
||||
|
||||
|
||||
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = kCorkscrewTotalHeight;
|
||||
input.totalAngle = kCorkscrewTurns * 2 * PI; // Default to 19 turns
|
||||
input.offsetCircumference = 0.0f;
|
||||
input.numLeds = 3;
|
||||
|
||||
// Generate the corkscrew map
|
||||
|
||||
|
||||
|
||||
Corkscrew corkscrew(input);
|
||||
|
||||
volatile Corkscrew::Output* output = &corkscrew.access();
|
||||
|
||||
// vec2<int16_t> first = corkscrew.at(0);
|
||||
// vec2<int16_t> second = corkscrew.at(1);
|
||||
|
||||
Corkscrew::iterator it = corkscrew.begin();
|
||||
Corkscrew::iterator end = corkscrew.end();
|
||||
|
||||
fl::sstream ss;
|
||||
ss << "\n";
|
||||
ss << "width: " << output->width << "\n";
|
||||
ss << "height: " << output->height << "\n";
|
||||
|
||||
while (it != end) {
|
||||
ss << *it << "\n";
|
||||
++it;
|
||||
}
|
||||
|
||||
FASTLED_WARN(ss.str());
|
||||
|
||||
|
||||
MESSAGE("done");
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew generateMap with two turns") {
|
||||
Corkscrew::Input input;
|
||||
input.totalHeight = 10.0f;
|
||||
input.totalAngle = 2 * TWO_PI; // Two full turns
|
||||
input.numLeds = 10; // 10 LEDs around the corkscrew
|
||||
input.offsetCircumference = 0.0f;
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
|
||||
CHECK_EQ(output.width, 5);
|
||||
CHECK_EQ(output.height, 2); // Two vertical segments for two turns
|
||||
CHECK_EQ(output.mapping.size(), 10); // 5 width * 2 height
|
||||
|
||||
// Check first pixel for correctness (basic integrity)
|
||||
CHECK_GE(output.mapping[0].x, 0.0f);
|
||||
CHECK_LE(output.mapping[0].x, 5.0f);
|
||||
CHECK_GE(output.mapping[0].y, 0.0f);
|
||||
CHECK_LE(output.mapping[0].y, 2.0f); // 2 vertical segments for 4π angle
|
||||
}
|
||||
|
||||
TEST_CASE("Corkscrew circumference test") {
|
||||
Corkscrew::Input input;
|
||||
// Use defaults: totalHeight = 100, totalAngle = 19 * 2 * PI
|
||||
input.totalHeight = 23.25f; // Total height of the corkscrew in centimeters
|
||||
input.totalAngle = 19.0f * TWO_PI; // Default to 19 turns
|
||||
input.offsetCircumference = 0.0f; // No offset
|
||||
input.numLeds = 288; // Default to dense 144 LEDs times two strips
|
||||
|
||||
Corkscrew::Output output = Corkscrew::generateMap(input);
|
||||
|
||||
// Basic sanity checks
|
||||
CHECK_EQ(output.width, 16);
|
||||
CHECK_EQ(output.height, 19);
|
||||
CHECK_EQ(output.mapping.size(), 288);
|
||||
|
||||
// Check that circumference matches calculated value
|
||||
// float expectedCircumference = 100.0f / 19.0f;
|
||||
// CHECK_CLOSE(output.circumference, expectedCircumference, 0.01f);
|
||||
}
|
||||
20
libraries/FastLED/tests/test_dbg.cpp
Normal file
20
libraries/FastLED/tests/test_dbg.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/dbg.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("fastled_file_offset") {
|
||||
const char* src = "src/fl/dbg.h";
|
||||
const char* result = fastled_file_offset(src);
|
||||
CHECK(strcmp(result, "src/fl/dbg.h") == 0);
|
||||
const char* src2 = "blah/blah/blah.h";
|
||||
const char* result2 = fastled_file_offset(src2);
|
||||
CHECK(strcmp(result2, "blah.h") == 0);
|
||||
}
|
||||
127
libraries/FastLED/tests/test_downscale.cpp
Normal file
127
libraries/FastLED/tests/test_downscale.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/downscale.h"
|
||||
#include "fl/dbg.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("downscale 2x2 to 1x1") {
|
||||
|
||||
CRGB red = CRGB(255, 0, 0);
|
||||
CRGB black = CRGB(0, 0, 0);
|
||||
|
||||
// We are going to simulate a 4x4 image with a 2x2 image. The source
|
||||
// image is square-cartesian while the dst image is square-serpentine.
|
||||
CRGB src[4] = {black, red, black, red};
|
||||
|
||||
SUBCASE("downscaleHalf from 2x2 to 1x1") {
|
||||
CRGB dst[1];
|
||||
downscaleHalf(src, 2, 2, dst);
|
||||
INFO("Src: " << src);
|
||||
INFO("Dst: " << dst);
|
||||
CHECK(dst[0].r == 128);
|
||||
CHECK(dst[0].g == 0);
|
||||
CHECK(dst[0].b == 0);
|
||||
}
|
||||
|
||||
SUBCASE("downscale from 2x2 to 1x1") {
|
||||
CRGB dst[1];
|
||||
XYMap srcMap = XYMap::constructRectangularGrid(2, 2);
|
||||
XYMap dstMap = XYMap::constructRectangularGrid(1, 1);
|
||||
|
||||
downscale(src, srcMap, dst, dstMap);
|
||||
INFO("Src: " << src);
|
||||
INFO("Dst: " << dst);
|
||||
CHECK(dst[0].r == 128);
|
||||
CHECK(dst[0].g == 0);
|
||||
CHECK(dst[0].b == 0);
|
||||
}
|
||||
|
||||
|
||||
SUBCASE("4x4 rectangle to 2x2 serpentine") {
|
||||
// We are going to simulate a 4x4 image with a 2x2 image. The source
|
||||
// image is square-cartesian while the dst image is square-serpentine.
|
||||
|
||||
CRGB src[16] = {// Upper left red, lower right red, upper right black,
|
||||
// lower left black
|
||||
red, red, black, black, red, red, black, black,
|
||||
black, black, red, red, black, black, red, red};
|
||||
|
||||
CRGB dst[4];
|
||||
|
||||
XYMap srcMap = XYMap::constructRectangularGrid(4, 4);
|
||||
XYMap dstMap = XYMap::constructSerpentine(2, 2);
|
||||
|
||||
downscale(src, srcMap, dst, dstMap);
|
||||
INFO("Src: " << src);
|
||||
INFO("Dst: " << dst);
|
||||
|
||||
CRGB lowerLeft = dst[dstMap.mapToIndex(0, 0)];
|
||||
CRGB lowerRight = dst[dstMap.mapToIndex(1, 0)];
|
||||
CRGB upperLeft = dst[dstMap.mapToIndex(0, 1)];
|
||||
CRGB upperRight = dst[dstMap.mapToIndex(1, 1)];
|
||||
|
||||
REQUIRE(lowerLeft == red);
|
||||
REQUIRE(lowerRight == black);
|
||||
REQUIRE(upperLeft == black);
|
||||
REQUIRE(upperRight == red);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("downscale 3x3 to 2x2") {
|
||||
CRGB red = CRGB(255, 0, 0);
|
||||
CRGB black = CRGB(0, 0, 0);
|
||||
|
||||
// Create a 3x3 checkerboard pattern:
|
||||
CRGB src[9];
|
||||
src[0] = red;
|
||||
src[1] = black;
|
||||
src[2] = red;
|
||||
src[3] = black;
|
||||
src[4] = red;
|
||||
src[5] = black;
|
||||
src[6] = red;
|
||||
src[7] = black;
|
||||
src[8] = red;
|
||||
|
||||
CRGB dst[4]; // 2x2 result
|
||||
|
||||
XYMap srcMap = XYMap::constructRectangularGrid(3, 3);
|
||||
XYMap dstMap = XYMap::constructRectangularGrid(2, 2);
|
||||
|
||||
downscale(src, srcMap, dst, dstMap);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
INFO("Dst[" << i << "]: " << dst[i]);
|
||||
CHECK(dst[i] == CRGB(142, 0, 0)); // Averaged color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("downscale 11x11 to 2x2") {
|
||||
CRGB red = CRGB(255, 0, 0);
|
||||
CRGB black = CRGB(0, 0, 0);
|
||||
|
||||
// Create a 3x3 checkerboard pattern:
|
||||
CRGB src[11*11];
|
||||
for (int i = 0; i < 11*11; ++i) {
|
||||
src[i] = (i % 2 == 0) ? red : black;
|
||||
}
|
||||
|
||||
CRGB dst[4]; // 2x2 result
|
||||
|
||||
XYMap srcMap = XYMap::constructRectangularGrid(11, 11);
|
||||
XYMap dstMap = XYMap::constructRectangularGrid(2, 2);
|
||||
|
||||
downscale(src, srcMap, dst, dstMap);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
INFO("Dst[" << i << "]: " << dst[i]);
|
||||
CHECK(dst[i] == CRGB(129, 0, 0)); // Averaged color
|
||||
}
|
||||
}
|
||||
21
libraries/FastLED/tests/test_fastled.cpp
Normal file
21
libraries/FastLED/tests/test_fastled.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "FastLED.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
#define NUM_LEDS 1000
|
||||
#define DATA_PIN 2
|
||||
#define CLOCK_PIN 3
|
||||
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
TEST_CASE("Simple") {
|
||||
FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, BGR>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
142
libraries/FastLED/tests/test_fft.cpp
Normal file
142
libraries/FastLED/tests/test_fft.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/fft.h"
|
||||
#include "fl/fft_impl.h"
|
||||
#include "fl/math.h"
|
||||
|
||||
// // Proof of concept FFTImpl using KISS FFTImpl. Right now this is fixed sized blocks
|
||||
// of 512. But this is
|
||||
// // intended to change with a C++ wrapper around ot/
|
||||
// typedef int16_t fft_audio_buffer_t[512];
|
||||
|
||||
// void fft_init(); // Explicit initialization of FFTImpl, otherwise it will be
|
||||
// initialized on first run. bool fft_is_initialized(); void fft_unit_test(const
|
||||
// fft_audio_buffer_t &buffer);
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
TEST_CASE("fft tester 512") {
|
||||
int16_t buffer[512] = {0};
|
||||
const int n = 512;
|
||||
// fill in with a sine wave
|
||||
for (int i = 0; i < n; ++i) {
|
||||
float rot = fl::map_range<float, float>(i, 0, n - 1, 0, 2 * PI * 10);
|
||||
float sin_x = sin(rot);
|
||||
buffer[i] = int16_t(32767 * sin_x);
|
||||
}
|
||||
FFTBins out(16);
|
||||
// fft_unit_test(buffer, &out);
|
||||
const int samples = n;
|
||||
FFTImpl fft(samples);
|
||||
fft.run(buffer, &out);
|
||||
|
||||
FASTLED_WARN("FFTImpl output raw bins: " << out.bins_raw);
|
||||
FASTLED_WARN("FFTImpl output db bins: " << out.bins_db);
|
||||
const float expected_output[16] = {
|
||||
3, 2, 2, 6, 6.08, 15.03, 3078.22, 4346.29,
|
||||
4033.16, 3109, 38.05, 4.47, 4, 2, 1.41, 1.41};
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
// CHECK(out[i] == Approx(expected_output[i]).epsilon(0.1));
|
||||
float a = out.bins_raw[i];
|
||||
float b = expected_output[i];
|
||||
bool almost_equal = ALMOST_EQUAL(a, b, 0.1);
|
||||
if (!almost_equal) {
|
||||
FASTLED_WARN("FFTImpl output mismatch at index " << i << ": " << a
|
||||
<< " != " << b);
|
||||
}
|
||||
CHECK(almost_equal);
|
||||
}
|
||||
|
||||
fl::Str info = fft.info();
|
||||
FASTLED_WARN("FFTImpl info: " << info);
|
||||
FASTLED_WARN("Done");
|
||||
}
|
||||
|
||||
TEST_CASE("fft tester 256") {
|
||||
// fft_audio_buffer_t buffer = {0};
|
||||
fl::vector<int16_t> buffer;
|
||||
const int n = 256;
|
||||
// fill in with a sine wave
|
||||
for (int i = 0; i < n; ++i) {
|
||||
float rot = fl::map_range<float, float>(i, 0, n - 1, 0, 2 * PI * 10);
|
||||
float sin_x = sin(rot);
|
||||
auto v = int16_t(32767 * sin_x);
|
||||
buffer.push_back(v);
|
||||
}
|
||||
FFTBins out(16);
|
||||
// fft_unit_test(buffer, &out);
|
||||
const int samples = n;
|
||||
FFTImpl fft(samples);
|
||||
fft.run(buffer, &out);
|
||||
|
||||
FASTLED_WARN("FFTImpl output: " << out);
|
||||
const float expected_output[16] = {
|
||||
3, 2, 4, 5, 5.10, 9.06, 11.05, 27.66,
|
||||
2779.93, 3811.66, 4176.58, 4185.02, 4174.50, 4017.63, 3638.46, 3327.60};
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
// CHECK(out[i] == Approx(expected_output[i]).epsilon(0.1));
|
||||
float a = out.bins_raw[i];
|
||||
float b = expected_output[i];
|
||||
bool almost_equal = ALMOST_EQUAL(a, b, 0.1);
|
||||
if (!almost_equal) {
|
||||
FASTLED_WARN("FFTImpl output mismatch at index " << i << ": " << a
|
||||
<< " != " << b);
|
||||
}
|
||||
CHECK(almost_equal);
|
||||
}
|
||||
|
||||
fl::Str info = fft.info();
|
||||
FASTLED_WARN("FFTImpl info: " << info);
|
||||
FASTLED_WARN("Done");
|
||||
}
|
||||
|
||||
TEST_CASE("fft tester 256 with 64 bands") {
|
||||
// fft_audio_buffer_t buffer = {0};
|
||||
fl::vector<int16_t> buffer;
|
||||
const int n = 256;
|
||||
// fill in with a sine wave
|
||||
for (int i = 0; i < n; ++i) {
|
||||
float rot = fl::map_range<float, float>(i, 0, n - 1, 0, 2 * PI * 10);
|
||||
float sin_x = sin(rot);
|
||||
auto v = int16_t(32767 * sin_x);
|
||||
buffer.push_back(v);
|
||||
}
|
||||
FFTBins out(64);
|
||||
// fft_unit_test(buffer, &out);
|
||||
const int samples = n;
|
||||
FFT_Args args(samples, 64);
|
||||
FFTImpl fft(args);
|
||||
fft.run(buffer, &out);
|
||||
FASTLED_WARN("FFTImpl output: " << out);
|
||||
const float expected_output[64] = {
|
||||
3, 3, 1, 2, 2, 3, 3,
|
||||
3, 3, 4, 3, 4, 4, 5,
|
||||
5, 3.16, 4.12, 5.10, 5.10, 6.08, 7,
|
||||
9.06, 9.06, 9.06, 10.20, 11.18, 15.13, 18.25,
|
||||
20.22, 26.31, 30.59, 63.95, 71.85, 2601.78, 2896.46,
|
||||
3281.87, 3473.71, 3678.96, 3876.88, 3960.81, 4023.50, 4203.33,
|
||||
4176.58, 4286.66, 4199.48, 4273.51, 4274.82, 4155.52, 4170.46,
|
||||
4121.19, 4131.86, 4044.44, 4072.49, 4120.38, 3966.60, 4017.84,
|
||||
3815.20, 3815.66, 3964.51, 3628.27, 3599.13, 3863.29, 3823.06,
|
||||
3327.600};
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
// CHECK(out[i] == Approx(expected_output[i]).epsilon(0.1));
|
||||
float a = out.bins_raw[i];
|
||||
float b = expected_output[i];
|
||||
bool almost_equal = ALMOST_EQUAL(a, b, 0.1);
|
||||
if (!almost_equal) {
|
||||
FASTLED_WARN("FFTImpl output mismatch at index " << i << ": " << a
|
||||
<< " != " << b);
|
||||
}
|
||||
CHECK(almost_equal);
|
||||
}
|
||||
fl::Str info = fft.info();
|
||||
FASTLED_WARN("FFTImpl info: " << info);
|
||||
FASTLED_WARN("Done");
|
||||
}
|
||||
97
libraries/FastLED/tests/test_fixed_set.cpp
Normal file
97
libraries/FastLED/tests/test_fixed_set.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// g++ --std=c++11 test_fixed_map.cpp -I../src
|
||||
|
||||
#include "test.h"
|
||||
#include "test.h"
|
||||
#include "fl/set.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
|
||||
TEST_CASE("FixedSet operations") {
|
||||
fl::FixedSet<int, 5> set;
|
||||
|
||||
SUBCASE("Insert and find") {
|
||||
CHECK(set.insert(1));
|
||||
CHECK(set.insert(2));
|
||||
CHECK(set.insert(3));
|
||||
CHECK(set.find(1) != set.end());
|
||||
CHECK(set.find(2) != set.end());
|
||||
CHECK(set.find(3) != set.end());
|
||||
CHECK(set.find(4) == set.end());
|
||||
CHECK_FALSE(set.insert(1)); // Duplicate insert should fail
|
||||
}
|
||||
|
||||
SUBCASE("Erase") {
|
||||
CHECK(set.insert(1));
|
||||
CHECK(set.insert(2));
|
||||
CHECK(set.erase(1));
|
||||
CHECK(set.find(1) == set.end());
|
||||
CHECK(set.find(2) != set.end());
|
||||
CHECK_FALSE(set.erase(3)); // Erasing non-existent element should fail
|
||||
}
|
||||
|
||||
SUBCASE("Next and prev") {
|
||||
CHECK(set.insert(1));
|
||||
CHECK(set.insert(2));
|
||||
CHECK(set.insert(3));
|
||||
|
||||
int next_value;
|
||||
CHECK(set.next(1, &next_value));
|
||||
CHECK(next_value == 2);
|
||||
CHECK(set.next(3, &next_value, true));
|
||||
CHECK(next_value == 1);
|
||||
|
||||
int prev_value;
|
||||
CHECK(set.prev(3, &prev_value));
|
||||
CHECK(prev_value == 2);
|
||||
CHECK(set.prev(1, &prev_value, true));
|
||||
CHECK(prev_value == 3);
|
||||
}
|
||||
|
||||
SUBCASE("Size and capacity") {
|
||||
CHECK(set.size() == 0);
|
||||
CHECK(set.capacity() == 5);
|
||||
CHECK(set.empty());
|
||||
|
||||
set.insert(1);
|
||||
set.insert(2);
|
||||
CHECK(set.size() == 2);
|
||||
CHECK_FALSE(set.empty());
|
||||
|
||||
set.clear();
|
||||
CHECK(set.size() == 0);
|
||||
CHECK(set.empty());
|
||||
}
|
||||
|
||||
SUBCASE("Iterators") {
|
||||
set.insert(1);
|
||||
set.insert(2);
|
||||
set.insert(3);
|
||||
|
||||
int sum = 0;
|
||||
for (const auto& value : set) {
|
||||
sum += value;
|
||||
}
|
||||
CHECK(sum == 6);
|
||||
|
||||
auto it = set.begin();
|
||||
CHECK(*it == 1);
|
||||
++it;
|
||||
CHECK(*it == 2);
|
||||
++it;
|
||||
CHECK(*it == 3);
|
||||
++it;
|
||||
CHECK(it == set.end());
|
||||
}
|
||||
|
||||
SUBCASE("Front and back") {
|
||||
set.insert(1);
|
||||
set.insert(2);
|
||||
set.insert(3);
|
||||
|
||||
CHECK(set.front() == 1);
|
||||
CHECK(set.back() == 3);
|
||||
}
|
||||
|
||||
}
|
||||
52
libraries/FastLED/tests/test_frame.cpp
Normal file
52
libraries/FastLED/tests/test_frame.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/frame.h"
|
||||
#include <cstdlib>
|
||||
#include "fl/allocator.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
namespace {
|
||||
int allocation_count = 0;
|
||||
|
||||
void* custom_malloc(size_t size) {
|
||||
allocation_count++;
|
||||
return std::malloc(size);
|
||||
}
|
||||
|
||||
void custom_free(void* ptr) {
|
||||
allocation_count--;
|
||||
std::free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("test frame custom allocator") {
|
||||
// Set our custom allocator
|
||||
SetPSRamAllocator(custom_malloc, custom_free);
|
||||
|
||||
FramePtr frame = FramePtr::New(100); // 100 pixels.
|
||||
CHECK(allocation_count == 1); // One for RGB.
|
||||
frame.reset();
|
||||
|
||||
// Frame should be destroyed here
|
||||
CHECK(allocation_count == 0);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("test blend by black") {
|
||||
SetPSRamAllocator(custom_malloc, custom_free);
|
||||
FramePtr frame = FramePtr::New(1); // 1 pixels.
|
||||
frame->rgb()[0] = CRGB(255, 0, 0); // Red
|
||||
CRGB out;
|
||||
frame->draw(&out, DRAW_MODE_BLEND_BY_MAX_BRIGHTNESS);
|
||||
CHECK(out == CRGB(255, 0, 0)); // full red because max luma is 255
|
||||
out = CRGB(0, 0, 0);
|
||||
frame->rgb()[0] = CRGB(128, 0, 0); // Red
|
||||
frame->draw(&out, DRAW_MODE_BLEND_BY_MAX_BRIGHTNESS);
|
||||
CHECK(out == CRGB(64, 0, 0));
|
||||
}
|
||||
27
libraries/FastLED/tests/test_frame_tracker.cpp
Normal file
27
libraries/FastLED/tests/test_frame_tracker.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/frame.h"
|
||||
#include "fx/video/frame_tracker.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("FrameTracker basic frame advancement") {
|
||||
FrameTracker tracker(1.0f); // 1fps == 1000ms per frame
|
||||
uint32_t currentFrame, nextFrame;
|
||||
uint8_t amountOfNextFrame;
|
||||
|
||||
//tracker.reset(1000); // start at time 1000ms
|
||||
|
||||
// Shift the time to 500 ms or 50% of the first frame
|
||||
tracker.get_interval_frames(500, ¤tFrame, &nextFrame, &amountOfNextFrame);
|
||||
CHECK(currentFrame == 0);
|
||||
CHECK(nextFrame == 1);
|
||||
CHECK(amountOfNextFrame == 127);
|
||||
}
|
||||
95
libraries/FastLED/tests/test_function.cpp
Normal file
95
libraries/FastLED/tests/test_function.cpp
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
|
||||
#include "fl/function.h"
|
||||
#include "fl/function_list.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
// Free function for testing
|
||||
static int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
int value = 0;
|
||||
void set(int v) { value = v; }
|
||||
int get() const { return value; }
|
||||
};
|
||||
|
||||
struct Mult {
|
||||
int operator()(int a, int b) const { return a * b; }
|
||||
};
|
||||
|
||||
TEST_CASE("function<bool()> is empty by default and bool-convertible") {
|
||||
function<void()> f;
|
||||
REQUIRE(!f);
|
||||
}
|
||||
|
||||
TEST_CASE("Test function with lambda") {
|
||||
function<int(int,int)> f = [](int a, int b) { return a + b; };
|
||||
REQUIRE(f);
|
||||
REQUIRE(f(2, 3) == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Test function with free function pointer") {
|
||||
function<int(int,int)> f(add);
|
||||
REQUIRE(f);
|
||||
REQUIRE(f(4, 6) == 10);
|
||||
}
|
||||
|
||||
TEST_CASE("Test function with functor object") {
|
||||
Mult m;
|
||||
function<int(int,int)> f(m);
|
||||
REQUIRE(f);
|
||||
REQUIRE(f(3, 7) == 21);
|
||||
}
|
||||
|
||||
TEST_CASE("Test function with non-const member function") {
|
||||
Foo foo;
|
||||
function<void(int)> fset(&Foo::set, &foo);
|
||||
REQUIRE(fset);
|
||||
fset(42);
|
||||
REQUIRE(foo.value == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Test function with const member function") {
|
||||
Foo foo;
|
||||
foo.value = 99;
|
||||
function<int()> fget(&Foo::get, &foo);
|
||||
REQUIRE(fget);
|
||||
REQUIRE(fget() == 99);
|
||||
}
|
||||
|
||||
TEST_CASE("Void free function test") {
|
||||
function<void(float)> f = [](float) { /* do nothing */ };
|
||||
REQUIRE(f);
|
||||
f(1);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Copy and move semantics") {
|
||||
function<int(int,int)> orig = [](int a, int b) { return a - b; };
|
||||
REQUIRE(orig(10, 4) == 6);
|
||||
|
||||
// Copy
|
||||
function<int(int,int)> copy = orig;
|
||||
REQUIRE(copy);
|
||||
REQUIRE(copy(8, 3) == 5);
|
||||
|
||||
// Move
|
||||
function<int(int,int)> moved = std::move(orig);
|
||||
REQUIRE(moved);
|
||||
REQUIRE(moved(7, 2) == 5);
|
||||
REQUIRE(!orig);
|
||||
}
|
||||
|
||||
TEST_CASE("Function list void float") {
|
||||
FunctionList<float> fl;
|
||||
fl.add([](float) { /* do nothing */ });
|
||||
fl.invoke(1);
|
||||
}
|
||||
32
libraries/FastLED/tests/test_fx.cpp
Normal file
32
libraries/FastLED/tests/test_fx.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/1d/cylon.h"
|
||||
#include "fx/1d/demoreel100.h"
|
||||
#include "fx/1d/noisewave.h"
|
||||
#include "fx/1d/pacifica.h"
|
||||
#include "fx/1d/pride2015.h" // needs XY defined or linker error.
|
||||
#include "fx/1d/twinklefox.h"
|
||||
#include "fx/2d/animartrix.hpp"
|
||||
#include "fx/2d/noisepalette.h"
|
||||
#include "fx/2d/scale_up.h"
|
||||
#include "fx/2d/redsquare.h"
|
||||
#include "fx/video.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
uint16_t XY(uint8_t, uint8_t); // declaration to fix compiler warning.
|
||||
|
||||
// To satisfy the linker, we must also define uint16_t XY( uint8_t, uint8_t);
|
||||
// This should go away someday and only use functions supplied by the user.
|
||||
uint16_t XY(uint8_t, uint8_t) { return 0; }
|
||||
|
||||
TEST_CASE("Compile Test") {
|
||||
// Suceessful compilation test
|
||||
}
|
||||
186
libraries/FastLED/tests/test_fx2d_blend.cpp
Normal file
186
libraries/FastLED/tests/test_fx2d_blend.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fx/2d/blend.h"
|
||||
#include "fx/fx.h"
|
||||
#include "fx/time.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
using std::cout;
|
||||
|
||||
// Simple test effect that fills with a solid color
|
||||
class SolidColorFx2d : public fl::Fx2d {
|
||||
public:
|
||||
SolidColorFx2d(uint16_t width, uint16_t height, CRGB color)
|
||||
: fl::Fx2d(fl::XYMap::constructRectangularGrid(width, height)),
|
||||
mColor(color) {}
|
||||
|
||||
fl::Str fxName() const override { return "SolidColorFx2d"; }
|
||||
|
||||
void draw(Fx::DrawContext context) override {
|
||||
for (uint16_t i = 0; i < mXyMap.getTotal(); i++) {
|
||||
context.leds[i] = mColor;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CRGB mColor;
|
||||
};
|
||||
|
||||
class TestFx2D : public fl::Fx2d {
|
||||
public:
|
||||
TestFx2D(uint16_t width, uint16_t height)
|
||||
: fl::Fx2d(fl::XYMap::constructRectangularGrid(width, height)) {
|
||||
mLeds.reset(new CRGB[width * height]);
|
||||
}
|
||||
|
||||
void set(uint16_t x, uint16_t y, CRGB color) {
|
||||
if (x < mXyMap.getWidth() && y < mXyMap.getHeight()) {
|
||||
uint16_t index = mXyMap(x, y);
|
||||
if (index < mXyMap.getTotal()) {
|
||||
mLeds[index] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fl::Str fxName() const override { return "TestFx2D"; }
|
||||
|
||||
void draw(Fx::DrawContext context) override {
|
||||
for (uint16_t i = 0; i < mXyMap.getTotal(); i++) {
|
||||
context.leds[i] = mLeds[i];
|
||||
}
|
||||
}
|
||||
|
||||
scoped_array<CRGB> mLeds;
|
||||
};
|
||||
|
||||
TEST_CASE("Test FX2d Layered Blending") {
|
||||
const uint16_t width = 1;
|
||||
const uint16_t height = 1;
|
||||
XYMap xyMap = XYMap::constructRectangularGrid(width, height);
|
||||
|
||||
// Create a red layer
|
||||
SolidColorFx2d redLayer(width, height, CRGB(255, 0, 0));
|
||||
|
||||
// Create a layered effect with just the red layer
|
||||
fl::Blend2d blendFx(xyMap);
|
||||
blendFx.add(redLayer);
|
||||
|
||||
// Create a buffer for the output
|
||||
CRGB led;
|
||||
|
||||
// Draw the layered effect
|
||||
Fx::DrawContext context(0, &led);
|
||||
context.now = 0;
|
||||
blendFx.draw(context);
|
||||
|
||||
// Verify the result - should be red
|
||||
CHECK(led.r == 255);
|
||||
CHECK(led.g == 0);
|
||||
CHECK(led.b == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Test FX2d Layered with XYMap") {
|
||||
enum {
|
||||
width = 2,
|
||||
height = 2,
|
||||
};
|
||||
|
||||
XYMap xyMapSerp = XYMap::constructSerpentine(width, height);
|
||||
XYMap xyRect = XYMap::constructRectangularGrid(width, height);
|
||||
|
||||
SUBCASE("Rectangular Grid") {
|
||||
// Create a blue layer
|
||||
// SolidColorFx2d blueLayer(width, height, CRGB(0, 0, 255));
|
||||
TestFx2D testFx(width, height);
|
||||
testFx.set(0, 0, CRGB(0, 0, 255)); // Set the first pixel to blue
|
||||
testFx.set(1, 0, CRGB(255, 0, 0)); // Set the second pixel to red
|
||||
testFx.set(0, 1, CRGB(0, 255, 0)); // Set the third pixel to gree
|
||||
testFx.set(1, 1, CRGB(0, 0, 0)); // Set the fourth pixel to black
|
||||
|
||||
// Create a layered effect with just the blue layer
|
||||
fl::Blend2d blendFx(xyRect);
|
||||
blendFx.add(testFx);
|
||||
|
||||
// Create a buffer for the output
|
||||
CRGB led[width * height] = {};
|
||||
|
||||
// Draw the layered effect
|
||||
Fx::DrawContext context(0, led);
|
||||
context.now = 0;
|
||||
blendFx.draw(context);
|
||||
|
||||
cout << "Layered Effect Output: " << led[0].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[1].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[2].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[3].toString().c_str() << std::endl;
|
||||
|
||||
// Verify the result - should be blue
|
||||
CHECK(led[0].r == 0);
|
||||
CHECK(led[0].g == 0);
|
||||
CHECK(led[0].b == 255);
|
||||
|
||||
CHECK(led[1].r == 255);
|
||||
CHECK(led[1].g == 0);
|
||||
CHECK(led[1].b == 0);
|
||||
|
||||
CHECK(led[2].r == 0);
|
||||
CHECK(led[2].g == 255);
|
||||
CHECK(led[2].b == 0);
|
||||
|
||||
CHECK(led[3].r == 0);
|
||||
CHECK(led[3].g == 0);
|
||||
CHECK(led[3].b == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Serpentine") {
|
||||
// Create a blue layer
|
||||
TestFx2D testFx(width, height);
|
||||
testFx.set(0, 0, CRGB(0, 0, 255)); // Set the first pixel to blue
|
||||
testFx.set(1, 0, CRGB(255, 0, 0)); // Set the second pixel to red
|
||||
testFx.set(0, 1, CRGB(0, 255, 0)); // Set the third pixel to gree
|
||||
testFx.set(1, 1, CRGB(0, 0, 0)); // Set the fourth pixel to black
|
||||
|
||||
// Create a layered effect with just the blue layer
|
||||
fl::Blend2d blendFx(xyMapSerp);
|
||||
blendFx.add(testFx);
|
||||
|
||||
// Create a buffer for the output
|
||||
CRGB led[width * height] = {};
|
||||
|
||||
// Draw the layered effect
|
||||
Fx::DrawContext context(0, led);
|
||||
context.now = 0;
|
||||
blendFx.draw(context);
|
||||
|
||||
cout << "Layered Effect Output: " << led[0].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[1].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[2].toString().c_str() << std::endl;
|
||||
cout << "Layered Effect Output: " << led[3].toString().c_str() << std::endl;
|
||||
|
||||
// Verify the result - should be blue
|
||||
CHECK(led[0].r == 0);
|
||||
CHECK(led[0].g == 0);
|
||||
CHECK(led[0].b == 255);
|
||||
|
||||
CHECK(led[1].r == 255);
|
||||
CHECK(led[1].g == 0);
|
||||
CHECK(led[1].b == 0);
|
||||
|
||||
// Now it's supposed to go up to the next line at the same column.
|
||||
CHECK(led[2].r == 0);
|
||||
CHECK(led[2].g == 0);
|
||||
CHECK(led[2].b == 0);
|
||||
|
||||
CHECK(led[3].r == 0);
|
||||
CHECK(led[3].g == 255);
|
||||
CHECK(led[3].b == 0);
|
||||
}
|
||||
}
|
||||
210
libraries/FastLED/tests/test_fx_engine.cpp
Normal file
210
libraries/FastLED/tests/test_fx_engine.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/fx.h"
|
||||
#include "fx/fx_engine.h"
|
||||
#include "fx/fx2d.h"
|
||||
#include "fl/vector.h"
|
||||
#include "FastLED.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
FASTLED_SMART_PTR(MockFx);
|
||||
|
||||
class MockFx : public Fx {
|
||||
public:
|
||||
MockFx(uint16_t numLeds, CRGB color) : Fx(numLeds), mColor(color) {}
|
||||
|
||||
void draw(DrawContext ctx) override {
|
||||
mLastDrawTime = ctx.now;
|
||||
for (uint16_t i = 0; i < mNumLeds; ++i) {
|
||||
ctx.leds[i] = mColor;
|
||||
}
|
||||
}
|
||||
|
||||
Str fxName() const override { return "MockFx"; }
|
||||
|
||||
private:
|
||||
CRGB mColor;
|
||||
uint32_t mLastDrawTime = 0;
|
||||
};
|
||||
|
||||
TEST_CASE("test_fx_engine") {
|
||||
constexpr uint16_t NUM_LEDS = 10;
|
||||
FxEngine engine(NUM_LEDS, false);
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
Ptr<MockFx> redFx = MockFxPtr::New(NUM_LEDS, CRGB::Red);
|
||||
Ptr<MockFx> blueFx = MockFxPtr::New(NUM_LEDS, CRGB::Blue);
|
||||
|
||||
int id0 = engine.addFx(redFx);
|
||||
int id1 = engine.addFx(blueFx);
|
||||
|
||||
REQUIRE_EQ(0, id0);
|
||||
REQUIRE_EQ(1, id1);
|
||||
|
||||
SUBCASE("Initial state") {
|
||||
int currId = engine.getCurrentFxId();
|
||||
CHECK(currId == id0);
|
||||
const bool ok = engine.draw(0, leds);
|
||||
CHECK(ok);
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
// CHECK(leds[i] == CRGB::Red);
|
||||
bool is_red = leds[i] == CRGB::Red;
|
||||
if (!is_red) {
|
||||
Str err = leds[i].toString();
|
||||
printf("leds[%d] is not red, was instead: %s\n", i, err.c_str());
|
||||
CHECK(is_red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SUBCASE("Transition") {
|
||||
bool ok = engine.nextFx(1000);
|
||||
if (!ok) {
|
||||
auto& effects = engine._getEffects();
|
||||
for (auto it = effects.begin(); it != effects.end(); ++it) {
|
||||
auto& fx = it->second;
|
||||
printf("fx: %s\n", fx->fxName().c_str());
|
||||
}
|
||||
FAIL("Failed to transition to next effect");
|
||||
}
|
||||
REQUIRE(ok);
|
||||
|
||||
// Start of transition
|
||||
ok = engine.draw(0, leds);
|
||||
REQUIRE(ok);
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
REQUIRE(leds[i] == CRGB::Red);
|
||||
}
|
||||
|
||||
// Middle of transition
|
||||
REQUIRE(engine.draw(500, leds));
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
REQUIRE(leds[i].r == 128);
|
||||
REQUIRE(leds[i].g == 0);
|
||||
REQUIRE(leds[i].b == 127);
|
||||
}
|
||||
|
||||
// End of transition
|
||||
REQUIRE(engine.draw(1000, leds));
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
CHECK(leds[i] == CRGB::Blue);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Transition with 0 time duration") {
|
||||
engine.nextFx(0);
|
||||
engine.draw(0, leds);
|
||||
for (uint16_t i = 0; i < NUM_LEDS; ++i) {
|
||||
CHECK(leds[i] == CRGB::Blue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("test_transition") {
|
||||
|
||||
SUBCASE("Initial state") {
|
||||
Transition transition;
|
||||
CHECK(transition.getProgress(0) == 0);
|
||||
CHECK_FALSE(transition.isTransitioning(0));
|
||||
}
|
||||
|
||||
SUBCASE("Start transition") {
|
||||
Transition transition;
|
||||
transition.start(100, 1000);
|
||||
CHECK(transition.isTransitioning(100));
|
||||
CHECK(transition.isTransitioning(1099));
|
||||
CHECK_FALSE(transition.isTransitioning(1100));
|
||||
}
|
||||
|
||||
SUBCASE("Progress calculation") {
|
||||
Transition transition;
|
||||
transition.start(100, 1000);
|
||||
CHECK(transition.getProgress(100) == 0);
|
||||
CHECK(transition.getProgress(600) == 127);
|
||||
CHECK(transition.getProgress(1100) == 255);
|
||||
}
|
||||
|
||||
SUBCASE("Progress before start time") {
|
||||
Transition transition;
|
||||
transition.start(100, 1000);
|
||||
CHECK(transition.getProgress(50) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Progress after end time") {
|
||||
Transition transition;
|
||||
transition.start(100, 1000);
|
||||
CHECK(transition.getProgress(1200) == 255);
|
||||
}
|
||||
|
||||
SUBCASE("Multiple transitions") {
|
||||
Transition transition;
|
||||
transition.start(100, 1000);
|
||||
CHECK(transition.isTransitioning(600));
|
||||
|
||||
transition.start(2000, 500);
|
||||
CHECK_FALSE(transition.isTransitioning(1500));
|
||||
CHECK(transition.isTransitioning(2200));
|
||||
CHECK(transition.getProgress(2250) == 127);
|
||||
}
|
||||
|
||||
SUBCASE("Zero duration transition") {
|
||||
Transition transition;
|
||||
transition.start(100, 0);
|
||||
CHECK_FALSE(transition.isTransitioning(100));
|
||||
CHECK(transition.getProgress(99) == 0);
|
||||
CHECK(transition.getProgress(100) == 255);
|
||||
CHECK(transition.getProgress(101) == 255);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Simple Fx2d object which writes a single red pixel to the first LED
|
||||
// with the red component being the intensity of the frame counter.
|
||||
class Fake2d : public Fx2d {
|
||||
public:
|
||||
Fake2d() : Fx2d(XYMap::constructRectangularGrid(1,1)) {}
|
||||
|
||||
void draw(DrawContext context) override {
|
||||
CRGB c = mColors[mFrameCounter % mColors.size()];
|
||||
context.leds[0] = c;
|
||||
mFrameCounter++;
|
||||
}
|
||||
|
||||
bool hasFixedFrameRate(float *fps) const override {
|
||||
*fps = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
Str fxName() const override { return "Fake2d"; }
|
||||
uint8_t mFrameCounter = 0;
|
||||
FixedVector<CRGB, 5> mColors;
|
||||
};
|
||||
|
||||
TEST_CASE("test_fixed_fps") {
|
||||
Fake2d fake;
|
||||
fake.mColors.push_back(CRGB(0, 0, 0));
|
||||
fake.mColors.push_back(CRGB(255, 0, 0));
|
||||
|
||||
CRGB leds[1];
|
||||
bool interpolate = true;
|
||||
FxEngine engine(1, interpolate);
|
||||
int id = engine.addFx(fake);
|
||||
CHECK_EQ(0, id);
|
||||
engine.draw(0, &leds[0]);
|
||||
CHECK_EQ(1, fake.mFrameCounter);
|
||||
CHECK_EQ(leds[0], CRGB(0, 0, 0));
|
||||
engine.draw(500, &leds[0]);
|
||||
CHECK_EQ(2, fake.mFrameCounter);
|
||||
CHECK_EQ(leds[0], CRGB(127, 0, 0));
|
||||
}
|
||||
60
libraries/FastLED/tests/test_fx_time.cpp
Normal file
60
libraries/FastLED/tests/test_fx_time.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/time.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("TimeWarp basic functionality") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
|
||||
SUBCASE("Initialization and normal time progression") {
|
||||
TimeWarp tw(1000, 1.0f); // 1000 ms is the start time, speed is set at 1x
|
||||
CHECK(tw.time() == 0);
|
||||
CHECK(tw.scale() == 1.0f);
|
||||
|
||||
tw.update(2000);
|
||||
CHECK(tw.time() == 1000);
|
||||
}
|
||||
|
||||
SUBCASE("Time scaling") {
|
||||
TimeWarp tw(1000);
|
||||
tw.setSpeed(2.0f); // now we are at 2x speed.
|
||||
CHECK(tw.time() == 0); // at t0 = 1000ms
|
||||
|
||||
tw.update(1500); // we give 500 at 2x => add 1000 to time counter.
|
||||
CHECK(tw.time() == 1000);
|
||||
|
||||
tw.setSpeed(0.5f); // Set half speed: 500ms.
|
||||
CHECK(tw.scale() == 0.5f);
|
||||
|
||||
tw.update(2500);
|
||||
CHECK(tw.time() == 1500);
|
||||
}
|
||||
|
||||
SUBCASE("Reset functionality") {
|
||||
TimeWarp tw(1000, 1.0f);
|
||||
tw.update(2000);
|
||||
CHECK(tw.time() == 1000);
|
||||
|
||||
tw.reset(3000);
|
||||
CHECK(tw.time() == 0);
|
||||
|
||||
tw.update(4000);
|
||||
CHECK(tw.time() == 1000);
|
||||
}
|
||||
|
||||
SUBCASE("Wrap-around protection - prevent from going below start time") {
|
||||
TimeWarp tw(1000, 1.0f);
|
||||
tw.update(1001);
|
||||
CHECK(tw.time() == 1);
|
||||
tw.setSpeed(-1.0f);
|
||||
tw.update(2000);
|
||||
CHECK_EQ(tw.time(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
24
libraries/FastLED/tests/test_grid.cpp
Normal file
24
libraries/FastLED/tests/test_grid.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/grid.h"
|
||||
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Grid_int16_t") {
|
||||
Grid<int16_t> grid(2, 2);
|
||||
REQUIRE_EQ(grid.width(), 2);
|
||||
REQUIRE_EQ(grid.height(), 2);
|
||||
auto min_max = grid.minMax();
|
||||
|
||||
REQUIRE_EQ(min_max.x, 0);
|
||||
REQUIRE_EQ(min_max.y, 0);
|
||||
|
||||
grid.at(0, 0) = 32767;
|
||||
REQUIRE_EQ(32767, grid.at(0, 0));
|
||||
}
|
||||
300
libraries/FastLED/tests/test_hashmap.cpp
Normal file
300
libraries/FastLED/tests/test_hashmap.cpp
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// test.cpp
|
||||
// g++ --std=c++11 test.cpp -o test && ./test
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "fl/hash_map.h"
|
||||
#include "fl/str.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Empty map properties") {
|
||||
HashMap<int, int> m;
|
||||
REQUIRE_EQ(m.size(), 0u);
|
||||
REQUIRE(!m.find_value(42));
|
||||
// begin()==end() on empty
|
||||
REQUIRE(m.begin() == m.end());
|
||||
}
|
||||
|
||||
TEST_CASE("Single insert, lookup & operator[]") {
|
||||
HashMap<int, int> m;
|
||||
m.insert(10, 20);
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
auto *v = m.find_value(10);
|
||||
REQUIRE(v);
|
||||
REQUIRE_EQ(*v, 20);
|
||||
|
||||
// operator[] default-construct & assignment
|
||||
HashMap<int, Str> ms;
|
||||
auto &ref = ms[5];
|
||||
REQUIRE(ref.empty()); // default-constructed
|
||||
REQUIRE_EQ(ms.size(), 1u);
|
||||
ref = "hello";
|
||||
REQUIRE_EQ(*ms.find_value(5), "hello");
|
||||
|
||||
// operator[] overwrite existing
|
||||
ms[5] = "world";
|
||||
REQUIRE_EQ(ms.size(), 1u);
|
||||
REQUIRE_EQ(*ms.find_value(5), "world");
|
||||
}
|
||||
|
||||
TEST_CASE("Insert duplicate key overwrites without growing") {
|
||||
HashMap<int, Str> m;
|
||||
m.insert(1, "foo");
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
REQUIRE_EQ(*m.find_value(1), "foo");
|
||||
|
||||
m.insert(1, "bar");
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
REQUIRE_EQ(*m.find_value(1), "bar");
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple distinct inserts & lookups") {
|
||||
HashMap<char, int> m;
|
||||
int count = 0;
|
||||
for (char c = 'a'; c < 'a' + 10; ++c) {
|
||||
MESSAGE("insert " << count++);
|
||||
m.insert(c, c - 'a');
|
||||
}
|
||||
REQUIRE_EQ(m.size(), 10u);
|
||||
for (char c = 'a'; c < 'a' + 10; ++c) {
|
||||
auto *v = m.find_value(c);
|
||||
REQUIRE(v);
|
||||
REQUIRE_EQ(*v, static_cast<int>(c - 'a'));
|
||||
}
|
||||
REQUIRE(!m.find_value('z'));
|
||||
}
|
||||
|
||||
TEST_CASE("Erase and remove behavior") {
|
||||
HashMap<int, int> m;
|
||||
m.insert(5, 55);
|
||||
m.insert(6, 66);
|
||||
REQUIRE_EQ(m.size(), 2u);
|
||||
|
||||
// erase existing
|
||||
REQUIRE(m.erase(5));
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
REQUIRE(!m.find_value(5));
|
||||
|
||||
// erase non-existent
|
||||
REQUIRE(!m.erase(5));
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
|
||||
REQUIRE(m.erase(6));
|
||||
REQUIRE_EQ(m.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_CASE("Re-insert after erase reuses slot") {
|
||||
HashMap<int, int> m(4);
|
||||
m.insert(1, 10);
|
||||
REQUIRE(m.erase(1));
|
||||
REQUIRE(!m.find_value(1));
|
||||
REQUIRE_EQ(m.size(), 0u);
|
||||
|
||||
m.insert(1, 20);
|
||||
REQUIRE(m.find_value(1));
|
||||
REQUIRE_EQ(*m.find_value(1), 20);
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
}
|
||||
|
||||
TEST_CASE("Clear resets map and allows fresh inserts") {
|
||||
HashMap<int, int> m(4);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
m.insert(i, i);
|
||||
m.remove(1);
|
||||
REQUIRE_EQ(m.size(), 2u);
|
||||
|
||||
m.clear();
|
||||
REQUIRE_EQ(m.size(), 0u);
|
||||
REQUIRE(!m.find_value(0));
|
||||
REQUIRE(!m.find_value(1));
|
||||
REQUIRE(!m.find_value(2));
|
||||
|
||||
m.insert(5, 50);
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
REQUIRE_EQ(*m.find_value(5), 50);
|
||||
}
|
||||
|
||||
TEST_CASE("Stress collisions & rehash with small initial capacity") {
|
||||
HashMap<int, int> m(1 /*capacity*/);
|
||||
const int N = 100;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
m.insert(i, i * 3);
|
||||
// test that size is increasing
|
||||
REQUIRE_EQ(m.size(), static_cast<std::size_t>(i + 1));
|
||||
}
|
||||
REQUIRE_EQ(m.size(), static_cast<std::size_t>(N));
|
||||
for (int i = 0; i < N; ++i) {
|
||||
auto *v = m.find_value(i);
|
||||
REQUIRE(v);
|
||||
REQUIRE_EQ(*v, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Iterator round-trip and const-iteration") {
|
||||
HashMap<int, int> m;
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
m.insert(i, i + 100);
|
||||
}
|
||||
|
||||
// non-const iteration
|
||||
std::size_t count = 0;
|
||||
for (auto kv : m) {
|
||||
REQUIRE_EQ(kv.second, kv.first + 100);
|
||||
++count;
|
||||
}
|
||||
REQUIRE_EQ(count, m.size());
|
||||
|
||||
// const iteration
|
||||
const auto &cm = m;
|
||||
count = 0;
|
||||
for (auto it = cm.begin(); it != cm.end(); ++it) {
|
||||
auto kv = *it;
|
||||
REQUIRE_EQ(kv.second, kv.first + 100);
|
||||
++count;
|
||||
}
|
||||
REQUIRE_EQ(count, cm.size());
|
||||
}
|
||||
|
||||
TEST_CASE("Remove non-existent returns false, find on const map") {
|
||||
HashMap<int, int> m;
|
||||
REQUIRE(!m.remove(999));
|
||||
|
||||
const HashMap<int, int> cm;
|
||||
REQUIRE(!cm.find_value(0));
|
||||
}
|
||||
|
||||
TEST_CASE("Inserting multiple elements while deleting them will trigger inline "
|
||||
"rehash") {
|
||||
const static int MAX_CAPACITY = 2;
|
||||
HashMap<int, int> m(8 /*capacity*/);
|
||||
REQUIRE_EQ(8, m.capacity());
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
m.insert(i, i);
|
||||
if (m.size() > MAX_CAPACITY) {
|
||||
m.remove(i);
|
||||
}
|
||||
}
|
||||
size_t new_capacity = m.capacity();
|
||||
// should still be 8
|
||||
REQUIRE_EQ(new_capacity, 8u);
|
||||
std::set<int> found_values;
|
||||
|
||||
for (auto it = m.begin(); it != m.end(); ++it) {
|
||||
auto kv = *it;
|
||||
auto key = kv.first;
|
||||
auto value = kv.second;
|
||||
REQUIRE_EQ(key, value);
|
||||
found_values.insert(kv.second);
|
||||
}
|
||||
|
||||
std::vector<int> found_values_vec(found_values.begin(), found_values.end());
|
||||
REQUIRE_EQ(MAX_CAPACITY, found_values_vec.size());
|
||||
for (int i = 0; i < MAX_CAPACITY; ++i) {
|
||||
auto value = found_values_vec[i];
|
||||
REQUIRE_EQ(value, i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("HashMap with standard iterator access") {
|
||||
HashMap<int, int> m;
|
||||
m.insert(1, 1);
|
||||
|
||||
REQUIRE_EQ(m.size(), 1u);
|
||||
|
||||
// standard iterator access
|
||||
auto it = m.begin();
|
||||
auto entry = *it;
|
||||
REQUIRE_EQ(entry.first, 1);
|
||||
REQUIRE_EQ(entry.second, 1);
|
||||
++it;
|
||||
REQUIRE(it == m.end());
|
||||
|
||||
auto bad_it = m.find(0);
|
||||
REQUIRE(bad_it == m.end());
|
||||
}
|
||||
|
||||
TEST_CASE("HashMap equivalence to std::unordered_map") {
|
||||
// Create both map types with the same operations
|
||||
HashMap<int, fl::Str> custom_map;
|
||||
std::unordered_map<int, fl::Str> std_map;
|
||||
|
||||
// Test insertion
|
||||
custom_map.insert(1, "one");
|
||||
std_map.insert({1, "one"});
|
||||
|
||||
custom_map.insert(2, "two");
|
||||
std_map.insert({2, "two"});
|
||||
|
||||
custom_map.insert(3, "three");
|
||||
std_map.insert({3, "three"});
|
||||
|
||||
// Test size
|
||||
REQUIRE_EQ(custom_map.size(), std_map.size());
|
||||
|
||||
// Test lookup
|
||||
REQUIRE(*custom_map.find_value(1) == std_map.at(1));
|
||||
REQUIRE(*custom_map.find_value(2) == std_map.at(2));
|
||||
REQUIRE(*custom_map.find_value(3) == std_map.at(3));
|
||||
|
||||
// Test non-existent key
|
||||
REQUIRE(!custom_map.find_value(99));
|
||||
bool std_throws = false;
|
||||
try {
|
||||
std_map.at(99);
|
||||
} catch (const std::out_of_range&) {
|
||||
std_throws = true;
|
||||
}
|
||||
REQUIRE(std_throws);
|
||||
|
||||
// Test overwrite
|
||||
custom_map.insert(2, "TWO");
|
||||
std_map[2] = "TWO";
|
||||
REQUIRE(*custom_map.find_value(2) == std_map.at(2));
|
||||
|
||||
// Test erase
|
||||
REQUIRE(custom_map.erase(2));
|
||||
std_map.erase(2);
|
||||
REQUIRE_EQ(custom_map.size(), std_map.size());
|
||||
REQUIRE(!custom_map.find_value(2));
|
||||
|
||||
// Test clear
|
||||
custom_map.clear();
|
||||
std_map.clear();
|
||||
REQUIRE_EQ(custom_map.size(), std_map.size());
|
||||
REQUIRE_EQ(custom_map.size(), 0u);
|
||||
|
||||
// Test operator[]
|
||||
custom_map[5] = "five";
|
||||
std_map[5] = "five";
|
||||
REQUIRE_EQ(custom_map.size(), std_map.size());
|
||||
REQUIRE(*custom_map.find_value(5) == std_map.at(5));
|
||||
|
||||
// Test iteration (collect all keys and values)
|
||||
for (int i = 10; i < 20; ++i) {
|
||||
fl::Str val = "val";
|
||||
val.append(i);
|
||||
custom_map.insert(i, val);
|
||||
std_map.insert({i, val});
|
||||
}
|
||||
|
||||
std::set<int> custom_keys;
|
||||
std::set<fl::Str> custom_values;
|
||||
for (auto kv : custom_map) {
|
||||
custom_keys.insert(kv.first);
|
||||
custom_values.insert(kv.second);
|
||||
}
|
||||
|
||||
std::set<int> std_keys;
|
||||
std::set<fl::Str> std_values;
|
||||
for (auto& kv : std_map) {
|
||||
std_keys.insert(kv.first);
|
||||
std_values.insert(kv.second);
|
||||
}
|
||||
|
||||
REQUIRE(custom_keys == std_keys);
|
||||
REQUIRE(custom_values == std_values);
|
||||
}
|
||||
151
libraries/FastLED/tests/test_hashmap_lru.cpp
Normal file
151
libraries/FastLED/tests/test_hashmap_lru.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// test.cpp
|
||||
// g++ --std=c++11 test.cpp -o test && ./test
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "fl/hash_map_lru.h"
|
||||
#include "fl/str.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Test HashMapLRU") {
|
||||
SUBCASE("Basic operations") {
|
||||
HashMapLru<int, int> lru(3);
|
||||
|
||||
// Test empty state
|
||||
CHECK(lru.empty());
|
||||
CHECK(lru.size() == 0);
|
||||
CHECK(lru.capacity() == 3);
|
||||
CHECK(lru.find_value(1) == nullptr);
|
||||
|
||||
// Test insertion
|
||||
lru.insert(1, 100);
|
||||
CHECK(!lru.empty());
|
||||
CHECK(lru.size() == 1);
|
||||
CHECK(*lru.find_value(1) == 100);
|
||||
|
||||
// Test operator[]
|
||||
lru[2] = 200;
|
||||
CHECK(lru.size() == 2);
|
||||
CHECK(*lru.find_value(2) == 200);
|
||||
|
||||
// Test update
|
||||
lru[1] = 150;
|
||||
CHECK(lru.size() == 2);
|
||||
CHECK(*lru.find_value(1) == 150);
|
||||
|
||||
// Test removal
|
||||
CHECK(lru.remove(1));
|
||||
CHECK(lru.size() == 1);
|
||||
CHECK(lru.find_value(1) == nullptr);
|
||||
CHECK(!lru.remove(1)); // Already removed
|
||||
|
||||
// Test clear
|
||||
lru.clear();
|
||||
CHECK(lru.empty());
|
||||
CHECK(lru.size() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("LRU eviction") {
|
||||
HashMapLru<int, int> lru(3);
|
||||
|
||||
// Fill the cache
|
||||
lru.insert(1, 100);
|
||||
lru.insert(2, 200);
|
||||
lru.insert(3, 300);
|
||||
CHECK(lru.size() == 3);
|
||||
|
||||
// Access key 1 to make it most recently used
|
||||
CHECK(*lru.find_value(1) == 100);
|
||||
|
||||
// Insert a new key, should evict key 2 (least recently used)
|
||||
lru.insert(4, 400);
|
||||
CHECK(lru.size() == 3);
|
||||
CHECK(lru.find_value(2) == nullptr); // Key 2 should be evicted
|
||||
CHECK(*lru.find_value(1) == 100);
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
CHECK(*lru.find_value(4) == 400);
|
||||
|
||||
// Access key 3, then insert new key
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
lru.insert(5, 500);
|
||||
CHECK(lru.size() == 3);
|
||||
CHECK(lru.find_value(1) == nullptr); // Key 1 should be evicted
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
CHECK(*lru.find_value(4) == 400);
|
||||
CHECK(*lru.find_value(5) == 500);
|
||||
}
|
||||
|
||||
SUBCASE("Operator[] LRU behavior") {
|
||||
HashMapLru<int, int> lru(3);
|
||||
|
||||
// Fill the cache using operator[]
|
||||
lru[1] = 100;
|
||||
lru[2] = 200;
|
||||
lru[3] = 300;
|
||||
|
||||
// Access key 1 to make it most recently used
|
||||
int val = lru[1];
|
||||
CHECK(val == 100);
|
||||
|
||||
// Insert a new key, should evict key 2
|
||||
lru[4] = 400;
|
||||
CHECK(lru.size() == 3);
|
||||
CHECK(lru.find_value(2) == nullptr);
|
||||
CHECK(*lru.find_value(1) == 100);
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
CHECK(*lru.find_value(4) == 400);
|
||||
}
|
||||
|
||||
SUBCASE("Edge cases") {
|
||||
// Test with capacity 1
|
||||
HashMapLru<int, int> tiny_lru(1);
|
||||
tiny_lru.insert(1, 100);
|
||||
CHECK(*tiny_lru.find_value(1) == 100);
|
||||
|
||||
tiny_lru.insert(2, 200);
|
||||
CHECK(tiny_lru.size() == 1);
|
||||
CHECK(tiny_lru.find_value(1) == nullptr);
|
||||
CHECK(*tiny_lru.find_value(2) == 200);
|
||||
|
||||
// Test with string keys
|
||||
HashMapLru<fl::Str, int> str_lru(2);
|
||||
str_lru.insert("one", 1);
|
||||
str_lru.insert("two", 2);
|
||||
CHECK(*str_lru.find_value("one") == 1);
|
||||
CHECK(*str_lru.find_value("two") == 2);
|
||||
|
||||
// This should evict "one" since it's least recently used
|
||||
str_lru.insert("three", 3);
|
||||
CHECK(str_lru.find_value("one") == nullptr);
|
||||
CHECK(*str_lru.find_value("two") == 2);
|
||||
CHECK(*str_lru.find_value("three") == 3);
|
||||
}
|
||||
|
||||
SUBCASE("Update existing key") {
|
||||
HashMapLru<int, int> lru(3);
|
||||
|
||||
// Fill the cache
|
||||
lru.insert(1, 100);
|
||||
lru.insert(2, 200);
|
||||
lru.insert(3, 300);
|
||||
|
||||
// Update an existing key
|
||||
lru.insert(2, 250);
|
||||
CHECK(lru.size() == 3);
|
||||
CHECK(*lru.find_value(1) == 100);
|
||||
CHECK(*lru.find_value(2) == 250);
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
|
||||
// Insert a new key, should evict key 1 (least recently used)
|
||||
lru.insert(4, 400);
|
||||
CHECK(lru.size() == 3);
|
||||
CHECK(lru.find_value(1) == nullptr);
|
||||
CHECK(*lru.find_value(2) == 250);
|
||||
CHECK(*lru.find_value(3) == 300);
|
||||
CHECK(*lru.find_value(4) == 400);
|
||||
}
|
||||
}
|
||||
22
libraries/FastLED/tests/test_intmap.cpp
Normal file
22
libraries/FastLED/tests/test_intmap.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("map8_to_16") {
|
||||
CHECK_EQ(map8_to_16(0), 0);
|
||||
CHECK_EQ(map8_to_16(1), 0x101);
|
||||
CHECK_EQ(map8_to_16(0xff), 0xffff);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("map8_to_32") {
|
||||
CHECK_EQ(map8_to_32(0), 0);
|
||||
CHECK_EQ(map8_to_32(1), 0x1010101);
|
||||
CHECK_EQ(map8_to_32(0xff), 0xffffffff);
|
||||
}
|
||||
207
libraries/FastLED/tests/test_line_simplification.cpp
Normal file
207
libraries/FastLED/tests/test_line_simplification.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "fl/line_simplification.h"
|
||||
#include "fl/warn.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Test Line Simplification") {
|
||||
// default‐constructed bitset is empty
|
||||
LineSimplifier<float> ls;
|
||||
ls.setMinimumDistance(0.1f);
|
||||
fl::vector<vec2<float>> points;
|
||||
points.push_back({0.0f, 0.0f});
|
||||
points.push_back({1.0f, 1.0f});
|
||||
points.push_back({2.0f, 2.0f});
|
||||
points.push_back({3.0f, 3.0f});
|
||||
points.push_back({4.0f, 4.0f});
|
||||
ls.simplifyInplace(&points);
|
||||
REQUIRE_EQ(2,
|
||||
points.size()); // Only 2 points on co-linear line should remain.
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 0.0f), points[0]);
|
||||
REQUIRE_EQ(vec2<float>(4.0f, 4.0f), points[1]);
|
||||
}
|
||||
|
||||
TEST_CASE("Test simple triangle") {
|
||||
LineSimplifier<float> ls;
|
||||
|
||||
fl::vector<vec2<float>> points;
|
||||
points.push_back({0.0f, 0.0f}); // First point of triangle
|
||||
points.push_back({0.5f, 0.5f});
|
||||
points.push_back({0.0f, 1.0f});
|
||||
float exceeds_thresh = 0.49f;
|
||||
float under_thresh = 0.51f;
|
||||
ls.setMinimumDistance(exceeds_thresh);
|
||||
fl::vector<vec2<float>> output;
|
||||
ls.simplify(points, &output);
|
||||
REQUIRE_EQ(3, output.size());
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 0.0f), output[0]);
|
||||
REQUIRE_EQ(vec2<float>(0.5f, 0.5f), output[1]);
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 1.0f), output[2]);
|
||||
|
||||
ls.setMinimumDistance(under_thresh);
|
||||
ls.simplify(points, &output);
|
||||
REQUIRE_EQ(2, output.size());
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 0.0f), output[0]);
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 1.0f), output[1]);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Line Simplification with Different Distance Thresholds") {
|
||||
LineSimplifier<float> ls;
|
||||
|
||||
// Test with a triangle shape - non-collinear points
|
||||
ls.setMinimumDistance(0.5f);
|
||||
fl::vector<vec2<float>> points1;
|
||||
points1.push_back({0.0f, 0.0f}); // First point of triangle
|
||||
points1.push_back({0.3f, 0.3f}); // Should be filtered out (distance < 0.5)
|
||||
points1.push_back({1.0f, 1.0f}); // Second point of triangle
|
||||
points1.push_back({0.8f, 1.2f}); // Should be filtered out (distance < 0.5)
|
||||
points1.push_back({0.0f, 2.0f}); // Third point of triangle
|
||||
ls.simplifyInplace(&points1);
|
||||
REQUIRE_EQ(3, points1.size()); // Triangle vertices should remain
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 0.0f), points1[0]);
|
||||
REQUIRE_EQ(vec2<float>(1.0f, 1.0f), points1[1]);
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 2.0f), points1[2]);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Line Simplification with Complex Shape") {
|
||||
// SUBCASE("at threshold") {
|
||||
// LineSimplifier<float> ls;
|
||||
// // Test with a more complex shape and smaller threshold
|
||||
// ls.setMinimumDistance(0.1f);
|
||||
// fl::vector<vec2<float>> points2;
|
||||
// points2.push_back({0.0f, 0.0f}); // Start point
|
||||
// points2.push_back({0.1f, 0.20f}); // Filtered out
|
||||
// points2.push_back({0.0f, 0.29f}); // Filtered out
|
||||
// points2.push_back({0.0f, 1.0f}); // Should be kept (distance > 0.2)
|
||||
// ls.simplifyInplace(&points2);
|
||||
// REQUIRE_EQ(3, points2.size());
|
||||
// REQUIRE_EQ(vec2<float>(0.0f, 0.0f), points2[0]);
|
||||
// REQUIRE_EQ(vec2<float>(0.10f, 0.10f), points2[1]);
|
||||
// REQUIRE_EQ(vec2<float>(0.0f, 1.0f), points2[2]);
|
||||
// };
|
||||
|
||||
SUBCASE("Above threshold") {
|
||||
LineSimplifier<float> ls;
|
||||
// Test with a more complex shape and larger threshold
|
||||
ls.setMinimumDistance(0.101f);
|
||||
fl::vector<vec2<float>> points3;
|
||||
points3.push_back({0.0f, 0.0f}); // Start point
|
||||
points3.push_back({0.1f, 0.1f}); // Filtered out
|
||||
points3.push_back({0.0f, 0.3f}); // Filtered out
|
||||
points3.push_back({0.0f, 1.0f}); // Should be kept (distance > 0.5)
|
||||
ls.simplifyInplace(&points3);
|
||||
REQUIRE_EQ(2, points3.size());
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 0.0f), points3[0]);
|
||||
REQUIRE_EQ(vec2<float>(0.0f, 1.0f), points3[1]);
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Iteratively find the closest point") {
|
||||
LineSimplifier<float> ls;
|
||||
fl::vector<vec2<float>> points;
|
||||
points.push_back({0.0f, 0.0f}); // First point of triangle
|
||||
points.push_back({0.5f, 0.5f});
|
||||
points.push_back({0.0f, 1.0f});
|
||||
|
||||
float thresh = 0.0;
|
||||
while (true) {
|
||||
ls.setMinimumDistance(thresh);
|
||||
fl::vector<vec2<float>> output;
|
||||
ls.simplify(points, &output);
|
||||
if (output.size() == 2) {
|
||||
break;
|
||||
}
|
||||
thresh += 0.01f;
|
||||
}
|
||||
REQUIRE(ALMOST_EQUAL(thresh, 0.5f, 0.01f));
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("Binary search the the threshold that gives 3 points") {
|
||||
LineSimplifierExact<float> ls;
|
||||
fl::vector<vec2<float>> points;
|
||||
points.push_back({0.0f, 0.0f}); // First point of triangle
|
||||
points.push_back({0.5f, 0.5f});
|
||||
points.push_back({0.0f, 1.0f});
|
||||
points.push_back({0.6f, 2.0f});
|
||||
points.push_back({0.0f, 6.0f});
|
||||
|
||||
ls.setCount(3);
|
||||
|
||||
fl::vector<vec2<float>> out;
|
||||
|
||||
ls.simplify(points, &out);
|
||||
REQUIRE_EQ(3, out.size());
|
||||
MESSAGE("Done");
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Known bad") {
|
||||
fl::vector<vec2<float>> points;
|
||||
points.push_back({-3136.439941f, 2546.339844f});
|
||||
points.push_back({4580.994141f, -3516.982422f});
|
||||
points.push_back({-1228.554688f, -5104.814453f});
|
||||
points.push_back({-8806.442383f, 3895.103516f});
|
||||
points.push_back({-2039.114746f, 1878.047852f});
|
||||
|
||||
LineSimplifierExact<float> ls;
|
||||
ls.setCount(3);
|
||||
fl::vector<vec2<float>> out;
|
||||
ls.simplify(points, &out);
|
||||
|
||||
MESSAGE("Output points: " << out.size());
|
||||
MESSAGE("Output points: " << out);
|
||||
|
||||
REQUIRE_EQ(3, out.size());
|
||||
}
|
||||
|
||||
|
||||
// TEST_CASE("Binary search reduction to 3 points from 5 random points (1000 runs)") {
|
||||
// constexpr int kTrials = 1000;
|
||||
// constexpr int kInputPoints = 5;
|
||||
// constexpr int kTargetPoints = 3;
|
||||
|
||||
// std::mt19937 rng(123); // fixed seed for reproducibility
|
||||
// std::uniform_real_distribution<float> dist(-10000.0f, 10000.0f);
|
||||
|
||||
// for (int trial = 0; trial < kTrials; ++trial) {
|
||||
// LineSimplifierExact<float> ls;
|
||||
// fl::vector<vec2<float>> points;
|
||||
|
||||
// for (int i = 0; i < kInputPoints; ++i) {
|
||||
// points.push_back({dist(rng), dist(rng)});
|
||||
// }
|
||||
|
||||
// ls.setCount(kTargetPoints);
|
||||
|
||||
// fl::vector<vec2<float>> out;
|
||||
// ls.simplify(points, &out);
|
||||
|
||||
|
||||
// const bool bad_value = (out.size() != kTargetPoints);
|
||||
|
||||
// if (bad_value) {
|
||||
// INFO("Trial " << trial << ": Input points: " << points.size()
|
||||
// << ", Output points: " << out.size() << ", " << out);
|
||||
|
||||
// for (size_t i = 0; i < points.size(); ++i) {
|
||||
// auto p = points[i];
|
||||
// FASTLED_WARN("Input point " << i << ": " << p);
|
||||
// }
|
||||
|
||||
// // Assert
|
||||
// REQUIRE_EQ(kTargetPoints, out.size());
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// MESSAGE("Completed 1000 trials of random 5→3 simplification");
|
||||
// }
|
||||
43
libraries/FastLED/tests/test_lut.cpp
Normal file
43
libraries/FastLED/tests/test_lut.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/lut.h"
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("LUT interp8") {
|
||||
LUT<uint16_t> lut(2);
|
||||
uint16_t* data = lut.getDataMutable();
|
||||
data[0] = 0;
|
||||
data[1] = 255;
|
||||
CHECK_EQ(lut.interp8(0), 0);
|
||||
CHECK_EQ(lut.interp8(255), 255);
|
||||
CHECK_EQ(lut.interp8(128), 128);
|
||||
|
||||
// Check the LUT interpolation for all values.
|
||||
for (uint16_t i = 0; i < 256; i++) {
|
||||
uint16_t expected = (i * 255) / 255;
|
||||
CHECK_EQ(lut.interp8(i), expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("LUT interp16") {
|
||||
LUT<uint16_t> lut(2);
|
||||
uint16_t* data = lut.getDataMutable();
|
||||
data[0] = 0;
|
||||
data[1] = 255;
|
||||
CHECK_EQ(lut.interp16(0), 0);
|
||||
CHECK_EQ(lut.interp16(0xffff), 255);
|
||||
CHECK_EQ(lut.interp16(0xffff / 2), 127);
|
||||
|
||||
// Check the LUT interpolation for all values.
|
||||
for (int i = 0; i < 256; i++) {
|
||||
uint16_t alpha16 = map8_to_16(i);
|
||||
CHECK_EQ(i, lut.interp16(alpha16));
|
||||
}
|
||||
}
|
||||
139
libraries/FastLED/tests/test_map.cpp
Normal file
139
libraries/FastLED/tests/test_map.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// g++ --std=c++11 test_fixed_map.cpp -I../src
|
||||
|
||||
#include "test.h"
|
||||
#include "test.h"
|
||||
#include "fl/map.h"
|
||||
#include <string>
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("fl::FixedMap operations") {
|
||||
fl::FixedMap<int, int, 5> map;
|
||||
|
||||
SUBCASE("Insert and find") {
|
||||
CHECK(map.insert(1, 10).first);
|
||||
CHECK(map.insert(2, 20).first);
|
||||
CHECK(map.insert(3, 30).first);
|
||||
|
||||
int value;
|
||||
CHECK(map.get(1, &value));
|
||||
CHECK(value == 10);
|
||||
CHECK(map.get(2, &value));
|
||||
CHECK(value == 20);
|
||||
CHECK(map.get(3, &value));
|
||||
CHECK(value == 30);
|
||||
CHECK_FALSE(map.get(4, &value));
|
||||
}
|
||||
|
||||
SUBCASE("Update") {
|
||||
CHECK(map.insert(1, 10).first);
|
||||
CHECK(map.update(1, 15));
|
||||
int value;
|
||||
CHECK(map.get(1, &value));
|
||||
CHECK(value == 15);
|
||||
|
||||
CHECK(map.update(2, 20, true)); // Insert if missing
|
||||
CHECK(map.get(2, &value));
|
||||
CHECK(value == 20);
|
||||
|
||||
CHECK_FALSE(map.update(3, 30, false)); // Don't insert if missing
|
||||
CHECK_FALSE(map.get(3, &value));
|
||||
}
|
||||
|
||||
SUBCASE("Next and prev") {
|
||||
CHECK(map.insert(1, 10).first);
|
||||
CHECK(map.insert(2, 20).first);
|
||||
CHECK(map.insert(3, 30).first);
|
||||
|
||||
int next_key;
|
||||
CHECK(map.next(1, &next_key));
|
||||
CHECK(next_key == 2);
|
||||
CHECK(map.next(2, &next_key));
|
||||
CHECK(next_key == 3);
|
||||
CHECK_FALSE(map.next(3, &next_key));
|
||||
CHECK(map.next(3, &next_key, true)); // With rollover
|
||||
CHECK(next_key == 1);
|
||||
|
||||
int prev_key;
|
||||
CHECK(map.prev(3, &prev_key));
|
||||
CHECK(prev_key == 2);
|
||||
CHECK(map.prev(2, &prev_key));
|
||||
CHECK(prev_key == 1);
|
||||
CHECK_FALSE(map.prev(1, &prev_key));
|
||||
CHECK(map.prev(1, &prev_key, true)); // With rollover
|
||||
CHECK(prev_key == 3);
|
||||
|
||||
// Additional test for prev on first element with rollover
|
||||
CHECK(map.prev(1, &prev_key, true));
|
||||
CHECK(prev_key == 3);
|
||||
}
|
||||
|
||||
SUBCASE("Size and capacity") {
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.capacity() == 5);
|
||||
CHECK(map.empty());
|
||||
|
||||
CHECK(map.insert(1, 10).first);
|
||||
CHECK(map.insert(2, 20).first);
|
||||
CHECK(map.size() == 2);
|
||||
CHECK_FALSE(map.empty());
|
||||
|
||||
map.clear();
|
||||
CHECK(map.size() == 0);
|
||||
CHECK(map.empty());
|
||||
}
|
||||
|
||||
SUBCASE("Iterators") {
|
||||
CHECK(map.insert(1, 10).first);
|
||||
CHECK(map.insert(2, 20).first);
|
||||
CHECK(map.insert(3, 30).first);
|
||||
|
||||
int sum = 0;
|
||||
for (const auto& pair : map) {
|
||||
sum += pair.second;
|
||||
}
|
||||
CHECK(sum == 60);
|
||||
}
|
||||
|
||||
SUBCASE("Operator[]") {
|
||||
CHECK(map[1] == 0); // Default value
|
||||
CHECK(!map.insert(1, 10).first);
|
||||
CHECK(map[1] == 0);
|
||||
CHECK(map[2] == 0); // Default value
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SortedHeapMap operations") {
|
||||
struct Less {
|
||||
bool operator()(int a, int b) const { return a < b; }
|
||||
};
|
||||
|
||||
SUBCASE("Insert maintains key order") {
|
||||
fl::SortedHeapMap<int, std::string, Less> map;
|
||||
|
||||
map.insert(3, "three");
|
||||
map.insert(1, "one");
|
||||
map.insert(4, "four");
|
||||
map.insert(2, "two");
|
||||
|
||||
CHECK(map.size() == 4);
|
||||
CHECK(map.has(1));
|
||||
CHECK(map.has(2));
|
||||
CHECK(map.has(3));
|
||||
CHECK(map.has(4));
|
||||
CHECK_FALSE(map.has(5));
|
||||
|
||||
// Verify order by iterating
|
||||
auto it = map.begin();
|
||||
CHECK(it->first == 1);
|
||||
++it;
|
||||
CHECK(it->first == 2);
|
||||
++it;
|
||||
CHECK(it->first == 3);
|
||||
++it;
|
||||
CHECK(it->first == 4);
|
||||
++it;
|
||||
CHECK(it == map.end());
|
||||
}
|
||||
}
|
||||
54
libraries/FastLED/tests/test_map_range.cpp
Normal file
54
libraries/FastLED/tests/test_map_range.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/map_range.h"
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("map_range<uint8_t>") {
|
||||
|
||||
CHECK_EQ(map_range<uint8_t>(0, 0, 255, 0, 255), 0);
|
||||
CHECK_EQ(map_range<uint8_t>(255, 0, 255, 0, 255), 255);
|
||||
CHECK_EQ(map_range<uint8_t>(128, 0, 255, 0, 255), 128);
|
||||
CHECK_EQ(map_range<uint8_t>(128, 0, 255, 0, 127), 63);
|
||||
// One past the max should roll over to to 128.
|
||||
CHECK_EQ(map_range<uint8_t>(128, 0, 127, 0, 127), 128);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("map_range<uint16_t>") {
|
||||
CHECK_EQ(map_range<uint16_t>(0, 0, 65535, 0, 65535), 0);
|
||||
CHECK_EQ(map_range<uint16_t>(65535, 0, 65535, 0, 65535), 65535);
|
||||
CHECK_EQ(map_range<uint16_t>(32768, 0, 65535, 0, 65535), 32768);
|
||||
CHECK_EQ(map_range<uint16_t>(32768, 0, 65535, 0, 32767), 16383);
|
||||
CHECK_EQ(map_range<uint16_t>(32768, 0, 32767, 0, 32767), 32768);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("map_range<float>") {
|
||||
CHECK_EQ(map_range<float>(0.0f, 0.0f, 1.0f, 0.0f, 1.0f), 0.0f);
|
||||
CHECK_EQ(map_range<float>(1.0f, 0.0f, 1.0f, 0.0f, 1.0f), 1.0f);
|
||||
CHECK_EQ(map_range<float>(0.5f, 0.0f, 1.0f, 0.0f, 1.0f), 0.5f);
|
||||
CHECK_EQ(map_range<float>(0.5f, 0.0f, 1.0f, 10.0f, 20.0f), 15.0f);
|
||||
CHECK_EQ(map_range<float>(2.5f, -1.5f, 2.5f, -10.5f, -20.5f), -20.5f);
|
||||
}
|
||||
|
||||
TEST_CASE("map_range<float, vec2<float>") {
|
||||
float min = 0.0f;
|
||||
float max = 1.0f;
|
||||
vec2<float> in_min(0.0f, 0.0f);
|
||||
vec2<float> out_max(1.0f, 2.0f);
|
||||
|
||||
vec2<float> out = map_range(.5f, min, max, in_min, out_max);
|
||||
CHECK_EQ(out.x, 0.5f);
|
||||
CHECK_EQ(out.y, 1.f);
|
||||
|
||||
// Now try negative number
|
||||
out = map_range(-1.f, min, max, in_min, out_max);
|
||||
CHECK_EQ(out.x, -1.f);
|
||||
CHECK_EQ(out.y, -2.f);
|
||||
}
|
||||
77
libraries/FastLED/tests/test_math.cpp
Normal file
77
libraries/FastLED/tests/test_math.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "FastLED.h"
|
||||
#include "lib8tion/scale8.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
|
||||
TEST_CASE("scale16") {
|
||||
CHECK_EQ(scale16(0, 0), 0);
|
||||
CHECK_EQ(scale16(0, 1), 0);
|
||||
CHECK_EQ(scale16(1, 0), 0);
|
||||
CHECK_EQ(scale16(0xffff, 0xffff), 0xffff);
|
||||
CHECK_EQ(scale16(0xffff, 0xffff >> 1), 0xffff >> 1);
|
||||
CHECK_EQ(scale16(0xffff >> 1, 0xffff >> 1), 0xffff >> 2);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
int total_bitshift = i + j;
|
||||
if (total_bitshift > 15) {
|
||||
break;
|
||||
}
|
||||
// print out info if this test fails to capture the i,j values that are failing
|
||||
INFO("i: " << i << " j: " << j << " total_bitshift: " << total_bitshift);
|
||||
CHECK_EQ(scale16(0xffff >> i, 0xffff >> j), 0xffff >> total_bitshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("scale16by8") {
|
||||
CHECK_EQ(scale16by8(0, 0), 0);
|
||||
CHECK_EQ(scale16by8(0, 1), 0);
|
||||
CHECK_EQ(scale16by8(1, 0), 0);
|
||||
CHECK_EQ(scale16by8(map8_to_16(1), 1), 2);
|
||||
CHECK_EQ(scale16by8(0xffff, 0xff), 0xffff);
|
||||
CHECK_EQ(scale16by8(0xffff, 0xff >> 1), 0xffff >> 1);
|
||||
CHECK_EQ(scale16by8(0xffff >> 1, 0xff >> 1), 0xffff >> 2);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
int total_bitshift = i + j;
|
||||
if (total_bitshift > 7) {
|
||||
break;
|
||||
}
|
||||
// print out info if this test fails to capture the i,j values that are failing
|
||||
INFO("i: " << i << " j: " << j << " total_bitshift: " << total_bitshift);
|
||||
CHECK_EQ(scale16by8(0xffff >> i, 0xff >> j), 0xffff >> total_bitshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("bit equivalence") {
|
||||
// tests that 8bit and 16bit are equivalent
|
||||
uint8_t r = 0xff;
|
||||
uint8_t r_scale = 0xff / 2;
|
||||
uint8_t brightness = 0xff / 2;
|
||||
uint16_t r_scale16 = map8_to_16(r_scale);
|
||||
uint16_t brightness16 = map8_to_16(brightness);
|
||||
uint16_t r16 = scale16by8(scale16(r_scale16, brightness16), r);
|
||||
uint8_t r8 = scale8(scale8(r_scale, brightness), r);
|
||||
CHECK_EQ(r16 >> 8, r8);
|
||||
}
|
||||
|
||||
TEST_CASE("sqrt16") {
|
||||
float f = sqrt(.5) * 0xff;
|
||||
uint8_t result = sqrt16(map8_to_16(0xff / 2));
|
||||
CHECK_EQ(int(f), result);
|
||||
CHECK_EQ(sqrt8(0xff / 2), result);
|
||||
}
|
||||
46
libraries/FastLED/tests/test_point.cpp
Normal file
46
libraries/FastLED/tests/test_point.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/geometry.h"
|
||||
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
#define REQUIRE_APPROX(a, b, tolerance) \
|
||||
do { \
|
||||
if (fabs((a - b)) > (tolerance)) { \
|
||||
std::cerr << "REQUIRE_APPROX failed: " << #a << " = " << (a) \
|
||||
<< ", " << #b << " = " << (b) \
|
||||
<< ", tolerance = " << (tolerance) << std::endl; \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
TEST_CASE("0 is 0 distance from diagonal line through the center") {
|
||||
line_xy<float> line(-100, -100, 100, 100);
|
||||
vec2<float> p(0, 0);
|
||||
vec2<float> projected;
|
||||
float dist = line.distance_to(p, &projected);
|
||||
REQUIRE_APPROX(projected.x, 0.0f, 0.001f);
|
||||
REQUIRE_APPROX(projected.y, 0.0f, 0.001f);
|
||||
REQUIRE_APPROX(dist, 0.0f, 0.001f);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("point closest to line") {
|
||||
line_xy<float> line(-100, -100, 100, 100);
|
||||
|
||||
vec2<float> p(50, 0);
|
||||
vec2<float> projected;
|
||||
|
||||
float dist = line.distance_to(p, &projected);
|
||||
|
||||
// The closest point should be (25, 25)
|
||||
REQUIRE_APPROX(projected.x, 25.0f, 0.001f);
|
||||
REQUIRE_APPROX(projected.y, 25.0f, 0.001f);
|
||||
|
||||
// Distance should be sqrt((50-25)^2 + (0-25)^2) = sqrt(1250) ≈ 35.355
|
||||
REQUIRE_APPROX(dist, 35.355f, 0.001f);
|
||||
}
|
||||
103
libraries/FastLED/tests/test_priority_queue.cpp
Normal file
103
libraries/FastLED/tests/test_priority_queue.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/priority_queue.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Simple Priority Queue") {
|
||||
PriorityQueue<int> pq;
|
||||
|
||||
// Test empty queue
|
||||
CHECK(pq.empty());
|
||||
CHECK(pq.size() == 0);
|
||||
|
||||
// Test push and top
|
||||
pq.push(5);
|
||||
CHECK(!pq.empty());
|
||||
CHECK(pq.size() == 1);
|
||||
CHECK(pq.top() == 5);
|
||||
|
||||
// Test priority ordering (default is max heap)
|
||||
pq.push(10);
|
||||
CHECK(pq.size() == 2);
|
||||
CHECK(pq.top() == 10);
|
||||
|
||||
pq.push(3);
|
||||
CHECK(pq.size() == 3);
|
||||
CHECK(pq.top() == 10);
|
||||
|
||||
pq.push(15);
|
||||
CHECK(pq.size() == 4);
|
||||
CHECK(pq.top() == 15);
|
||||
|
||||
// Test pop
|
||||
pq.pop();
|
||||
CHECK(pq.size() == 3);
|
||||
CHECK(pq.top() == 10);
|
||||
|
||||
pq.pop();
|
||||
CHECK(pq.size() == 2);
|
||||
CHECK(pq.top() == 5);
|
||||
|
||||
pq.pop();
|
||||
CHECK(pq.size() == 1);
|
||||
CHECK(pq.top() == 3);
|
||||
|
||||
pq.pop();
|
||||
CHECK(pq.empty());
|
||||
CHECK(pq.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Priority Queue with Custom Type") {
|
||||
struct Task {
|
||||
int priority;
|
||||
const char* name;
|
||||
|
||||
bool operator<(const Task& other) const {
|
||||
return priority < other.priority;
|
||||
}
|
||||
};
|
||||
|
||||
PriorityQueue<Task> pq;
|
||||
|
||||
pq.push({1, "Low priority task"});
|
||||
pq.push({5, "Medium priority task"});
|
||||
pq.push({10, "High priority task"});
|
||||
|
||||
CHECK(pq.size() == 3);
|
||||
CHECK(pq.top().priority == 10);
|
||||
CHECK(pq.top().name == "High priority task");
|
||||
|
||||
pq.pop();
|
||||
CHECK(pq.top().priority == 5);
|
||||
CHECK(pq.top().name == "Medium priority task");
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Priority Queue with Custom Comparator") {
|
||||
struct MinHeapCompare {
|
||||
bool operator()(int a, int b) const {
|
||||
return a > b; // For min heap
|
||||
}
|
||||
};
|
||||
|
||||
// Create a min heap using custom heap operations
|
||||
HeapVector<int> vec;
|
||||
vec.push_back(5);
|
||||
vec.push_back(10);
|
||||
vec.push_back(3);
|
||||
|
||||
// Use our custom heap functions with a custom comparator
|
||||
push_heap(vec.begin(), vec.end(), MinHeapCompare());
|
||||
CHECK(vec[0] == 3); // Min element should be at the top
|
||||
|
||||
vec.push_back(1);
|
||||
push_heap(vec.begin(), vec.end(), MinHeapCompare());
|
||||
CHECK(vec[0] == 1); // New min element
|
||||
|
||||
// Test pop_heap with custom comparator
|
||||
pop_heap(vec.begin(), vec.end(), MinHeapCompare());
|
||||
CHECK(vec[0] == 3); // Next min element
|
||||
vec.pop_back(); // Remove the element we popped to the end
|
||||
}
|
||||
32
libraries/FastLED/tests/test_raster.cpp
Normal file
32
libraries/FastLED/tests/test_raster.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "fl/raster.h"
|
||||
#include "fl/tile2x2.h"
|
||||
#include "fl/xypath.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
TEST_CASE("XYRasterU8SparseTest should match bounds of pixels draw area") {
|
||||
XYPathPtr path = XYPath::NewLinePath(-1, -1, 1, 1);
|
||||
path->setDrawBounds(4,4);
|
||||
Tile2x2_u8 sp0 = path->at_subpixel(0);
|
||||
Tile2x2_u8 sp1 = path->at_subpixel(1);
|
||||
Tile2x2_u8 subpixels[2] = {sp0, sp1};
|
||||
|
||||
MESSAGE("subpixels[0] = " << subpixels[0]);
|
||||
MESSAGE("subpixels[1] = " << subpixels[1]);
|
||||
XYRasterU8Sparse raster(4, 4);
|
||||
raster.rasterize(subpixels);
|
||||
auto obligatory_bounds = raster.bounds();
|
||||
REQUIRE_EQ(rect<uint16_t>(0, 0, 4, 4), obligatory_bounds);
|
||||
|
||||
auto pixel_bounds = raster.bounds_pixels();
|
||||
REQUIRE_EQ(rect<uint16_t>(0, 0, 4, 4), pixel_bounds);
|
||||
}
|
||||
205
libraries/FastLED/tests/test_rectangular_buffer.cpp
Normal file
205
libraries/FastLED/tests/test_rectangular_buffer.cpp
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "fl/rectangular_draw_buffer.h"
|
||||
#include "rgbw.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Rectangular Buffer") {
|
||||
RectangularDrawBuffer buffer;
|
||||
|
||||
SUBCASE("Empty buffer has no LEDs") {
|
||||
CHECK(buffer.getTotalBytes() == 0);
|
||||
CHECK(buffer.getMaxBytesInStrip() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Add one strip of 10 RGB LEDs") {
|
||||
buffer.queue(DrawItem(1, 10, false));
|
||||
|
||||
CHECK(buffer.getMaxBytesInStrip() == 30);
|
||||
CHECK(buffer.getTotalBytes() == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Add two strips of 10 RGB LEDs") {
|
||||
buffer.queue(DrawItem(1, 10, false));
|
||||
buffer.queue(DrawItem(2, 10, false));
|
||||
|
||||
CHECK(buffer.getMaxBytesInStrip() == 30);
|
||||
CHECK(buffer.getTotalBytes() == 60);
|
||||
}
|
||||
|
||||
SUBCASE("Add one strip of 10 RGBW LEDs") {
|
||||
buffer.queue(DrawItem(1, 10, true));
|
||||
|
||||
uint32_t num_bytes = Rgbw::size_as_rgb(10) * 3;
|
||||
|
||||
CHECK(buffer.getMaxBytesInStrip() == num_bytes);
|
||||
CHECK(buffer.getTotalBytes() == num_bytes);
|
||||
}
|
||||
|
||||
SUBCASE("Add one strip of 10 RGBW LEDs and one strip of 10 RGB LEDs") {
|
||||
buffer.queue(DrawItem(1, 10, true));
|
||||
buffer.queue(DrawItem(2, 10, false));
|
||||
|
||||
uint32_t max_size_strip_bytes = Rgbw::size_as_rgb(10) * 3;
|
||||
|
||||
CHECK(buffer.getMaxBytesInStrip() == max_size_strip_bytes);
|
||||
CHECK(buffer.getTotalBytes() == max_size_strip_bytes * 2);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Rectangular Buffer queue tests") {
|
||||
RectangularDrawBuffer buffer;
|
||||
|
||||
SUBCASE("Queueing start and done") {
|
||||
CHECK(buffer.mQueueState == RectangularDrawBuffer::IDLE);
|
||||
buffer.onQueuingStart();
|
||||
CHECK(buffer.mQueueState == RectangularDrawBuffer::QUEUEING);
|
||||
buffer.onQueuingDone();
|
||||
CHECK(buffer.mQueueState == RectangularDrawBuffer::QUEUE_DONE);
|
||||
buffer.onQueuingStart();
|
||||
CHECK(buffer.mQueueState == RectangularDrawBuffer::QUEUEING);
|
||||
}
|
||||
|
||||
SUBCASE("Queue and then draw") {
|
||||
buffer.onQueuingStart();
|
||||
buffer.queue(DrawItem(1, 10, false));
|
||||
buffer.queue(DrawItem(2, 10, false));
|
||||
buffer.onQueuingDone();
|
||||
|
||||
CHECK(buffer.mPinToLedSegment.size() == 2);
|
||||
CHECK(buffer.mAllLedsBufferUint8Size == 60);
|
||||
|
||||
fl::Slice<uint8_t> slice1 = buffer.getLedsBufferBytesForPin(1, true);
|
||||
fl::Slice<uint8_t> slice2 = buffer.getLedsBufferBytesForPin(2, true);
|
||||
// Expect that the address of slice1 happens before slice2 in memory.
|
||||
CHECK(slice1.data() < slice2.data());
|
||||
// Check that the size of each slice is 30 bytes.
|
||||
CHECK(slice1.size() == 30);
|
||||
CHECK(slice2.size() == 30);
|
||||
// Check that the uint8_t buffer is zeroed out.
|
||||
for (size_t i = 0; i < slice1.size(); ++i) {
|
||||
REQUIRE(slice1[i] == 0);
|
||||
REQUIRE(slice2[i] == 0);
|
||||
}
|
||||
// now fill slice1 with 0x1, slice2 with 0x2
|
||||
for (size_t i = 0; i < slice1.size(); i += 3) {
|
||||
slice1[i] = 0x1;
|
||||
slice2[i] = 0x2;
|
||||
}
|
||||
// Check that the uint8_t buffer is filled with 0x1 and 0x2.
|
||||
uint8_t *all_leds = buffer.mAllLedsBufferUint8.get();
|
||||
uint32_t n_bytes = buffer.mAllLedsBufferUint8Size;
|
||||
for (size_t i = 0; i < n_bytes; i += 3) {
|
||||
if (i < slice1.size()) {
|
||||
REQUIRE(all_leds[i] == 0x1);
|
||||
} else {
|
||||
REQUIRE(all_leds[i] == 0x2);
|
||||
}
|
||||
}
|
||||
|
||||
// bonus, test the slice::pop_front() works as expected, this time fill
|
||||
// with 0x3 and 0x4
|
||||
while (!slice1.empty()) {
|
||||
slice1[0] = 0x3;
|
||||
slice1.pop_front();
|
||||
}
|
||||
|
||||
while (!slice2.empty()) {
|
||||
slice2[0] = 0x4;
|
||||
slice2.pop_front();
|
||||
}
|
||||
|
||||
// Check that the uint8_t buffer is filled with 0x3 and 0x4.
|
||||
for (size_t i = 0; i < 60; ++i) {
|
||||
if (i < 30) {
|
||||
REQUIRE(all_leds[i] == 0x3);
|
||||
} else {
|
||||
REQUIRE(all_leds[i] == 0x4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Test that the order that the pins are added are preserved") {
|
||||
buffer.onQueuingStart();
|
||||
buffer.queue(DrawItem(2, 10, false));
|
||||
buffer.queue(DrawItem(1, 10, false));
|
||||
buffer.queue(DrawItem(3, 10, false));
|
||||
buffer.onQueuingDone();
|
||||
|
||||
CHECK(buffer.mPinToLedSegment.size() == 3);
|
||||
CHECK(buffer.mAllLedsBufferUint8Size == 90);
|
||||
|
||||
fl::Slice<uint8_t> slice1 = buffer.getLedsBufferBytesForPin(2, true);
|
||||
fl::Slice<uint8_t> slice2 = buffer.getLedsBufferBytesForPin(1, true);
|
||||
fl::Slice<uint8_t> slice3 = buffer.getLedsBufferBytesForPin(3, true);
|
||||
|
||||
// Expect that the address of slice1 happens before slice2 in memory.
|
||||
CHECK(slice1.data() < slice2.data());
|
||||
CHECK(slice2.data() < slice3.data());
|
||||
|
||||
// Check that the end byte of slice1 is the first byte of slice2
|
||||
CHECK(slice1.data() + slice1.size() == slice2.data());
|
||||
// Check that the end byte of slice2 is the first byte of slice3
|
||||
CHECK(slice2.data() + slice2.size() == slice3.data());
|
||||
// Check that the ptr of the first byte of slice1 is the same as the ptr
|
||||
// of the first byte of the buffer
|
||||
CHECK(slice1.data() == buffer.mAllLedsBufferUint8.get());
|
||||
// check that the start address is aligned to 4 bytes
|
||||
CHECK((reinterpret_cast<uintptr_t>(slice1.data()) & 0x3) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Complex test where all strip data is confirmed to be inside the "
|
||||
"buffer block") {
|
||||
buffer.onQueuingStart();
|
||||
buffer.queue(DrawItem(1, 10, true));
|
||||
buffer.queue(DrawItem(2, 11, false));
|
||||
buffer.queue(DrawItem(3, 12, true));
|
||||
buffer.queue(DrawItem(4, 13, false));
|
||||
buffer.queue(DrawItem(5, 14, true));
|
||||
buffer.queue(DrawItem(6, 15, false));
|
||||
buffer.queue(DrawItem(7, 16, true));
|
||||
buffer.queue(DrawItem(8, 17, false));
|
||||
buffer.queue(DrawItem(9, 18, true));
|
||||
buffer.onQueuingDone();
|
||||
CHECK(buffer.mPinToLedSegment.size() == 9);
|
||||
|
||||
uint32_t expected_max_strip_bytes = Rgbw::size_as_rgb(18) * 3;
|
||||
uint32_t actual_max_strip_bytes = buffer.getMaxBytesInStrip();
|
||||
CHECK(actual_max_strip_bytes == expected_max_strip_bytes);
|
||||
|
||||
uint32_t expected_total_bytes = expected_max_strip_bytes * 9;
|
||||
uint32_t actual_total_bytes = buffer.getTotalBytes();
|
||||
CHECK(actual_total_bytes == expected_total_bytes);
|
||||
|
||||
uint8_t pins[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
for (uint8_t pin : pins) {
|
||||
fl::Slice<uint8_t> slice =
|
||||
buffer.getLedsBufferBytesForPin(pin, true);
|
||||
CHECK(slice.size() == expected_max_strip_bytes);
|
||||
const uint8_t *first_address = &slice.front();
|
||||
const uint8_t *last_address = &slice.back();
|
||||
// check that they are both in the buffer
|
||||
CHECK(first_address >= buffer.mAllLedsBufferUint8.get());
|
||||
CHECK(first_address <= buffer.mAllLedsBufferUint8.get() +
|
||||
buffer.mAllLedsBufferUint8Size);
|
||||
CHECK(last_address >= buffer.mAllLedsBufferUint8.get());
|
||||
CHECK(last_address <= buffer.mAllLedsBufferUint8.get() +
|
||||
buffer.mAllLedsBufferUint8Size);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("I2S test where we load 16 X 256 leds") {
|
||||
buffer.onQueuingStart();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
buffer.queue(DrawItem(i, 256, false));
|
||||
}
|
||||
buffer.onQueuingDone();
|
||||
CHECK(buffer.mPinToLedSegment.size() == 16);
|
||||
for (uint32_t i = 0; i < buffer.mAllLedsBufferUint8Size; ++i) {
|
||||
buffer.mAllLedsBufferUint8[i] = i % 256;
|
||||
}
|
||||
}
|
||||
};
|
||||
234
libraries/FastLED/tests/test_refptr.cpp
Normal file
234
libraries/FastLED/tests/test_refptr.cpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/ptr.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
class MyClass;
|
||||
typedef Ptr<MyClass> MyClassPtr;
|
||||
class MyClass : public fl::Referent {
|
||||
public:
|
||||
MyClass() {}
|
||||
~MyClass() {
|
||||
destructor_signal = 0xdeadbeef;
|
||||
}
|
||||
virtual void ref() const override { Referent::ref(); }
|
||||
virtual void unref() const override { Referent::unref(); }
|
||||
virtual void destroy() const override { Referent::destroy(); }
|
||||
uint32_t destructor_signal = 0;
|
||||
};
|
||||
|
||||
TEST_CASE("Ptr basic functionality") {
|
||||
Ptr<MyClass> ptr = MyClassPtr::New();
|
||||
|
||||
SUBCASE("Ptr is not null after construction") {
|
||||
CHECK(ptr.get() != nullptr);
|
||||
}
|
||||
|
||||
SUBCASE("Ptr increments reference count") {
|
||||
CHECK(ptr->ref_count() == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Ptr can be reassigned") {
|
||||
Ptr<MyClass> ptr2 = ptr;
|
||||
CHECK(ptr2.get() == ptr.get());
|
||||
CHECK(ptr->ref_count() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Ptr move semantics") {
|
||||
|
||||
SUBCASE("Move constructor works correctly") {
|
||||
Ptr<MyClass> ptr1 = MyClassPtr::New();
|
||||
MyClass *rawPtr = ptr1.get();
|
||||
Ptr<MyClass> ptr2(std::move(ptr1));
|
||||
CHECK(ptr2.get() == rawPtr);
|
||||
CHECK(ptr1.get() == nullptr);
|
||||
CHECK(ptr2->ref_count() == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Move assignment works correctly") {
|
||||
Ptr<MyClass> ptr1 = MyClassPtr::New();
|
||||
MyClass *rawPtr = ptr1.get();
|
||||
Ptr<MyClass> ptr2;
|
||||
ptr2 = std::move(ptr1);
|
||||
CHECK(ptr2.get() == rawPtr);
|
||||
CHECK(ptr1.get() == nullptr);
|
||||
CHECK(ptr2->ref_count() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Ptr reference counting") {
|
||||
|
||||
SUBCASE("Reference count increases when copied") {
|
||||
Ptr<MyClass> ptr1 = MyClassPtr::New();
|
||||
Ptr<MyClass> ptr2 = ptr1;
|
||||
CHECK(ptr1->ref_count() == 2);
|
||||
CHECK(ptr2->ref_count() == 2);
|
||||
}
|
||||
|
||||
SUBCASE("Reference count decreases when Ptr goes out of scope") {
|
||||
Ptr<MyClass> ptr1 = MyClassPtr::New();
|
||||
{
|
||||
Ptr<MyClass> ptr2 = ptr1;
|
||||
CHECK(ptr1->ref_count() == 2);
|
||||
}
|
||||
CHECK(ptr1->ref_count() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("Ptr reset functionality") {
|
||||
|
||||
SUBCASE("Reset to nullptr") {
|
||||
Ptr<MyClass> ptr = Ptr<MyClass>::New();
|
||||
CHECK_EQ(1, ptr->ref_count());
|
||||
ptr->ref();
|
||||
CHECK_EQ(2, ptr->ref_count());
|
||||
MyClass *originalPtr = ptr.get();
|
||||
ptr.reset();
|
||||
CHECK_EQ(1, originalPtr->ref_count());
|
||||
CHECK(ptr.get() == nullptr);
|
||||
CHECK(originalPtr->ref_count() == 1);
|
||||
originalPtr->unref();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Ptr from static memory") {
|
||||
MyClass staticObject;
|
||||
{
|
||||
MyClassPtr ptr = MyClassPtr::NoTracking(staticObject);
|
||||
}
|
||||
CHECK_EQ(staticObject.ref_count(), 0);
|
||||
CHECK_NE(staticObject.destructor_signal, 0xdeadbeef);
|
||||
}
|
||||
|
||||
TEST_CASE("WeakPtr functionality") {
|
||||
WeakPtr<MyClass> weakPtr;
|
||||
MyClassPtr strongPtr = MyClassPtr::New();
|
||||
weakPtr = strongPtr;
|
||||
|
||||
REQUIRE_EQ(strongPtr->ref_count(), 1);
|
||||
bool expired = weakPtr.expired();
|
||||
REQUIRE_FALSE(expired);
|
||||
REQUIRE(weakPtr != nullptr);
|
||||
|
||||
SUBCASE("WeakPtr can be locked to get a strong reference") {
|
||||
MyClassPtr lockedPtr = weakPtr.lock();
|
||||
CHECK(lockedPtr.get() == strongPtr.get());
|
||||
CHECK_EQ(strongPtr->ref_count(), 2);
|
||||
}
|
||||
weakPtr.reset();
|
||||
expired = weakPtr.expired();
|
||||
CHECK(expired);
|
||||
}
|
||||
|
||||
TEST_CASE("WeakPtr functionality early expiration") {
|
||||
WeakPtr<MyClass> weakPtr;
|
||||
MyClassPtr strongPtr = MyClassPtr::New();
|
||||
weakPtr = strongPtr;
|
||||
|
||||
REQUIRE_EQ(strongPtr->ref_count(), 1);
|
||||
bool expired = weakPtr.expired();
|
||||
REQUIRE_FALSE(expired);
|
||||
REQUIRE(weakPtr != nullptr);
|
||||
|
||||
SUBCASE("WeakPtr can be locked to get a strong reference") {
|
||||
MyClassPtr lockedPtr = weakPtr.lock();
|
||||
CHECK(lockedPtr.get() == strongPtr.get());
|
||||
CHECK_EQ(strongPtr->ref_count(), 2);
|
||||
}
|
||||
strongPtr.reset();
|
||||
expired = weakPtr.expired();
|
||||
CHECK(expired);
|
||||
}
|
||||
|
||||
TEST_CASE("WeakPtr additional functionality") {
|
||||
SUBCASE("WeakPtr default constructor") {
|
||||
WeakPtr<MyClass> weakPtr;
|
||||
CHECK(weakPtr.expired());
|
||||
CHECK(weakPtr.lock() == nullptr);
|
||||
}
|
||||
|
||||
SUBCASE("WeakPtr assignment and reset") {
|
||||
MyClassPtr strongRef1 = MyClassPtr::New();
|
||||
MyClassPtr strongRef2 = MyClassPtr::New();
|
||||
WeakPtr<MyClass> weakPtr = strongRef1;
|
||||
|
||||
CHECK_FALSE(weakPtr.expired());
|
||||
CHECK(weakPtr.lock().get() == strongRef1.get());
|
||||
|
||||
weakPtr = strongRef2;
|
||||
CHECK_FALSE(weakPtr.expired());
|
||||
CHECK(weakPtr.lock().get() == strongRef2.get());
|
||||
|
||||
weakPtr.reset();
|
||||
CHECK(weakPtr.expired());
|
||||
CHECK(weakPtr.lock() == nullptr);
|
||||
}
|
||||
|
||||
SUBCASE("WeakPtr multiple instances") {
|
||||
MyClassPtr strongPtr = MyClassPtr::New();
|
||||
WeakPtr<MyClass> weakRef1 = strongPtr;
|
||||
WeakPtr<MyClass> weakRef2 = strongPtr;
|
||||
|
||||
CHECK_FALSE(weakRef1.expired());
|
||||
CHECK_FALSE(weakRef2.expired());
|
||||
CHECK(weakRef1.lock().get() == weakRef2.lock().get());
|
||||
|
||||
strongPtr.reset();
|
||||
CHECK(weakRef1.expired());
|
||||
CHECK(weakRef2.expired());
|
||||
}
|
||||
|
||||
SUBCASE("WeakPtr with temporary strong pointer") {
|
||||
WeakPtr<MyClass> weakPtr;
|
||||
{
|
||||
MyClassPtr tempStrongPtr = MyClassPtr::New();
|
||||
weakPtr = tempStrongPtr;
|
||||
CHECK_FALSE(weakPtr.expired());
|
||||
}
|
||||
CHECK(weakPtr.expired());
|
||||
}
|
||||
|
||||
SUBCASE("WeakPtr lock performance") {
|
||||
MyClassPtr strongPtr = MyClassPtr::New();
|
||||
WeakPtr<MyClass> weakPtr = strongPtr;
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
MyClassPtr lockedPtr = weakPtr.lock();
|
||||
CHECK(lockedPtr.get() == strongPtr.get());
|
||||
}
|
||||
CHECK_EQ(strongPtr->ref_count(), 1);
|
||||
}
|
||||
|
||||
SUBCASE("WeakPtr with inheritance") {
|
||||
class DerivedClass : public MyClass {};
|
||||
Ptr<DerivedClass> derivedPtr = Ptr<DerivedClass>::New();
|
||||
WeakPtr<MyClass> weakBasePtr = derivedPtr;
|
||||
WeakPtr<DerivedClass> weakDerivedPtr = derivedPtr;
|
||||
|
||||
CHECK_FALSE(weakBasePtr.expired());
|
||||
CHECK_FALSE(weakDerivedPtr.expired());
|
||||
CHECK(weakBasePtr.lock().get() == weakDerivedPtr.lock().get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("PtrNew") {
|
||||
MyClassPtr ptr = NewPtr<MyClass>(); // compile test.
|
||||
CHECK(ptr.get() != nullptr);
|
||||
|
||||
MyClass stack_objects;
|
||||
MyClassPtr stack_ptr = NewPtrNoTracking<MyClass>(stack_objects);
|
||||
CHECK(stack_ptr.get() == &stack_objects);
|
||||
}
|
||||
159
libraries/FastLED/tests/test_screenmap.cpp
Normal file
159
libraries/FastLED/tests/test_screenmap.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/screenmap.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
using fl::Str;
|
||||
|
||||
TEST_CASE("ScreenMap basic functionality") {
|
||||
// Create a screen map for 3 LEDs
|
||||
ScreenMap map(3);
|
||||
|
||||
// Set some x,y coordinates
|
||||
map.set(0, {1.0f, 2.0f});
|
||||
map.set(1, {3.0f, 4.0f});
|
||||
map.set(2, {5.0f, 6.0f});
|
||||
|
||||
// Test coordinate retrieval
|
||||
CHECK(map[0].x == 1.0f);
|
||||
CHECK(map[0].y == 2.0f);
|
||||
CHECK(map[1].x == 3.0f);
|
||||
CHECK(map[1].y == 4.0f);
|
||||
CHECK(map[2].x == 5.0f);
|
||||
CHECK(map[2].y == 6.0f);
|
||||
|
||||
// Test length
|
||||
CHECK(map.getLength() == 3);
|
||||
|
||||
// Test diameter (default should be -1.0)
|
||||
CHECK(map.getDiameter() == -1.0f);
|
||||
|
||||
// Test mapToIndex (should give same results as operator[])
|
||||
auto coords = map.mapToIndex(1);
|
||||
CHECK(coords.x == 3.0f);
|
||||
CHECK(coords.y == 4.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("ScreenMap JSON parsing") {
|
||||
const char* json = R"({
|
||||
"map": {
|
||||
"strip1": {
|
||||
"x": [10.5, 30.5, 50.5],
|
||||
"y": [20.5, 40.5, 60.5],
|
||||
"diameter": 2.5
|
||||
},
|
||||
"strip2": {
|
||||
"x": [15.0, 35.0],
|
||||
"y": [25.0, 45.0],
|
||||
"diameter": 1.5
|
||||
}
|
||||
|
||||
}
|
||||
})";
|
||||
|
||||
fl::FixedMap<Str, ScreenMap, 16> segmentMaps;
|
||||
ScreenMap::ParseJson(json, &segmentMaps);
|
||||
|
||||
ScreenMap& strip1 = segmentMaps["strip1"];
|
||||
ScreenMap& strip2 = segmentMaps["strip2"];
|
||||
|
||||
// Check first strip
|
||||
|
||||
CHECK(strip1.getLength() == 3);
|
||||
CHECK(strip1.getDiameter() == 2.5f);
|
||||
CHECK(strip1[0].x == 10.5f);
|
||||
CHECK(strip1[0].y == 20.5f);
|
||||
CHECK(strip1[1].x == 30.5f);
|
||||
CHECK(strip1[1].y == 40.5f);
|
||||
|
||||
// Check second strip
|
||||
CHECK(strip2.getLength() == 2);
|
||||
CHECK(strip2.getDiameter() == 1.5f);
|
||||
CHECK(strip2[0].x == 15.0f);
|
||||
CHECK(strip2[0].y == 25.0f);
|
||||
CHECK(strip2[1].x == 35.0f);
|
||||
CHECK(strip2[1].y == 45.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("ScreenMap multiple strips JSON serialization") {
|
||||
// Create a map with multiple strips
|
||||
fl::FixedMap<Str, ScreenMap, 16> originalMaps;
|
||||
|
||||
// First strip
|
||||
ScreenMap strip1(2, 2.0f);
|
||||
strip1.set(0, {1.0f, 2.0f});
|
||||
strip1.set(1, {3.0f, 4.0f});
|
||||
originalMaps.insert("strip1", strip1);
|
||||
|
||||
// Second strip
|
||||
ScreenMap strip2(3, 1.5f);
|
||||
strip2.set(0, {10.0f, 20.0f});
|
||||
strip2.set(1, {30.0f, 40.0f});
|
||||
strip2.set(2, {50.0f, 60.0f});
|
||||
originalMaps.insert("strip2", strip2);
|
||||
|
||||
// Serialize to JSON string
|
||||
Str jsonStr;
|
||||
ScreenMap::toJsonStr(originalMaps, &jsonStr);
|
||||
|
||||
// Deserialize back to a new map
|
||||
fl::FixedMap<Str, ScreenMap, 16> deserializedMaps;
|
||||
ScreenMap::ParseJson(jsonStr.c_str(), &deserializedMaps);
|
||||
|
||||
// Verify first strip
|
||||
ScreenMap& deserializedStrip1 = deserializedMaps["strip1"];
|
||||
CHECK(deserializedStrip1.getLength() == 2);
|
||||
CHECK(deserializedStrip1.getDiameter() == 2.0f);
|
||||
CHECK(deserializedStrip1[0].x == 1.0f);
|
||||
CHECK(deserializedStrip1[0].y == 2.0f);
|
||||
CHECK(deserializedStrip1[1].x == 3.0f);
|
||||
CHECK(deserializedStrip1[1].y == 4.0f);
|
||||
|
||||
// Verify second strip
|
||||
ScreenMap& deserializedStrip2 = deserializedMaps["strip2"];
|
||||
CHECK(deserializedStrip2.getLength() == 3);
|
||||
CHECK(deserializedStrip2.getDiameter() == 1.5f);
|
||||
CHECK(deserializedStrip2[0].x == 10.0f);
|
||||
CHECK(deserializedStrip2[0].y == 20.0f);
|
||||
CHECK(deserializedStrip2[1].x == 30.0f);
|
||||
CHECK(deserializedStrip2[1].y == 40.0f);
|
||||
CHECK(deserializedStrip2[2].x == 50.0f);
|
||||
CHECK(deserializedStrip2[2].y == 60.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("ScreenMap getBounds functionality") {
|
||||
// Create a screen map with points at different coordinates
|
||||
ScreenMap map(4);
|
||||
map.set(0, {1.0f, 2.0f});
|
||||
map.set(1, {-3.0f, 4.0f});
|
||||
map.set(2, {5.0f, -6.0f});
|
||||
map.set(3, {-2.0f, -1.0f});
|
||||
|
||||
// Get the bounds
|
||||
vec2f bounds = map.getBounds();
|
||||
|
||||
// The bounds should be the difference between max and min values
|
||||
// Max X: 5.0, Min X: -3.0 => Width = 8.0
|
||||
// Max Y: 4.0, Min Y: -6.0 => Height = 10.0
|
||||
CHECK(bounds.x == 8.0f);
|
||||
CHECK(bounds.y == 10.0f);
|
||||
|
||||
// Test with a single point
|
||||
ScreenMap singlePoint(1);
|
||||
singlePoint.set(0, {3.5f, 4.5f});
|
||||
vec2f singleBounds = singlePoint.getBounds();
|
||||
CHECK(singleBounds.x == 0.0f);
|
||||
CHECK(singleBounds.y == 0.0f);
|
||||
|
||||
// Test with an empty map
|
||||
ScreenMap emptyMap(0);
|
||||
vec2f emptyBounds = emptyMap.getBounds();
|
||||
CHECK(emptyBounds.x == 0.0f);
|
||||
CHECK(emptyBounds.y == 0.0f);
|
||||
}
|
||||
33
libraries/FastLED/tests/test_sin32.cpp
Normal file
33
libraries/FastLED/tests/test_sin32.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/sin32.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
// 16777216 is 1 cycle
|
||||
const uint32_t _360 = 16777216;
|
||||
const uint32_t _ONE = 2147418112;
|
||||
const uint32_t _NEG_ONE = -2147418112;
|
||||
|
||||
TEST_CASE("compile test") {
|
||||
int32_t result = sin32(0);
|
||||
REQUIRE(result == 0);
|
||||
|
||||
result = sin32(_360);
|
||||
REQUIRE(result == 0);
|
||||
|
||||
result = sin32(_360 / 4);
|
||||
REQUIRE(result == _ONE);
|
||||
|
||||
result = sin32(_360 / 2);
|
||||
REQUIRE(result == 0);
|
||||
|
||||
result = sin32(_360 / 4 * 3);
|
||||
REQUIRE(result == _NEG_ONE);
|
||||
|
||||
}
|
||||
97
libraries/FastLED/tests/test_slice.cpp
Normal file
97
libraries/FastLED/tests/test_slice.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/slice.h"
|
||||
#include "fl/unused.h"
|
||||
#include "fl/vector.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("vector slice") {
|
||||
HeapVector<int> vec;
|
||||
|
||||
vec.push_back(1);
|
||||
vec.push_back(2);
|
||||
vec.push_back(3);
|
||||
vec.push_back(4);
|
||||
|
||||
Slice<int> slice(vec.data(), vec.size());
|
||||
|
||||
REQUIRE_EQ(slice.length(), 4);
|
||||
REQUIRE_EQ(slice[0], 1);
|
||||
REQUIRE_EQ(slice[1], 2);
|
||||
REQUIRE_EQ(slice[2], 3);
|
||||
REQUIRE_EQ(slice[3], 4);
|
||||
|
||||
Slice<int> slice2 = slice.slice(1, 3);
|
||||
REQUIRE_EQ(slice2.length(), 2);
|
||||
REQUIRE_EQ(slice2[0], 2);
|
||||
REQUIRE_EQ(slice2[1], 3);
|
||||
}
|
||||
|
||||
TEST_CASE("matrix compile") {
|
||||
int data[2][2] = {{1, 2}, {3, 4}};
|
||||
|
||||
// Window from (0,0) up to (1,1)
|
||||
MatrixSlice<int> slice(&data[0][0], // data pointer
|
||||
2, // data width
|
||||
2, // data height
|
||||
0, 0, // bottom-left x,y
|
||||
1, 1 // top-right x,y
|
||||
);
|
||||
|
||||
FASTLED_UNUSED(slice);
|
||||
// just a compile‐time smoke test
|
||||
}
|
||||
|
||||
TEST_CASE("matrix slice returns correct values") {
|
||||
int data[2][2] = {{1, 2}, {3, 4}};
|
||||
|
||||
// Window from (0,0) up to (1,1)
|
||||
MatrixSlice<int> slice(&data[0][0], // data pointer
|
||||
2, // data width
|
||||
2, // data height
|
||||
0, 0, // bottom-left x,y
|
||||
1, 1 // top-right x,y
|
||||
);
|
||||
|
||||
// sanity‐check each element
|
||||
REQUIRE_EQ(slice(0, 0), data[0][0]);
|
||||
REQUIRE_EQ(slice(1, 0), data[0][1]);
|
||||
REQUIRE_EQ(slice(0, 1), data[1][0]);
|
||||
REQUIRE_EQ(slice(1, 1), data[1][1]);
|
||||
|
||||
// Require that the [][] operator works the same as the data
|
||||
REQUIRE_EQ(slice[0][0], data[0][0]);
|
||||
REQUIRE_EQ(slice[0][1], data[0][1]);
|
||||
REQUIRE_EQ(slice[1][0], data[1][0]);
|
||||
REQUIRE_EQ(slice[1][1], data[1][1]);
|
||||
}
|
||||
|
||||
TEST_CASE("4x4 matrix slice returns correct values") {
|
||||
int data[4][4] = {
|
||||
{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
|
||||
|
||||
// Take a 2×2 window from (1,1) up to (2,2)
|
||||
MatrixSlice<int> slice(&data[0][0], // data pointer
|
||||
4, // data width
|
||||
4, // data height
|
||||
1, 1, // bottom-left x,y
|
||||
2, 2 // top-right x,y
|
||||
);
|
||||
|
||||
// test array access
|
||||
REQUIRE_EQ(slice[0][0], data[1][1]);
|
||||
REQUIRE_EQ(slice[0][1], data[1][2]);
|
||||
REQUIRE_EQ(slice[1][0], data[2][1]);
|
||||
REQUIRE_EQ(slice[1][1], data[2][2]);
|
||||
|
||||
// Remember that array access is row-major, so data[y][x] == slice(x,y)
|
||||
REQUIRE_EQ(slice(0, 0), data[1][1]);
|
||||
REQUIRE_EQ(slice(1, 0), data[1][2]);
|
||||
REQUIRE_EQ(slice(0, 1), data[2][1]);
|
||||
REQUIRE_EQ(slice(1, 1), data[2][2]);
|
||||
}
|
||||
151
libraries/FastLED/tests/test_splat.cpp
Normal file
151
libraries/FastLED/tests/test_splat.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/splat.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("splat simple test") {
|
||||
// Define a simple input coordinate
|
||||
vec2f input(0, 0);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
|
||||
// Verify the output
|
||||
REQUIRE_EQ(result.lower_left(), 255); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 0); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 0); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (0.0, 0.5)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(0.0f, 0.5f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 128); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 0); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 128); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (0.0, 0.99)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(0.0f, 0.99f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 3); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 0); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 252); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (0.0, 1.0)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(0.0f, 1.0f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 1);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 3);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 255); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 0); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 0); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (0.5, 0.0)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(0.5f, 0.0f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 128); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 128); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 0); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (0.99, 0.0)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(0.99f, 0.0f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 0);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 2);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 3); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 252); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 0); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
|
||||
TEST_CASE("splat test for input (1.0, 0.0)") {
|
||||
// Define the input coordinate
|
||||
vec2f input(1.0f, 0.0f);
|
||||
|
||||
// Call the splat function
|
||||
Tile2x2_u8 result = splat(input);
|
||||
|
||||
// Verify the bounds of the tile
|
||||
REQUIRE(result.bounds().mMin.x == 1);
|
||||
REQUIRE(result.bounds().mMin.y == 0);
|
||||
REQUIRE(result.bounds().mMax.x == 3);
|
||||
REQUIRE(result.bounds().mMax.y == 2);
|
||||
|
||||
// Verify the output intensities
|
||||
REQUIRE_EQ(result.lower_left(), 255); // Expected intensity for lower-left
|
||||
REQUIRE_EQ(result.lower_right(), 0); // Expected intensity for lower-right
|
||||
REQUIRE_EQ(result.upper_left(), 0); // Expected intensity for upper-left
|
||||
REQUIRE_EQ(result.upper_right(), 0); // Expected intensity for upper-right
|
||||
}
|
||||
147
libraries/FastLED/tests/test_str.cpp
Normal file
147
libraries/FastLED/tests/test_str.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/vector.h"
|
||||
#include "crgb.h"
|
||||
#include <sstream>
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Str basic operations") {
|
||||
SUBCASE("Construction and assignment") {
|
||||
Str s1;
|
||||
CHECK(s1.size() == 0);
|
||||
CHECK(s1.c_str()[0] == '\0');
|
||||
|
||||
Str s2("hello");
|
||||
CHECK(s2.size() == 5);
|
||||
CHECK(strcmp(s2.c_str(), "hello") == 0);
|
||||
|
||||
Str s3 = s2;
|
||||
CHECK(s3.size() == 5);
|
||||
CHECK(strcmp(s3.c_str(), "hello") == 0);
|
||||
|
||||
s1 = "world";
|
||||
CHECK(s1.size() == 5);
|
||||
CHECK(strcmp(s1.c_str(), "world") == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Comparison operators") {
|
||||
Str s1("hello");
|
||||
Str s2("hello");
|
||||
Str s3("world");
|
||||
|
||||
CHECK(s1 == s2);
|
||||
CHECK(s1 != s3);
|
||||
}
|
||||
|
||||
SUBCASE("Indexing") {
|
||||
Str s("hello");
|
||||
CHECK(s[0] == 'h');
|
||||
CHECK(s[4] == 'o');
|
||||
CHECK(s[5] == '\0'); // Null terminator
|
||||
}
|
||||
|
||||
SUBCASE("Append") {
|
||||
Str s("hello");
|
||||
s.append(" world");
|
||||
CHECK(s.size() == 11);
|
||||
CHECK(strcmp(s.c_str(), "hello world") == 0);
|
||||
}
|
||||
|
||||
SUBCASE("CRGB to Str") {
|
||||
CRGB c(255, 0, 0);
|
||||
Str s = c.toString();
|
||||
CHECK_EQ(s, "CRGB(255,0,0)");
|
||||
}
|
||||
|
||||
SUBCASE("Copy-on-write behavior") {
|
||||
Str s1("hello");
|
||||
Str s2 = s1;
|
||||
s2.append(" world");
|
||||
CHECK(strcmp(s1.c_str(), "hello") == 0);
|
||||
CHECK(strcmp(s2.c_str(), "hello world") == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Str::reserve") {
|
||||
Str s;
|
||||
s.reserve(10);
|
||||
CHECK(s.size() == 0);
|
||||
CHECK(s.capacity() >= 10);
|
||||
|
||||
s.reserve(5);
|
||||
CHECK(s.size() == 0);
|
||||
CHECK(s.capacity() >= 10);
|
||||
|
||||
s.reserve(500);
|
||||
CHECK(s.size() == 0);
|
||||
CHECK(s.capacity() >= 500);
|
||||
// s << "hello";
|
||||
s.append("hello");
|
||||
CHECK(s.size() == 5);
|
||||
CHECK_EQ(s, "hello");
|
||||
}
|
||||
|
||||
TEST_CASE("Str with fl::FixedVector") {
|
||||
fl::FixedVector<Str, 10> vec;
|
||||
vec.push_back(Str("hello"));
|
||||
vec.push_back(Str("world"));
|
||||
|
||||
CHECK(vec.size() == 2);
|
||||
CHECK(strcmp(vec[0].c_str(), "hello") == 0);
|
||||
CHECK(strcmp(vec[1].c_str(), "world") == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Str with long strings") {
|
||||
const char* long_string = "This is a very long string that exceeds the inline buffer size and should be allocated on the heap";
|
||||
Str s(long_string);
|
||||
CHECK(s.size() == strlen(long_string));
|
||||
CHECK(strcmp(s.c_str(), long_string) == 0);
|
||||
|
||||
Str s2 = s;
|
||||
CHECK(s2.size() == strlen(long_string));
|
||||
CHECK(strcmp(s2.c_str(), long_string) == 0);
|
||||
|
||||
s2.append(" with some additional text");
|
||||
CHECK(strcmp(s.c_str(), long_string) == 0); // Original string should remain unchanged
|
||||
}
|
||||
|
||||
TEST_CASE("Str overflowing inline data") {
|
||||
SUBCASE("Construction with long string") {
|
||||
std::string long_string(FASTLED_STR_INLINED_SIZE + 10, 'a'); // Create a string longer than the inline buffer
|
||||
Str s(long_string.c_str());
|
||||
CHECK(s.size() == long_string.length());
|
||||
CHECK(strcmp(s.c_str(), long_string.c_str()) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Appending to overflow") {
|
||||
Str s("Short string");
|
||||
std::string append_string(FASTLED_STR_INLINED_SIZE, 'b'); // String to append that will cause overflow
|
||||
s.append(append_string.c_str());
|
||||
CHECK(s.size() == strlen("Short string") + append_string.length());
|
||||
CHECK(s[0] == 'S');
|
||||
CHECK(s[s.size() - 1] == 'b');
|
||||
}
|
||||
|
||||
SUBCASE("Copy on write with long string") {
|
||||
std::string long_string(FASTLED_STR_INLINED_SIZE + 20, 'c');
|
||||
Str s1(long_string.c_str());
|
||||
Str s2 = s1;
|
||||
CHECK(s1.size() == s2.size());
|
||||
CHECK(strcmp(s1.c_str(), s2.c_str()) == 0);
|
||||
|
||||
s2.append("extra");
|
||||
CHECK(s1.size() == long_string.length());
|
||||
CHECK(s2.size() == long_string.length() + 5);
|
||||
CHECK(strcmp(s1.c_str(), long_string.c_str()) == 0);
|
||||
CHECK(s2[s2.size() - 1] == 'a');
|
||||
}
|
||||
}
|
||||
45
libraries/FastLED/tests/test_strip_id_map.cpp
Normal file
45
libraries/FastLED/tests/test_strip_id_map.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "cled_controller.h"
|
||||
#include "platforms/wasm/strip_id_map.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
struct FakeSpi {
|
||||
int value = 0;
|
||||
};
|
||||
|
||||
class FakeCLedController : public CLEDController {
|
||||
public:
|
||||
FakeSpi fake_spi;
|
||||
virtual void showColor(const CRGB &data, int nLeds,
|
||||
uint8_t brightness) override {}
|
||||
|
||||
virtual void show(const struct CRGB *data, int nLeds,
|
||||
uint8_t brightness) override {}
|
||||
virtual void init() override {}
|
||||
};
|
||||
|
||||
TEST_CASE("StripIdMap Simple Test") {
|
||||
StripIdMap::test_clear();
|
||||
FakeCLedController fake_controller;
|
||||
int id = StripIdMap::addOrGetId(&fake_controller);
|
||||
CHECK(id == 0);
|
||||
CLEDController *owner = StripIdMap::getOwner(id);
|
||||
CLEDController *match = &fake_controller;
|
||||
printf("Owner: %p, Match: %p\n", owner, match);
|
||||
CHECK_EQ(owner, match);
|
||||
CHECK(StripIdMap::getId(&fake_controller) == 0);
|
||||
id = StripIdMap::getOrFindByAddress(reinterpret_cast<uintptr_t>(&fake_controller));
|
||||
CHECK(id == 0);
|
||||
id = StripIdMap::getOrFindByAddress(reinterpret_cast<uintptr_t>(&fake_controller.fake_spi));
|
||||
CHECK(id == 0);
|
||||
|
||||
}
|
||||
66
libraries/FastLED/tests/test_strstream.cpp
Normal file
66
libraries/FastLED/tests/test_strstream.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/str.h"
|
||||
#include "fl/strstream.h"
|
||||
#include "fl/vector.h"
|
||||
#include "crgb.h"
|
||||
#include <sstream>
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("StrStream basic operations") {
|
||||
SUBCASE("Construction and assignment") {
|
||||
StrStream s;
|
||||
CHECK(s.str().size() == 0);
|
||||
CHECK(s.str().c_str()[0] == '\0');
|
||||
|
||||
StrStream s2("hello");
|
||||
CHECK(s2.str().size() == 5);
|
||||
CHECK(strcmp(s2.str().c_str(), "hello") == 0);
|
||||
|
||||
StrStream s3 = s2;
|
||||
CHECK(s3.str().size() == 5);
|
||||
CHECK(strcmp(s3.str().c_str(), "hello") == 0);
|
||||
|
||||
s = "world";
|
||||
CHECK(s.str().size() == 5);
|
||||
CHECK(strcmp(s.str().c_str(), "world") == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Comparison operators") {
|
||||
StrStream s1("hello");
|
||||
StrStream s2("hello");
|
||||
StrStream s3("world");
|
||||
|
||||
CHECK(s1.str() == s2.str());
|
||||
CHECK(s1.str() != s3.str());
|
||||
}
|
||||
|
||||
SUBCASE("Indexing") {
|
||||
StrStream s("hello");
|
||||
CHECK(s.str()[0] == 'h');
|
||||
CHECK(s.str()[4] == 'o');
|
||||
CHECK(s.str()[5] == '\0'); // Null terminator
|
||||
}
|
||||
|
||||
SUBCASE("Append") {
|
||||
StrStream s("hello");
|
||||
s << " world";
|
||||
CHECK(s.str().size() == 11);
|
||||
CHECK(strcmp(s.str().c_str(), "hello world") == 0);
|
||||
}
|
||||
|
||||
SUBCASE("CRGB to StrStream") {
|
||||
CRGB c(255, 0, 0);
|
||||
StrStream s;
|
||||
s << c;
|
||||
CHECK(s.str().size() == 13); // "CRGB(255,0,0)" is 13 chars
|
||||
CHECK(strcmp(s.str().c_str(), "CRGB(255,0,0)") == 0);
|
||||
}
|
||||
}
|
||||
18
libraries/FastLED/tests/test_template_magic.cpp
Normal file
18
libraries/FastLED/tests/test_template_magic.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/template_magic.h"
|
||||
#include "fl/namespace.h"
|
||||
#include <type_traits>
|
||||
|
||||
class Base {};
|
||||
class Derived : public Base {};
|
||||
|
||||
TEST_CASE("is_base_of") {
|
||||
CHECK(fl::is_base_of<Base, Derived>::value);
|
||||
CHECK_FALSE(fl::is_base_of<Derived, Base>::value);
|
||||
}
|
||||
|
||||
74
libraries/FastLED/tests/test_transform.cpp
Normal file
74
libraries/FastLED/tests/test_transform.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/transform.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/unused.h"
|
||||
#include <string>
|
||||
|
||||
using namespace fl;
|
||||
|
||||
|
||||
TEST_CASE("Transform16::ToBounds(max_value)") {
|
||||
// Transform16 tx = Transform16::ToBounds(255);
|
||||
|
||||
SUBCASE("Check bounds at 128") {
|
||||
// known bad at i == 128
|
||||
Transform16 tx = Transform16::ToBounds(255);
|
||||
uint16_t i16 = map8_to_16(128);
|
||||
vec2<uint16_t> xy_input = vec2<uint16_t>(i16, i16);
|
||||
vec2<uint16_t> xy = tx.transform(xy_input);
|
||||
INFO("i = " << 128);
|
||||
REQUIRE_EQ(128, xy.x);
|
||||
REQUIRE_EQ(128, xy.y);
|
||||
}
|
||||
|
||||
SUBCASE("Check identity from 8 -> 16") {
|
||||
Transform16 tx = Transform16::ToBounds(255);
|
||||
for (uint16_t i = 0; i < 256; i++) {
|
||||
uint16_t i16 = map8_to_16(i);
|
||||
vec2<uint16_t> xy_input = vec2<uint16_t>(i16, i16);
|
||||
vec2<uint16_t> xy = tx.transform(xy_input);
|
||||
INFO("i = " << i);
|
||||
REQUIRE_EQ(i, xy.x);
|
||||
REQUIRE_EQ(i, xy.y);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Check all bounds are in 255") {
|
||||
Transform16 tx = Transform16::ToBounds(255);
|
||||
uint32_t smallest = ~0;
|
||||
uint32_t largest = 0;
|
||||
for (uint16_t i = 0; i < 256; i++) {
|
||||
uint16_t i16 = map8_to_16(i);
|
||||
vec2<uint16_t> xy_input = vec2<uint16_t>(i16, i16);
|
||||
vec2<uint16_t> xy = tx.transform(xy_input);
|
||||
INFO("i = " << i);
|
||||
REQUIRE_LE(xy.x, 255);
|
||||
REQUIRE_LE(xy.y, 255);
|
||||
smallest = MIN(smallest, xy.x);
|
||||
largest = MAX(largest, xy.x);
|
||||
}
|
||||
|
||||
REQUIRE_EQ(0, smallest);
|
||||
REQUIRE_EQ(255, largest);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Transform16::ToBounds(min, max)") {
|
||||
SUBCASE("Check bounds at 128") {
|
||||
uint16_t low = 127;
|
||||
uint16_t high = 255 + 127;
|
||||
vec2<uint16_t> min = vec2<uint16_t>(low, low);
|
||||
vec2<uint16_t> max = vec2<uint16_t>(high, high);
|
||||
Transform16 tx = Transform16::ToBounds(min, max);
|
||||
auto t1 = tx.transform(vec2<uint16_t>(0, 0));
|
||||
auto t2 = tx.transform(vec2<uint16_t>(0xffff, 0xffff));
|
||||
REQUIRE_EQ(vec2<uint16_t>(low, low), t1);
|
||||
REQUIRE_EQ(vec2<uint16_t>(high, high), t2);
|
||||
}
|
||||
}
|
||||
72
libraries/FastLED/tests/test_transition_ramp.cpp
Normal file
72
libraries/FastLED/tests/test_transition_ramp.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// test_transition_ramp.cpp
|
||||
// g++ --std=c++11 test_transition_ramp.cpp
|
||||
|
||||
#include "fl/namespace.h"
|
||||
#include "fl/time_alpha.h"
|
||||
#include "test.h"
|
||||
#include <cstdint>
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Test transition ramp") {
|
||||
// total latch = 100 ms, ramp‑up = 10 ms, ramp‑down = 10 ms
|
||||
TimeRamp ramp(10, 100, 10);
|
||||
uint32_t t0 = 0;
|
||||
ramp.trigger(t0);
|
||||
|
||||
// at start: still at zero
|
||||
REQUIRE(ramp.update8(t0) == 0);
|
||||
|
||||
// mid‑rise: 5 ms → (5*255/10) ≈ 127
|
||||
REQUIRE(ramp.update8(t0 + 5) == static_cast<uint8_t>((5 * 255) / 10));
|
||||
|
||||
// end of rise: 10 ms → full on
|
||||
REQUIRE(ramp.update8(t0 + 10) == 255);
|
||||
|
||||
// plateau: well within [10, 90) ms
|
||||
REQUIRE(ramp.update8(t0 + 50) == 255);
|
||||
|
||||
// mid‑fall: elapsed=95 ms → fallingElapsed=5 ms → 255 - (5*255/10) ≈ 128
|
||||
uint8_t value = ramp.update8(t0 + 115);
|
||||
uint8_t expected = static_cast<uint8_t>(255 - (5 * 255) / 10);
|
||||
REQUIRE_EQ(expected, value);
|
||||
|
||||
// after latch: 110 ms → off
|
||||
// REQUIRE(ramp.update8(t0 + 110) == 0);
|
||||
|
||||
// now do it again
|
||||
ramp.trigger(200);
|
||||
// at start: still at zero
|
||||
REQUIRE(ramp.update8(200) == 0);
|
||||
// mid‑rise: 205 ms → (5*255/10) ≈ 127
|
||||
REQUIRE(ramp.update8(205) == static_cast<uint8_t>((5 * 255) / 10));
|
||||
// end of rise: 210 ms → full on
|
||||
REQUIRE(ramp.update8(210) == 255);
|
||||
// plateau: well within [210, 290) ms
|
||||
REQUIRE(ramp.update8(250) == 255);
|
||||
// mid‑fall: elapsed=295 ms → fallingElapsed=5 ms → 255 - (5*255/10) ≈ 128
|
||||
REQUIRE(ramp.update8(315) == static_cast<uint8_t>(255 - (5 * 255) / 10));
|
||||
// after latch: 310 ms → off
|
||||
REQUIRE(ramp.update8(320) == 0);
|
||||
// after latch: 410 ms → off
|
||||
REQUIRE(ramp.update8(410) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Real world Bug") {
|
||||
TimeRamp transition = TimeRamp(500, 0, 500);
|
||||
|
||||
uint8_t value = transition.update8(0);
|
||||
CHECK(value == 0);
|
||||
value = transition.update8(1);
|
||||
CHECK(value == 0);
|
||||
|
||||
transition.trigger(6900);
|
||||
value = transition.update8(6900);
|
||||
REQUIRE_EQ(value, 0);
|
||||
|
||||
value = transition.update8(6900 + 500);
|
||||
REQUIRE_EQ(value, 255);
|
||||
|
||||
value = transition.update8(6900 + 250);
|
||||
REQUIRE_EQ(value, 127);
|
||||
}
|
||||
92
libraries/FastLED/tests/test_traverse_grid.cpp
Normal file
92
libraries/FastLED/tests/test_traverse_grid.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#include "fl/namespace.h"
|
||||
#include "fl/time_alpha.h"
|
||||
#include "fl/traverse_grid.h"
|
||||
#include "test.h"
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
using namespace fl;
|
||||
|
||||
struct CollectingVisitor {
|
||||
std::set<std::pair<int, int>> visited;
|
||||
|
||||
void visit(int x, int y) {
|
||||
visited.insert({x, y});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Traverse grid") {
|
||||
SUBCASE("Horizontal line") {
|
||||
vec2f start{1.2f, 2.5f};
|
||||
vec2f end{5.7f, 2.5f};
|
||||
|
||||
CollectingVisitor visitor;
|
||||
traverseGridSegment(start, end, visitor);
|
||||
|
||||
CHECK(visitor.visited.size() == 5);
|
||||
for (int x = 1; x <= 5; ++x) {
|
||||
CHECK(visitor.visited.count({x, 2}) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Vertical line") {
|
||||
vec2f start{3.4f, 1.1f};
|
||||
vec2f end{3.4f, 4.9f};
|
||||
|
||||
CollectingVisitor visitor;
|
||||
traverseGridSegment(start, end, visitor);
|
||||
|
||||
CHECK(visitor.visited.size() == 4);
|
||||
for (int y = 1; y <= 4; ++y) {
|
||||
CHECK(visitor.visited.count({3, y}) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SUBCASE("Forward diagonal") {
|
||||
vec2f start{1.1f, 1.1f};
|
||||
vec2f end{4.9f, 4.9f};
|
||||
|
||||
CollectingVisitor visitor;
|
||||
traverseGridSegment(start, end, visitor);
|
||||
|
||||
std::set<std::pair<int, int>> expected = {
|
||||
{1,1}, {1,2}, {2,2}, {2,3}, {3,3}, {3,4}, {4,4}
|
||||
};
|
||||
|
||||
CHECK(visitor.visited.size() == expected.size());
|
||||
for (const auto& cell : expected) {
|
||||
CHECK(visitor.visited.count(cell) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Backward diagonal") {
|
||||
vec2f start{4.9f, 1.1f};
|
||||
vec2f end{1.1f, 4.9f};
|
||||
|
||||
CollectingVisitor visitor;
|
||||
traverseGridSegment(start, end, visitor);
|
||||
|
||||
std::set<std::pair<int, int>> expected = {
|
||||
{4,1}, {4,2}, {3,2}, {3,3}, {2,3}, {2,4}, {1,4}
|
||||
};
|
||||
|
||||
CHECK(visitor.visited.size() == expected.size());
|
||||
for (const auto& cell : expected) {
|
||||
CHECK(visitor.visited.count(cell) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Single cell") {
|
||||
vec2f start{2.2f, 3.3f};
|
||||
vec2f end{2.2f, 3.3f};
|
||||
|
||||
CollectingVisitor visitor;
|
||||
traverseGridSegment(start, end, visitor);
|
||||
|
||||
CHECK(visitor.visited.size() == 1);
|
||||
CHECK(visitor.visited.count({2, 3}) == 1);
|
||||
}
|
||||
}
|
||||
150
libraries/FastLED/tests/test_type_traits.cpp
Normal file
150
libraries/FastLED/tests/test_type_traits.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/type_traits.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
// Forward declarations
|
||||
class TestClass;
|
||||
void takeLValue(TestClass & obj);
|
||||
void takeRValue(TestClass && obj);
|
||||
template <typename T> void forwardValue(T && obj);
|
||||
|
||||
TEST_CASE("is_integral<T> value") {
|
||||
// Test with integral types
|
||||
REQUIRE(is_integral<bool>::value);
|
||||
REQUIRE(is_integral<char>::value);
|
||||
REQUIRE(is_integral<signed char>::value);
|
||||
REQUIRE(is_integral<unsigned char>::value);
|
||||
REQUIRE(is_integral<int>::value);
|
||||
REQUIRE(is_integral<unsigned int>::value);
|
||||
REQUIRE(is_integral<short>::value);
|
||||
REQUIRE(is_integral<long>::value);
|
||||
REQUIRE(is_integral<long long>::value);
|
||||
|
||||
// Test with sized types
|
||||
REQUIRE(is_integral<int8_t>::value);
|
||||
REQUIRE(is_integral<uint8_t>::value);
|
||||
REQUIRE(is_integral<int16_t>::value);
|
||||
REQUIRE(is_integral<uint16_t>::value);
|
||||
REQUIRE(is_integral<int32_t>::value);
|
||||
REQUIRE(is_integral<uint32_t>::value);
|
||||
REQUIRE(is_integral<int64_t>::value);
|
||||
REQUIRE(is_integral<uint64_t>::value);
|
||||
|
||||
// Test with non-integral types
|
||||
REQUIRE_FALSE(is_integral<float>::value);
|
||||
REQUIRE_FALSE(is_integral<double>::value);
|
||||
REQUIRE_FALSE(is_integral<char *>::value);
|
||||
}
|
||||
|
||||
TEST_CASE("Test fl::move") {
|
||||
// Test with a simple class that tracks move operations
|
||||
class MoveTracker {
|
||||
public:
|
||||
MoveTracker() : moved_from(false) {}
|
||||
|
||||
// Copy constructor
|
||||
MoveTracker(const MoveTracker &other) : moved_from(false) {
|
||||
// Regular copy
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
MoveTracker(MoveTracker &&other) : moved_from(false) {
|
||||
other.moved_from = true;
|
||||
}
|
||||
|
||||
bool was_moved_from() const { return moved_from; }
|
||||
|
||||
private:
|
||||
bool moved_from;
|
||||
};
|
||||
|
||||
// Test 1: Basic move operation
|
||||
{
|
||||
MoveTracker original;
|
||||
REQUIRE_FALSE(original.was_moved_from());
|
||||
|
||||
// Use fl::move to trigger move constructor
|
||||
MoveTracker moved(fl::move(original));
|
||||
|
||||
// Original should be marked as moved from
|
||||
REQUIRE(original.was_moved_from());
|
||||
REQUIRE_FALSE(moved.was_moved_from());
|
||||
}
|
||||
|
||||
// Test 2: Move vs copy behavior
|
||||
{
|
||||
MoveTracker original;
|
||||
|
||||
// Regular copy - shouldn't mark original as moved
|
||||
MoveTracker copied(original);
|
||||
REQUIRE_FALSE(original.was_moved_from());
|
||||
|
||||
// Move should mark as moved
|
||||
MoveTracker moved(fl::move(original));
|
||||
REQUIRE(original.was_moved_from());
|
||||
}
|
||||
}
|
||||
|
||||
// A simple class to test with
|
||||
class TestClass {
|
||||
public:
|
||||
int value;
|
||||
TestClass() : value(0) {}
|
||||
TestClass(int v) : value(v) {}
|
||||
};
|
||||
|
||||
// Function that takes an lvalue reference
|
||||
void takeLValue(TestClass & obj) { obj.value = 42; }
|
||||
|
||||
// Function that takes an rvalue reference
|
||||
void takeRValue(TestClass && obj) { obj.value = 100; }
|
||||
|
||||
// Function template that forwards its argument to an lvalue reference function
|
||||
template <typename T> void forwardToLValue(T &&obj) {
|
||||
takeLValue(fl::forward<T>(obj));
|
||||
}
|
||||
|
||||
// Function template that forwards its argument to an rvalue reference function
|
||||
template <typename T> void forwardValue(T && obj) {
|
||||
takeRValue(fl::forward<T>(obj));
|
||||
}
|
||||
|
||||
TEST_CASE("fl::forward preserves value categories") {
|
||||
|
||||
SUBCASE("Forwarding lvalues") {
|
||||
TestClass obj(10);
|
||||
|
||||
// Should call takeLValue
|
||||
forwardToLValue(obj);
|
||||
REQUIRE(obj.value == 42);
|
||||
|
||||
// This would fail to compile if we tried to forward an lvalue to an
|
||||
// rvalue reference Uncomment to see the error: forwardValue(obj); //
|
||||
// This should fail at compile time
|
||||
}
|
||||
|
||||
SUBCASE("Forwarding rvalues") {
|
||||
// Should call takeRValue
|
||||
forwardValue(TestClass(20));
|
||||
|
||||
// We can also test with a temporary
|
||||
TestClass temp(30);
|
||||
forwardValue(fl::move(temp));
|
||||
// temp.value should now be 100, but we can't check it here since it was
|
||||
// moved
|
||||
}
|
||||
|
||||
SUBCASE("Move and forward") {
|
||||
TestClass obj(50);
|
||||
|
||||
// Move creates an rvalue, forward preserves that
|
||||
forwardValue(fl::move(obj));
|
||||
|
||||
// obj was moved from, so we don't make assertions about its state
|
||||
}
|
||||
}
|
||||
15
libraries/FastLED/tests/test_ui.cpp
Normal file
15
libraries/FastLED/tests/test_ui.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/ui.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("compile ui test") {
|
||||
}
|
||||
|
||||
|
||||
152
libraries/FastLED/tests/test_variant.cpp
Normal file
152
libraries/FastLED/tests/test_variant.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/ui.h"
|
||||
#include "fl/variant.h"
|
||||
#include "fl/optional.h"
|
||||
#include "fl/str.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Variant tests") {
|
||||
// 1) Default is empty
|
||||
Variant<int, fl::Str> v;
|
||||
REQUIRE(v.empty());
|
||||
REQUIRE(!v.is<int>());
|
||||
REQUIRE(!v.is<fl::Str>());
|
||||
|
||||
// 2) Emplace a T
|
||||
v = 123;
|
||||
REQUIRE(v.is<int>());
|
||||
REQUIRE(!v.is<fl::Str>());
|
||||
REQUIRE_EQ(*v.ptr<int>(), 123);
|
||||
|
||||
// 3) Reset back to empty
|
||||
v.reset();
|
||||
REQUIRE(v.empty());
|
||||
|
||||
|
||||
// 4) Emplace a U
|
||||
v = fl::Str("hello");
|
||||
REQUIRE(v.is<fl::Str>());
|
||||
REQUIRE(!v.is<int>());
|
||||
REQUIRE(v.equals(fl::Str("hello")));
|
||||
|
||||
|
||||
// 5) Copy‐construct preserves the U
|
||||
Variant<int, fl::Str> v2(v);
|
||||
REQUIRE(v2.is<fl::Str>());
|
||||
|
||||
fl::Str* str_ptr = v2.ptr<fl::Str>();
|
||||
REQUIRE_NE(str_ptr, nullptr);
|
||||
REQUIRE_EQ(*str_ptr, fl::Str("hello"));
|
||||
const bool is_str = v2.is<fl::Str>();
|
||||
const bool is_int = v2.is<int>();
|
||||
|
||||
CHECK(is_str);
|
||||
CHECK(!is_int);
|
||||
|
||||
#if 0
|
||||
// 6) Move‐construct leaves source empty
|
||||
Variant<int, fl::Str> v3(std::move(v2));
|
||||
REQUIRE(v3.is<fl::Str>());
|
||||
REQUIRE_EQ(v3.getU(), fl::Str("hello"));
|
||||
REQUIRE(v2.isEmpty());
|
||||
|
||||
// 7) Copy‐assign
|
||||
Variant<int, fl::Str> v4;
|
||||
v4 = v3;
|
||||
REQUIRE(v4.is<fl::Str>());
|
||||
REQUIRE_EQ(v4.getU(), fl::Str("hello"));
|
||||
|
||||
|
||||
// 8) Swap two variants
|
||||
v4.emplaceT(7);
|
||||
v3.swap(v4);
|
||||
REQUIRE(v3.is<int>());
|
||||
REQUIRE_EQ(v3.getT(), 7);
|
||||
REQUIRE(v4.is<fl::Str>());
|
||||
REQUIRE_EQ(v4.getU(), fl::Str("hello"));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Variant") {
|
||||
// 1) Default is empty
|
||||
Variant<int, fl::Str, double> v;
|
||||
REQUIRE(v.empty());
|
||||
REQUIRE(!v.is<int>());
|
||||
REQUIRE(!v.is<fl::Str>());
|
||||
REQUIRE(!v.is<double>());
|
||||
|
||||
// 2) Construct with a value
|
||||
Variant<int, fl::Str, double> v1(123);
|
||||
REQUIRE(v1.is<int>());
|
||||
REQUIRE(!v1.is<fl::Str>());
|
||||
REQUIRE(!v1.is<double>());
|
||||
REQUIRE_EQ(*v1.ptr<int>(), 123);
|
||||
|
||||
// 3) Construct with a different type
|
||||
Variant<int, fl::Str, double> v2(fl::Str("hello"));
|
||||
REQUIRE(!v2.is<int>());
|
||||
REQUIRE(v2.is<fl::Str>());
|
||||
REQUIRE(!v2.is<double>());
|
||||
REQUIRE_EQ(*v2.ptr<fl::Str>(), fl::Str("hello"));
|
||||
|
||||
// 4) Copy construction
|
||||
Variant<int, fl::Str, double> v3(v2);
|
||||
REQUIRE(v3.is<fl::Str>());
|
||||
REQUIRE(v3.equals(fl::Str("hello")));
|
||||
|
||||
// 5) Assignment
|
||||
v = v1;
|
||||
REQUIRE(v.is<int>());
|
||||
REQUIRE_EQ(*v.ptr<int>(), 123);
|
||||
|
||||
// 6) Reset
|
||||
v.reset();
|
||||
REQUIRE(v.empty());
|
||||
|
||||
// 7) Assignment of a value
|
||||
v = 3.14;
|
||||
REQUIRE(v.is<double>());
|
||||
// REQUIRE_EQ(v.get<double>(), 3.14);
|
||||
REQUIRE_EQ(*v.ptr<double>(), 3.14);
|
||||
|
||||
// 8) Visitor pattern
|
||||
struct TestVisitor {
|
||||
int result = 0;
|
||||
|
||||
void accept(int value) { result = value; }
|
||||
void accept(const fl::Str& value) { result = value.length(); }
|
||||
void accept(double value) { result = static_cast<int>(value); }
|
||||
};
|
||||
|
||||
TestVisitor visitor;
|
||||
v.visit(visitor);
|
||||
REQUIRE_EQ(visitor.result, 3); // 3.14 truncated to 3
|
||||
|
||||
v = fl::Str("hello world");
|
||||
v.visit(visitor);
|
||||
REQUIRE_EQ(visitor.result, 11); // length of "hello world"
|
||||
|
||||
v = 42;
|
||||
v.visit(visitor);
|
||||
REQUIRE_EQ(visitor.result, 42);
|
||||
}
|
||||
|
||||
|
||||
// TEST_CASE("Optional") {
|
||||
// Optional<int> opt;
|
||||
// REQUIRE(opt.empty());
|
||||
|
||||
// opt = 42;
|
||||
// REQUIRE(!opt.empty());
|
||||
// REQUIRE_EQ(*opt.ptr(), 42);
|
||||
|
||||
// Optional<int> opt2 = opt;
|
||||
// REQUIRE(!opt2.empty());
|
||||
// REQUIRE_EQ(*opt2.ptr(), 42);
|
||||
|
||||
// opt2 = 100;
|
||||
// REQUIRE_EQ(*opt2.ptr(), 100);
|
||||
// }
|
||||
424
libraries/FastLED/tests/test_vector.cpp
Normal file
424
libraries/FastLED/tests/test_vector.cpp
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/vector.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
using namespace fl;
|
||||
|
||||
TEST_CASE("Fixed vector simple") {
|
||||
fl::FixedVector<int, 5> vec;
|
||||
|
||||
SUBCASE("Initial state") {
|
||||
CHECK(vec.size() == 0);
|
||||
CHECK(vec.capacity() == 5);
|
||||
CHECK(vec.empty());
|
||||
}
|
||||
|
||||
SUBCASE("Push back and access") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
|
||||
CHECK(vec.size() == 3);
|
||||
CHECK_FALSE(vec.empty());
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 20);
|
||||
CHECK(vec[2] == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Push back beyond capacity") {
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
vec.push_back(i * 10);
|
||||
}
|
||||
|
||||
CHECK(vec.size() == 5);
|
||||
CHECK(vec.capacity() == 5);
|
||||
CHECK(vec[4] == 40);
|
||||
}
|
||||
|
||||
SUBCASE("Clear") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.clear();
|
||||
|
||||
CHECK(vec.size() == 0);
|
||||
CHECK(vec.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Fixed vector insert") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
fl::FixedVector<int, 5> vec;
|
||||
|
||||
SUBCASE("Insert at beginning") {
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
bool inserted = vec.insert(vec.begin(), 10);
|
||||
|
||||
CHECK(inserted);
|
||||
CHECK(vec.size() == 3);
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 20);
|
||||
CHECK(vec[2] == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Insert in middle") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(30);
|
||||
bool inserted = vec.insert(vec.begin() + 1, 20);
|
||||
|
||||
CHECK(inserted);
|
||||
CHECK(vec.size() == 3);
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 20);
|
||||
CHECK(vec[2] == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Insert at end") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
bool inserted = vec.insert(vec.end(), 30);
|
||||
|
||||
CHECK(inserted);
|
||||
CHECK(vec.size() == 3);
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 20);
|
||||
CHECK(vec[2] == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Insert when full") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
vec.push_back(40);
|
||||
vec.push_back(50);
|
||||
bool inserted = vec.insert(vec.begin() + 2, 25);
|
||||
|
||||
CHECK_FALSE(inserted);
|
||||
CHECK(vec.size() == 5);
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 20);
|
||||
CHECK(vec[2] == 30);
|
||||
CHECK(vec[3] == 40);
|
||||
CHECK(vec[4] == 50);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Fixed vector find_if with predicate") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
fl::FixedVector<int, 5> vec;
|
||||
|
||||
SUBCASE("Find even number") {
|
||||
vec.push_back(1);
|
||||
vec.push_back(2);
|
||||
vec.push_back(3);
|
||||
vec.push_back(4);
|
||||
vec.push_back(5);
|
||||
|
||||
auto it = vec.find_if([](int n) { return n % 2 == 0; });
|
||||
CHECK(it != vec.end());
|
||||
CHECK(*it == 2);
|
||||
}
|
||||
|
||||
SUBCASE("Find number greater than 3") {
|
||||
vec.push_back(1);
|
||||
vec.push_back(2);
|
||||
vec.push_back(3);
|
||||
vec.push_back(4);
|
||||
vec.push_back(5);
|
||||
|
||||
auto it = vec.find_if([](int n) { return n > 3; });
|
||||
CHECK(it != vec.end());
|
||||
CHECK(*it == 4);
|
||||
}
|
||||
|
||||
SUBCASE("Find non-existent condition") {
|
||||
vec.push_back(1);
|
||||
vec.push_back(3);
|
||||
vec.push_back(5);
|
||||
|
||||
auto it = vec.find_if([](int n) { return n % 2 == 0; });
|
||||
CHECK(it == vec.end());
|
||||
}
|
||||
|
||||
SUBCASE("Find in empty vector") {
|
||||
auto it = vec.find_if([](int n) { return true; });
|
||||
CHECK(it == vec.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fl::FixedVector construction and destruction") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
|
||||
static int live_object_count = 0;
|
||||
|
||||
struct TestObject {
|
||||
int value;
|
||||
TestObject(int v = 0) : value(v) { ++live_object_count; }
|
||||
~TestObject() { --live_object_count; }
|
||||
TestObject(const TestObject& other) : value(other.value) { ++live_object_count; }
|
||||
TestObject& operator=(const TestObject& other) {
|
||||
value = other.value;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
SUBCASE("Construction and destruction") {
|
||||
REQUIRE_EQ(0, live_object_count);
|
||||
live_object_count = 0;
|
||||
{
|
||||
fl::FixedVector<TestObject, 3> vec;
|
||||
CHECK(live_object_count == 0);
|
||||
|
||||
vec.push_back(TestObject(1));
|
||||
vec.push_back(TestObject(2));
|
||||
vec.push_back(TestObject(3));
|
||||
|
||||
CHECK(live_object_count == 3); // 3 objects in the vector
|
||||
|
||||
vec.pop_back();
|
||||
CHECK(live_object_count == 2); // 2 objects left in the vector
|
||||
}
|
||||
// vec goes out of scope here
|
||||
REQUIRE_EQ(live_object_count, 0);
|
||||
}
|
||||
|
||||
SUBCASE("Clear") {
|
||||
live_object_count = 0;
|
||||
{
|
||||
fl::FixedVector<TestObject, 3> vec;
|
||||
vec.push_back(TestObject(1));
|
||||
vec.push_back(TestObject(2));
|
||||
|
||||
CHECK(live_object_count == 2);
|
||||
|
||||
vec.clear();
|
||||
|
||||
CHECK(live_object_count == 0); // All objects should be destroyed after clear
|
||||
}
|
||||
CHECK(live_object_count == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Stress test clear, insert and remove") {
|
||||
|
||||
fl::vector_inlined<TestObject, 20> vec;
|
||||
size_t checked_size = 0;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
int random_value = rand() % 4;
|
||||
|
||||
switch (random_value) {
|
||||
case 0:
|
||||
if (!vec.full()) {
|
||||
vec.push_back(TestObject(i));
|
||||
++checked_size;
|
||||
} else {
|
||||
REQUIRE_EQ(20, vec.size());
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (!vec.empty()) {
|
||||
vec.pop_back();
|
||||
--checked_size;
|
||||
} else {
|
||||
REQUIRE_EQ(0, checked_size);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
vec.clear();
|
||||
checked_size = 0;
|
||||
REQUIRE_EQ(0, vec.size());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Fixed vector advanced") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
fl::FixedVector<int, 5> vec;
|
||||
|
||||
SUBCASE("Pop back") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.pop_back();
|
||||
|
||||
CHECK(vec.size() == 1);
|
||||
CHECK(vec[0] == 10);
|
||||
}
|
||||
|
||||
SUBCASE("Front and back") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
|
||||
CHECK(vec.front() == 10);
|
||||
CHECK(vec.back() == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Iterator") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
|
||||
int sum = 0;
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it) {
|
||||
sum += *it;
|
||||
}
|
||||
|
||||
CHECK(sum == 60);
|
||||
}
|
||||
|
||||
SUBCASE("Erase") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
|
||||
vec.erase(vec.begin() + 1);
|
||||
|
||||
CHECK(vec.size() == 2);
|
||||
CHECK(vec[0] == 10);
|
||||
CHECK(vec[1] == 30);
|
||||
}
|
||||
|
||||
SUBCASE("Find and has") {
|
||||
vec.push_back(10);
|
||||
vec.push_back(20);
|
||||
vec.push_back(30);
|
||||
|
||||
CHECK(vec.has(20));
|
||||
CHECK_FALSE(vec.has(40));
|
||||
|
||||
auto it = vec.find(20);
|
||||
CHECK(it != vec.end());
|
||||
CHECK(*it == 20);
|
||||
|
||||
it = vec.find(40);
|
||||
CHECK(it == vec.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Fixed vector with custom type") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
struct Point {
|
||||
int x, y;
|
||||
Point(int x = 0, int y = 0) : x(x), y(y) {}
|
||||
bool operator==(const Point& other) const { return x == other.x && y == other.y; }
|
||||
};
|
||||
|
||||
fl::FixedVector<Point, 3> vec;
|
||||
|
||||
SUBCASE("Push and access custom type") {
|
||||
vec.push_back(Point(1, 2));
|
||||
vec.push_back(Point(3, 4));
|
||||
|
||||
CHECK(vec.size() == 2);
|
||||
CHECK(vec[0].x == 1);
|
||||
CHECK(vec[0].y == 2);
|
||||
CHECK(vec[1].x == 3);
|
||||
CHECK(vec[1].y == 4);
|
||||
}
|
||||
|
||||
SUBCASE("Find custom type") {
|
||||
vec.push_back(Point(1, 2));
|
||||
vec.push_back(Point(3, 4));
|
||||
|
||||
auto it = vec.find(Point(3, 4));
|
||||
CHECK(it != vec.end());
|
||||
CHECK(it->x == 3);
|
||||
CHECK(it->y == 4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SortedVector") {
|
||||
struct Less {
|
||||
bool operator()(int a, int b) const { return a < b; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
SUBCASE("Insert maintains order") {
|
||||
SortedHeapVector<int, Less> vec;
|
||||
vec.insert(3);
|
||||
vec.insert(1);
|
||||
vec.insert(4);
|
||||
vec.insert(2);
|
||||
|
||||
CHECK(vec.size() == 4);
|
||||
CHECK(vec[0] == 1);
|
||||
CHECK(vec[1] == 2);
|
||||
CHECK(vec[2] == 3);
|
||||
CHECK(vec[3] == 4);
|
||||
}
|
||||
|
||||
SUBCASE("Erase removes element") {
|
||||
SortedHeapVector<int, Less> vec;
|
||||
vec.insert(3);
|
||||
vec.insert(1);
|
||||
vec.insert(4);
|
||||
vec.insert(2);
|
||||
|
||||
vec.erase(3); // Remove the value 3
|
||||
|
||||
CHECK(vec.size() == 3);
|
||||
CHECK_FALSE(vec.has(3)); // Verify 3 is no longer present
|
||||
|
||||
// Verify remaining elements are still in order
|
||||
CHECK(vec[0] == 1);
|
||||
CHECK(vec[1] == 2);
|
||||
CHECK(vec[2] == 4);
|
||||
}
|
||||
|
||||
SUBCASE("Insert when full") {
|
||||
SortedHeapVector<int, Less> vec;
|
||||
vec.setMaxSize(5);
|
||||
// Fill the vector to capacity
|
||||
vec.insert(1);
|
||||
vec.insert(2);
|
||||
vec.insert(3);
|
||||
vec.insert(4);
|
||||
vec.insert(5); // Max size is 5
|
||||
|
||||
InsertResult result;
|
||||
vec.insert(6, &result); // Try to insert into full vector
|
||||
|
||||
CHECK_EQ(InsertResult::kMaxSize, result); // Should return false
|
||||
CHECK(vec.size() == 5); // Size shouldn't change
|
||||
CHECK(vec[4] == 5); // Last element should still be 5
|
||||
}
|
||||
|
||||
SUBCASE("Erase from empty") {
|
||||
SortedHeapVector<int, Less> vec;
|
||||
bool ok = vec.erase(1); // Try to erase from empty vector
|
||||
CHECK(!ok); // Should return false
|
||||
CHECK(vec.size() == 0); // Should still be empty
|
||||
CHECK(vec.empty());
|
||||
|
||||
ok = vec.erase(vec.end());
|
||||
CHECK(!ok); // Should return false
|
||||
CHECK(vec.size() == 0); // Should still be empty
|
||||
CHECK(vec.empty());
|
||||
|
||||
ok = vec.erase(vec.begin());
|
||||
CHECK(!ok); // Should return false
|
||||
CHECK(vec.size() == 0); // Should still be empty
|
||||
CHECK(vec.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("HeapVector") {
|
||||
SUBCASE("resize") {
|
||||
HeapVector<int> vec;
|
||||
vec.resize(5);
|
||||
CHECK(vec.size() == 5);
|
||||
CHECK(vec.capacity() >= 5);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
CHECK_EQ(0, vec[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
232
libraries/FastLED/tests/test_video.cpp
Normal file
232
libraries/FastLED/tests/test_video.cpp
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/bytestreammemory.h"
|
||||
#include "fl/ptr.h"
|
||||
#include "fx/video.h"
|
||||
#include "fx/video/pixel_stream.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
|
||||
#define FPS 30
|
||||
#define FRAME_TIME 1000 / FPS
|
||||
#define VIDEO_WIDTH 10
|
||||
#define VIDEO_HEIGHT 10
|
||||
#define LEDS_PER_FRAME VIDEO_WIDTH *VIDEO_HEIGHT
|
||||
|
||||
FASTLED_SMART_PTR(FakeFileHandle);
|
||||
|
||||
using namespace fl;
|
||||
|
||||
class FakeFileHandle : public FileHandle {
|
||||
public:
|
||||
virtual ~FakeFileHandle() {}
|
||||
bool available() const override { return mPos < data.size(); }
|
||||
size_t bytesLeft() const override { return data.size() - mPos; }
|
||||
size_t size() const override { return data.size(); }
|
||||
bool valid() const override { return true; }
|
||||
|
||||
size_t write(const uint8_t *src, size_t len) {
|
||||
data.insert(data.end(), src, src + len);
|
||||
return len;
|
||||
}
|
||||
size_t writeCRGB(const CRGB *src, size_t len) {
|
||||
size_t bytes_written = write((const uint8_t *)src, len * 3);
|
||||
return bytes_written / 3;
|
||||
}
|
||||
size_t read(uint8_t *dst, size_t bytesToRead) override {
|
||||
size_t bytesRead = 0;
|
||||
while (bytesRead < bytesToRead && mPos < data.size()) {
|
||||
dst[bytesRead] = data[mPos];
|
||||
bytesRead++;
|
||||
mPos++;
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
size_t pos() const override { return mPos; }
|
||||
const char *path() const override { return "fake"; }
|
||||
bool seek(size_t pos) override {
|
||||
this->mPos = pos;
|
||||
return true;
|
||||
}
|
||||
void close() override {}
|
||||
std::vector<uint8_t> data;
|
||||
size_t mPos = 0;
|
||||
};
|
||||
|
||||
TEST_CASE("video with memory stream") {
|
||||
// Video video(LEDS_PER_FRAME, FPS);
|
||||
Video video(LEDS_PER_FRAME, FPS, 1);
|
||||
video.setFade(0, 0);
|
||||
ByteStreamMemoryPtr memoryStream =
|
||||
ByteStreamMemoryPtr::New(LEDS_PER_FRAME * 3);
|
||||
CRGB testData[LEDS_PER_FRAME] = {};
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
testData[i] = i % 2 == 0 ? CRGB::Red : CRGB::Black;
|
||||
}
|
||||
size_t pixels_written = memoryStream->writeCRGB(testData, LEDS_PER_FRAME);
|
||||
REQUIRE_EQ(pixels_written, LEDS_PER_FRAME);
|
||||
video.beginStream(memoryStream);
|
||||
CRGB leds[LEDS_PER_FRAME];
|
||||
bool ok = video.draw(FRAME_TIME + 1, leds);
|
||||
REQUIRE(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
CHECK_EQ(leds[i], testData[i]);
|
||||
}
|
||||
ok = video.draw(2 * FRAME_TIME + 1, leds);
|
||||
REQUIRE(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
// CHECK_EQ(leds[i], testData[i]);
|
||||
REQUIRE_EQ(leds[i].r, testData[i].r);
|
||||
REQUIRE_EQ(leds[i].g, testData[i].g);
|
||||
REQUIRE_EQ(leds[i].b, testData[i].b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("video with memory stream, interpolated") {
|
||||
// Video video(LEDS_PER_FRAME, FPS);
|
||||
Video video(LEDS_PER_FRAME, 1);
|
||||
video.setFade(0, 0);
|
||||
ByteStreamMemoryPtr memoryStream =
|
||||
ByteStreamMemoryPtr::New(LEDS_PER_FRAME * sizeof(CRGB) * 2);
|
||||
CRGB testData[LEDS_PER_FRAME] = {};
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
testData[i] = CRGB::Red;
|
||||
}
|
||||
size_t pixels_written = memoryStream->writeCRGB(testData, LEDS_PER_FRAME);
|
||||
CHECK_EQ(pixels_written, LEDS_PER_FRAME);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
testData[i] = CRGB::Black;
|
||||
}
|
||||
pixels_written = memoryStream->writeCRGB(testData, LEDS_PER_FRAME);
|
||||
CHECK_EQ(pixels_written, LEDS_PER_FRAME);
|
||||
video.beginStream(memoryStream); // One frame per second.
|
||||
CRGB leds[LEDS_PER_FRAME];
|
||||
bool ok = video.draw(0, leds); // First frame starts time 0.
|
||||
ok = video.draw(500, leds); // Half a frame.
|
||||
CHECK(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
int r = leds[i].r;
|
||||
int g = leds[i].g;
|
||||
int b = leds[i].b;
|
||||
REQUIRE_EQ(128, r); // We expect the color to be interpolated to 128.
|
||||
REQUIRE_EQ(0, g);
|
||||
REQUIRE_EQ(0, b);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("video with file handle") {
|
||||
// Video video(LEDS_PER_FRAME, FPS);
|
||||
Video video(LEDS_PER_FRAME, FPS);
|
||||
video.setFade(0, 0);
|
||||
FakeFileHandlePtr fileHandle = FakeFileHandlePtr::New();
|
||||
CRGB led_frame[LEDS_PER_FRAME];
|
||||
// alternate between red and black
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
led_frame[i] = i % 2 == 0 ? CRGB::Red : CRGB::Black;
|
||||
}
|
||||
// now write the data
|
||||
size_t leds_written = fileHandle->writeCRGB(led_frame, LEDS_PER_FRAME);
|
||||
CHECK_EQ(leds_written, LEDS_PER_FRAME);
|
||||
video.begin(fileHandle);
|
||||
CRGB leds[LEDS_PER_FRAME];
|
||||
bool ok = video.draw(FRAME_TIME + 1, leds);
|
||||
REQUIRE(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
CHECK_EQ(leds[i], led_frame[i]);
|
||||
}
|
||||
ok = video.draw(2 * FRAME_TIME + 1, leds);
|
||||
CHECK(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
CHECK_EQ(leds[i], led_frame[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Video duration") {
|
||||
Video video(LEDS_PER_FRAME, FPS);
|
||||
FakeFileHandlePtr fileHandle = FakeFileHandlePtr::New();
|
||||
CRGB led_frame[LEDS_PER_FRAME];
|
||||
// just set all the leds to white
|
||||
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
led_frame[i] = CRGB::White;
|
||||
}
|
||||
// fill frames for all of one second
|
||||
for (uint32_t i = 0; i < FPS; i++) {
|
||||
size_t leds_written = fileHandle->writeCRGB(led_frame, LEDS_PER_FRAME);
|
||||
CHECK_EQ(leds_written, LEDS_PER_FRAME);
|
||||
}
|
||||
|
||||
video.begin(fileHandle);
|
||||
int32_t duration = video.durationMicros();
|
||||
float duration_f = duration / 1000.0;
|
||||
CHECK_EQ(1000, uint32_t(duration_f + 0.5));
|
||||
}
|
||||
|
||||
TEST_CASE("video with end frame fadeout") {
|
||||
Video video(LEDS_PER_FRAME, FPS);
|
||||
video.setFade(0, 1000);
|
||||
FakeFileHandlePtr fileHandle = FakeFileHandlePtr::New();
|
||||
CRGB led_frame[LEDS_PER_FRAME];
|
||||
// just set all the leds to white
|
||||
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
led_frame[i] = CRGB::White;
|
||||
}
|
||||
// fill frames for all of one second
|
||||
for (uint32_t i = 0; i < FPS; i++) {
|
||||
size_t leds_written = fileHandle->writeCRGB(led_frame, LEDS_PER_FRAME);
|
||||
CHECK_EQ(leds_written, LEDS_PER_FRAME);
|
||||
}
|
||||
|
||||
video.begin(fileHandle);
|
||||
CRGB leds[LEDS_PER_FRAME];
|
||||
bool ok = video.draw(0, leds);
|
||||
REQUIRE(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
CHECK_EQ(leds[i], led_frame[i]);
|
||||
}
|
||||
ok = video.draw(500, leds);
|
||||
// test that the leds are about half as bright
|
||||
REQUIRE(ok);
|
||||
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
// This is what the values should be but we don't do inter-frame
|
||||
// interpolation yet. CHECK_EQ(leds[i].r, 127); CHECK_EQ(leds[i].g,
|
||||
// 127); CHECK_EQ(leds[i].b, 127);
|
||||
CHECK_EQ(leds[i].r, 110);
|
||||
CHECK_EQ(leds[i].g, 110);
|
||||
CHECK_EQ(leds[i].b, 110);
|
||||
}
|
||||
|
||||
ok = video.draw(900, leds); // close to last frame
|
||||
REQUIRE(ok);
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
CHECK_EQ(leds[i].r, 8);
|
||||
CHECK_EQ(leds[i].g, 8);
|
||||
CHECK_EQ(leds[i].b, 8);
|
||||
}
|
||||
|
||||
ok = video.draw(965, leds); // Last frame
|
||||
REQUIRE(ok);
|
||||
// test that the leds are almost black
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
REQUIRE_EQ(leds[i], CRGB(0, 0, 0));
|
||||
}
|
||||
#if 0 // Bug - we do not handle wrapping around
|
||||
ok = video.draw(1000, leds); // Bug - we have to let the buffer drain with one frame.
|
||||
ok = video.draw(1000, leds); // After last frame we flip around
|
||||
for (uint32_t i = 0; i < LEDS_PER_FRAME; i++) {
|
||||
REQUIRE_EQ(leds[i], CRGB(4, 4, 4));
|
||||
}
|
||||
#endif //
|
||||
}
|
||||
53
libraries/FastLED/tests/test_videofx_wrapper.cpp
Normal file
53
libraries/FastLED/tests/test_videofx_wrapper.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "fx/fx.h"
|
||||
#include "fx/fx2d.h"
|
||||
#include "fx/video.h"
|
||||
#include "fl/vector.h"
|
||||
#include "FastLED.h"
|
||||
|
||||
|
||||
FASTLED_SMART_PTR(Fake2d);
|
||||
|
||||
// Simple Fx2d object which writes a single red pixel to the first LED
|
||||
// with the red component being the intensity of the frame counter.
|
||||
class Fake2d : public Fx2d {
|
||||
public:
|
||||
Fake2d() : Fx2d(XYMap::constructRectangularGrid(1,1)) {}
|
||||
|
||||
void draw(DrawContext context) override {
|
||||
CRGB c = mColors[mFrameCounter % mColors.size()];
|
||||
context.leds[0] = c;
|
||||
mFrameCounter++;
|
||||
}
|
||||
|
||||
bool hasFixedFrameRate(float *fps) const override {
|
||||
*fps = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
Str fxName() const override { return "Fake2d"; }
|
||||
uint8_t mFrameCounter = 0;
|
||||
FixedVector<CRGB, 5> mColors;
|
||||
};
|
||||
|
||||
|
||||
|
||||
TEST_CASE("test_fixed_fps") {
|
||||
Fake2dPtr fake = Fake2dPtr::New();
|
||||
fake->mColors.push_back(CRGB(0, 0, 0));
|
||||
fake->mColors.push_back(CRGB(255, 0, 0));
|
||||
VideoFxWrapper wrapper(fake);
|
||||
wrapper.setFade(0, 0);
|
||||
CRGB leds[1];
|
||||
Fx::DrawContext context(0, leds);
|
||||
wrapper.draw(context);
|
||||
CHECK_EQ(1, fake->mFrameCounter);
|
||||
CHECK_EQ(leds[0], CRGB(0, 0, 0));
|
||||
context.now = 500;
|
||||
wrapper.draw(context);
|
||||
CHECK_EQ(2, fake->mFrameCounter);
|
||||
CHECK_EQ(leds[0], CRGB(127, 0, 0));
|
||||
}
|
||||
323
libraries/FastLED/tests/test_xypath.cpp
Normal file
323
libraries/FastLED/tests/test_xypath.cpp
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "lib8tion/intmap.h"
|
||||
#include "fl/xypath.h"
|
||||
#include "fl/vector.h"
|
||||
#include "fl/unused.h"
|
||||
#include <string>
|
||||
|
||||
using namespace fl;
|
||||
|
||||
#define MESSAGE_TILE(TILE) \
|
||||
MESSAGE("\nTile:\n" \
|
||||
<< " " << TILE.at(0, 0) << " " << TILE.at(1, 0) << "\n" \
|
||||
<< " " << TILE.at(0, 1) << " " << TILE.at(1, 1) << "\n");
|
||||
|
||||
#define MESSAGE_TILE_ROW(TILE, ROW) \
|
||||
MESSAGE("\nTile Row " << ROW << ":\n" \
|
||||
<< " " << TILE.at(0, ROW) << " " << TILE.at(1, ROW) << "\n");
|
||||
|
||||
|
||||
TEST_CASE("LinePath") {
|
||||
LinePath path(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
vec2f xy = path.compute(0.5f);
|
||||
REQUIRE(xy.x == 0.5f);
|
||||
REQUIRE(xy.y == 0.5f);
|
||||
|
||||
xy = path.compute(1.0f);
|
||||
REQUIRE(xy.x == 1.0f);
|
||||
REQUIRE(xy.y == 1.0f);
|
||||
|
||||
xy = path.compute(0.0f);
|
||||
REQUIRE(xy.x == 0.0f);
|
||||
REQUIRE(xy.y == 0.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("LinePath at_subpixel") {
|
||||
// Tests that we can get the correct subpixel values at center point 0,0
|
||||
LinePath line(-1.0f, -1.0f, 1.0f, -1.0f);
|
||||
XYPath path(NewPtrNoTracking(line));
|
||||
path.setDrawBounds(2,2);
|
||||
Tile2x2_u8 tile = path.at_subpixel(0);
|
||||
REQUIRE_EQ(vec2<uint16_t>(0, 0), tile.origin());
|
||||
MESSAGE_TILE(tile);
|
||||
REQUIRE_EQ(255, tile.at(0, 0));
|
||||
}
|
||||
|
||||
TEST_CASE("LinePath simple float sweep") {
|
||||
// Tests that we can get the correct gaussian values at center point 0,0
|
||||
LinePath point(0, 1.f, 1.f, 1.f);
|
||||
XYPath path(NewPtrNoTracking(point));
|
||||
auto xy = path.at(0);
|
||||
//MESSAGE_TILE(tile);
|
||||
REQUIRE_EQ(xy, vec2f(0.0f, 1.f));
|
||||
xy = path.at(1);
|
||||
REQUIRE_EQ(xy, vec2f(1.f, 1.f));
|
||||
}
|
||||
|
||||
TEST_CASE("Point at exactly the middle") {
|
||||
// Tests that we can get the correct gaussian values at center point 0,0
|
||||
PointPath point(0.0, 0.0); // Right in middle.
|
||||
XYPath path(NewPtrNoTracking(point));
|
||||
path.setDrawBounds(2,2);
|
||||
// auto xy = path.at(0);
|
||||
fl::Tile2x2_u8 sp = path.at_subpixel(0);
|
||||
//MESSAGE_TILE(tile);
|
||||
// REQUIRE_EQ(vec2f(0.0f, 0.f), sp);
|
||||
// print out
|
||||
auto origin = sp.origin();
|
||||
MESSAGE("Origin: " << origin.x << ", " << origin.y);
|
||||
|
||||
MESSAGE(sp.at(0, 0));
|
||||
MESSAGE(sp.at(0, 1));
|
||||
MESSAGE(sp.at(1, 0));
|
||||
MESSAGE(sp.at(1, 1));
|
||||
|
||||
// require that all alpha be the same
|
||||
REQUIRE_EQ(sp.at(0, 0), sp.at(0, 1));
|
||||
REQUIRE_EQ(sp.at(0, 0), sp.at(1, 0));
|
||||
REQUIRE_EQ(sp.at(0, 0), sp.at(1, 1));
|
||||
REQUIRE_EQ(sp.at(0, 0), 64);
|
||||
}
|
||||
|
||||
TEST_CASE("LinePath simple sweep in draw bounds") {
|
||||
// Tests that we can get the correct gaussian values at center point 0,0
|
||||
LinePath point(-1.f, -1.f, 1.f, -1.f);
|
||||
XYPath path(NewPtrNoTracking(point));
|
||||
int width = 2;
|
||||
path.setDrawBounds(width, width);
|
||||
auto begin = path.at(0);
|
||||
auto end = path.at(1);
|
||||
REQUIRE_EQ(vec2f(0.5f, 0.5f), begin);
|
||||
REQUIRE_EQ(vec2f(1.5f, 0.5f), end);
|
||||
}
|
||||
|
||||
TEST_CASE("LinePath at_subpixel moves x") {
|
||||
// Tests that we can get the correct subpixel.
|
||||
LinePath point(-1.f, -1.f, 1.f, -1.f);
|
||||
XYPath path(NewPtrNoTracking(point));
|
||||
path.setDrawBounds(3, 3);
|
||||
Tile2x2_u8 tile = path.at_subpixel(0.0f);
|
||||
// MESSAGE_TILE(tile);
|
||||
REQUIRE_EQ(tile.origin(), vec2<uint16_t>(0, 0));
|
||||
REQUIRE_EQ(tile.at(0, 0), 255);
|
||||
tile = path.at_subpixel(1.0f);
|
||||
REQUIRE_EQ(tile.origin(), vec2<uint16_t>(2, 0));
|
||||
REQUIRE_EQ(tile.at(0, 0), 255);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Test HeartPath") {
|
||||
HeartPathPtr heart = HeartPathPtr::New();
|
||||
|
||||
// Track min and max values to help with scaling
|
||||
float min_x = 1.0f;
|
||||
float max_x = -1.0f;
|
||||
float min_y = 1.0f;
|
||||
float max_y = -1.0f;
|
||||
|
||||
// Sample points along the heart curve
|
||||
const int num_samples = 100;
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float alpha = static_cast<float>(i) / (num_samples - 1);
|
||||
vec2f point = heart->compute(alpha);
|
||||
|
||||
// Update min/max values
|
||||
min_x = MIN(min_x, point.x);
|
||||
max_x = MAX(max_x, point.x);
|
||||
min_y = MIN(min_y, point.y);
|
||||
max_y = MAX(max_y, point.y);
|
||||
|
||||
// Print every 10th point for visual inspection
|
||||
if (i % 10 == 0) {
|
||||
MESSAGE("Heart point at alpha=" << alpha << ": (" << point.x << ", " << point.y << ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the min/max values
|
||||
MESSAGE("\nHeart shape bounds:");
|
||||
MESSAGE("X range: [" << min_x << ", " << max_x << "]");
|
||||
MESSAGE("Y range: [" << min_y << ", " << max_y << "]");
|
||||
|
||||
// Verify the heart is within the expected bounds
|
||||
REQUIRE(min_x >= -1.0f);
|
||||
REQUIRE(max_x <= 1.0f);
|
||||
REQUIRE(min_y >= -1.0f);
|
||||
REQUIRE(max_y <= 1.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("Test ArchimedeanSpiralPath") {
|
||||
ArchimedeanSpiralPathPtr spiral = ArchimedeanSpiralPathPtr::New(3, 1.0f);
|
||||
|
||||
// Track min and max values to help with scaling
|
||||
float min_x = 1.0f;
|
||||
float max_x = -1.0f;
|
||||
float min_y = 1.0f;
|
||||
float max_y = -1.0f;
|
||||
|
||||
// Sample points along the spiral curve
|
||||
const int num_samples = 100;
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float alpha = static_cast<float>(i) / (num_samples - 1);
|
||||
vec2f point = spiral->compute(alpha);
|
||||
|
||||
// Update min/max values
|
||||
min_x = MIN(min_x, point.x);
|
||||
max_x = MAX(max_x, point.x);
|
||||
min_y = MIN(min_y, point.y);
|
||||
max_y = MAX(max_y, point.y);
|
||||
|
||||
// Print every 10th point for visual inspection
|
||||
if (i % 10 == 0) {
|
||||
MESSAGE("Spiral point at alpha=" << alpha << ": (" << point.x << ", " << point.y << ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the min/max values
|
||||
MESSAGE("\nSpiral shape bounds:");
|
||||
MESSAGE("X range: [" << min_x << ", " << max_x << "]");
|
||||
MESSAGE("Y range: [" << min_y << ", " << max_y << "]");
|
||||
|
||||
// Verify the spiral is within the expected bounds
|
||||
REQUIRE(min_x >= -1.0f);
|
||||
REQUIRE(max_x <= 1.0f);
|
||||
REQUIRE(min_y >= -1.0f);
|
||||
REQUIRE(max_y <= 1.0f);
|
||||
}
|
||||
|
||||
TEST_CASE("Test RosePath") {
|
||||
// Test with different petal configurations
|
||||
SUBCASE("3-petal rose") {
|
||||
RosePathPtr rose = RosePathPtr::New(3, 1);
|
||||
|
||||
// Track min and max values to help with scaling
|
||||
float min_x = 1.0f;
|
||||
float max_x = -1.0f;
|
||||
float min_y = 1.0f;
|
||||
float max_y = -1.0f;
|
||||
|
||||
// Sample points along the rose curve
|
||||
const int num_samples = 100;
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float alpha = static_cast<float>(i) / (num_samples - 1);
|
||||
vec2f point = rose->compute(alpha);
|
||||
|
||||
// Update min/max values
|
||||
min_x = MIN(min_x, point.x);
|
||||
max_x = MAX(max_x, point.x);
|
||||
min_y = MIN(min_y, point.y);
|
||||
max_y = MAX(max_y, point.y);
|
||||
|
||||
// Print every 10th point for visual inspection
|
||||
if (i % 10 == 0) {
|
||||
MESSAGE("3-petal rose point at alpha=" << alpha << ": (" << point.x << ", " << point.y << ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Print the min/max values
|
||||
MESSAGE("\n3-petal rose shape bounds:");
|
||||
MESSAGE("X range: [" << min_x << ", " << max_x << "]");
|
||||
MESSAGE("Y range: [" << min_y << ", " << max_y << "]");
|
||||
|
||||
// Verify the rose is within the expected bounds
|
||||
REQUIRE(min_x >= -1.0f);
|
||||
REQUIRE(max_x <= 1.0f);
|
||||
REQUIRE(min_y >= -1.0f);
|
||||
REQUIRE(max_y <= 1.0f);
|
||||
}
|
||||
|
||||
SUBCASE("4-petal rose") {
|
||||
RosePathPtr rose = RosePathPtr::New(2, 1); // n=2 gives 4 petals
|
||||
|
||||
// Track min and max values to help with scaling
|
||||
float min_x = 1.0f;
|
||||
float max_x = -1.0f;
|
||||
float min_y = 1.0f;
|
||||
float max_y = -1.0f;
|
||||
|
||||
// Sample points along the rose curve
|
||||
const int num_samples = 100;
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float alpha = static_cast<float>(i) / (num_samples - 1);
|
||||
vec2f point = rose->compute(alpha);
|
||||
|
||||
// Update min/max values
|
||||
min_x = MIN(min_x, point.x);
|
||||
max_x = MAX(max_x, point.x);
|
||||
min_y = MIN(min_y, point.y);
|
||||
max_y = MAX(max_y, point.y);
|
||||
}
|
||||
|
||||
// Verify the rose is within the expected bounds
|
||||
REQUIRE(min_x >= -1.0f);
|
||||
REQUIRE(max_x <= 1.0f);
|
||||
REQUIRE(min_y >= -1.0f);
|
||||
REQUIRE(max_y <= 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Check complex types") {
|
||||
HeapVector<XYPathPtr> paths;
|
||||
XYPathPtr circle = XYPath::NewCirclePath();
|
||||
paths.push_back(circle);
|
||||
|
||||
// Add heart path to the tests
|
||||
XYPathPtr heart = XYPath::NewHeartPath();
|
||||
paths.push_back(heart);
|
||||
|
||||
// Add spiral path to the tests
|
||||
XYPathPtr spiral = XYPath::NewArchimedeanSpiralPath();
|
||||
paths.push_back(spiral);
|
||||
|
||||
// Add rose path to the tests
|
||||
XYPathPtr rose = XYPath::NewRosePath();
|
||||
paths.push_back(rose);
|
||||
|
||||
// Add phyllotaxis path to the tests
|
||||
XYPathPtr phyllotaxis = XYPath::NewPhyllotaxisPath();
|
||||
paths.push_back(phyllotaxis);
|
||||
|
||||
// paths.push_back(LissajousPathPtr::New());
|
||||
// paths.push_back(GielisCurvePathPtr::New());
|
||||
// paths.push_back(CatmullRomPathPtr::New());
|
||||
|
||||
SUBCASE("Check floating point range") {
|
||||
for (auto &path : paths) {
|
||||
for (float alpha = 0.0f; true; alpha += 0.01f) {
|
||||
alpha = MIN(1.f, alpha);
|
||||
vec2f xy = path->at(alpha);
|
||||
REQUIRE(xy.x >= -1.0f);
|
||||
REQUIRE(xy.x <= 1.0f);
|
||||
REQUIRE(xy.y >= -1.0f);
|
||||
REQUIRE(xy.y <= 1.0f);
|
||||
if (ALMOST_EQUAL(alpha, 1.0f, 0.001f)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Check float point range with transform to -8,8") {
|
||||
TransformFloat tx;
|
||||
tx.set_scale(4.0f);
|
||||
|
||||
for (auto &path : paths) {
|
||||
for (float alpha = 0.0f; true; alpha += 0.01f) {
|
||||
alpha = MIN(1.f, alpha);
|
||||
vec2f xy = path->at(alpha, tx);
|
||||
REQUIRE_GE(xy.x, -4.0f);
|
||||
REQUIRE_LE(xy.x, 4.0f);
|
||||
REQUIRE_GE(xy.y, -4.0f);
|
||||
REQUIRE_LE(xy.y, 4.0f);
|
||||
if (ALMOST_EQUAL(alpha, 1.0f, 0.001f)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue