1#[cfg(feature = "color")]
4use anstream::panic;
5
6use crate::IntoData;
7
8#[derive(Debug)]
10pub struct Command {
11 cmd: std::process::Command,
12 stdin: Option<crate::Data>,
13 timeout: Option<std::time::Duration>,
14 _stderr_to_stdout: bool,
15 config: crate::Assert,
16}
17
18impl Command {
20 pub fn cargo_bin(name: &str) -> Self {
31 Self::new(cargo_bin(name))
32 }
33
34 pub fn new(program: impl AsRef<std::ffi::OsStr>) -> Self {
35 Self {
36 cmd: std::process::Command::new(program),
37 stdin: None,
38 timeout: None,
39 _stderr_to_stdout: false,
40 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
41 }
42 }
43
44 pub fn from_std(cmd: std::process::Command) -> Self {
46 Self {
47 cmd,
48 stdin: None,
49 timeout: None,
50 _stderr_to_stdout: false,
51 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
52 }
53 }
54
55 pub fn with_assert(mut self, config: crate::Assert) -> Self {
57 self.config = config;
58 self
59 }
60
61 pub fn arg(mut self, arg: impl AsRef<std::ffi::OsStr>) -> Self {
98 self.cmd.arg(arg);
99 self
100 }
101
102 pub fn args(mut self, args: impl IntoIterator<Item = impl AsRef<std::ffi::OsStr>>) -> Self {
121 self.cmd.args(args);
122 self
123 }
124
125 pub fn env(
143 mut self,
144 key: impl AsRef<std::ffi::OsStr>,
145 value: impl AsRef<std::ffi::OsStr>,
146 ) -> Self {
147 self.cmd.env(key, value);
148 self
149 }
150
151 pub fn envs(
175 mut self,
176 vars: impl IntoIterator<Item = (impl AsRef<std::ffi::OsStr>, impl AsRef<std::ffi::OsStr>)>,
177 ) -> Self {
178 self.cmd.envs(vars);
179 self
180 }
181
182 pub fn env_remove(mut self, key: impl AsRef<std::ffi::OsStr>) -> Self {
197 self.cmd.env_remove(key);
198 self
199 }
200
201 pub fn env_clear(mut self) -> Self {
216 self.cmd.env_clear();
217 self
218 }
219
220 pub fn current_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
245 self.cmd.current_dir(dir);
246 self
247 }
248
249 pub fn stdin(mut self, stream: impl IntoData) -> Self {
263 self.stdin = Some(stream.into_data());
264 self
265 }
266
267 #[cfg(feature = "cmd")]
280 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
281 self.timeout = Some(timeout);
282 self
283 }
284
285 #[cfg(feature = "cmd")]
287 pub fn stderr_to_stdout(mut self) -> Self {
288 self._stderr_to_stdout = true;
289 self
290 }
291}
292
293impl Command {
295 #[track_caller]
307 #[must_use]
308 pub fn assert(self) -> OutputAssert {
309 let config = self.config.clone();
310 match self.output() {
311 Ok(output) => OutputAssert::new(output).with_assert(config),
312 Err(err) => {
313 panic!("Failed to spawn: {}", err)
314 }
315 }
316 }
317
318 #[cfg(feature = "cmd")]
320 pub fn output(self) -> Result<std::process::Output, std::io::Error> {
321 if self._stderr_to_stdout {
322 self.single_output()
323 } else {
324 self.split_output()
325 }
326 }
327
328 #[cfg(not(feature = "cmd"))]
329 pub fn output(self) -> Result<std::process::Output, std::io::Error> {
330 self.split_output()
331 }
332
333 #[cfg(feature = "cmd")]
334 fn single_output(mut self) -> Result<std::process::Output, std::io::Error> {
335 self.cmd.stdin(std::process::Stdio::piped());
336 let (reader, writer) = os_pipe::pipe()?;
337 let writer_clone = writer.try_clone()?;
338 self.cmd.stdout(writer);
339 self.cmd.stderr(writer_clone);
340 let mut child = self.cmd.spawn()?;
341 drop(self.cmd);
345
346 let stdin = self
347 .stdin
348 .as_ref()
349 .map(|d| d.to_bytes())
350 .transpose()
351 .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
352 let stdout = process_single_io(&mut child, reader, stdin)?;
353
354 let status = wait(child, self.timeout)?;
355 let stdout = stdout.join().unwrap().ok().unwrap_or_default();
356
357 Ok(std::process::Output {
358 status,
359 stdout,
360 stderr: Default::default(),
361 })
362 }
363
364 fn split_output(mut self) -> Result<std::process::Output, std::io::Error> {
365 self.cmd.stdin(std::process::Stdio::piped());
366 self.cmd.stdout(std::process::Stdio::piped());
367 self.cmd.stderr(std::process::Stdio::piped());
368 let mut child = self.cmd.spawn()?;
369
370 let stdin = self
371 .stdin
372 .as_ref()
373 .map(|d| d.to_bytes())
374 .transpose()
375 .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
376 let (stdout, stderr) = process_split_io(&mut child, stdin)?;
377
378 let status = wait(child, self.timeout)?;
379 let stdout = stdout
380 .and_then(|t| t.join().unwrap().ok())
381 .unwrap_or_default();
382 let stderr = stderr
383 .and_then(|t| t.join().unwrap().ok())
384 .unwrap_or_default();
385
386 Ok(std::process::Output {
387 status,
388 stdout,
389 stderr,
390 })
391 }
392}
393
394fn process_split_io(
395 child: &mut std::process::Child,
396 input: Option<Vec<u8>>,
397) -> std::io::Result<(Option<Stream>, Option<Stream>)> {
398 use std::io::Write;
399
400 let stdin = input.and_then(|i| {
401 child
402 .stdin
403 .take()
404 .map(|mut stdin| std::thread::spawn(move || stdin.write_all(&i)))
405 });
406 let stdout = child.stdout.take().map(threaded_read);
407 let stderr = child.stderr.take().map(threaded_read);
408
409 stdin.and_then(|t| t.join().unwrap().ok());
411
412 Ok((stdout, stderr))
413}
414
415#[cfg(feature = "cmd")]
416fn process_single_io(
417 child: &mut std::process::Child,
418 stdout: os_pipe::PipeReader,
419 input: Option<Vec<u8>>,
420) -> std::io::Result<Stream> {
421 use std::io::Write;
422
423 let stdin = input.and_then(|i| {
424 child
425 .stdin
426 .take()
427 .map(|mut stdin| std::thread::spawn(move || stdin.write_all(&i)))
428 });
429 let stdout = threaded_read(stdout);
430 debug_assert!(child.stdout.is_none());
431 debug_assert!(child.stderr.is_none());
432
433 stdin.and_then(|t| t.join().unwrap().ok());
435
436 Ok(stdout)
437}
438
439type Stream = std::thread::JoinHandle<Result<Vec<u8>, std::io::Error>>;
440
441fn threaded_read<R>(mut input: R) -> Stream
442where
443 R: std::io::Read + Send + 'static,
444{
445 std::thread::spawn(move || {
446 let mut ret = Vec::new();
447 input.read_to_end(&mut ret).map(|_| ret)
448 })
449}
450
451impl From<std::process::Command> for Command {
452 fn from(cmd: std::process::Command) -> Self {
453 Self::from_std(cmd)
454 }
455}
456
457pub struct OutputAssert {
463 output: std::process::Output,
464 config: crate::Assert,
465}
466
467impl OutputAssert {
468 pub fn new(output: std::process::Output) -> Self {
472 Self {
473 output,
474 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
475 }
476 }
477
478 pub fn with_assert(mut self, config: crate::Assert) -> Self {
480 self.config = config;
481 self
482 }
483
484 pub fn get_output(&self) -> &std::process::Output {
488 &self.output
489 }
490
491 #[track_caller]
502 pub fn success(self) -> Self {
503 if !self.output.status.success() {
504 let desc = format!(
505 "Expected {}, was {}",
506 self.config.palette.info("success"),
507 self.config
508 .palette
509 .error(display_exit_status(self.output.status))
510 );
511
512 use std::fmt::Write;
513 let mut buf = String::new();
514 writeln!(&mut buf, "{desc}").unwrap();
515 self.write_stdout(&mut buf).unwrap();
516 self.write_stderr(&mut buf).unwrap();
517 panic!("{}", buf);
518 }
519 self
520 }
521
522 #[track_caller]
534 pub fn failure(self) -> Self {
535 if self.output.status.success() {
536 let desc = format!(
537 "Expected {}, was {}",
538 self.config.palette.info("failure"),
539 self.config.palette.error("success")
540 );
541
542 use std::fmt::Write;
543 let mut buf = String::new();
544 writeln!(&mut buf, "{desc}").unwrap();
545 self.write_stdout(&mut buf).unwrap();
546 self.write_stderr(&mut buf).unwrap();
547 panic!("{}", buf);
548 }
549 self
550 }
551
552 #[track_caller]
554 pub fn interrupted(self) -> Self {
555 if self.output.status.code().is_some() {
556 let desc = format!(
557 "Expected {}, was {}",
558 self.config.palette.info("interrupted"),
559 self.config
560 .palette
561 .error(display_exit_status(self.output.status))
562 );
563
564 use std::fmt::Write;
565 let mut buf = String::new();
566 writeln!(&mut buf, "{desc}").unwrap();
567 self.write_stdout(&mut buf).unwrap();
568 self.write_stderr(&mut buf).unwrap();
569 panic!("{}", buf);
570 }
571 self
572 }
573
574 #[track_caller]
586 pub fn code(self, expected: i32) -> Self {
587 if self.output.status.code() != Some(expected) {
588 let desc = format!(
589 "Expected {}, was {}",
590 self.config.palette.info(expected),
591 self.config
592 .palette
593 .error(display_exit_status(self.output.status))
594 );
595
596 use std::fmt::Write;
597 let mut buf = String::new();
598 writeln!(&mut buf, "{desc}").unwrap();
599 self.write_stdout(&mut buf).unwrap();
600 self.write_stderr(&mut buf).unwrap();
601 panic!("{}", buf);
602 }
603 self
604 }
605
606 #[track_caller]
645 pub fn stdout_eq(self, expected: impl IntoData) -> Self {
646 let expected = expected.into_data();
647 self.stdout_eq_inner(expected)
648 }
649
650 #[track_caller]
651 #[deprecated(since = "0.6.0", note = "Replaced with `OutputAssert::stdout_eq`")]
652 pub fn stdout_eq_(self, expected: impl IntoData) -> Self {
653 self.stdout_eq(expected)
654 }
655
656 #[track_caller]
657 fn stdout_eq_inner(self, expected: crate::Data) -> Self {
658 let actual = self.output.stdout.as_slice().into_data();
659 if let Err(err) = self.config.try_eq(Some(&"stdout"), actual, expected) {
660 err.panic();
661 }
662
663 self
664 }
665
666 #[track_caller]
705 pub fn stderr_eq(self, expected: impl IntoData) -> Self {
706 let expected = expected.into_data();
707 self.stderr_eq_inner(expected)
708 }
709
710 #[track_caller]
711 #[deprecated(since = "0.6.0", note = "Replaced with `OutputAssert::stderr_eq`")]
712 pub fn stderr_eq_(self, expected: impl IntoData) -> Self {
713 self.stderr_eq(expected)
714 }
715
716 #[track_caller]
717 fn stderr_eq_inner(self, expected: crate::Data) -> Self {
718 let actual = self.output.stderr.as_slice().into_data();
719 if let Err(err) = self.config.try_eq(Some(&"stderr"), actual, expected) {
720 err.panic();
721 }
722
723 self
724 }
725
726 fn write_stdout(&self, writer: &mut dyn std::fmt::Write) -> Result<(), std::fmt::Error> {
727 if !self.output.stdout.is_empty() {
728 writeln!(writer, "stdout:")?;
729 writeln!(writer, "```")?;
730 writeln!(writer, "{}", String::from_utf8_lossy(&self.output.stdout))?;
731 writeln!(writer, "```")?;
732 }
733 Ok(())
734 }
735
736 fn write_stderr(&self, writer: &mut dyn std::fmt::Write) -> Result<(), std::fmt::Error> {
737 if !self.output.stderr.is_empty() {
738 writeln!(writer, "stderr:")?;
739 writeln!(writer, "```")?;
740 writeln!(writer, "{}", String::from_utf8_lossy(&self.output.stderr))?;
741 writeln!(writer, "```")?;
742 }
743 Ok(())
744 }
745}
746
747#[cfg(not(feature = "cmd"))]
749pub fn display_exit_status(status: std::process::ExitStatus) -> String {
750 basic_exit_status(status)
751}
752
753#[cfg(feature = "cmd")]
755pub fn display_exit_status(status: std::process::ExitStatus) -> String {
756 #[cfg(unix)]
757 fn detailed_exit_status(status: std::process::ExitStatus) -> Option<String> {
758 use std::os::unix::process::ExitStatusExt;
759
760 let signal = status.signal()?;
761 let name = match signal as libc::c_int {
762 libc::SIGABRT => ", SIGABRT: process abort signal",
763 libc::SIGALRM => ", SIGALRM: alarm clock",
764 libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
765 libc::SIGHUP => ", SIGHUP: hangup",
766 libc::SIGILL => ", SIGILL: illegal instruction",
767 libc::SIGINT => ", SIGINT: terminal interrupt signal",
768 libc::SIGKILL => ", SIGKILL: kill",
769 libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
770 libc::SIGQUIT => ", SIGQUIT: terminal quit signal",
771 libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
772 libc::SIGTERM => ", SIGTERM: termination signal",
773 libc::SIGBUS => ", SIGBUS: access to undefined memory",
774 #[cfg(not(target_os = "haiku"))]
775 libc::SIGSYS => ", SIGSYS: bad system call",
776 libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
777 _ => "",
778 };
779 Some(format!("signal: {signal}{name}"))
780 }
781
782 #[cfg(windows)]
783 fn detailed_exit_status(status: std::process::ExitStatus) -> Option<String> {
784 use windows_sys::Win32::Foundation::*;
785
786 let extra = match status.code().unwrap() as NTSTATUS {
787 STATUS_ACCESS_VIOLATION => "STATUS_ACCESS_VIOLATION",
788 STATUS_IN_PAGE_ERROR => "STATUS_IN_PAGE_ERROR",
789 STATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE",
790 STATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER",
791 STATUS_NO_MEMORY => "STATUS_NO_MEMORY",
792 STATUS_ILLEGAL_INSTRUCTION => "STATUS_ILLEGAL_INSTRUCTION",
793 STATUS_NONCONTINUABLE_EXCEPTION => "STATUS_NONCONTINUABLE_EXCEPTION",
794 STATUS_INVALID_DISPOSITION => "STATUS_INVALID_DISPOSITION",
795 STATUS_ARRAY_BOUNDS_EXCEEDED => "STATUS_ARRAY_BOUNDS_EXCEEDED",
796 STATUS_FLOAT_DENORMAL_OPERAND => "STATUS_FLOAT_DENORMAL_OPERAND",
797 STATUS_FLOAT_DIVIDE_BY_ZERO => "STATUS_FLOAT_DIVIDE_BY_ZERO",
798 STATUS_FLOAT_INEXACT_RESULT => "STATUS_FLOAT_INEXACT_RESULT",
799 STATUS_FLOAT_INVALID_OPERATION => "STATUS_FLOAT_INVALID_OPERATION",
800 STATUS_FLOAT_OVERFLOW => "STATUS_FLOAT_OVERFLOW",
801 STATUS_FLOAT_STACK_CHECK => "STATUS_FLOAT_STACK_CHECK",
802 STATUS_FLOAT_UNDERFLOW => "STATUS_FLOAT_UNDERFLOW",
803 STATUS_INTEGER_DIVIDE_BY_ZERO => "STATUS_INTEGER_DIVIDE_BY_ZERO",
804 STATUS_INTEGER_OVERFLOW => "STATUS_INTEGER_OVERFLOW",
805 STATUS_PRIVILEGED_INSTRUCTION => "STATUS_PRIVILEGED_INSTRUCTION",
806 STATUS_STACK_OVERFLOW => "STATUS_STACK_OVERFLOW",
807 STATUS_DLL_NOT_FOUND => "STATUS_DLL_NOT_FOUND",
808 STATUS_ORDINAL_NOT_FOUND => "STATUS_ORDINAL_NOT_FOUND",
809 STATUS_ENTRYPOINT_NOT_FOUND => "STATUS_ENTRYPOINT_NOT_FOUND",
810 STATUS_CONTROL_C_EXIT => "STATUS_CONTROL_C_EXIT",
811 STATUS_DLL_INIT_FAILED => "STATUS_DLL_INIT_FAILED",
812 STATUS_FLOAT_MULTIPLE_FAULTS => "STATUS_FLOAT_MULTIPLE_FAULTS",
813 STATUS_FLOAT_MULTIPLE_TRAPS => "STATUS_FLOAT_MULTIPLE_TRAPS",
814 STATUS_REG_NAT_CONSUMPTION => "STATUS_REG_NAT_CONSUMPTION",
815 STATUS_HEAP_CORRUPTION => "STATUS_HEAP_CORRUPTION",
816 STATUS_STACK_BUFFER_OVERRUN => "STATUS_STACK_BUFFER_OVERRUN",
817 STATUS_ASSERTION_FAILURE => "STATUS_ASSERTION_FAILURE",
818 _ => return None,
819 };
820 Some(extra.to_owned())
821 }
822
823 if let Some(extra) = detailed_exit_status(status) {
824 format!("{} ({})", basic_exit_status(status), extra)
825 } else {
826 basic_exit_status(status)
827 }
828}
829
830fn basic_exit_status(status: std::process::ExitStatus) -> String {
831 if let Some(code) = status.code() {
832 code.to_string()
833 } else {
834 "interrupted".to_owned()
835 }
836}
837
838#[cfg(feature = "cmd")]
839fn wait(
840 mut child: std::process::Child,
841 timeout: Option<std::time::Duration>,
842) -> std::io::Result<std::process::ExitStatus> {
843 if let Some(timeout) = timeout {
844 wait_timeout::ChildExt::wait_timeout(&mut child, timeout)
845 .transpose()
846 .unwrap_or_else(|| {
847 let _ = child.kill();
848 child.wait()
849 })
850 } else {
851 child.wait()
852 }
853}
854
855#[cfg(not(feature = "cmd"))]
856fn wait(
857 mut child: std::process::Child,
858 _timeout: Option<std::time::Duration>,
859) -> std::io::Result<std::process::ExitStatus> {
860 child.wait()
861}
862
863#[doc(inline)]
864pub use crate::cargo_bin;
865
866pub fn cargo_bin(name: &str) -> std::path::PathBuf {
877 cargo_bin_opt(name).unwrap_or_else(|| missing_cargo_bin(name))
878}
879
880pub fn cargo_bin_opt(name: &str) -> Option<std::path::PathBuf> {
889 let env_var = format!("{CARGO_BIN_EXE_}{name}");
890 std::env::var_os(env_var)
891 .map(|p| p.into())
892 .or_else(|| legacy_cargo_bin(name))
893}
894
895pub fn cargo_bins() -> impl Iterator<Item = (String, std::path::PathBuf)> {
900 std::env::vars_os()
901 .filter_map(|(k, v)| {
902 k.into_string()
903 .ok()
904 .map(|k| (k, std::path::PathBuf::from(v)))
905 })
906 .filter_map(|(k, v)| k.strip_prefix(CARGO_BIN_EXE_).map(|s| (s.to_owned(), v)))
907}
908
909const CARGO_BIN_EXE_: &str = "CARGO_BIN_EXE_";
910
911fn missing_cargo_bin(name: &str) -> ! {
912 let possible_names: Vec<_> = cargo_bins().map(|(k, _)| k).collect();
913 if possible_names.is_empty() {
914 panic!("`CARGO_BIN_EXE_{name}` is unset
915help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_{name}`")
916 } else {
917 let mut names = String::new();
918 for (i, name) in possible_names.iter().enumerate() {
919 use std::fmt::Write as _;
920 if i != 0 {
921 let _ = write!(&mut names, ", ");
922 }
923 let _ = write!(&mut names, "\"{name}\"");
924 }
925 panic!(
926 "`CARGO_BIN_EXE_{name}` is unset
927help: available binary names are {names}"
928 )
929 }
930}
931
932fn legacy_cargo_bin(name: &str) -> Option<std::path::PathBuf> {
933 let target_dir = target_dir()?;
934 let bin_path = target_dir.join(format!("{}{}", name, std::env::consts::EXE_SUFFIX));
935 if !bin_path.exists() {
936 return None;
937 }
938 Some(bin_path)
939}
940
941fn target_dir() -> Option<std::path::PathBuf> {
944 let mut path = std::env::current_exe().ok()?;
945 let _test_bin_name = path.pop();
946 if path.ends_with("deps") {
947 let _deps = path.pop();
948 }
949 Some(path)
950}
951
952#[cfg(feature = "examples")]
953pub use examples::{compile_example, compile_examples};
954
955#[cfg(feature = "examples")]
956pub(crate) mod examples {
957 #[cfg(feature = "examples")]
969 pub fn compile_example<'a>(
970 target_name: &str,
971 args: impl IntoIterator<Item = &'a str>,
972 ) -> crate::assert::Result<std::path::PathBuf> {
973 crate::debug!("Compiling example {}", target_name);
974 let messages = escargot::CargoBuild::new()
975 .current_target()
976 .current_release()
977 .example(target_name)
978 .args(args)
979 .exec()
980 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
981 for message in messages {
982 let message = message.map_err(|e| crate::assert::Error::new(e.to_string()))?;
983 let message = message
984 .decode()
985 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
986 crate::debug!("Message: {:?}", message);
987 if let Some(bin) = decode_example_message(&message) {
988 let (name, bin) = bin?;
989 assert_eq!(target_name, name);
990 return bin;
991 }
992 }
993
994 Err(crate::assert::Error::new(format!(
995 "Unknown error building example {target_name}"
996 )))
997 }
998
999 #[cfg(feature = "examples")]
1011 pub fn compile_examples<'a>(
1012 args: impl IntoIterator<Item = &'a str>,
1013 ) -> crate::assert::Result<
1014 impl Iterator<Item = (String, crate::assert::Result<std::path::PathBuf>)>,
1015 > {
1016 crate::debug!("Compiling examples");
1017 let mut examples = std::collections::BTreeMap::new();
1018
1019 let messages = escargot::CargoBuild::new()
1020 .current_target()
1021 .current_release()
1022 .examples()
1023 .args(args)
1024 .exec()
1025 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
1026 for message in messages {
1027 let message = message.map_err(|e| crate::assert::Error::new(e.to_string()))?;
1028 let message = message
1029 .decode()
1030 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
1031 crate::debug!("Message: {:?}", message);
1032 if let Some(bin) = decode_example_message(&message) {
1033 let (name, bin) = bin?;
1034 examples.insert(name.to_owned(), bin);
1035 }
1036 }
1037
1038 Ok(examples.into_iter())
1039 }
1040
1041 #[allow(clippy::type_complexity)]
1042 fn decode_example_message<'m>(
1043 message: &'m escargot::format::Message<'_>,
1044 ) -> Option<crate::assert::Result<(&'m str, crate::assert::Result<std::path::PathBuf>)>> {
1045 match message {
1046 escargot::format::Message::CompilerMessage(msg) => {
1047 let level = msg.message.level;
1048 if level == escargot::format::diagnostic::DiagnosticLevel::Ice
1049 || level == escargot::format::diagnostic::DiagnosticLevel::Error
1050 {
1051 let output = msg
1052 .message
1053 .rendered
1054 .as_deref()
1055 .unwrap_or_else(|| msg.message.message.as_ref())
1056 .to_owned();
1057 if is_example_target(&msg.target) {
1058 let bin = Err(crate::assert::Error::new(output));
1059 Some(Ok((msg.target.name.as_ref(), bin)))
1060 } else {
1061 Some(Err(crate::assert::Error::new(output)))
1062 }
1063 } else {
1064 None
1065 }
1066 }
1067 escargot::format::Message::CompilerArtifact(artifact) => {
1068 if !artifact.profile.test && is_example_target(&artifact.target) {
1069 let path = artifact
1070 .executable
1071 .clone()
1072 .expect("cargo is new enough for this to be present");
1073 let bin = Ok(path.into_owned());
1074 Some(Ok((artifact.target.name.as_ref(), bin)))
1075 } else {
1076 None
1077 }
1078 }
1079 _ => None,
1080 }
1081 }
1082
1083 fn is_example_target(target: &escargot::format::Target<'_>) -> bool {
1084 target.crate_types == ["bin"] && target.kind == ["example"]
1085 }
1086}
1087
1088#[test]
1089#[should_panic = "`CARGO_BIN_EXE_non-existent` is unset
1090help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_non-existent`"]
1091fn cargo_bin_in_unit_test() {
1092 cargo_bin("non-existent");
1093}