use protest::generator::ConstantGenerator;
use protest::primitives::{HashMapGenerator, IntGenerator, StringGenerator};
use protest::{
AsyncProperty, Property, PropertyError, PropertyTestBuilder, TestConfig, check, check_async,
check_async_with_config,
};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[tokio::test]
async fn test_basic_async_property() {
let generator = IntGenerator::new(1, 100);
struct PositiveValueProperty;
impl AsyncProperty<i32> for PositiveValueProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
if value <= 0 {
Err(PropertyError::PropertyFailed {
message: "Value must be positive".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let result = check_async(generator, PositiveValueProperty).await;
assert!(result.is_ok());
if let Ok(success) = result {
assert_eq!(success.iterations, 100); assert!(success.stats.is_some());
}
}
#[tokio::test]
async fn test_async_property_with_config() {
let generator = IntGenerator::new(1, 50);
let config = TestConfig {
iterations: 20,
seed: Some(42),
max_shrink_iterations: 100,
shrink_timeout: Duration::from_secs(5),
..TestConfig::default()
};
struct ValueRangeProperty;
impl AsyncProperty<i32> for ValueRangeProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(2)).await;
if value > 100 {
Err(PropertyError::PropertyFailed {
message: "Value too large".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let result = check_async_with_config(generator, ValueRangeProperty, config).await;
assert!(result.is_ok());
if let Ok(success) = result {
assert_eq!(success.iterations, 20);
assert_eq!(success.config.seed, Some(42));
}
}
#[tokio::test]
async fn test_async_property_failure_with_shrinking() {
let generator = IntGenerator::new(50, 200);
let config = TestConfig {
iterations: 10,
max_shrink_iterations: 50,
shrink_timeout: Duration::from_secs(2),
..TestConfig::default()
};
struct LargeValueProperty;
impl AsyncProperty<i32> for LargeValueProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
if value > 100 {
Err(PropertyError::PropertyFailed {
message: format!("Value {} is too large", value),
context: Some("async test".to_string()),
iteration: None,
})
} else {
Ok(())
}
}
}
let result = check_async_with_config(generator, LargeValueProperty, config).await;
assert!(result.is_err());
if let Err(failure) = result {
assert!(failure.original_input >= 50);
assert!(failure.original_input <= 200);
if let Some(shrunk) = failure.shrunk_input {
assert!(shrunk > 100); assert!(shrunk <= failure.original_input); }
match &failure.error {
PropertyError::PropertyFailed {
message,
context,
iteration,
} => {
assert!(message.contains("too large"));
assert_eq!(context, &Some("async test".to_string()));
assert!(iteration.is_some());
}
_ => panic!("Expected PropertyFailed error"),
}
}
}
#[tokio::test]
async fn test_async_sync_interoperability() {
struct SyncProperty;
impl Property<i32> for SyncProperty {
type Output = ();
fn test(&self, input: i32) -> Result<Self::Output, PropertyError> {
if input < 0 {
Err(PropertyError::PropertyFailed {
message: "Negative value".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
struct AsyncPropertyTest;
impl AsyncProperty<i32> for AsyncPropertyTest {
type Output = ();
async fn test(&self, input: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
if input > 1000 {
Err(PropertyError::PropertyFailed {
message: "Value too large".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let generator1 = IntGenerator::new(0, 500);
let generator2 = IntGenerator::new(0, 500);
let sync_result = check(generator1, SyncProperty);
assert!(sync_result.is_ok());
let async_result = check_async(generator2, AsyncPropertyTest).await;
assert!(async_result.is_ok());
if let (Ok(sync_success), Ok(async_success)) = (sync_result, async_result) {
assert_eq!(sync_success.iterations, async_success.iterations);
}
}
#[tokio::test]
async fn test_async_property_with_complex_data() {
use std::collections::HashMap;
let generator = HashMapGenerator::new(
StringGenerator::ascii_printable(5, 10),
IntGenerator::new(1, 100),
0,
10,
);
struct SumProperty;
impl AsyncProperty<HashMap<String, i32>> for SumProperty {
type Output = ();
async fn test(&self, map: HashMap<String, i32>) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
let sum: i32 = map.values().sum();
if sum >= 1000 {
Err(PropertyError::PropertyFailed {
message: format!("Sum {} is too large", sum),
context: Some("complex data test".to_string()),
iteration: None,
})
} else {
Ok(())
}
}
}
let result = check_async(generator, SumProperty).await;
match result {
Ok(_) => {
}
Err(failure) => {
assert!(!failure.original_input.is_empty() || failure.original_input.is_empty());
match &failure.error {
PropertyError::PropertyFailed { message, .. } => {
assert!(message.contains("too large"));
}
_ => panic!("Expected PropertyFailed error"),
}
}
}
}
#[tokio::test]
async fn test_async_property_with_timeout() {
let generator = ConstantGenerator::new(42);
let config = TestConfig {
iterations: 5,
shrink_timeout: Duration::from_millis(100), ..TestConfig::default()
};
struct SlowFailingProperty;
impl AsyncProperty<i32> for SlowFailingProperty {
type Output = ();
async fn test(&self, _value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(50)).await;
Err(PropertyError::PropertyFailed {
message: "Always fails".to_string(),
context: None,
iteration: None,
})
}
}
let result = check_async_with_config(generator, SlowFailingProperty, config).await;
assert!(result.is_err());
if let Err(failure) = result {
assert_eq!(failure.original_input, 42);
assert!(failure.shrink_duration <= Duration::from_millis(150)); }
}
#[tokio::test]
async fn test_async_error_handling() {
let generator = IntGenerator::new(1, 10);
struct ErrorHandlingProperty;
impl AsyncProperty<i32> for ErrorHandlingProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
match value {
1 => Err(PropertyError::PropertyFailed {
message: "Value is 1".to_string(),
context: Some("specific case".to_string()),
iteration: None,
}),
2 => Err(PropertyError::GenerationFailed {
message: "Simulated generation error".to_string(),
context: Some("error test".to_string()),
}),
3 => Err(PropertyError::TestCancelled {
reason: "User requested cancellation".to_string(),
}),
_ => Ok(()),
}
}
}
let result = check_async(generator, ErrorHandlingProperty).await;
assert!(result.is_err());
if let Err(failure) = result {
match &failure.error {
PropertyError::PropertyFailed {
message,
context,
iteration,
} => {
assert_eq!(message, "Value is 1");
assert_eq!(context, &Some("specific case".to_string()));
assert!(iteration.is_some()); }
PropertyError::GenerationFailed { .. } => {
}
PropertyError::TestCancelled { .. } => {
}
_ => panic!("Unexpected error type: {:?}", failure.error),
}
}
}
#[tokio::test]
async fn test_async_property_with_builder() {
let generator = IntGenerator::new(10, 50);
struct DivisibleBy7Property;
impl AsyncProperty<i32> for DivisibleBy7Property {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
if value % 7 == 0 {
Err(PropertyError::PropertyFailed {
message: "Divisible by 7".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let result = PropertyTestBuilder::new()
.iterations(30)
.seed(123)
.max_shrink_iterations(20)
.shrink_timeout(Duration::from_secs(1))
.run_async(generator, DivisibleBy7Property)
.await;
match result {
Ok(success) => {
assert_eq!(success.iterations, 30);
assert_eq!(success.config.seed, Some(123));
}
Err(failure) => {
assert!(failure.original_input % 7 == 0);
assert_eq!(failure.config.seed, Some(123));
}
}
}
#[tokio::test]
async fn test_async_performance() {
let generator = IntGenerator::new(1, 1000);
let config = TestConfig {
iterations: 100,
..TestConfig::default()
};
struct FastAsyncProperty;
impl AsyncProperty<i32> for FastAsyncProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::task::yield_now().await;
if value < 0 {
Err(PropertyError::PropertyFailed {
message: "Negative value".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let start = Instant::now();
let result = check_async_with_config(generator, FastAsyncProperty, config).await;
let duration = start.elapsed();
assert!(result.is_ok());
assert!(
duration < Duration::from_secs(1),
"Async execution took too long: {:?}",
duration
);
if let Ok(success) = result {
assert_eq!(success.iterations, 100);
println!("Async performance test completed in {:?}", duration);
}
}
#[tokio::test]
async fn test_concurrent_async_properties() {
let generator1 = IntGenerator::new(1, 50);
let generator2 = IntGenerator::new(51, 100);
struct Property1;
impl AsyncProperty<i32> for Property1 {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(2)).await;
if value > 25 {
Err(PropertyError::PropertyFailed {
message: "Value too large for property1".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
struct Property2;
impl AsyncProperty<i32> for Property2 {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(3)).await;
if value < 75 {
Err(PropertyError::PropertyFailed {
message: "Value too small for property2".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let config = TestConfig {
iterations: 10,
..TestConfig::default()
};
let (result1, result2) = tokio::join!(
check_async_with_config(generator1, Property1, config.clone()),
check_async_with_config(generator2, Property2, config)
);
assert!(result1.is_err());
assert!(result2.is_err());
if let (Err(failure1), Err(failure2)) = (result1, result2) {
match &failure1.error {
PropertyError::PropertyFailed { message, .. } => {
assert!(message.contains("property1"));
}
_ => panic!("Expected PropertyFailed for property1"),
}
match &failure2.error {
PropertyError::PropertyFailed { message, .. } => {
assert!(message.contains("property2"));
}
_ => panic!("Expected PropertyFailed for property2"),
}
}
}
#[tokio::test]
async fn test_async_property_with_shared_state() {
let counter = Arc::new(Mutex::new(0));
let generator = IntGenerator::new(1, 20);
struct SharedStateProperty {
counter: Arc<Mutex<i32>>,
}
impl AsyncProperty<i32> for SharedStateProperty {
type Output = ();
async fn test(&self, value: i32) -> Result<Self::Output, PropertyError> {
tokio::time::sleep(Duration::from_millis(1)).await;
{
let mut count = self.counter.lock().unwrap();
*count += 1;
}
if value > 15 {
Err(PropertyError::PropertyFailed {
message: "Value too large".to_string(),
context: None,
iteration: None,
})
} else {
Ok(())
}
}
}
let config = TestConfig {
iterations: 10,
..TestConfig::default()
};
let property = SharedStateProperty {
counter: counter.clone(),
};
let result = check_async_with_config(generator, property, config).await;
let final_count = *counter.lock().unwrap();
match result {
Ok(_) => {
assert_eq!(final_count, 10);
}
Err(_) => {
assert!(final_count > 0); }
}
}