Add assets settings document tests
This commit is contained in:
@@ -85,7 +85,8 @@ target_link_libraries(pp_foundation
|
||||
|
||||
add_library(pp_assets STATIC
|
||||
src/assets/image_format.cpp
|
||||
src/assets/ppi_header.cpp)
|
||||
src/assets/ppi_header.cpp
|
||||
src/assets/settings_document.cpp)
|
||||
target_include_directories(pp_assets
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
|
||||
@@ -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 event/logging/task queue coverage, PPI header, paint
|
||||
stroke sampling, and layout XML parse coverage.
|
||||
including foundation event/logging/task queue coverage, PPI header, settings
|
||||
document, 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.
|
||||
|
||||
@@ -307,9 +307,9 @@ boundary/overread tests. It also owns strict decimal `uint32` parsing used by
|
||||
input. A synchronous event dispatcher, 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.
|
||||
invalid-end tests. `pp_assets` has started with PNG/JPEG signature detection,
|
||||
PPI header recognition, and a pure typed settings document model, with
|
||||
corrupt/truncated/unsupported and key/value limit tests.
|
||||
`pp_paint` has started with CPU reference math for the five current shader
|
||||
blend modes plus deterministic stroke spacing/interpolation. `pp_document` has
|
||||
started with a pure canvas/layer/frame model and layer/frame/undo-redo history
|
||||
@@ -525,7 +525,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_event_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
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_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_assets_settings_document_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
|
||||
@@ -546,6 +546,7 @@ Results:
|
||||
- `pp_foundation_trace_tests` passed.
|
||||
- `pp_assets_image_format_tests` passed.
|
||||
- `pp_assets_ppi_header_tests` passed.
|
||||
- `pp_assets_settings_document_tests` passed.
|
||||
- `pp_paint_blend_tests` passed.
|
||||
- `pp_paint_stroke_tests` passed.
|
||||
- `pp_document_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_event_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")
|
||||
[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_event_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_assets_settings_document_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_event_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}"
|
||||
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_event_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_assets_settings_document_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"
|
||||
|
||||
183
src/assets/settings_document.cpp
Normal file
183
src/assets/settings_document.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "assets/settings_document.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool is_valid_key_char(char value) noexcept
|
||||
{
|
||||
const auto ch = static_cast<unsigned char>(value);
|
||||
return std::isalnum(ch) != 0 || value == '_' || value == '-' || value == '.';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t SettingsDocument::size() const noexcept
|
||||
{
|
||||
return entries_.size();
|
||||
}
|
||||
|
||||
bool SettingsDocument::empty() const noexcept
|
||||
{
|
||||
return entries_.empty();
|
||||
}
|
||||
|
||||
bool SettingsDocument::has(std::string_view key) const noexcept
|
||||
{
|
||||
return find_entry(key) != entries_.end();
|
||||
}
|
||||
|
||||
const std::vector<SettingsEntry>& SettingsDocument::entries() const noexcept
|
||||
{
|
||||
return entries_;
|
||||
}
|
||||
|
||||
pp::foundation::Status SettingsDocument::set(std::string_view key, SettingsValue value)
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return key_status;
|
||||
}
|
||||
|
||||
const auto value_status = validate_settings_value(value);
|
||||
if (!value_status.ok()) {
|
||||
return value_status;
|
||||
}
|
||||
|
||||
auto found = find_entry(key);
|
||||
if (found != entries_.end()) {
|
||||
found->value = value;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
if (entries_.size() >= max_settings_entries) {
|
||||
return pp::foundation::Status::out_of_range("settings entry count exceeds the configured limit");
|
||||
}
|
||||
|
||||
entries_.push_back(SettingsEntry {
|
||||
.key = std::string(key),
|
||||
.value = value,
|
||||
});
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Result<SettingsValue> SettingsDocument::get(std::string_view key) const
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return pp::foundation::Result<SettingsValue>::failure(key_status);
|
||||
}
|
||||
|
||||
const auto found = find_entry(key);
|
||||
if (found == entries_.end()) {
|
||||
return pp::foundation::Result<SettingsValue>::failure(
|
||||
pp::foundation::Status::out_of_range("settings key was not found"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<SettingsValue>::success(found->value);
|
||||
}
|
||||
|
||||
pp::foundation::Status SettingsDocument::unset(std::string_view key) noexcept
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return key_status;
|
||||
}
|
||||
|
||||
const auto found = find_entry(key);
|
||||
if (found == entries_.end()) {
|
||||
return pp::foundation::Status::out_of_range("settings key was not found");
|
||||
}
|
||||
|
||||
entries_.erase(found);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void SettingsDocument::clear() noexcept
|
||||
{
|
||||
entries_.clear();
|
||||
}
|
||||
|
||||
std::vector<SettingsEntry>::iterator SettingsDocument::find_entry(std::string_view key) noexcept
|
||||
{
|
||||
return std::find_if(
|
||||
entries_.begin(),
|
||||
entries_.end(),
|
||||
[key](const SettingsEntry& entry) {
|
||||
return entry.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<SettingsEntry>::const_iterator SettingsDocument::find_entry(std::string_view key) const noexcept
|
||||
{
|
||||
return std::find_if(
|
||||
entries_.begin(),
|
||||
entries_.end(),
|
||||
[key](const SettingsEntry& entry) {
|
||||
return entry.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_settings_key(std::string_view key) noexcept
|
||||
{
|
||||
if (key.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("settings key must not be empty");
|
||||
}
|
||||
|
||||
if (key.size() > max_settings_key_length) {
|
||||
return pp::foundation::Status::out_of_range("settings key length exceeds the configured limit");
|
||||
}
|
||||
|
||||
if (key.front() == '.' || key.back() == '.') {
|
||||
return pp::foundation::Status::invalid_argument("settings key must not start or end with a dot");
|
||||
}
|
||||
|
||||
for (const auto ch : key) {
|
||||
if (!is_valid_key_char(ch)) {
|
||||
return pp::foundation::Status::invalid_argument("settings key contains an unsupported character");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_settings_value(const SettingsValue& value) noexcept
|
||||
{
|
||||
if (const auto* string_value = std::get_if<std::string>(&value)) {
|
||||
if (string_value->size() > max_settings_string_length) {
|
||||
return pp::foundation::Status::out_of_range("settings string length exceeds the configured limit");
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* double_value = std::get_if<double>(&value)) {
|
||||
if (!std::isfinite(*double_value)) {
|
||||
return pp::foundation::Status::invalid_argument("settings floating point value must be finite");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
const char* settings_value_type_name(const SettingsValue& value) noexcept
|
||||
{
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
return "bool";
|
||||
}
|
||||
if (std::holds_alternative<std::int64_t>(value)) {
|
||||
return "int64";
|
||||
}
|
||||
if (std::holds_alternative<double>(value)) {
|
||||
return "double";
|
||||
}
|
||||
if (std::holds_alternative<std::string>(value)) {
|
||||
return "string";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
}
|
||||
48
src/assets/settings_document.h
Normal file
48
src/assets/settings_document.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
constexpr std::size_t max_settings_entries = 4096;
|
||||
constexpr std::size_t max_settings_key_length = 128;
|
||||
constexpr std::size_t max_settings_string_length = 4096;
|
||||
|
||||
using SettingsValue = std::variant<bool, std::int64_t, double, std::string>;
|
||||
|
||||
struct SettingsEntry {
|
||||
std::string key;
|
||||
SettingsValue value;
|
||||
};
|
||||
|
||||
class SettingsDocument {
|
||||
public:
|
||||
[[nodiscard]] std::size_t size() const noexcept;
|
||||
[[nodiscard]] bool empty() const noexcept;
|
||||
[[nodiscard]] bool has(std::string_view key) const noexcept;
|
||||
[[nodiscard]] const std::vector<SettingsEntry>& entries() const noexcept;
|
||||
|
||||
[[nodiscard]] pp::foundation::Status set(std::string_view key, SettingsValue value);
|
||||
[[nodiscard]] pp::foundation::Result<SettingsValue> get(std::string_view key) const;
|
||||
[[nodiscard]] pp::foundation::Status unset(std::string_view key) noexcept;
|
||||
void clear() noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<SettingsEntry>::iterator find_entry(std::string_view key) noexcept;
|
||||
[[nodiscard]] std::vector<SettingsEntry>::const_iterator find_entry(std::string_view key) const noexcept;
|
||||
|
||||
std::vector<SettingsEntry> entries_;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_settings_key(std::string_view key) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_settings_value(const SettingsValue& value) noexcept;
|
||||
[[nodiscard]] const char* settings_value_type_name(const SettingsValue& value) noexcept;
|
||||
|
||||
}
|
||||
@@ -86,6 +86,16 @@ add_test(NAME pp_assets_ppi_header_tests COMMAND pp_assets_ppi_header_tests)
|
||||
set_tests_properties(pp_assets_ppi_header_tests PROPERTIES
|
||||
LABELS "assets;desktop-fast")
|
||||
|
||||
add_executable(pp_assets_settings_document_tests
|
||||
assets/settings_document_tests.cpp)
|
||||
target_link_libraries(pp_assets_settings_document_tests PRIVATE
|
||||
pp_assets
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_assets_settings_document_tests COMMAND pp_assets_settings_document_tests)
|
||||
set_tests_properties(pp_assets_settings_document_tests PROPERTIES
|
||||
LABELS "assets;desktop-fast")
|
||||
|
||||
add_executable(pp_paint_blend_tests
|
||||
paint/blend_tests.cpp)
|
||||
target_link_libraries(pp_paint_blend_tests PRIVATE
|
||||
|
||||
122
tests/assets/settings_document_tests.cpp
Normal file
122
tests/assets/settings_document_tests.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "assets/settings_document.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
using pp::assets::SettingsDocument;
|
||||
using pp::assets::SettingsValue;
|
||||
using pp::assets::max_settings_entries;
|
||||
using pp::assets::settings_value_type_name;
|
||||
using pp::assets::validate_settings_key;
|
||||
using pp::assets::validate_settings_value;
|
||||
using pp::foundation::StatusCode;
|
||||
|
||||
namespace {
|
||||
|
||||
void stores_updates_and_reads_typed_values(pp::tests::Harness& h)
|
||||
{
|
||||
SettingsDocument document;
|
||||
|
||||
PP_EXPECT(h, document.empty());
|
||||
PP_EXPECT(h, document.set("ui.theme", std::string("dark")).ok());
|
||||
PP_EXPECT(h, document.set("brush.size", std::int64_t { 42 }).ok());
|
||||
PP_EXPECT(h, document.set("brush.opacity", 0.75).ok());
|
||||
PP_EXPECT(h, document.set("tablet.enabled", true).ok());
|
||||
PP_EXPECT(h, document.size() == 4U);
|
||||
PP_EXPECT(h, document.has("brush.size"));
|
||||
|
||||
const auto theme = document.get("ui.theme");
|
||||
const auto size = document.get("brush.size");
|
||||
const auto opacity = document.get("brush.opacity");
|
||||
const auto tablet = document.get("tablet.enabled");
|
||||
|
||||
PP_EXPECT(h, theme.ok());
|
||||
PP_EXPECT(h, std::get<std::string>(theme.value()) == std::string_view("dark"));
|
||||
PP_EXPECT(h, settings_value_type_name(theme.value()) == std::string_view("string"));
|
||||
PP_EXPECT(h, size.ok());
|
||||
PP_EXPECT(h, std::get<std::int64_t>(size.value()) == 42);
|
||||
PP_EXPECT(h, opacity.ok());
|
||||
PP_EXPECT(h, std::fabs(std::get<double>(opacity.value()) - 0.75) < 0.0001);
|
||||
PP_EXPECT(h, tablet.ok());
|
||||
PP_EXPECT(h, std::get<bool>(tablet.value()));
|
||||
|
||||
PP_EXPECT(h, document.set("brush.size", std::int64_t { 64 }).ok());
|
||||
PP_EXPECT(h, document.size() == 4U);
|
||||
PP_EXPECT(h, std::get<std::int64_t>(document.get("brush.size").value()) == 64);
|
||||
}
|
||||
|
||||
void unsets_and_clears_entries(pp::tests::Harness& h)
|
||||
{
|
||||
SettingsDocument document;
|
||||
|
||||
PP_EXPECT(h, document.set("a", true).ok());
|
||||
PP_EXPECT(h, document.set("b", std::int64_t { 2 }).ok());
|
||||
PP_EXPECT(h, document.unset("a").ok());
|
||||
PP_EXPECT(h, !document.has("a"));
|
||||
PP_EXPECT(h, document.size() == 1U);
|
||||
|
||||
const auto missing = document.unset("a");
|
||||
PP_EXPECT(h, !missing.ok());
|
||||
PP_EXPECT(h, missing.code == StatusCode::out_of_range);
|
||||
|
||||
document.clear();
|
||||
PP_EXPECT(h, document.empty());
|
||||
}
|
||||
|
||||
void rejects_bad_keys_and_values(pp::tests::Harness& h)
|
||||
{
|
||||
const auto empty = validate_settings_key("");
|
||||
const auto dotted_start = validate_settings_key(".bad");
|
||||
const auto dotted_end = validate_settings_key("bad.");
|
||||
const auto invalid_char = validate_settings_key("bad/key");
|
||||
const auto long_key = validate_settings_key(std::string(129, 'a'));
|
||||
const auto non_finite = validate_settings_value(SettingsValue { std::nan("") });
|
||||
const auto huge_string = validate_settings_value(SettingsValue { std::string(4097, 'x') });
|
||||
|
||||
PP_EXPECT(h, !empty.ok());
|
||||
PP_EXPECT(h, empty.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !dotted_start.ok());
|
||||
PP_EXPECT(h, dotted_start.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !dotted_end.ok());
|
||||
PP_EXPECT(h, dotted_end.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !invalid_char.ok());
|
||||
PP_EXPECT(h, invalid_char.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !long_key.ok());
|
||||
PP_EXPECT(h, long_key.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !non_finite.ok());
|
||||
PP_EXPECT(h, non_finite.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !huge_string.ok());
|
||||
PP_EXPECT(h, huge_string.code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
void rejects_missing_and_excessive_entries(pp::tests::Harness& h)
|
||||
{
|
||||
SettingsDocument document;
|
||||
const auto missing = document.get("missing");
|
||||
PP_EXPECT(h, !missing.ok());
|
||||
PP_EXPECT(h, missing.status().code == StatusCode::out_of_range);
|
||||
|
||||
for (std::size_t i = 0; i < max_settings_entries; ++i) {
|
||||
const auto key = std::string("k") + std::to_string(i);
|
||||
PP_EXPECT(h, document.set(key, std::int64_t { 1 }).ok());
|
||||
}
|
||||
|
||||
const auto excessive = document.set("one-more", std::int64_t { 1 });
|
||||
PP_EXPECT(h, !excessive.ok());
|
||||
PP_EXPECT(h, excessive.code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("stores_updates_and_reads_typed_values", stores_updates_and_reads_typed_values);
|
||||
harness.run("unsets_and_clears_entries", unsets_and_clears_entries);
|
||||
harness.run("rejects_bad_keys_and_values", rejects_bad_keys_and_values);
|
||||
harness.run("rejects_missing_and_excessive_entries", rejects_missing_and_excessive_entries);
|
||||
return harness.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user