nextest_runner/cargo_config/
custom_platform.rs

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
154
155
156
157
158
159
160
161
162
163
// Copyright (c) The nextest Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use super::TargetTripleSource;
use crate::errors::TargetTripleError;
use camino::{Utf8Path, Utf8PathBuf};
use camino_tempfile::Utf8TempDir;

/// Represents a custom platform that was extracted from stored metadata.
///
/// The platform is stored in a temporary directory, and is deleted when this struct is dropped.
#[derive(Debug)]
pub struct ExtractedCustomPlatform {
    source: TargetTripleSource,
    dir: Utf8TempDir,
    path: Utf8PathBuf,
}

impl ExtractedCustomPlatform {
    /// Writes the custom JSON to a temporary directory.
    pub fn new(
        triple_str: &str,
        json: &str,
        source: TargetTripleSource,
    ) -> Result<Self, TargetTripleError> {
        // Extract the JSON to a temporary file. Cargo requires that the file name be of the form
        // `<triple_str>.json`.
        let temp_dir = camino_tempfile::Builder::new()
            .prefix("nextest-custom-target-")
            .rand_bytes(5)
            .tempdir()
            .map_err(|error| TargetTripleError::CustomPlatformTempDirError {
                source: source.clone(),
                error,
            })?;

        let path = temp_dir.path().join(format!("{triple_str}.json"));

        std::fs::write(&path, json).map_err(|error| {
            TargetTripleError::CustomPlatformWriteError {
                source: source.clone(),
                path: path.clone(),
                error,
            }
        })?;

        Ok(Self {
            source,
            dir: temp_dir,
            path,
        })
    }

    /// Returns the source of the custom platform.
    pub fn source(&self) -> &TargetTripleSource {
        &self.source
    }

    /// Returns the temporary directory.
    pub fn dir(&self) -> &Utf8TempDir {
        &self.dir
    }

    /// Returns the path to the JSON file containing the custom platform.
    pub fn path(&self) -> &Utf8Path {
        &self.path
    }

    /// Close the temporary directory.
    ///
    /// The directory is deleted when this struct is dropped, but this method can be used to detect
    /// errors during cleanup.
    pub fn close(self) -> Result<(), TargetTripleError> {
        let dir_path = self.dir.path().to_owned();
        self.dir
            .close()
            .map_err(|error| TargetTripleError::CustomPlatformCloseError {
                source: self.source,
                dir_path,
                error,
            })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cargo_config::{
        test_helpers::setup_temp_dir, CargoTargetArg, TargetDefinitionLocation, TargetTriple,
    };
    use color_eyre::eyre::{bail, eyre, Context, Result};

    #[test]
    fn test_extracted_custom_platform() -> Result<()> {
        // Integration testing a full custom platform is hard because it requires a build of std. So
        // we just do a limited unit test: use the existing custom platform fixture, and run through
        // the serialize/extract process, ensuring that the initial and final platform instances
        // produced are the same.

        let target = {
            // Put this in here to ensure that this temp dir is dropped -- once the target is read
            // there should be no further access to the temp dir.
            let temp_dir = setup_temp_dir()?;
            let platform_path = temp_dir.path().join("custom-target/my-target.json");

            // Read in the custom platform and turn it into a `TargetTriple`.
            TargetTriple::custom_from_path(
                &platform_path,
                TargetTripleSource::CliOption,
                temp_dir.path(),
            )?
        };

        // Serialize the `TargetTriple` to a `PlatformSummary`.
        let summary = target.platform.to_summary();

        // Now deserialize the `PlatformSummary` back into a `TargetTriple`.
        let target2 = TargetTriple::deserialize(Some(summary))
            .wrap_err("deserializing target triple")?
            .ok_or_else(|| eyre!("deserializing target triple resulted in None"))?;

        assert_eq!(target2.source, TargetTripleSource::Metadata);
        assert!(
            matches!(
                target2.location,
                TargetDefinitionLocation::MetadataCustom(_)
            ),
            "triple2.location should be MetadataCustom: {:?}",
            target2.location
        );

        // Now attempt to extract the custom platform.
        let arg = target2
            .to_cargo_target_arg()
            .wrap_err("converting to cargo target arg")?;
        let extracted = match &arg {
            CargoTargetArg::Extracted(extracted) => extracted,
            _ => bail!("expected CargoTargetArg::Extracted, found {:?}", arg),
        };

        // Generally ensure that Cargo will work with the extracted path.
        assert!(extracted.path().is_absolute(), "path should be absolute");
        assert!(
            extracted.path().ends_with("my-target.json"),
            "extracted path should end with 'my-target.json'"
        );
        assert_eq!(
            arg.to_string(),
            extracted.path(),
            "arg matches extracted path"
        );

        // Now, read in the path and turn it into another TargetTriple.
        let target3 = TargetTriple::custom_from_path(
            extracted.path(),
            TargetTripleSource::CliOption,
            extracted.dir().path(),
        )?;
        assert_eq!(target3.platform, target.platform, "platform roundtrips");

        Ok(())
    }
}