iai_callgrind/bin_bench.rs
1use std::ffi::{OsStr, OsString};
2use std::fmt::Display;
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::net::SocketAddr;
6use std::path::{Path, PathBuf};
7use std::time::Duration;
8
9use derive_more::AsRef;
10use iai_callgrind_macros::IntoInner;
11
12use crate::{DelayKind, Stdin, Stdio, ValgrindTool, __internal};
13
14/// [low level api](`crate::binary_benchmark_group`) only: Create a new benchmark id
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct BenchmarkId(String);
17
18/// The configuration of a binary benchmark
19///
20/// The [`BinaryBenchmarkConfig`] can be specified at multiple levels and configures the benchmarks
21/// at this level. For example a [`BinaryBenchmarkConfig`] at (`main`)[`crate::main`] level
22/// configures all benchmarks. A configuration at [`group`](crate::binary_benchmark_group) level
23/// configures all benchmarks in this group inheriting the configuration of the `main` level and if
24/// not specified otherwise overwrites the values of the `main` configuration if the option is
25/// specified in both [`BinaryBenchmarkConfig`]s. The deeper levels are the
26/// (`#[binary_benchmark] attribute`)[`crate::binary_benchmark`], then `#[bench]` and the
27/// `#[benches]` attribute.
28///
29/// # Examples
30///
31/// ```rust
32/// # use iai_callgrind::binary_benchmark_group;
33/// # binary_benchmark_group!(name = some_group; benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
34/// use iai_callgrind::{BinaryBenchmarkConfig, main, Callgrind};
35///
36/// main!(
37/// config = BinaryBenchmarkConfig::default()
38/// .tool(Callgrind::with_args(["toggle-collect=something"]));
39/// binary_benchmark_groups = some_group
40/// );
41/// ```
42#[derive(Debug, Default, Clone, IntoInner, AsRef)]
43pub struct BinaryBenchmarkConfig(__internal::InternalBinaryBenchmarkConfig);
44
45/// [low level api](`crate::binary_benchmark_group`) only: The top level struct to add binary
46/// benchmarks to
47///
48/// This struct doesn't need to be instantiated by yourself. It is passed as mutable reference to
49/// the expression in `benchmarks`.
50///
51/// ```rust
52/// use iai_callgrind::binary_benchmark_group;
53///
54/// binary_benchmark_group!(
55/// name = my_group;
56/// benchmarks = |_group: &mut BinaryBenchmarkGroup| {
57/// // Access the BinaryBenchmarkGroup with the identifier `group` to add benchmarks to the
58/// // group.
59/// //
60/// // group.binary_benchmark(/* BinaryBenchmark::new(...) */);
61/// }
62/// );
63/// ```
64#[derive(Debug, Default, PartialEq, Clone)]
65pub struct BinaryBenchmarkGroup {
66 /// All [`BinaryBenchmark`]s
67 pub binary_benchmarks: Vec<BinaryBenchmark>,
68}
69
70/// [low level api](`crate::binary_benchmark_group`) only: This struct mirrors the `#[bench]` and
71/// `#[benches]` attribute of a [`crate::binary_benchmark`]
72#[derive(Debug, Clone)]
73pub struct Bench {
74 /// The [`BenchmarkId`] used to uniquely identify this benchmark within a [`BinaryBenchmark`]
75 pub id: BenchmarkId,
76 /// All [`Command`]s
77 pub commands: Vec<Command>,
78 /// An optional [`BinaryBenchmarkConfig`]
79 ///
80 /// This field stores the internal representation of the [`BinaryBenchmarkConfig`]. Use
81 /// `BinaryBenchmarkConfig::into` to generate the internal configuration from a
82 /// [`BinaryBenchmarkConfig`]
83 pub config: Option<__internal::InternalBinaryBenchmarkConfig>,
84 /// The `setup` function to be executed before the [`Command`] is executed
85 pub setup: Option<fn()>,
86 /// The `teardown` function to be executed after the [`Command`] is executed
87 pub teardown: Option<fn()>,
88}
89
90/// [low level api](`crate::binary_benchmark_group`) only: Mirror the [`crate::binary_benchmark`]
91/// attribute
92///
93/// A `BinaryBenchmark` can be created in two ways. Either with [`BinaryBenchmark::new`]. Or via the
94/// [`crate::binary_benchmark_attribute`] macro used with a function annotated with the
95/// [`crate::binary_benchmark`] attribute. So, you can start with the high-level api using the
96/// attribute and then go on in the low-level api.
97///
98/// # Examples
99///
100/// For examples using [`BinaryBenchmark::new`], see there. Here's an example using the
101/// [`crate::binary_benchmark_attribute`]
102///
103/// ```rust
104/// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
105/// use iai_callgrind::{
106/// binary_benchmark, binary_benchmark_group, binary_benchmark_attribute, Bench
107/// };
108///
109/// #[binary_benchmark]
110/// #[bench::foo("foo")]
111/// fn bench_binary(arg: &str) -> iai_callgrind::Command {
112/// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-foo"))
113/// .arg(arg)
114/// .build()
115/// }
116///
117/// binary_benchmark_group!(
118/// name = my_group;
119/// benchmarks = |group: &mut BinaryBenchmarkGroup| {
120/// let mut binary_benchmark = binary_benchmark_attribute!(bench_binary);
121///
122/// // Continue and add another `Bench` to the `BinaryBenchmark`
123/// binary_benchmark.bench(Bench::new("bar")
124/// .command(iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-foo"))
125/// .arg("bar")
126/// )
127/// );
128///
129/// // Finally, add the `BinaryBenchmark` to the group
130/// group
131/// .binary_benchmark(binary_benchmark);
132/// }
133/// );
134/// # fn main() {}
135/// ```
136#[derive(Debug, Clone)]
137pub struct BinaryBenchmark {
138 /// An id which has to be unique within the same [`BinaryBenchmarkGroup`]
139 ///
140 /// In the high-level api this is the name of the function which is annotated by
141 /// [`crate::binary_benchmark`]
142 pub id: BenchmarkId,
143 /// An optional [`BinaryBenchmarkConfig`] which is applied to all [`Command`]s within this
144 /// [`BinaryBenchmark`]
145 pub config: Option<__internal::InternalBinaryBenchmarkConfig>,
146 /// All [`Bench`]es which were added to this [`BinaryBenchmark`]
147 pub benches: Vec<Bench>,
148 /// The default `setup` function for all [`Bench`]es within this [`BinaryBenchmark`]. It can be
149 /// overwritten in a [`Bench`]
150 pub setup: Option<fn()>,
151 /// The default `teardown` function for all [`Bench`]es within this [`BinaryBenchmark`]. It can
152 /// be overwritten in a [`Bench`]
153 pub teardown: Option<fn()>,
154}
155
156/// Provide the [`Command`] to be benchmarked
157///
158/// `Command` is a builder for the binary which is going to be benchmarked providing fine-grained
159/// control over how the `Command` for the valgrind benchmark should be executed.
160///
161/// The default configuration is created with [`Command::new`] providing a path to an executable.
162/// Adding a crate's binary is usually done with `env!("CARGO_BIN_EXE_<name>")` where `<name>` is
163/// the name of the binary. The builder methods allow the configuration to be changed prior to
164/// [`Command::build`]. The [`Command`] can be reused to build multiple processes.
165///
166/// # Examples
167///
168/// Suppose your crate's binary is called `my-echo`:
169///
170/// ```rust
171/// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
172/// use iai_callgrind::Command;
173/// let command = Command::new(env!("CARGO_BIN_EXE_my-echo"));
174/// ```
175///
176/// However, an iai-callgrind benchmark is not limited to a crate's binaries, it can be any
177/// executable in the `$PATH`, or an absolute path to a binary installed on your system. The
178/// following will create a `Command` for the system's `echo` from the `$PATH`:
179///
180/// ```rust
181/// use iai_callgrind::Command;
182/// let command = Command::new("echo");
183/// ```
184#[derive(Debug, Default, Clone, PartialEq, IntoInner, AsRef)]
185pub struct Command(__internal::InternalCommand);
186
187/// Provide the [`crate::Delay`] to specify the event for [`crate::Command`] execution start.
188///
189/// The default configuration is created with [`Delay::new`] providing a [`crate::DelayKind`] to
190/// specify the event type and parameters for the `Delay`.
191///
192/// Additionally, the `Delay` can be created using `from*()` methods.
193/// - [`Delay::from(duration)`](Delay::from)
194/// - [`Delay::from_tcp_socket(addr)`](Delay::from_tcp_socket)
195/// - [`Delay::from_udp_request(addr, request)`](Delay::from_udp_request)
196/// - [`Delay::from_path(path)`](Delay::from_path)
197///
198/// # Examples
199///
200/// Suppose your command needs to start 60 seconds after the benchmark started:
201///
202/// ```rust
203/// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
204/// use std::time::Duration;
205///
206/// use iai_callgrind::{Command, Delay, DelayKind};
207///
208/// let command = Command::new(env!("CARGO_BIN_EXE_my-echo")).delay(Delay::new(
209/// DelayKind::DurationElapse(Duration::from_secs(60)),
210/// ));
211///
212/// let command_from =
213/// Command::new(env!("CARGO_BIN_EXE_my-echo")).delay(Delay::from(Duration::from_secs(60)));
214///
215/// let command_duration =
216/// Command::new(env!("CARGO_BIN_EXE_my-echo")).delay(Duration::from_secs(60));
217/// ```
218///
219/// However, an iai-callgrind [`Delay`] is not limited to a duration, it can be any
220/// path creation event, a successful TCP connect or as well a received UDP response.
221///
222/// ```rust
223/// use iai_callgrind::{Command, Delay, DelayKind};
224///
225/// let command = Command::new("echo").delay(Delay::new(DelayKind::PathExists(
226/// "/your/path/to/wait/for".into(),
227/// )));
228///
229/// let command_from = Command::new("echo").delay(Delay::from_path("/your/path/to/wait/for"));
230/// ```
231///
232/// ```rust
233/// use std::net::SocketAddr;
234/// use std::time::Duration;
235///
236/// use iai_callgrind::{Command, Delay, DelayKind};
237///
238/// let command = Command::new("echo").delay(
239/// Delay::new(DelayKind::TcpConnect(
240/// "127.0.0.1:31000".parse::<SocketAddr>().unwrap(),
241/// ))
242/// .timeout(Duration::from_secs(3))
243/// .poll(Duration::from_millis(50)),
244/// );
245///
246/// let command_from = Command::new("echo").delay(
247/// Delay::from_tcp_socket("127.0.0.1:31000".parse::<SocketAddr>().unwrap())
248/// .timeout(Duration::from_secs(3))
249/// .poll(Duration::from_millis(50)),
250/// );
251/// ```
252///
253/// ```rust
254/// use std::net::SocketAddr;
255/// use std::time::Duration;
256///
257/// use iai_callgrind::{Command, Delay, DelayKind};
258///
259/// let command = Command::new("echo").delay(
260/// Delay::new(DelayKind::UdpResponse(
261/// "127.0.0.1:34000".parse::<SocketAddr>().unwrap(),
262/// vec![1],
263/// ))
264/// .timeout(Duration::from_secs(3))
265/// .poll(Duration::from_millis(50)),
266/// );
267///
268/// let command_from = Command::new("echo").delay(
269/// Delay::from_udp_request("127.0.0.1:34000".parse::<SocketAddr>().unwrap(), vec![1])
270/// .timeout(Duration::from_secs(3))
271/// .poll(Duration::from_millis(50)),
272/// );
273/// ```
274#[derive(Debug, Default, Clone, PartialEq, IntoInner, AsRef)]
275pub struct Delay(__internal::InternalDelay);
276
277// TODO: Refactor: Use api::ExitWith directly
278/// Set the expected exit status of a binary benchmark
279///
280/// Per default, the benchmarked binary is expected to succeed, but if a benchmark is expected to
281/// fail, setting this option is required.
282///
283/// # Examples
284///
285/// ```rust
286/// # use iai_callgrind::{binary_benchmark_group};
287/// # binary_benchmark_group!(
288/// # name = my_group;
289/// # benchmarks = |group: &mut BinaryBenchmarkGroup| {});
290/// use iai_callgrind::{main, BinaryBenchmarkConfig, ExitWith};
291///
292/// # fn main() {
293/// main!(
294/// config = BinaryBenchmarkConfig::default().exit_with(ExitWith::Code(1));
295/// binary_benchmark_groups = my_group
296/// );
297/// # }
298/// ```
299#[derive(Debug, Clone, Copy)]
300pub enum ExitWith {
301 /// Exit with success is similar to `ExitCode(0)`
302 Success,
303 /// Exit with failure is similar to setting the `ExitCode` to something different from `0`
304 /// without having to rely on a specific exit code
305 Failure,
306 /// The exact `ExitCode` of the benchmark run
307 Code(i32),
308}
309
310/// The `Sandbox` in which the `setup`, `teardown` and the [`Command`] are run
311///
312/// The `Sandbox` is a temporary directory which is created before the execution of the
313/// [`setup`](`crate::binary_benchmark`) and deleted after the
314/// [`teardown`](`crate::binary_benchmark`). `setup`, the [`Command`] and `teardown` are executed
315/// inside this temporary directory.
316///
317/// # Background and reasons for using a `Sandbox`
318///
319/// A [`Sandbox`] can help mitigating differences in benchmark results on different machines. As
320/// long as `$TMP_DIR` is unset or set to `/tmp`, the temporary directory has a constant length on
321/// unix machines (except android which uses `/data/local/tmp`). The directory itself
322/// is created with a constant length but random name like `/tmp/.a23sr8fk`. It is not implausible
323/// that an executable has different event counts just because the directory it is executed in has a
324/// different length. For example, if a member of your project has set up the project in
325/// `/home/bob/workspace/our-project` running the benchmarks in this directory, and the ci runs the
326/// benchmarks in `/runner/our-project`, the event counts might differ. If possible, the benchmarks
327/// should be run in an as constant as possible environment. Clearing the environment variables is
328/// also such a counter-measure.
329///
330/// Other reasons for using a `Sandbox` are convenience, such as if you're creating files during
331/// `setup` and the [`Command`] run and don't want to delete all the files manually. Or, more
332/// importantly, if the [`Command`] is destructive and deletes files, it is usually safer to execute
333/// such a [`Command`] in a temporary directory where it cannot do any harm to your or others file
334/// systems during the benchmark runs.
335///
336/// # Sandbox cleanup
337///
338/// The changes the `setup` makes in this directory persist until the `teardown` has finished. So,
339/// the [`Command`] can for example pick up any files created by the `setup` method. If run in a
340/// `Sandbox`, the `teardown` usually doesn't have to delete any files, because the whole
341/// directory is deleted after its usage. There is an exception to the rule. If any of the files
342/// inside the directory is not removable, for example because the permissions of a file don't allow
343/// the file to be deleted, then the whole directory persists. You can use the `teardown` to reset
344/// all permission bits to be readable and writable, so the cleanup can succeed.
345///
346/// To simply copy fixtures or whole directories into the `Sandbox` use [`Sandbox::fixtures`].
347#[derive(Debug, Clone, IntoInner, AsRef)]
348pub struct Sandbox(__internal::InternalSandbox);
349
350impl Bench {
351 /// Create a new `Bench` with a unique [`BenchmarkId`]
352 ///
353 /// If the provided [`BenchmarkId`] is invalid, `iai-callgrind` exits with an error.
354 ///
355 /// # Scope of uniqueness of the [`BenchmarkId`]
356 ///
357 /// The id needs to be unique within the same [`BinaryBenchmark`]
358 ///
359 /// # Examples
360 ///
361 /// The [`BenchmarkId`] can be created from any &str-like
362 ///
363 /// ```
364 /// use iai_callgrind::Bench;
365 ///
366 /// let bench = Bench::new("my_unique_id");
367 /// ```
368 ///
369 /// but you can also provide the [`BenchmarkId`]
370 ///
371 /// ```
372 /// use iai_callgrind::{Bench, BenchmarkId};
373 ///
374 /// let bench = Bench::new(BenchmarkId::new("my_unique_id"));
375 /// ```
376 pub fn new<T>(id: T) -> Self
377 where
378 T: Into<BenchmarkId>,
379 {
380 Self {
381 id: id.into(),
382 config: None,
383 commands: vec![],
384 setup: None,
385 teardown: None,
386 }
387 }
388
389 /// Add a [`BinaryBenchmarkConfig`] for this `Bench`
390 ///
391 /// A `Bench` without a `BinaryBenchmarkConfig` behaves like having specified the default
392 /// [`BinaryBenchmarkConfig`]. This [`BinaryBenchmarkConfig`] overwrites the values of a
393 /// [`BinaryBenchmarkConfig`] specified at a higher level. See there for more details.
394 ///
395 /// # Examples
396 ///
397 /// ```
398 /// use iai_callgrind::{Bench, BinaryBenchmarkConfig};
399 ///
400 /// let bench = Bench::new("some_id").config(BinaryBenchmarkConfig::default().env("FOO", "BAR"));
401 /// ```
402 pub fn config<T>(&mut self, config: T) -> &mut Self
403 where
404 T: Into<__internal::InternalBinaryBenchmarkConfig>,
405 {
406 self.config = Some(config.into());
407 self
408 }
409
410 /// Add a [`Command`] to this `Bench`
411 ///
412 /// A `Bench` with multiple `Commands` behaves exactly as the
413 /// [`#[benches]`](crate::binary_benchmark) attribute
414 ///
415 /// # Errors
416 ///
417 /// It is an error if a `Bench` does not contain any `Commands`, so this method or
418 /// [`Bench::commands`] has to be called at least once.
419 ///
420 /// # Examples
421 ///
422 /// Suppose the crate's binary is called `my-echo`:
423 ///
424 /// ```rust
425 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
426 /// use iai_callgrind::{Bench, Command};
427 ///
428 /// let bench = Bench::new("some_id")
429 /// .command(Command::new(env!("CARGO_BIN_EXE_my-echo")))
430 /// .clone();
431 ///
432 /// assert_eq!(bench.commands.len(), 1);
433 /// ```
434 pub fn command<T>(&mut self, command: T) -> &mut Self
435 where
436 T: Into<Command>,
437 {
438 self.commands.push(command.into());
439 self
440 }
441
442 /// Add multiple [`Command`]s to this `Bench`
443 ///
444 /// See also [`Bench::command`].
445 ///
446 /// # Examples
447 ///
448 /// Suppose the crate's binary is called `my-echo`
449 ///
450 /// ```rust
451 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
452 /// use iai_callgrind::{Bench, Command};
453 ///
454 /// let mut command = Command::new(env!("CARGO_BIN_EXE_my-echo"));
455 ///
456 /// let echo_foo = command.clone().arg("foo").build();
457 /// let echo_bar = command.arg("bar").build();
458 ///
459 /// let mut bench = Bench::new("some_id");
460 /// bench.commands([echo_foo, echo_bar]).clone();
461 ///
462 /// assert_eq!(bench.commands.len(), 2);
463 /// ```
464 pub fn commands<I, T>(&mut self, commands: T) -> &mut Self
465 where
466 I: Into<Command>,
467 T: IntoIterator<Item = I>,
468 {
469 self.commands.extend(commands.into_iter().map(Into::into));
470 self
471 }
472
473 /// Add a `setup` function to be executed before the [`Command`] is executed
474 ///
475 /// This `setup` function overwrites the `setup` function of [`BinaryBenchmark`]. In the
476 /// presence of a [`Sandbox`], this function is executed in the sandbox.
477 ///
478 /// # Examples
479 ///
480 /// ```rust
481 /// use iai_callgrind::Bench;
482 ///
483 /// fn my_setup() {
484 /// println!("Place everything in this function you want to be executed prior to the Command");
485 /// }
486 ///
487 /// let mut bench = Bench::new("some_id");
488 /// bench.setup(my_setup);
489 ///
490 /// assert!(bench.setup.is_some())
491 /// ```
492 ///
493 /// Overwrite the setup function from a [`BinaryBenchmark`]
494 ///
495 /// ```rust
496 /// use iai_callgrind::{Bench, BinaryBenchmark};
497 /// fn binary_benchmark_setup() {
498 /// println!("setup in BinaryBenchmark")
499 /// }
500 ///
501 /// fn bench_setup() {
502 /// println!("setup in Bench")
503 /// }
504 ///
505 /// BinaryBenchmark::new("bench_binary")
506 /// .setup(binary_benchmark_setup)
507 /// .bench(Bench::new("some_id").setup(bench_setup));
508 /// ```
509 pub fn setup(&mut self, setup: fn()) -> &mut Self {
510 self.setup = Some(setup);
511 self
512 }
513
514 /// Add a `teardown` function to be executed after the [`Command`] is executed
515 ///
516 /// This `teardown` function overwrites the `teardown` function of [`BinaryBenchmark`]. In the
517 /// presence of a [`Sandbox`], this function is executed in the sandbox.
518 ///
519 /// # Examples
520 ///
521 /// ```rust
522 /// use iai_callgrind::Bench;
523 ///
524 /// fn my_teardown() {
525 /// println!(
526 /// "Place everything in this function you want to be executed after the execution of the \
527 /// Command"
528 /// );
529 /// }
530 ///
531 /// let mut bench = Bench::new("some_id");
532 /// bench.teardown(my_teardown);
533 ///
534 /// assert!(bench.teardown.is_some())
535 /// ```
536 ///
537 /// Overwrite the teardown function from a [`BinaryBenchmark`]
538 ///
539 /// ```rust
540 /// use iai_callgrind::{Bench, BinaryBenchmark};
541 /// fn binary_benchmark_teardown() {
542 /// println!("teardown in BinaryBenchmark")
543 /// }
544 ///
545 /// fn bench_teardown() {
546 /// println!("teardown in Bench")
547 /// }
548 ///
549 /// BinaryBenchmark::new("bench_binary")
550 /// .teardown(binary_benchmark_teardown)
551 /// .bench(Bench::new("some_id").teardown(bench_teardown));
552 /// ```
553 pub fn teardown(&mut self, teardown: fn()) -> &mut Self {
554 self.teardown = Some(teardown);
555 self
556 }
557
558 /// Add each line of a file as [`Command`] to this [`Bench`] using a `generator` function.
559 ///
560 /// This method mirrors the `file` parameter of the `#[benches]` attribute as far as possible.
561 /// In the low-level api you can achieve the same or more quickly yourself and this method
562 /// exists for the sake of completeness (and convenience).
563 ///
564 /// The file has to exist at the time you're using this method and the file has to be encoded in
565 /// UTF-8. The `generator` function tells us how to convert each line of the file into a
566 /// [`Command`].
567 ///
568 /// # Notable differences to high-level api
569 ///
570 /// If the file path in the high-level api is relative we interpret the path relative to the
571 /// workspace root (and make it absolute). In this method we use the path AS IS.
572 ///
573 /// # Errors
574 ///
575 /// If the file is empty, cannot be opened for reading or a line in the file cannot be converted
576 /// to a String. Also, the error from the `generator` is propagated. The errors containing the
577 /// line number use a 0-indexed line number.
578 ///
579 /// # Examples
580 ///
581 /// Suppose your cargo's binary is named `my-echo` and you want to convert a file with inputs
582 /// `benches/inputs` into commands and each line is the only argument for your `my-echo` binary:
583 ///
584 /// ```rust
585 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
586 /// use iai_callgrind::{binary_benchmark_group, BinaryBenchmark, Bench};
587 ///
588 /// binary_benchmark_group!(
589 /// name = my_group;
590 /// benchmarks = |group: &mut BinaryBenchmarkGroup| {
591 /// group.binary_benchmark(BinaryBenchmark::new("some_id")
592 /// .bench(Bench::new("bench_id")
593 /// .file("benches/inputs", |line| {
594 /// Ok(iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-echo"))
595 /// .arg(line)
596 /// .build())
597 /// }).unwrap()
598 /// )
599 /// )
600 /// }
601 /// );
602 /// # fn main() {}
603 /// ```
604 pub fn file<T>(
605 &mut self,
606 path: T,
607 generator: fn(String) -> Result<Command, String>,
608 ) -> Result<&mut Self, String>
609 where
610 T: AsRef<Path>,
611 {
612 let path = path.as_ref();
613 let file = File::open(path).map_err(|error| {
614 format!(
615 "{}: Error opening file '{}': {error}",
616 self.id,
617 path.display(),
618 )
619 })?;
620
621 let reader = BufReader::new(file);
622 let mut has_lines = false;
623 for (index, line) in reader.lines().enumerate() {
624 has_lines = true;
625
626 let line = line.map_err(|error| {
627 format!(
628 "{}: Error reading line {index} in file '{}': {error}",
629 self.id,
630 path.display()
631 )
632 })?;
633
634 let command = generator(line).map_err(|error| {
635 format!(
636 "{}: Error generating command from line {index} in file '{}': {error}",
637 self.id,
638 path.display()
639 )
640 })?;
641 self.commands.push(command);
642 }
643
644 if !has_lines {
645 return Err(format!("{}: Empty file '{}'", self.id, path.display()));
646 }
647 Ok(self)
648 }
649}
650
651impl From<&mut Bench> for Bench {
652 fn from(value: &mut Bench) -> Self {
653 value.clone()
654 }
655}
656
657impl From<&Bench> for Bench {
658 fn from(value: &Bench) -> Self {
659 value.clone()
660 }
661}
662
663impl PartialEq for Bench {
664 fn eq(&self, other: &Self) -> bool {
665 self.id == other.id
666 && self.commands == other.commands
667 && self.config == other.config
668 && (self.setup.is_some() && other.setup.is_some()
669 || self.setup.is_none() && other.setup.is_none())
670 && (self.teardown.is_some() && other.teardown.is_some()
671 || self.teardown.is_none() && other.teardown.is_none())
672 }
673}
674
675impl BenchmarkId {
676 /// Convenience method to create a `BenchmarkId` with a parameter in the [low level
677 /// api](crate::binary_benchmark_group)
678 ///
679 /// The `parameter` is simply appended to the `id` with an underscore, so
680 /// `BenchmarkId::with_parameter("some", 1)` is equivalent to `BenchmarkId::new("some_1")`
681 ///
682 /// # Examples
683 ///
684 /// ```
685 /// use iai_callgrind::BenchmarkId;
686 ///
687 /// let new_id = BenchmarkId::new("prefix_1");
688 /// let with_parameter = BenchmarkId::with_parameter("prefix", 1);
689 /// assert_eq!(new_id, with_parameter);
690 /// ```
691 ///
692 /// ```rust
693 /// # use iai_callgrind::main;
694 /// use iai_callgrind::{binary_benchmark_group,BenchmarkId, BinaryBenchmark, Bench, Command};
695 /// use std::ffi::OsStr;
696 ///
697 /// binary_benchmark_group!(
698 /// name = low_level_group;
699 /// benchmarks = |group: &mut BinaryBenchmarkGroup| {
700 /// let mut binary_benchmark = BinaryBenchmark::new("some_id");
701 /// for arg in 0..10 {
702 /// let id = BenchmarkId::with_parameter("prefix", arg);
703 /// binary_benchmark.bench(
704 /// Bench::new(id)
705 /// .command(
706 /// Command::new("echo").arg(arg.to_string()).build()
707 /// )
708 /// );
709 /// }
710 /// group.binary_benchmark(binary_benchmark);
711 /// }
712 /// );
713 /// # fn main() {
714 /// # main!(binary_benchmark_groups = low_level_group);
715 /// # }
716 /// ```
717 pub fn with_parameter<T, P>(id: T, parameter: P) -> Self
718 where
719 T: AsRef<str>,
720 P: Display,
721 {
722 Self(format!("{}_{parameter}", id.as_ref()))
723 }
724
725 /// Create a new `BenchmarkId`
726 ///
727 /// `BenchmarkId`s can be created from any string-like input. See [`BenchmarkId::validate`] for
728 /// ids which are considered valid.
729 ///
730 /// # Examples
731 ///
732 /// ```rust
733 /// use iai_callgrind::BenchmarkId;
734 ///
735 /// let id = BenchmarkId::new("my_id");
736 ///
737 /// assert!(id.validate().is_ok());
738 /// ```
739 pub fn new<T>(id: T) -> Self
740 where
741 T: Into<String>,
742 {
743 Self(id.into())
744 }
745
746 /// Returns ok if this [`BenchmarkId`] is valid
747 ///
748 /// An id should be short, descriptive besides being unique. The requirements for the uniqueness
749 /// differ for the structs where a `BenchmarkId` is used and is further described there.
750 ///
751 /// We use a minimal subset of rust's identifiers. A valid `BenchmarkId` starts with an ascii
752 /// alphabetic letter `[a-zA-Z]` or underscore `[_]`. All following characters can be an ascii
753 /// alphabetic letter, underscore or a digit `[0-9]`. At least one valid character must be
754 /// present.
755 ///
756 /// The `BenchmarkId` is used by `iai-callgrind` as file and directory name for the output files
757 /// of the benchmarks. Therefore, the limit for an id is chosen to be 120 bytes. This is a
758 /// calculation with some headroom. There are file systems which do not even allow 255 bytes. If
759 /// you're working on such a peculiar file system, you have to restrict your ids to even fewer
760 /// bytes which is `floor(MAX_LENGTH/2) - 1`. Leaving the maximum bytes aside, the best IDs are
761 /// simple, short and descriptive.
762 ///
763 /// Usually, it is not necessary to call this function, since we already check the validity of
764 /// benchmark ids prior to the execution of the benchmark runner. But if your ids come from an
765 /// untrusted source you can use this method for more immediate feedback.
766 ///
767 /// # Errors
768 ///
769 /// This function will return an error describing the source of the error if the id is invalid
770 ///
771 /// # Examples
772 ///
773 /// ```rust
774 /// use iai_callgrind::BenchmarkId;
775 ///
776 /// assert!(BenchmarkId::new("").validate().is_err());
777 /// assert!(BenchmarkId::new("0a").validate().is_err());
778 /// assert!(BenchmarkId::new("id with spaces").validate().is_err());
779 /// assert!(BenchmarkId::new("path/to").validate().is_err());
780 /// assert!(BenchmarkId::new("no::module::too").validate().is_err());
781 ///
782 /// assert!(BenchmarkId::new("_").validate().is_ok());
783 /// assert!(BenchmarkId::new("abc").validate().is_ok());
784 /// assert!(BenchmarkId::new("a9").validate().is_ok());
785 /// assert!(BenchmarkId::new("z_").validate().is_ok());
786 /// assert!(BenchmarkId::new("some_id").validate().is_ok());
787 /// ```
788 #[allow(clippy::missing_panics_doc)]
789 pub fn validate(&self) -> Result<(), String> {
790 const MAX_LENGTH_ID: usize = 255;
791 if self.0.is_empty() {
792 return Err("Invalid id: Cannot be empty".to_owned());
793 }
794
795 let mut bytes = self.0.bytes();
796 // This unwrap is safe, since we just checked that the string is not empty
797 let first = bytes.next().unwrap();
798
799 if first.is_ascii_alphabetic() || first == b'_' {
800 for (index, byte) in (1..).zip(bytes) {
801 if index > MAX_LENGTH_ID {
802 return Err(format!(
803 "Invalid id '{}': Maximum length of {MAX_LENGTH_ID} bytes reached",
804 &self.0,
805 ));
806 }
807 if byte.is_ascii() {
808 if !(byte.is_ascii_alphanumeric() || byte == b'_') {
809 return Err(format!(
810 "Invalid id '{}' at position {index}: Invalid character '{}'",
811 &self.0,
812 char::from(byte)
813 ));
814 }
815 } else {
816 return Err(format!(
817 "Invalid id '{}' at position {index}: Encountered non-ascii character",
818 &self.0
819 ));
820 }
821 }
822 } else if first.is_ascii() {
823 return Err(format!(
824 "Invalid id '{}': As first character is '{}' not allowed",
825 &self.0,
826 char::from(first)
827 ));
828 } else {
829 return Err(format!(
830 "Invalid id '{}': Encountered non-ascii character as first character",
831 &self.0
832 ));
833 }
834 Ok(())
835 }
836}
837
838impl Display for BenchmarkId {
839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
840 f.write_str(&self.0)
841 }
842}
843
844impl From<BenchmarkId> for String {
845 fn from(value: BenchmarkId) -> Self {
846 value.0
847 }
848}
849
850impl<T> From<T> for BenchmarkId
851where
852 T: AsRef<str>,
853{
854 fn from(value: T) -> Self {
855 Self(value.as_ref().to_owned())
856 }
857}
858
859impl BinaryBenchmark {
860 /// Create a new `BinaryBenchmark`
861 ///
862 /// A `BinaryBenchmark` is the equivalent of the
863 /// [`#[binary_benchmark]`](`crate::binary_benchmark`) attribute in the low-level api and needs
864 /// a [`BenchmarkId`]. In the high-level api the id is derived from the function name.
865 ///
866 /// The [`BenchmarkId`] is used together with the id of each [`Bench`] to create a directory
867 /// name. This usually limits the combined length of the ids to `255 - 1` but can be less
868 /// depending on the file system. See [`BenchmarkId`] for more details
869 ///
870 /// # Scope of uniqueness of the [`BenchmarkId`]
871 ///
872 /// The id needs to be unique within the same [`crate::binary_benchmark_group`]. It is
873 /// recommended to use an id which is unique within the whole benchmark file, although doing
874 /// otherwise does currently not incur any negative consequence.
875 ///
876 /// # Examples
877 ///
878 /// ```rust
879 /// use iai_callgrind::{BenchmarkId, BinaryBenchmark};
880 ///
881 /// let binary_benchmark = BinaryBenchmark::new("some_id");
882 /// assert_eq!(binary_benchmark.id, BenchmarkId::new("some_id"));
883 /// ```
884 pub fn new<T>(id: T) -> Self
885 where
886 T: Into<BenchmarkId>,
887 {
888 Self {
889 id: id.into(),
890 config: None,
891 benches: vec![],
892 setup: None,
893 teardown: None,
894 }
895 }
896
897 /// Add a [`BinaryBenchmarkConfig`] to this `BinaryBenchmark`
898 ///
899 /// # Examples
900 ///
901 /// ```rust
902 /// use iai_callgrind::{BinaryBenchmark, BinaryBenchmarkConfig};
903 ///
904 /// let binary_benchmark = BinaryBenchmark::new("some_id")
905 /// .config(BinaryBenchmarkConfig::default().env("FOO", "BAR"))
906 /// .clone();
907 ///
908 /// assert_eq!(
909 /// binary_benchmark.config,
910 /// Some(BinaryBenchmarkConfig::default().env("FOO", "BAR").into())
911 /// );
912 /// ```
913 pub fn config<T>(&mut self, config: T) -> &mut Self
914 where
915 T: Into<__internal::InternalBinaryBenchmarkConfig>,
916 {
917 self.config = Some(config.into());
918 self
919 }
920
921 /// Add a [`Bench`] to this `BinaryBenchmark`
922 ///
923 /// Adding a [`Bench`] which doesn't contain a [`Command`] is an error.
924 ///
925 /// # Examples
926 ///
927 /// ```rust
928 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
929 /// use iai_callgrind::{Bench, BinaryBenchmark, Command};
930 ///
931 /// // Each `Bench` needs at least one `Command`!
932 /// let mut bench = Bench::new("some_id");
933 /// bench.command(Command::new(env!("CARGO_BIN_EXE_my-echo")));
934 ///
935 /// let binary_benchmark = BinaryBenchmark::new("bench_binary")
936 /// .bench(bench.clone())
937 /// .clone();
938 ///
939 /// assert_eq!(binary_benchmark.benches[0], bench);
940 /// ```
941 pub fn bench<T>(&mut self, bench: T) -> &mut Self
942 where
943 T: Into<Bench>,
944 {
945 self.benches.push(bench.into());
946 self
947 }
948
949 /// Add a `setup` function to this `BinaryBenchmark`
950 ///
951 /// This `setup` function is used in all [`Bench`]es of this `BinaryBenchmark` if not overridden
952 /// by the `Bench`.
953 ///
954 /// # Examples
955 ///
956 /// ```rust
957 /// use iai_callgrind::Bench;
958 /// fn my_setup() {
959 /// println!(
960 /// "Place everything in this function you want to be executed before the execution of \
961 /// the Command"
962 /// );
963 /// }
964 ///
965 /// let bench = Bench::new("some_id").setup(my_setup).clone();
966 ///
967 /// assert!(bench.setup.is_some())
968 /// ```
969 ///
970 /// Overwrite the setup function from this `BinaryBenchmark` in a [`Bench`]
971 ///
972 /// ```rust
973 /// use iai_callgrind::{Bench, BinaryBenchmark};
974 /// fn binary_benchmark_setup() {
975 /// println!("setup in BinaryBenchmark")
976 /// }
977 ///
978 /// fn bench_setup() {
979 /// println!("setup in Bench")
980 /// }
981 ///
982 /// BinaryBenchmark::new("bench_binary")
983 /// .setup(binary_benchmark_setup)
984 /// .bench(Bench::new("some_id").setup(bench_setup));
985 /// ```
986 pub fn setup(&mut self, setup: fn()) -> &mut Self {
987 self.setup = Some(setup);
988 self
989 }
990
991 /// Add a `teardown` function to this `BinaryBenchmark`
992 ///
993 /// This `teardown` function is used in all [`Bench`]es of this `BinaryBenchmark` if not
994 /// overridden by the `Bench`.
995 ///
996 /// # Examples
997 ///
998 /// ```rust
999 /// use iai_callgrind::Bench;
1000 /// fn my_teardown() {
1001 /// println!(
1002 /// "Place everything in this function you want to be executed after the execution of the \
1003 /// Command"
1004 /// );
1005 /// }
1006 ///
1007 /// let bench = Bench::new("some_id").teardown(my_teardown).clone();
1008 ///
1009 /// assert!(bench.teardown.is_some())
1010 /// ```
1011 ///
1012 /// Overwrite the teardown function from this `BinaryBenchmark` in a [`Bench`]
1013 ///
1014 /// ```rust
1015 /// use iai_callgrind::{Bench, BinaryBenchmark};
1016 /// fn binary_benchmark_teardown() {
1017 /// println!("teardown in BinaryBenchmark")
1018 /// }
1019 ///
1020 /// fn bench_teardown() {
1021 /// println!("teardown in Bench")
1022 /// }
1023 ///
1024 /// BinaryBenchmark::new("bench_binary")
1025 /// .teardown(binary_benchmark_teardown)
1026 /// .bench(Bench::new("some_id").teardown(bench_teardown));
1027 /// ```
1028 pub fn teardown(&mut self, teardown: fn()) -> &mut Self {
1029 self.teardown = Some(teardown);
1030 self
1031 }
1032}
1033
1034impl From<&mut BinaryBenchmark> for BinaryBenchmark {
1035 fn from(value: &mut BinaryBenchmark) -> Self {
1036 value.clone()
1037 }
1038}
1039
1040impl From<&BinaryBenchmark> for BinaryBenchmark {
1041 fn from(value: &BinaryBenchmark) -> Self {
1042 value.clone()
1043 }
1044}
1045
1046impl PartialEq for BinaryBenchmark {
1047 fn eq(&self, other: &Self) -> bool {
1048 self.id == other.id
1049 && self.config == other.config
1050 && self.benches == other.benches
1051 && (self.setup.is_some() && other.setup.is_some()
1052 || self.setup.is_none() && other.setup.is_none())
1053 && (self.teardown.is_some() && other.teardown.is_some()
1054 || self.teardown.is_none() && other.teardown.is_none())
1055 }
1056}
1057
1058impl BinaryBenchmarkConfig {
1059 /// Change the default tool to something different than callgrind
1060 ///
1061 /// See also [`crate::LibraryBenchmarkConfig::default_tool`].
1062 ///
1063 /// # Example for using cachegrind on the fly
1064 ///
1065 /// ```rust
1066 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1067 /// use iai_callgrind::{
1068 /// main, binary_benchmark, binary_benchmark_group, BinaryBenchmarkConfig, ValgrindTool
1069 /// };
1070 ///
1071 /// #[binary_benchmark(
1072 /// config = BinaryBenchmarkConfig::default()
1073 /// .default_tool(ValgrindTool::Cachegrind)
1074 /// )]
1075 /// fn bench_me() -> iai_callgrind::Command {
1076 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_echo"))
1077 /// }
1078 ///
1079 /// binary_benchmark_group!(
1080 /// name = my_group;
1081 /// benchmarks = bench_me
1082 /// );
1083 ///
1084 /// # fn main() {
1085 /// main!(binary_benchmark_groups = my_group);
1086 /// # }
1087 /// ```
1088 pub fn default_tool(&mut self, tool: ValgrindTool) -> &mut Self {
1089 self.0.default_tool = Some(tool);
1090 self
1091 }
1092
1093 /// Pass valgrind arguments to all tools
1094 ///
1095 /// Only core [valgrind
1096 /// arguments](https://valgrind.org/docs/manual/manual-core.html#manual-core.options) are
1097 /// allowed.
1098 ///
1099 /// These arguments can be overwritten by tool specific arguments for example with
1100 /// [`crate::Callgrind::args`].
1101 ///
1102 /// # Examples
1103 ///
1104 /// Specify `--trace-children=no` for all configured tools (including callgrind):
1105 ///
1106 /// ```rust
1107 /// # use iai_callgrind::{binary_benchmark_group};
1108 /// # binary_benchmark_group!(
1109 /// # name = my_group;
1110 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1111 /// use iai_callgrind::{main, BinaryBenchmarkConfig, Dhat};
1112 ///
1113 /// # fn main() {
1114 /// main!(
1115 /// config = BinaryBenchmarkConfig::default()
1116 /// .valgrind_args(["--trace-children=no"])
1117 /// .tool(Dhat::default());
1118 /// binary_benchmark_groups = my_group
1119 /// );
1120 /// # }
1121 /// ```
1122 ///
1123 /// Overwrite the valgrind argument `--num-callers=25` for `DHAT` with `--num-callers=30`:
1124 ///
1125 /// ```rust
1126 /// # use iai_callgrind::{binary_benchmark_group};
1127 /// # binary_benchmark_group!(
1128 /// # name = my_group;
1129 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1130 /// use iai_callgrind::{main, BinaryBenchmarkConfig, Dhat};
1131 ///
1132 /// # fn main() {
1133 /// main!(
1134 /// config = BinaryBenchmarkConfig::default()
1135 /// .valgrind_args(["--num-callers=25"])
1136 /// .tool(Dhat::with_args(["--num-callers=30"]));
1137 /// binary_benchmark_groups = my_group
1138 /// );
1139 /// # }
1140 /// ```
1141 pub fn valgrind_args<I, T>(&mut self, args: T) -> &mut Self
1142 where
1143 I: AsRef<str>,
1144 T: IntoIterator<Item = I>,
1145 {
1146 self.0.valgrind_args.extend_ignore_flag(args);
1147 self
1148 }
1149
1150 /// Add an environment variable to the [`Command`]
1151 ///
1152 /// These environment variables are available independently of the setting of
1153 /// [`BinaryBenchmarkConfig::env_clear`].
1154 ///
1155 /// # Examples
1156 ///
1157 /// An example for a custom environment variable "FOO=BAR":
1158 ///
1159 /// ```rust
1160 /// # use iai_callgrind::{binary_benchmark_group};
1161 /// # binary_benchmark_group!(
1162 /// # name = my_group;
1163 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1164 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1165 ///
1166 /// # fn main() {
1167 /// main!(
1168 /// config = BinaryBenchmarkConfig::default().env("FOO", "BAR");
1169 /// binary_benchmark_groups = my_group
1170 /// );
1171 /// # }
1172 /// ```
1173 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
1174 where
1175 K: Into<OsString>,
1176 V: Into<OsString>,
1177 {
1178 self.0.envs.push((key.into(), Some(value.into())));
1179 self
1180 }
1181
1182 /// Add multiple environment variables to the [`Command`]
1183 ///
1184 /// # Examples
1185 ///
1186 /// ```rust
1187 /// # use iai_callgrind::{binary_benchmark_group};
1188 /// # binary_benchmark_group!(
1189 /// # name = my_group;
1190 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1191 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1192 ///
1193 /// # fn main() {
1194 /// main!(
1195 /// config = BinaryBenchmarkConfig::default().envs([("FOO", "BAR"),("BAR", "BAZ")]);
1196 /// binary_benchmark_groups = my_group
1197 /// );
1198 /// # }
1199 /// ```
1200 pub fn envs<K, V, T>(&mut self, envs: T) -> &mut Self
1201 where
1202 K: Into<OsString>,
1203 V: Into<OsString>,
1204 T: IntoIterator<Item = (K, V)>,
1205 {
1206 self.0
1207 .envs
1208 .extend(envs.into_iter().map(|(k, v)| (k.into(), Some(v.into()))));
1209 self
1210 }
1211
1212 /// Specify a pass-through environment variable
1213 ///
1214 /// Usually, the environment variables before running a binary benchmark are cleared
1215 /// but specifying pass-through variables makes this environment variable available to
1216 /// the benchmark as it actually appeared in the root environment.
1217 ///
1218 /// Pass-through environment variables are ignored if they don't exist in the root
1219 /// environment.
1220 ///
1221 /// # Examples
1222 ///
1223 /// Here, we chose to pass through the original value of the `HOME` variable:
1224 ///
1225 /// ```rust
1226 /// # use iai_callgrind::{binary_benchmark_group};
1227 /// # binary_benchmark_group!(
1228 /// # name = my_group;
1229 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1230 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1231 ///
1232 /// # fn main() {
1233 /// main!(
1234 /// config = BinaryBenchmarkConfig::default().pass_through_env("HOME");
1235 /// binary_benchmark_groups = my_group
1236 /// );
1237 /// # }
1238 /// ```
1239 pub fn pass_through_env<K>(&mut self, key: K) -> &mut Self
1240 where
1241 K: Into<OsString>,
1242 {
1243 self.0.envs.push((key.into(), None));
1244 self
1245 }
1246
1247 /// Specify multiple pass-through environment variables
1248 ///
1249 /// See also [`crate::BinaryBenchmarkConfig::pass_through_env`].
1250 ///
1251 /// # Examples
1252 ///
1253 /// ```rust
1254 /// # use iai_callgrind::binary_benchmark_group;
1255 /// # binary_benchmark_group!(
1256 /// # name = my_group;
1257 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1258 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1259 ///
1260 /// # fn main() {
1261 /// main!(
1262 /// config = BinaryBenchmarkConfig::default().pass_through_envs(["HOME", "USER"]);
1263 /// binary_benchmark_groups = my_group
1264 /// );
1265 /// # }
1266 /// ```
1267 pub fn pass_through_envs<K, T>(&mut self, envs: T) -> &mut Self
1268 where
1269 K: Into<OsString>,
1270 T: IntoIterator<Item = K>,
1271 {
1272 self.0
1273 .envs
1274 .extend(envs.into_iter().map(|k| (k.into(), None)));
1275 self
1276 }
1277
1278 /// If false, don't clear the environment variables before running the benchmark (Default: true)
1279 ///
1280 /// # Examples
1281 ///
1282 /// ```rust
1283 /// # use iai_callgrind::{binary_benchmark_group};
1284 /// # binary_benchmark_group!(
1285 /// # name = my_group;
1286 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1287 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1288 ///
1289 /// # fn main() {
1290 /// main!(
1291 /// config = BinaryBenchmarkConfig::default().env_clear(false);
1292 /// binary_benchmark_groups = my_group
1293 /// );
1294 /// # }
1295 /// ```
1296 pub fn env_clear(&mut self, value: bool) -> &mut Self {
1297 self.0.env_clear = Some(value);
1298 self
1299 }
1300
1301 /// Set the directory of the benchmarked binary (Default: Unchanged)
1302 ///
1303 /// Unchanged means, in the case of running with the sandbox enabled, the root of the sandbox.
1304 /// In the case of running without sandboxing enabled, this'll be the directory which `cargo
1305 /// bench` sets. If running the benchmark within the sandbox, and the path is relative then this
1306 /// new directory must be contained in the sandbox.
1307 ///
1308 /// # Examples
1309 ///
1310 /// ```rust
1311 /// # use iai_callgrind::{binary_benchmark_group};
1312 /// # binary_benchmark_group!(
1313 /// # name = my_group;
1314 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1315 /// use iai_callgrind::{main, BinaryBenchmarkConfig};
1316 ///
1317 /// # fn main() {
1318 /// main!(
1319 /// config = BinaryBenchmarkConfig::default().current_dir("/tmp");
1320 /// binary_benchmark_groups = my_group
1321 /// );
1322 /// # }
1323 /// ```
1324 ///
1325 /// and the following will change the current directory to `fixtures` assuming it is
1326 /// contained in the root of the sandbox
1327 ///
1328 /// ```rust
1329 /// # use iai_callgrind::{binary_benchmark_group};
1330 /// # binary_benchmark_group!(
1331 /// # name = my_group;
1332 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1333 /// use iai_callgrind::{main, BinaryBenchmarkConfig, Sandbox};
1334 ///
1335 /// # fn main() {
1336 /// main!(
1337 /// config = BinaryBenchmarkConfig::default()
1338 /// .sandbox(Sandbox::new(true))
1339 /// .current_dir("fixtures");
1340 /// binary_benchmark_groups = my_group
1341 /// );
1342 /// # }
1343 /// ```
1344 pub fn current_dir<T>(&mut self, value: T) -> &mut Self
1345 where
1346 T: Into<PathBuf>,
1347 {
1348 self.0.current_dir = Some(value.into());
1349 self
1350 }
1351
1352 /// Set the expected exit status [`ExitWith`] of a benchmarked binary
1353 ///
1354 /// Per default, the benchmarked binary is expected to succeed which is the equivalent of
1355 /// [`ExitWith::Success`]. But, if a benchmark is expected to fail, setting this option is
1356 /// required.
1357 ///
1358 /// # Examples
1359 ///
1360 /// If the benchmark is expected to fail with a specific exit code, for example `100`:
1361 ///
1362 /// ```rust
1363 /// # use iai_callgrind::{binary_benchmark_group};
1364 /// # binary_benchmark_group!(
1365 /// # name = my_group;
1366 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1367 /// use iai_callgrind::{main, BinaryBenchmarkConfig, ExitWith};
1368 ///
1369 /// # fn main() {
1370 /// main!(
1371 /// config = BinaryBenchmarkConfig::default().exit_with(ExitWith::Code(100));
1372 /// binary_benchmark_groups = my_group
1373 /// );
1374 /// # }
1375 /// ```
1376 ///
1377 /// If a benchmark is expected to fail, but the exit code doesn't matter:
1378 ///
1379 /// ```rust
1380 /// # use iai_callgrind::{binary_benchmark_group};
1381 /// # binary_benchmark_group!(
1382 /// # name = my_group;
1383 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1384 /// use iai_callgrind::{main, BinaryBenchmarkConfig, ExitWith};
1385 ///
1386 /// # fn main() {
1387 /// main!(
1388 /// config = BinaryBenchmarkConfig::default().exit_with(ExitWith::Failure);
1389 /// binary_benchmark_groups = my_group
1390 /// );
1391 /// # }
1392 /// ```
1393 pub fn exit_with<T>(&mut self, value: T) -> &mut Self
1394 where
1395 T: Into<__internal::InternalExitWith>,
1396 {
1397 self.0.exit_with = Some(value.into());
1398 self
1399 }
1400
1401 /// Add a configuration for a valgrind tool
1402 ///
1403 /// Valid configurations are [`crate::Callgrind`], [`crate::Cachegrind`], [`crate::Dhat`],
1404 /// [`crate::Memcheck`], [`crate::Helgrind`], [`crate::Drd`], [`crate::Massif`] and
1405 /// [`crate::Bbv`].
1406 ///
1407 /// # Examples
1408 ///
1409 /// Run DHAT in addition to callgrind.
1410 ///
1411 /// ```rust
1412 /// # use iai_callgrind::{binary_benchmark_group};
1413 /// # binary_benchmark_group!(
1414 /// # name = my_group;
1415 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1416 /// use iai_callgrind::{main, BinaryBenchmarkConfig, Dhat, ValgrindTool};
1417 ///
1418 /// # fn main() {
1419 /// main!(
1420 /// config = BinaryBenchmarkConfig::default()
1421 /// .tool(Dhat::default());
1422 /// binary_benchmark_groups = my_group
1423 /// );
1424 /// # }
1425 /// ```
1426 pub fn tool<T>(&mut self, tool: T) -> &mut Self
1427 where
1428 T: Into<__internal::InternalTool>,
1429 {
1430 self.0.tools.update(tool.into());
1431 self
1432 }
1433
1434 /// Override previously defined configurations of valgrind tools
1435 ///
1436 /// See also [`crate::LibraryBenchmarkConfig::tool_override`] for more details.
1437 ///
1438 /// # Example
1439 ///
1440 /// The following will run `DHAT` and `Massif` (and the default callgrind) for all benchmarks
1441 /// in `main!` besides for `foo` which will just run `Memcheck` (and callgrind).
1442 ///
1443 /// ```rust
1444 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1445 /// use iai_callgrind::{
1446 /// binary_benchmark, binary_benchmark_group, BinaryBenchmarkConfig, main, Memcheck, Dhat,
1447 /// Massif
1448 /// };
1449 ///
1450 /// #[binary_benchmark]
1451 /// #[bench::some(
1452 /// config = BinaryBenchmarkConfig::default()
1453 /// .tool_override(Memcheck::default())
1454 /// )]
1455 /// fn bench_binary() -> iai_callgrind::Command {
1456 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
1457 /// }
1458 ///
1459 /// binary_benchmark_group!(
1460 /// name = my_group;
1461 /// benchmarks = bench_binary
1462 /// );
1463 ///
1464 /// # fn main() {
1465 /// main!(
1466 /// config = BinaryBenchmarkConfig::default()
1467 /// .tool(Dhat::default())
1468 /// .tool(Massif::default());
1469 /// binary_benchmark_groups = my_group
1470 /// );
1471 /// # }
1472 /// ```
1473 pub fn tool_override<T>(&mut self, tool: T) -> &mut Self
1474 where
1475 T: Into<__internal::InternalTool>,
1476 {
1477 self.0
1478 .tools_override
1479 .get_or_insert(__internal::InternalTools::default())
1480 .update(tool.into());
1481 self
1482 }
1483
1484 /// Configure benchmarks to run in a [`Sandbox`] (Default: false)
1485 ///
1486 /// If specified, we create a temporary directory in which the `setup` and `teardown` functions
1487 /// of the `#[binary_benchmark]` (`#[bench]`, `#[benches]`) and the [`Command`] itself are run.
1488 ///
1489 /// A good reason for using a temporary directory as workspace is, that the length of the path
1490 /// where a benchmark is executed may have an influence on the benchmark results. For example,
1491 /// running the benchmark in your repository `/home/me/my/repository` and someone else's
1492 /// repository located under `/home/someone/else/repository` may produce different results only
1493 /// because the length of the first path is shorter. To run benchmarks as deterministic as
1494 /// possible across different systems, the length of the path should be the same wherever the
1495 /// benchmark is executed. This crate ensures this property by using the tempfile crate which
1496 /// creates the temporary directory in `/tmp` with a random name of fixed length like
1497 /// `/tmp/.tmp12345678`. This ensures that the length of the directory will be the same on all
1498 /// unix hosts where the benchmarks are run.
1499 ///
1500 /// # Examples
1501 ///
1502 /// ```rust
1503 /// # use iai_callgrind::{binary_benchmark_group};
1504 /// # binary_benchmark_group!(
1505 /// # name = my_group;
1506 /// # benchmarks = |_group: &mut BinaryBenchmarkGroup| {});
1507 /// use iai_callgrind::{main, BinaryBenchmarkConfig, Sandbox};
1508 ///
1509 /// # fn main() {
1510 /// main!(
1511 /// config = BinaryBenchmarkConfig::default().sandbox(Sandbox::new(true));
1512 /// binary_benchmark_groups = my_group
1513 /// );
1514 /// # }
1515 /// ```
1516 pub fn sandbox<T>(&mut self, sandbox: T) -> &mut Self
1517 where
1518 T: Into<__internal::InternalSandbox>,
1519 {
1520 self.0.sandbox = Some(sandbox.into());
1521 self
1522 }
1523
1524 /// Configure the [`crate::OutputFormat`] of the terminal output of Iai-Callgrind
1525 ///
1526 /// # Examples
1527 ///
1528 /// ```rust
1529 /// use iai_callgrind::{main, BinaryBenchmarkConfig, OutputFormat};
1530 /// # use iai_callgrind::{binary_benchmark, binary_benchmark_group};
1531 /// # #[binary_benchmark]
1532 /// # fn some_func() -> iai_callgrind::Command { iai_callgrind::Command::new("some/path") }
1533 /// # binary_benchmark_group!(
1534 /// # name = some_group;
1535 /// # benchmarks = some_func
1536 /// # );
1537 /// # fn main() {
1538 /// main!(
1539 /// config = BinaryBenchmarkConfig::default()
1540 /// .output_format(OutputFormat::default()
1541 /// .truncate_description(Some(200))
1542 /// );
1543 /// binary_benchmark_groups = some_group
1544 /// );
1545 /// # }
1546 pub fn output_format<T>(&mut self, output_format: T) -> &mut Self
1547 where
1548 T: Into<__internal::InternalOutputFormat>,
1549 {
1550 self.0.output_format = Some(output_format.into());
1551 self
1552 }
1553
1554 /// Execute the `setup` in parallel to the [`Command`].
1555 ///
1556 /// See also [`Command::setup_parallel`]
1557 ///
1558 /// # Examples
1559 ///
1560 /// ```rust
1561 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1562 /// use std::time::Duration;
1563 /// use std::net::{SocketAddr, TcpListener};
1564 /// use std::thread;
1565 /// use iai_callgrind::{
1566 /// binary_benchmark_group, binary_benchmark, main, BinaryBenchmarkConfig, Command,
1567 /// Delay, DelayKind
1568 /// };
1569 ///
1570 /// fn setup_tcp_server() {
1571 /// thread::sleep(Duration::from_millis(300));
1572 /// let _listener = TcpListener::bind("127.0.0.1:31000".parse::<SocketAddr>().unwrap()).unwrap();
1573 /// thread::sleep(Duration::from_secs(1));
1574 /// }
1575 ///
1576 /// #[binary_benchmark]
1577 /// #[bench::delay(
1578 /// setup = setup_tcp_server(),
1579 /// config = BinaryBenchmarkConfig::default()
1580 /// .setup_parallel(true)
1581 /// )]
1582 /// fn bench_binary() -> iai_callgrind::Command {
1583 /// Command::new(env!("CARGO_BIN_EXE_my-echo"))
1584 /// .delay(
1585 /// Delay::new(
1586 /// DelayKind::TcpConnect("127.0.0.1:31000".parse::<SocketAddr>().unwrap()))
1587 /// .timeout(Duration::from_millis(500))
1588 /// ).build()
1589 /// }
1590 ///
1591 /// binary_benchmark_group!(name = delay; benchmarks = bench_binary);
1592 /// # fn main() {
1593 /// main!(binary_benchmark_groups = delay);
1594 /// # }
1595 /// ```
1596 pub fn setup_parallel(&mut self, setup_parallel: bool) -> &mut Self {
1597 self.0.setup_parallel = Some(setup_parallel);
1598 self
1599 }
1600}
1601
1602impl BinaryBenchmarkGroup {
1603 /// Add a [`BinaryBenchmark`] to this group
1604 ///
1605 /// This can be a binary benchmark created with [`BinaryBenchmark::new`] or a
1606 /// [`crate::binary_benchmark`] attributed function addable with the
1607 /// [`crate::binary_benchmark_attribute`] macro.
1608 ///
1609 /// It is an error to add a [`BinaryBenchmark`] without having added a [`Bench`] to it.
1610 ///
1611 /// # Examples
1612 ///
1613 /// Add a [`BinaryBenchmark`] to this group
1614 ///
1615 /// ```rust
1616 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1617 /// use iai_callgrind::{binary_benchmark_group, BinaryBenchmark, Bench, BinaryBenchmarkGroup};
1618 ///
1619 /// fn setup_my_group(group: &mut BinaryBenchmarkGroup) {
1620 /// group.binary_benchmark(BinaryBenchmark::new("bench_binary")
1621 /// .bench(Bench::new("foo")
1622 /// .command(iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-foo"))
1623 /// .arg("foo")
1624 /// )
1625 /// )
1626 /// );
1627 /// }
1628 ///
1629 /// binary_benchmark_group!(
1630 /// name = my_group;
1631 /// benchmarks = |group: &mut BinaryBenchmarkGroup| setup_my_group(group)
1632 /// );
1633 /// # fn main() {}
1634 /// ```
1635 ///
1636 /// Or, add a `#[binary_benchmark]` annotated function to this group:
1637 ///
1638 /// ```rust
1639 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1640 /// use iai_callgrind::{binary_benchmark, binary_benchmark_group, binary_benchmark_attribute};
1641 ///
1642 /// #[binary_benchmark]
1643 /// #[bench::foo("foo")]
1644 /// fn bench_binary(arg: &str) -> iai_callgrind::Command {
1645 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-foo"))
1646 /// .arg(arg)
1647 /// .build()
1648 /// }
1649 ///
1650 /// binary_benchmark_group!(
1651 /// name = my_group;
1652 /// benchmarks = |group: &mut BinaryBenchmarkGroup| {
1653 /// group
1654 /// .binary_benchmark(binary_benchmark_attribute!(bench_binary))
1655 /// }
1656 /// );
1657 /// # fn main() {}
1658 /// ```
1659 pub fn binary_benchmark<T>(&mut self, binary_benchmark: T) -> &mut Self
1660 where
1661 T: Into<BinaryBenchmark>,
1662 {
1663 self.binary_benchmarks.push(binary_benchmark.into());
1664 self
1665 }
1666
1667 /// Add multiple [`BinaryBenchmark`]s at once
1668 pub fn binary_benchmarks<I, T>(&mut self, binary_benchmarks: T) -> &mut Self
1669 where
1670 I: Into<BinaryBenchmark>,
1671 T: IntoIterator<Item = I>,
1672 {
1673 self.binary_benchmarks
1674 .extend(binary_benchmarks.into_iter().map(Into::into));
1675 self
1676 }
1677}
1678
1679impl Command {
1680 /// Create a new [`Command`] which is run under valgrind.
1681 ///
1682 /// Use
1683 /// [`env!("CARGO_BIN_EXE_<name>)`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates)
1684 /// to provide the path to an executable of your project instead of `target/release/<name>`.
1685 ///
1686 /// This `Command` is a builder for the binary which is going to be benchmarked but is not
1687 /// executed right away. We simply gather all the information to be finally able to execute the
1688 /// command under valgrind, later (after we collected all the commands in this benchmark file).
1689 /// As opposed to [`std::process::Command`], the build is finalized with [`Command::build`].
1690 ///
1691 /// # Relative paths
1692 ///
1693 /// Relative paths are interpreted relative to the current directory and if not running the
1694 /// benchmarks in a [`Sandbox`], depends on where `cargo bench` sets the current directory.
1695 /// Usually, it's best to use [`Path::canonicalize`] to resolve the relative path to a binary in
1696 /// your project's directory. In case you're running the benchmark in a [`Sandbox`], the path is
1697 /// interpreted relative to the root directory of the `Sandbox`. Iai-Callgrind tries to resolve
1698 /// simple names like `Command::new("echo")` searching the `$PATH`. To disambiguate between
1699 /// simple names and relative paths, use `./`. For example `echo` is searched in the `$PATH` and
1700 /// `./echo` is interpreted relative.
1701 ///
1702 /// # Examples
1703 ///
1704 /// Assume the project's binary or one of your project's binaries name is `my-echo`:
1705 ///
1706 /// ```
1707 /// # macro_rules! env { ($m:tt) => {{ "/home/my_project/target/release/my-echo" }} }
1708 /// use iai_callgrind::Command;
1709 ///
1710 /// let command = Command::new(env!("CARGO_BIN_EXE_my-echo"));
1711 /// ```
1712 ///
1713 /// or use your system's echo from the `$PATH` with
1714 ///
1715 /// ```
1716 /// use iai_callgrind::Command;
1717 ///
1718 /// let command = Command::new("echo").arg("foo").build();
1719 /// ```
1720 pub fn new<T>(path: T) -> Self
1721 where
1722 T: AsRef<OsStr>,
1723 {
1724 Self(__internal::InternalCommand {
1725 path: PathBuf::from(path.as_ref()),
1726 ..Default::default()
1727 })
1728 }
1729
1730 /// Delay the execution of the [`Command`]
1731 ///
1732 /// This option allows to delay the [`Command`] execution till a certain event has happened.
1733 /// Supported events are:
1734 /// - Timer expired
1735 /// - File path exists
1736 /// - TCP/UDP connect succeeded
1737 ///
1738 /// [`Delay`] can be used in combination with [`Command::setup_parallel`] to wait for an event
1739 /// that is triggered within the `setup()` function. E.g. the setup starts a server that is
1740 /// needed by the [`Command`].
1741 ///
1742 /// # Examples
1743 ///
1744 /// ```rust
1745 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1746 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, main, Command, Delay, DelayKind};
1747 ///
1748 /// fn start_server() {
1749 /// // action to start the server, creates pid file
1750 /// std::fs::File::create("/tmp/my-server.pid").unwrap();
1751 /// }
1752 ///
1753 /// #[binary_benchmark]
1754 /// #[bench::server(setup = start_server)]
1755 /// fn bench_binary() -> Command {
1756 /// Command::new(env!("CARGO_BIN_EXE_my-echo"))
1757 /// .setup_parallel(true)
1758 /// .delay(Delay::new(DelayKind::PathExists("/tmp/my-server.pid".into())))
1759 /// .build()
1760 /// }
1761 ///
1762 /// binary_benchmark_group!(name = my_group; benchmarks = bench_binary);
1763 /// # fn main() {
1764 /// main!(binary_benchmark_groups = my_group);
1765 /// # }
1766 /// ```
1767 pub fn delay<T: Into<Delay>>(&mut self, delay: T) -> &mut Self {
1768 self.0.delay = Some(delay.into().into());
1769 self
1770 }
1771
1772 /// Execute the `setup()` in parallel to the [`Command`].
1773 ///
1774 /// This option changes the execution flow in a way that the [`Command`] is executed parallel
1775 /// to the `setup` instead of waiting for the `setup` to complete.
1776 ///
1777 /// This can be combined with the usage of [`Delay`] to further control the timing when the
1778 /// [`Command`] is executed.
1779 ///
1780 /// # Examples
1781 ///
1782 /// ```rust
1783 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1784 /// use std::time::Duration;
1785 /// use std::net::{SocketAddr, TcpListener};
1786 /// use std::thread;
1787 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, main, Command, Delay, DelayKind};
1788 ///
1789 /// fn setup_tcp_server() {
1790 /// thread::sleep(Duration::from_millis(300));
1791 /// let _listener = TcpListener::bind("127.0.0.1:31000".parse::<SocketAddr>().unwrap()).unwrap();
1792 /// thread::sleep(Duration::from_secs(1));
1793 /// }
1794 ///
1795 /// #[binary_benchmark]
1796 /// #[bench::delay(setup = setup_tcp_server())]
1797 /// fn bench_binary() -> iai_callgrind::Command {
1798 /// Command::new(env!("CARGO_BIN_EXE_my-echo"))
1799 /// .setup_parallel(true)
1800 /// .delay(
1801 /// Delay::new(
1802 /// DelayKind::TcpConnect("127.0.0.1:31000".parse::<SocketAddr>().unwrap()))
1803 /// .timeout(Duration::from_millis(500))
1804 /// ).build()
1805 /// }
1806 ///
1807 /// binary_benchmark_group!(name = delay; benchmarks = bench_binary);
1808 /// # fn main() {
1809 /// main!(binary_benchmark_groups = delay);
1810 /// # }
1811 /// ```
1812 pub fn setup_parallel(&mut self, setup_parallel: bool) -> &mut Self {
1813 self.0.config.setup_parallel = Some(setup_parallel);
1814 self
1815 }
1816
1817 /// Adds an argument to pass to the [`Command`]
1818 ///
1819 /// This option works exactly the same way as [`std::process::Command::arg`]. To pass multiple
1820 /// arguments see [`Command::args`].
1821 ///
1822 /// # Examples
1823 ///
1824 /// ```rust
1825 /// # use iai_callgrind::main;
1826 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1827 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark};
1828 ///
1829 /// #[binary_benchmark]
1830 /// fn bench_binary() -> iai_callgrind::Command {
1831 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-echo"))
1832 /// .arg("foo")
1833 /// .build()
1834 /// }
1835 ///
1836 /// binary_benchmark_group!(
1837 /// name = my_group;
1838 /// benchmarks = bench_binary
1839 /// );
1840 /// # fn main() {
1841 /// # main!(binary_benchmark_groups = my_group);
1842 /// # }
1843 /// ```
1844 pub fn arg<T>(&mut self, arg: T) -> &mut Self
1845 where
1846 T: Into<OsString>,
1847 {
1848 self.0.args.push(arg.into());
1849 self
1850 }
1851
1852 /// Adds multiple arguments to pass to the [`Command`]
1853 ///
1854 /// This option works exactly the same way as [`std::process::Command::args`].
1855 ///
1856 /// # Examples
1857 ///
1858 /// The following will execute `my-echo foo`.
1859 ///
1860 /// ```rust
1861 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1862 /// # use iai_callgrind::main;
1863 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark};
1864 ///
1865 /// #[binary_benchmark]
1866 /// fn bench_binary() -> iai_callgrind::Command {
1867 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-echo"))
1868 /// .arg("foo")
1869 /// .build()
1870 /// }
1871 ///
1872 /// binary_benchmark_group!(
1873 /// name = my_group;
1874 /// benchmarks = bench_binary
1875 /// );
1876 /// # fn main() {
1877 /// # main!(binary_benchmark_groups = my_group);
1878 /// # }
1879 /// ```
1880 pub fn args<I, T>(&mut self, args: T) -> &mut Self
1881 where
1882 I: Into<OsString>,
1883 T: IntoIterator<Item = I>,
1884 {
1885 self.0.args.extend(args.into_iter().map(Into::into));
1886 self
1887 }
1888
1889 /// Configuration for the process's standard input ([`Stdin`])
1890 ///
1891 /// This method takes an [`Stdin`], [`Stdio`] and everything that implements `Into<Stdin>`. The
1892 /// [`Stdin`] enum mirrors most of the possibilities of [`std::process::Stdio`] but also some
1893 /// additional possibilities most notably [`Stdin::Setup`] (see there for more details).
1894 ///
1895 /// Per default, the stdin is not inherited from the parent and any attempt by the child process
1896 /// to read from the stdin stream will result in the stream immediately closing.
1897 ///
1898 /// The options you might be interested in the most are [`Stdin::File`], which mirrors the
1899 /// behaviour of [`std::process::Stdio`] if `Stdio` is a [`std::fs::File`], and
1900 /// [`Stdin::Setup`], which is special to `iai-callgrind` and lets you pipe the output of
1901 /// the `setup` function into the Stdin of this [`Command`]. If you need to delay the `Command`
1902 /// when using [`Stdin::Setup`], you can do so with [`Command::delay`].
1903 ///
1904 /// # Implementation details
1905 ///
1906 /// As the [`Command`] itself is not executed immediately, the [`std::process::Stdio`] is not
1907 /// created either. We only use the information from here to create the [`std::process::Stdio`]
1908 /// later after we collected all commands. Setting the Stdin to `Inherit` is discouraged and
1909 /// won't have the effect you might expect, since the benchmark runner (the parent) uses the
1910 /// Stdin for its own purposes and closes it before this [`Command`] is executed.
1911 ///
1912 /// # Examples
1913 ///
1914 /// Pipe the content of a file (`benches/fixture.txt`) into the stdin of this [`Command`]:
1915 ///
1916 /// ```rust
1917 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1918 /// # use iai_callgrind::main;
1919 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdio};
1920 ///
1921 /// #[binary_benchmark]
1922 /// fn bench_binary() -> iai_callgrind::Command {
1923 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
1924 /// .stdin(Stdio::File("benches/fixture.txt".into()))
1925 /// .build()
1926 /// }
1927 ///
1928 /// binary_benchmark_group!(
1929 /// name = my_group;
1930 /// benchmarks = bench_binary
1931 /// );
1932 /// # fn main() {
1933 /// # main!(binary_benchmark_groups = my_group);
1934 /// # }
1935 /// ```
1936 ///
1937 /// Pipe the Stdout of setup into the Stdin of this [`Command`]:
1938 ///
1939 /// ```rust
1940 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1941 /// # use iai_callgrind::main;
1942 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdin, Pipe};
1943 ///
1944 /// fn setup_pipe() {
1945 /// // All output to Stdout of this function will be piped into the Stdin of `my-exe`
1946 /// println!("This string will be piped into the stdin of my-exe");
1947 /// }
1948 ///
1949 /// #[binary_benchmark(setup = setup_pipe())]
1950 /// fn bench_binary() -> iai_callgrind::Command {
1951 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
1952 /// .stdin(Stdin::Setup(Pipe::Stdout))
1953 /// .build()
1954 /// }
1955 ///
1956 /// binary_benchmark_group!(
1957 /// name = my_group;
1958 /// benchmarks = bench_binary
1959 /// );
1960 /// # fn main() {
1961 /// # main!(binary_benchmark_groups = my_group);
1962 /// # }
1963 pub fn stdin<T>(&mut self, stdin: T) -> &mut Self
1964 where
1965 T: Into<Stdin>,
1966 {
1967 self.0.stdin = Some(stdin.into());
1968 self
1969 }
1970
1971 /// Configuration for the [`Command`]s standard output (Stdout) handle.
1972 ///
1973 /// The output of benchmark commands and functions are usually captured by the benchmark runner.
1974 /// This can be changed for example with the `--nocapture` option or here. Any option specified
1975 /// here takes precedence over the changes which `--nocapture` makes to the Stdout of the
1976 /// command.
1977 ///
1978 /// # Examples
1979 ///
1980 /// To see the output of this [`Command`] regardless of `--nocapture` in the benchmark output
1981 ///
1982 /// ```rust
1983 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
1984 /// # use iai_callgrind::main;
1985 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdio};
1986 ///
1987 /// #[binary_benchmark]
1988 /// fn bench_binary() -> iai_callgrind::Command {
1989 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
1990 /// .stdout(Stdio::Inherit)
1991 /// .build()
1992 /// }
1993 ///
1994 /// binary_benchmark_group!(
1995 /// name = my_group;
1996 /// benchmarks = bench_binary
1997 /// );
1998 /// # fn main() {
1999 /// # main!(binary_benchmark_groups = my_group);
2000 /// # }
2001 /// ```
2002 ///
2003 /// To pipe the Stdout into a file `/tmp/benchmark.output`:
2004 ///
2005 /// ```rust
2006 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2007 /// # use iai_callgrind::main;
2008 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdio};
2009 /// use std::path::PathBuf;
2010 ///
2011 /// #[binary_benchmark]
2012 /// fn bench_binary() -> iai_callgrind::Command {
2013 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2014 /// .stdout(Stdio::File("/tmp/benchmark.output".into()))
2015 /// // or
2016 /// .stdout(PathBuf::from("/tmp/benchmark.output"))
2017 /// .build()
2018 /// }
2019 ///
2020 /// binary_benchmark_group!(
2021 /// name = my_group;
2022 /// benchmarks = bench_binary
2023 /// );
2024 /// # fn main() {
2025 /// # main!(binary_benchmark_groups = my_group);
2026 /// # }
2027 /// ```
2028 pub fn stdout<T>(&mut self, stdio: T) -> &mut Self
2029 where
2030 T: Into<Stdio>,
2031 {
2032 self.0.stdout = Some(stdio.into());
2033 self
2034 }
2035
2036 /// Configuration for the [`Command`]s standard error output (Stderr) handle.
2037 ///
2038 /// This option is similar to [`Command::stdout`] but configures the Stderr. See there for more
2039 /// details.
2040 ///
2041 /// # Examples
2042 ///
2043 /// To see the error output of this [`Command`] regardless of `--nocapture` in the benchmark
2044 /// output
2045 ///
2046 /// ```rust
2047 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2048 /// # use iai_callgrind::main;
2049 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdio};
2050 ///
2051 /// #[binary_benchmark]
2052 /// fn bench_binary() -> iai_callgrind::Command {
2053 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2054 /// .stderr(Stdio::Inherit)
2055 /// .build()
2056 /// }
2057 ///
2058 /// binary_benchmark_group!(
2059 /// name = my_group;
2060 /// benchmarks = bench_binary
2061 /// );
2062 /// # fn main() {
2063 /// # main!(binary_benchmark_groups = my_group);
2064 /// # }
2065 /// ```
2066 ///
2067 /// To pipe the Stderr into a file `/tmp/benchmark.output`:
2068 ///
2069 /// ```rust
2070 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2071 /// # use iai_callgrind::main;
2072 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Stdio};
2073 /// use std::path::PathBuf;
2074 ///
2075 /// #[binary_benchmark]
2076 /// fn bench_binary() -> iai_callgrind::Command {
2077 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2078 /// .stderr(Stdio::File("/tmp/benchmark.output".into()))
2079 /// // or
2080 /// .stderr(PathBuf::from("/tmp/benchmark.output"))
2081 /// .build()
2082 /// }
2083 ///
2084 /// binary_benchmark_group!(
2085 /// name = my_group;
2086 /// benchmarks = bench_binary
2087 /// );
2088 /// # fn main() {
2089 /// # main!(binary_benchmark_groups = my_group);
2090 /// # }
2091 /// ```
2092 pub fn stderr<T>(&mut self, stdio: T) -> &mut Self
2093 where
2094 T: Into<Stdio>,
2095 {
2096 self.0.stderr = Some(stdio.into());
2097 self
2098 }
2099
2100 /// Add an environment variable available for this [`Command`]
2101 ///
2102 /// These environment variables are available independently of the setting of
2103 /// [`BinaryBenchmarkConfig::env_clear`] and additive to environment variables added with
2104 /// [`BinaryBenchmarkConfig::env`].
2105 ///
2106 /// # Examples
2107 ///
2108 /// An example for a custom environment variable "FOO=BAR":
2109 ///
2110 /// ```rust
2111 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2112 /// # use iai_callgrind::main;
2113 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark};
2114 ///
2115 /// #[binary_benchmark]
2116 /// fn bench_binary() -> iai_callgrind::Command {
2117 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2118 /// .env("FOO", "BAR")
2119 /// .build()
2120 /// }
2121 ///
2122 /// binary_benchmark_group!(
2123 /// name = my_group;
2124 /// benchmarks = bench_binary
2125 /// );
2126 /// # fn main() {
2127 /// # main!(binary_benchmark_groups = my_group);
2128 /// # }
2129 pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
2130 where
2131 K: Into<OsString>,
2132 V: Into<OsString>,
2133 {
2134 self.0.config.envs.push((key.into(), Some(value.into())));
2135 self
2136 }
2137
2138 /// Add multiple environment variables available for this [`Command`]
2139 ///
2140 /// See [`Command::env`] for more details.
2141 ///
2142 /// # Examples
2143 ///
2144 /// Add the custom environment variables "FOO=BAR" and `BAR=BAZ`:
2145 ///
2146 /// ```rust
2147 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2148 /// # use iai_callgrind::main;
2149 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark};
2150 ///
2151 /// #[binary_benchmark]
2152 /// fn bench_binary() -> iai_callgrind::Command {
2153 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2154 /// .envs([("FOO", "BAR"), ("BAR", "BAZ")])
2155 /// .build()
2156 /// }
2157 ///
2158 /// binary_benchmark_group!(
2159 /// name = my_group;
2160 /// benchmarks = bench_binary
2161 /// );
2162 /// # fn main() {
2163 /// # main!(binary_benchmark_groups = my_group);
2164 /// # }
2165 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
2166 where
2167 I: IntoIterator<Item = (K, V)>,
2168 K: Into<OsString>,
2169 V: Into<OsString>,
2170 {
2171 self.0
2172 .config
2173 .envs
2174 .extend(vars.into_iter().map(|(k, v)| (k.into(), Some(v.into()))));
2175 self
2176 }
2177
2178 /// Set the directory of the benchmarked binary (Default: Unchanged)
2179 ///
2180 /// See also [`BinaryBenchmarkConfig::current_dir`]
2181 ///
2182 /// # Examples
2183 ///
2184 /// To set the working directory of your [`Command`] to `/tmp`:
2185 ///
2186 /// ```rust
2187 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2188 /// # use iai_callgrind::main;
2189 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark};
2190 ///
2191 /// #[binary_benchmark]
2192 /// fn bench_binary() -> iai_callgrind::Command {
2193 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2194 /// .current_dir("/tmp")
2195 /// .build()
2196 /// }
2197 ///
2198 /// binary_benchmark_group!(
2199 /// name = my_group;
2200 /// benchmarks = bench_binary
2201 /// );
2202 /// # fn main() {
2203 /// # main!(binary_benchmark_groups = my_group);
2204 /// # }
2205 /// ```
2206 ///
2207 /// and the following will change the current directory to `fixtures` located in the root of the
2208 /// [`BinaryBenchmarkConfig::sandbox`]
2209 ///
2210 /// ```rust
2211 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2212 /// # use iai_callgrind::main;
2213 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, Sandbox, BinaryBenchmarkConfig};
2214 ///
2215 /// fn setup_sandbox() {
2216 /// std::fs::create_dir("fixtures").unwrap();
2217 /// }
2218 ///
2219 /// #[binary_benchmark(
2220 /// setup = setup_sandbox(),
2221 /// config = BinaryBenchmarkConfig::default().sandbox(Sandbox::new(true))
2222 /// )]
2223 /// fn bench_binary() -> iai_callgrind::Command {
2224 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2225 /// .current_dir("fixtures")
2226 /// .build()
2227 /// }
2228 ///
2229 /// binary_benchmark_group!(
2230 /// name = my_group;
2231 /// benchmarks = bench_binary
2232 /// );
2233 /// # fn main() {
2234 /// # main!(binary_benchmark_groups = my_group);
2235 /// # }
2236 /// ```
2237 pub fn current_dir<T>(&mut self, value: T) -> &mut Self
2238 where
2239 T: Into<PathBuf>,
2240 {
2241 self.0.config.current_dir = Some(value.into());
2242 self
2243 }
2244
2245 /// Set the expected exit status [`ExitWith`] of this [`Command`]
2246 ///
2247 /// See also [`BinaryBenchmarkConfig::exit_with`]. This setting overwrites the setting of the
2248 /// [`BinaryBenchmarkConfig`].
2249 ///
2250 /// # Examples
2251 ///
2252 /// If the command is expected to exit with a specific code, for example `100`:
2253 ///
2254 /// ```rust
2255 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2256 /// # use iai_callgrind::main;
2257 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, ExitWith};
2258 ///
2259 /// #[binary_benchmark]
2260 /// fn bench_binary() -> iai_callgrind::Command {
2261 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2262 /// .exit_with(ExitWith::Code(100))
2263 /// .build()
2264 /// }
2265 ///
2266 /// binary_benchmark_group!(
2267 /// name = my_group;
2268 /// benchmarks = bench_binary
2269 /// );
2270 /// # fn main() {
2271 /// # main!(binary_benchmark_groups = my_group);
2272 /// # }
2273 /// ```
2274 ///
2275 /// If a command is expected to fail, but the exit code doesn't matter:
2276 ///
2277 /// ```rust
2278 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2279 /// # use iai_callgrind::main;
2280 /// use iai_callgrind::{binary_benchmark_group, binary_benchmark, ExitWith};
2281 ///
2282 /// #[binary_benchmark]
2283 /// fn bench_binary() -> iai_callgrind::Command {
2284 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-exe"))
2285 /// .exit_with(ExitWith::Failure)
2286 /// .build()
2287 /// }
2288 ///
2289 /// binary_benchmark_group!(
2290 /// name = my_group;
2291 /// benchmarks = bench_binary
2292 /// );
2293 /// # fn main() {
2294 /// # main!(binary_benchmark_groups = my_group);
2295 /// # }
2296 /// ```
2297 pub fn exit_with(&mut self, exit_with: ExitWith) -> &mut Self {
2298 self.0.config.exit_with = Some(exit_with.into());
2299 self
2300 }
2301
2302 /// Finalize and build this [`Command`]
2303 ///
2304 /// # Examples
2305 ///
2306 /// ```rust
2307 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2308 /// use iai_callgrind::Command;
2309 ///
2310 /// let command: Command = Command::new(env!("CARGO_BIN_EXE_my-exe"))
2311 /// .arg("foo")
2312 /// .build();
2313 /// ```
2314 #[must_use]
2315 pub fn build(&mut self) -> Self {
2316 self.clone()
2317 }
2318}
2319
2320impl From<&mut Command> for Command {
2321 fn from(value: &mut Command) -> Self {
2322 value.clone()
2323 }
2324}
2325
2326impl From<&Command> for Command {
2327 fn from(value: &Command) -> Self {
2328 value.clone()
2329 }
2330}
2331
2332impl Delay {
2333 /// Instantiate a [`Delay`] which will wait until a path exists ([`Path::exists`]).
2334 ///
2335 /// ```rust
2336 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2337 ///
2338 /// use iai_callgrind::{Command, Delay};
2339 ///
2340 /// let command = Command::new("echo").delay(Delay::from_path("/your/path/to/wait/for"));
2341 /// ```
2342 pub fn from_path<T>(path: T) -> Self
2343 where
2344 T: Into<PathBuf>,
2345 {
2346 Self(__internal::InternalDelay {
2347 kind: DelayKind::PathExists(path.into()),
2348 ..Default::default()
2349 })
2350 }
2351
2352 /// Instantiate a [`Delay`] which will wait until successful TCP connect
2353 /// ([`std::net::TcpStream::connect`]).
2354 ///
2355 /// ```rust
2356 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2357 ///
2358 /// use std::net::SocketAddr;
2359 ///
2360 /// use iai_callgrind::{Command, Delay};
2361 ///
2362 /// let command = Command::new("echo").delay(Delay::from_tcp_socket(
2363 /// "127.0.0.1:31000".parse::<SocketAddr>().unwrap(),
2364 /// ));
2365 /// ```
2366 pub fn from_tcp_socket<T>(addr: T) -> Self
2367 where
2368 T: Into<SocketAddr>,
2369 {
2370 Self(__internal::InternalDelay {
2371 kind: DelayKind::TcpConnect(addr.into()),
2372 ..Default::default()
2373 })
2374 }
2375
2376 /// Instantiate a [`Delay`] which will wait until a UDP response is received after
2377 /// sending the UDP request. The poll duration is also used as the reconnect and request resend
2378 /// interval. ([`std::net::UdpSocket::bind`], [`std::net::UdpSocket::connect`],
2379 /// [`std::net::UdpSocket::recv`]).
2380 ///
2381 /// ```rust
2382 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2383 ///
2384 /// use std::net::SocketAddr;
2385 ///
2386 /// use iai_callgrind::{Command, Delay};
2387 ///
2388 /// let command = Command::new("echo").delay(Delay::from_udp_request(
2389 /// "127.0.0.1:34000".parse::<SocketAddr>().unwrap(),
2390 /// vec![1],
2391 /// ));
2392 /// ```
2393 pub fn from_udp_request<T, U>(addr: T, req: U) -> Self
2394 where
2395 T: Into<SocketAddr>,
2396 U: Into<Vec<u8>>,
2397 {
2398 Self(__internal::InternalDelay {
2399 kind: DelayKind::UdpResponse(addr.into(), req.into()),
2400 ..Default::default()
2401 })
2402 }
2403
2404 /// Instantiate a [`Delay`] waiting until an event has happened.
2405 ///
2406 /// The possible events are defined in [`DelayKind`].
2407 ///
2408 /// ```rust
2409 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2410 ///
2411 /// use std::time::Duration;
2412 ///
2413 /// use iai_callgrind::{Command, Delay, DelayKind};
2414 ///
2415 /// let command = Command::new("echo").delay(Delay::new(DelayKind::DurationElapse(
2416 /// Duration::from_secs(15),
2417 /// )));
2418 /// ```
2419 pub fn new(kind: DelayKind) -> Self {
2420 Self(__internal::InternalDelay {
2421 kind,
2422 ..Default::default()
2423 })
2424 }
2425
2426 /// Update the [`Delay`] poll interval.
2427 ///
2428 /// The poll interval should be considered together with the [`Delay::timeout`], and ideally
2429 /// should have a value of `n * timeout duration`.
2430 ///
2431 /// In case the poll interval is set to a value `>=` timeout duration it is attempted to set
2432 /// the poll interval to a value of `timeout duration - 5ms`.
2433 ///
2434 /// ```rust
2435 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2436 ///
2437 /// use std::net::SocketAddr;
2438 /// use std::time::Duration;
2439 ///
2440 /// use iai_callgrind::{Command, Delay};
2441 ///
2442 /// let command = Command::new("echo").delay(
2443 /// Delay::from_udp_request("127.0.0.1:34000".parse::<SocketAddr>().unwrap(), vec![1])
2444 /// .poll(Duration::from_millis(150)),
2445 /// );
2446 /// ```
2447 #[must_use]
2448 pub fn poll<T: Into<Duration>>(mut self, duration: T) -> Self {
2449 self.0.poll = Some(duration.into());
2450 self
2451 }
2452
2453 /// Update the [`Delay`] timeout interval.
2454 ///
2455 /// The timeout duration should be considered together with the poll interval. For further
2456 /// details please refer to [`Delay::poll`]. The minimum timeout duration is `10ms`.
2457 ///
2458 /// ```rust
2459 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2460 ///
2461 /// use std::net::SocketAddr;
2462 /// use std::time::Duration;
2463 ///
2464 /// use iai_callgrind::{Command, Delay};
2465 /// let command = Command::new("echo").delay(
2466 /// Delay::from_tcp_socket("127.0.0.1:31000".parse::<SocketAddr>().unwrap())
2467 /// .timeout(Duration::from_secs(5)),
2468 /// );
2469 /// ```
2470 #[must_use]
2471 pub fn timeout<T: Into<Duration>>(mut self, duration: T) -> Self {
2472 self.0.timeout = Some(duration.into());
2473 self
2474 }
2475}
2476
2477impl<T> From<T> for Delay
2478where
2479 T: Into<Duration>,
2480{
2481 /// Instantiate a [`Delay`] which will wait until the duration has elapsed.
2482 ///
2483 /// ```rust
2484 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2485 ///
2486 /// use std::time::Duration;
2487 ///
2488 /// use iai_callgrind::{Command, Delay};
2489 ///
2490 /// let command = Command::new("echo").delay(Delay::from(Duration::from_secs(10)));
2491 /// ```
2492 fn from(duration: T) -> Self {
2493 Self(__internal::InternalDelay {
2494 kind: DelayKind::DurationElapse(duration.into()),
2495 ..Default::default()
2496 })
2497 }
2498}
2499
2500impl From<ExitWith> for __internal::InternalExitWith {
2501 fn from(value: ExitWith) -> Self {
2502 match value {
2503 ExitWith::Success => Self::Success,
2504 ExitWith::Failure => Self::Failure,
2505 ExitWith::Code(c) => Self::Code(c),
2506 }
2507 }
2508}
2509
2510impl From<&ExitWith> for __internal::InternalExitWith {
2511 fn from(value: &ExitWith) -> Self {
2512 match value {
2513 ExitWith::Success => Self::Success,
2514 ExitWith::Failure => Self::Failure,
2515 ExitWith::Code(c) => Self::Code(*c),
2516 }
2517 }
2518}
2519
2520impl Sandbox {
2521 /// Create a new `Sandbox` builder
2522 ///
2523 /// Per default, a [`Command`] is not run in a `Sandbox` because setting up a `Sandbox` usually
2524 /// involves some user interaction, for example copying fixtures into it with
2525 /// [`Sandbox::fixtures`].
2526 ///
2527 /// The temporary directory is only created immediately before the `setup` and the [`Command`]
2528 /// are executed.
2529 ///
2530 /// # Examples
2531 ///
2532 /// Enable the sandbox for all benchmarks
2533 ///
2534 /// ```rust
2535 /// use iai_callgrind::{BinaryBenchmarkConfig, Sandbox, main};
2536 /// # use iai_callgrind::binary_benchmark_group;
2537 /// # binary_benchmark_group!(name = my_group; benchmarks = |_group| {});
2538 /// # fn main() {
2539 /// main!(
2540 /// config = BinaryBenchmarkConfig::default().sandbox(Sandbox::new(true));
2541 /// binary_benchmark_groups = my_group
2542 /// );
2543 /// # }
2544 /// ```
2545 pub fn new(enabled: bool) -> Self {
2546 Self(__internal::InternalSandbox {
2547 enabled: Some(enabled),
2548 ..Default::default()
2549 })
2550 }
2551
2552 /// Specify the directories and/or files you want to copy into the root of the `Sandbox`
2553 ///
2554 /// The paths are interpreted relative to the workspace root as it is reported by `cargo`. In a
2555 /// multi-crate project this is the directory with the top-level `Cargo.toml`. Otherwise, it is
2556 /// simply the directory with your `Cargo.toml` file in it.
2557 ///
2558 /// # Examples
2559 ///
2560 /// Assuming you crate's binary is called `my-foo` taking a file path as the first argument and
2561 /// the fixtures directory is `$WORKSPACE_ROOT/benches/fixtures` containing a fixture
2562 /// `fix_1.txt`:
2563 ///
2564 /// ```rust
2565 /// # macro_rules! env { ($m:tt) => {{ "/some/path" }} }
2566 /// # use iai_callgrind::{binary_benchmark_group, main};
2567 /// use iai_callgrind::{binary_benchmark, BinaryBenchmarkConfig, Sandbox};
2568 ///
2569 /// #[binary_benchmark]
2570 /// #[bench::fix_1(
2571 /// args = ("fix_1.txt"),
2572 /// config = BinaryBenchmarkConfig::default()
2573 /// .sandbox(Sandbox::new(true)
2574 /// .fixtures(["benches/fixtures/fix_1.txt"])
2575 /// )
2576 /// )]
2577 /// fn bench_with_fixtures(path: &str) -> iai_callgrind::Command {
2578 /// iai_callgrind::Command::new(env!("CARGO_BIN_EXE_my-foo"))
2579 /// .arg(path)
2580 /// .build()
2581 /// }
2582 /// # binary_benchmark_group!(name = my_group; benchmarks = bench_with_fixtures);
2583 /// # fn main() { main!(binary_benchmark_groups = my_group); }
2584 /// ```
2585 pub fn fixtures<I, T>(&mut self, paths: T) -> &mut Self
2586 where
2587 I: Into<PathBuf>,
2588 T: IntoIterator<Item = I>,
2589 {
2590 self.0.fixtures.extend(paths.into_iter().map(Into::into));
2591 self
2592 }
2593}
2594
2595#[cfg(test)]
2596mod tests {
2597 use rstest::rstest;
2598
2599 use super::*;
2600
2601 #[rstest]
2602 #[case::empty("")]
2603 #[case::simple_invalid("-")]
2604 #[case::when_0_char_minus_1("a\x2f")]
2605 #[case::when_9_char_plus_1("a\x3a")]
2606 #[case::when_big_a_char_minus_1("\x40")]
2607 #[case::when_big_z_char_plus_1("\x5b")]
2608 #[case::when_low_a_char_minus_1("\x60")]
2609 #[case::when_low_z_char_plus_1("\x7b")]
2610 #[case::invalid_2nd("a-")]
2611 #[case::invalid_3rd("ab-")]
2612 #[case::all_invalid("---")]
2613 #[case::number_as_first("0")]
2614 // This would be a valid rust identifier, but we don't allow the whole set
2615 #[case::non_ascii_1st("µ")]
2616 #[case::non_ascii_2nd("aµ")]
2617 #[case::non_ascii_3rd("aaµ")]
2618 #[case::non_ascii_middle("aµa")]
2619 fn test_benchmark_id_validate_when_error(#[case] id: &str) {
2620 let id = BenchmarkId::new(id);
2621 assert!(id.validate().is_err());
2622 }
2623
2624 #[rstest]
2625 #[case::lowercase_a("a")]
2626 #[case::lowercase_b("b")]
2627 #[case::lowercase_m("m")]
2628 #[case::lowercase_y("y")]
2629 #[case::lowercase_z("z")]
2630 #[case::uppercase_a("A")]
2631 #[case::uppercase_b("B")]
2632 #[case::uppercase_n("N")]
2633 #[case::uppercase_y("Y")]
2634 #[case::uppercase_z("Z")]
2635 #[case::zero_2nd("a0")]
2636 #[case::one_2nd("a1")]
2637 #[case::eight_2nd("a8")]
2638 #[case::nine_2nd("a9")]
2639 #[case::number_middle("b4t")]
2640 #[case::underscore("_")]
2641 #[case::only_underscore("___")]
2642 #[case::underscore_last("a_")]
2643 #[case::mixed_all("auAEwer9__2xcd")]
2644 fn test_benchmark_id_validate(#[case] id: &str) {
2645 let id = BenchmarkId::new(id);
2646 assert!(id.validate().is_ok());
2647 }
2648
2649 #[rstest]
2650 #[case::empty("", "Invalid id: Cannot be empty")]
2651 #[case::non_ascii_first(
2652 "µ",
2653 "Invalid id 'µ': Encountered non-ascii character as first character"
2654 )]
2655 #[case::multibyte_middle(
2656 "aµ",
2657 "Invalid id 'aµ' at position 1: Encountered non-ascii character"
2658 )]
2659 #[case::non_ascii_middle("a-", "Invalid id 'a-' at position 1: Invalid character '-'")]
2660 #[case::invalid_first("-", "Invalid id '-': As first character is '-' not allowed")]
2661 fn test_benchmark_id_validate_error_message(#[case] id: &str, #[case] message: &str) {
2662 let id = BenchmarkId::new(id);
2663 assert_eq!(
2664 id.validate()
2665 .expect_err("Validation should return an error"),
2666 message
2667 );
2668 }
2669}