Skip to content

Commit 36e4ea2

Browse files
authored
Add tests and benchmarks for map (de)serialization. (#283)
Cast usize to u32 to allow running them on 64-bit systems. Signed-off-by: Piotr Sikora <code@piotrsikora.dev>
1 parent 7711048 commit 36e4ea2

File tree

5 files changed

+237
-6
lines changed

5 files changed

+237
-6
lines changed

.github/workflows/rust.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ jobs:
150150
- name: Clippy (wasm32-wasi)
151151
run: cargo clippy --release --all-targets --target=wasm32-wasi
152152

153+
- name: Test
154+
run: cargo test
155+
153156
- name: Format (rustfmt)
154157
run: cargo fmt -- --check
155158

@@ -212,6 +215,9 @@ jobs:
212215
- name: Clippy (wasm32-wasip1)
213216
run: cargo clippy --release --all-targets --target=wasm32-wasip1
214217

218+
- name: Test
219+
run: cargo test
220+
215221
- name: Format (rustfmt)
216222
run: cargo fmt -- --check
217223

@@ -275,6 +281,12 @@ jobs:
275281
- name: Clippy (wasm32-wasip1)
276282
run: cargo clippy --release --all-targets --target=wasm32-wasip1
277283

284+
- name: Test
285+
run: cargo test
286+
287+
- name: Bench
288+
run: cargo bench
289+
278290
- name: Format (rustfmt)
279291
run: cargo fmt -- --check
280292

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ opt-level = 3
2020
codegen-units = 1
2121
panic = "abort"
2222
strip = "debuginfo"
23+
24+
[profile.test]
25+
inherits = "release"
26+
debug = true
27+
28+
[profile.bench]
29+
inherits = "release"
30+
debug = true

build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@
1515
fn main() {
1616
println!("cargo:rerun-if-changed=build.rs");
1717
println!("cargo:rerun-if-env-changed=RUSTFLAGS");
18+
println!("cargo:rustc-check-cfg=cfg(nightly)");
1819
println!("cargo:rustc-check-cfg=cfg(wasi_exec_model_reactor)");
1920

21+
if let Some(toolchain) = std::env::var_os("RUSTUP_TOOLCHAIN") {
22+
if toolchain.to_string_lossy().contains("nightly") {
23+
println!("cargo:rustc-cfg=nightly");
24+
}
25+
}
26+
2027
if let Some(target_os) = std::env::var_os("CARGO_CFG_TARGET_OS") {
2128
if target_os != "wasi" {
2229
return;

src/hostcalls.rs

Lines changed: 205 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,10 +1174,10 @@ mod utils {
11741174
size += name.len() + value.len() + 10;
11751175
}
11761176
let mut bytes: Bytes = Vec::with_capacity(size);
1177-
bytes.extend_from_slice(&map.len().to_le_bytes());
1177+
bytes.extend_from_slice(&(map.len() as u32).to_le_bytes());
11781178
for (name, value) in &map {
1179-
bytes.extend_from_slice(&name.len().to_le_bytes());
1180-
bytes.extend_from_slice(&value.len().to_le_bytes());
1179+
bytes.extend_from_slice(&(name.len() as u32).to_le_bytes());
1180+
bytes.extend_from_slice(&(value.len() as u32).to_le_bytes());
11811181
}
11821182
for (name, value) in &map {
11831183
bytes.extend_from_slice(name.as_bytes());
@@ -1194,10 +1194,10 @@ mod utils {
11941194
size += name.len() + value.len() + 10;
11951195
}
11961196
let mut bytes: Bytes = Vec::with_capacity(size);
1197-
bytes.extend_from_slice(&map.len().to_le_bytes());
1197+
bytes.extend_from_slice(&(map.len() as u32).to_le_bytes());
11981198
for (name, value) in &map {
1199-
bytes.extend_from_slice(&name.len().to_le_bytes());
1200-
bytes.extend_from_slice(&value.len().to_le_bytes());
1199+
bytes.extend_from_slice(&(name.len() as u32).to_le_bytes());
1200+
bytes.extend_from_slice(&(value.len() as u32).to_le_bytes());
12011201
}
12021202
for (name, value) in &map {
12031203
bytes.extend_from_slice(name.as_bytes());
@@ -1252,4 +1252,203 @@ mod utils {
12521252
}
12531253
map
12541254
}
1255+
1256+
#[cfg(test)]
1257+
mod tests {
1258+
use super::*;
1259+
1260+
#[cfg(nightly)]
1261+
use test::Bencher;
1262+
1263+
static MAP: &[(&str, &str)] = &[
1264+
(":method", "GET"),
1265+
(":path", "/bytes/1"),
1266+
(":authority", "httpbin.org"),
1267+
("Powered-By", "proxy-wasm"),
1268+
];
1269+
1270+
#[rustfmt::skip]
1271+
static SERIALIZED_MAP: &[u8] = &[
1272+
// num entries
1273+
4, 0, 0, 0,
1274+
// len (":method", "GET")
1275+
7, 0, 0, 0, 3, 0, 0, 0,
1276+
// len (":path", "/bytes/1")
1277+
5, 0, 0, 0, 8, 0, 0, 0,
1278+
// len (":authority", "httpbin.org")
1279+
10, 0, 0, 0, 11, 0, 0, 0,
1280+
// len ("Powered-By", "proxy-wasm")
1281+
10, 0, 0, 0, 10, 0, 0, 0,
1282+
// ":method"
1283+
58, 109, 101, 116, 104, 111, 100, 0,
1284+
// "GET"
1285+
71, 69, 84, 0,
1286+
// ":path"
1287+
58, 112, 97, 116, 104, 0,
1288+
// "/bytes/1"
1289+
47, 98, 121, 116, 101, 115, 47, 49, 0,
1290+
// ":authority"
1291+
58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 0,
1292+
// "httpbin.org"
1293+
104, 116, 116, 112, 98, 105, 110, 46, 111, 114, 103, 0,
1294+
// "Powered-By"
1295+
80, 111, 119, 101, 114, 101, 100, 45, 66, 121, 0,
1296+
// "proxy-wasm"
1297+
112, 114, 111, 120, 121, 45, 119, 97, 115, 109, 0,
1298+
];
1299+
1300+
#[test]
1301+
fn test_serialize_map_empty() {
1302+
let serialized_map = serialize_map(vec![]);
1303+
assert_eq!(serialized_map, [0, 0, 0, 0]);
1304+
}
1305+
1306+
#[test]
1307+
fn test_serialize_map_empty_bytes() {
1308+
let serialized_map = serialize_map_bytes(vec![]);
1309+
assert_eq!(serialized_map, [0, 0, 0, 0]);
1310+
}
1311+
1312+
#[test]
1313+
fn test_deserialize_map_empty() {
1314+
let map = deserialize_map(&[]);
1315+
assert_eq!(map, []);
1316+
let map = deserialize_map(&[0, 0, 0, 0]);
1317+
assert_eq!(map, []);
1318+
}
1319+
1320+
#[test]
1321+
fn test_deserialize_map_empty_bytes() {
1322+
let map = deserialize_map_bytes(&[]);
1323+
assert_eq!(map, []);
1324+
let map = deserialize_map_bytes(&[0, 0, 0, 0]);
1325+
assert_eq!(map, []);
1326+
}
1327+
1328+
#[test]
1329+
fn test_serialize_map() {
1330+
let serialized_map = serialize_map(MAP.to_vec());
1331+
assert_eq!(serialized_map, SERIALIZED_MAP);
1332+
}
1333+
1334+
#[test]
1335+
fn test_serialize_map_bytes() {
1336+
let map: Vec<(&str, &[u8])> = MAP.iter().map(|x| (x.0, x.1.as_bytes())).collect();
1337+
let serialized_map = serialize_map_bytes(map);
1338+
assert_eq!(serialized_map, SERIALIZED_MAP);
1339+
}
1340+
1341+
#[test]
1342+
fn test_deserialize_map() {
1343+
let map = deserialize_map(SERIALIZED_MAP);
1344+
assert_eq!(map.len(), MAP.len());
1345+
for (got, expected) in map.into_iter().zip(MAP) {
1346+
assert_eq!(got.0, expected.0);
1347+
assert_eq!(got.1, expected.1);
1348+
}
1349+
}
1350+
1351+
#[test]
1352+
fn test_deserialize_map_bytes() {
1353+
let map = deserialize_map_bytes(SERIALIZED_MAP);
1354+
assert_eq!(map.len(), MAP.len());
1355+
for (got, expected) in map.into_iter().zip(MAP) {
1356+
assert_eq!(got.0, expected.0);
1357+
assert_eq!(got.1, expected.1.as_bytes());
1358+
}
1359+
}
1360+
1361+
#[test]
1362+
fn test_deserialize_map_roundtrip() {
1363+
let map = deserialize_map(SERIALIZED_MAP);
1364+
// TODO(v0.3): fix arguments, so that maps can be reused without conversion.
1365+
let map_refs: Vec<(&str, &str)> =
1366+
map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
1367+
let serialized_map = serialize_map(map_refs);
1368+
assert_eq!(serialized_map, SERIALIZED_MAP);
1369+
}
1370+
1371+
#[test]
1372+
fn test_deserialize_map_roundtrip_bytes() {
1373+
let map = deserialize_map_bytes(SERIALIZED_MAP);
1374+
// TODO(v0.3): fix arguments, so that maps can be reused without conversion.
1375+
let map_refs: Vec<(&str, &[u8])> =
1376+
map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
1377+
let serialized_map = serialize_map_bytes(map_refs);
1378+
assert_eq!(serialized_map, SERIALIZED_MAP);
1379+
}
1380+
1381+
#[test]
1382+
fn test_deserialize_map_all_chars() {
1383+
// 0x00-0x7f are valid single-byte UTF-8 characters.
1384+
for i in 0..0x7f {
1385+
let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
1386+
let map = deserialize_map(&serialized_src);
1387+
// TODO(v0.3): fix arguments, so that maps can be reused without conversion.
1388+
let map_refs: Vec<(&str, &str)> =
1389+
map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
1390+
let serialized_map = serialize_map(map_refs);
1391+
assert_eq!(serialized_map, serialized_src);
1392+
}
1393+
// 0x80-0xff are invalid single-byte UTF-8 characters.
1394+
for i in 0x80..0xff {
1395+
let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
1396+
std::panic::set_hook(Box::new(|_| {}));
1397+
let result = std::panic::catch_unwind(|| {
1398+
deserialize_map(&serialized_src);
1399+
});
1400+
assert!(result.is_err());
1401+
}
1402+
}
1403+
1404+
#[test]
1405+
fn test_deserialize_map_all_chars_bytes() {
1406+
// All 256 single-byte characters are allowed when emitting bytes.
1407+
for i in 0..0xff {
1408+
let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
1409+
let map = deserialize_map_bytes(&serialized_src);
1410+
// TODO(v0.3): fix arguments, so that maps can be reused without conversion.
1411+
let map_refs: Vec<(&str, &[u8])> =
1412+
map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
1413+
let serialized_map = serialize_map_bytes(map_refs);
1414+
assert_eq!(serialized_map, serialized_src);
1415+
}
1416+
}
1417+
1418+
#[cfg(nightly)]
1419+
#[bench]
1420+
fn bench_serialize_map(b: &mut Bencher) {
1421+
let map = MAP.to_vec();
1422+
b.iter(|| {
1423+
serialize_map(test::black_box(map.clone()));
1424+
});
1425+
}
1426+
1427+
#[cfg(nightly)]
1428+
#[bench]
1429+
fn bench_serialize_map_bytes(b: &mut Bencher) {
1430+
let map: Vec<(&str, &[u8])> = MAP.iter().map(|x| (x.0, x.1.as_bytes())).collect();
1431+
b.iter(|| {
1432+
serialize_map_bytes(test::black_box(map.clone()));
1433+
});
1434+
}
1435+
1436+
#[cfg(nightly)]
1437+
#[bench]
1438+
fn bench_deserialize_map(b: &mut Bencher) {
1439+
let serialized_map = SERIALIZED_MAP.to_vec();
1440+
b.iter(|| {
1441+
deserialize_map(test::black_box(&serialized_map));
1442+
});
1443+
}
1444+
1445+
#[cfg(nightly)]
1446+
#[bench]
1447+
fn bench_deserialize_map_bytes(b: &mut Bencher) {
1448+
let serialized_map = SERIALIZED_MAP.to_vec();
1449+
b.iter(|| {
1450+
deserialize_map_bytes(test::black_box(&serialized_map));
1451+
});
1452+
}
1453+
}
12551454
}

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#![cfg_attr(all(test, nightly), feature(test))]
16+
17+
#[cfg(all(test, nightly))]
18+
extern crate test;
19+
1520
pub mod hostcalls;
1621
pub mod traits;
1722
pub mod types;

0 commit comments

Comments
 (0)