Skip to main content

nu_test_support/harness/
deps.rs

1use std::{
2    borrow::Cow,
3    path::PathBuf,
4    process::{Command, Stdio},
5};
6
7#[cfg(feature = "plugin")]
8use nu_protocol::ShellError;
9
10use crate::harness::{BUILD_PROFILE, BUILD_TARGET, TARGET_DIR};
11
12#[non_exhaustive]
13#[derive(derive_more::Debug, Clone, PartialEq, Eq, Hash)]
14pub struct Dependency<'a> {
15    /// Name of the binary without extension.
16    pub bin_name: Cow<'a, str>,
17
18    /// Args to build the binary.
19    ///
20    /// Do not include `cargo build` for example.
21    #[debug("{:?}", format!("cargo build {build_args}"))]
22    build_args: Cow<'a, str>,
23
24    /// Whether the binary is a plugin.
25    ///
26    /// Plugins get automatically loaded.
27    pub is_plugin: bool,
28}
29
30macro_rules! dependency {
31    ($dep:literal) => { pastey::paste! {
32        /// Binary dependency on
33        #[doc = concat!("`", $dep, "`.")]
34        ///
35        /// Before executing this test, we automatically run
36        #[doc = concat!("`cargo build --package ", $dep, "`.")]
37        /// The binary will also automatically made available to use via
38        /// [`test()`](crate::prelude::test).
39        pub const [<$dep:snake:upper>]: &'static Dependency<'static> = &[<$dep:snake:upper _DEP>];
40        static [<$dep:snake:upper _DEP>]: Dependency<'static> =
41            Dependency::new($dep, concat!("--package ", $dep));
42    }}
43}
44
45dependency!("nu");
46dependency!("nu_plugin_custom_values");
47dependency!("nu_plugin_example");
48dependency!("nu_plugin_formats");
49dependency!("nu_plugin_gstat");
50dependency!("nu_plugin_inc");
51dependency!("nu_plugin_polars");
52dependency!("nu_plugin_query");
53dependency!("nu_plugin_stress_internals");
54
55macro_rules! testbin_dependency {
56    ($bin:literal) => { pastey::paste! {
57        /// Test binary dependency
58        #[doc = concat!("`", $bin, "`.")]
59        ///
60        /// Before executing this test, we automatically run
61        #[doc = concat!("`cargo build --package testbins --bin ", $bin, "`.")]
62        /// The binary will also automatically made available to use via
63        /// [`test()`](crate::prelude::test).
64        pub const [< TESTBIN_$bin:snake:upper>]: &'static Dependency<'static> = &[<TESTBIN_ $bin:snake:upper _DEP>];
65        static [<TESTBIN_ $bin:snake:upper _DEP>]: Dependency<'static> =
66            Dependency::new($bin, concat!("--package testbins --bin ", $bin));
67    }}
68}
69
70testbin_dependency!("chop");
71testbin_dependency!("cococo");
72testbin_dependency!("echo_env");
73testbin_dependency!("echo_env_mixed");
74testbin_dependency!("echo_env_stderr");
75testbin_dependency!("echo_env_stderr_fail");
76testbin_dependency!("fail");
77testbin_dependency!("iecho");
78testbin_dependency!("input_bytes_length");
79testbin_dependency!("meow");
80testbin_dependency!("meowb");
81testbin_dependency!("nonu");
82testbin_dependency!("relay");
83testbin_dependency!("repeat_bytes");
84testbin_dependency!("repeater");
85
86impl Dependency<'static> {
87    const fn new(bin_name: &'static str, build_args: &'static str) -> Self {
88        Dependency {
89            bin_name: Cow::Borrowed(bin_name),
90            build_args: Cow::Borrowed(build_args),
91            is_plugin: bin_name.len() >= "nu_plugin".len()
92                && nu_utils::const_str::eq(bin_name.split_at("nu_plugin".len()).0, "nu_plugin"),
93        }
94    }
95
96    pub fn build_command(&self) -> Command {
97        let mut command = Command::new("cargo");
98        command
99            .arg("build")
100            .args(self.build_args.split(" "))
101            .stdout(Stdio::inherit())
102            .stderr(Stdio::inherit());
103
104        if BUILD_PROFILE != "debug" {
105            command.arg(format!("--profile={BUILD_PROFILE}"));
106        }
107
108        if let Some(target) = BUILD_TARGET {
109            command.arg(format!("--target={target}"));
110        }
111
112        // ensure that cargo is called cleanly to avoid unnecessary rebuilds
113        for (key, _) in std::env::vars() {
114            #[rustfmt::skip]
115            match key.as_ref() {
116                "CARGO"
117                | "CARGO_MANIFEST_DIR"
118                | "CARGO_MANIFEST_PATH"
119                | "CARGO_MANIFEST_LINKS"
120                | "CARGO_CRATE_NAME"
121                | "CARGO_BIN_NAME"
122                | "OUT_DIR"
123                | "PROFILE"
124                | "OPT_LEVEL"
125                | "DEBUG"
126                | "HOST"
127                | "TARGET" => command.env_remove(key),
128
129                key if key.starts_with("CARGO_PKG_")
130                    || key.starts_with("CARGO_CFG_")
131                    || key.starts_with("CARGO_FEATURE_")
132                    || key.starts_with("CARGO_BIN_EXE_")
133                    || key.starts_with("DEP_") => command.env_remove(key),
134
135                _ => &mut command,
136            };
137        }
138
139        command
140    }
141
142    /// Path to the directory that contains the binary.
143    ///
144    /// If you need the binary path, use [`path`].
145    #[track_caller]
146    pub fn bin_dir(&self) -> PathBuf {
147        let target_dir = TARGET_DIR.get().expect("TARGET_DIR is not set");
148        match BUILD_TARGET {
149            Some(target) => target_dir.join(target).join(BUILD_PROFILE),
150            None => target_dir.join(BUILD_PROFILE),
151        }
152    }
153
154    /// Path to the binary.
155    ///
156    /// If you need the dir this binary is in, use [`bin_dir`].
157    #[track_caller]
158    pub fn path(&self) -> PathBuf {
159        #[cfg(not(windows))]
160        let bin_name = self.bin_name.as_ref();
161
162        #[cfg(windows)]
163        let bin_name = format!("{}.exe", self.bin_name.as_ref());
164
165        self.bin_dir().join(bin_name)
166    }
167
168    #[cfg(feature = "plugin")]
169    pub fn preload_plugin(&self) -> Result<PreloadedPlugin, ShellError> {
170        use nu_plugin_engine::{GetPlugin, PersistentPlugin};
171        use nu_protocol::{PluginIdentity, RegisteredPlugin};
172        use std::sync::Arc;
173
174        let filename = self.path();
175        let identity = PluginIdentity::new(filename, None).expect("valid plugin name");
176        let plugin = Arc::new(PersistentPlugin::new(identity.clone(), Default::default()));
177
178        let interface = plugin.clone().get_plugin(None)?;
179        let metadata = interface.get_metadata()?;
180        plugin.set_metadata(Some(metadata.clone()));
181        let signatures = Arc::from(interface.get_signature()?);
182        drop(interface);
183
184        Ok(PreloadedPlugin {
185            identity: Arc::new(identity),
186            plugin,
187            metadata,
188            signatures,
189        })
190    }
191}
192
193impl Ord for Dependency<'_> {
194    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
195        self.bin_name.cmp(&other.bin_name)
196    }
197}
198
199impl PartialOrd for Dependency<'_> {
200    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
201        Some(self.cmp(other))
202    }
203}
204
205#[cfg(feature = "plugin")]
206#[non_exhaustive]
207#[derive(Debug, Clone)]
208pub struct PreloadedPlugin {
209    pub(crate) identity: std::sync::Arc<nu_protocol::PluginIdentity>,
210    pub(crate) plugin: std::sync::Arc<nu_plugin_engine::PersistentPlugin>,
211    pub(crate) metadata: nu_protocol::PluginMetadata,
212    pub(crate) signatures: std::sync::Arc<[nu_protocol::PluginSignature]>,
213}