-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid memory leaks #46
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ name = "libtest-mimic" | |
version = "0.7.3" | ||
authors = ["Lukas Kalbertodt <[email protected]>"] | ||
edition = "2021" | ||
rust-version = "1.60" | ||
rust-version = "1.63" | ||
|
||
description = """ | ||
Write your own test harness that looks and behaves like the built-in test \ | ||
|
@@ -20,7 +20,6 @@ exclude = [".github"] | |
|
||
[dependencies] | ||
clap = { version = "4.0.8", features = ["derive"] } | ||
threadpool = "1.8.1" | ||
termcolor = "1.0.5" | ||
escape8259 = "0.5.2" | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -71,18 +71,22 @@ | |||||
|
||||||
#![forbid(unsafe_code)] | ||||||
|
||||||
use std::{borrow::Cow, fmt, process::{self, ExitCode}, sync::mpsc, time::Instant}; | ||||||
use std::{ | ||||||
borrow::Cow, | ||||||
fmt, | ||||||
process::{self, ExitCode}, | ||||||
sync::mpsc, | ||||||
time::Instant, | ||||||
}; | ||||||
|
||||||
mod args; | ||||||
mod pool; | ||||||
mod printer; | ||||||
|
||||||
use printer::Printer; | ||||||
use threadpool::ThreadPool; | ||||||
|
||||||
pub use crate::args::{Arguments, ColorSetting, FormatSetting}; | ||||||
|
||||||
|
||||||
|
||||||
/// A single test or benchmark. | ||||||
/// | ||||||
/// The original `libtest` often calls benchmarks "tests", which is a bit | ||||||
|
@@ -143,8 +147,9 @@ impl Trial { | |||||
Err(failed) => Outcome::Failed(failed), | ||||||
Ok(_) if test_mode => Outcome::Passed, | ||||||
Ok(Some(measurement)) => Outcome::Measured(measurement), | ||||||
Ok(None) | ||||||
=> Outcome::Failed("bench runner returned `Ok(None)` in bench mode".into()), | ||||||
Ok(None) => { | ||||||
Outcome::Failed("bench runner returned `Ok(None)` in bench mode".into()) | ||||||
} | ||||||
}), | ||||||
info: TestInfo { | ||||||
name: name.into(), | ||||||
|
@@ -284,13 +289,11 @@ impl Failed { | |||||
impl<M: std::fmt::Display> From<M> for Failed { | ||||||
fn from(msg: M) -> Self { | ||||||
Self { | ||||||
msg: Some(msg.to_string()) | ||||||
msg: Some(msg.to_string()), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
|
||||||
|
||||||
/// The outcome of performing a test/benchmark. | ||||||
#[derive(Debug, Clone)] | ||||||
enum Outcome { | ||||||
|
@@ -473,15 +476,22 @@ pub fn run(args: &Arguments, mut tests: Vec<Trial>) -> Conclusion { | |||||
Outcome::Failed(failed) => { | ||||||
failed_tests.push((test, failed.msg)); | ||||||
conclusion.num_failed += 1; | ||||||
}, | ||||||
} | ||||||
Outcome::Ignored => conclusion.num_ignored += 1, | ||||||
Outcome::Measured(_) => conclusion.num_measured += 1, | ||||||
} | ||||||
}; | ||||||
|
||||||
// Execute all tests. | ||||||
let test_mode = !args.bench; | ||||||
if platform_defaults_to_one_thread() || args.test_threads == Some(1) { | ||||||
|
||||||
let num_threads = platform_defaults_to_one_thread() | ||||||
.then_some(1) | ||||||
.or(args.test_threads) | ||||||
.or_else(|| std::thread::available_parallelism().ok().map(Into::into)) | ||||||
.unwrap_or(1); | ||||||
|
||||||
if num_threads == 1 { | ||||||
// Run test sequentially in main thread | ||||||
for test in tests { | ||||||
// Print `test foo ...`, run the test, then print the outcome in | ||||||
|
@@ -496,28 +506,29 @@ pub fn run(args: &Arguments, mut tests: Vec<Trial>) -> Conclusion { | |||||
} | ||||||
} else { | ||||||
// Run test in thread pool. | ||||||
let pool = match args.test_threads { | ||||||
Some(num_threads) => ThreadPool::new(num_threads), | ||||||
None => ThreadPool::default() | ||||||
}; | ||||||
let num_tests = tests.len(); | ||||||
let (sender, receiver) = mpsc::channel(); | ||||||
|
||||||
let num_tests = tests.len(); | ||||||
for test in tests { | ||||||
let mut tasks: Vec<pool::BoxedTask> = Default::default(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this work?
Suggested change
|
||||||
|
||||||
for test in tests.into_iter() { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Should work? |
||||||
if args.is_ignored(&test) { | ||||||
sender.send((Outcome::Ignored, test.info)).unwrap(); | ||||||
} else { | ||||||
let sender = sender.clone(); | ||||||
pool.execute(move || { | ||||||
|
||||||
tasks.push(Box::new(move || { | ||||||
// It's fine to ignore the result of sending. If the | ||||||
// receiver has hung up, everything will wind down soon | ||||||
// anyway. | ||||||
let outcome = run_single(test.runner, test_mode); | ||||||
let _ = sender.send((outcome, test.info)); | ||||||
}); | ||||||
})); | ||||||
} | ||||||
} | ||||||
|
||||||
pool::scoped_run_tasks(tasks, num_threads); | ||||||
|
||||||
Comment on lines
+530
to
+531
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function is pretty short and only used in a single place (here), so why not inline it? Similar to what @hanna-kruppe said above, I think in this case we lose more by obscuring what actually happens compared to what we gain by black box abstraction. Further, using Aaand in that case we can also get rid of the first loop, since we already have a vector of "tasks" (the trials) we can iterate over. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the way you inlined it in d194ff9 has a bug: it spawns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh no There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooof, that's awkward. Thanks for catching it! Should be fixed in d6ac84f. Would you mind reviewing that, to avoid me publishing another avoidable bug? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left a comment on that commit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
for (outcome, test_info) in receiver.iter().take(num_tests) { | ||||||
// In multithreaded mode, we do only print the start of the line | ||||||
// after the test ran, as otherwise it would lead to terribly | ||||||
|
@@ -552,7 +563,8 @@ fn run_single(runner: Box<dyn FnOnce(bool) -> Outcome + Send>, test_mode: bool) | |||||
// The `panic` information is just an `Any` object representing the | ||||||
// value the panic was invoked with. For most panics (which use | ||||||
// `panic!` like `println!`), this is either `&str` or `String`. | ||||||
let payload = e.downcast_ref::<String>() | ||||||
let payload = e | ||||||
.downcast_ref::<String>() | ||||||
.map(|s| s.as_str()) | ||||||
.or(e.downcast_ref::<&str>().map(|s| *s)); | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use std::{sync, thread}; | ||
|
||
pub(crate) type Task = dyn FnOnce() + Send; | ||
pub(crate) type BoxedTask = Box<Task>; | ||
Comment on lines
+3
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think these type aliases pull their weight. With my suggestion from a previous comment, they are only used in one spot. So please remove the aliases and just write out the type in the |
||
|
||
pub(crate) fn scoped_run_tasks( | ||
tasks: Vec<BoxedTask>, | ||
num_threads: usize, | ||
) { | ||
if num_threads < 2 { | ||
// There is another code path for num_threads == 1 running entirely in the main thread. | ||
panic!("`run_on_scoped_pool` may not be called with `num_threads` less than 2"); | ||
} | ||
|
||
let sync_iter = sync::Mutex::new(tasks.into_iter()); | ||
let next_task = || sync_iter.lock().unwrap().next(); | ||
|
||
thread::scope(|scope| { | ||
for _ in 0..num_threads { | ||
scope.spawn(|| { | ||
while let Some(task) = next_task() { | ||
task(); | ||
} | ||
}); | ||
} | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move the
let num_tests
line to its previous position (right above the loop) to reduce the diff size?