Add foundation task queue tests

This commit is contained in:
2026-06-01 08:23:59 +02:00
parent a7bb04f54b
commit 3f5711773e
9 changed files with 252 additions and 8 deletions

View File

@@ -71,6 +71,7 @@ add_library(pp_foundation STATIC
src/foundation/binary_stream.cpp
src/foundation/log.cpp
src/foundation/parse.cpp
src/foundation/task_queue.cpp
src/foundation/trace.cpp)
target_include_directories(pp_foundation
PUBLIC

View File

@@ -80,8 +80,8 @@ Known local toolchain state:
`platform-build` automation wrapper for `pp_foundation`, `pp_assets`,
`pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`,
`pp_ui_core`, `pano_cli`, and their current headless test binaries,
including foundation logging, PPI header, paint stroke sampling, and layout
XML parse coverage.
including foundation logging/task queue coverage, PPI header, paint stroke
sampling, and layout XML parse coverage.
- `panopainter_validate_shaders` validates the current combined GLSL shader
files for one vertex stage marker, one fragment stage marker, valid marker
order, and existing relative includes.

View File

@@ -304,9 +304,9 @@ Goal: split libraries while keeping current app behavior.
Status: started. `pp_foundation` exists with binary stream utilities and
boundary/overread tests. It also owns strict decimal `uint32` parsing used by
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
input. A structured logging facade and deterministic `TraceRecorder` now
record component/name/thread/frame/stroke metadata with filtering and invalid-end
tests. `pp_assets` has started with
input. A structured logging facade, bounded FIFO task queue, and deterministic
`TraceRecorder` now record component/name/thread/frame/stroke metadata with
filtering, capacity, and invalid-end tests. `pp_assets` has started with
PNG/JPEG signature detection plus PPI header recognition, with
corrupt/truncated/unsupported tests.
`pp_paint` has started with CPU reference math for the five current shader
@@ -524,7 +524,7 @@ Last verified on 2026-06-01:
```powershell
cmake --preset windows-msvc-default
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter
ctest --preset desktop-fast --build-config Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
@@ -540,6 +540,7 @@ Results:
- `pp_foundation_binary_stream_tests` passed.
- `pp_foundation_log_tests` passed.
- `pp_foundation_parse_tests` passed.
- `pp_foundation_task_queue_tests` passed.
- `pp_foundation_trace_tests` passed.
- `pp_assets_image_format_tests` passed.
- `pp_assets_ppi_header_tests` passed.

View File

@@ -1,7 +1,7 @@
[CmdletBinding()]
param(
[string[]]$Presets = @("android-arm64"),
[string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_ppi_header_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_document_tests", "pp_renderer_api_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests")
[string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_task_queue_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_ppi_header_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_document_tests", "pp_renderer_api_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests")
)
$ErrorActionPreference = "Stop"

View File

@@ -3,7 +3,7 @@ set -u
preset="${1:-android-arm64}"
shift || true
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}"
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_ppi_header_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}"
start="$(date +%s)"
cmake --preset "$preset"

View File

@@ -0,0 +1,83 @@
#include "foundation/task_queue.h"
namespace pp::foundation {
TaskQueue::TaskQueue(std::size_t max_entries) noexcept
: max_entries_(max_entries)
{
}
std::size_t TaskQueue::size() const noexcept
{
return tasks_.size();
}
bool TaskQueue::empty() const noexcept
{
return tasks_.empty();
}
std::size_t TaskQueue::max_entries() const noexcept
{
return max_entries_;
}
Status TaskQueue::push(TaskItem task)
{
if (max_entries_ == 0U || max_entries_ > max_task_queue_entries) {
return Status::out_of_range("task queue capacity is outside the configured range");
}
if (task.callback == nullptr) {
return Status::invalid_argument("task callback must not be null");
}
if (tasks_.size() >= max_entries_) {
return Status::out_of_range("task queue is full");
}
tasks_.push_back(task);
return Status::success();
}
Result<TaskItem> TaskQueue::pop() noexcept
{
if (tasks_.empty()) {
return Result<TaskItem>::failure(Status::out_of_range("task queue is empty"));
}
const auto task = tasks_.front();
tasks_.pop_front();
return Result<TaskItem>::success(task);
}
Status TaskQueue::run_next() noexcept
{
auto task = pop();
if (!task.ok()) {
return task.status();
}
task.value().callback(task.value().user_data);
return Status::success();
}
std::size_t TaskQueue::run_all() noexcept
{
std::size_t count = 0;
while (!tasks_.empty()) {
const auto status = run_next();
if (!status.ok()) {
break;
}
++count;
}
return count;
}
void TaskQueue::clear() noexcept
{
tasks_.clear();
}
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "foundation/result.h"
#include <cstddef>
#include <cstdint>
#include <deque>
namespace pp::foundation {
constexpr std::size_t max_task_queue_entries = 65536;
using TaskCallback = void (*)(void* user_data) noexcept;
struct TaskItem {
TaskCallback callback = nullptr;
void* user_data = nullptr;
std::uint64_t id = 0;
};
class TaskQueue {
public:
explicit TaskQueue(std::size_t max_entries = max_task_queue_entries) noexcept;
[[nodiscard]] std::size_t size() const noexcept;
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] std::size_t max_entries() const noexcept;
[[nodiscard]] Status push(TaskItem task);
[[nodiscard]] Result<TaskItem> pop() noexcept;
[[nodiscard]] Status run_next() noexcept;
[[nodiscard]] std::size_t run_all() noexcept;
void clear() noexcept;
private:
std::size_t max_entries_ = max_task_queue_entries;
std::deque<TaskItem> tasks_;
};
}

View File

@@ -36,6 +36,16 @@ add_test(NAME pp_foundation_parse_tests COMMAND pp_foundation_parse_tests)
set_tests_properties(pp_foundation_parse_tests PROPERTIES
LABELS "foundation;desktop-fast")
add_executable(pp_foundation_task_queue_tests
foundation/task_queue_tests.cpp)
target_link_libraries(pp_foundation_task_queue_tests PRIVATE
pp_foundation
pp_test_harness)
add_test(NAME pp_foundation_task_queue_tests COMMAND pp_foundation_task_queue_tests)
set_tests_properties(pp_foundation_task_queue_tests PROPERTIES
LABELS "foundation;desktop-fast")
add_executable(pp_foundation_trace_tests
foundation/trace_tests.cpp)
target_link_libraries(pp_foundation_trace_tests PRIVATE

View File

@@ -0,0 +1,109 @@
#include "foundation/task_queue.h"
#include "test_harness.h"
using pp::foundation::StatusCode;
using pp::foundation::TaskItem;
using pp::foundation::TaskQueue;
using pp::foundation::max_task_queue_entries;
namespace {
struct Counter {
int value = 0;
};
void increment(void* user_data) noexcept
{
auto* counter = static_cast<Counter*>(user_data);
++counter->value;
}
void add_two(void* user_data) noexcept
{
auto* counter = static_cast<Counter*>(user_data);
counter->value += 2;
}
void runs_tasks_in_fifo_order(pp::tests::Harness& h)
{
Counter counter;
TaskQueue queue(4);
PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter, .id = 1 }).ok());
PP_EXPECT(h, queue.push(TaskItem { .callback = add_two, .user_data = &counter, .id = 2 }).ok());
PP_EXPECT(h, queue.size() == 2U);
PP_EXPECT(h, queue.run_next().ok());
PP_EXPECT(h, counter.value == 1);
PP_EXPECT(h, queue.run_next().ok());
PP_EXPECT(h, counter.value == 3);
PP_EXPECT(h, queue.empty());
}
void pops_without_running(pp::tests::Harness& h)
{
Counter counter;
TaskQueue queue(2);
PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter, .id = 42 }).ok());
const auto task = queue.pop();
PP_EXPECT(h, task.ok());
PP_EXPECT(h, task.value().id == 42U);
PP_EXPECT(h, counter.value == 0);
PP_EXPECT(h, queue.empty());
}
void rejects_invalid_or_excessive_work(pp::tests::Harness& h)
{
Counter counter;
TaskQueue queue(1);
TaskQueue invalid_queue(0);
TaskQueue too_large(max_task_queue_entries + 1U);
const auto null_task = queue.push(TaskItem {});
PP_EXPECT(h, !null_task.ok());
PP_EXPECT(h, null_task.code == StatusCode::invalid_argument);
PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
const auto full = queue.push(TaskItem { .callback = increment, .user_data = &counter });
PP_EXPECT(h, !full.ok());
PP_EXPECT(h, full.code == StatusCode::out_of_range);
const auto invalid_capacity = invalid_queue.push(TaskItem { .callback = increment, .user_data = &counter });
const auto excessive_capacity = too_large.push(TaskItem { .callback = increment, .user_data = &counter });
PP_EXPECT(h, !invalid_capacity.ok());
PP_EXPECT(h, invalid_capacity.code == StatusCode::out_of_range);
PP_EXPECT(h, !excessive_capacity.ok());
PP_EXPECT(h, excessive_capacity.code == StatusCode::out_of_range);
}
void run_all_and_clear_are_bounded(pp::tests::Harness& h)
{
Counter counter;
TaskQueue queue(4);
PP_EXPECT(h, queue.max_entries() == 4U);
PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
PP_EXPECT(h, queue.push(TaskItem { .callback = increment, .user_data = &counter }).ok());
PP_EXPECT(h, queue.run_all() == 2U);
PP_EXPECT(h, counter.value == 2);
PP_EXPECT(h, queue.push(TaskItem { .callback = add_two, .user_data = &counter }).ok());
queue.clear();
PP_EXPECT(h, queue.empty());
const auto empty_pop = queue.pop();
PP_EXPECT(h, !empty_pop.ok());
PP_EXPECT(h, empty_pop.status().code == StatusCode::out_of_range);
}
}
int main()
{
pp::tests::Harness harness;
harness.run("runs_tasks_in_fifo_order", runs_tasks_in_fifo_order);
harness.run("pops_without_running", pops_without_running);
harness.run("rejects_invalid_or_excessive_work", rejects_invalid_or_excessive_work);
harness.run("run_all_and_clear_are_bounded", run_all_and_clear_are_bounded);
return harness.finish();
}