Skip to content

Commit 1b82309

Browse files
committed
assert grpc calls
Signed-off-by: Alex Snaps <[email protected]>
1 parent 1dca120 commit 1b82309

File tree

7 files changed

+241
-28
lines changed

7 files changed

+241
-28
lines changed

examples/http_auth_random.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ fn main() -> Result<()> {
5555
http_auth_random
5656
.call_proxy_on_http_call_response(http_context, 0, 0, buffer_data.len() as i32, 0)
5757
.expect_get_buffer_bytes(Some(BufferType::HttpCallResponseBody))
58-
.returning(Some(buffer_data))
58+
.returning(Some(buffer_data.as_bytes()))
5959
.expect_send_local_response(
6060
Some(403),
6161
Some("Access forbidden.\n"),

src/expect_interface.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl<'a> ExpectGetBufferBytes<'a> {
5151
}
5252
}
5353

54-
pub fn returning(&mut self, buffer_data: Option<&str>) -> &mut Tester {
54+
pub fn returning(&mut self, buffer_data: Option<&[u8]>) -> &mut Tester {
5555
self.tester
5656
.get_expect_handle()
5757
.staged
@@ -150,3 +150,48 @@ impl<'a> ExpectHttpCall<'a> {
150150
self.tester
151151
}
152152
}
153+
154+
pub struct ExpectGrpcCall<'a> {
155+
tester: &'a mut Tester,
156+
service: Option<&'a str>,
157+
service_name: Option<&'a str>,
158+
method_name: Option<&'a str>,
159+
initial_metadata: Option<&'a [u8]>,
160+
request: Option<&'a [u8]>,
161+
timeout: Option<u64>,
162+
}
163+
164+
impl<'a> ExpectGrpcCall<'a> {
165+
pub fn expecting(
166+
tester: &'a mut Tester,
167+
service: Option<&'a str>,
168+
service_name: Option<&'a str>,
169+
method_name: Option<&'a str>,
170+
initial_metadata: Option<&'a [u8]>,
171+
request: Option<&'a [u8]>,
172+
timeout: Option<u64>,
173+
) -> Self {
174+
Self {
175+
tester,
176+
service,
177+
service_name,
178+
method_name,
179+
initial_metadata,
180+
request,
181+
timeout,
182+
}
183+
}
184+
185+
pub fn returning(&mut self, token_id: Option<u32>) -> &mut Tester {
186+
self.tester.get_expect_handle().staged.set_expect_grpc_call(
187+
self.service,
188+
self.service_name,
189+
self.method_name,
190+
self.initial_metadata,
191+
self.request,
192+
self.timeout,
193+
token_id,
194+
);
195+
self.tester
196+
}
197+
}

src/expectations.rs

+88-10
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,20 @@ impl ExpectHandle {
4141
self.staged = Expect::new(allow_unexpected);
4242
}
4343

44-
pub fn assert_stage(&self) {
44+
pub fn assert_stage(&self) -> Option<String> {
4545
if self.staged.expect_count > 0 {
46-
panic!(
46+
return Some(format!(
4747
"Error: failed to consume all expectations - total remaining: {}",
4848
self.staged.expect_count
49-
);
49+
));
5050
} else if self.staged.expect_count < 0 {
51-
panic!(
51+
return Some(format!(
5252
"Error: expectations failed to account for all host calls by {} \n\
5353
if this is intended, please use --allow-unexpected (-a) mode",
5454
-1 * self.staged.expect_count
55-
);
55+
));
5656
}
57+
None
5758
}
5859

5960
pub fn print_staged(&self) {
@@ -86,6 +87,15 @@ pub struct Expect {
8687
Option<Duration>,
8788
Option<u32>,
8889
)>,
90+
grpc_call: Vec<(
91+
Option<String>,
92+
Option<String>,
93+
Option<String>,
94+
Option<Bytes>,
95+
Option<Bytes>,
96+
Option<Duration>,
97+
Option<u32>,
98+
)>,
8999
}
90100

91101
impl Expect {
@@ -106,6 +116,7 @@ impl Expect {
106116
add_header_map_value: vec![],
107117
send_local_response: vec![],
108118
http_call: vec![],
119+
grpc_call: vec![],
109120
}
110121
}
111122

@@ -190,13 +201,11 @@ impl Expect {
190201
pub fn set_expect_get_buffer_bytes(
191202
&mut self,
192203
buffer_type: Option<i32>,
193-
buffer_data: Option<&str>,
204+
buffer_data: Option<&[u8]>,
194205
) {
195206
self.expect_count += 1;
196-
self.get_buffer_bytes.push((
197-
buffer_type,
198-
buffer_data.map(|data| data.as_bytes().to_vec()),
199-
));
207+
self.get_buffer_bytes
208+
.push((buffer_type, buffer_data.map(|data| data.to_vec())));
200209
}
201210

202211
pub fn get_expect_get_buffer_bytes(&mut self, buffer_type: i32) -> Option<Bytes> {
@@ -571,4 +580,73 @@ impl Expect {
571580
}
572581
}
573582
}
583+
584+
pub fn set_expect_grpc_call(
585+
&mut self,
586+
service: Option<&str>,
587+
service_name: Option<&str>,
588+
method_name: Option<&str>,
589+
initial_metadata: Option<&[u8]>,
590+
request: Option<&[u8]>,
591+
timeout: Option<u64>,
592+
token_id: Option<u32>,
593+
) {
594+
self.expect_count += 1;
595+
self.grpc_call.push((
596+
service.map(ToString::to_string),
597+
service_name.map(ToString::to_string),
598+
method_name.map(ToString::to_string),
599+
initial_metadata.map(|s| s.to_vec()),
600+
request.map(|s| s.to_vec()),
601+
timeout.map(Duration::from_millis),
602+
token_id,
603+
));
604+
}
605+
606+
pub fn get_expect_grpc_call(
607+
&mut self,
608+
service: String,
609+
service_name: String,
610+
method: String,
611+
initial_metadata: &[u8],
612+
request: &[u8],
613+
timeout: i32,
614+
) -> Option<u32> {
615+
match self.grpc_call.len() {
616+
0 => {
617+
if !self.allow_unexpected {
618+
self.expect_count -= 1;
619+
}
620+
set_status(ExpectStatus::Unexpected);
621+
None
622+
}
623+
_ => {
624+
self.expect_count -= 1;
625+
let (
626+
expected_service,
627+
expected_service_name,
628+
expected_method,
629+
expected_initial_metadata,
630+
expected_request,
631+
expected_duration,
632+
result,
633+
) = self.grpc_call.remove(0);
634+
635+
let expected = expected_service.map(|e| e == service).unwrap_or(true)
636+
&& expected_service_name
637+
.map(|e| e == service_name)
638+
.unwrap_or(true)
639+
&& expected_method.map(|e| e == method).unwrap_or(true)
640+
&& expected_initial_metadata
641+
.map(|e| e == initial_metadata)
642+
.unwrap_or(true)
643+
&& expected_request.map(|e| e == request).unwrap_or(true)
644+
&& expected_duration
645+
.map(|e| e.as_millis() as i32 == timeout)
646+
.unwrap_or(true);
647+
set_expect_status(expected);
648+
return result;
649+
}
650+
}
651+
}
574652
}

src/host_settings.rs

+4
Original file line numberDiff line numberDiff line change
@@ -294,5 +294,9 @@ pub fn default_buffer_bytes() -> HashMap<i32, Bytes> {
294294
BufferType::HttpCallResponseBody as i32,
295295
"default_call_response_body".as_bytes().to_vec(),
296296
);
297+
default_bytes.insert(
298+
BufferType::GrpcReceiveBuffer as i32,
299+
"default_grpc_receive_buffer".as_bytes().to_vec(),
300+
);
297301
default_bytes
298302
}

