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/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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
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
|
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
|
||||||
|
|||||||
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