use super::*;
use crate::snippets::session::SessionSpec;
use crate::snippets::types::{SnippetMetadata, SourceOrigin};
use crate::snippets::validators::SnippetValidator;
use std::sync::{Arc, Condvar};
use std::time::Duration;
const PAIR: usize = 2;
const RENDEZVOUS_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Default)]
struct Rendezvous {
in_flight: usize,
peak: usize,
}
struct RendezvousValidator {
state: Arc<(Mutex<Rendezvous>, Condvar)>,
exclusive: bool,
}
impl SnippetValidator for RendezvousValidator {
fn language(&self) -> crate::snippets::types::Language {
crate::snippets::types::Language::Rust
}
fn is_available(&self) -> bool {
true
}
fn validate(
&self,
_snippet: &Snippet,
_level: ValidationLevel,
_timeout_secs: u64,
) -> Result<(SnippetStatus, Option<String>)> {
self.rendezvous()
}
fn validate_in_session(
&self,
_snippet: &Snippet,
_level: ValidationLevel,
_timeout_secs: u64,
_session: Option<&crate::snippets::session::ValidationSession>,
) -> Result<(SnippetStatus, Option<String>)> {
self.rendezvous()
}
fn max_level(&self) -> ValidationLevel {
ValidationLevel::Run
}
fn requires_session_exclusivity(&self) -> bool {
self.exclusive
}
}
impl RendezvousValidator {
fn rendezvous(&self) -> Result<(SnippetStatus, Option<String>)> {
let (lock, condvar) = &*self.state;
let mut state = lock.lock().expect("rendezvous");
state.in_flight += 1;
state.peak = state.peak.max(state.in_flight);
condvar.notify_all();
while state.in_flight < PAIR {
let (next, timeout) = condvar
.wait_timeout(state, RENDEZVOUS_TIMEOUT)
.expect("rendezvous wait");
state = next;
if timeout.timed_out() {
break;
}
}
state.in_flight -= 1;
Ok((SnippetStatus::Pass, None))
}
}
fn snippet() -> Snippet {
Snippet {
id: None,
path: "example.md".into(),
language: crate::snippets::types::Language::Rust,
title: None,
code: "fn main() {}".into(),
start_line: 1,
block_index: 0,
annotation: None,
metadata: SnippetMetadata {
target: Some("shared".into()),
..SnippetMetadata::default()
},
source_origin: SourceOrigin {
path: "example.md".into(),
line: 1,
block_index: 0,
},
}
}
fn peak_concurrency(exclusive: bool) -> usize {
let state = Arc::new((Mutex::new(Rendezvous::default()), Condvar::new()));
let mut registry = ValidatorRegistry::new();
registry.register(Box::new(RendezvousValidator {
state: Arc::clone(&state),
exclusive,
}));
let directory = tempfile::tempdir().expect("session directory");
let config = RunnerConfig {
level: ValidationLevel::Syntax,
parallelism: PAIR,
cache_dir: None,
sessions: HashMap::from([(
"shared".to_string(),
SessionSpec {
language: crate::snippets::types::Language::Rust,
working_directory: directory.path().into(),
manifest: None,
before: Vec::new(),
env: Default::default(),
include_paths: Vec::new(),
rust_features: Vec::new(),
rust_dependencies: Default::default(),
},
)]),
..RunnerConfig::default()
};
let summary = run_validation(&[snippet(), snippet()], ®istry, &config).expect("validation completes");
assert_eq!(summary.passed, PAIR);
state.0.lock().expect("rendezvous").peak
}
#[test]
fn a_validator_without_shared_session_state_runs_concurrently() {
assert_eq!(
peak_concurrency(false),
PAIR,
"snippets sharing a session must run concurrently when the validator declares no shared state"
);
}
#[test]
fn a_validator_with_shared_session_state_is_still_serialized() {
assert_eq!(
peak_concurrency(true),
1,
"a validator that shares fixed-name files in the session workspace must not run concurrently"
);
}