#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::time::Duration;
#[cfg(test)]
use proptest::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RequirementLevel {
Must,
Should,
May,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TestCategory {
CausalityOrdering,
DporCoverage,
EventIndependence,
LogicalClocks,
RaceDetection,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConformanceCase {
pub id: &'static str,
pub section: &'static str,
pub level: RequirementLevel,
pub category: TestCategory,
pub description: &'static str,
}
#[derive(Debug, Serialize)]
pub enum TestResult {
Pass,
Fail { reason: String },
Skipped { reason: String },
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct LogicalTime {
pub clock: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CausalOrder {
Before,
After,
Equal,
Concurrent,
}
#[derive(Debug, Clone)]
pub struct TraceEvent {
pub id: u64,
pub task_id: u64,
pub logical_time: LogicalTime,
pub event_type: TraceEventType,
pub resource_accesses: Vec<ResourceAccess>,
pub sequence_number: u64,
pub virtual_time: Duration,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TraceEventType {
TaskSpawn { child_task: u64 },
TaskComplete { result: TaskResult },
TaskCancel,
ChannelSend { channel_id: u64, message_id: u64 },
ChannelReceive { channel_id: u64, message_id: u64 },
MutexAcquire { mutex_id: u64 },
MutexRelease { mutex_id: u64 },
RegionCreate { region_id: u64 },
RegionClose { region_id: u64 },
ObligationCommit { obligation_id: u64 },
ObligationAbort { obligation_id: u64 },
}
#[derive(Debug, Clone, PartialEq)]
pub enum TaskResult {
Success,
Cancelled,
Panicked,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResourceAccess {
pub resource: Resource,
pub access_type: AccessType,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Resource {
Channel(u64),
Mutex(u64),
Region(u64),
Task(u64),
Obligation(u64),
Memory(u64), }
#[derive(Debug, Clone, PartialEq)]
pub enum AccessType {
Read,
Write,
Create,
Destroy,
}
#[derive(Debug, Clone)]
pub struct CausalityViolation {
pub kind: CausalityViolationKind,
pub earlier_event: u64,
pub later_event: u64,
pub earlier_time: LogicalTime,
pub later_time: LogicalTime,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CausalityViolationKind {
NonMonotonic,
BackwardCausation,
InvalidHappensBefore,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Race {
pub earlier_event: u64,
pub later_event: u64,
pub dependency_type: DependencyType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DependencyType {
ReadWrite,
WriteRead,
WriteWrite,
Control,
}
#[derive(Debug, Clone)]
pub struct DporAnalysis {
pub races: Vec<Race>,
pub backtrack_points: Vec<BacktrackPoint>,
pub sleep_set: HashSet<u64>,
pub explored_schedules: Vec<ExecutionSchedule>,
}
#[derive(Debug, Clone)]
pub struct BacktrackPoint {
pub decision_point: u64,
pub alternative_event: u64,
pub justification: String,
}
#[derive(Debug, Clone)]
pub struct ExecutionSchedule {
pub schedule_id: u64,
pub event_order: Vec<u64>,
pub final_state: HashMap<Resource, String>,
}
#[derive(Debug, Clone)]
pub struct MockTraceAnalyzer {
events: Vec<TraceEvent>,
logical_clock_domains: HashMap<u64, u64>, causality_graph: HashMap<u64, Vec<u64>>, resource_state: HashMap<Resource, Vec<u64>>, }
impl MockTraceAnalyzer {
pub fn new() -> Self {
Self {
events: Vec::new(),
logical_clock_domains: HashMap::new(),
causality_graph: HashMap::new(),
resource_state: HashMap::new(),
}
}
pub fn add_event(&mut self, mut event: TraceEvent) -> Result<(), String> {
let dependencies = self.find_causality_dependencies(&event);
let dependency_clock = dependencies
.iter()
.filter_map(|dep_id| self.events.iter().find(|e| e.id == *dep_id))
.map(|dep_event| dep_event.logical_time.clock)
.max()
.unwrap_or(0);
let current_clock = self
.logical_clock_domains
.get(&event.task_id)
.cloned()
.unwrap_or(0);
let next_clock = current_clock.max(dependency_clock) + 1;
event.logical_time = LogicalTime { clock: next_clock };
self.logical_clock_domains.insert(event.task_id, next_clock);
for access in &event.resource_accesses {
self.resource_state
.entry(access.resource.clone())
.or_insert_with(Vec::new)
.push(event.id);
}
if !dependencies.is_empty() {
self.causality_graph.insert(event.id, dependencies);
}
self.events.push(event);
Ok(())
}
pub fn verify_topological_ordering(&self) -> Result<(), Vec<CausalityViolation>> {
let mut violations = Vec::new();
for (i, event) in self.events.iter().enumerate() {
for (_j, other_event) in self.events.iter().enumerate().skip(i + 1) {
if event.task_id == other_event.task_id {
if event.logical_time.clock >= other_event.logical_time.clock {
violations.push(CausalityViolation {
kind: CausalityViolationKind::NonMonotonic,
earlier_event: event.id,
later_event: other_event.id,
earlier_time: event.logical_time.clone(),
later_time: other_event.logical_time.clone(),
});
}
}
}
if let Some(dependencies) = self.causality_graph.get(&event.id) {
for dep_id in dependencies {
if let Some(dep_event) = self.events.iter().find(|e| e.id == *dep_id) {
if dep_event.logical_time.clock >= event.logical_time.clock {
violations.push(CausalityViolation {
kind: CausalityViolationKind::InvalidHappensBefore,
earlier_event: dep_event.id,
later_event: event.id,
earlier_time: dep_event.logical_time.clone(),
later_time: event.logical_time.clone(),
});
}
}
}
}
}
if violations.is_empty() {
Ok(())
} else {
Err(violations)
}
}
pub fn detect_backward_causation(&self) -> Vec<CausalityViolation> {
let mut violations = Vec::new();
for event in &self.events {
match &event.event_type {
TraceEventType::ChannelReceive {
channel_id,
message_id,
} => {
if let Some(send_event) = self.events.iter().find(|e| {
matches!(e.event_type, TraceEventType::ChannelSend { channel_id: cid, message_id: mid }
if cid == *channel_id && mid == *message_id)
}) {
if event.logical_time.clock <= send_event.logical_time.clock {
violations.push(CausalityViolation {
kind: CausalityViolationKind::BackwardCausation,
earlier_event: send_event.id,
later_event: event.id,
earlier_time: send_event.logical_time.clone(),
later_time: event.logical_time.clone(),
});
}
}
}
_ => {}
}
}
violations
}
fn find_causality_dependencies(&self, event: &TraceEvent) -> Vec<u64> {
let mut dependencies = Vec::new();
match &event.event_type {
TraceEventType::ChannelReceive {
channel_id,
message_id,
} => {
if let Some(send_event) = self.events.iter().find(|e| {
matches!(e.event_type, TraceEventType::ChannelSend { channel_id: cid, message_id: mid }
if cid == *channel_id && mid == *message_id)
}) {
dependencies.push(send_event.id);
}
}
TraceEventType::MutexAcquire { mutex_id } => {
for other in self.events.iter().rev() {
if matches!(other.event_type, TraceEventType::MutexRelease { mutex_id: mid } if mid == *mutex_id)
{
dependencies.push(other.id);
break;
}
}
}
_ => {}
}
dependencies
}
pub fn analyze_dpor(&self) -> DporAnalysis {
let races = self.detect_races();
let backtrack_points = self.generate_backtrack_points(&races);
let sleep_set = self.compute_sleep_set();
let explored_schedules = self.generate_alternative_schedules(&backtrack_points);
DporAnalysis {
races,
backtrack_points,
sleep_set,
explored_schedules,
}
}
pub fn detect_races(&self) -> Vec<Race> {
let mut races = Vec::new();
for (i, event_a) in self.events.iter().enumerate() {
for (j, event_b) in self.events.iter().enumerate().skip(i + 1) {
if event_a.task_id == event_b.task_id {
continue;
}
if let Some(dependency_type) = self.check_dependency(event_a, event_b) {
let has_intervening = self.events[i + 1..j].iter().any(|intervening| {
self.check_dependency(event_a, intervening).is_some()
&& self.check_dependency(intervening, event_b).is_some()
});
if !has_intervening {
races.push(Race {
earlier_event: event_a.id,
later_event: event_b.id,
dependency_type,
});
}
}
}
}
races
}
fn check_dependency(
&self,
event_a: &TraceEvent,
event_b: &TraceEvent,
) -> Option<DependencyType> {
for access_a in &event_a.resource_accesses {
for access_b in &event_b.resource_accesses {
if access_a.resource == access_b.resource {
return match (&access_a.access_type, &access_b.access_type) {
(AccessType::Read, AccessType::Write) => Some(DependencyType::ReadWrite),
(AccessType::Write, AccessType::Read) => Some(DependencyType::WriteRead),
(AccessType::Write, AccessType::Write) => Some(DependencyType::WriteWrite),
_ => None,
};
}
}
}
match (&event_a.event_type, &event_b.event_type) {
(TraceEventType::TaskSpawn { child_task }, _) if *child_task == event_b.task_id => {
Some(DependencyType::Control)
}
_ => None,
}
}
fn generate_backtrack_points(&self, races: &[Race]) -> Vec<BacktrackPoint> {
let mut backtrack_points = Vec::new();
for (i, race) in races.iter().enumerate() {
backtrack_points.push(BacktrackPoint {
decision_point: race.earlier_event,
alternative_event: race.later_event,
justification: format!("Race {} - explore alternative ordering", i),
});
}
backtrack_points
}
fn compute_sleep_set(&self) -> HashSet<u64> {
let mut sleep_set = HashSet::new();
for event in &self.events {
match &event.event_type {
TraceEventType::MutexAcquire { .. } | TraceEventType::ChannelSend { .. } => {
sleep_set.insert(event.id);
}
_ => {}
}
}
sleep_set
}
fn generate_alternative_schedules(
&self,
backtrack_points: &[BacktrackPoint],
) -> Vec<ExecutionSchedule> {
let mut schedules = Vec::new();
for (i, _backtrack) in backtrack_points.iter().enumerate() {
let mut alternative_order = self.events.iter().map(|e| e.id).collect::<Vec<_>>();
if alternative_order.len() >= 2 {
let last_idx = alternative_order.len() - 1;
alternative_order.swap(0, last_idx);
}
schedules.push(ExecutionSchedule {
schedule_id: i as u64,
event_order: alternative_order,
final_state: self.compute_final_state(),
});
}
schedules
}
fn compute_final_state(&self) -> HashMap<Resource, String> {
let mut state = HashMap::new();
for event in &self.events {
for access in &event.resource_accesses {
let state_desc = match access.access_type {
AccessType::Create => "created".to_string(),
AccessType::Destroy => "destroyed".to_string(),
AccessType::Read => "read".to_string(),
AccessType::Write => "written".to_string(),
};
state.insert(access.resource.clone(), state_desc);
}
}
state
}
pub fn verify_event_independence(&self) -> Result<(), String> {
for (i, event_a) in self.events.iter().enumerate() {
for (_j, event_b) in self.events.iter().enumerate().skip(i + 1) {
if self.are_independent(event_a, event_b) {
if !self.can_reorder_safely(event_a, event_b) {
return Err(format!(
"Events {} and {} are independent but cannot be safely reordered",
event_a.id, event_b.id
));
}
}
}
}
Ok(())
}
fn are_independent(&self, event_a: &TraceEvent, event_b: &TraceEvent) -> bool {
for access_a in &event_a.resource_accesses {
for access_b in &event_b.resource_accesses {
if access_a.resource == access_b.resource {
return false;
}
}
}
match (&event_a.event_type, &event_b.event_type) {
(TraceEventType::TaskSpawn { child_task }, _) if *child_task == event_b.task_id => {
false
}
(_, TraceEventType::TaskSpawn { child_task }) if *child_task == event_a.task_id => {
false
}
_ => true,
}
}
fn can_reorder_safely(&self, _event_a: &TraceEvent, _event_b: &TraceEvent) -> bool {
true
}
pub fn analyze_resource_footprints(&self) -> HashMap<Resource, Vec<u64>> {
let mut footprints: HashMap<Resource, Vec<u64>> = HashMap::new();
for event in &self.events {
for access in &event.resource_accesses {
footprints
.entry(access.resource.clone())
.or_insert_with(Vec::new)
.push(event.id);
}
}
footprints
}
}
const TRACE_CONFORMANCE_CASES: &[ConformanceCase] = &[
ConformanceCase {
id: "TRACE-CAUSALITY-01",
section: "causality.ordering",
level: RequirementLevel::Must,
category: TestCategory::CausalityOrdering,
description: "Events maintain happens-before partial order",
},
ConformanceCase {
id: "TRACE-CAUSALITY-02",
section: "causality.timestamps",
level: RequirementLevel::Must,
category: TestCategory::CausalityOrdering,
description: "Logical timestamps respect causal dependencies",
},
ConformanceCase {
id: "TRACE-CAUSALITY-03",
section: "causality.backward",
level: RequirementLevel::Must,
category: TestCategory::CausalityOrdering,
description: "No backward causation in trace events",
},
ConformanceCase {
id: "TRACE-CAUSALITY-04",
section: "causality.concurrent",
level: RequirementLevel::Must,
category: TestCategory::CausalityOrdering,
description: "Concurrent events properly identified",
},
ConformanceCase {
id: "TRACE-DPOR-01",
section: "dpor.races",
level: RequirementLevel::Must,
category: TestCategory::DporCoverage,
description: "All races detected between dependent events",
},
ConformanceCase {
id: "TRACE-DPOR-02",
section: "dpor.backtrack",
level: RequirementLevel::Must,
category: TestCategory::DporCoverage,
description: "Backtrack points identify alternative schedules",
},
ConformanceCase {
id: "TRACE-DPOR-03",
section: "dpor.sleep",
level: RequirementLevel::Must,
category: TestCategory::DporCoverage,
description: "Sleep sets reduce redundant exploration",
},
ConformanceCase {
id: "TRACE-DPOR-04",
section: "dpor.semantics",
level: RequirementLevel::Must,
category: TestCategory::DporCoverage,
description: "Partial order reduction preserves program semantics",
},
ConformanceCase {
id: "TRACE-INDEPENDENCE-01",
section: "independence.reordering",
level: RequirementLevel::Must,
category: TestCategory::EventIndependence,
description: "Independent events can be reordered without semantic change",
},
ConformanceCase {
id: "TRACE-INDEPENDENCE-02",
section: "independence.conflicts",
level: RequirementLevel::Must,
category: TestCategory::EventIndependence,
description: "Resource conflicts correctly identified",
},
ConformanceCase {
id: "TRACE-INDEPENDENCE-03",
section: "independence.analysis",
level: RequirementLevel::Must,
category: TestCategory::EventIndependence,
description: "Dependency analysis accurate for all event types",
},
];
#[cfg(test)]
mod tests {
use super::*;
fn run_conformance_test(case: &ConformanceCase) -> TestResult {
match case.id {
"TRACE-CAUSALITY-01" => test_happens_before_ordering(),
"TRACE-CAUSALITY-02" => test_logical_timestamp_consistency(),
"TRACE-CAUSALITY-03" => test_no_backward_causation(),
"TRACE-CAUSALITY-04" => test_concurrent_event_identification(),
"TRACE-DPOR-01" => test_race_detection(),
"TRACE-DPOR-02" => test_backtrack_point_generation(),
"TRACE-DPOR-03" => test_sleep_set_optimization(),
"TRACE-DPOR-04" => test_semantic_preservation(),
"TRACE-INDEPENDENCE-01" => test_independent_event_reordering(),
"TRACE-INDEPENDENCE-02" => test_resource_conflict_detection(),
"TRACE-INDEPENDENCE-03" => test_dependency_analysis_accuracy(),
_ => TestResult::Skipped {
reason: "No registered trace conformance case for this id".to_string(),
},
}
}
fn create_sample_trace() -> MockTraceAnalyzer {
let mut analyzer = MockTraceAnalyzer::new();
analyzer
.add_event(TraceEvent {
id: 1,
task_id: 1,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::ChannelSend {
channel_id: 1,
message_id: 1,
},
resource_accesses: vec![ResourceAccess {
resource: Resource::Channel(1),
access_type: AccessType::Write,
}],
sequence_number: 1,
virtual_time: Duration::from_millis(10),
})
.unwrap();
analyzer
.add_event(TraceEvent {
id: 2,
task_id: 2,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::ChannelReceive {
channel_id: 1,
message_id: 1,
},
resource_accesses: vec![ResourceAccess {
resource: Resource::Channel(1),
access_type: AccessType::Read,
}],
sequence_number: 2,
virtual_time: Duration::from_millis(20),
})
.unwrap();
analyzer
.add_event(TraceEvent {
id: 3,
task_id: 3,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::MutexAcquire { mutex_id: 1 },
resource_accesses: vec![ResourceAccess {
resource: Resource::Mutex(1),
access_type: AccessType::Write,
}],
sequence_number: 3,
virtual_time: Duration::from_millis(30),
})
.unwrap();
analyzer
}
fn test_happens_before_ordering() -> TestResult {
let analyzer = create_sample_trace();
match analyzer.verify_topological_ordering() {
Ok(()) => TestResult::Pass,
Err(violations) => TestResult::Fail {
reason: format!("Causality violations detected: {:?}", violations),
},
}
}
fn test_logical_timestamp_consistency() -> TestResult {
let analyzer = create_sample_trace();
let mut task_clocks: HashMap<u64, u64> = HashMap::new();
for event in &analyzer.events {
if let Some(&prev_clock) = task_clocks.get(&event.task_id) {
if event.logical_time.clock <= prev_clock {
return TestResult::Fail {
reason: format!(
"Non-monotonic logical time in task {}: {} <= {}",
event.task_id, event.logical_time.clock, prev_clock
),
};
}
}
task_clocks.insert(event.task_id, event.logical_time.clock);
}
TestResult::Pass
}
fn test_no_backward_causation() -> TestResult {
let analyzer = create_sample_trace();
let violations = analyzer.detect_backward_causation();
if violations.is_empty() {
TestResult::Pass
} else {
TestResult::Fail {
reason: format!("Backward causation detected: {:?}", violations),
}
}
}
fn test_concurrent_event_identification() -> TestResult {
let analyzer = create_sample_trace();
let event1 = &analyzer.events[0]; let event3 = &analyzer.events[2];
if analyzer.are_independent(event1, event3) {
TestResult::Pass
} else {
TestResult::Fail {
reason: "Independent events incorrectly marked as dependent".to_string(),
}
}
}
fn test_race_detection() -> TestResult {
let analyzer = create_sample_trace();
let dpor_analysis = analyzer.analyze_dpor();
let has_channel_race = dpor_analysis
.races
.iter()
.any(|race| matches!(race.dependency_type, DependencyType::WriteRead));
if has_channel_race {
TestResult::Pass
} else {
TestResult::Fail {
reason: "Expected race between channel send/receive not detected".to_string(),
}
}
}
fn test_backtrack_point_generation() -> TestResult {
let analyzer = create_sample_trace();
let dpor_analysis = analyzer.analyze_dpor();
if dpor_analysis.backtrack_points.len() >= dpor_analysis.races.len() {
TestResult::Pass
} else {
TestResult::Fail {
reason: format!(
"Insufficient backtrack points: {} for {} races",
dpor_analysis.backtrack_points.len(),
dpor_analysis.races.len()
),
}
}
}
fn test_sleep_set_optimization() -> TestResult {
let analyzer = create_sample_trace();
let dpor_analysis = analyzer.analyze_dpor();
if !dpor_analysis.sleep_set.is_empty() {
TestResult::Pass
} else {
TestResult::Fail {
reason: "Sleep set optimization not working - empty sleep set".to_string(),
}
}
}
fn test_semantic_preservation() -> TestResult {
let analyzer = create_sample_trace();
let dpor_analysis = analyzer.analyze_dpor();
let original_state = analyzer.compute_final_state();
for schedule in &dpor_analysis.explored_schedules {
if schedule.final_state.keys().collect::<HashSet<_>>()
!= original_state.keys().collect::<HashSet<_>>()
{
return TestResult::Fail {
reason: "Alternative schedule changes resource set".to_string(),
};
}
}
TestResult::Pass
}
fn test_independent_event_reordering() -> TestResult {
let analyzer = create_sample_trace();
match analyzer.verify_event_independence() {
Ok(()) => TestResult::Pass,
Err(e) => TestResult::Fail { reason: e },
}
}
fn test_resource_conflict_detection() -> TestResult {
let analyzer = create_sample_trace();
let send_event = &analyzer.events[0];
let recv_event = &analyzer.events[1];
if let Some(dep_type) = analyzer.check_dependency(send_event, recv_event) {
match dep_type {
DependencyType::WriteRead => TestResult::Pass,
_ => TestResult::Fail {
reason: format!("Incorrect dependency type: {:?}", dep_type),
},
}
} else {
TestResult::Fail {
reason: "Channel send/receive dependency not detected".to_string(),
}
}
}
fn test_dependency_analysis_accuracy() -> TestResult {
let analyzer = create_sample_trace();
let footprints = analyzer.analyze_resource_footprints();
if let Some(channel_events) = footprints.get(&Resource::Channel(1)) {
if channel_events.len() == 2 {
TestResult::Pass
} else {
TestResult::Fail {
reason: format!(
"Incorrect channel footprint: {} events",
channel_events.len()
),
}
}
} else {
TestResult::Fail {
reason: "Channel resource footprint not found".to_string(),
}
}
}
#[test]
fn trace_conformance_full_suite() {
let mut pass_count = 0;
let mut fail_count = 0;
let mut skip_count = 0;
for case in TRACE_CONFORMANCE_CASES {
let result = run_conformance_test(case);
match result {
TestResult::Pass => {
pass_count += 1;
println!("✓ {}: {}", case.id, case.description);
}
TestResult::Fail { reason } => {
fail_count += 1;
println!("✗ {}: {} - {}", case.id, case.description, reason);
}
TestResult::Skipped { reason } => {
skip_count += 1;
println!("⚠ {}: {} - {}", case.id, case.description, reason);
}
}
}
let total = pass_count + fail_count + skip_count;
println!(
"\nTrace Conformance Results: {}/{} passed, {} failed, {} skipped",
pass_count, total, fail_count, skip_count
);
let must_cases: Vec<_> = TRACE_CONFORMANCE_CASES
.iter()
.filter(|c| c.level == RequirementLevel::Must)
.collect();
let mut must_failures = 0;
for case in &must_cases {
if let TestResult::Fail { .. } = run_conformance_test(case) {
must_failures += 1;
}
}
assert_eq!(
must_failures, 0,
"{} MUST requirements failed",
must_failures
);
}
proptest! {
#[test]
fn prop_causality_ordering(
task_count in 1usize..10,
event_count in 1usize..20
) {
let mut analyzer = MockTraceAnalyzer::new();
for i in 0..event_count {
let event = TraceEvent {
id: i as u64,
task_id: (i % task_count) as u64,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::TaskComplete { result: TaskResult::Success },
resource_accesses: vec![],
sequence_number: i as u64,
virtual_time: Duration::from_millis(i as u64 * 10),
};
analyzer.add_event(event).unwrap();
}
prop_assert!(analyzer.verify_topological_ordering().is_ok());
}
#[test]
fn prop_race_detection_completeness(
channel_id in 0u64..5,
message_count in 1usize..10
) {
let mut analyzer = MockTraceAnalyzer::new();
for i in 0..message_count {
analyzer.add_event(TraceEvent {
id: i as u64 * 2,
task_id: 1,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::ChannelSend {
channel_id,
message_id: i as u64
},
resource_accesses: vec![ResourceAccess {
resource: Resource::Channel(channel_id),
access_type: AccessType::Write,
}],
sequence_number: i as u64 * 2,
virtual_time: Duration::from_millis(i as u64 * 10),
}).unwrap();
analyzer.add_event(TraceEvent {
id: i as u64 * 2 + 1,
task_id: 2,
logical_time: LogicalTime { clock: 0 },
event_type: TraceEventType::ChannelReceive {
channel_id,
message_id: i as u64
},
resource_accesses: vec![ResourceAccess {
resource: Resource::Channel(channel_id),
access_type: AccessType::Read,
}],
sequence_number: i as u64 * 2 + 1,
virtual_time: Duration::from_millis(i as u64 * 10 + 5),
}).unwrap();
}
let dpor_analysis = analyzer.analyze_dpor();
prop_assert!(dpor_analysis.races.len() >= message_count);
}
#[test]
fn prop_independence_symmetry(
task_a in 0u64..5,
task_b in 0u64..5,
resource_a in 0u64..5,
resource_b in 0u64..5
) {
prop_assume!(task_a != task_b);
prop_assume!(resource_a != resource_b);
let event_a = TraceEvent {
id: 1,
task_id: task_a,
logical_time: LogicalTime { clock: 1 },
event_type: TraceEventType::TaskComplete { result: TaskResult::Success },
resource_accesses: vec![ResourceAccess {
resource: Resource::Memory(resource_a),
access_type: AccessType::Read,
}],
sequence_number: 1,
virtual_time: Duration::from_millis(10),
};
let event_b = TraceEvent {
id: 2,
task_id: task_b,
logical_time: LogicalTime { clock: 1 },
event_type: TraceEventType::TaskComplete { result: TaskResult::Success },
resource_accesses: vec![ResourceAccess {
resource: Resource::Memory(resource_b),
access_type: AccessType::Read,
}],
sequence_number: 2,
virtual_time: Duration::from_millis(20),
};
let analyzer = MockTraceAnalyzer::new();
let a_independent_of_b = analyzer.are_independent(&event_a, &event_b);
let b_independent_of_a = analyzer.are_independent(&event_b, &event_a);
prop_assert_eq!(a_independent_of_b, b_independent_of_a);
}
}
}