nextest_runner/cargo_config/
custom_platform.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::TargetTripleSource;
5use crate::errors::TargetTripleError;
6use camino::{Utf8Path, Utf8PathBuf};
7use camino_tempfile::Utf8TempDir;
8
9/// Represents a custom platform that was extracted from stored metadata.
10///
11/// The platform is stored in a temporary directory, and is deleted when this struct is dropped.
12#[derive(Debug)]
13pub struct ExtractedCustomPlatform {
14    source: TargetTripleSource,
15    dir: Utf8TempDir,
16    path: Utf8PathBuf,
17}
18
19impl ExtractedCustomPlatform {
20    /// Writes the custom JSON to a temporary directory.
21    pub fn new(
22        triple_str: &str,
23        json: &str,
24        source: TargetTripleSource,
25    ) -> Result<Self, TargetTripleError> {
26        // Extract the JSON to a temporary file. Cargo requires that the file name be of the form
27        // `<triple_str>.json`.
28        let temp_dir = camino_tempfile::Builder::new()
29            .prefix("nextest-custom-target-")
30            .rand_bytes(5)
31            .tempdir()
32            .map_err(|error| TargetTripleError::CustomPlatformTempDirError {
33                source: source.clone(),
34                error,
35            })?;
36
37        let path = temp_dir.path().join(format!("{triple_str}.json"));
38
39        std::fs::write(&path, json).map_err(|error| {
40            TargetTripleError::CustomPlatformWriteError {
41                source: source.clone(),
42                path: path.clone(),
43                error,
44            }
45        })?;
46
47        Ok(Self {
48            source,
49            dir: temp_dir,
50            path,
51        })
52    }
53
54    /// Returns the source of the custom platform.
55    pub fn source(&self) -> &TargetTripleSource {
56        &self.source
57    }
58
59    /// Returns the temporary directory.
60    pub fn dir(&self) -> &Utf8TempDir {
61        &self.dir
62    }
63
64    /// Returns the path to the JSON file containing the custom platform.
65    pub fn path(&self) -> &Utf8Path {
66        &self.path
67    }
68
69    /// Close the temporary directory.
70    ///
71    /// The directory is deleted when this struct is dropped, but this method can be used to detect
72    /// errors during cleanup.
73    pub fn close(self) -> Result<(), TargetTripleError> {
74        let dir_path = self.dir.path().to_owned();
75        self.dir
76            .close()
77            .map_err(|error| TargetTripleError::CustomPlatformCloseError {
78                source: self.source,
79                dir_path,
80                error,
81            })
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::cargo_config::{
89        CargoTargetArg, TargetDefinitionLocation, TargetTriple, test_helpers::setup_temp_dir,
90    };
91    use color_eyre::eyre::{Context, Result, bail, eyre};
92
93    #[test]
94    fn test_extracted_custom_platform() -> Result<()> {
95        // Integration testing a full custom platform is hard because it requires a build of std. So
96        // we just do a limited unit test: use the existing custom platform fixture, and run through
97        // the serialize/extract process, ensuring that the initial and final platform instances
98        // produced are the same.
99
100        let target = {
101            // Put this in here to ensure that this temp dir is dropped -- once the target is read
102            // there should be no further access to the temp dir.
103            let temp_dir = setup_temp_dir()?;
104            let platform_path = temp_dir.path().join("custom-target/my-target.json");
105
106            // Read in the custom platform and turn it into a `TargetTriple`.
107            TargetTriple::custom_from_path(
108                &platform_path,
109                TargetTripleSource::CliOption,
110                temp_dir.path(),
111            )?
112        };
113
114        // Serialize the `TargetTriple` to a `PlatformSummary`.
115        let summary = target.platform.to_summary();
116
117        // Now deserialize the `PlatformSummary` back into a `TargetTriple`.
118        let target2 = TargetTriple::deserialize(Some(summary))
119            .wrap_err("deserializing target triple")?
120            .ok_or_else(|| eyre!("deserializing target triple resulted in None"))?;
121
122        assert_eq!(target2.source, TargetTripleSource::Metadata);
123        assert!(
124            matches!(
125                target2.location,
126                TargetDefinitionLocation::MetadataCustom(_)
127            ),
128            "triple2.location should be MetadataCustom: {:?}",
129            target2.location
130        );
131
132        // Now attempt to extract the custom platform.
133        let arg = target2
134            .to_cargo_target_arg()
135            .wrap_err("converting to cargo target arg")?;
136        let extracted = match &arg {
137            CargoTargetArg::Extracted(extracted) => extracted,
138            _ => bail!("expected CargoTargetArg::Extracted, found {:?}", arg),
139        };
140
141        // Generally ensure that Cargo will work with the extracted path.
142        assert!(extracted.path().is_absolute(), "path should be absolute");
143        assert!(
144            extracted.path().ends_with("my-target.json"),
145            "extracted path should end with 'my-target.json'"
146        );
147        assert_eq!(
148            arg.to_string(),
149            extracted.path(),
150            "arg matches extracted path"
151        );
152
153        // Now, read in the path and turn it into another TargetTriple.
154        let target3 = TargetTriple::custom_from_path(
155            extracted.path(),
156            TargetTripleSource::CliOption,
157            extracted.dir().path(),
158        )?;
159        assert_eq!(target3.platform, target.platform, "platform roundtrips");
160
161        Ok(())
162    }
163}