src/hostcalls.rs

+75-15
Original file line numberDiff line numberDiff line change
@@ -1390,20 +1390,67 @@ fn get_hostfunc(
13901390
"proxy_grpc_call" => {
13911391
Some(Func::wrap(
13921392
store,
1393-
|_caller: Caller<'_, ()>,
1394-
_service_ptr: i32,
1395-
_service_size: i32,
1396-
_service_name_ptr: i32,
1397-
_service_name_size: i32,
1398-
_method_name_ptr: i32,
1399-
_method_name_size: i32,
1400-
_initial_metadata_ptr: i32,
1401-
_initial_metadata_size: i32,
1402-
_request_ptr: i32,
1403-
_request_size: i32,
1404-
_timeout_milliseconds: i32,
1405-
_token_ptr: i32|
1393+
|mut caller: Caller<'_, ()>,
1394+
service_ptr: i32,
1395+
service_size: i32,
1396+
service_name_ptr: i32,
1397+
service_name_size: i32,
1398+
method_name_ptr: i32,
1399+
method_name_size: i32,
1400+
initial_metadata_ptr: i32,
1401+
initial_metadata_size: i32,
1402+
request_ptr: i32,
1403+
request_size: i32,
1404+
timeout_milliseconds: i32,
1405+
token_ptr: i32|
14061406
-> i32 {
1407+
print!("[vm->host] proxy_grpc_call({initial_metadata_ptr}, {initial_metadata_size})");
1408+
1409+
// Default Function: receives and displays http call from proxy-wasm module
1410+
// Expectation: asserts equal the receieved http call with the expected one
1411+
let mem = match caller.get_export("memory") {
1412+
Some(Extern::Memory(mem)) => mem,
1413+
_ => {
1414+
println!("Error: proxy_http_call cannot get export \"memory\"");
1415+
println!(
1416+
"[vm<-host] proxy_http_call(...) -> (return_token) return: {:?}",
1417+
Status::InternalFailure
1418+
);
1419+
return Status::InternalFailure as i32;
1420+
}
1421+
};
1422+
1423+
let service = read_string(&caller, mem, service_ptr, service_size);
1424+
let service_name =
1425+
read_string(&caller, mem, service_name_ptr, service_name_size);
1426+
let method_name = read_string(&caller, mem, method_name_ptr, method_name_size);
1427+
let initial_metadata =
1428+
read_bytes(&caller, mem, initial_metadata_ptr, initial_metadata_size)
1429+
.unwrap();
1430+
let request = read_bytes(&caller, mem, request_ptr, request_size).unwrap();
1431+
1432+
println!(
1433+
"[vm->host] proxy_grpc_call(service={service}, service_name={service_name}, method_name={method_name}, initial_metadata={initial_metadata:?}, request={request:?}, timeout={timeout_milliseconds}");
1434+
1435+
let token_id = match EXPECT.lock().unwrap().staged.get_expect_grpc_call(
1436+
service,
1437+
service_name,
1438+
method_name,
1439+
initial_metadata,
1440+
request,
1441+
timeout_milliseconds,
1442+
) {
1443+
Some(expect_token) => expect_token,
1444+
None => 0,
1445+
};
1446+
1447+
unsafe {
1448+
let return_token_add = mem.data_mut(&mut caller).get_unchecked_mut(
1449+
token_ptr as u32 as usize..token_ptr as u32 as usize + 4,
1450+
);
1451+
return_token_add.copy_from_slice(&token_id.to_le_bytes());
1452+
}
1453+
14071454
// Default Function:
14081455
// Expectation:
14091456
println!(
@@ -1412,9 +1459,10 @@ fn get_hostfunc(
14121459
);
14131460
println!(
14141461
"[vm<-host] proxy_grpc_call() -> (..) return: {:?}",
1415-
Status::InternalFailure
1462+
Status::Ok
14161463
);
1417-
return Status::InternalFailure as i32;
1464+
assert_ne!(get_status(), ExpectStatus::Failed);
1465+
return Status::Ok as i32;
14181466
},
14191467
))
14201468
}
@@ -1641,6 +1689,18 @@ fn get_hostfunc(
16411689
}
16421690
}
16431691

1692+
fn read_string(caller: &Caller<()>, mem: Memory, ptr: i32, size: i32) -> String {
1693+
read_bytes(caller, mem, ptr, size)
1694+
.map(String::from_utf8_lossy)
1695+
.unwrap()
1696+
.to_string()
1697+
}
1698+
1699+
fn read_bytes<'a>(caller: &'a Caller<()>, mem: Memory, ptr: i32, size: i32) -> Option<&'a [u8]> {
1700+
mem.data(caller)
1701+
.get(ptr as usize..ptr as usize + size as usize)
1702+
}
1703+
16441704
pub mod serial_utils {
16451705

16461706
type Bytes = Vec<u8>;

src/tester.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,26 @@ impl Tester {
268268
ExpectHttpCall::expecting(self, upstream, headers, body, trailers, timeout)
269269
}
270270

271+
pub fn expect_grpc_call(
272+
&mut self,
273+
service: Option<&'static str>,
274+
service_name: Option<&'static str>,
275+
method_name: Option<&'static str>,
276+
initial_metadata: Option<&'static [u8]>,
277+
request: Option<&'static [u8]>,
278+
timeout: Option<u64>,
279+
) -> ExpectGrpcCall {
280+
ExpectGrpcCall::expecting(
281+
self,
282+
service,
283+
service_name,
284+
method_name,
285+
initial_metadata,
286+
request,
287+
timeout,
288+
)
289+
}
290+
271291
/* ------------------------------------- High-level Expectation Setting ------------------------------------- */
272292

273293
pub fn set_quiet(&mut self, quiet: bool) {
@@ -323,7 +343,10 @@ impl Tester {
323343
}
324344

325345
fn assert_expect_stage(&mut self) {
326-
self.expect.lock().unwrap().assert_stage();
346+
let err = self.expect.lock().unwrap().assert_stage();
347+
if let Some(msg) = err {
348+
panic!("{}", msg)
349+
}
327350
}
328351

329352
pub fn get_settings_handle(&self) -> MutexGuard<HostHandle> {

src/types.rs

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ pub enum BufferType {
8888
DownstreamData = 2,
8989
UpstreamData = 3,
9090
HttpCallResponseBody = 4,
91+
GrpcReceiveBuffer = 5,
92+
VmConfiguration = 6,
93+
PluginConfiguration = 7,
9194
}
9295

9396
#[repr(u32)]

0 commit comments

Comments
 (0)