Skip to content

Commit 104b860

Browse files
authored
feat(harness): Allow tests access to send events (#122)
`TestContext` exposes `notify(Event)` and `test_name` / `elapsed_s` to create an `Event`
2 parents f9a851a + 1b875d3 commit 104b860

5 files changed

Lines changed: 116 additions & 135 deletions

File tree

crates/libtest2-harness/src/context.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
pub(crate) use crate::*;
22

3-
#[derive(Debug)]
43
pub struct TestContext {
5-
mode: RunMode,
6-
run_ignored: bool,
4+
pub(crate) start: std::time::Instant,
5+
pub(crate) mode: RunMode,
6+
pub(crate) run_ignored: bool,
7+
pub(crate) notifier: notify::ArcNotifier,
8+
pub(crate) test_name: String,
79
}
810

911
impl TestContext {
@@ -26,21 +28,30 @@ impl TestContext {
2628
pub fn current_mode(&self) -> RunMode {
2729
self.mode
2830
}
29-
}
3031

31-
impl TestContext {
32-
pub(crate) fn new() -> Self {
33-
Self {
34-
mode: Default::default(),
35-
run_ignored: false,
36-
}
32+
pub fn notify(&self, event: notify::Event) -> std::io::Result<()> {
33+
self.notifier().notify(event)
34+
}
35+
36+
pub fn elapased_s(&self) -> notify::Elapsed {
37+
notify::Elapsed(self.start.elapsed())
38+
}
39+
40+
pub fn test_name(&self) -> &str {
41+
&self.test_name
3742
}
3843

39-
pub(crate) fn set_mode(&mut self, mode: RunMode) {
40-
self.mode = mode;
44+
pub(crate) fn notifier(&self) -> &notify::ArcNotifier {
45+
&self.notifier
4146
}
4247

43-
pub(crate) fn set_run_ignored(&mut self, yes: bool) {
44-
self.run_ignored = yes;
48+
pub(crate) fn clone(&self) -> Self {
49+
Self {
50+
start: self.start,
51+
mode: self.mode,
52+
run_ignored: self.run_ignored,
53+
notifier: self.notifier.clone(),
54+
test_name: self.test_name.clone(),
55+
}
4556
}
4657
}

crates/libtest2-harness/src/harness.rs

Lines changed: 51 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ impl Harness<StateArgs> {
8383
pub struct StateParsed {
8484
start: std::time::Instant,
8585
opts: libtest_lexarg::TestOpts,
86-
notifier: Box<dyn notify::Notifier>,
86+
notifier: notify::ArcNotifier,
8787
}
8888
impl HarnessState for StateParsed {}
8989
impl sealed::_HarnessState_is_Sealed for StateParsed {}
9090

9191
impl Harness<StateParsed> {
9292
pub fn discover(
93-
mut self,
93+
self,
9494
cases: impl IntoIterator<Item = impl Case + 'static>,
9595
) -> std::io::Result<Harness<StateDiscovered>> {
9696
self.state.notifier.notify(
@@ -144,22 +144,22 @@ impl Harness<StateParsed> {
144144
pub struct StateDiscovered {
145145
start: std::time::Instant,
146146
opts: libtest_lexarg::TestOpts,
147-
notifier: Box<dyn notify::Notifier>,
147+
notifier: notify::ArcNotifier,
148148
cases: Vec<Box<dyn Case>>,
149149
}
150150
impl HarnessState for StateDiscovered {}
151151
impl sealed::_HarnessState_is_Sealed for StateDiscovered {}
152152

153153
impl Harness<StateDiscovered> {
154-
pub fn run(mut self) -> std::io::Result<bool> {
154+
pub fn run(self) -> std::io::Result<bool> {
155155
if self.state.opts.list {
156156
Ok(true)
157157
} else {
158158
run(
159159
&self.state.start,
160160
&self.state.opts,
161161
self.state.cases,
162-
self.state.notifier.as_mut(),
162+
self.state.notifier,
163163
)
164164
}
165165
}
@@ -252,16 +252,16 @@ fn parse<'p>(parser: &mut cli::Parser<'p>) -> Result<libtest_lexarg::TestOpts, c
252252
Ok(opts)
253253
}
254254

255-
fn notifier(opts: &libtest_lexarg::TestOpts) -> Box<dyn notify::Notifier> {
255+
fn notifier(opts: &libtest_lexarg::TestOpts) -> notify::ArcNotifier {
256256
#[cfg(feature = "color")]
257257
let stdout = anstream::stdout();
258258
#[cfg(not(feature = "color"))]
259259
let stdout = std::io::stdout();
260260
match opts.format {
261-
OutputFormat::Json => Box::new(notify::JsonNotifier::new(stdout)),
262-
_ if opts.list => Box::new(notify::TerseListNotifier::new(stdout)),
263-
OutputFormat::Pretty => Box::new(notify::PrettyRunNotifier::new(stdout)),
264-
OutputFormat::Terse => Box::new(notify::TerseRunNotifier::new(stdout)),
261+
OutputFormat::Json => notify::ArcNotifier::new(notify::JsonNotifier::new(stdout)),
262+
_ if opts.list => notify::ArcNotifier::new(notify::TerseListNotifier::new(stdout)),
263+
OutputFormat::Pretty => notify::ArcNotifier::new(notify::PrettyRunNotifier::new(stdout)),
264+
OutputFormat::Terse => notify::ArcNotifier::new(notify::TerseRunNotifier::new(stdout)),
265265
}
266266
}
267267

@@ -292,7 +292,7 @@ fn run(
292292
start: &std::time::Instant,
293293
opts: &libtest_lexarg::TestOpts,
294294
cases: Vec<Box<dyn Case>>,
295-
notifier: &mut dyn notify::Notifier,
295+
notifier: notify::ArcNotifier,
296296
) -> std::io::Result<bool> {
297297
notifier.notify(
298298
notify::event::RunStart {
@@ -316,7 +316,6 @@ fn run(
316316

317317
let threads = opts.test_threads.map(|t| t.get()).unwrap_or(1);
318318

319-
let mut context = TestContext::new();
320319
let run_ignored = match opts.run_ignored {
321320
libtest_lexarg::RunIgnored::Yes | libtest_lexarg::RunIgnored::Only => true,
322321
libtest_lexarg::RunIgnored::No => false,
@@ -331,9 +330,13 @@ fn run(
331330
(false, true) => RunMode::Bench,
332331
(false, false) => unreachable!("libtest-lexarg` should always ensure at least one is set"),
333332
};
334-
context.set_mode(mode);
335-
context.set_run_ignored(run_ignored);
336-
let context = std::sync::Arc::new(context);
333+
let context = TestContext {
334+
start: *start,
335+
mode,
336+
run_ignored,
337+
notifier,
338+
test_name: String::new(),
339+
};
337340

338341
let mut success = true;
339342

@@ -345,88 +348,49 @@ fn run(
345348
.partition::<Vec<_>, _>(|c| c.exclusive(&context))
346349
};
347350
if !concurrent_cases.is_empty() {
348-
notifier.threaded(true);
349-
struct RunningTest {
350-
join_handle: std::thread::JoinHandle<()>,
351-
}
352-
353-
impl RunningTest {
354-
fn join(
355-
self,
356-
start: &std::time::Instant,
357-
event: &notify::event::CaseComplete,
358-
notifier: &mut dyn notify::Notifier,
359-
) -> std::io::Result<()> {
360-
if self.join_handle.join().is_err() {
361-
let kind = notify::MessageKind::Error;
362-
let message = Some("panicked after reporting success".to_owned());
363-
notifier.notify(
364-
notify::event::CaseMessage {
365-
name: event.name.clone(),
366-
kind,
367-
message,
368-
elapsed_s: Some(notify::Elapsed(start.elapsed())),
369-
}
370-
.into(),
371-
)?;
372-
}
373-
Ok(())
374-
}
375-
}
351+
context.notifier().threaded(true);
376352

377353
// Use a deterministic hasher
378354
type TestMap = std::collections::HashMap<
379355
String,
380-
RunningTest,
356+
std::thread::JoinHandle<std::io::Result<bool>>,
381357
std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>,
382358
>;
383359

384360
let sync_success = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(success));
385-
let mut running_tests: TestMap = Default::default();
386-
let mut pending = 0;
387-
let (tx, rx) = std::sync::mpsc::channel::<notify::Event>();
361+
let mut running: TestMap = Default::default();
362+
let (tx, rx) = std::sync::mpsc::channel::<String>();
388363
let mut remaining = std::collections::VecDeque::from(concurrent_cases);
389-
while pending > 0 || !remaining.is_empty() {
390-
while pending < threads && !remaining.is_empty() {
364+
while !running.is_empty() || !remaining.is_empty() {
365+
while running.len() < threads && !remaining.is_empty() {
391366
let case = remaining.pop_front().unwrap();
367+
let case = std::sync::Arc::new(case);
392368
let name = case.name().to_owned();
393369

394370
let cfg = std::thread::Builder::new().name(name.clone());
395-
let start = *start;
396-
let tx = tx.clone();
397-
let case = std::sync::Arc::new(case);
398-
let case_fallback = case.clone();
399-
let context = context.clone();
400-
let context_fallback = context.clone();
401-
let sync_success = sync_success.clone();
402-
let sync_success_fallback = sync_success.clone();
371+
let thread_tx = tx.clone();
372+
let thread_case = case.clone();
373+
let mut thread_context = context.clone();
374+
thread_context.test_name = name.clone();
375+
let thread_sync_success = sync_success.clone();
403376
let join_handle = cfg.spawn(move || {
404-
let mut notifier = SenderNotifier { tx: tx.clone() };
405-
let case_success =
406-
run_case(&start, case.as_ref().as_ref(), &context, &mut notifier)
407-
.expect("`SenderNotifier` is infallible");
408-
if !case_success {
409-
sync_success.store(case_success, std::sync::atomic::Ordering::Relaxed);
377+
let status = run_case(thread_case.as_ref().as_ref(), &thread_context);
378+
if !matches!(status, Ok(true)) {
379+
thread_sync_success.store(false, std::sync::atomic::Ordering::Relaxed);
410380
}
381+
let _ = thread_tx.send(thread_case.name().to_owned());
382+
status
411383
});
412384
match join_handle {
413385
Ok(join_handle) => {
414-
running_tests.insert(name.clone(), RunningTest { join_handle });
415-
pending += 1;
386+
running.insert(name.clone(), join_handle);
416387
}
417388
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
418389
// `ErrorKind::WouldBlock` means hitting the thread limit on some
419390
// platforms, so run the test synchronously here instead.
420-
let case_success = run_case(
421-
&start,
422-
case_fallback.as_ref().as_ref(),
423-
&context_fallback,
424-
notifier,
425-
)
426-
.expect("`SenderNotifier` is infallible");
391+
let case_success = run_case(case.as_ref().as_ref(), &context)?;
427392
if !case_success {
428-
sync_success_fallback
429-
.store(case_success, std::sync::atomic::Ordering::Relaxed);
393+
sync_success.store(case_success, std::sync::atomic::Ordering::Relaxed);
430394
}
431395
}
432396
Err(e) => {
@@ -435,13 +399,9 @@ fn run(
435399
}
436400
}
437401

438-
let event = rx.recv().unwrap();
439-
if let notify::Event::CaseComplete(event) = &event {
440-
let running_test = running_tests.remove(&event.name).unwrap();
441-
running_test.join(start, event, notifier)?;
442-
pending -= 1;
443-
}
444-
notifier.notify(event)?;
402+
let test_name = rx.recv().unwrap();
403+
let running_test = running.remove(&test_name).unwrap();
404+
let _ = running_test.join();
445405
success &= sync_success.load(std::sync::atomic::Ordering::SeqCst);
446406
if !success && opts.fail_fast {
447407
break;
@@ -450,16 +410,16 @@ fn run(
450410
}
451411

452412
if !exclusive_cases.is_empty() {
453-
notifier.threaded(false);
413+
context.notifier().threaded(false);
454414
for case in exclusive_cases {
455-
success &= run_case(start, case.as_ref(), &context, notifier)?;
415+
success &= run_case(case.as_ref(), &context)?;
456416
if !success && opts.fail_fast {
457417
break;
458418
}
459419
}
460420
}
461421

462-
notifier.notify(
422+
context.notifier().notify(
463423
notify::event::RunComplete {
464424
elapsed_s: Some(notify::Elapsed(start.elapsed())),
465425
}
@@ -469,16 +429,11 @@ fn run(
469429
Ok(success)
470430
}
471431

472-
fn run_case(
473-
start: &std::time::Instant,
474-
case: &dyn Case,
475-
context: &TestContext,
476-
notifier: &mut dyn notify::Notifier,
477-
) -> std::io::Result<bool> {
478-
notifier.notify(
432+
fn run_case(case: &dyn Case, context: &TestContext) -> std::io::Result<bool> {
433+
context.notifier().notify(
479434
notify::event::CaseStart {
480435
name: case.name().to_owned(),
481-
elapsed_s: Some(notify::Elapsed(start.elapsed())),
436+
elapsed_s: Some(context.elapased_s()),
482437
}
483438
.into(),
484439
)?;
@@ -507,21 +462,21 @@ fn run_case(
507462
let kind = err.status();
508463
case_status = Some(kind);
509464
let message = err.cause().map(|c| c.to_string());
510-
notifier.notify(
465+
context.notifier().notify(
511466
notify::event::CaseMessage {
512467
name: case.name().to_owned(),
513468
kind,
514469
message,
515-
elapsed_s: Some(notify::Elapsed(start.elapsed())),
470+
elapsed_s: Some(context.elapased_s()),
516471
}
517472
.into(),
518473
)?;
519474
}
520475

521-
notifier.notify(
476+
context.notifier().notify(
522477
notify::event::CaseComplete {
523478
name: case.name().to_owned(),
524-
elapsed_s: Some(notify::Elapsed(start.elapsed())),
479+
elapsed_s: Some(context.elapased_s()),
525480
}
526481
.into(),
527482
)?;
@@ -537,16 +492,3 @@ fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
537492
// prevent this frame from being tail-call optimised away
538493
std::hint::black_box(result)
539494
}
540-
541-
#[derive(Clone, Debug)]
542-
struct SenderNotifier {
543-
tx: std::sync::mpsc::Sender<notify::Event>,
544-
}
545-
546-
impl notify::Notifier for SenderNotifier {
547-
fn notify(&mut self, event: notify::Event) -> std::io::Result<()> {
548-
// If the sender doesn't care, neither do we
549-
let _ = self.tx.send(event);
550-
Ok(())
551-
}
552-
}

0 commit comments

Comments
 (0)