1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Interact with `cargo`

#[doc(inline)]
pub use snapbox::cmd::cargo_bin;

#[cfg(feature = "examples")]
pub use examples::{compile_example, compile_examples};

#[cfg(feature = "examples")]
pub(crate) mod examples {
    /// Prepare an example for testing
    ///
    /// Unlike `cargo_bin!`, this does not inherit all of the current compiler settings.  It
    /// will match the current target and profile but will not get feature flags.  Pass those arguments
    /// to the compiler via `args`.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// #[test]
    /// fn cli_tests() {
    ///     trycmd::TestCases::new()
    ///         .register_bin("example-fixture", trycmd::cargo::compile_example("example-fixture", []))
    ///         .case("examples/cmd/*.trycmd");
    /// }
    /// ```
    pub fn compile_example<'a>(
        target_name: &str,
        args: impl IntoIterator<Item = &'a str>,
    ) -> crate::schema::Bin {
        compile_example_path(target_name, args).into()
    }

    fn compile_example_path<'a>(
        target_name: &str,
        args: impl IntoIterator<Item = &'a str>,
    ) -> Result<crate::schema::Bin, crate::Error> {
        snapbox::debug!("Compiling example {}", target_name);
        let messages = escargot::CargoBuild::new()
            .current_target()
            .current_release()
            .example(target_name)
            .args(args)
            .exec()
            .map_err(|e| crate::Error::new(e.to_string()))?;
        for message in messages {
            let message = message.map_err(|e| crate::Error::new(e.to_string()))?;
            let message = message
                .decode()
                .map_err(|e| crate::Error::new(e.to_string()))?;
            snapbox::debug!("Message: {:?}", message);
            if let Some(bin) = decode_example_message(&message) {
                let (name, bin) = bin?;
                assert_eq!(target_name, name);
                return Ok(bin);
            }
        }

        Err(crate::Error::new(format!(
            "Unknown error building example {}",
            target_name
        )))
    }

    /// Prepare all examples for testing
    ///
    /// Unlike `cargo_bin!`, this does not inherit all of the current compiler settings.  It
    /// will match the current target and profile but will not get feature flags.  Pass those arguments
    /// to the compiler via `args`.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// #[test]
    /// fn cli_tests() {
    ///     trycmd::TestCases::new()
    ///         .register_bins(trycmd::cargo::compile_examples([]).unwrap())
    ///         .case("examples/cmd/*.trycmd");
    /// }
    /// ```
    pub fn compile_examples<'a>(
        args: impl IntoIterator<Item = &'a str>,
    ) -> Result<impl Iterator<Item = (String, crate::schema::Bin)>, crate::Error> {
        snapbox::debug!("Compiling examples");
        let mut examples = std::collections::BTreeMap::new();

        let messages = escargot::CargoBuild::new()
            .current_target()
            .current_release()
            .examples()
            .args(args)
            .exec()
            .map_err(|e| crate::Error::new(e.to_string()))?;
        for message in messages {
            let message = message.map_err(|e| crate::Error::new(e.to_string()))?;
            let message = message
                .decode()
                .map_err(|e| crate::Error::new(e.to_string()))?;
            snapbox::debug!("Message: {:?}", message);
            if let Some(bin) = decode_example_message(&message) {
                let (name, bin) = bin?;
                examples.insert(name.to_owned(), bin);
            }
        }

        Ok(examples.into_iter())
    }

    fn decode_example_message<'m>(
        message: &'m escargot::format::Message,
    ) -> Option<Result<(&'m str, crate::schema::Bin), crate::Error>> {
        match message {
            escargot::format::Message::CompilerMessage(msg) => {
                let level = msg.message.level;
                if level == escargot::format::diagnostic::DiagnosticLevel::Ice
                    || level == escargot::format::diagnostic::DiagnosticLevel::Error
                {
                    let output = msg
                        .message
                        .rendered
                        .as_deref()
                        .unwrap_or_else(|| msg.message.message.as_ref())
                        .to_owned();
                    if is_example_target(&msg.target) {
                        let bin = crate::schema::Bin::Error(crate::Error::new(output));
                        Some(Ok((msg.target.name.as_ref(), bin)))
                    } else {
                        Some(Err(crate::Error::new(output)))
                    }
                } else {
                    None
                }
            }
            escargot::format::Message::CompilerArtifact(artifact) => {
                if !artifact.profile.test && is_example_target(&artifact.target) {
                    let path = artifact
                        .executable
                        .clone()
                        .expect("cargo is new enough for this to be present");
                    let bin = crate::schema::Bin::Path(path.into_owned());
                    Some(Ok((artifact.target.name.as_ref(), bin)))
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    fn is_example_target(target: &escargot::format::Target) -> bool {
        target.crate_types == ["bin"] && target.kind == ["example"]
    }
}