Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ProxyStubFunctionalTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
- "JsonGenerator/**"
- ".github/workflows/ProxyStubFunctionalTests.yml"
pull_request:
branches: [ master ]
branches: [ "master", "development/uncompliant-property-coverage" ]
paths:
- "ProxyStubGenerator/**"
- "JsonGenerator/**"
Expand Down
32 changes: 20 additions & 12 deletions tests/FunctionalTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ The build is fully self-contained: Thunder and GoogleTest are fetched automatica

## Test interfaces

| CMake option | Interface | What it tests |
|---|---|---|
| `TEST_PRIMITIVES` | `ITestPrimitives` | Round-trip marshalling of every scalar type: `int8/16/24/32/64`, `uint8/16/24/32/64`, `float`, `double`, `bool`, `instance_id`, `Core::Time`, `Core::MACAddress`, `string` |
| `TEST_BUFFERS` | `ITestBuffers` | `@length` / `@maxlength` annotations on `uint8_t[]` parameters: input, output, in/out, and multi-buffer patterns |
| `TEST_OPTIONALS` | `ITestOptionals` | `Core::OptionalType<T>` on input and output parameters, `@default` values, and the optional-buffer convention |
| `TEST_RESTRICTIONS` | `ITestRestrictions` | `@restrict` range validation on integers (signed/unsigned), floats, strings (length), the `nonempty` shorthand, the `K`-suffix, and `OptionalType` combined with a range |
| `TEST_ENUMS` | `ITestEnums` | Enum serialisation: `@encode:text`, `@encode:bitmask`, `@property`, in/out/inout parameters, optional enums |
| `TEST_STRUCTS` | `ITestStructs` | POD struct marshalling: in/out/inout parameters, nested structs, `std::vector<struct>`, `@opaque`, `@index` (property slot), `@restrict` on vectors |
| `TEST_EVENTS` | `ITestEvents` | Event callback pattern (`@event`, `INotification`): scalar payloads, struct payloads, `std::vector` payloads, `OptionalType` payloads, `@statuslistener` |
| `TEST_ASYNC` | `ITestAsync` | `@async` pattern: concurrent slots, `ICallback` interface, `@property` with `@index`, `OptionalType` in a callback |
| `TEST_INTERFACES` | `ITestInterfacePointers` | Generator control annotations: `@interface` (void\* + ID dynamic typing), `@stub` (server-side only), `@omit` (excluded from both proxy and stub) |
| `TEST_ITERATORS` | `ITestIterators` | `RPC::IStringIterator` / `IValueIterator` passing and the store-and-query pattern that avoids COM-RPC channel deadlock |
| CMake option | Interface | COM-RPC | JSON-RPC | What it tests |
|---|---|---|---|---|
| `TEST_PRIMITIVES` | `ITestPrimitives` | ✓ | ✓ | Round-trip marshalling of every scalar type: `int8/16/24/32/64`, `uint8/16/24/32/64`, `float`, `double`, `bool`, `instance_id`, `Core::Time`, `Core::MACAddress`, `string` |
| `TEST_BUFFERS` | `ITestBuffers` | ✓ | — | `@length` / `@maxlength` annotations on `uint8_t[]` parameters: input, output, in/out, and multi-buffer patterns |
| `TEST_OPTIONALS` | `ITestOptionals` | ✓ | — | `Core::OptionalType<T>` on input and output parameters, `@default` values, and the optional-buffer convention |
| `TEST_RESTRICTIONS` | `ITestRestrictions` | ✓ | ✓ | `@restrict` range validation on integers (signed/unsigned), floats, strings (length), the `nonempty` shorthand, the `K`-suffix, and `OptionalType` combined with a range |
| `TEST_ENUMS` | `ITestEnums` | ✓ | ✓ | Enum serialisation: `@encode:text`, `@encode:bitmask`, `@property`, in/out/inout parameters, optional enums |
| `TEST_STRUCTS` | `ITestStructs` | ✓ | ✓ | POD struct marshalling: in/out/inout parameters, nested structs, `std::vector<struct>`, `@opaque`, `@index` (property slot), `@restrict` on vectors |
| `TEST_EVENTS` | `ITestEvents` | ✓ | — | Event callback pattern (`@event`, `INotification`): scalar payloads, struct payloads, `std::vector` payloads, `OptionalType` payloads, `@statuslistener` |
| `TEST_ASYNC` | `ITestAsync` | ✓ | ✓ | `@async` pattern: concurrent slots, `ICallback` interface, `@property` with `@index`, `OptionalType` in a callback |
| `TEST_INTERFACES` | `ITestInterfaces` | ✓ | — | Generator control annotations: `@interface` (void\* + ID dynamic typing), `@stub` (server-side only), `@omit` (excluded from both proxy and stub) |
| `TEST_ITERATORS` | `ITestIterators` | ✓ | — | `RPC::IStringIterator` / `IValueIterator` passing and the store-and-query pattern that avoids COM-RPC channel deadlock |
| `TEST_ENCODING_MAC` | `ITestEncodingMac` | ✓ | ✓ | `@encode:mac` — MAC address encoding/decoding round-trip |
| `TEST_LENGTH_MODES` | `ITestLengthModes` | ✓ | — | `@length:void` and `@length:return` — length-less and return-value-as-length buffer modes |
| `TEST_JSON_SHAPE` | `ITestJsonShape` | ✓ | ✓ | `@wrapped` (single output enclosed in object), `@extract` (list flattening) |
| `TEST_JSON_TEXT_KEEP` | `ITestJsonTextKeep` | ✓ | ✓ | `@text:keep` — method and parameter names preserved exactly as in C++ |
| `TEST_JSON_TEXT_CASE` | `ITestJsonTextCase` | ✓ | ✓ | `@text:legacy` — names lowercased per legacy convention |
| `TEST_JSON_COMPLIANT` | `ITestJsonCompliant` | ✓ | ✓ | `@compliant` — baseline compliant JSON-RPC format (single-param method wrapped in object) |
| `TEST_JSON_UNCOMPLIANT_EXT` | `ITestJsonUncompliantExtended` | ✓ | ✓ | `@uncompliant:extended` (**deprecated** — tested for regression) — property SET sends bare scalar; method params remain wrapped |
| `TEST_JSON_UNCOMPLIANT_COL` | `ITestJsonUncompliantCollapsed` | ✓ | ✓ | `@uncompliant:collapsed` (**deprecated** — tested for regression) — property SET and single-param methods both send bare scalars; wrapped method params are rejected |

