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 new(program: impl AsRef<std::ffi::OsStr>) -> Self {
21 Self {
22 cmd: std::process::Command::new(program),
23 stdin: None,
24 timeout: None,
25 _stderr_to_stdout: false,
26 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
27 }
28 }
29
30 pub fn from_std(cmd: std::process::Command) -> Self {
32 Self {
33 cmd,
34 stdin: None,
35 timeout: None,
36 _stderr_to_stdout: false,
37 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
38 }
39 }
40
41 pub fn with_assert(mut self, config: crate::Assert) -> Self {
43 self.config = config;
44 self
45 }
46
47 pub fn arg(mut self, arg: impl AsRef<std::ffi::OsStr>) -> Self {
84 self.cmd.arg(arg);
85 self
86 }
87
88 pub fn args(mut self, args: impl IntoIterator<Item = impl AsRef<std::ffi::OsStr>>) -> Self {
107 self.cmd.args(args);
108 self
109 }
110
111 pub fn env(
129 mut self,
130 key: impl AsRef<std::ffi::OsStr>,
131 value: impl AsRef<std::ffi::OsStr>,
132 ) -> Self {
133 self.cmd.env(key, value);
134 self
135 }
136
137 pub fn envs(
161 mut self,
162 vars: impl IntoIterator<Item = (impl AsRef<std::ffi::OsStr>, impl AsRef<std::ffi::OsStr>)>,
163 ) -> Self {
164 self.cmd.envs(vars);
165 self
166 }
167
168 pub fn env_remove(mut self, key: impl AsRef<std::ffi::OsStr>) -> Self {
183 self.cmd.env_remove(key);
184 self
185 }
186
187 pub fn env_clear(mut self) -> Self {
202 self.cmd.env_clear();
203 self
204 }
205
206 pub fn current_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
231 self.cmd.current_dir(dir);
232 self
233 }
234
235 pub fn stdin(mut self, stream: impl IntoData) -> Self {
249 self.stdin = Some(stream.into_data());
250 self
251 }
252
253 #[cfg(feature = "cmd")]
266 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
267 self.timeout = Some(timeout);
268 self
269 }
270
271 #[cfg(feature = "cmd")]
273 pub fn stderr_to_stdout(mut self) -> Self {
274 self._stderr_to_stdout = true;
275 self
276 }
277}
278
279impl Command {
281 #[track_caller]
293 #[must_use]
294 pub fn assert(self) -> OutputAssert {
295 let config = self.config.clone();
296 match self.output() {
297 Ok(output) => OutputAssert::new(output).with_assert(config),
298 Err(err) => {
299 panic!("Failed to spawn: {}", err)
300 }
301 }
302 }
303
304 #[cfg(feature = "cmd")]
306 pub fn output(self) -> Result<std::process::Output, std::io::Error> {
307 if self._stderr_to_stdout {
308 self.single_output()
309 } else {
310 self.split_output()
311 }
312 }
313
314 #[cfg(not(feature = "cmd"))]
315 pub fn output(self) -> Result<std::process::Output, std::io::Error> {
316 self.split_output()
317 }
318
319 #[cfg(feature = "cmd")]
320 fn single_output(mut self) -> Result<std::process::Output, std::io::Error> {
321 self.cmd.stdin(std::process::Stdio::piped());
322 let (reader, writer) = os_pipe::pipe()?;
323 let writer_clone = writer.try_clone()?;
324 self.cmd.stdout(writer);
325 self.cmd.stderr(writer_clone);
326 let mut child = self.cmd.spawn()?;
327 drop(self.cmd);
331
332 let stdin = self
333 .stdin
334 .as_ref()
335 .map(|d| d.to_bytes())
336 .transpose()
337 .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
338 let stdout = process_single_io(&mut child, reader, stdin)?;
339
340 let status = wait(child, self.timeout)?;
341 let stdout = stdout.join().unwrap().ok().unwrap_or_default();
342
343 Ok(std::process::Output {
344 status,
345 stdout,
346 stderr: Default::default(),
347 })
348 }
349
350 fn split_output(mut self) -> Result<std::process::Output, std::io::Error> {
351 self.cmd.stdin(std::process::Stdio::piped());
352 self.cmd.stdout(std::process::Stdio::piped());
353 self.cmd.stderr(std::process::Stdio::piped());
354 let mut child = self.cmd.spawn()?;
355
356 let stdin = self
357 .stdin
358 .as_ref()
359 .map(|d| d.to_bytes())
360 .transpose()
361 .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
362 let (stdout, stderr) = process_split_io(&mut child, stdin)?;
363
364 let status = wait(child, self.timeout)?;
365 let stdout = stdout
366 .and_then(|t| t.join().unwrap().ok())
367 .unwrap_or_default();
368 let stderr = stderr
369 .and_then(|t| t.join().unwrap().ok())
370 .unwrap_or_default();
371
372 Ok(std::process::Output {
373 status,
374 stdout,
375 stderr,
376 })
377 }
378}
379
380fn process_split_io(
381 child: &mut std::process::Child,
382 input: Option<Vec<u8>>,
383) -> std::io::Result<(Option<Stream>, Option<Stream>)> {
384 use std::io::Write;
385
386 let stdin = input.and_then(|i| {
387 child
388 .stdin
389 .take()
390 .map(|mut stdin| std::thread::spawn(move || stdin.write_all(&i)))
391 });
392 let stdout = child.stdout.take().map(threaded_read);
393 let stderr = child.stderr.take().map(threaded_read);
394
395 stdin.and_then(|t| t.join().unwrap().ok());
397
398 Ok((stdout, stderr))
399}
400
401#[cfg(feature = "cmd")]
402fn process_single_io(
403 child: &mut std::process::Child,
404 stdout: os_pipe::PipeReader,
405 input: Option<Vec<u8>>,
406) -> std::io::Result<Stream> {
407 use std::io::Write;
408
409 let stdin = input.and_then(|i| {
410 child
411 .stdin
412 .take()
413 .map(|mut stdin| std::thread::spawn(move || stdin.write_all(&i)))
414 });
415 let stdout = threaded_read(stdout);
416 debug_assert!(child.stdout.is_none());
417 debug_assert!(child.stderr.is_none());
418
419 stdin.and_then(|t| t.join().unwrap().ok());
421
422 Ok(stdout)
423}
424
425type Stream = std::thread::JoinHandle<Result<Vec<u8>, std::io::Error>>;
426
427fn threaded_read<R>(mut input: R) -> Stream
428where
429 R: std::io::Read + Send + 'static,
430{
431 std::thread::spawn(move || {
432 let mut ret = Vec::new();
433 input.read_to_end(&mut ret).map(|_| ret)
434 })
435}
436
437impl From<std::process::Command> for Command {
438 fn from(cmd: std::process::Command) -> Self {
439 Self::from_std(cmd)
440 }
441}
442
443pub struct OutputAssert {
449 output: std::process::Output,
450 config: crate::Assert,
451}
452
453impl OutputAssert {
454 pub fn new(output: std::process::Output) -> Self {
458 Self {
459 output,
460 config: crate::Assert::new().action_env(crate::assert::DEFAULT_ACTION_ENV),
461 }
462 }
463
464 pub fn with_assert(mut self, config: crate::Assert) -> Self {
466 self.config = config;
467 self
468 }
469
470 pub fn get_output(&self) -> &std::process::Output {
474 &self.output
475 }
476
477 #[track_caller]
488 pub fn success(self) -> Self {
489 if !self.output.status.success() {
490 let desc = format!(
491 "Expected {}, was {}",
492 self.config.palette.info("success"),
493 self.config
494 .palette
495 .error(display_exit_status(self.output.status))
496 );
497
498 use std::fmt::Write;
499 let mut buf = String::new();
500 writeln!(&mut buf, "{desc}").unwrap();
501 self.write_stdout(&mut buf).unwrap();
502 self.write_stderr(&mut buf).unwrap();
503 panic!("{}", buf);
504 }
505 self
506 }
507
508 #[track_caller]
520 pub fn failure(self) -> Self {
521 if self.output.status.success() {
522 let desc = format!(
523 "Expected {}, was {}",
524 self.config.palette.info("failure"),
525 self.config.palette.error("success")
526 );
527
528 use std::fmt::Write;
529 let mut buf = String::new();
530 writeln!(&mut buf, "{desc}").unwrap();
531 self.write_stdout(&mut buf).unwrap();
532 self.write_stderr(&mut buf).unwrap();
533 panic!("{}", buf);
534 }
535 self
536 }
537
538 #[track_caller]
540 pub fn interrupted(self) -> Self {
541 if self.output.status.code().is_some() {
542 let desc = format!(
543 "Expected {}, was {}",
544 self.config.palette.info("interrupted"),
545 self.config
546 .palette
547 .error(display_exit_status(self.output.status))
548 );
549
550 use std::fmt::Write;
551 let mut buf = String::new();
552 writeln!(&mut buf, "{desc}").unwrap();
553 self.write_stdout(&mut buf).unwrap();
554 self.write_stderr(&mut buf).unwrap();
555 panic!("{}", buf);
556 }
557 self
558 }
559
560 #[track_caller]
572 pub fn code(self, expected: i32) -> Self {
573 if self.output.status.code() != Some(expected) {
574 let desc = format!(
575 "Expected {}, was {}",
576 self.config.palette.info(expected),
577 self.config
578 .palette
579 .error(display_exit_status(self.output.status))
580 );
581
582 use std::fmt::Write;
583 let mut buf = String::new();
584 writeln!(&mut buf, "{desc}").unwrap();
585 self.write_stdout(&mut buf).unwrap();
586 self.write_stderr(&mut buf).unwrap();
587 panic!("{}", buf);
588 }
589 self
590 }
591
592 #[track_caller]
631 pub fn stdout_eq(self, expected: impl IntoData) -> Self {
632 let expected = expected.into_data();
633 self.stdout_eq_inner(expected)
634 }
635
636 #[track_caller]
637 #[deprecated(since = "0.6.0", note = "Replaced with `OutputAssert::stdout_eq`")]
638 pub fn stdout_eq_(self, expected: impl IntoData) -> Self {
639 self.stdout_eq(expected)
640 }
641
642 #[track_caller]
643 fn stdout_eq_inner(self, expected: crate::Data) -> Self {
644 let actual = self.output.stdout.as_slice().into_data();
645 if let Err(err) = self.config.try_eq(Some(&"stdout"), actual, expected) {
646 err.panic();
647 }
648
649 self
650 }
651
652 #[track_caller]
691 pub fn stderr_eq(self, expected: impl IntoData) -> Self {
692 let expected = expected.into_data();
693 self.stderr_eq_inner(expected)
694 }
695
696 #[track_caller]
697 #[deprecated(since = "0.6.0", note = "Replaced with `OutputAssert::stderr_eq`")]
698 pub fn stderr_eq_(self, expected: impl IntoData) -> Self {
699 self.stderr_eq(expected)
700 }
701
702 #[track_caller]
703 fn stderr_eq_inner(self, expected: crate::Data) -> Self {
704 let actual = self.output.stderr.as_slice().into_data();
705 if let Err(err) = self.config.try_eq(Some(&"stderr"), actual, expected) {
706 err.panic();
707 }
708
709 self
710 }
711
712 fn write_stdout(&self, writer: &mut dyn std::fmt::Write) -> Result<(), std::fmt::Error> {
713 if !self.output.stdout.is_empty() {
714 writeln!(writer, "stdout:")?;
715 writeln!(writer, "```")?;
716 writeln!(writer, "{}", String::from_utf8_lossy(&self.output.stdout))?;
717 writeln!(writer, "```")?;
718 }
719 Ok(())
720 }
721
722 fn write_stderr(&self, writer: &mut dyn std::fmt::Write) -> Result<(), std::fmt::Error> {
723 if !self.output.stderr.is_empty() {
724 writeln!(writer, "stderr:")?;
725 writeln!(writer, "```")?;
726 writeln!(writer, "{}", String::from_utf8_lossy(&self.output.stderr))?;
727 writeln!(writer, "```")?;
728 }
729 Ok(())
730 }
731}
732
733#[cfg(not(feature = "cmd"))]
735pub fn display_exit_status(status: std::process::ExitStatus) -> String {
736 basic_exit_status(status)
737}
738
739#[cfg(feature = "cmd")]
741pub fn display_exit_status(status: std::process::ExitStatus) -> String {
742 #[cfg(unix)]
743 fn detailed_exit_status(status: std::process::ExitStatus) -> Option<String> {
744 use std::os::unix::process::ExitStatusExt;
745
746 let signal = status.signal()?;
747 let name = match signal as libc::c_int {
748 libc::SIGABRT => ", SIGABRT: process abort signal",
749 libc::SIGALRM => ", SIGALRM: alarm clock",
750 libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
751 libc::SIGHUP => ", SIGHUP: hangup",
752 libc::SIGILL => ", SIGILL: illegal instruction",
753 libc::SIGINT => ", SIGINT: terminal interrupt signal",
754 libc::SIGKILL => ", SIGKILL: kill",
755 libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
756 libc::SIGQUIT => ", SIGQUIT: terminal quit signal",
757 libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
758 libc::SIGTERM => ", SIGTERM: termination signal",
759 libc::SIGBUS => ", SIGBUS: access to undefined memory",
760 #[cfg(not(target_os = "haiku"))]
761 libc::SIGSYS => ", SIGSYS: bad system call",
762 libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
763 _ => "",
764 };
765 Some(format!("signal: {signal}{name}"))
766 }
767
768 #[cfg(windows)]
769 fn detailed_exit_status(status: std::process::ExitStatus) -> Option<String> {
770 use windows_sys::Win32::Foundation::*;
771
772 let extra = match status.code().unwrap() as NTSTATUS {
773 STATUS_ACCESS_VIOLATION => "STATUS_ACCESS_VIOLATION",
774 STATUS_IN_PAGE_ERROR => "STATUS_IN_PAGE_ERROR",
775 STATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE",
776 STATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER",
777 STATUS_NO_MEMORY => "STATUS_NO_MEMORY",
778 STATUS_ILLEGAL_INSTRUCTION => "STATUS_ILLEGAL_INSTRUCTION",
779 STATUS_NONCONTINUABLE_EXCEPTION => "STATUS_NONCONTINUABLE_EXCEPTION",
780 STATUS_INVALID_DISPOSITION => "STATUS_INVALID_DISPOSITION",
781 STATUS_ARRAY_BOUNDS_EXCEEDED => "STATUS_ARRAY_BOUNDS_EXCEEDED",
782 STATUS_FLOAT_DENORMAL_OPERAND => "STATUS_FLOAT_DENORMAL_OPERAND",
783 STATUS_FLOAT_DIVIDE_BY_ZERO => "STATUS_FLOAT_DIVIDE_BY_ZERO",
784 STATUS_FLOAT_INEXACT_RESULT => "STATUS_FLOAT_INEXACT_RESULT",
785 STATUS_FLOAT_INVALID_OPERATION => "STATUS_FLOAT_INVALID_OPERATION",
786 STATUS_FLOAT_OVERFLOW => "STATUS_FLOAT_OVERFLOW",
787 STATUS_FLOAT_STACK_CHECK => "STATUS_FLOAT_STACK_CHECK",
788 STATUS_FLOAT_UNDERFLOW => "STATUS_FLOAT_UNDERFLOW",
789 STATUS_INTEGER_DIVIDE_BY_ZERO => "STATUS_INTEGER_DIVIDE_BY_ZERO",
790 STATUS_INTEGER_OVERFLOW => "STATUS_INTEGER_OVERFLOW",
791 STATUS_PRIVILEGED_INSTRUCTION => "STATUS_PRIVILEGED_INSTRUCTION",
792 STATUS_STACK_OVERFLOW => "STATUS_STACK_OVERFLOW",
793 STATUS_DLL_NOT_FOUND => "STATUS_DLL_NOT_FOUND",
794 STATUS_ORDINAL_NOT_FOUND => "STATUS_ORDINAL_NOT_FOUND",
795 STATUS_ENTRYPOINT_NOT_FOUND => "STATUS_ENTRYPOINT_NOT_FOUND",
796 STATUS_CONTROL_C_EXIT => "STATUS_CONTROL_C_EXIT",
797 STATUS_DLL_INIT_FAILED => "STATUS_DLL_INIT_FAILED",
798 STATUS_FLOAT_MULTIPLE_FAULTS => "STATUS_FLOAT_MULTIPLE_FAULTS",
799 STATUS_FLOAT_MULTIPLE_TRAPS => "STATUS_FLOAT_MULTIPLE_TRAPS",
800 STATUS_REG_NAT_CONSUMPTION => "STATUS_REG_NAT_CONSUMPTION",
801 STATUS_HEAP_CORRUPTION => "STATUS_HEAP_CORRUPTION",
802 STATUS_STACK_BUFFER_OVERRUN => "STATUS_STACK_BUFFER_OVERRUN",
803 STATUS_ASSERTION_FAILURE => "STATUS_ASSERTION_FAILURE",
804 _ => return None,
805 };
806 Some(extra.to_owned())
807 }
808
809 if let Some(extra) = detailed_exit_status(status) {
810 format!("{} ({})", basic_exit_status(status), extra)
811 } else {
812 basic_exit_status(status)
813 }
814}
815
816fn basic_exit_status(status: std::process::ExitStatus) -> String {
817 if let Some(code) = status.code() {
818 code.to_string()
819 } else {
820 "interrupted".to_owned()
821 }
822}
823
824#[cfg(feature = "cmd")]
825fn wait(
826 mut child: std::process::Child,
827 timeout: Option<std::time::Duration>,
828) -> std::io::Result<std::process::ExitStatus> {
829 if let Some(timeout) = timeout {
830 wait_timeout::ChildExt::wait_timeout(&mut child, timeout)
831 .transpose()
832 .unwrap_or_else(|| {
833 let _ = child.kill();
834 child.wait()
835 })
836 } else {
837 child.wait()
838 }
839}
840
841#[cfg(not(feature = "cmd"))]
842fn wait(
843 mut child: std::process::Child,
844 _timeout: Option<std::time::Duration>,
845) -> std::io::Result<std::process::ExitStatus> {
846 child.wait()
847}
848
849#[doc(inline)]
850pub use crate::cargo_bin;
851
852pub fn cargo_bin(name: &str) -> std::path::PathBuf {
863 cargo_bin_opt(name).unwrap_or_else(|| missing_cargo_bin(name))
864}
865
866pub fn cargo_bin_opt(name: &str) -> Option<std::path::PathBuf> {
875 let env_var = format!("{CARGO_BIN_EXE_}{name}");
876 std::env::var_os(env_var)
877 .map(|p| p.into())
878 .or_else(|| legacy_cargo_bin(name))
879}
880
881pub fn cargo_bins() -> impl Iterator<Item = (String, std::path::PathBuf)> {
886 std::env::vars_os()
887 .filter_map(|(k, v)| {
888 k.into_string()
889 .ok()
890 .map(|k| (k, std::path::PathBuf::from(v)))
891 })
892 .filter_map(|(k, v)| k.strip_prefix(CARGO_BIN_EXE_).map(|s| (s.to_owned(), v)))
893}
894
895const CARGO_BIN_EXE_: &str = "CARGO_BIN_EXE_";
896
897fn missing_cargo_bin(name: &str) -> ! {
898 let possible_names: Vec<_> = cargo_bins().map(|(k, _)| k).collect();
899 if possible_names.is_empty() {
900 panic!("`CARGO_BIN_EXE_{name}` is unset
901help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_{name}`")
902 } else {
903 let mut names = String::new();
904 for (i, name) in possible_names.iter().enumerate() {
905 use std::fmt::Write as _;
906 if i != 0 {
907 let _ = write!(&mut names, ", ");
908 }
909 let _ = write!(&mut names, "\"{name}\"");
910 }
911 panic!(
912 "`CARGO_BIN_EXE_{name}` is unset
913help: available binary names are {names}"
914 )
915 }
916}
917
918fn legacy_cargo_bin(name: &str) -> Option<std::path::PathBuf> {
919 let target_dir = target_dir()?;
920 let bin_path = target_dir.join(format!("{}{}", name, std::env::consts::EXE_SUFFIX));
921 if !bin_path.exists() {
922 return None;
923 }
924 Some(bin_path)
925}
926
927fn target_dir() -> Option<std::path::PathBuf> {
930 let mut path = std::env::current_exe().ok()?;
931 let _test_bin_name = path.pop();
932 if path.ends_with("deps") {
933 let _deps = path.pop();
934 }
935 Some(path)
936}
937
938#[cfg(feature = "examples")]
939pub use examples::{compile_example, compile_examples};
940
941#[cfg(feature = "examples")]
942pub(crate) mod examples {
943 #[cfg(feature = "examples")]
955 pub fn compile_example<'a>(
956 target_name: &str,
957 args: impl IntoIterator<Item = &'a str>,
958 ) -> crate::assert::Result<std::path::PathBuf> {
959 crate::debug!("Compiling example {}", target_name);
960 let messages = escargot::CargoBuild::new()
961 .current_target()
962 .current_release()
963 .example(target_name)
964 .args(args)
965 .exec()
966 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
967 for message in messages {
968 let message = message.map_err(|e| crate::assert::Error::new(e.to_string()))?;
969 let message = message
970 .decode()
971 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
972 crate::debug!("Message: {:?}", message);
973 if let Some(bin) = decode_example_message(&message) {
974 let (name, bin) = bin?;
975 assert_eq!(target_name, name);
976 return bin;
977 }
978 }
979
980 Err(crate::assert::Error::new(format!(
981 "Unknown error building example {target_name}"
982 )))
983 }
984
985 #[cfg(feature = "examples")]
997 pub fn compile_examples<'a>(
998 args: impl IntoIterator<Item = &'a str>,
999 ) -> crate::assert::Result<
1000 impl Iterator<Item = (String, crate::assert::Result<std::path::PathBuf>)>,
1001 > {
1002 crate::debug!("Compiling examples");
1003 let mut examples = std::collections::BTreeMap::new();
1004
1005 let messages = escargot::CargoBuild::new()
1006 .current_target()
1007 .current_release()
1008 .examples()
1009 .args(args)
1010 .exec()
1011 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
1012 for message in messages {
1013 let message = message.map_err(|e| crate::assert::Error::new(e.to_string()))?;
1014 let message = message
1015 .decode()
1016 .map_err(|e| crate::assert::Error::new(e.to_string()))?;
1017 crate::debug!("Message: {:?}", message);
1018 if let Some(bin) = decode_example_message(&message) {
1019 let (name, bin) = bin?;
1020 examples.insert(name.to_owned(), bin);
1021 }
1022 }
1023
1024 Ok(examples.into_iter())
1025 }
1026
1027 #[allow(clippy::type_complexity)]
1028 fn decode_example_message<'m>(
1029 message: &'m escargot::format::Message<'_>,
1030 ) -> Option<crate::assert::Result<(&'m str, crate::assert::Result<std::path::PathBuf>)>> {
1031 match message {
1032 escargot::format::Message::CompilerMessage(msg) => {
1033 let level = msg.message.level;
1034 if level == escargot::format::diagnostic::DiagnosticLevel::Ice
1035 || level == escargot::format::diagnostic::DiagnosticLevel::Error
1036 {
1037 let output = msg
1038 .message
1039 .rendered
1040 .as_deref()
1041 .unwrap_or_else(|| msg.message.message.as_ref())
1042 .to_owned();
1043 if is_example_target(&msg.target) {
1044 let bin = Err(crate::assert::Error::new(output));
1045 Some(Ok((msg.target.name.as_ref(), bin)))
1046 } else {
1047 Some(Err(crate::assert::Error::new(output)))
1048 }
1049 } else {
1050 None
1051 }
1052 }
1053 escargot::format::Message::CompilerArtifact(artifact) => {
1054 if !artifact.profile.test && is_example_target(&artifact.target) {
1055 let path = artifact
1056 .executable
1057 .clone()
1058 .expect("cargo is new enough for this to be present");
1059 let bin = Ok(path.into_owned());
1060 Some(Ok((artifact.target.name.as_ref(), bin)))
1061 } else {
1062 None
1063 }
1064 }
1065 _ => None,
1066 }
1067 }
1068
1069 fn is_example_target(target: &escargot::format::Target<'_>) -> bool {
1070 target.crate_types == ["bin"] && target.kind == ["example"]
1071 }
1072}
1073
1074#[test]
1075#[should_panic = "`CARGO_BIN_EXE_non-existent` is unset
1076help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_non-existent`"]
1077fn cargo_bin_in_unit_test() {
1078 cargo_bin("non-existent");
1079}