1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::env::{current_exe, temp_dir};
6 use std::fs::{create_dir_all, remove_dir_all};
7 use std::ops::Deref;
8 use std::path::{Path, PathBuf};
9 use std::thread::panicking;
10 
11 use super::linux::{getpid, gettid};
12 
13 /// Returns a stable path based on the label, pid, and tid. If the label isn't provided the
14 /// current_exe is used instead.
get_temp_path(label: Option<&str>) -> PathBuf15 pub fn get_temp_path(label: Option<&str>) -> PathBuf {
16     if let Some(label) = label {
17         temp_dir().join(format!("{}-{}-{}", label, getpid(), gettid()))
18     } else {
19         get_temp_path(Some(current_exe().unwrap().to_str().unwrap()))
20     }
21 }
22 
23 /// Automatically deletes the path it contains when it goes out of scope unless it is a test and
24 /// drop is called after a panic!.
25 ///
26 /// This is particularly useful for creating temporary directories for use with tests.
27 pub struct ScopedPath<P: AsRef<Path>>(P);
28 
29 impl<P: AsRef<Path>> ScopedPath<P> {
create(p: P) -> Result<Self, std::io::Error>30     pub fn create(p: P) -> Result<Self, std::io::Error> {
31         create_dir_all(p.as_ref())?;
32         Ok(ScopedPath(p))
33     }
34 }
35 
36 impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
as_ref(&self) -> &Path37     fn as_ref(&self) -> &Path {
38         self.0.as_ref()
39     }
40 }
41 
42 impl<P: AsRef<Path>> Deref for ScopedPath<P> {
43     type Target = Path;
44 
deref(&self) -> &Self::Target45     fn deref(&self) -> &Self::Target {
46         self.0.as_ref()
47     }
48 }
49 
50 impl<P: AsRef<Path>> Drop for ScopedPath<P> {
drop(&mut self)51     fn drop(&mut self) {
52         // Leave the files on a failed test run for debugging.
53         if panicking() && cfg!(test) {
54             eprintln!("NOTE: Not removing {}", self.display());
55             return;
56         }
57         if let Err(e) = remove_dir_all(&**self) {
58             eprintln!("Failed to remove {}: {}", self.display(), e);
59         }
60     }
61 }
62 
63 #[cfg(test)]
64 pub(crate) mod tests {
65     use super::*;
66 
67     use std::panic::catch_unwind;
68 
69     #[test]
gettemppath()70     fn gettemppath() {
71         assert_ne!("", get_temp_path(None).to_string_lossy());
72         assert_eq!(
73             get_temp_path(None),
74             get_temp_path(Some(current_exe().unwrap().to_str().unwrap()))
75         );
76         assert_ne!(
77             get_temp_path(Some("label")),
78             get_temp_path(Some(current_exe().unwrap().to_str().unwrap()))
79         );
80     }
81 
82     #[test]
scopedpath_exists()83     fn scopedpath_exists() {
84         let tmp_path = get_temp_path(None);
85         {
86             let scoped_path = ScopedPath::create(&tmp_path).unwrap();
87             assert!(scoped_path.exists());
88         }
89         assert!(!tmp_path.exists());
90     }
91 
92     #[test]
scopedpath_notexists()93     fn scopedpath_notexists() {
94         let tmp_path = get_temp_path(None);
95         {
96             let _scoped_path = ScopedPath(&tmp_path);
97         }
98         assert!(!tmp_path.exists());
99     }
100 
101     #[test]
scopedpath_panic()102     fn scopedpath_panic() {
103         let tmp_path = get_temp_path(None);
104         assert!(catch_unwind(|| {
105             {
106                 let scoped_path = ScopedPath::create(&tmp_path).unwrap();
107                 assert!(scoped_path.exists());
108                 panic!()
109             }
110         })
111         .is_err());
112         assert!(tmp_path.exists());
113         remove_dir_all(&tmp_path).unwrap();
114     }
115 }
116