`TEST_INTERFACES` and `TEST_ITERATORS` are off by default pending a thorough review of the generated code.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ namespace TestImplementation {
return Core::ERROR_NONE;
}

Core::hresult Value(uint32_t& counter) const override
{
counter = _value;
return Core::ERROR_NONE;
}

Core::hresult Value(const uint32_t counter) override
{
_value = counter;
return Core::ERROR_NONE;
}

BEGIN_INTERFACE_MAP(TestJsonUncompliantCollapsedImpl)
INTERFACE_ENTRY(FunctionalTest::ITestJsonUncompliantCollapsed)
END_INTERFACE_MAP

private:
uint32_t _value{0};
};

static Factory::Registrar<FunctionalTest::ITestJsonUncompliantCollapsed, TestJsonUncompliantCollapsedImpl> g_registrar;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ namespace TestImplementation {
return Core::ERROR_NONE;
}

Core::hresult Value(uint32_t& counter) const override
{
counter = _value;
return Core::ERROR_NONE;
}

Core::hresult Value(const uint32_t counter) override
{
_value = counter;
return Core::ERROR_NONE;
}

BEGIN_INTERFACE_MAP(TestJsonUncompliantExtendedImpl)
INTERFACE_ENTRY(FunctionalTest::ITestJsonUncompliantExtended)
END_INTERFACE_MAP

private:
uint32_t _value{0};
};

