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/binary_stream.cpp
src/foundation/log.cpp src/foundation/log.cpp
src/foundation/parse.cpp src/foundation/parse.cpp
src/foundation/task_queue.cpp
src/foundation/trace.cpp) src/foundation/trace.cpp)
target_include_directories(pp_foundation target_include_directories(pp_foundation
PUBLIC PUBLIC

View File

@@ -80,8 +80,8 @@ Known local toolchain state:
`platform-build` automation wrapper for `pp_foundation`, `pp_assets`, `platform-build` automation wrapper for `pp_foundation`, `pp_assets`,
`pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`, `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`,
`pp_ui_core`, `pano_cli`, and their current headless test binaries, `pp_ui_core`, `pano_cli`, and their current headless test binaries,
including foundation logging, PPI header, paint stroke sampling, and layout including foundation logging/task queue coverage, PPI header, paint stroke
XML parse coverage. sampling, and layout XML parse coverage.
- `panopainter_validate_shaders` validates the current combined GLSL shader - `panopainter_validate_shaders` validates the current combined GLSL shader
files for one vertex stage marker, one fragment stage marker, valid marker files for one vertex stage marker, one fragment stage marker, valid marker
order, and existing relative includes. 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 Status: started. `pp_foundation` exists with binary stream utilities and
boundary/overread tests. It also owns strict decimal `uint32` parsing used by boundary/overread tests. It also owns strict decimal `uint32` parsing used by
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing `pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
input. A structured logging facade and deterministic `TraceRecorder` now input. A structured logging facade, bounded FIFO task queue, and deterministic
record component/name/thread/frame/stroke metadata with filtering and invalid-end `TraceRecorder` now record component/name/thread/frame/stroke metadata with
tests. `pp_assets` has started with filtering, capacity, and invalid-end tests. `pp_assets` has started with
PNG/JPEG signature detection plus PPI header recognition, with PNG/JPEG signature detection plus PPI header recognition, with
corrupt/truncated/unsupported tests. corrupt/truncated/unsupported tests.
`pp_paint` has started with CPU reference math for the five current shader `pp_paint` has started with CPU reference math for the five current shader
@@ -524,7 +524,7 @@ Last verified on 2026-06-01:
```powershell ```powershell
cmake --preset windows-msvc-default 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 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\test.ps1 -Preset desktop-fast -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli 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_binary_stream_tests` passed.
- `pp_foundation_log_tests` passed. - `pp_foundation_log_tests` passed.
- `pp_foundation_parse_tests` passed. - `pp_foundation_parse_tests` passed.
- `pp_foundation_task_queue_tests` passed.
- `pp_foundation_trace_tests` passed. - `pp_foundation_trace_tests` passed.
- `pp_assets_image_format_tests` passed. - `pp_assets_image_format_tests` passed.
- `pp_assets_ppi_header_tests` passed. - `pp_assets_ppi_header_tests` passed.

View File

@@ -1,7 +1,7 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string[]]$Presets = @("android-arm64"), [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" $ErrorActionPreference = "Stop"

View File

@@ -3,7 +3,7 @@ set -u
preset="${1:-android-arm64}" preset="${1:-android-arm64}"
shift || true 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)" start="$(date +%s)"
cmake --preset "$preset" 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 set_tests_properties(pp_foundation_parse_tests PROPERTIES
LABELS "foundation;desktop-fast") 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 add_executable(pp_foundation_trace_tests
foundation/trace_tests.cpp) foundation/trace_tests.cpp)
target_link_libraries(pp_foundation_trace_tests PRIVATE 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();
}