diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bc34e44..98b0c8b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -150,6 +150,9 @@ jobs: - name: Clippy (wasm32-wasi) run: cargo clippy --release --all-targets --target=wasm32-wasi + - name: Test + run: cargo test + - name: Format (rustfmt) run: cargo fmt -- --check @@ -212,6 +215,9 @@ jobs: - name: Clippy (wasm32-wasip1) run: cargo clippy --release --all-targets --target=wasm32-wasip1 + - name: Test + run: cargo test + - name: Format (rustfmt) run: cargo fmt -- --check @@ -275,6 +281,12 @@ jobs: - name: Clippy (wasm32-wasip1) run: cargo clippy --release --all-targets --target=wasm32-wasip1 + - name: Test + run: cargo test + + - name: Bench + run: cargo bench + - name: Format (rustfmt) run: cargo fmt -- --check diff --git a/Cargo.toml b/Cargo.toml index 7b0469b..1f31459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,11 @@ opt-level = 3 codegen-units = 1 panic = "abort" strip = "debuginfo" + +[profile.test] +inherits = "release" +debug = true + +[profile.bench] +inherits = "release" +debug = true diff --git a/build.rs b/build.rs index fad8963..8a6a252 100644 --- a/build.rs +++ b/build.rs @@ -15,8 +15,15 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=RUSTFLAGS"); + println!("cargo:rustc-check-cfg=cfg(nightly)"); println!("cargo:rustc-check-cfg=cfg(wasi_exec_model_reactor)"); + if let Some(toolchain) = std::env::var_os("RUSTUP_TOOLCHAIN") { + if toolchain.to_string_lossy().contains("nightly") { + println!("cargo:rustc-cfg=nightly"); + } + } + if let Some(target_os) = std::env::var_os("CARGO_CFG_TARGET_OS") { if target_os != "wasi" { return; diff --git a/src/hostcalls.rs b/src/hostcalls.rs index 8dbc335..fd3dbeb 100644 --- a/src/hostcalls.rs +++ b/src/hostcalls.rs @@ -1174,10 +1174,10 @@ mod utils { size += name.len() + value.len() + 10; } let mut bytes: Bytes = Vec::with_capacity(size); - bytes.extend_from_slice(&map.len().to_le_bytes()); + bytes.extend_from_slice(&(map.len() as u32).to_le_bytes()); for (name, value) in &map { - bytes.extend_from_slice(&name.len().to_le_bytes()); - bytes.extend_from_slice(&value.len().to_le_bytes()); + bytes.extend_from_slice(&(name.len() as u32).to_le_bytes()); + bytes.extend_from_slice(&(value.len() as u32).to_le_bytes()); } for (name, value) in &map { bytes.extend_from_slice(name.as_bytes()); @@ -1194,10 +1194,10 @@ mod utils { size += name.len() + value.len() + 10; } let mut bytes: Bytes = Vec::with_capacity(size); - bytes.extend_from_slice(&map.len().to_le_bytes()); + bytes.extend_from_slice(&(map.len() as u32).to_le_bytes()); for (name, value) in &map { - bytes.extend_from_slice(&name.len().to_le_bytes()); - bytes.extend_from_slice(&value.len().to_le_bytes()); + bytes.extend_from_slice(&(name.len() as u32).to_le_bytes()); + bytes.extend_from_slice(&(value.len() as u32).to_le_bytes()); } for (name, value) in &map { bytes.extend_from_slice(name.as_bytes()); @@ -1252,4 +1252,203 @@ mod utils { } map } + + #[cfg(test)] + mod tests { + use super::*; + + #[cfg(nightly)] + use test::Bencher; + + static MAP: &[(&str, &str)] = &[ + (":method", "GET"), + (":path", "/bytes/1"), + (":authority", "httpbin.org"), + ("Powered-By", "proxy-wasm"), + ]; + + #[rustfmt::skip] + static SERIALIZED_MAP: &[u8] = &[ + // num entries + 4, 0, 0, 0, + // len (":method", "GET") + 7, 0, 0, 0, 3, 0, 0, 0, + // len (":path", "/bytes/1") + 5, 0, 0, 0, 8, 0, 0, 0, + // len (":authority", "httpbin.org") + 10, 0, 0, 0, 11, 0, 0, 0, + // len ("Powered-By", "proxy-wasm") + 10, 0, 0, 0, 10, 0, 0, 0, + // ":method" + 58, 109, 101, 116, 104, 111, 100, 0, + // "GET" + 71, 69, 84, 0, + // ":path" + 58, 112, 97, 116, 104, 0, + // "/bytes/1" + 47, 98, 121, 116, 101, 115, 47, 49, 0, + // ":authority" + 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 0, + // "httpbin.org" + 104, 116, 116, 112, 98, 105, 110, 46, 111, 114, 103, 0, + // "Powered-By" + 80, 111, 119, 101, 114, 101, 100, 45, 66, 121, 0, + // "proxy-wasm" + 112, 114, 111, 120, 121, 45, 119, 97, 115, 109, 0, + ]; + + #[test] + fn test_serialize_map_empty() { + let serialized_map = serialize_map(vec![]); + assert_eq!(serialized_map, [0, 0, 0, 0]); + } + + #[test] + fn test_serialize_map_empty_bytes() { + let serialized_map = serialize_map_bytes(vec![]); + assert_eq!(serialized_map, [0, 0, 0, 0]); + } + + #[test] + fn test_deserialize_map_empty() { + let map = deserialize_map(&[]); + assert_eq!(map, []); + let map = deserialize_map(&[0, 0, 0, 0]); + assert_eq!(map, []); + } + + #[test] + fn test_deserialize_map_empty_bytes() { + let map = deserialize_map_bytes(&[]); + assert_eq!(map, []); + let map = deserialize_map_bytes(&[0, 0, 0, 0]); + assert_eq!(map, []); + } + + #[test] + fn test_serialize_map() { + let serialized_map = serialize_map(MAP.to_vec()); + assert_eq!(serialized_map, SERIALIZED_MAP); + } + + #[test] + fn test_serialize_map_bytes() { + let map: Vec<(&str, &[u8])> = MAP.iter().map(|x| (x.0, x.1.as_bytes())).collect(); + let serialized_map = serialize_map_bytes(map); + assert_eq!(serialized_map, SERIALIZED_MAP); + } + + #[test] + fn test_deserialize_map() { + let map = deserialize_map(SERIALIZED_MAP); + assert_eq!(map.len(), MAP.len()); + for (got, expected) in map.into_iter().zip(MAP) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, expected.1); + } + } + + #[test] + fn test_deserialize_map_bytes() { + let map = deserialize_map_bytes(SERIALIZED_MAP); + assert_eq!(map.len(), MAP.len()); + for (got, expected) in map.into_iter().zip(MAP) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, expected.1.as_bytes()); + } + } + + #[test] + fn test_deserialize_map_roundtrip() { + let map = deserialize_map(SERIALIZED_MAP); + // TODO(v0.3): fix arguments, so that maps can be reused without conversion. + let map_refs: Vec<(&str, &str)> = + map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect(); + let serialized_map = serialize_map(map_refs); + assert_eq!(serialized_map, SERIALIZED_MAP); + } + + #[test] + fn test_deserialize_map_roundtrip_bytes() { + let map = deserialize_map_bytes(SERIALIZED_MAP); + // TODO(v0.3): fix arguments, so that maps can be reused without conversion. + let map_refs: Vec<(&str, &[u8])> = + map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect(); + let serialized_map = serialize_map_bytes(map_refs); + assert_eq!(serialized_map, SERIALIZED_MAP); + } + + #[test] + fn test_deserialize_map_all_chars() { + // 0x00-0x7f are valid single-byte UTF-8 characters. + for i in 0..0x7f { + let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0]; + let map = deserialize_map(&serialized_src); + // TODO(v0.3): fix arguments, so that maps can be reused without conversion. + let map_refs: Vec<(&str, &str)> = + map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect(); + let serialized_map = serialize_map(map_refs); + assert_eq!(serialized_map, serialized_src); + } + // 0x80-0xff are invalid single-byte UTF-8 characters. + for i in 0x80..0xff { + let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0]; + std::panic::set_hook(Box::new(|_| {})); + let result = std::panic::catch_unwind(|| { + deserialize_map(&serialized_src); + }); + assert!(result.is_err()); + } + } + + #[test] + fn test_deserialize_map_all_chars_bytes() { + // All 256 single-byte characters are allowed when emitting bytes. + for i in 0..0xff { + let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0]; + let map = deserialize_map_bytes(&serialized_src); + // TODO(v0.3): fix arguments, so that maps can be reused without conversion. + let map_refs: Vec<(&str, &[u8])> = + map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect(); + let serialized_map = serialize_map_bytes(map_refs); + assert_eq!(serialized_map, serialized_src); + } + } + + #[cfg(nightly)] + #[bench] + fn bench_serialize_map(b: &mut Bencher) { + let map = MAP.to_vec(); + b.iter(|| { + serialize_map(test::black_box(map.clone())); + }); + } + + #[cfg(nightly)] + #[bench] + fn bench_serialize_map_bytes(b: &mut Bencher) { + let map: Vec<(&str, &[u8])> = MAP.iter().map(|x| (x.0, x.1.as_bytes())).collect(); + b.iter(|| { + serialize_map_bytes(test::black_box(map.clone())); + }); + } + + #[cfg(nightly)] + #[bench] + fn bench_deserialize_map(b: &mut Bencher) { + let serialized_map = SERIALIZED_MAP.to_vec(); + b.iter(|| { + deserialize_map(test::black_box(&serialized_map)); + }); + } + + #[cfg(nightly)] + #[bench] + fn bench_deserialize_map_bytes(b: &mut Bencher) { + let serialized_map = SERIALIZED_MAP.to_vec(); + b.iter(|| { + deserialize_map_bytes(test::black_box(&serialized_map)); + }); + } + } } diff --git a/src/lib.rs b/src/lib.rs index a8f4265..d41069c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(all(test, nightly), feature(test))] + +#[cfg(all(test, nightly))] +extern crate test; + pub mod hostcalls; pub mod traits; pub mod types;