static Factory::Registrar<FunctionalTest::ITestJsonUncompliantExtended, TestJsonUncompliantExtendedImpl> g_registrar;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,30 @@ namespace FunctionalTest {
// @json 1.0.0
// @uncompliant:collapsed
//
// NOTE: @uncompliant:collapsed is deprecated (Thunder docs/interfaces/tags.md) and must not
// NOTE: @uncompliant:collapsed is deprecated (Thunder docs/plugin/interfaces/tags.md) and must not
// be used in new interfaces. This interface exists solely to pin generator behaviour for
// existing consumers that already use this mode. Do not copy PingCollapsed as a usage example.
// existing consumers that already use this mode. Do not copy this interface as a usage example.
struct EXTERNAL ITestJsonUncompliantCollapsed : virtual public Core::IUnknown {
enum { ID = ID_TEST_JSON_UNCOMPLIANT_COL };

// @brief Interface-level tag target for collapsed uncompliant mode.
// @brief Echo method — single-param method.
// In @uncompliant:collapsed mode, a single method parameter is sent as a bare
// scalar (e.g. "abc", not {"payload":"abc"}).
// @param payload Input payload.
// @param reply Receives output payload.
// @retval ERROR_NONE Operation completed.
virtual Core::hresult PingCollapsed(const string& payload /* @in */, string& reply /* @out */) const = 0;

// @property
// @brief Stored counter value — read/write.
// Captures the essential property behaviour of uncompliant:collapsed mode:
// the SET request sends the value as a bare scalar (e.g. 42),
// not wrapped in an object as compliant mode would.
// The GET response is placed in the "params" field of the JSON-RPC envelope
// (not "result" as in compliant and uncompliant:extended modes).
// @param counter Counter value to store or retrieve.
virtual Core::hresult Value(uint32_t& counter /* @out */) const = 0;
virtual Core::hresult Value(const uint32_t counter /* @in */) = 0;
};

} // namespace FunctionalTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,30 @@ namespace FunctionalTest {
// @json 1.0.0
// @uncompliant:extended
//
// NOTE: @uncompliant:extended is deprecated (Thunder docs/interfaces/tags.md) and must not
// NOTE: @uncompliant:extended is deprecated (Thunder docs/plugin/interfaces/tags.md) and must not
// be used in new interfaces. This interface exists solely to pin generator behaviour for
// existing consumers that already use this mode. Do not copy PingExtended as a usage example.
// existing consumers that already use this mode. Do not copy this interface as a usage example.
struct EXTERNAL ITestJsonUncompliantExtended : virtual public Core::IUnknown {
enum { ID = ID_TEST_JSON_UNCOMPLIANT_EXT };

// @brief Interface-level tag target for extended uncompliant mode.
// @brief Echo method — single-param method.
// In @uncompliant:extended mode, method parameters are still wrapped in an object
// (same as @compliant), e.g. {"payload":"abc"}.
// This is the key difference from @uncompliant:collapsed where methods also collapse.
// @param payload Input payload.
// @param reply Receives output payload.
// @retval ERROR_NONE Operation completed.
virtual Core::hresult PingExtended(const string& payload /* @in */, string& reply /* @out */) const = 0;

// @property
// @brief Stored counter value — read/write.
// Captures the essential property behaviour of uncompliant:extended mode:
// the SET request sends the value as a bare scalar (e.g. 42),
// not wrapped in an object as compliant mode would.
// The GET response still uses the "result" field (same as compliant mode).
// @param counter Counter value to store or retrieve.
virtual Core::hresult Value(uint32_t& counter /* @out */) const = 0;
virtual Core::hresult Value(const uint32_t counter /* @in */) = 0;
};

} // namespace FunctionalTest
Expand Down
39 changes: 36 additions & 3 deletions tests/FunctionalTests/common/interfaces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ Validates the `@async` pattern where a completion callback replaces the return p
### ITestInterfaces
Validates three stub-generator control annotations: `@interface:id` on `void*` return values and output parameters (dynamic interface pointer passing with generated QueryInterface/AddRef/Release), `@stub` (method present server-side only, no proxy emitted), and `@omit` (method excluded from both stub and proxy, vtable slot preserved).

### ITestEncodingMac
Validates `@encode:mac` on a `Core::MACAddress` parameter: the JSON-RPC layer encodes the six-byte address as a colon-separated hex string (e.g. `"AA:BB:CC:DD:EE:FF"`). Exercises both input (set) and output (get) directions.

