1use super::Diff;
2use command_extra::CommandExtra;
3use derive_more::{Display, Error};
4use pipe_trait::Pipe;
5use std::{
6 fs, io,
7 process::{Command, Output},
8};
9use tempfile::tempdir;
10
11#[derive(Debug, Display, Error)]
13pub enum DiffExecError {
14 #[display(fmt = "Failed to create temporary directory: {_0}")]
15 Workspace(io::Error),
16 #[display(fmt = "Failed to create temporary file the left value: {_0}")]
17 Left(io::Error),
18 #[display(fmt = "Failed to create temporary file the right value: {_0}")]
19 Right(io::Error),
20 #[display(fmt = "Failed to execute the 'diff' command: {_0}")]
21 Exec(io::Error),
22 #[display(fmt = "The 'diff' process exits with code {code:?}")]
23 Status { code: Option<i32>, stderr: Vec<u8> },
24}
25
26impl<Left, Right> Diff<Left, Right>
27where
28 Left: AsRef<str>,
29 Right: AsRef<str>,
30{
31 pub fn exec(&self) -> Result<Option<String>, DiffExecError> {
38 let Diff {
39 left,
40 right,
41 color,
42 unified,
43 } = self;
44
45 let workspace = tempdir().map_err(DiffExecError::Workspace)?;
46
47 let write_file = |name: &str, text: &str| fs::write(workspace.path().join(name), text);
48 write_file("left", left.as_ref()).map_err(DiffExecError::Left)?;
49 write_file("right", right.as_ref()).map_err(DiffExecError::Right)?;
50
51 let output = Command::new("diff")
52 .with_current_dir(&workspace)
53 .with_args(color.as_flag())
54 .with_args(unified.then_some("--unified"))
55 .with_arg("--label=left")
56 .with_arg("--label=right")
57 .with_arg("left")
58 .with_arg("right")
59 .output()
60 .map_err(DiffExecError::Exec)?;
61 let Output {
62 status,
63 stdout,
64 stderr,
65 } = output;
66
67 if status.success() {
68 return Ok(None);
69 }
70
71 if status.code() == Some(1) {
72 return stdout
73 .pipe(String::from_utf8)
74 .expect("The stdout of the diff command should be valid UTF-8")
75 .pipe(Some)
76 .pipe(Ok);
77 }
78
79 Err(DiffExecError::Status {
80 code: status.code(),
81 stderr,
82 })
83 }
84
85 pub fn assert_eq(&self) {
89 match self.exec() {
90 Ok(None) => { }
91 Ok(Some(diff)) => panic!("assertion failed: `(left == right)`\n{diff}"),
92 Err(_) => pretty_assertions::assert_str_eq!(self.left.as_ref(), self.right.as_ref()),
93 }
94 }
95}