Initial commit of Arduino libraries
This commit is contained in:
230
FastLED/tests/CMakeLists.txt
Normal file
230
FastLED/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,230 @@
|
||||
# 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)
|
||||
|
||||
# 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=suggest-override
|
||||
-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=non-virtual-dtor
|
||||
-Werror=reorder
|
||||
-Werror=sign-compare
|
||||
-Werror=float-equal
|
||||
#-Werror=conversion
|
||||
-Werror=switch-enum
|
||||
#-Werror=switch-default
|
||||
-Werror=unused-parameter
|
||||
-Werror=unused-variable
|
||||
-Werror=unused-value
|
||||
-Werror=cast-align
|
||||
-DFASTLED_FIVE_BIT_HD_GAMMA_FUNCTION_2_8
|
||||
# ignore Arduino/PlatformIO-specific PROGMEM macro
|
||||
-DPROGMEM=
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND COMMON_COMPILE_FLAGS -Werror=self-assign -Werror=infinite-recursion -Werror=extra-tokens)
|
||||
endif()
|
||||
|
||||
|
||||
set(UNIT_TEST_COMPILE_FLAGS
|
||||
-Wall
|
||||
#-Wextra
|
||||
#-Wpedantic
|
||||
-funwind-tables
|
||||
-g3
|
||||
-ggdb
|
||||
-fno-omit-frame-pointer
|
||||
-O0
|
||||
-Werror=suggest-override
|
||||
-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=non-virtual-dtor
|
||||
#-Werror=reorder
|
||||
#-Werror=sign-compare
|
||||
#-Werror=float-equal
|
||||
#-Werror=conversion
|
||||
-Werror=switch-enum
|
||||
#-Werror=switch-default
|
||||
#-Werror=unused-parameter
|
||||
#-Werror=unused-variable
|
||||
#-Werror=unused-value
|
||||
|
||||
# Not supported in gcc.
|
||||
#-Werror=infinite-recursion
|
||||
#-v
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
# 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)
|
||||
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)
|
||||
target_link_options(${TEST_NAME} PRIVATE -static-libgcc -static-libstdc++)
|
||||
endif()
|
||||
target_compile_options(${TEST_NAME} PRIVATE ${UNIT_TEST_COMPILE_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
FastLED/tests/crash_handler.h
Normal file
49
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
FastLED/tests/doctest.h
Normal file
7106
FastLED/tests/doctest.h
Normal file
File diff suppressed because it is too large
Load Diff
3
FastLED/tests/readme
Normal file
3
FastLED/tests/readme
Normal file
@@ -0,0 +1,3 @@
|
||||
To run tests use
|
||||
|
||||
`uv run ci/cpp_test_run.py`
|
||||
24
FastLED/tests/test.h
Normal file
24
FastLED/tests/test.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include "doctest.h"
|
||||
|
||||
#include "crgb.h"
|
||||
#include "fl/str.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();
|
||||
}
|
||||
};
|
||||
}
|
||||
283
FastLED/tests/test_apa102_hd.cpp
Normal file
283
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
|
||||
|
||||
}
|
||||
|
||||
177
FastLED/tests/test_brightness_bitshifter.cpp
Normal file
177
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
FastLED/tests/test_bytestream.cpp
Normal file
261
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
FastLED/tests/test_circular_buffer.cpp
Normal file
297
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());
|
||||
}
|
||||
20
FastLED/tests/test_dbg.cpp
Normal file
20
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);
|
||||
}
|
||||
21
FastLED/tests/test_fastled.cpp
Normal file
21
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);
|
||||
}
|
||||
|
||||
97
FastLED/tests/test_fixed_set.cpp
Normal file
97
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
FastLED/tests/test_frame.cpp
Normal file
52
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
|
||||
SetLargeBlockAllocator(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") {
|
||||
SetLargeBlockAllocator(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_BLACK);
|
||||
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_BLACK);
|
||||
CHECK(out == CRGB(64, 0, 0));
|
||||
}
|
||||
284
FastLED/tests/test_frame_interpolator.cpp
Normal file
284
FastLED/tests/test_frame_interpolator.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
|
||||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fx/frame.h"
|
||||
#include "fx/video/frame_interpolator.h"
|
||||
#include "fl/namespace.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("FrameInterpolator::selectFrames") {
|
||||
SUBCASE("Empty interpolator") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
const Frame *selected1;
|
||||
const Frame *selected2;
|
||||
CHECK_FALSE(interpolator.selectFrames(0, &selected1, &selected2));
|
||||
}
|
||||
|
||||
SUBCASE("2 frame interpolator before") {
|
||||
// Create an interpolator with capacity for 2 frames
|
||||
FrameInterpolator interpolator(2, -1);
|
||||
|
||||
// Create some test frames with different timestamps
|
||||
FrameRef frame1 = FrameRef::New(10, false); // 10 pixels, no alpha
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
|
||||
|
||||
|
||||
// Add frames with timestamps
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 2000));
|
||||
|
||||
const Frame *selected1;
|
||||
const Frame *selected2;
|
||||
|
||||
// Falls between two frames->
|
||||
bool selected = interpolator.selectFrames(0, &selected1, &selected2);
|
||||
CHECK(selected);
|
||||
CHECK(selected1);
|
||||
CHECK(selected2);
|
||||
// Now check that it's the same frame.
|
||||
CHECK(selected1 == selected2);
|
||||
// now check that the timestamp of the first frame is less than the
|
||||
// timestamp of the second frame
|
||||
CHECK(selected1->getTimestamp() == 1000);
|
||||
CHECK(selected2->getTimestamp() == 1000);
|
||||
}
|
||||
|
||||
SUBCASE("2 frame interpolator between") {
|
||||
// Create an interpolator with capacity for 2 frames
|
||||
FrameInterpolator interpolator(2, -1);
|
||||
|
||||
// Create some test frames with different timestamps
|
||||
FrameRef frame1 = FrameRef::New(10, false); // 10 pixels, no alpha
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
|
||||
// Add frames with timestamps
|
||||
//CHECK(interpolator.addWithTimestamp(frame1, 0));
|
||||
//CHECK(interpolator.addWithTimestamp(frame2, 1000));
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 2000));
|
||||
|
||||
|
||||
const Frame *selected1;
|
||||
const Frame *selected2;
|
||||
|
||||
// Falls between two frames->
|
||||
bool selected = interpolator.selectFrames(500, &selected1, &selected2);
|
||||
CHECK(selected);
|
||||
CHECK(selected1);
|
||||
CHECK(selected2);
|
||||
// now check that the frames are different
|
||||
CHECK(selected1 != selected2);
|
||||
// now check that the timestamp of the first frame is less than the
|
||||
// timestamp of the second frame
|
||||
CHECK(selected1->getTimestamp() == 0);
|
||||
CHECK(selected2->getTimestamp() == 1000);
|
||||
}
|
||||
|
||||
SUBCASE("2 frame interpolator after") {
|
||||
// Create an interpolator with capacity for 2 frames
|
||||
FrameInterpolator interpolator(2, -1);
|
||||
|
||||
// Create some test frames with different timestamps
|
||||
FrameRef frame1 = FrameRef::New(10, false); // 10 pixels, no alpha
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
|
||||
// Add frames with timestamps
|
||||
CHECK(interpolator.push_front(frame1, 0, 0));
|
||||
CHECK(interpolator.push_front(frame2, 1, 1000));
|
||||
|
||||
const Frame *selected1;
|
||||
const Frame *selected2;
|
||||
|
||||
// Falls between two frames->
|
||||
bool selected = interpolator.selectFrames(1500, &selected1, &selected2);
|
||||
CHECK(selected);
|
||||
CHECK(selected1);
|
||||
CHECK(selected2);
|
||||
// now check that the frames are different
|
||||
CHECK(selected1 == selected2);
|
||||
// now check that the timestamp of the first frame is less than the
|
||||
// timestamp of the second frame
|
||||
CHECK(selected1->getTimestamp() == 1000);
|
||||
CHECK(selected2->getTimestamp() == 1000);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("FrameInterpolator::addWithTimestamp") {
|
||||
SUBCASE("add first frame") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame, 0, 1000));
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(frames->size(), 1);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 1000);
|
||||
}
|
||||
|
||||
SUBCASE("add second frame which is before first frame and should be rejected") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK_FALSE(interpolator.push_front(frame2, 1, 500));
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(frames->size(), 1);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 1000);
|
||||
}
|
||||
|
||||
|
||||
SUBCASE("add second frame which has the same timestamp as first frame and should be rejected") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK_FALSE(interpolator.push_front(frame2, 1, 1000));
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(frames->size(), 1);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 1000);
|
||||
}
|
||||
|
||||
SUBCASE("add second frame which is after first frame and should be accepted") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 1500));
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(frames->size(), 2);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 1500);
|
||||
CHECK_EQ(frames->back().frame->getTimestamp(), 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("FrameInterpolator::addWithTimestamp and overflow") {
|
||||
SUBCASE("add two frames and check time") {
|
||||
FrameInterpolator interpolator(2, -1);
|
||||
FrameRef frame = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame, 1, 2000));
|
||||
CHECK(interpolator.push_front(frame, 2, 3000));
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(frames->size(), 2);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 3000);
|
||||
CHECK_EQ(frames->back().frame->getTimestamp(), 2000);
|
||||
}
|
||||
|
||||
SUBCASE("add two frames and check that Frame object was recycled") {
|
||||
FrameInterpolator interpolator(2, -1);
|
||||
FrameInterpolator::FrameBuffer* frames = interpolator.getFrames();
|
||||
CHECK_EQ(2, frames->capacity());
|
||||
CHECK_EQ(0, frames->size());
|
||||
FrameRef frame = FrameRef::New(2, false);
|
||||
CHECK(interpolator.push_front(frame, 0, 1000));
|
||||
CHECK_EQ(2, frames->capacity());
|
||||
CHECK_EQ(1, frames->size());
|
||||
|
||||
CHECK(interpolator.push_front(frame, 1, 2000));
|
||||
CHECK_EQ(2, frames->capacity());
|
||||
CHECK_EQ(2, frames->size());
|
||||
CHECK(frames->full());
|
||||
|
||||
Frame* frameThatShouldBeRecycled = frames->back().frame.get();
|
||||
CHECK(interpolator.push_front(frame, 2, 3000));
|
||||
CHECK_EQ(frames->size(), 2);
|
||||
CHECK_EQ(frames->front().frame->getTimestamp(), 3000);
|
||||
// check pointers are equal
|
||||
CHECK(frames->front().frame == frameThatShouldBeRecycled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("FrameInterpolator::draw") {
|
||||
SUBCASE("Empty interpolator") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame = FrameRef::New(10, false);
|
||||
FrameRef dst = FrameRef::New(10, false);
|
||||
CHECK_FALSE(interpolator.draw(0, dst.get()));
|
||||
}
|
||||
|
||||
SUBCASE("Add one frame and check that we will draw with that") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame, 0, 1000));
|
||||
FrameRef dst = FrameRef::New(10, false);
|
||||
CHECK(interpolator.draw(0, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1000);
|
||||
CHECK(interpolator.draw(2000, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1000);
|
||||
}
|
||||
|
||||
SUBCASE("Add two frames and check behavior for drawing before, between and after") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 2000));
|
||||
FrameRef dst = FrameRef::New(10, false);
|
||||
CHECK(interpolator.draw(0, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1000);
|
||||
CHECK(interpolator.draw(1500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1500);
|
||||
CHECK(interpolator.draw(2500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 2000);
|
||||
}
|
||||
|
||||
SUBCASE("Add three frames and check behavior for drawing before, between 0&1, between 1&2 and after") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
FrameRef frame3 = FrameRef::New(10, false);
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 2000));
|
||||
CHECK(interpolator.push_front(frame3, 2, 3000));
|
||||
FrameRef dst = FrameRef::New(10, false);
|
||||
CHECK(interpolator.draw(0, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1000);
|
||||
// now check what happens when we draw exactly at 1000
|
||||
CHECK(interpolator.draw(1000, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1000);
|
||||
CHECK(interpolator.draw(1500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1500);
|
||||
// now check what happens when we draw exactly at 2000
|
||||
CHECK(interpolator.draw(2000, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 2000);
|
||||
CHECK(interpolator.draw(2500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 2500);
|
||||
CHECK(interpolator.draw(3500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 3000);
|
||||
}
|
||||
|
||||
SUBCASE("Check that the draw command interpolates between two added frames when queried from the middle") {
|
||||
FrameInterpolator interpolator(5, -1);
|
||||
FrameRef frame1 = FrameRef::New(10, false);
|
||||
FrameRef frame2 = FrameRef::New(10, false);
|
||||
// frame 1 is all red
|
||||
for (int i = 0; i < 10; i++) {
|
||||
frame1->rgb()[i] = CRGB::Red;
|
||||
}
|
||||
// frame 2 is all blue
|
||||
for (int i = 0; i < 10; i++) {
|
||||
frame2->rgb()[i] = CRGB::Blue;
|
||||
}
|
||||
CHECK(interpolator.push_front(frame1, 0, 1000));
|
||||
CHECK(interpolator.push_front(frame2, 1, 2000));
|
||||
FrameRef dst = FrameRef::New(10, false);
|
||||
CHECK(interpolator.draw(1500, dst.get()));
|
||||
CHECK_EQ(dst->getTimestamp(), 1500);
|
||||
// now check that the frame is interpolated between red and blue
|
||||
for (int i = 0; i < 10; i++) {
|
||||
CHECK(dst->rgb()[i] == CRGB(128, 0, 127));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
27
FastLED/tests/test_frame_tracker.cpp
Normal file
27
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);
|
||||
}
|
||||
32
FastLED/tests/test_fx.cpp
Normal file
32
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
FastLED/tests/test_fx2d_blend.cpp
Normal file
186
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
FastLED/tests/test_fx_engine.cpp
Normal file
210
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
FastLED/tests/test_fx_time.cpp
Normal file
60
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("TimeScale basic functionality") {
|
||||
FASTLED_USING_NAMESPACE;
|
||||
|
||||
SUBCASE("Initialization and normal time progression") {
|
||||
TimeScale 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") {
|
||||
TimeScale tw(1000);
|
||||
tw.setScale(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.setScale(0.5f); // Set half speed: 500ms.
|
||||
CHECK(tw.scale() == 0.5f);
|
||||
|
||||
tw.update(2500);
|
||||
CHECK(tw.time() == 1500);
|
||||
}
|
||||
|
||||
SUBCASE("Reset functionality") {
|
||||
TimeScale 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") {
|
||||
TimeScale tw(1000, 1.0f);
|
||||
tw.update(1001);
|
||||
CHECK(tw.time() == 1);
|
||||
tw.setScale(-1.0f);
|
||||
tw.update(2000);
|
||||
CHECK_EQ(tw.time(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
22
FastLED/tests/test_intmap.cpp
Normal file
22
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);
|
||||
}
|
||||
139
FastLED/tests/test_map.cpp
Normal file
139
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());
|
||||
}
|
||||
}
|
||||
77
FastLED/tests/test_math.cpp
Normal file
77
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);
|
||||
}
|
||||
205
FastLED/tests/test_rectangular_buffer.cpp
Normal file
205
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
226
FastLED/tests/test_refptr.cpp
Normal file
226
FastLED/tests/test_refptr.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
|
||||
// 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() override { Referent::ref(); }
|
||||
virtual void unref() override { Referent::unref(); }
|
||||
virtual void destroy() 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
128
FastLED/tests/test_screenmap.cpp
Normal file
128
FastLED/tests/test_screenmap.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
// 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);
|
||||
}
|
||||
33
FastLED/tests/test_sin32.cpp
Normal file
33
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);
|
||||
|
||||
}
|
||||
14
FastLED/tests/test_slice.cpp
Normal file
14
FastLED/tests/test_slice.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "fl/slice.h"
|
||||
|
||||
#include "fl/namespace.h"
|
||||
FASTLED_USING_NAMESPACE
|
||||
|
||||
TEST_CASE("compile test") {
|
||||
|
||||
}
|
||||
147
FastLED/tests/test_str.cpp
Normal file
147
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
FastLED/tests/test_strip_id_map.cpp
Normal file
45
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
FastLED/tests/test_strstream.cpp
Normal file
66
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
FastLED/tests/test_template_magic.cpp
Normal file
18
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);
|
||||
}
|
||||
|
||||
72
FastLED/tests/test_transition_ramp.cpp
Normal file
72
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.update(t0) == 0);
|
||||
|
||||
// mid‑rise: 5 ms → (5*255/10) ≈ 127
|
||||
REQUIRE(ramp.update(t0 + 5) == static_cast<uint8_t>((5 * 255) / 10));
|
||||
|
||||
// end of rise: 10 ms → full on
|
||||
REQUIRE(ramp.update(t0 + 10) == 255);
|
||||
|
||||
// plateau: well within [10, 90) ms
|
||||
REQUIRE(ramp.update(t0 + 50) == 255);
|
||||
|
||||
// mid‑fall: elapsed=95 ms → fallingElapsed=5 ms → 255 - (5*255/10) ≈ 128
|
||||
uint8_t value = ramp.update(t0 + 115);
|
||||
uint8_t expected = static_cast<uint8_t>(255 - (5 * 255) / 10);
|
||||
REQUIRE_EQ(expected, value);
|
||||
|
||||
// after latch: 110 ms → off
|
||||
// REQUIRE(ramp.update(t0 + 110) == 0);
|
||||
|
||||
// now do it again
|
||||
ramp.trigger(200);
|
||||
// at start: still at zero
|
||||
REQUIRE(ramp.update(200) == 0);
|
||||
// mid‑rise: 205 ms → (5*255/10) ≈ 127
|
||||
REQUIRE(ramp.update(205) == static_cast<uint8_t>((5 * 255) / 10));
|
||||
// end of rise: 210 ms → full on
|
||||
REQUIRE(ramp.update(210) == 255);
|
||||
// plateau: well within [210, 290) ms
|
||||
REQUIRE(ramp.update(250) == 255);
|
||||
// mid‑fall: elapsed=295 ms → fallingElapsed=5 ms → 255 - (5*255/10) ≈ 128
|
||||
REQUIRE(ramp.update(315) == static_cast<uint8_t>(255 - (5 * 255) / 10));
|
||||
// after latch: 310 ms → off
|
||||
REQUIRE(ramp.update(320) == 0);
|
||||
// after latch: 410 ms → off
|
||||
REQUIRE(ramp.update(410) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Real world Bug") {
|
||||
TimeRamp transition = TimeRamp(500, 0, 500);
|
||||
|
||||
uint8_t value = transition.update(0);
|
||||
CHECK(value == 0);
|
||||
value = transition.update(1);
|
||||
CHECK(value == 0);
|
||||
|
||||
transition.trigger(6900);
|
||||
value = transition.update(6900);
|
||||
REQUIRE_EQ(value, 0);
|
||||
|
||||
value = transition.update(6900 + 500);
|
||||
REQUIRE_EQ(value, 255);
|
||||
|
||||
value = transition.update(6900 + 250);
|
||||
REQUIRE_EQ(value, 127);
|
||||
}
|
||||
15
FastLED/tests/test_ui.cpp
Normal file
15
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") {
|
||||
}
|
||||
|
||||
|
||||
390
FastLED/tests/test_vector.cpp
Normal file
390
FastLED/tests/test_vector.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
|
||||
// g++ --std=c++11 test.cpp
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
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
FastLED/tests/test_video.cpp
Normal file
232
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
FastLED/tests/test_videofx_wrapper.cpp
Normal file
53
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));
|
||||
}
|
||||
Reference in New Issue
Block a user