Skip to content

Commit a0b1c5e

Browse files
committed
test(cpp): cover map runtime roundtrips
Restores the C++ map runtime coverage deferred in #1590 and adds the import direction, which never existed. The runner exercises the span-based borrowed-map lowering plus owned maps inside records, variants, results, and tuples. The C++ harness now links a core module with --skip-wit-component and componentizes with the workspace's wit-component, mirroring the C harness. This keeps the wasi-sdk's bundled wasm-component-ld out of the WIT processing path, which predates the component-model map encoding and was the reason #1590 dropped its runtime test. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 61bad33 commit a0b1c5e

3 files changed

Lines changed: 318 additions & 3 deletions

File tree

crates/test/src/cpp.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,13 @@ impl LanguageMethods for Cpp {
146146
.arg(&bindings_object);
147147
runner.run_command(&mut cmd)?;
148148

149-
// Now compile the runner's source code to with the above object and the
150-
// component-type object into a final component.
149+
// Now link the runner's source code with the above object and the
150+
// component-type object into a core module, then componentize with
151+
// the workspace's `wit-component` (mirroring the C harness). This
152+
// skips the WIT processing of the wasi-sdk's bundled
153+
// `wasm-component-ld`, which may predate component-model features
154+
// used by the embedded metadata (e.g. `map`).
155+
let output = compile.output.with_extension("core.wasm");
151156
let mut cmd = Command::new(compiler);
152157
cmd.arg(&compile.component.path)
153158
.arg(&bindings_object)
@@ -170,12 +175,17 @@ impl LanguageMethods for Cpp {
170175
.arg("-std=c++20")
171176
.arg("-g")
172177
.arg("-o")
173-
.arg(&compile.output);
178+
.arg(&output);
174179
for flag in Vec::from(config.cflags) {
175180
cmd.arg(flag);
176181
}
177182
cmd.arg("-mexec-model=reactor");
183+
cmd.arg("-Wl,--skip-wit-component");
178184
runner.run_command(&mut cmd)?;
185+
186+
runner
187+
.convert_p1_to_component(&output, compile)
188+
.with_context(|| format!("failed to convert {output:?}"))?;
179189
Ok(())
180190
}
181191

tests/runtime/map/runner.cpp

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
//@ wasmtime-flags = '-Wcomponent-model-map'
2+
3+
#include <assert.h>
4+
#include <runner_cpp.h>
5+
6+
#include <cstring>
7+
#include <string>
8+
#include <vector>
9+
10+
namespace to_test = ::test::maps::to_test;
11+
12+
using wit::string;
13+
using wit::unordered_map;
14+
15+
static bool str_eq(string const& a, std::string_view b) { return a.get_view() == b; }
16+
17+
template <class V>
18+
static V const* find(unordered_map<string, V> const& m, std::string_view key) {
19+
for (auto const& [k, v] : m) {
20+
if (k.get_view() == key) {
21+
return &v;
22+
}
23+
}
24+
return nullptr;
25+
}
26+
27+
template <class V>
28+
static V const* find(unordered_map<uint32_t, V> const& m, uint32_t key) {
29+
for (auto const& [k, v] : m) {
30+
if (k == key) {
31+
return &v;
32+
}
33+
}
34+
return nullptr;
35+
}
36+
37+
static unordered_map<uint32_t, string> make_names(
38+
std::initializer_list<std::pair<uint32_t, std::string_view>> entries) {
39+
auto result = unordered_map<uint32_t, string>::allocate(entries.size());
40+
size_t i = 0;
41+
for (auto const& [key, value] : entries) {
42+
result.initialize(i++, std::make_pair(key, string::from_view(value)));
43+
}
44+
return result;
45+
}
46+
47+
static void test_named_roundtrip() {
48+
std::vector<std::pair<uint32_t, std::string_view>> input{{1, "uno"}, {2, "two"}};
49+
auto result = to_test::NamedRoundtrip(input);
50+
assert(result.size() == 2);
51+
auto uno = find(result, "uno");
52+
assert(uno && *uno == 1);
53+
auto two = find(result, "two");
54+
assert(two && *two == 2);
55+
}
56+
57+
static void test_bytes_roundtrip() {
58+
uint8_t const world_bytes[] = {'w', 'o', 'r', 'l', 'd'};
59+
uint8_t const bin_bytes[] = {0, 1, 2};
60+
std::vector<std::pair<std::string_view, std::span<uint8_t const>>> input{
61+
{"hello", world_bytes},
62+
{"bin", bin_bytes},
63+
};
64+
auto result = to_test::BytesRoundtrip(input);
65+
assert(result.size() == 2);
66+
auto hello = find(result, "hello");
67+
assert(hello && hello->size() == 5);
68+
assert(memcmp(hello->data(), "world", 5) == 0);
69+
auto bin = find(result, "bin");
70+
assert(bin && bin->size() == 3);
71+
assert((*bin)[0] == 0 && (*bin)[1] == 1 && (*bin)[2] == 2);
72+
}
73+
74+
static void test_empty_roundtrip() {
75+
auto result = to_test::EmptyRoundtrip({});
76+
assert(result.empty());
77+
}
78+
79+
static void test_option_roundtrip() {
80+
std::vector<std::pair<std::string_view, std::optional<uint32_t>>> input{
81+
{"some", 42},
82+
{"none", std::nullopt},
83+
};
84+
auto result = to_test::OptionRoundtrip(input);
85+
assert(result.size() == 2);
86+
auto some = find(result, "some");
87+
assert(some && some->has_value() && **some == 42);
88+
auto none = find(result, "none");
89+
assert(none && !none->has_value());
90+
}
91+
92+
static void test_record_roundtrip() {
93+
to_test::LabeledEntry input{
94+
string::from_view("test-label"),
95+
make_names({{10, "ten"}, {20, "twenty"}}),
96+
};
97+
auto result = to_test::RecordRoundtrip(std::move(input));
98+
assert(str_eq(result.label, "test-label"));
99+
assert(result.values.size() == 2);
100+
auto ten = find(result.values, 10);
101+
assert(ten && str_eq(*ten, "ten"));
102+
auto twenty = find(result.values, 20);
103+
assert(twenty && str_eq(*twenty, "twenty"));
104+
}
105+
106+
static void test_inline_roundtrip() {
107+
std::vector<std::pair<uint32_t, std::string_view>> input{{1, "one"}, {2, "two"}};
108+
auto result = to_test::InlineRoundtrip(input);
109+
assert(result.size() == 2);
110+
auto one = find(result, "one");
111+
assert(one && *one == 1);
112+
auto two = find(result, "two");
113+
assert(two && *two == 2);
114+
}
115+
116+
static void test_large_roundtrip() {
117+
size_t const n = 100;
118+
std::vector<std::string> storage;
119+
storage.reserve(n);
120+
std::vector<std::pair<uint32_t, std::string_view>> input;
121+
input.reserve(n);
122+
for (size_t i = 0; i < n; i++) {
123+
storage.push_back("value-" + std::to_string(i));
124+
input.emplace_back(uint32_t(i), storage.back());
125+
}
126+
auto result = to_test::LargeRoundtrip(input);
127+
assert(result.size() == n);
128+
auto value = find(result, 42);
129+
assert(value && str_eq(*value, "value-42"));
130+
}
131+
132+
static void test_multi_param_roundtrip() {
133+
std::vector<std::pair<uint32_t, std::string_view>> names{{1, "one"}, {2, "two"}};
134+
uint8_t const payload[] = {42};
135+
std::vector<std::pair<std::string_view, std::span<uint8_t const>>> bytes{{"key", payload}};
136+
auto [ids, bytes_out] = to_test::MultiParamRoundtrip(names, bytes);
137+
assert(ids.size() == 2);
138+
auto one = find(ids, "one");
139+
assert(one && *one == 1);
140+
auto two = find(ids, "two");
141+
assert(two && *two == 2);
142+
assert(bytes_out.size() == 1);
143+
auto key = find(bytes_out, "key");
144+
assert(key && key->size() == 1 && (*key)[0] == 42);
145+
}
146+
147+
static void test_nested_roundtrip() {
148+
std::vector<std::pair<uint32_t, std::string_view>> inner_a{{1, "one"}, {2, "two"}};
149+
std::vector<std::pair<uint32_t, std::string_view>> inner_b{{10, "ten"}};
150+
std::vector<std::pair<std::string_view, std::span<std::pair<uint32_t, std::string_view> const>>>
151+
outer{
152+
{"group-a", inner_a},
153+
{"group-b", inner_b},
154+
};
155+
auto result = to_test::NestedRoundtrip(outer);
156+
assert(result.size() == 2);
157+
auto group_a = find(result, "group-a");
158+
assert(group_a && group_a->size() == 2);
159+
auto two = find(*group_a, 2);
160+
assert(two && str_eq(*two, "two"));
161+
auto group_b = find(result, "group-b");
162+
assert(group_b && group_b->size() == 1);
163+
auto ten = find(*group_b, 10);
164+
assert(ten && str_eq(*ten, "ten"));
165+
}
166+
167+
static void test_variant_roundtrip() {
168+
to_test::MapOrString map_input{to_test::MapOrString::AsMap{make_names({{1, "one"}})}};
169+
auto map_result = to_test::VariantRoundtrip(std::move(map_input));
170+
auto* as_map = std::get_if<to_test::MapOrString::AsMap>(&map_result.variants);
171+
assert(as_map && as_map->value.size() == 1);
172+
auto one = find(as_map->value, 1);
173+
assert(one && str_eq(*one, "one"));
174+
175+
to_test::MapOrString string_input{to_test::MapOrString::AsString{string::from_view("hello")}};
176+
auto string_result = to_test::VariantRoundtrip(std::move(string_input));
177+
auto* as_string = std::get_if<to_test::MapOrString::AsString>(&string_result.variants);
178+
assert(as_string && str_eq(as_string->value, "hello"));
179+
}
180+
181+
static void test_result_roundtrip() {
182+
std::expected<unordered_map<uint32_t, string>, string> ok_input{make_names({{5, "five"}})};
183+
auto ok_result = to_test::ResultRoundtrip(std::move(ok_input));
184+
assert(ok_result.has_value());
185+
assert(ok_result->size() == 1);
186+
auto five = find(*ok_result, 5);
187+
assert(five && str_eq(*five, "five"));
188+
189+
std::expected<unordered_map<uint32_t, string>, string> err_input{
190+
std::unexpected(string::from_view("bad input"))};
191+
auto err_result = to_test::ResultRoundtrip(std::move(err_input));
192+
assert(!err_result.has_value());
193+
assert(str_eq(err_result.error(), "bad input"));
194+
}
195+
196+
static void test_tuple_roundtrip() {
197+
std::vector<std::pair<uint32_t, std::string_view>> entries{{7, "seven"}};
198+
auto result = to_test::TupleRoundtrip(std::make_tuple(
199+
std::span<std::pair<uint32_t, std::string_view> const>(entries), uint64_t(42)));
200+
auto& [values, number] = result;
201+
assert(values.size() == 1);
202+
auto seven = find(values, 7);
203+
assert(seven && str_eq(*seven, "seven"));
204+
assert(number == 42);
205+
}
206+
207+
static void test_single_entry_roundtrip() {
208+
std::vector<std::pair<uint32_t, std::string_view>> input{{99, "ninety-nine"}};
209+
auto result = to_test::SingleEntryRoundtrip(input);
210+
assert(result.size() == 1);
211+
auto value = find(result, 99);
212+
assert(value && str_eq(*value, "ninety-nine"));
213+
}
214+
215+
void exports::runner::Run() {
216+
test_named_roundtrip();
217+
test_bytes_roundtrip();
218+
test_empty_roundtrip();
219+
test_option_roundtrip();
220+
test_record_roundtrip();
221+
test_inline_roundtrip();
222+
test_large_roundtrip();
223+
test_multi_param_roundtrip();
224+
test_nested_roundtrip();
225+
test_variant_roundtrip();
226+
test_result_roundtrip();
227+
test_tuple_roundtrip();
228+
test_single_entry_roundtrip();
229+
}

tests/runtime/map/test.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#include <test_cpp.h>
2+
3+
namespace test_exports = ::exports::test::maps::to_test;
4+
5+
using wit::string;
6+
using wit::unordered_map;
7+
using wit::vector;
8+
9+
static unordered_map<string, uint32_t> invert(unordered_map<uint32_t, string> a) {
10+
auto result = unordered_map<string, uint32_t>::allocate(a.size());
11+
size_t i = 0;
12+
for (auto& [id, name] : a) {
13+
result.initialize(i++, std::make_pair(std::move(name), id));
14+
}
15+
return result;
16+
}
17+
18+
unordered_map<string, uint32_t> test_exports::NamedRoundtrip(unordered_map<uint32_t, string> a) {
19+
return invert(std::move(a));
20+
}
21+
22+
unordered_map<string, vector<uint8_t>> test_exports::BytesRoundtrip(
23+
unordered_map<string, vector<uint8_t>> a) {
24+
return a;
25+
}
26+
27+
unordered_map<uint32_t, string> test_exports::EmptyRoundtrip(unordered_map<uint32_t, string> a) {
28+
return a;
29+
}
30+
31+
unordered_map<string, std::optional<uint32_t>> test_exports::OptionRoundtrip(
32+
unordered_map<string, std::optional<uint32_t>> a) {
33+
return a;
34+
}
35+
36+
test_exports::LabeledEntry test_exports::RecordRoundtrip(test_exports::LabeledEntry a) {
37+
return a;
38+
}
39+
40+
unordered_map<string, uint32_t> test_exports::InlineRoundtrip(unordered_map<uint32_t, string> a) {
41+
return invert(std::move(a));
42+
}
43+
44+
unordered_map<uint32_t, string> test_exports::LargeRoundtrip(unordered_map<uint32_t, string> a) {
45+
return a;
46+
}
47+
48+
std::tuple<unordered_map<string, uint32_t>, unordered_map<string, vector<uint8_t>>>
49+
test_exports::MultiParamRoundtrip(unordered_map<uint32_t, string> a,
50+
unordered_map<string, vector<uint8_t>> b) {
51+
return std::make_tuple(invert(std::move(a)), std::move(b));
52+
}
53+
54+
unordered_map<string, unordered_map<uint32_t, string>> test_exports::NestedRoundtrip(
55+
unordered_map<string, unordered_map<uint32_t, string>> a) {
56+
return a;
57+
}
58+
59+
test_exports::MapOrString test_exports::VariantRoundtrip(test_exports::MapOrString a) {
60+
return a;
61+
}
62+
63+
std::expected<unordered_map<uint32_t, string>, string> test_exports::ResultRoundtrip(
64+
std::expected<unordered_map<uint32_t, string>, string> a) {
65+
return a;
66+
}
67+
68+
std::tuple<unordered_map<uint32_t, string>, uint64_t> test_exports::TupleRoundtrip(
69+
std::tuple<unordered_map<uint32_t, string>, uint64_t> a) {
70+
return a;
71+
}
72+
73+
unordered_map<uint32_t, string> test_exports::SingleEntryRoundtrip(
74+
unordered_map<uint32_t, string> a) {
75+
return a;
76+
}

0 commit comments

Comments
 (0)