I tried the following approaches:
fn get_system_extension(&self) -> String {
if cfg!(target_os = "windows") {
String::from(".lib")
} else {
String::new()
}
}
mod test {
#[allow(unused_imports)]
use super::*;
use std::env;
#[test]
fn get_system_extension_one() -> Result<(), CcrustyError> {
env::set_var("CARGO_BUILD_TARGET", "linux");
let result = get_system_extension();
assert_eq!(String::new(), result);
Ok(())
}
#[test]
fn get_system_extension_two() -> Result<(), CcrustyError> {
env::set_var("CARGO_CFG_UNIX", "");
let result = get_system_extension();
assert_eq!(String::new(), result);
Ok(())
}
}
According to the documentation those could be set in a file, but I have no clue how to dynamically link them to tests.
Are you looking for something something like:
#[test]
fn currect_system_extension() -> Result<(), CcrustyError> {
let system = env::var_os("CARGO_BUILD_TARGET");
let result = get_system_extension();
match system => {
"linux" => assert_eq!(String::new(), result),
"windows" => assert_eq!(".libs".to_string(), result),
_ => (),
}
Ok(())
}
Even with this you would have to compile both versions for this test to cover the code. take a look at using --all-targets
https://doc.rust-lang.org/cargo/commands/cargo-test.html
Related
I am trying to bridging the async functionality of the SDK with sync code. I need this to create a tokio_stream (a struct with Stream trait implemented.) The problem is quite suprising,
#[cfg(test)]
mod tests {
use aws_config::meta::region::RegionProviderChain;
use aws_sdk_kinesis::{Client, Region};
use aws_config::SdkConfig;
use aws_sdk_kinesis::error::ListShardsError;
use aws_sdk_kinesis::output::ListShardsOutput;
use aws_sdk_kinesis::types::SdkError;
use futures::executor::block_on;
use tokio::runtime::Runtime;
/// It passes this test, without a problem.
#[tokio::test]
async fn testing_kinesis_stream_connection() -> () {
let region_provider = RegionProviderChain::first_try(Region::new("eu-central-1".to_string()));
let sdk_config =
aws_config::from_env()
.region(region_provider)
.load().await;
let client = Client::new(&sdk_config);
let shard_request = client.list_shards().stream_name("datafusion-stream".to_string());
let shards = shard_request.send().await.unwrap();
println!("{:?}", shards);
}
/// It passes this test, without a problem.
#[test]
fn testing_kinesis_stream_connection_without_tokio() -> () {
let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
let region_provider = RegionProviderChain::first_try(Region::new("eu-central-1".to_string()));
let sdk_config =
runtime.block_on(aws_config::from_env()
.region(region_provider)
.load());
let client = Client::new(&sdk_config);
let shard_request = client.list_shards().stream_name("datafusion-stream".to_string());
let shards = runtime.block_on(shard_request.send()).unwrap();
println!("{:?}", shards);
}
fn this_needs_to_be_sync_1() -> SdkConfig {
let region_provider = RegionProviderChain::first_try(Region::new("eu-central-1".to_string()));
let sdk_config =
futures::executor::block_on(aws_config::from_env()
.region(region_provider)
.load());
sdk_config
}
fn this_needs_to_be_sync_2(client: Client) -> ListShardsOutput {
let shard_request = client.list_shards().stream_name("datafusion-stream".to_string());
let shards = futures::executor::block_on(shard_request.send()).unwrap();
shards
}
/// It hangs in the second block_on.
#[tokio::test]
async fn testing_kinesis_stream_connection_sync_inside_async() -> () {
// Passes this block_on
let sdk_config = this_needs_to_be_sync_1();
let client = Client::new(&sdk_config);
// Blocks here
let shards = this_needs_to_be_sync_2(client);
println!("{:?}", shards);
}
}
I could not come up with a solution since there is no error, only hanged process.
I am writing some unit tests for my Rust http server handlers. But when I am running one of the tests it get stuck at the end of the inner function. Here is relevant part of the code:
async fn generate(request: Request<Body>) -> Result<Response<Body>, hyper::Error> {
let result = process_request(request).await;
println!("This message doesn't get printed!!");
let (spec, override) = match result {
Ok((s, o)) => (s, o),
Err(process_error) => {
return Ok(Response::new(Body::from(format!("{}", process_error))));
},
};
...
Ok(Response::new(Body::from(format!("{}", response))))
}
async fn process_request(request: Request<Body>) -> Result<(Spec, Option<Config>), Error> {
let body = body::to_bytes(request.into_body()).await;
let payload: Payload = serde_json::from_slice(&body.unwrap().to_vec()).unwrap();
let spec_str = payload.spec.to_owned();
...
println!("Function runs to this point and prints this message");
Ok((spec, override))
}
#[tokio::test]
async fn test_gen() {
let payload = Payload {
spec: a_spec(),
};
let payload_json = serde_json::to_string_pretty(&payload).unwrap();
let request = Request::builder().body(Body::from(payload_json));
let result = generate(request.unwrap()).await.unwrap();
// Some asserts ...
}
I am wondering what I am doing wrong?
Looks like the inner function starts another thread, so the solution was to decorate the test with:
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
This resolved the issue for my unit tests.
Given suspend fun:
private suspend fun fun1(arugment1: String): NetworkResult<Unit>
how the unit test the following fun2 and fun3
Q1:
the function which called suspend fun:
fun fun2(argument1: String) {
launch {
CustomService().fun1(argument1))
.onSuccessEmpty { _: Int, _: Headers ->
Log.d("TEST", "onSuccessEmpty")
}
.onSuccess { _: Int, _: Headers, _: Unit ->
Log.d("TEST", "onSuccess")
}.onError {
Log.d("TEST", "onError $it")
}
}
}
Q2:
fun fun2(argument1: String) {
runBlocking {
CustomService().fun1(argument1))
.onSuccessEmpty { _: Int, _: Headers ->
Log.d("TEST", "onSuccessEmpty")
}
.onSuccess { _: Int, _: Headers, _: Unit ->
Log.d("TEST", "onSuccess")
}.onError {
Log.d("TEST", "onError $it")
}
}
}
Since we never want to have top-level functions in our tests suspend, we always run them in a blocking manner. So the correct way would be Q2. Since Q1 uses launch, it'd just fire-and-forget. (I might've misunderstood your question, but I believe you're asking which of the two ways is better.)
I have a function as follows
pub fn registration(student_id: &T::StudentId, registrar: &T::RegistrarID) {
// More code here.
if num_of_students < student_limit {
Self::function_one(®istrar, &num_of_students);
} else {
Self::function_two(&num_of_students);
}
}
In unit tests, I am planning to check whether function_one or function_two was called.
#[test]
fn registration_more_students_should_call_functon_one() {
with_test_data(
&mut TestBuilder::default().num_of_students(1000).build(),
|| {
//assert_called!(module_name::registration("TV:009", "DF-000-09"));
},
);
}
How can I test if a function was called in Rust?
Strong opinion alert: you are doing your testing wrong. This is on the same level as "how do I test a private method". You shouldn't care about the implementation of registration to this level of detail.
That being said, if it's actually important to know which if branch is taken, then use dependency injection:
fn registration(mut registration: impl Registration, registrar: i32) {
let num_of_students = 0;
let student_limit = 0;
if num_of_students < student_limit {
registration.function_one(registrar, num_of_students);
} else {
registration.function_two(num_of_students);
}
}
trait Registration {
fn function_one(&mut self, registrar: i32, num_of_students: i32);
fn function_two(&mut self, num_of_students: i32);
}
impl<R: Registration> Registration for &'_ mut R {
fn function_one(&mut self, registrar: i32, num_of_students: i32) {
(**self).function_one(registrar, num_of_students)
}
fn function_two(&mut self, num_of_students: i32) {
(**self).function_two(num_of_students)
}
}
/*
// An example implementation for production
struct DatabaseRegistration;
impl Registration for DatabaseRegistration {
fn function_one(&mut self, registrar: i32, num_of_students: i32) {
eprintln!("Do DB work: {}, {}", registrar, num_of_students)
}
fn function_two(&mut self, num_of_students: i32) {
eprintln!("Do DB work: {}", num_of_students)
}
}
*/
#[cfg(test)]
mod test {
use super::*;
#[derive(Debug, Copy, Clone, Default)]
struct TestRegistration {
calls_to_one: usize,
calls_to_two: usize,
}
impl Registration for TestRegistration {
fn function_one(&mut self, _: i32, _: i32) {
self.calls_to_one += 1;
}
fn function_two(&mut self, _: i32) {
self.calls_to_two += 1;
}
}
#[test]
fn calls_the_right_one() {
let mut reg = TestRegistration::default();
registration(&mut reg, 42);
assert_eq!(1, reg.calls_to_two)
}
}
Once you have done this, then you can see that registration calls the appropriate trait function (as shown in the example test).
This prevents your production code from having test-specific detritus strewn about while also giving you the ability to be more flexible and test more cases rapidly.
See also:
How can I test stdin and stdout?
How to mock external dependencies in tests?
Is there a cleaner way to test functions that use functions that require user input in Rust?
How can I test Rust methods that depend on environment variables?
Is there a way of detecting whether code is being called from tests in Rust?
Here is a naïve attempt using #[cfg(test)] in multiple places:
struct Registration {
students: Vec<String>,
#[cfg(test)]
function_1_called: bool,
}
impl Registration {
fn new() -> Self {
Registration {
students: Vec::new(),
#[cfg(test)]
function_1_called: false,
}
}
fn function_1(&mut self, students: Vec<String>) {
self.students.extend(students);
#[cfg(test)]
{
self.function_1_called = true;
}
}
fn function_2(&mut self, students: Vec<String>) {}
fn f(&mut self, students: Vec<String>) {
if students.len() < 100 {
self.function_1(students);
} else {
self.function_2(students);
}
}
}
fn main() {
println!("Hello, world!");
let r = Registration::new();
// won't compile during `run`:
// println!("{}", r.function_1_called);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_f() {
let mut r = Registration::new();
r.function_1(vec!["Alice".to_string(), "Bob".to_string()]);
assert!(r.function_1_called);
}
}
The code is loosely based on the snippets that you provided. There is a Registration struct that holds a list of student names, two methods function_1 and function_2 for registering students, and a function f that chooses between function_1 and function_2 depending o how many students there are.
During tests, Registration is compiled with an additional Boolean variable function_1_called. This variable is set only if function_1 is called (the block of code that does that is also marked with #[cfg(test)]).
In combination, this makes an additional Boolean flag available during the tests, so that you can make assertions like the following one:
assert!(r.function_1_called);
Obviously, this could work for structures much more complicated than a single boolean flag (which does not at all mean that you should actually do it).
I cannot comment on whether this is idiomatic in Rust or not. The whole setup feels as if it should be hidden behind fancy macros, so if this style of testing is used in Rust at all, there should already be crates that provide those (or similar) macros.
If I execute the below testcases with cargo test, the output of one_thread_test will be suppressed as stated in the documentation.
However the output from multi_thread_test will appear on stdout. Is it possible to match the behavior of single- and multi-threaded testcases?
#[test]
fn one_thread_test() {
println!("A");
println!("B");
}
#[test]
fn multi_thread_test() {
use std::thread;
let mut threads = vec![];
for _ in 0..100 {
let t = thread::spawn(move || {
println!("from thread");
});
threads.push(t);
}
for thread in threads {
thread.join().unwrap();
}
}
Here is a quick-and-dirty workaround.
It works by sending messages to a receiver owned by a struct in the main thread. The receiver prints all of the accumulated messages when it is dropped - this is important so that panics caused by failed assertions don't prevent the printing.
use std::sync::mpsc::{channel, Sender, Receiver};
struct TestPrinter {
receiver: Receiver<String>,
sender: Sender<String>,
}
impl TestPrinter {
fn new() -> TestPrinter {
let (sender, receiver) = channel();
TestPrinter { receiver, sender }
}
fn sender(&self) -> Sender<String> {
self.sender.clone()
}
}
impl Drop for TestPrinter {
fn drop(&mut self) {
while let Some(v) = self.receiver.try_recv().ok() {
println!("later: {}", v);
}
}
}
And a convenience macro so it feels mostly like calling println!:
macro_rules! myprint {
($send: expr, $($arg:tt)*) => {
(*&$send).send(format!($($arg)*));
};
}
In order to send messages for printing, you have get a sender for each thread:
#[test]
fn multi_thread_test() {
use std::thread;
let mut threads = vec![];
let printer = TestPrinter::new();
for _ in 0..100 {
let sender = printer.sender();
let t = thread::spawn(move || {
myprint!(sender, "from thread");
});
threads.push(t);
}
for thread in threads {
thread.join().unwrap();
}
}
The actual printing happens when printer goes out of scope. It's in the main thread so it won't print during successful tests unless --nocapture is specified.