### ITestLengthModes
Covers the two buffer-length variants not in `ITestBuffers`: `@length:void` (no length parameter; caller provides null-terminated or fixed-size data) and `@length:return` (the method return value carries the buffer length rather than a separate parameter). COM-RPC only — `@length:return` is incompatible with `uint32_t` return types used for JSON-RPC error codes.

### ITestJsonShape
Exercises JSON payload shaping annotations: `@wrapped` (a single scalar output is enclosed in a named object rather than returned bare) and `@extract` (a single-element list may be flattened to a scalar in the JSON response).

### ITestJsonTextKeep
Validates `@text:keep` at interface level: every method name, parameter name, and POD member name in the generated JSON-RPC interface matches the C++ identifier exactly, with no case transformation applied.

### ITestJsonTextCase
Validates `@text:legacy` at interface level: all method names, parameter names, and POD member names are lowercased in the generated JSON-RPC interface, matching the pre-5.2 Thunder convention.

### ITestJsonCompliant
Baseline for the default `@compliant` JSON-RPC format. Single-parameter method inputs are enclosed in a named object (`{"payload":"abc"}`), and the response value is placed in the `"result"` field of the JSON-RPC envelope. Provides a comparison point for the two uncompliant modes below.

### ITestJsonUncompliantExtended ⚠️ deprecated
Pins the generator behaviour for `@uncompliant:extended` (deprecated — do not use in new interfaces; see Thunder `docs/plugin/interfaces/tags.md`). The essential property contract: the SET request sends the value as a **bare scalar** (e.g. `42`, not `{"value":42}`) while the GET response still uses the `"result"` field. Methods are **not** affected — they remain wrapped just like `@compliant`. Without a `@property`, the mode is indistinguishable from `@compliant` in tests.

### ITestJsonUncompliantCollapsed ⚠️ deprecated
Pins the generator behaviour for `@uncompliant:collapsed` (deprecated — do not use in new interfaces; see Thunder `docs/plugin/interfaces/tags.md`). Extends the bare-scalar contract to **all** single-parameter methods, properties, and notifications. Property SET sends a bare scalar, and the GET response is placed in the `"params"` field of the JSON-RPC envelope (not `"result"` as in compliant and extended modes). Sending a wrapped object to a collapsed method must be rejected.

---

## Annotation Coverage Matrix
Expand All @@ -44,12 +68,14 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r
|---|---|
| `@in` / `@out` / `@inout` | All |
| `@length:param` | ITestBuffers, ITestOptionals, ITestPrimitives |
| `@length:void` | ITestLengthModes |
| `@length:return` | ITestLengthModes |
| `@maxlength:param` | ITestBuffers, ITestOptionals |
| `@encode:base64` | ITestBuffers |
| `@encode:hex` | ITestBuffers |
| `@encode:text` | ITestEnums |
| `@encode:bitmask` | ITestEnums |
| `@encode:mac` | ITestAsync (via ICallback::Complete) |
| `@encode:mac` | ITestEncodingMac |
| `@opaque` | ITestStructs |
| `@restrict` (integer range) | ITestRestrictions, ITestBuffers, ITestAsync |
| `@restrict` (float range) | ITestRestrictions |
Expand All @@ -59,7 +85,7 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r
| `@restrict` on `std::vector` | ITestStructs, ITestEvents |
| `@default` | ITestAsync, ITestOptionals |
| `@optional` / `Core::OptionalType<T>` | ITestAsync, ITestOptionals, ITestEnums, ITestEvents, ITestRestrictions |
| `@property` (read-write) | ITestEnums, ITestStructs, ITestAsync |
| `@property` (read-write) | ITestEnums, ITestStructs, ITestAsync, ITestJsonUncompliantExtended, ITestJsonUncompliantCollapsed |
| `@property` (read-only) | ITestEnums, ITestAsync |
| `@property` (write-only) | ITestEnums |
| `@index` | ITestStructs, ITestEvents, ITestAsync |
Expand All @@ -71,7 +97,14 @@ Validates three stub-generator control annotations: `@interface:id` on `void*` r
| `@stub` | ITestInterfaces |
| `@omit` | ITestInterfaces |
| `@json:omit` | ITestPrimitives |
| `@json 1.0.0` | ITestPrimitives, ITestEnums, ITestOptionals, ITestRestrictions, ITestStructs, ITestEvents, ITestAsync |
| `@wrapped` | ITestJsonShape |
| `@extract` | ITestJsonShape |
| `@text:keep` | ITestJsonTextKeep |
| `@text:legacy` | ITestJsonTextCase |
| `@compliant` | ITestJsonCompliant |
| `@uncompliant:extended` ⚠️ deprecated | ITestJsonUncompliantExtended |
| `@uncompliant:collapsed` ⚠️ deprecated | ITestJsonUncompliantCollapsed |
| `@json 1.0.0` | ITestPrimitives, ITestEnums, ITestOptionals, ITestRestrictions, ITestStructs, ITestEvents, ITestAsync, ITestEncodingMac, ITestJsonShape, ITestJsonTextKeep, ITestJsonTextCase, ITestJsonCompliant, ITestJsonUncompliantExtended, ITestJsonUncompliantCollapsed |
| `std::vector<Primitive>` | ITestStructs |
| `std::vector<Struct>` | ITestStructs, ITestEvents |
| `RPC::IStringIterator` | ITestIterators |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,25 @@ TEST_F(TestJsonUncompliantCollapsed, PingCollapsed) {
ASSERT_EQ(_proxy->PingCollapsed("payload", reply), Core::ERROR_NONE);
EXPECT_EQ(reply, "payload");
}

