Add foundation task queue tests
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
83
src/foundation/task_queue.cpp
Normal file
83
src/foundation/task_queue.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
40
src/foundation/task_queue.h
Normal file
40
src/foundation/task_queue.h
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
109
tests/foundation/task_queue_tests.cpp
Normal file
109
tests/foundation/task_queue_tests.cpp
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user