1 // Copyright 2021 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::io::Result;
8 use std::ops::Deref;
9 use std::path::{Path, PathBuf};
10 use std::thread::panicking;
11
12 use crate::{getpid, gettid};
13
14 /// Returns a stable path based on the label, pid, and tid. If the label isn't provided the
15 /// current_exe is used instead.
get_temp_path(label: Option<&str>) -> PathBuf16 pub fn get_temp_path(label: Option<&str>) -> PathBuf {
17 if let Some(label) = label {
18 temp_dir().join(format!("{}-{}-{}", label, getpid(), gettid()))
19 } else {
20 get_temp_path(Some(
21 current_exe()
22 .unwrap()
23 .file_name()
24 .unwrap()
25 .to_str()
26 .unwrap(),
27 ))
28 }
29 }
30
31 /// Automatically deletes the path it contains when it goes out of scope unless it is a test and
32 /// drop is called after a panic!.
33 ///
34 /// This is particularly useful for creating temporary directories for use with tests.
35 pub struct ScopedPath<P: AsRef<Path>>(P);
36
37 impl<P: AsRef<Path>> ScopedPath<P> {
create(p: P) -> Result<Self>38 pub fn create(p: P) -> Result<Self> {
39 create_dir_all(p.as_ref())?;
40 Ok(ScopedPath(p))
41 }
42 }
43
44 impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
as_ref(&self) -> &Path45 fn as_ref(&self) -> &Path {
46 self.0.as_ref()
47 }
48 }
49
50 impl<P: AsRef<Path>> Deref for ScopedPath<P> {
51 type Target = Path;
52
deref(&self) -> &Self::Target53 fn deref(&self) -> &Self::Target {
54 self.0.as_ref()
55 }
56 }
57
58 impl<P: AsRef<Path>> Drop for ScopedPath<P> {
drop(&mut self)59 fn drop(&mut self) {
60 // Leave the files on a failed test run for debugging.
61 if panicking() && cfg!(test) {
62 eprintln!("NOTE: Not removing {}", self.display());
63 return;
64 }
65 if let Err(e) = remove_dir_all(&**self) {
66 eprintln!("Failed to remove {}: {}", self.display(), e);
67 }
68 }
69 }
70
71 #[cfg(test)]
72 pub(crate) mod tests {
73 use super::*;
74
75 use std::panic::catch_unwind;
76
77 #[test]
gettemppath()78 fn gettemppath() {
79 assert_ne!("", get_temp_path(None).to_string_lossy());
80 assert!(get_temp_path(None).starts_with(temp_dir()));
81 assert_eq!(
82 get_temp_path(None),
83 get_temp_path(Some(
84 current_exe()
85 .unwrap()
86 .file_name()
87 .unwrap()
88 .to_str()
89 .unwrap()
90 ))
91 );
92 assert_ne!(
93 get_temp_path(Some("label")),
94 get_temp_path(Some(
95 current_exe()
96 .unwrap()
97 .file_name()
98 .unwrap()
99 .to_str()
100 .unwrap()
101 ))
102 );
103 }
104
105 #[test]
scopedpath_exists()106 fn scopedpath_exists() {
107 let tmp_path = get_temp_path(None);
108 {
109 let scoped_path = ScopedPath::create(&tmp_path).unwrap();
110 assert!(scoped_path.exists());
111 }
112 assert!(!tmp_path.exists());
113 }
114
115 #[test]
scopedpath_notexists()116 fn scopedpath_notexists() {
117 let tmp_path = get_temp_path(None);
118 {
119 let _scoped_path = ScopedPath(&tmp_path);
120 }
121 assert!(!tmp_path.exists());
122 }
123
124 #[test]
scopedpath_panic()125 fn scopedpath_panic() {
126 let tmp_path = get_temp_path(None);
127 assert!(catch_unwind(|| {
128 {
129 let scoped_path = ScopedPath::create(&tmp_path).unwrap();
130 assert!(scoped_path.exists());
131 panic!()
132 }
133 })
134 .is_err());
135 assert!(tmp_path.exists());
136 remove_dir_all(&tmp_path).unwrap();
137 }
138 }
139