Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test Framework Base #1

Merged
merged 22 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "proxy-wasm-abi-test-harness"
agiachris marked this conversation as resolved.
Show resolved Hide resolved
version = "0.1.0"
authors = ["Christopher Agia <chrisagia@google.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmtime = "0.18.0"
agiachris marked this conversation as resolved.
Show resolved Hide resolved
anyhow = "1.0.31"
lazy_static = "1.4.0"
chrono = "0.4"
agiachris marked this conversation as resolved.
Show resolved Hide resolved
more-asserts = "0.2.1"
rand = "0.7.3"

[patch.crates-io]
wasmtime = {path = '/usr/local/google/home/chrisagia/.cargo/registry/src/github.com-1ecc6299db9ec823/wasmtime-0.18.0' }
agiachris marked this conversation as resolved.
Show resolved Hide resolved
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
# WebAssembly for Proxies (test framework)
agiachris marked this conversation as resolved.
Show resolved Hide resolved
The Proxy-Wasm ABI and associated SDKs enable developers to build extensions in
any of the supported languages and deliver the plugins as WebAssembly modules at
run-time. This repository contains a standalone runner which serves as a test
harness and simulator for Proxy-Wasm extensions, enabling quick testing in a
controlled environment.

### Examples
The basic usage of this test-framework is provided in the examples/ folder which
contains mocking of proxy-wasm modules provided in the proxy-wasm-rust-sdk
examples/.

## Supported
- Low-level expectation setting over most host-side functions that are consumed
immediately
- Checking of residual low-level expectations after wasm-function call has
completed
- Various high-level (simulator defaults) expectations that persist across
several host-function calls as opposed to being immediately consumed
- Expectation setting over returns from functions exposed on the proxy-wasm
module

## In Progress
- Complete default implementation for all host-side functions
- Low-level expectation setting for the remaining host-side functions
- High-level expectation setting for the remaining host-side functions
- Integration test examples
47 changes: 47 additions & 0 deletions examples/hello_world.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


use proxy_wasm_abi_test_harness::{tester, types::*};
use anyhow::Result;


fn main() -> Result<()>{

let hello_world = "/usr/local/google/home/chrisagia/ws/proxy-wasm-rust-sdk/target\
/wasm32-unknown-unknown/release/examples/hello_world.wasm";
agiachris marked this conversation as resolved.
Show resolved Hide resolved

let mut hello_world_test = tester::test(hello_world)?;

hello_world_test.call_start()
.execute_and_expect(None)?;

hello_world_test.call_proxy_on_context_create(1, 0)
.execute_and_expect(None)?;

hello_world_test.call_proxy_on_vm_start(1, 0)
.expect_log(LogLevel::Info, "Hello, World!")
.expect_set_tick_period_millis(5 * 10u64.pow(3))
.execute_and_expect(Some(1))?;


hello_world_test.call_proxy_on_tick(1)
.expect_get_current_time_nanos().returning(0 * 10u64.pow(9))
.execute_and_expect(None)?;

hello_world_test.call_proxy_on_tick(1)
.execute_and_expect(None)?;

return Ok(());
}
53 changes: 53 additions & 0 deletions examples/http_auth_random.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


use proxy_wasm_abi_test_harness::{tester, types::*};
use anyhow::Result;


fn main() -> Result<()>{

let http_auth_random = "/usr/local/google/home/chrisagia/ws/proxy-wasm-rust-sdk/target\
/wasm32-unknown-unknown/release/examples/http_auth_random.wasm";
agiachris marked this conversation as resolved.
Show resolved Hide resolved
let mut http_auth_random = tester::test(http_auth_random)?;

http_auth_random.call_start()
.execute_and_expect(None)?;

http_auth_random.call_proxy_on_context_create(1, 0)
.execute_and_expect(None)?;

http_auth_random.call_proxy_on_context_create(2, 1)
.execute_and_expect(None)?;

let http_call_headers = vec![(":method", "GET"), (":path", "/bytes/1"), (":authority", "httpbin.org")];
http_auth_random.call_proxy_on_request_headers(2, 0)
.expect_http_call("httpbin", http_call_headers, None, vec![], 5 * 10u64.pow(3)).returning(0)
.execute_and_expect(Some(1))?;

let buffer_data = "custom_developer_body";
let buffer_size = buffer_data.len() as i32;
http_auth_random.call_proxy_on_http_call_response(2, 0, 0, buffer_size, 0)
.expect_get_buffer_bytes(BufferType::HttpCallResponseBody).returning(buffer_data)
.execute_and_expect(None)?;

http_auth_random.call_proxy_on_response_headers(2, 0)
.execute_and_expect(Some(0))?;

// http_auth_random.call_proxy_on_http_call_response(2, 0, 0, 8, 0)
// .execute_and_expect(None)?;

return Ok(());
}
65 changes: 65 additions & 0 deletions examples/http_headers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


use proxy_wasm_abi_test_harness::{tester, types::*};
use anyhow::Result;


fn main() -> Result<()>{

let http_headers = "/usr/local/google/home/chrisagia/ws/proxy-wasm-rust-sdk/target\
/wasm32-unknown-unknown/release/examples/http_headers.wasm";
agiachris marked this conversation as resolved.
Show resolved Hide resolved

let mut http_headers_test = tester::test(http_headers)?; // create mock object

http_headers_test.call_start()
.execute_and_expect(None)?;

http_headers_test.call_proxy_on_context_create(1, 0) // create root context
.execute_and_expect(None)?;

http_headers_test.call_proxy_on_context_create(2, 1) // create http context
.execute_and_expect(None)?;

// custom expectation over call to proxy_on_request_headers
let header_map_pairs = vec![(":method", "GET"), (":path", "/hello"), (":authority", "developer")];
let send_local_response_headers = vec![("Hello", "World"), ("Powered-By", "proxy-wasm")];
agiachris marked this conversation as resolved.
Show resolved Hide resolved
http_headers_test.call_proxy_on_request_headers(2, 0)
.expect_get_header_map_pairs(MapType::HttpRequestHeaders).returning(header_map_pairs)
.expect_get_header_map_value(MapType::HttpRequestHeaders, ":path").returning("/hello")
.expect_send_local_response(200, Some("Hello, World!\n"), send_local_response_headers, -1)
.execute_and_expect(Some(1))?;

// custom expectation over call to proxy_on_response_headers
let header_map_pairs = vec![(":method", "GET"), (":path", "/goodbye"), (":authority", "developer")];
http_headers_test.call_proxy_on_response_headers(2, 0)
.expect_get_header_map_pairs(MapType::HttpResponseHeaders).returning(header_map_pairs)
.expect_log(LogLevel::Trace, "#2 <- :method: GET")
.expect_log(LogLevel::Trace, "#2 <- :path: /goodbye")
.expect_log(LogLevel::Trace, "#2 <- :authority: developer")
.execute_and_expect(Some(0))?;

http_headers_test.call_proxy_on_log(2)
.execute_and_expect(None)?;


// (1) proxy_on_request_headers (2) proxy_on_response_headers (3) proxy_on_log
// Ideally: context, request headers, request body, request trailers
http_headers_test.call_proxy_on_context_create(3, 1)
.execute_and_expect(None)?;

http_headers_test.quick_http_request(3, 0, 0);
return Ok(());
}
165 changes: 165 additions & 0 deletions src/expect_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


use crate::tester::Tester;

// As of now, the following expectations do not require "fn returning()" implementations and hence
// no structure is provided for them. Setting of these expectations are built directly into tester.rs:

/* proxy_log(), proxy_set_tick_period_millis(), proxy_replace_header_map_value(), proxy_remove_header_map_value()
proxy_add_header_map_value(), proxy_send_loca_response(), etc.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of the same comment? Is this still accurate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'm basically keeping track of which expectations do not require a structure in expect_interface.rs, as they don't require an associate returning function. Hence, these expectations are set directly through tester.rs Tester methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it might be useful to list those host-functions in case a developer notices that their corresponding expect_interface.rs structure is not present. Also, in case of future ABI changes, where host-function signatures/function might change, and thus, a structure may need to be added for one of the commented functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make them part of the same comment section, using the same style-prefix?



pub struct ExpectGetCurrentTimeNanos<'a> {
tester: &'a mut Tester,
}

impl<'a> ExpectGetCurrentTimeNanos<'a> {

pub fn expecting(tester: &'a mut Tester) -> ExpectGetCurrentTimeNanos {
ExpectGetCurrentTimeNanos {
tester: tester,
}
}

pub fn returning(&mut self, current_time_nanos: u64) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_get_current_time_nanos(current_time_nanos);
self.tester
}
}


pub struct ExpectGetBufferBytes<'a> {
tester: &'a mut Tester,
buffer_type: i32,
}

impl<'a> ExpectGetBufferBytes<'a> {

pub fn expecting(tester: &'a mut Tester, buffer_type: i32) -> ExpectGetBufferBytes {
ExpectGetBufferBytes {
tester: tester,
buffer_type: buffer_type,
}
}

pub fn returning(&mut self, buffer_data: &str) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_get_buffer_bytes(
self.buffer_type, buffer_data);
self.tester
}
}


pub struct ExpectGetHeaderMapPairs<'a> {
tester: &'a mut Tester,
map_type: i32,
}

impl<'a> ExpectGetHeaderMapPairs<'a> {

pub fn expecting(tester: &'a mut Tester, map_type: i32) -> ExpectGetHeaderMapPairs {
ExpectGetHeaderMapPairs {
tester: tester,
map_type: map_type,
}
}

pub fn returning(&mut self, header_map_pairs: Vec<(&str, &str)>) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_get_header_map_pairs(
self.map_type, header_map_pairs);
self.tester
}
}


pub struct ExpectSetHeaderMapPairs<'a> {
tester: &'a mut Tester,
map_type: i32,
}

impl<'a> ExpectSetHeaderMapPairs<'a> {

pub fn expecting(tester: &'a mut Tester, map_type: i32) -> ExpectSetHeaderMapPairs {
ExpectSetHeaderMapPairs {
tester: tester,
map_type: map_type,
}
}

pub fn returning(&mut self, header_map_pairs: Vec<(&str, &str)>) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_set_header_map_pairs(
self.map_type, header_map_pairs);
self.tester
}
}


pub struct ExpectGetHeaderMapValue<'a> {
tester: &'a mut Tester,
map_type: i32,
header_map_key: &'static str,
}

impl<'a> ExpectGetHeaderMapValue<'a> {

pub fn expecting(tester: &'a mut Tester, map_type: i32, header_map_key: &'static str) -> ExpectGetHeaderMapValue<'a> {
ExpectGetHeaderMapValue {
tester: tester,
map_type: map_type,
header_map_key: header_map_key,
}
}

pub fn returning(&mut self, header_map_value: &str) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_get_header_map_value(
self.map_type, self.header_map_key, header_map_value);
self.tester
}
}



pub struct ExpectHttpCall<'a> {
tester: &'a mut Tester,
upstream: &'a str,
headers: Option<Vec<(&'a str, &'a str)>>,
body: Option<&'a str>,
trailers: Option<Vec<(&'a str, &'a str)>>,
timeout: u64,
}

impl<'a> ExpectHttpCall<'a> {

pub fn expecting(tester: &'a mut Tester, upstream: &'a str, headers: Vec<(&'a str, &'a str)>,
body: Option<&'a str>, trailers: Vec<(&'a str, &'a str)>, timeout: u64) -> ExpectHttpCall<'a> {
ExpectHttpCall {
tester: tester,
upstream: upstream,
headers: Some(headers),
body: body,
trailers: Some(trailers),
timeout: timeout,
}
}

pub fn returning(&mut self, token_id: u32) -> &mut Tester {
self.tester.get_expect_handle().staged.set_expect_http_call(self.upstream, self.headers.take().unwrap(), self.body,
self.trailers.take().unwrap(), self.timeout, token_id);
self.tester
}

}
Loading