Add Android headless preset and parser tests
This commit is contained in:
@@ -53,7 +53,8 @@ add_custom_target(panopainter_modernization_status
|
||||
VERBATIM)
|
||||
|
||||
add_library(pp_foundation STATIC
|
||||
src/foundation/binary_stream.cpp)
|
||||
src/foundation/binary_stream.cpp
|
||||
src/foundation/parse.cpp)
|
||||
target_include_directories(pp_foundation
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
"PP_ENABLE_VIDEO": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "platform-headless-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"PP_BUILD_APP": "OFF",
|
||||
"PP_ENABLE_CLOUD": "OFF",
|
||||
"PP_ENABLE_VIDEO": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs2026-x64",
|
||||
"inherits": "base",
|
||||
@@ -50,7 +60,7 @@
|
||||
},
|
||||
{
|
||||
"name": "linux-clang",
|
||||
"inherits": "base",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Linux clang",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
@@ -59,28 +69,57 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm64",
|
||||
"inherits": "base",
|
||||
"displayName": "Android arm64-v8a",
|
||||
"name": "android-base",
|
||||
"hidden": true,
|
||||
"inherits": "platform-headless-base",
|
||||
"generator": "Ninja",
|
||||
"toolchainFile": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"ANDROID_PLATFORM": "android-26"
|
||||
"ANDROID_PLATFORM": "android-26",
|
||||
"ANDROID_STL": "c++_shared",
|
||||
"PP_ENABLE_VR": "OFF",
|
||||
"PP_ENABLE_OPENGL": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x64",
|
||||
"inherits": "base",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android x86_64",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "x86_64",
|
||||
"ANDROID_PLATFORM": "android-26"
|
||||
"ANDROID_ABI": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-quest-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android Quest arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"PP_ENABLE_VR": "ON",
|
||||
"PP_ANDROID_FLAVOR": "quest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-focus-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android Focus/Wave arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"PP_ENABLE_VR": "ON",
|
||||
"PP_ANDROID_FLAVOR": "focus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emscripten",
|
||||
"inherits": "base",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Emscripten WebGL",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
@@ -90,13 +129,13 @@
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"inherits": "base",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "macOS",
|
||||
"generator": "Ninja"
|
||||
},
|
||||
{
|
||||
"name": "ios-device",
|
||||
"inherits": "base",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "iOS device",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
@@ -106,7 +145,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ios-simulator",
|
||||
"inherits": "base",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "iOS simulator",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
@@ -131,6 +170,22 @@
|
||||
{
|
||||
"name": "linux-clang",
|
||||
"configurePreset": "linux-clang"
|
||||
},
|
||||
{
|
||||
"name": "android-arm64",
|
||||
"configurePreset": "android-arm64"
|
||||
},
|
||||
{
|
||||
"name": "android-x64",
|
||||
"configurePreset": "android-x64"
|
||||
},
|
||||
{
|
||||
"name": "android-quest-arm64",
|
||||
"configurePreset": "android-quest-arm64"
|
||||
},
|
||||
{
|
||||
"name": "android-focus-arm64",
|
||||
"configurePreset": "android-focus-arm64"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
|
||||
@@ -14,3 +14,6 @@ option(PP_ENABLE_TSAN "Enable ThreadSanitizer for headless targets where support
|
||||
option(PP_ENABLE_MSVC_ANALYZE "Enable MSVC static analysis." OFF)
|
||||
option(PP_ENABLE_CLANG_TIDY "Enable clang-tidy integration." OFF)
|
||||
option(PP_ENABLE_CPPCHECK "Enable cppcheck integration." OFF)
|
||||
|
||||
set(PP_ANDROID_FLAVOR "standard" CACHE STRING "Android package flavor: standard, quest, or focus.")
|
||||
set_property(CACHE PP_ANDROID_FLAVOR PROPERTY STRINGS standard quest focus)
|
||||
|
||||
@@ -63,6 +63,8 @@ cmake --build --preset windows-msvc-default --config Debug --target 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
|
||||
cmake --preset android-arm64
|
||||
cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests
|
||||
```
|
||||
|
||||
Known local toolchain state:
|
||||
@@ -71,6 +73,7 @@ Known local toolchain state:
|
||||
- Local Visual Studio generator selected by CMake: Visual Studio 17 2022
|
||||
- Android SDK: `C:\Users\omara\AppData\Local\Android\Sdk`
|
||||
- Android NDK: `C:\Users\omara\AppData\Local\Android\Sdk\ndk\29.0.14206865`
|
||||
- Android arm64 headless configure/build passes through root CMake.
|
||||
- `vcpkg` is not on PATH yet; see DEBT-0007.
|
||||
|
||||
Known warnings after the current CMake app build:
|
||||
|
||||
@@ -28,6 +28,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0006 | Open | Modernization | `pano_cli create-document` validates and emits JSON command contracts but does not yet invoke the legacy document/app model | The document model has not been extracted from `Canvas`/`App` yet | `pano_cli create-document --width 64 --height 32 --layers 2`; CTest `pano_cli_create_document_smoke` | Replace command contract implementation with real `pp_document` creation once Phase 4 extracts the document model |
|
||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` exists but CMake is not yet using a validated vcpkg toolchain on this machine | `vcpkg` is not available on PATH and Visual Studio reports manifest mode is disabled | `cmake --preset windows-msvc-default` currently configures with vendored dependencies | Add validated vcpkg toolchain/preset integration for desktop, Android, and Apple triplets |
|
||||
| DEBT-0008 | Open | Modernization | `windows-msvc-default` preset is used for local validation because the VS 2026 generator is not installed here | The target VS 2026 preset must remain, but this machine configures with Visual Studio 17 2022 | `cmake --preset windows-msvc-default`; `ctest --preset desktop-fast --build-config Debug` | Validate `windows-vs2026-x64` on a machine with Visual Studio 2026 installed and make it the default Windows validation preset |
|
||||
| DEBT-0009 | Open | Modernization | Android root CMake validation currently builds headless targets only, not APK/package variants | Platform app entrypoints still live in legacy Gradle/CMake projects and need Phase 6 alignment | `cmake --preset android-arm64`; `cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests` | Android standard, Quest, and Focus/Wave package targets consume shared component targets and have package smoke commands |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -142,8 +142,9 @@ Goal: make CMake the canonical source list without breaking existing projects.
|
||||
Status: in progress. Root `CMakeLists.txt`, `CMakePresets.json`, and project
|
||||
option targets exist. The Windows desktop app builds through CMake as
|
||||
`PanoPainter`; the raw Visual Studio solution/project files were removed on
|
||||
2026-05-31 by user decision. Non-Windows platform build files remain during
|
||||
Phase 6 alignment.
|
||||
2026-05-31 by user decision. Android arm64 now configures and builds headless
|
||||
foundation/tool targets through the root CMake/NDK path. Non-Windows platform
|
||||
app/package files remain during Phase 6 alignment.
|
||||
|
||||
Implementation tasks:
|
||||
|
||||
@@ -297,8 +298,10 @@ Gate:
|
||||
Goal: split libraries while keeping current app behavior.
|
||||
|
||||
Status: started. `pp_foundation` exists with binary stream utilities and
|
||||
boundary/overread tests. Continue extracting legacy-safe utilities before
|
||||
moving assets, paint, or document behavior.
|
||||
boundary/overread tests. It also owns strict decimal `uint32` parsing used by
|
||||
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
|
||||
input. Continue extracting legacy-safe utilities before moving assets, paint,
|
||||
or document behavior.
|
||||
|
||||
Implementation tasks:
|
||||
|
||||
@@ -498,20 +501,25 @@ Last verified on 2026-05-31:
|
||||
|
||||
```powershell
|
||||
cmake --preset windows-msvc-default
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_tests pano_cli PanoPainter
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_parse_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
|
||||
cmake --preset android-arm64
|
||||
cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- `pp_foundation_tests` passed.
|
||||
- `pp_foundation_binary_stream_tests` passed.
|
||||
- `pp_foundation_parse_tests` passed.
|
||||
- `pano_cli_create_document_smoke` passed.
|
||||
- `PanoPainter.exe` built through CMake at
|
||||
`out/build/windows-msvc-default/Debug/PanoPainter.exe`.
|
||||
- PowerShell build/test automation wrappers return JSON summaries and passed
|
||||
local smoke checks.
|
||||
- Android arm64 configured with NDK 29.0.14206865 and compiled headless
|
||||
foundation/tool/test targets.
|
||||
- Known remaining warnings: legacy project/vendor diagnostics, Visual Studio
|
||||
vcpkg-manifest warning, `LNK4099` missing libyuv PDBs, and `LNK4098` runtime
|
||||
library conflict from retained vendor binaries.
|
||||
|
||||
37
src/foundation/parse.cpp
Normal file
37
src/foundation/parse.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "foundation/parse.h"
|
||||
|
||||
#include <charconv>
|
||||
|
||||
namespace pp::foundation {
|
||||
|
||||
Result<std::uint32_t> parse_u32(std::string_view text) noexcept
|
||||
{
|
||||
if (text.empty()) {
|
||||
return Result<std::uint32_t>::failure(
|
||||
Status::invalid_argument("value must not be empty"));
|
||||
}
|
||||
|
||||
if (text.front() == '-' || text.front() == '+') {
|
||||
return Result<std::uint32_t>::failure(
|
||||
Status::invalid_argument("value must be an unsigned integer without a sign"));
|
||||
}
|
||||
|
||||
std::uint32_t value = 0;
|
||||
const auto* begin = text.data();
|
||||
const auto* end = text.data() + text.size();
|
||||
const auto [ptr, ec] = std::from_chars(begin, end, value);
|
||||
|
||||
if (ec == std::errc::result_out_of_range) {
|
||||
return Result<std::uint32_t>::failure(
|
||||
Status::out_of_range("value is outside the uint32 range"));
|
||||
}
|
||||
|
||||
if (ec != std::errc {} || ptr != end) {
|
||||
return Result<std::uint32_t>::failure(
|
||||
Status::invalid_argument("value must contain only decimal digits"));
|
||||
}
|
||||
|
||||
return Result<std::uint32_t>::success(value);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/foundation/parse.h
Normal file
12
src/foundation/parse.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::foundation {
|
||||
|
||||
[[nodiscard]] Result<std::uint32_t> parse_u32(std::string_view text) noexcept;
|
||||
|
||||
}
|
||||
@@ -5,14 +5,25 @@ target_link_libraries(pp_test_harness INTERFACE
|
||||
pp_project_options
|
||||
pp_project_warnings)
|
||||
|
||||
add_executable(pp_foundation_tests
|
||||
foundation/binary_stream_tests.cpp)
|
||||
target_link_libraries(pp_foundation_tests PRIVATE
|
||||
add_executable(pp_foundation_binary_stream_tests
|
||||
foundation/binary_stream_tests.cpp
|
||||
)
|
||||
target_link_libraries(pp_foundation_binary_stream_tests PRIVATE
|
||||
pp_foundation
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_foundation_tests COMMAND pp_foundation_tests)
|
||||
set_tests_properties(pp_foundation_tests PROPERTIES
|
||||
add_test(NAME pp_foundation_binary_stream_tests COMMAND pp_foundation_binary_stream_tests)
|
||||
set_tests_properties(pp_foundation_binary_stream_tests PROPERTIES
|
||||
LABELS "foundation;desktop-fast")
|
||||
|
||||
add_executable(pp_foundation_parse_tests
|
||||
foundation/parse_tests.cpp)
|
||||
target_link_libraries(pp_foundation_parse_tests PRIVATE
|
||||
pp_foundation
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_foundation_parse_tests COMMAND pp_foundation_parse_tests)
|
||||
set_tests_properties(pp_foundation_parse_tests PROPERTIES
|
||||
LABELS "foundation;desktop-fast")
|
||||
|
||||
if(TARGET pano_cli)
|
||||
|
||||
66
tests/foundation/parse_tests.cpp
Normal file
66
tests/foundation/parse_tests.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "foundation/parse.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
using pp::foundation::parse_u32;
|
||||
using pp::foundation::StatusCode;
|
||||
|
||||
namespace {
|
||||
|
||||
void accepts_decimal_uint32_values(pp::tests::Harness& h)
|
||||
{
|
||||
const auto zero = parse_u32("0");
|
||||
const auto ordinary = parse_u32("12345");
|
||||
const auto max = parse_u32("4294967295");
|
||||
|
||||
PP_EXPECT(h, zero.ok());
|
||||
PP_EXPECT(h, zero.value() == 0U);
|
||||
PP_EXPECT(h, ordinary.ok());
|
||||
PP_EXPECT(h, ordinary.value() == 12345U);
|
||||
PP_EXPECT(h, max.ok());
|
||||
PP_EXPECT(h, max.value() == UINT32_MAX);
|
||||
}
|
||||
|
||||
void rejects_empty_signed_and_mixed_input(pp::tests::Harness& h)
|
||||
{
|
||||
const auto empty = parse_u32("");
|
||||
const auto negative = parse_u32("-1");
|
||||
const auto positive = parse_u32("+1");
|
||||
const auto trailing = parse_u32("12px");
|
||||
const auto spaced = parse_u32(" 12");
|
||||
|
||||
PP_EXPECT(h, !empty.ok());
|
||||
PP_EXPECT(h, empty.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !negative.ok());
|
||||
PP_EXPECT(h, negative.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !positive.ok());
|
||||
PP_EXPECT(h, positive.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !trailing.ok());
|
||||
PP_EXPECT(h, trailing.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !spaced.ok());
|
||||
PP_EXPECT(h, spaced.status().code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void rejects_overflow_without_wrapping(pp::tests::Harness& h)
|
||||
{
|
||||
const auto overflow = parse_u32("4294967296");
|
||||
const auto very_large = parse_u32("999999999999999999999999999999999999");
|
||||
|
||||
PP_EXPECT(h, !overflow.ok());
|
||||
PP_EXPECT(h, overflow.status().code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !very_large.ok());
|
||||
PP_EXPECT(h, very_large.status().code == StatusCode::out_of_range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("accepts_decimal_uint32_values", accepts_decimal_uint32_values);
|
||||
harness.run("rejects_empty_signed_and_mixed_input", rejects_empty_signed_and_mixed_input);
|
||||
harness.run("rejects_overflow_without_wrapping", rejects_overflow_without_wrapping);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "foundation/parse.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
@@ -13,14 +13,6 @@ struct DocumentArgs {
|
||||
std::uint32_t layers = 1;
|
||||
};
|
||||
|
||||
bool parse_u32(std::string_view text, std::uint32_t& value)
|
||||
{
|
||||
const auto* begin = text.data();
|
||||
const auto* end = text.data() + text.size();
|
||||
const auto [ptr, ec] = std::from_chars(begin, end, value);
|
||||
return ec == std::errc {} && ptr == end;
|
||||
}
|
||||
|
||||
void print_error(std::string_view command, std::string_view message)
|
||||
{
|
||||
std::cout << "{\"ok\":false,\"command\":\"" << command
|
||||
@@ -44,17 +36,17 @@ pp::foundation::Status parse_document_args(int argc, char** argv, DocumentArgs&
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
|
||||
std::uint32_t value = 0;
|
||||
if (!parse_u32(argv[++i], value)) {
|
||||
return pp::foundation::Status::invalid_argument("option value must be an unsigned integer");
|
||||
const auto value = pp::foundation::parse_u32(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
|
||||
if (key == "--width") {
|
||||
args.width = value;
|
||||
args.width = value.value();
|
||||
} else if (key == "--height") {
|
||||
args.height = value;
|
||||
args.height = value.value();
|
||||
} else {
|
||||
args.layers = value;
|
||||
args.layers = value.value();
|
||||
}
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
|
||||
Reference in New Issue
Block a user