TEST_F(TestJsonUncompliantCollapsed, Value_SetGet_RoundTrip) {
ASSERT_EQ(_proxy->Value(77u), Core::ERROR_NONE);
uint32_t got = 0;
ASSERT_EQ(static_cast<const ITestJsonUncompliantCollapsed*>(_proxy)->Value(got), Core::ERROR_NONE);
EXPECT_EQ(got, 77u);
}

TEST_F(TestJsonUncompliantCollapsed, Value_DefaultIsZero) {
uint32_t got = ~0u;
EXPECT_EQ(static_cast<const ITestJsonUncompliantCollapsed*>(_proxy)->Value(got), Core::ERROR_NONE);
}

TEST_F(TestJsonUncompliantCollapsed, Value_MultipleWrites) {
const uint32_t values[] = {0u, 1u, 0xFFFFFFFFu, 42u};
for (uint32_t v : values) {
ASSERT_EQ(_proxy->Value(v), Core::ERROR_NONE);
uint32_t got = ~v;
ASSERT_EQ(static_cast<const ITestJsonUncompliantCollapsed*>(_proxy)->Value(got), Core::ERROR_NONE);
EXPECT_EQ(got, v);
}
}
24 changes: 24 additions & 0 deletions tests/FunctionalTests/comrpc/tests/TestJsonUncompliantExtended.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,27 @@ TEST_F(TestJsonUncompliantExtended, PingExtended) {
ASSERT_EQ(_proxy->PingExtended("payload", reply), Core::ERROR_NONE);
EXPECT_EQ(reply, "payload");
}

TEST_F(TestJsonUncompliantExtended, Value_SetGet_RoundTrip) {
ASSERT_EQ(_proxy->Value(99u), Core::ERROR_NONE);
uint32_t got = 0;
ASSERT_EQ(static_cast<const ITestJsonUncompliantExtended*>(_proxy)->Value(got), Core::ERROR_NONE);
EXPECT_EQ(got, 99u);
}

TEST_F(TestJsonUncompliantExtended, Value_DefaultIsZero) {
// A fresh singleton implementation starts at 0 unless a previous test wrote to it.
// This test just verifies the getter returns a valid uint32 without error.
uint32_t got = ~0u;
EXPECT_EQ(static_cast<const ITestJsonUncompliantExtended*>(_proxy)->Value(got), Core::ERROR_NONE);
}

TEST_F(TestJsonUncompliantExtended, Value_MultipleWrites) {
const uint32_t values[] = {0u, 1u, 0xFFFFFFFFu, 42u};
for (uint32_t v : values) {
ASSERT_EQ(_proxy->Value(v), Core::ERROR_NONE);
uint32_t got = ~v;
ASSERT_EQ(static_cast<const ITestJsonUncompliantExtended*>(_proxy)->Value(got), Core::ERROR_NONE);
EXPECT_EQ(got, v);
}
}
Loading
Loading