1*c2e18aaaSAndroid Build Coastguard Worker //! Recursively hash the contents of a directory
2*c2e18aaaSAndroid Build Coastguard Worker use anyhow::Result;
3*c2e18aaaSAndroid Build Coastguard Worker use hex::encode;
4*c2e18aaaSAndroid Build Coastguard Worker use rayon::iter::IntoParallelIterator;
5*c2e18aaaSAndroid Build Coastguard Worker use rayon::iter::ParallelIterator;
6*c2e18aaaSAndroid Build Coastguard Worker use ring::digest::{self, SHA256};
7*c2e18aaaSAndroid Build Coastguard Worker use serde::{Deserialize, Serialize};
8*c2e18aaaSAndroid Build Coastguard Worker use std::collections::HashMap;
9*c2e18aaaSAndroid Build Coastguard Worker use std::fs;
10*c2e18aaaSAndroid Build Coastguard Worker use std::io::{self, Read, Write};
11*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::FileTypeExt;
12*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::MetadataExt;
13*c2e18aaaSAndroid Build Coastguard Worker use std::os::unix::fs::PermissionsExt;
14*c2e18aaaSAndroid Build Coastguard Worker use std::path::{Path, PathBuf};
15*c2e18aaaSAndroid Build Coastguard Worker use walkdir::WalkDir;
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Worker #[allow(missing_docs)]
18*c2e18aaaSAndroid Build Coastguard Worker #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
19*c2e18aaaSAndroid Build Coastguard Worker pub enum FileType {
20*c2e18aaaSAndroid Build Coastguard Worker #[default]
21*c2e18aaaSAndroid Build Coastguard Worker File,
22*c2e18aaaSAndroid Build Coastguard Worker Symlink,
23*c2e18aaaSAndroid Build Coastguard Worker Directory,
24*c2e18aaaSAndroid Build Coastguard Worker }
25*c2e18aaaSAndroid Build Coastguard Worker
26*c2e18aaaSAndroid Build Coastguard Worker #[derive(Clone, Copy, PartialEq)]
27*c2e18aaaSAndroid Build Coastguard Worker #[allow(dead_code)]
28*c2e18aaaSAndroid Build Coastguard Worker pub enum DiffMode {
29*c2e18aaaSAndroid Build Coastguard Worker IgnorePermissions,
30*c2e18aaaSAndroid Build Coastguard Worker UsePermissions,
31*c2e18aaaSAndroid Build Coastguard Worker }
32*c2e18aaaSAndroid Build Coastguard Worker
33*c2e18aaaSAndroid Build Coastguard Worker /// Represents a file, directory, or symlink.
34*c2e18aaaSAndroid Build Coastguard Worker /// We need enough information to be able to tell if:
35*c2e18aaaSAndroid Build Coastguard Worker /// 1) A regular file changes to a directory or symlink.
36*c2e18aaaSAndroid Build Coastguard Worker /// 2) A symlink's target file path changes.
37*c2e18aaaSAndroid Build Coastguard Worker #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
38*c2e18aaaSAndroid Build Coastguard Worker pub struct FileMetadata {
39*c2e18aaaSAndroid Build Coastguard Worker /// Is this a file, dir or symlink?
40*c2e18aaaSAndroid Build Coastguard Worker pub file_type: FileType,
41*c2e18aaaSAndroid Build Coastguard Worker
42*c2e18aaaSAndroid Build Coastguard Worker /// Path that this symlinks to or ""
43*c2e18aaaSAndroid Build Coastguard Worker #[serde(skip_serializing_if = "String::is_empty", default)]
44*c2e18aaaSAndroid Build Coastguard Worker pub symlink: String,
45*c2e18aaaSAndroid Build Coastguard Worker
46*c2e18aaaSAndroid Build Coastguard Worker /// Sha256 of contents for regular files.
47*c2e18aaaSAndroid Build Coastguard Worker #[serde(skip_serializing_if = "String::is_empty", default)]
48*c2e18aaaSAndroid Build Coastguard Worker pub digest: String,
49*c2e18aaaSAndroid Build Coastguard Worker
50*c2e18aaaSAndroid Build Coastguard Worker /// Permission bits.
51*c2e18aaaSAndroid Build Coastguard Worker #[serde(default, skip_serializing_if = "is_default")]
52*c2e18aaaSAndroid Build Coastguard Worker pub permission_bits: u32,
53*c2e18aaaSAndroid Build Coastguard Worker
54*c2e18aaaSAndroid Build Coastguard Worker // A unique string used only to determine if digest should be recomputed
55*c2e18aaaSAndroid Build Coastguard Worker // or can be used from cache.
56*c2e18aaaSAndroid Build Coastguard Worker // The key includes: path, size, mtime, and ctime
57*c2e18aaaSAndroid Build Coastguard Worker //
58*c2e18aaaSAndroid Build Coastguard Worker // The cache_key will be different for the host/device and is not used to compare
59*c2e18aaaSAndroid Build Coastguard Worker // if the files are different
60*c2e18aaaSAndroid Build Coastguard Worker #[serde(skip)]
61*c2e18aaaSAndroid Build Coastguard Worker pub cache_key: String,
62*c2e18aaaSAndroid Build Coastguard Worker }
63*c2e18aaaSAndroid Build Coastguard Worker
is_default<T: Default + PartialEq>(t: &T) -> bool64*c2e18aaaSAndroid Build Coastguard Worker fn is_default<T: Default + PartialEq>(t: &T) -> bool {
65*c2e18aaaSAndroid Build Coastguard Worker t == &T::default()
66*c2e18aaaSAndroid Build Coastguard Worker }
67*c2e18aaaSAndroid Build Coastguard Worker
68*c2e18aaaSAndroid Build Coastguard Worker impl FileMetadata {
from_path(file_path: &Path, cache: &Cache) -> Result<Self>69*c2e18aaaSAndroid Build Coastguard Worker pub fn from_path(file_path: &Path, cache: &Cache) -> Result<Self> {
70*c2e18aaaSAndroid Build Coastguard Worker let metadata = fs::symlink_metadata(file_path)?;
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker if metadata.is_dir() {
73*c2e18aaaSAndroid Build Coastguard Worker Ok(FileMetadata::from_dir())
74*c2e18aaaSAndroid Build Coastguard Worker } else if metadata.is_symlink() {
75*c2e18aaaSAndroid Build Coastguard Worker FileMetadata::from_symlink(file_path, &metadata)
76*c2e18aaaSAndroid Build Coastguard Worker } else {
77*c2e18aaaSAndroid Build Coastguard Worker Ok(FileMetadata::from_file(file_path, &metadata, cache)?)
78*c2e18aaaSAndroid Build Coastguard Worker }
79*c2e18aaaSAndroid Build Coastguard Worker }
80*c2e18aaaSAndroid Build Coastguard Worker
from_dir() -> Self81*c2e18aaaSAndroid Build Coastguard Worker pub fn from_dir() -> Self {
82*c2e18aaaSAndroid Build Coastguard Worker FileMetadata { file_type: FileType::Directory, ..Default::default() }
83*c2e18aaaSAndroid Build Coastguard Worker }
from_symlink(file_path: &Path, metadata: &fs::Metadata) -> Result<Self>84*c2e18aaaSAndroid Build Coastguard Worker pub fn from_symlink(file_path: &Path, metadata: &fs::Metadata) -> Result<Self> {
85*c2e18aaaSAndroid Build Coastguard Worker let link = fs::read_link(file_path)?;
86*c2e18aaaSAndroid Build Coastguard Worker let target_path_string =
87*c2e18aaaSAndroid Build Coastguard Worker link.into_os_string().into_string().expect("Expected valid file name");
88*c2e18aaaSAndroid Build Coastguard Worker let mut perms = 0;
89*c2e18aaaSAndroid Build Coastguard Worker
90*c2e18aaaSAndroid Build Coastguard Worker // Getting the permissions doesn't work on windows, so don't try and don't compare them.
91*c2e18aaaSAndroid Build Coastguard Worker if !cfg!(windows) {
92*c2e18aaaSAndroid Build Coastguard Worker perms = metadata.permissions().mode();
93*c2e18aaaSAndroid Build Coastguard Worker }
94*c2e18aaaSAndroid Build Coastguard Worker Ok(FileMetadata {
95*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::Symlink,
96*c2e18aaaSAndroid Build Coastguard Worker symlink: target_path_string,
97*c2e18aaaSAndroid Build Coastguard Worker permission_bits: perms,
98*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
99*c2e18aaaSAndroid Build Coastguard Worker })
100*c2e18aaaSAndroid Build Coastguard Worker }
from_file(file_path: &Path, metadata: &fs::Metadata, cache: &Cache) -> Result<Self>101*c2e18aaaSAndroid Build Coastguard Worker pub fn from_file(file_path: &Path, metadata: &fs::Metadata, cache: &Cache) -> Result<Self> {
102*c2e18aaaSAndroid Build Coastguard Worker // Getting the permissions doesn't work on windows, so don't try and don't compare them.
103*c2e18aaaSAndroid Build Coastguard Worker let mut perms = 0;
104*c2e18aaaSAndroid Build Coastguard Worker if !cfg!(windows) {
105*c2e18aaaSAndroid Build Coastguard Worker perms = metadata.permissions().mode();
106*c2e18aaaSAndroid Build Coastguard Worker }
107*c2e18aaaSAndroid Build Coastguard Worker
108*c2e18aaaSAndroid Build Coastguard Worker let (digest, cache_key) = get_or_compute_digest(file_path, metadata, cache)?;
109*c2e18aaaSAndroid Build Coastguard Worker Ok(FileMetadata {
110*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
111*c2e18aaaSAndroid Build Coastguard Worker digest,
112*c2e18aaaSAndroid Build Coastguard Worker cache_key,
113*c2e18aaaSAndroid Build Coastguard Worker permission_bits: perms,
114*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
115*c2e18aaaSAndroid Build Coastguard Worker })
116*c2e18aaaSAndroid Build Coastguard Worker }
117*c2e18aaaSAndroid Build Coastguard Worker }
118*c2e18aaaSAndroid Build Coastguard Worker
119*c2e18aaaSAndroid Build Coastguard Worker /// A description of the differences on the filesystems between the host
120*c2e18aaaSAndroid Build Coastguard Worker /// and device. Each file that is different will be a key in one of
121*c2e18aaaSAndroid Build Coastguard Worker /// three maps with the value indicating the difference.
122*c2e18aaaSAndroid Build Coastguard Worker #[derive(Debug, Default, PartialEq)]
123*c2e18aaaSAndroid Build Coastguard Worker pub struct Diffs {
124*c2e18aaaSAndroid Build Coastguard Worker /// Files on host, but not on device
125*c2e18aaaSAndroid Build Coastguard Worker pub device_needs: HashMap<PathBuf, FileMetadata>,
126*c2e18aaaSAndroid Build Coastguard Worker /// Files on device, but not host.
127*c2e18aaaSAndroid Build Coastguard Worker pub device_extra: HashMap<PathBuf, FileMetadata>,
128*c2e18aaaSAndroid Build Coastguard Worker /// Files that are different between host and device.
129*c2e18aaaSAndroid Build Coastguard Worker pub device_diffs: HashMap<PathBuf, FileMetadata>,
130*c2e18aaaSAndroid Build Coastguard Worker }
131*c2e18aaaSAndroid Build Coastguard Worker
132*c2e18aaaSAndroid Build Coastguard Worker /// Compute the files that need to be added, removed or updated on the device.
133*c2e18aaaSAndroid Build Coastguard Worker /// Each file should land in of the three categories (i.e. updated, not
134*c2e18aaaSAndroid Build Coastguard Worker /// removed and added);
135*c2e18aaaSAndroid Build Coastguard Worker /// TODO(rbraunstein): Fix allow(unused) by breaking out methods not
136*c2e18aaaSAndroid Build Coastguard Worker /// needed by adevice_fingerprint.
137*c2e18aaaSAndroid Build Coastguard Worker #[allow(unused)]
diff( host_files: &HashMap<PathBuf, FileMetadata>, device_files: &HashMap<PathBuf, FileMetadata>, diff_mode: DiffMode, ) -> Diffs138*c2e18aaaSAndroid Build Coastguard Worker pub fn diff(
139*c2e18aaaSAndroid Build Coastguard Worker host_files: &HashMap<PathBuf, FileMetadata>,
140*c2e18aaaSAndroid Build Coastguard Worker device_files: &HashMap<PathBuf, FileMetadata>,
141*c2e18aaaSAndroid Build Coastguard Worker diff_mode: DiffMode,
142*c2e18aaaSAndroid Build Coastguard Worker ) -> Diffs {
143*c2e18aaaSAndroid Build Coastguard Worker let mut diffs = Diffs {
144*c2e18aaaSAndroid Build Coastguard Worker device_needs: HashMap::new(),
145*c2e18aaaSAndroid Build Coastguard Worker device_extra: HashMap::new(),
146*c2e18aaaSAndroid Build Coastguard Worker device_diffs: HashMap::new(),
147*c2e18aaaSAndroid Build Coastguard Worker };
148*c2e18aaaSAndroid Build Coastguard Worker
149*c2e18aaaSAndroid Build Coastguard Worker // Insert diffs files that are on the host, but not on the device or
150*c2e18aaaSAndroid Build Coastguard Worker // file on the host that are different on the device.
151*c2e18aaaSAndroid Build Coastguard Worker for (file_name, host_metadata) in host_files {
152*c2e18aaaSAndroid Build Coastguard Worker match device_files.get(file_name) {
153*c2e18aaaSAndroid Build Coastguard Worker // File on host and device, but the metadata is different.
154*c2e18aaaSAndroid Build Coastguard Worker Some(device_metadata)
155*c2e18aaaSAndroid Build Coastguard Worker if is_metadata_diff(device_metadata, host_metadata, diff_mode) =>
156*c2e18aaaSAndroid Build Coastguard Worker {
157*c2e18aaaSAndroid Build Coastguard Worker diffs.device_diffs.insert(file_name.clone(), host_metadata.clone())
158*c2e18aaaSAndroid Build Coastguard Worker }
159*c2e18aaaSAndroid Build Coastguard Worker // If the device metadata == host metadata there is nothing to do.
160*c2e18aaaSAndroid Build Coastguard Worker Some(_) => None,
161*c2e18aaaSAndroid Build Coastguard Worker // Not on the device yet, insert it
162*c2e18aaaSAndroid Build Coastguard Worker None => diffs.device_needs.insert(file_name.clone(), host_metadata.clone()),
163*c2e18aaaSAndroid Build Coastguard Worker };
164*c2e18aaaSAndroid Build Coastguard Worker }
165*c2e18aaaSAndroid Build Coastguard Worker
166*c2e18aaaSAndroid Build Coastguard Worker // Files on the device, but not one the host.
167*c2e18aaaSAndroid Build Coastguard Worker for (file_name, metadata) in device_files {
168*c2e18aaaSAndroid Build Coastguard Worker if host_files.get(file_name).is_none() {
169*c2e18aaaSAndroid Build Coastguard Worker diffs.device_extra.insert(file_name.clone(), metadata.clone());
170*c2e18aaaSAndroid Build Coastguard Worker }
171*c2e18aaaSAndroid Build Coastguard Worker }
172*c2e18aaaSAndroid Build Coastguard Worker diffs
173*c2e18aaaSAndroid Build Coastguard Worker }
174*c2e18aaaSAndroid Build Coastguard Worker
175*c2e18aaaSAndroid Build Coastguard Worker /// Return true if left != right ignoring cachekey since that include last_modifed
176*c2e18aaaSAndroid Build Coastguard Worker /// When useing DiffMode::IgnorePermissions, clear the permission bits before doing the comparison
is_metadata_diff(left: &FileMetadata, right: &FileMetadata, diff_mode: DiffMode) -> bool177*c2e18aaaSAndroid Build Coastguard Worker pub fn is_metadata_diff(left: &FileMetadata, right: &FileMetadata, diff_mode: DiffMode) -> bool {
178*c2e18aaaSAndroid Build Coastguard Worker let mut cleared_left = left.clone();
179*c2e18aaaSAndroid Build Coastguard Worker let mut cleared_right = right.clone();
180*c2e18aaaSAndroid Build Coastguard Worker cleared_left.cache_key = "".to_string();
181*c2e18aaaSAndroid Build Coastguard Worker cleared_right.cache_key = "".to_string();
182*c2e18aaaSAndroid Build Coastguard Worker
183*c2e18aaaSAndroid Build Coastguard Worker if diff_mode == DiffMode::UsePermissions {
184*c2e18aaaSAndroid Build Coastguard Worker return cleared_left != cleared_right;
185*c2e18aaaSAndroid Build Coastguard Worker }
186*c2e18aaaSAndroid Build Coastguard Worker cleared_left.permission_bits = 0;
187*c2e18aaaSAndroid Build Coastguard Worker cleared_right.permission_bits = 0;
188*c2e18aaaSAndroid Build Coastguard Worker cleared_left != cleared_right
189*c2e18aaaSAndroid Build Coastguard Worker }
190*c2e18aaaSAndroid Build Coastguard Worker
191*c2e18aaaSAndroid Build Coastguard Worker /// Given a `partition_root`, traverse all files under the named |partitions|
192*c2e18aaaSAndroid Build Coastguard Worker /// at the root. Typically, ["system", "apex"] are partition_names.
193*c2e18aaaSAndroid Build Coastguard Worker /// The keys will be rooted at the `partition root`, ie. if system contains
194*c2e18aaaSAndroid Build Coastguard Worker /// a file named FILE and system is the `partition_root`, the key wil be
195*c2e18aaaSAndroid Build Coastguard Worker /// system/FILE.
196*c2e18aaaSAndroid Build Coastguard Worker /// Cache is used only to speed up computing digests; if the cache_key is the same
197*c2e18aaaSAndroid Build Coastguard Worker /// as an earlier fingerprint; then we reuse it rather than recomputing.
fingerprint_partitions( partition_root: &Path, partition_names: &[PathBuf], ) -> Result<HashMap<PathBuf, FileMetadata>>198*c2e18aaaSAndroid Build Coastguard Worker pub fn fingerprint_partitions(
199*c2e18aaaSAndroid Build Coastguard Worker partition_root: &Path,
200*c2e18aaaSAndroid Build Coastguard Worker partition_names: &[PathBuf],
201*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<HashMap<PathBuf, FileMetadata>> {
202*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::read().unwrap_or_default();
203*c2e18aaaSAndroid Build Coastguard Worker let filenames: Vec<PathBuf> = partition_names
204*c2e18aaaSAndroid Build Coastguard Worker .iter()
205*c2e18aaaSAndroid Build Coastguard Worker .flat_map(|p| WalkDir::new(partition_root.join(p)).follow_links(false))
206*c2e18aaaSAndroid Build Coastguard Worker .map(|result| result.expect("Walking directory").path().to_path_buf())
207*c2e18aaaSAndroid Build Coastguard Worker .collect();
208*c2e18aaaSAndroid Build Coastguard Worker
209*c2e18aaaSAndroid Build Coastguard Worker // Compute digest for each file.
210*c2e18aaaSAndroid Build Coastguard Worker let results = filenames
211*c2e18aaaSAndroid Build Coastguard Worker .into_par_iter()
212*c2e18aaaSAndroid Build Coastguard Worker // Walking the /data partition quickly leads to sockets, filter those out.
213*c2e18aaaSAndroid Build Coastguard Worker .filter(|file_path| !is_special_file(file_path))
214*c2e18aaaSAndroid Build Coastguard Worker .map(|file_path| {
215*c2e18aaaSAndroid Build Coastguard Worker (
216*c2e18aaaSAndroid Build Coastguard Worker file_path.strip_prefix(partition_root).unwrap().to_owned(),
217*c2e18aaaSAndroid Build Coastguard Worker FileMetadata::from_path(&file_path, &cache).unwrap(),
218*c2e18aaaSAndroid Build Coastguard Worker )
219*c2e18aaaSAndroid Build Coastguard Worker })
220*c2e18aaaSAndroid Build Coastguard Worker .collect();
221*c2e18aaaSAndroid Build Coastguard Worker cache.write(&results)?;
222*c2e18aaaSAndroid Build Coastguard Worker Ok(results)
223*c2e18aaaSAndroid Build Coastguard Worker }
224*c2e18aaaSAndroid Build Coastguard Worker
225*c2e18aaaSAndroid Build Coastguard Worker /// Return true for special files like sockets that would be incorrect
226*c2e18aaaSAndroid Build Coastguard Worker /// to digest and we that we can skip when comparing the device
227*c2e18aaaSAndroid Build Coastguard Worker /// to the build tree.
is_special_file(file_path: &Path) -> bool228*c2e18aaaSAndroid Build Coastguard Worker fn is_special_file(file_path: &Path) -> bool {
229*c2e18aaaSAndroid Build Coastguard Worker // `symlink_metadata` doesn't follow links. We don't want to follow symlinks here.
230*c2e18aaaSAndroid Build Coastguard Worker // The stat costs much less than the digest operations we are about to perform.
231*c2e18aaaSAndroid Build Coastguard Worker let file_metadata = fs::symlink_metadata(file_path).expect("no metadata");
232*c2e18aaaSAndroid Build Coastguard Worker file_metadata.file_type().is_block_device()
233*c2e18aaaSAndroid Build Coastguard Worker || file_metadata.file_type().is_char_device()
234*c2e18aaaSAndroid Build Coastguard Worker || file_metadata.file_type().is_fifo()
235*c2e18aaaSAndroid Build Coastguard Worker || file_metadata.file_type().is_socket()
236*c2e18aaaSAndroid Build Coastguard Worker }
237*c2e18aaaSAndroid Build Coastguard Worker
238*c2e18aaaSAndroid Build Coastguard Worker /// Compute the sha256 and return it as a lowercase hex string.
compute_digest(file_path: &Path) -> Result<String>239*c2e18aaaSAndroid Build Coastguard Worker fn compute_digest(file_path: &Path) -> Result<String> {
240*c2e18aaaSAndroid Build Coastguard Worker let input = fs::File::open(file_path)?;
241*c2e18aaaSAndroid Build Coastguard Worker let mut reader = io::BufReader::new(input);
242*c2e18aaaSAndroid Build Coastguard Worker let mut context = digest::Context::new(&SHA256);
243*c2e18aaaSAndroid Build Coastguard Worker let mut buffer = [0; 4096];
244*c2e18aaaSAndroid Build Coastguard Worker
245*c2e18aaaSAndroid Build Coastguard Worker loop {
246*c2e18aaaSAndroid Build Coastguard Worker let num_bytes_read = reader.read(&mut buffer)?;
247*c2e18aaaSAndroid Build Coastguard Worker if num_bytes_read == 0 {
248*c2e18aaaSAndroid Build Coastguard Worker break;
249*c2e18aaaSAndroid Build Coastguard Worker }
250*c2e18aaaSAndroid Build Coastguard Worker context.update(&buffer[..num_bytes_read]);
251*c2e18aaaSAndroid Build Coastguard Worker }
252*c2e18aaaSAndroid Build Coastguard Worker
253*c2e18aaaSAndroid Build Coastguard Worker Ok(encode(context.finish().as_ref()))
254*c2e18aaaSAndroid Build Coastguard Worker }
255*c2e18aaaSAndroid Build Coastguard Worker
256*c2e18aaaSAndroid Build Coastguard Worker /// Get digest from cache or compute digest and return a cache_key
get_or_compute_digest( file_path: &Path, metadata: &fs::Metadata, cache: &Cache, ) -> Result<(String, String)>257*c2e18aaaSAndroid Build Coastguard Worker fn get_or_compute_digest(
258*c2e18aaaSAndroid Build Coastguard Worker file_path: &Path,
259*c2e18aaaSAndroid Build Coastguard Worker metadata: &fs::Metadata,
260*c2e18aaaSAndroid Build Coastguard Worker cache: &Cache,
261*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<(String, String)> {
262*c2e18aaaSAndroid Build Coastguard Worker let cache_key = cache.cache_key(file_path, metadata)?;
263*c2e18aaaSAndroid Build Coastguard Worker let digest;
264*c2e18aaaSAndroid Build Coastguard Worker if let Some(cached_digest) = cache.get(&cache_key) {
265*c2e18aaaSAndroid Build Coastguard Worker digest = cached_digest.to_string();
266*c2e18aaaSAndroid Build Coastguard Worker } else {
267*c2e18aaaSAndroid Build Coastguard Worker digest = compute_digest(file_path)?;
268*c2e18aaaSAndroid Build Coastguard Worker }
269*c2e18aaaSAndroid Build Coastguard Worker Ok((digest, cache_key))
270*c2e18aaaSAndroid Build Coastguard Worker }
271*c2e18aaaSAndroid Build Coastguard Worker
272*c2e18aaaSAndroid Build Coastguard Worker // The cache is intended to be used to skip computing digests - it relies on the assumption that unchanged
273*c2e18aaaSAndroid Build Coastguard Worker // file stats imply unchanged content (and therefore, unchanged digest).
274*c2e18aaaSAndroid Build Coastguard Worker //
275*c2e18aaaSAndroid Build Coastguard Worker // The host stores the cache file in $OUT/ by default. Acloud/physical devices don't have $OUT so will attempt to store
276*c2e18aaaSAndroid Build Coastguard Worker // the cache in /cache/. Any file modification (add/delete/change) triggers a cache key recomputation for that specific file
277*c2e18aaaSAndroid Build Coastguard Worker // and the cache file will be updated. The cache should persist across reboots but may be deleted between flashes
278*c2e18aaaSAndroid Build Coastguard Worker #[derive(Default)]
279*c2e18aaaSAndroid Build Coastguard Worker pub struct Cache {
280*c2e18aaaSAndroid Build Coastguard Worker pub data: HashMap<String, String>,
281*c2e18aaaSAndroid Build Coastguard Worker }
282*c2e18aaaSAndroid Build Coastguard Worker impl Cache {
get(&self, key: &str) -> Option<&String>283*c2e18aaaSAndroid Build Coastguard Worker pub fn get(&self, key: &str) -> Option<&String> {
284*c2e18aaaSAndroid Build Coastguard Worker self.data.get(key)
285*c2e18aaaSAndroid Build Coastguard Worker }
286*c2e18aaaSAndroid Build Coastguard Worker
287*c2e18aaaSAndroid Build Coastguard Worker // Generate cache key from file metadata
cache_key(&self, file_path: &Path, metadata: &fs::Metadata) -> Result<String>288*c2e18aaaSAndroid Build Coastguard Worker pub fn cache_key(&self, file_path: &Path, metadata: &fs::Metadata) -> Result<String> {
289*c2e18aaaSAndroid Build Coastguard Worker Ok(format!(
290*c2e18aaaSAndroid Build Coastguard Worker "{}#{}#{}.{}#{}.{}",
291*c2e18aaaSAndroid Build Coastguard Worker file_path.display(),
292*c2e18aaaSAndroid Build Coastguard Worker metadata.len(),
293*c2e18aaaSAndroid Build Coastguard Worker metadata.mtime(),
294*c2e18aaaSAndroid Build Coastguard Worker metadata.mtime_nsec(),
295*c2e18aaaSAndroid Build Coastguard Worker metadata.ctime(),
296*c2e18aaaSAndroid Build Coastguard Worker metadata.ctime_nsec()
297*c2e18aaaSAndroid Build Coastguard Worker ))
298*c2e18aaaSAndroid Build Coastguard Worker }
299*c2e18aaaSAndroid Build Coastguard Worker
300*c2e18aaaSAndroid Build Coastguard Worker /// Reads cache from a file
read_from_file(file_path: &Path) -> Result<Self>301*c2e18aaaSAndroid Build Coastguard Worker pub fn read_from_file(file_path: &Path) -> Result<Self> {
302*c2e18aaaSAndroid Build Coastguard Worker let mut file = fs::File::open(file_path)?;
303*c2e18aaaSAndroid Build Coastguard Worker let mut contents = String::new();
304*c2e18aaaSAndroid Build Coastguard Worker file.read_to_string(&mut contents)?;
305*c2e18aaaSAndroid Build Coastguard Worker match serde_json::from_str(&contents) {
306*c2e18aaaSAndroid Build Coastguard Worker Ok(data) => Ok(Cache { data }),
307*c2e18aaaSAndroid Build Coastguard Worker Err(_error) => Err(_error.into()),
308*c2e18aaaSAndroid Build Coastguard Worker }
309*c2e18aaaSAndroid Build Coastguard Worker }
310*c2e18aaaSAndroid Build Coastguard Worker
read() -> Result<Self>311*c2e18aaaSAndroid Build Coastguard Worker pub fn read() -> Result<Self> {
312*c2e18aaaSAndroid Build Coastguard Worker let cache_file_path = Cache::default_cache_path();
313*c2e18aaaSAndroid Build Coastguard Worker Cache::read_from_file(&cache_file_path)
314*c2e18aaaSAndroid Build Coastguard Worker }
315*c2e18aaaSAndroid Build Coastguard Worker
316*c2e18aaaSAndroid Build Coastguard Worker /// Writes cache to a file
write_to_file( self, results: &HashMap<PathBuf, FileMetadata>, file_path: &Path, ) -> Result<()>317*c2e18aaaSAndroid Build Coastguard Worker pub fn write_to_file(
318*c2e18aaaSAndroid Build Coastguard Worker self,
319*c2e18aaaSAndroid Build Coastguard Worker results: &HashMap<PathBuf, FileMetadata>,
320*c2e18aaaSAndroid Build Coastguard Worker file_path: &Path,
321*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<()> {
322*c2e18aaaSAndroid Build Coastguard Worker let mut new_cache: HashMap<String, String> = HashMap::new();
323*c2e18aaaSAndroid Build Coastguard Worker for meta in results.values() {
324*c2e18aaaSAndroid Build Coastguard Worker if !meta.cache_key.is_empty() {
325*c2e18aaaSAndroid Build Coastguard Worker new_cache.insert(meta.cache_key.clone(), meta.digest.clone());
326*c2e18aaaSAndroid Build Coastguard Worker }
327*c2e18aaaSAndroid Build Coastguard Worker }
328*c2e18aaaSAndroid Build Coastguard Worker if new_cache == self.data {
329*c2e18aaaSAndroid Build Coastguard Worker // cache did not change - skip write.
330*c2e18aaaSAndroid Build Coastguard Worker return Ok(());
331*c2e18aaaSAndroid Build Coastguard Worker }
332*c2e18aaaSAndroid Build Coastguard Worker let cache_str = serde_json::to_string(&new_cache)?;
333*c2e18aaaSAndroid Build Coastguard Worker let mut file = fs::File::create(file_path)?;
334*c2e18aaaSAndroid Build Coastguard Worker file.write_all(cache_str.as_bytes())?;
335*c2e18aaaSAndroid Build Coastguard Worker Ok(())
336*c2e18aaaSAndroid Build Coastguard Worker }
337*c2e18aaaSAndroid Build Coastguard Worker
write(self, results: &HashMap<PathBuf, FileMetadata>) -> Result<()>338*c2e18aaaSAndroid Build Coastguard Worker pub fn write(self, results: &HashMap<PathBuf, FileMetadata>) -> Result<()> {
339*c2e18aaaSAndroid Build Coastguard Worker let cache_file_path = Cache::default_cache_path();
340*c2e18aaaSAndroid Build Coastguard Worker self.write_to_file(results, &cache_file_path)
341*c2e18aaaSAndroid Build Coastguard Worker }
342*c2e18aaaSAndroid Build Coastguard Worker
default_cache_path() -> PathBuf343*c2e18aaaSAndroid Build Coastguard Worker pub fn default_cache_path() -> PathBuf {
344*c2e18aaaSAndroid Build Coastguard Worker // Attempt to use $OUT, then /cache and finally fall back to /tmp
345*c2e18aaaSAndroid Build Coastguard Worker // /tmp is deleted on reboot on acloud devices
346*c2e18aaaSAndroid Build Coastguard Worker let mut cache_dir = std::env::var("OUT").unwrap_or_else(|_| "/cache".to_string());
347*c2e18aaaSAndroid Build Coastguard Worker if !Path::new(&cache_dir).is_dir() {
348*c2e18aaaSAndroid Build Coastguard Worker cache_dir = "/tmp".to_string();
349*c2e18aaaSAndroid Build Coastguard Worker }
350*c2e18aaaSAndroid Build Coastguard Worker
351*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from(cache_dir).join("adevice_digest_cache.json")
352*c2e18aaaSAndroid Build Coastguard Worker }
353*c2e18aaaSAndroid Build Coastguard Worker }
354*c2e18aaaSAndroid Build Coastguard Worker
355*c2e18aaaSAndroid Build Coastguard Worker #[cfg(test)]
356*c2e18aaaSAndroid Build Coastguard Worker mod tests {
357*c2e18aaaSAndroid Build Coastguard Worker use super::*;
358*c2e18aaaSAndroid Build Coastguard Worker use crate::fingerprint::DiffMode::UsePermissions;
359*c2e18aaaSAndroid Build Coastguard Worker use std::collections::BTreeSet;
360*c2e18aaaSAndroid Build Coastguard Worker use std::path::PathBuf;
361*c2e18aaaSAndroid Build Coastguard Worker use tempfile::TempDir;
362*c2e18aaaSAndroid Build Coastguard Worker
363*c2e18aaaSAndroid Build Coastguard Worker #[test]
empty_inputs()364*c2e18aaaSAndroid Build Coastguard Worker fn empty_inputs() {
365*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(diff(&HashMap::new(), &HashMap::new(), UsePermissions), Diffs::default());
366*c2e18aaaSAndroid Build Coastguard Worker }
367*c2e18aaaSAndroid Build Coastguard Worker
368*c2e18aaaSAndroid Build Coastguard Worker #[test]
same_inputs()369*c2e18aaaSAndroid Build Coastguard Worker fn same_inputs() {
370*c2e18aaaSAndroid Build Coastguard Worker let file_entry = HashMap::from([(
371*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
372*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
373*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
374*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
375*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
376*c2e18aaaSAndroid Build Coastguard Worker },
377*c2e18aaaSAndroid Build Coastguard Worker )]);
378*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(diff(&file_entry, &file_entry.clone(), UsePermissions), Diffs::default());
379*c2e18aaaSAndroid Build Coastguard Worker }
380*c2e18aaaSAndroid Build Coastguard Worker
381*c2e18aaaSAndroid Build Coastguard Worker #[test]
same_inputs_with_permissions()382*c2e18aaaSAndroid Build Coastguard Worker fn same_inputs_with_permissions() {
383*c2e18aaaSAndroid Build Coastguard Worker let file_entry = HashMap::from([(
384*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
385*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
386*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
387*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
388*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o644,
389*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
390*c2e18aaaSAndroid Build Coastguard Worker },
391*c2e18aaaSAndroid Build Coastguard Worker )]);
392*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(diff(&file_entry, &file_entry.clone(), UsePermissions), Diffs::default());
393*c2e18aaaSAndroid Build Coastguard Worker }
394*c2e18aaaSAndroid Build Coastguard Worker
395*c2e18aaaSAndroid Build Coastguard Worker #[test]
same_inputs_with_different_permissions_are_not_equal()396*c2e18aaaSAndroid Build Coastguard Worker fn same_inputs_with_different_permissions_are_not_equal() {
397*c2e18aaaSAndroid Build Coastguard Worker let orig = HashMap::from([(
398*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
399*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
400*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
401*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
402*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o644,
403*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
404*c2e18aaaSAndroid Build Coastguard Worker },
405*c2e18aaaSAndroid Build Coastguard Worker )]);
406*c2e18aaaSAndroid Build Coastguard Worker let mut copy = orig.clone();
407*c2e18aaaSAndroid Build Coastguard Worker copy.entry(PathBuf::from("a/b/foo.so")).and_modify(|v| v.permission_bits = 0);
408*c2e18aaaSAndroid Build Coastguard Worker
409*c2e18aaaSAndroid Build Coastguard Worker // Not equal
410*c2e18aaaSAndroid Build Coastguard Worker assert_ne!(diff(&orig, ©, UsePermissions), Diffs::default());
411*c2e18aaaSAndroid Build Coastguard Worker }
412*c2e18aaaSAndroid Build Coastguard Worker
413*c2e18aaaSAndroid Build Coastguard Worker #[test]
same_inputs_ignoring_permissions()414*c2e18aaaSAndroid Build Coastguard Worker fn same_inputs_ignoring_permissions() {
415*c2e18aaaSAndroid Build Coastguard Worker let orig = HashMap::from([(
416*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
417*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
418*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
419*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
420*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o644,
421*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
422*c2e18aaaSAndroid Build Coastguard Worker },
423*c2e18aaaSAndroid Build Coastguard Worker )]);
424*c2e18aaaSAndroid Build Coastguard Worker let mut copy = orig.clone();
425*c2e18aaaSAndroid Build Coastguard Worker copy.entry(PathBuf::from("a/b/foo.so")).and_modify(|v| v.permission_bits = 0);
426*c2e18aaaSAndroid Build Coastguard Worker
427*c2e18aaaSAndroid Build Coastguard Worker // Equal when we ignore the different permission bits.
428*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(diff(&orig, ©, DiffMode::IgnorePermissions), Diffs::default());
429*c2e18aaaSAndroid Build Coastguard Worker }
430*c2e18aaaSAndroid Build Coastguard Worker
431*c2e18aaaSAndroid Build Coastguard Worker #[test]
different_file_type()432*c2e18aaaSAndroid Build Coastguard Worker fn different_file_type() {
433*c2e18aaaSAndroid Build Coastguard Worker let host_map_with_filename_as_file = HashMap::from([(
434*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
435*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
436*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
437*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
438*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
439*c2e18aaaSAndroid Build Coastguard Worker },
440*c2e18aaaSAndroid Build Coastguard Worker )]);
441*c2e18aaaSAndroid Build Coastguard Worker
442*c2e18aaaSAndroid Build Coastguard Worker let device_map_with_filename_as_dir = HashMap::from([(
443*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("a/b/foo.so"),
444*c2e18aaaSAndroid Build Coastguard Worker FileMetadata { file_type: FileType::Directory, ..Default::default() },
445*c2e18aaaSAndroid Build Coastguard Worker )]);
446*c2e18aaaSAndroid Build Coastguard Worker
447*c2e18aaaSAndroid Build Coastguard Worker let diffs =
448*c2e18aaaSAndroid Build Coastguard Worker diff(&host_map_with_filename_as_file, &device_map_with_filename_as_dir, UsePermissions);
449*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
450*c2e18aaaSAndroid Build Coastguard Worker diffs.device_diffs.get(&PathBuf::from("a/b/foo.so")).expect("Missing file"),
451*c2e18aaaSAndroid Build Coastguard Worker // `diff` returns FileMetadata for host, but we really only care that the
452*c2e18aaaSAndroid Build Coastguard Worker // file name was found.
453*c2e18aaaSAndroid Build Coastguard Worker &FileMetadata {
454*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
455*c2e18aaaSAndroid Build Coastguard Worker digest: "deadbeef".to_string(),
456*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
457*c2e18aaaSAndroid Build Coastguard Worker },
458*c2e18aaaSAndroid Build Coastguard Worker );
459*c2e18aaaSAndroid Build Coastguard Worker }
460*c2e18aaaSAndroid Build Coastguard Worker
461*c2e18aaaSAndroid Build Coastguard Worker #[test]
diff_simple_trees()462*c2e18aaaSAndroid Build Coastguard Worker fn diff_simple_trees() {
463*c2e18aaaSAndroid Build Coastguard Worker let host_map = HashMap::from([
464*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("matching_file"), file_metadata("digest_matching_file")),
465*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("path/to/diff_file"), file_metadata("digest_file2")),
466*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("path/to/new_file"), file_metadata("digest_new_file")),
467*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("same_link"), link_metadata("matching_file")),
468*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("diff_link"), link_metadata("targetxx")),
469*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("new_link"), link_metadata("new_target")),
470*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("matching dir"), dir_metadata()),
471*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("new_dir"), dir_metadata()),
472*c2e18aaaSAndroid Build Coastguard Worker ]);
473*c2e18aaaSAndroid Build Coastguard Worker
474*c2e18aaaSAndroid Build Coastguard Worker let device_map = HashMap::from([
475*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("matching_file"), file_metadata("digest_matching_file")),
476*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("path/to/diff_file"), file_metadata("digest_file2_DIFF")),
477*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("path/to/deleted_file"), file_metadata("digest_deleted_file")),
478*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("same_link"), link_metadata("matching_file")),
479*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("diff_link"), link_metadata("targetxx_DIFF")),
480*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("deleted_link"), link_metadata("new_target")),
481*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("matching dir"), dir_metadata()),
482*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("deleted_dir"), dir_metadata()),
483*c2e18aaaSAndroid Build Coastguard Worker ]);
484*c2e18aaaSAndroid Build Coastguard Worker
485*c2e18aaaSAndroid Build Coastguard Worker let diffs = diff(&host_map, &device_map, UsePermissions);
486*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
487*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from_iter(diffs.device_diffs.keys()),
488*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from([&PathBuf::from("diff_link"), &PathBuf::from("path/to/diff_file")])
489*c2e18aaaSAndroid Build Coastguard Worker );
490*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
491*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from_iter(diffs.device_needs.keys()),
492*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from([
493*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("path/to/new_file"),
494*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("new_link"),
495*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("new_dir")
496*c2e18aaaSAndroid Build Coastguard Worker ])
497*c2e18aaaSAndroid Build Coastguard Worker );
498*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
499*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from_iter(diffs.device_extra.keys()),
500*c2e18aaaSAndroid Build Coastguard Worker BTreeSet::from([
501*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("path/to/deleted_file"),
502*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("deleted_link"),
503*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("deleted_dir")
504*c2e18aaaSAndroid Build Coastguard Worker ])
505*c2e18aaaSAndroid Build Coastguard Worker );
506*c2e18aaaSAndroid Build Coastguard Worker }
507*c2e18aaaSAndroid Build Coastguard Worker
508*c2e18aaaSAndroid Build Coastguard Worker #[test]
compute_digest_empty_file()509*c2e18aaaSAndroid Build Coastguard Worker fn compute_digest_empty_file() {
510*c2e18aaaSAndroid Build Coastguard Worker let tmpdir = TempDir::new().unwrap();
511*c2e18aaaSAndroid Build Coastguard Worker let file_path = tmpdir.path().join("empty_file");
512*c2e18aaaSAndroid Build Coastguard Worker fs::write(&file_path, "").unwrap();
513*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
514*c2e18aaaSAndroid Build Coastguard Worker "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
515*c2e18aaaSAndroid Build Coastguard Worker compute_digest(&file_path).unwrap()
516*c2e18aaaSAndroid Build Coastguard Worker );
517*c2e18aaaSAndroid Build Coastguard Worker }
518*c2e18aaaSAndroid Build Coastguard Worker
519*c2e18aaaSAndroid Build Coastguard Worker #[test]
compute_digest_small_file()520*c2e18aaaSAndroid Build Coastguard Worker fn compute_digest_small_file() {
521*c2e18aaaSAndroid Build Coastguard Worker let tmpdir = TempDir::new().unwrap();
522*c2e18aaaSAndroid Build Coastguard Worker let file_path = tmpdir.path().join("small_file");
523*c2e18aaaSAndroid Build Coastguard Worker fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
524*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
525*c2e18aaaSAndroid Build Coastguard Worker "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e".to_string(),
526*c2e18aaaSAndroid Build Coastguard Worker compute_digest(&file_path).unwrap()
527*c2e18aaaSAndroid Build Coastguard Worker );
528*c2e18aaaSAndroid Build Coastguard Worker }
529*c2e18aaaSAndroid Build Coastguard Worker
530*c2e18aaaSAndroid Build Coastguard Worker #[test]
get_or_compute_digest_small_file()531*c2e18aaaSAndroid Build Coastguard Worker fn get_or_compute_digest_small_file() {
532*c2e18aaaSAndroid Build Coastguard Worker let tmpdir = TempDir::new().unwrap();
533*c2e18aaaSAndroid Build Coastguard Worker let file_path = tmpdir.path().join("small_file");
534*c2e18aaaSAndroid Build Coastguard Worker fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
535*c2e18aaaSAndroid Build Coastguard Worker
536*c2e18aaaSAndroid Build Coastguard Worker let metadata = fs::metadata(&file_path).expect("file metadata");
537*c2e18aaaSAndroid Build Coastguard Worker let mut cache = Cache::default();
538*c2e18aaaSAndroid Build Coastguard Worker let (digest, cache_key) = get_or_compute_digest(&file_path, &metadata, &cache).unwrap();
539*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
540*c2e18aaaSAndroid Build Coastguard Worker "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e".to_string(),
541*c2e18aaaSAndroid Build Coastguard Worker digest
542*c2e18aaaSAndroid Build Coastguard Worker );
543*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
544*c2e18aaaSAndroid Build Coastguard Worker cache_key,
545*c2e18aaaSAndroid Build Coastguard Worker format!(
546*c2e18aaaSAndroid Build Coastguard Worker "{}#{}#{}.{}#{}.{}",
547*c2e18aaaSAndroid Build Coastguard Worker file_path.display(),
548*c2e18aaaSAndroid Build Coastguard Worker metadata.len(),
549*c2e18aaaSAndroid Build Coastguard Worker metadata.mtime(),
550*c2e18aaaSAndroid Build Coastguard Worker metadata.mtime_nsec(),
551*c2e18aaaSAndroid Build Coastguard Worker metadata.ctime(),
552*c2e18aaaSAndroid Build Coastguard Worker metadata.ctime_nsec()
553*c2e18aaaSAndroid Build Coastguard Worker )
554*c2e18aaaSAndroid Build Coastguard Worker );
555*c2e18aaaSAndroid Build Coastguard Worker
556*c2e18aaaSAndroid Build Coastguard Worker // if cache entry exists; than that is used instead of recomputing
557*c2e18aaaSAndroid Build Coastguard Worker cache.data.insert(cache_key, "test-saved-cache-digest".to_string());
558*c2e18aaaSAndroid Build Coastguard Worker let (digest, _) = get_or_compute_digest(&file_path, &metadata, &cache).unwrap();
559*c2e18aaaSAndroid Build Coastguard Worker assert_eq!("test-saved-cache-digest".to_string(), digest);
560*c2e18aaaSAndroid Build Coastguard Worker }
561*c2e18aaaSAndroid Build Coastguard Worker
562*c2e18aaaSAndroid Build Coastguard Worker // Generate some files near the buffer size to check for off-by-one errors
563*c2e18aaaSAndroid Build Coastguard Worker // and compute the digest and store here.
564*c2e18aaaSAndroid Build Coastguard Worker // We can't check these into testdata and read from testdata unless we serialize
565*c2e18aaaSAndroid Build Coastguard Worker // all the tests. Some tests to `cd` to create relative symlinks and that affects
566*c2e18aaaSAndroid Build Coastguard Worker // any tests that want to read from testdata.
567*c2e18aaaSAndroid Build Coastguard Worker #[test]
verify_edge_case_digests()568*c2e18aaaSAndroid Build Coastguard Worker fn verify_edge_case_digests() {
569*c2e18aaaSAndroid Build Coastguard Worker let tmpdir = TempDir::new().unwrap();
570*c2e18aaaSAndroid Build Coastguard Worker // We could use a RNG with a seed, but lets just create simple files of bytes.
571*c2e18aaaSAndroid Build Coastguard Worker let raw_bytes: &[u8; 10] = &[0, 1, 17, 200, 11, 8, 0, 32, 9, 10];
572*c2e18aaaSAndroid Build Coastguard Worker let mut boring_buff = Vec::new();
573*c2e18aaaSAndroid Build Coastguard Worker for _ in 1..1000 {
574*c2e18aaaSAndroid Build Coastguard Worker boring_buff.extend_from_slice(raw_bytes);
575*c2e18aaaSAndroid Build Coastguard Worker }
576*c2e18aaaSAndroid Build Coastguard Worker
577*c2e18aaaSAndroid Build Coastguard Worker for (num_bytes, digest) in
578*c2e18aaaSAndroid Build Coastguard Worker &[(4095, "a0e88b2743"), (4096, "b2e324aac3"), (4097, "70fcbe6a8d")]
579*c2e18aaaSAndroid Build Coastguard Worker {
580*c2e18aaaSAndroid Build Coastguard Worker let file_path = tmpdir.path().join(num_bytes.to_string());
581*c2e18aaaSAndroid Build Coastguard Worker fs::write(&file_path, &boring_buff[0..*num_bytes]).unwrap();
582*c2e18aaaSAndroid Build Coastguard Worker assert!(
583*c2e18aaaSAndroid Build Coastguard Worker compute_digest(&file_path).unwrap().starts_with(digest),
584*c2e18aaaSAndroid Build Coastguard Worker "Expected file {:?} to have a digest starting with {:?}",
585*c2e18aaaSAndroid Build Coastguard Worker file_path,
586*c2e18aaaSAndroid Build Coastguard Worker digest
587*c2e18aaaSAndroid Build Coastguard Worker );
588*c2e18aaaSAndroid Build Coastguard Worker }
589*c2e18aaaSAndroid Build Coastguard Worker }
590*c2e18aaaSAndroid Build Coastguard Worker
591*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_file_for_file()592*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_file_for_file() {
593*c2e18aaaSAndroid Build Coastguard Worker let partition_root = TempDir::new().unwrap();
594*c2e18aaaSAndroid Build Coastguard Worker let file_path = partition_root.path().join("small_file");
595*c2e18aaaSAndroid Build Coastguard Worker fs::write(&file_path, "This is a test\nof a small file.\n").unwrap();
596*c2e18aaaSAndroid Build Coastguard Worker
597*c2e18aaaSAndroid Build Coastguard Worker // NOTE: files are 0x644 on the host tests and 0x655 on device tests
598*c2e18aaaSAndroid Build Coastguard Worker // for me and CI so changing the file to always be 655 during the test.
599*c2e18aaaSAndroid Build Coastguard Worker let mut perms = fs::metadata(&file_path).expect("Getting permissions").permissions();
600*c2e18aaaSAndroid Build Coastguard Worker perms.set_mode(0o100655);
601*c2e18aaaSAndroid Build Coastguard Worker assert!(fs::set_permissions(&file_path, perms).is_ok());
602*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
603*c2e18aaaSAndroid Build Coastguard Worker let entry = FileMetadata::from_path(&file_path, &cache).unwrap();
604*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
605*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
606*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::File,
607*c2e18aaaSAndroid Build Coastguard Worker digest: "a519d054afdf2abfbdd90a738d248f606685d6c187e96390bde22e958240449e"
608*c2e18aaaSAndroid Build Coastguard Worker .to_string(),
609*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o100655,
610*c2e18aaaSAndroid Build Coastguard Worker cache_key: entry.cache_key.clone(),
611*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
612*c2e18aaaSAndroid Build Coastguard Worker },
613*c2e18aaaSAndroid Build Coastguard Worker entry
614*c2e18aaaSAndroid Build Coastguard Worker )
615*c2e18aaaSAndroid Build Coastguard Worker }
616*c2e18aaaSAndroid Build Coastguard Worker
617*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_file_for_relative_symlink()618*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_file_for_relative_symlink() {
619*c2e18aaaSAndroid Build Coastguard Worker let partition_root = TempDir::new().unwrap();
620*c2e18aaaSAndroid Build Coastguard Worker let file_path = partition_root.path().join("small_file");
621*c2e18aaaSAndroid Build Coastguard Worker fs::write(file_path, "This is a test\nof a small file.\n").unwrap();
622*c2e18aaaSAndroid Build Coastguard Worker
623*c2e18aaaSAndroid Build Coastguard Worker let link = create_symlink(
624*c2e18aaaSAndroid Build Coastguard Worker &PathBuf::from("small_file"),
625*c2e18aaaSAndroid Build Coastguard Worker "link_to_small_file",
626*c2e18aaaSAndroid Build Coastguard Worker partition_root.path(),
627*c2e18aaaSAndroid Build Coastguard Worker );
628*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
629*c2e18aaaSAndroid Build Coastguard Worker let entry = FileMetadata::from_path(&link, &cache).unwrap();
630*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
631*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
632*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::Symlink,
633*c2e18aaaSAndroid Build Coastguard Worker symlink: "small_file".to_string(),
634*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o120777,
635*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
636*c2e18aaaSAndroid Build Coastguard Worker },
637*c2e18aaaSAndroid Build Coastguard Worker entry
638*c2e18aaaSAndroid Build Coastguard Worker )
639*c2e18aaaSAndroid Build Coastguard Worker }
640*c2e18aaaSAndroid Build Coastguard Worker
641*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_file_for_absolute_symlink()642*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_file_for_absolute_symlink() {
643*c2e18aaaSAndroid Build Coastguard Worker let partition_root = TempDir::new().unwrap();
644*c2e18aaaSAndroid Build Coastguard Worker let link = create_symlink(&PathBuf::from("/tmp"), "link_to_tmp", partition_root.path());
645*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
646*c2e18aaaSAndroid Build Coastguard Worker let entry = FileMetadata::from_path(&link, &cache).unwrap();
647*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
648*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
649*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::Symlink,
650*c2e18aaaSAndroid Build Coastguard Worker symlink: "/tmp".to_string(),
651*c2e18aaaSAndroid Build Coastguard Worker permission_bits: 0o120777,
652*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
653*c2e18aaaSAndroid Build Coastguard Worker },
654*c2e18aaaSAndroid Build Coastguard Worker entry
655*c2e18aaaSAndroid Build Coastguard Worker )
656*c2e18aaaSAndroid Build Coastguard Worker }
657*c2e18aaaSAndroid Build Coastguard Worker
658*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_file_for_directory()659*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_file_for_directory() {
660*c2e18aaaSAndroid Build Coastguard Worker let partition_root = TempDir::new().unwrap();
661*c2e18aaaSAndroid Build Coastguard Worker let newdir_path = partition_root.path().join("some_dir");
662*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir(&newdir_path).expect("Should have create 'some_dir' in temp dir");
663*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
664*c2e18aaaSAndroid Build Coastguard Worker let entry = FileMetadata::from_path(&newdir_path, &cache).unwrap();
665*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(FileMetadata { file_type: FileType::Directory, ..Default::default() }, entry)
666*c2e18aaaSAndroid Build Coastguard Worker }
667*c2e18aaaSAndroid Build Coastguard Worker
668*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_file_on_bad_path_reports_err()669*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_file_on_bad_path_reports_err() {
670*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
671*c2e18aaaSAndroid Build Coastguard Worker if FileMetadata::from_path(Path::new("testdata/not_exist"), &cache).is_ok() {
672*c2e18aaaSAndroid Build Coastguard Worker panic!("Should have failed on invalid path")
673*c2e18aaaSAndroid Build Coastguard Worker }
674*c2e18aaaSAndroid Build Coastguard Worker }
675*c2e18aaaSAndroid Build Coastguard Worker
676*c2e18aaaSAndroid Build Coastguard Worker /// /tmp/.tmpxO0pRC/system
677*c2e18aaaSAndroid Build Coastguard Worker /// % tree
678*c2e18aaaSAndroid Build Coastguard Worker /// .
679*c2e18aaaSAndroid Build Coastguard Worker /// ├── cycle1 -> cycle2
680*c2e18aaaSAndroid Build Coastguard Worker /// ├── cycle2 -> cycle1
681*c2e18aaaSAndroid Build Coastguard Worker /// ├── danglers
682*c2e18aaaSAndroid Build Coastguard Worker /// │ ├── d1 -> nowhere
683*c2e18aaaSAndroid Build Coastguard Worker /// │ └── d2 -> /not/existing
684*c2e18aaaSAndroid Build Coastguard Worker /// ├── dir1
685*c2e18aaaSAndroid Build Coastguard Worker /// │ ├── dir2
686*c2e18aaaSAndroid Build Coastguard Worker /// │ │ ├── nested
687*c2e18aaaSAndroid Build Coastguard Worker /// │ │ └── nested2
688*c2e18aaaSAndroid Build Coastguard Worker /// │ ├── dir4
689*c2e18aaaSAndroid Build Coastguard Worker /// │ └── f1.txt
690*c2e18aaaSAndroid Build Coastguard Worker /// ├── dir3
691*c2e18aaaSAndroid Build Coastguard Worker /// │ ├── to_tmp -> /tmp
692*c2e18aaaSAndroid Build Coastguard Worker /// │ └── to_tmp2 -> /system/cycle1
693*c2e18aaaSAndroid Build Coastguard Worker /// ├── file1.so
694*c2e18aaaSAndroid Build Coastguard Worker /// ├── file2.so
695*c2e18aaaSAndroid Build Coastguard Worker /// ├── link1 -> file1.so
696*c2e18aaaSAndroid Build Coastguard Worker /// └── link2 -> link1
697*c2e18aaaSAndroid Build Coastguard Worker #[test]
698*c2e18aaaSAndroid Build Coastguard Worker
fingerprint_simple_partition()699*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_simple_partition() {
700*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
701*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Change make_partition to look more like `expected` variable below.
702*c2e18aaaSAndroid Build Coastguard Worker // i.e. use file_type rather than pass files, dirs, and symlinks in different arrays.
703*c2e18aaaSAndroid Build Coastguard Worker // Or use a struct with named fields as the args.
704*c2e18aaaSAndroid Build Coastguard Worker make_partition(
705*c2e18aaaSAndroid Build Coastguard Worker tmp_root.path(),
706*c2e18aaaSAndroid Build Coastguard Worker "system",
707*c2e18aaaSAndroid Build Coastguard Worker &[
708*c2e18aaaSAndroid Build Coastguard Worker ("file1.so", "some text"),
709*c2e18aaaSAndroid Build Coastguard Worker ("file2.so", "more text"),
710*c2e18aaaSAndroid Build Coastguard Worker ("dir1/f1.txt", ""),
711*c2e18aaaSAndroid Build Coastguard Worker ("dir1/dir2/nested", "some more text"),
712*c2e18aaaSAndroid Build Coastguard Worker ("dir1/dir2/nested2", "some more text"),
713*c2e18aaaSAndroid Build Coastguard Worker ],
714*c2e18aaaSAndroid Build Coastguard Worker // Empty directories/
715*c2e18aaaSAndroid Build Coastguard Worker &["dir3", "dir1/dir4", "danglers"],
716*c2e18aaaSAndroid Build Coastguard Worker // Symlinks:
717*c2e18aaaSAndroid Build Coastguard Worker // Linkname, target.
718*c2e18aaaSAndroid Build Coastguard Worker &[
719*c2e18aaaSAndroid Build Coastguard Worker ("link1", "file1.so"),
720*c2e18aaaSAndroid Build Coastguard Worker ("link2", "link1"),
721*c2e18aaaSAndroid Build Coastguard Worker ("cycle1", "cycle2"),
722*c2e18aaaSAndroid Build Coastguard Worker ("cycle2", "cycle1"),
723*c2e18aaaSAndroid Build Coastguard Worker ("dir3/to_tmp", "/tmp"),
724*c2e18aaaSAndroid Build Coastguard Worker ("dir3/to_tmp2", "/system/cycle1"),
725*c2e18aaaSAndroid Build Coastguard Worker ("danglers/d1", "nowhere"),
726*c2e18aaaSAndroid Build Coastguard Worker ("danglers/d2", "/not/existing"),
727*c2e18aaaSAndroid Build Coastguard Worker ],
728*c2e18aaaSAndroid Build Coastguard Worker );
729*c2e18aaaSAndroid Build Coastguard Worker let result = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
730*c2e18aaaSAndroid Build Coastguard Worker println!("RESULTS\n");
731*c2e18aaaSAndroid Build Coastguard Worker for x in &result {
732*c2e18aaaSAndroid Build Coastguard Worker println!("{:?}", x);
733*c2e18aaaSAndroid Build Coastguard Worker }
734*c2e18aaaSAndroid Build Coastguard Worker let expected = &[
735*c2e18aaaSAndroid Build Coastguard Worker ("system/file1.so", FileType::File, "b94f"),
736*c2e18aaaSAndroid Build Coastguard Worker ("system/file2.so", FileType::File, "c0dc"),
737*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1/f1.txt", FileType::File, "e3b0c"),
738*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1/dir2/nested", FileType::File, "bde27b"),
739*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1/dir2/nested2", FileType::File, "bde27b"),
740*c2e18aaaSAndroid Build Coastguard Worker ("system/dir3", FileType::Directory, ""),
741*c2e18aaaSAndroid Build Coastguard Worker ("system/danglers", FileType::Directory, ""),
742*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1", FileType::Directory, ""),
743*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1/dir2", FileType::Directory, ""),
744*c2e18aaaSAndroid Build Coastguard Worker ("system/dir1/dir4", FileType::Directory, ""),
745*c2e18aaaSAndroid Build Coastguard Worker ("system/link1", FileType::Symlink, "file1.so"),
746*c2e18aaaSAndroid Build Coastguard Worker ("system/link2", FileType::Symlink, "link1"),
747*c2e18aaaSAndroid Build Coastguard Worker ("system/cycle1", FileType::Symlink, "cycle2"),
748*c2e18aaaSAndroid Build Coastguard Worker ("system/cycle2", FileType::Symlink, "cycle1"),
749*c2e18aaaSAndroid Build Coastguard Worker ("system/dir3/to_tmp", FileType::Symlink, "/tmp"),
750*c2e18aaaSAndroid Build Coastguard Worker ("system/dir3/to_tmp2", FileType::Symlink, "/system/cycle1"),
751*c2e18aaaSAndroid Build Coastguard Worker ("system/danglers/d1", FileType::Symlink, "nowhere"),
752*c2e18aaaSAndroid Build Coastguard Worker ("system/danglers/d2", FileType::Symlink, "/not/existing"),
753*c2e18aaaSAndroid Build Coastguard Worker ("system", FileType::Directory, ""),
754*c2e18aaaSAndroid Build Coastguard Worker ];
755*c2e18aaaSAndroid Build Coastguard Worker
756*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
757*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
758*c2e18aaaSAndroid Build Coastguard Worker result.len(),
759*c2e18aaaSAndroid Build Coastguard Worker "expected: {}, result {}",
760*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
761*c2e18aaaSAndroid Build Coastguard Worker result.len()
762*c2e18aaaSAndroid Build Coastguard Worker );
763*c2e18aaaSAndroid Build Coastguard Worker
764*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_type, data) in expected {
765*c2e18aaaSAndroid Build Coastguard Worker match file_type {
766*c2e18aaaSAndroid Build Coastguard Worker FileType::File => assert!(
767*c2e18aaaSAndroid Build Coastguard Worker matching_file_fingerprint(file_name, data, &result),
768*c2e18aaaSAndroid Build Coastguard Worker "mismatch on {:?} {:?}",
769*c2e18aaaSAndroid Build Coastguard Worker file_name,
770*c2e18aaaSAndroid Build Coastguard Worker data
771*c2e18aaaSAndroid Build Coastguard Worker ),
772*c2e18aaaSAndroid Build Coastguard Worker FileType::Directory => assert!(result
773*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
774*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|d| d.file_type == FileType::Directory)),
775*c2e18aaaSAndroid Build Coastguard Worker FileType::Symlink => assert!(result
776*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
777*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
778*c2e18aaaSAndroid Build Coastguard Worker };
779*c2e18aaaSAndroid Build Coastguard Worker }
780*c2e18aaaSAndroid Build Coastguard Worker }
781*c2e18aaaSAndroid Build Coastguard Worker
782*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_multiple_partitions()783*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_multiple_partitions() {
784*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
785*c2e18aaaSAndroid Build Coastguard Worker // Use same file name, with and without same contents in two different partitions.
786*c2e18aaaSAndroid Build Coastguard Worker make_partition(
787*c2e18aaaSAndroid Build Coastguard Worker tmp_root.path(),
788*c2e18aaaSAndroid Build Coastguard Worker "system",
789*c2e18aaaSAndroid Build Coastguard Worker &[("file1.so", "some text"), ("file2", "system part")],
790*c2e18aaaSAndroid Build Coastguard Worker // Empty directories/
791*c2e18aaaSAndroid Build Coastguard Worker &[],
792*c2e18aaaSAndroid Build Coastguard Worker // Symlinks
793*c2e18aaaSAndroid Build Coastguard Worker &[],
794*c2e18aaaSAndroid Build Coastguard Worker );
795*c2e18aaaSAndroid Build Coastguard Worker make_partition(
796*c2e18aaaSAndroid Build Coastguard Worker tmp_root.path(),
797*c2e18aaaSAndroid Build Coastguard Worker "data",
798*c2e18aaaSAndroid Build Coastguard Worker &[("file1.so", "some text"), ("file2", "data part")],
799*c2e18aaaSAndroid Build Coastguard Worker // Empty directories/
800*c2e18aaaSAndroid Build Coastguard Worker &[],
801*c2e18aaaSAndroid Build Coastguard Worker // Symlinks
802*c2e18aaaSAndroid Build Coastguard Worker &[],
803*c2e18aaaSAndroid Build Coastguard Worker );
804*c2e18aaaSAndroid Build Coastguard Worker
805*c2e18aaaSAndroid Build Coastguard Worker let result = fingerprint_partitions(
806*c2e18aaaSAndroid Build Coastguard Worker tmp_root.path(),
807*c2e18aaaSAndroid Build Coastguard Worker &[PathBuf::from("system"), PathBuf::from("data")],
808*c2e18aaaSAndroid Build Coastguard Worker )
809*c2e18aaaSAndroid Build Coastguard Worker .unwrap();
810*c2e18aaaSAndroid Build Coastguard Worker println!("RESULTS\n");
811*c2e18aaaSAndroid Build Coastguard Worker for x in &result {
812*c2e18aaaSAndroid Build Coastguard Worker println!("{:?}", x);
813*c2e18aaaSAndroid Build Coastguard Worker }
814*c2e18aaaSAndroid Build Coastguard Worker let expected = &[
815*c2e18aaaSAndroid Build Coastguard Worker ("system/file1.so", FileType::File, "b94f"),
816*c2e18aaaSAndroid Build Coastguard Worker ("data/file1.so", FileType::File, "b94f"),
817*c2e18aaaSAndroid Build Coastguard Worker ("system/file2", FileType::File, "ae7c6c"),
818*c2e18aaaSAndroid Build Coastguard Worker ("data/file2", FileType::File, "4ae46d"),
819*c2e18aaaSAndroid Build Coastguard Worker ("data", FileType::Directory, ""),
820*c2e18aaaSAndroid Build Coastguard Worker ("system", FileType::Directory, ""),
821*c2e18aaaSAndroid Build Coastguard Worker ];
822*c2e18aaaSAndroid Build Coastguard Worker
823*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
824*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
825*c2e18aaaSAndroid Build Coastguard Worker result.len(),
826*c2e18aaaSAndroid Build Coastguard Worker "expected: {}, result {}",
827*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
828*c2e18aaaSAndroid Build Coastguard Worker result.len()
829*c2e18aaaSAndroid Build Coastguard Worker );
830*c2e18aaaSAndroid Build Coastguard Worker
831*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_type, data) in expected {
832*c2e18aaaSAndroid Build Coastguard Worker match file_type {
833*c2e18aaaSAndroid Build Coastguard Worker FileType::File => assert!(
834*c2e18aaaSAndroid Build Coastguard Worker matching_file_fingerprint(file_name, data, &result),
835*c2e18aaaSAndroid Build Coastguard Worker "mismatch on {:?} {:?}",
836*c2e18aaaSAndroid Build Coastguard Worker file_name,
837*c2e18aaaSAndroid Build Coastguard Worker data
838*c2e18aaaSAndroid Build Coastguard Worker ),
839*c2e18aaaSAndroid Build Coastguard Worker FileType::Directory => assert!(result
840*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
841*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|d| d.file_type == FileType::Directory)),
842*c2e18aaaSAndroid Build Coastguard Worker _ => (),
843*c2e18aaaSAndroid Build Coastguard Worker };
844*c2e18aaaSAndroid Build Coastguard Worker }
845*c2e18aaaSAndroid Build Coastguard Worker }
846*c2e18aaaSAndroid Build Coastguard Worker
847*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_partition_with_interesting_file_names()848*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_partition_with_interesting_file_names() {
849*c2e18aaaSAndroid Build Coastguard Worker let tmp_dir = TempDir::new().unwrap();
850*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = tmp_dir.path().to_owned();
851*c2e18aaaSAndroid Build Coastguard Worker println!("DEBUG: {tmp_root:?}");
852*c2e18aaaSAndroid Build Coastguard Worker make_partition(
853*c2e18aaaSAndroid Build Coastguard Worker &tmp_root,
854*c2e18aaaSAndroid Build Coastguard Worker "funky",
855*c2e18aaaSAndroid Build Coastguard Worker &[("안녕하세요", "hello\n")],
856*c2e18aaaSAndroid Build Coastguard Worker // Empty directories/
857*c2e18aaaSAndroid Build Coastguard Worker &[
858*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): This invalid file name (embedded newlind and Nil) breaks tests.
859*c2e18aaaSAndroid Build Coastguard Worker // Need to fix the code to remove `unwraps` and propagate errors.
860*c2e18aaaSAndroid Build Coastguard Worker // "d\ni\0r3"
861*c2e18aaaSAndroid Build Coastguard Worker ],
862*c2e18aaaSAndroid Build Coastguard Worker // symlinks
863*c2e18aaaSAndroid Build Coastguard Worker // linkname, target
864*c2e18aaaSAndroid Build Coastguard Worker &[("שלום", "안녕하세요")],
865*c2e18aaaSAndroid Build Coastguard Worker );
866*c2e18aaaSAndroid Build Coastguard Worker let result = fingerprint_partitions(&tmp_root, &[PathBuf::from("funky")]).unwrap();
867*c2e18aaaSAndroid Build Coastguard Worker println!("RESULTS\n");
868*c2e18aaaSAndroid Build Coastguard Worker for x in &result {
869*c2e18aaaSAndroid Build Coastguard Worker println!("{:?}", x);
870*c2e18aaaSAndroid Build Coastguard Worker }
871*c2e18aaaSAndroid Build Coastguard Worker let expected = &[
872*c2e18aaaSAndroid Build Coastguard Worker ("funky/안녕하세요", FileType::File, "5891b"),
873*c2e18aaaSAndroid Build Coastguard Worker // ("funky/d\ni\0r3", FileType::Directory, ""),
874*c2e18aaaSAndroid Build Coastguard Worker ("funky/שלום", FileType::Symlink, "안녕하세요"),
875*c2e18aaaSAndroid Build Coastguard Worker ("funky", FileType::Directory, ""),
876*c2e18aaaSAndroid Build Coastguard Worker ];
877*c2e18aaaSAndroid Build Coastguard Worker
878*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
879*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
880*c2e18aaaSAndroid Build Coastguard Worker result.len(),
881*c2e18aaaSAndroid Build Coastguard Worker "expected: {}, result {}",
882*c2e18aaaSAndroid Build Coastguard Worker expected.len(),
883*c2e18aaaSAndroid Build Coastguard Worker result.len()
884*c2e18aaaSAndroid Build Coastguard Worker );
885*c2e18aaaSAndroid Build Coastguard Worker
886*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_type, data) in expected {
887*c2e18aaaSAndroid Build Coastguard Worker match file_type {
888*c2e18aaaSAndroid Build Coastguard Worker FileType::File => assert!(
889*c2e18aaaSAndroid Build Coastguard Worker matching_file_fingerprint(file_name, data, &result),
890*c2e18aaaSAndroid Build Coastguard Worker "mismatch on {:?} {:?}",
891*c2e18aaaSAndroid Build Coastguard Worker file_name,
892*c2e18aaaSAndroid Build Coastguard Worker data
893*c2e18aaaSAndroid Build Coastguard Worker ),
894*c2e18aaaSAndroid Build Coastguard Worker FileType::Directory => assert!(result
895*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
896*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|d| d.file_type == FileType::Directory)),
897*c2e18aaaSAndroid Build Coastguard Worker FileType::Symlink => assert!(result
898*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
899*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
900*c2e18aaaSAndroid Build Coastguard Worker };
901*c2e18aaaSAndroid Build Coastguard Worker }
902*c2e18aaaSAndroid Build Coastguard Worker }
903*c2e18aaaSAndroid Build Coastguard Worker
904*c2e18aaaSAndroid Build Coastguard Worker #[test]
fingerprint_partition_cache_mismatch_test()905*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint_partition_cache_mismatch_test() {
906*c2e18aaaSAndroid Build Coastguard Worker // test to assure that when a file is modified; the cache doesn't return the same digest
907*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
908*c2e18aaaSAndroid Build Coastguard Worker make_partition(
909*c2e18aaaSAndroid Build Coastguard Worker tmp_root.path(),
910*c2e18aaaSAndroid Build Coastguard Worker "system",
911*c2e18aaaSAndroid Build Coastguard Worker &[("file1.so", "some text"), ("file2.so", "more text")],
912*c2e18aaaSAndroid Build Coastguard Worker // Empty directories/
913*c2e18aaaSAndroid Build Coastguard Worker &[],
914*c2e18aaaSAndroid Build Coastguard Worker // Symlinks
915*c2e18aaaSAndroid Build Coastguard Worker &[("link1.so", "file1.so")],
916*c2e18aaaSAndroid Build Coastguard Worker );
917*c2e18aaaSAndroid Build Coastguard Worker let result = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
918*c2e18aaaSAndroid Build Coastguard Worker let expected = &[
919*c2e18aaaSAndroid Build Coastguard Worker ("system/file1.so", FileType::File, "b94f"),
920*c2e18aaaSAndroid Build Coastguard Worker ("system/file2.so", FileType::File, "c0dc"),
921*c2e18aaaSAndroid Build Coastguard Worker ("system/link1.so", FileType::Symlink, "file1.so"),
922*c2e18aaaSAndroid Build Coastguard Worker ];
923*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_type, data) in expected {
924*c2e18aaaSAndroid Build Coastguard Worker match file_type {
925*c2e18aaaSAndroid Build Coastguard Worker FileType::File => assert!(
926*c2e18aaaSAndroid Build Coastguard Worker matching_file_fingerprint(file_name, data, &result),
927*c2e18aaaSAndroid Build Coastguard Worker "mismatch on {:?} {:?}",
928*c2e18aaaSAndroid Build Coastguard Worker file_name,
929*c2e18aaaSAndroid Build Coastguard Worker data
930*c2e18aaaSAndroid Build Coastguard Worker ),
931*c2e18aaaSAndroid Build Coastguard Worker FileType::Directory => assert!(result
932*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
933*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|d| d.file_type == FileType::Directory)),
934*c2e18aaaSAndroid Build Coastguard Worker FileType::Symlink => assert!(result
935*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
936*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
937*c2e18aaaSAndroid Build Coastguard Worker }
938*c2e18aaaSAndroid Build Coastguard Worker }
939*c2e18aaaSAndroid Build Coastguard Worker
940*c2e18aaaSAndroid Build Coastguard Worker // modify file
941*c2e18aaaSAndroid Build Coastguard Worker let file_path = tmp_root.path().join("system/file1.so");
942*c2e18aaaSAndroid Build Coastguard Worker fs::write(file_path, "modified file.").unwrap();
943*c2e18aaaSAndroid Build Coastguard Worker let result2 = fingerprint_partitions(tmp_root.path(), &[PathBuf::from("system")]).unwrap();
944*c2e18aaaSAndroid Build Coastguard Worker let expected2 = &[
945*c2e18aaaSAndroid Build Coastguard Worker ("system/file1.so", FileType::File, "047c5"),
946*c2e18aaaSAndroid Build Coastguard Worker ("system/file2.so", FileType::File, "c0dc"),
947*c2e18aaaSAndroid Build Coastguard Worker ];
948*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_type, data) in expected2 {
949*c2e18aaaSAndroid Build Coastguard Worker match file_type {
950*c2e18aaaSAndroid Build Coastguard Worker FileType::File => assert!(
951*c2e18aaaSAndroid Build Coastguard Worker matching_file_fingerprint(file_name, data, &result2),
952*c2e18aaaSAndroid Build Coastguard Worker "mismatch on {:?} {:?}",
953*c2e18aaaSAndroid Build Coastguard Worker file_name,
954*c2e18aaaSAndroid Build Coastguard Worker data
955*c2e18aaaSAndroid Build Coastguard Worker ),
956*c2e18aaaSAndroid Build Coastguard Worker FileType::Directory => assert!(result2
957*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
958*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|d| d.file_type == FileType::Directory)),
959*c2e18aaaSAndroid Build Coastguard Worker FileType::Symlink => assert!(result
960*c2e18aaaSAndroid Build Coastguard Worker .get(&PathBuf::from(file_name))
961*c2e18aaaSAndroid Build Coastguard Worker .is_some_and(|s| s.file_type == FileType::Symlink && &s.symlink == data)),
962*c2e18aaaSAndroid Build Coastguard Worker }
963*c2e18aaaSAndroid Build Coastguard Worker }
964*c2e18aaaSAndroid Build Coastguard Worker }
965*c2e18aaaSAndroid Build Coastguard Worker
966*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_write_and_read_cache_file()967*c2e18aaaSAndroid Build Coastguard Worker fn test_write_and_read_cache_file() {
968*c2e18aaaSAndroid Build Coastguard Worker let root = TempDir::new().unwrap();
969*c2e18aaaSAndroid Build Coastguard Worker let file_path = root.path().join("cache.json");
970*c2e18aaaSAndroid Build Coastguard Worker let results = HashMap::from([
971*c2e18aaaSAndroid Build Coastguard Worker (
972*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("path1"),
973*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
974*c2e18aaaSAndroid Build Coastguard Worker cache_key: "key1".to_string(),
975*c2e18aaaSAndroid Build Coastguard Worker digest: "value1".to_string(),
976*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
977*c2e18aaaSAndroid Build Coastguard Worker },
978*c2e18aaaSAndroid Build Coastguard Worker ),
979*c2e18aaaSAndroid Build Coastguard Worker (
980*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("path2"),
981*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
982*c2e18aaaSAndroid Build Coastguard Worker cache_key: "key2".to_string(),
983*c2e18aaaSAndroid Build Coastguard Worker digest: "value2".to_string(),
984*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
985*c2e18aaaSAndroid Build Coastguard Worker },
986*c2e18aaaSAndroid Build Coastguard Worker ),
987*c2e18aaaSAndroid Build Coastguard Worker ]);
988*c2e18aaaSAndroid Build Coastguard Worker
989*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::default();
990*c2e18aaaSAndroid Build Coastguard Worker let write_result = cache.write_to_file(&results, &file_path);
991*c2e18aaaSAndroid Build Coastguard Worker assert!(write_result.is_ok());
992*c2e18aaaSAndroid Build Coastguard Worker
993*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::read_from_file(&file_path).unwrap();
994*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(cache.get("key1"), Some(&"value1".to_string()));
995*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(cache.get("key2"), Some(&"value2".to_string()));
996*c2e18aaaSAndroid Build Coastguard Worker }
997*c2e18aaaSAndroid Build Coastguard Worker
998*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_read_cache_file_no_file()999*c2e18aaaSAndroid Build Coastguard Worker fn test_read_cache_file_no_file() {
1000*c2e18aaaSAndroid Build Coastguard Worker let bad_path = Path::new("/tmp/fake/non/existing/path");
1001*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::read_from_file(bad_path).unwrap_or_default();
1002*c2e18aaaSAndroid Build Coastguard Worker assert!(cache.data.is_empty());
1003*c2e18aaaSAndroid Build Coastguard Worker }
1004*c2e18aaaSAndroid Build Coastguard Worker
1005*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_read_cache_file_invalid_file()1006*c2e18aaaSAndroid Build Coastguard Worker fn test_read_cache_file_invalid_file() {
1007*c2e18aaaSAndroid Build Coastguard Worker let root = TempDir::new().unwrap();
1008*c2e18aaaSAndroid Build Coastguard Worker let file_path = root.path().join("cache.json");
1009*c2e18aaaSAndroid Build Coastguard Worker fs::write(file_path.clone(), "invalid cache data").unwrap();
1010*c2e18aaaSAndroid Build Coastguard Worker
1011*c2e18aaaSAndroid Build Coastguard Worker let cache = Cache::read_from_file(&file_path).unwrap_or_default();
1012*c2e18aaaSAndroid Build Coastguard Worker assert!(cache.data.is_empty());
1013*c2e18aaaSAndroid Build Coastguard Worker }
1014*c2e18aaaSAndroid Build Coastguard Worker
1015*c2e18aaaSAndroid Build Coastguard Worker // Ensure the FileMetadata for the given file matches the prefix of the digest.
1016*c2e18aaaSAndroid Build Coastguard Worker // We don't require whole digests as that just muddys up the test code and
1017*c2e18aaaSAndroid Build Coastguard Worker // other methods tests full digests.
matching_file_fingerprint( file_name: &str, digest_prefix: &str, fingerprints: &HashMap<PathBuf, FileMetadata>, ) -> bool1018*c2e18aaaSAndroid Build Coastguard Worker fn matching_file_fingerprint(
1019*c2e18aaaSAndroid Build Coastguard Worker file_name: &str,
1020*c2e18aaaSAndroid Build Coastguard Worker digest_prefix: &str,
1021*c2e18aaaSAndroid Build Coastguard Worker fingerprints: &HashMap<PathBuf, FileMetadata>,
1022*c2e18aaaSAndroid Build Coastguard Worker ) -> bool {
1023*c2e18aaaSAndroid Build Coastguard Worker match fingerprints.get(&PathBuf::from(file_name)) {
1024*c2e18aaaSAndroid Build Coastguard Worker None => false,
1025*c2e18aaaSAndroid Build Coastguard Worker Some(metadata) => {
1026*c2e18aaaSAndroid Build Coastguard Worker metadata.file_type == FileType::File
1027*c2e18aaaSAndroid Build Coastguard Worker && metadata.symlink.is_empty()
1028*c2e18aaaSAndroid Build Coastguard Worker && metadata.digest.starts_with(digest_prefix)
1029*c2e18aaaSAndroid Build Coastguard Worker }
1030*c2e18aaaSAndroid Build Coastguard Worker }
1031*c2e18aaaSAndroid Build Coastguard Worker }
1032*c2e18aaaSAndroid Build Coastguard Worker
1033*c2e18aaaSAndroid Build Coastguard Worker // Create a temporary folder and create files, directories and symlinks under it.
make_partition( tmp_root: &Path, partition_name: &str, files: &[(&str, &str)], directories: &[&str], symlinks: &[(&str, &str)], )1034*c2e18aaaSAndroid Build Coastguard Worker fn make_partition(
1035*c2e18aaaSAndroid Build Coastguard Worker tmp_root: &Path,
1036*c2e18aaaSAndroid Build Coastguard Worker partition_name: &str,
1037*c2e18aaaSAndroid Build Coastguard Worker files: &[(&str, &str)],
1038*c2e18aaaSAndroid Build Coastguard Worker directories: &[&str],
1039*c2e18aaaSAndroid Build Coastguard Worker symlinks: &[(&str, &str)],
1040*c2e18aaaSAndroid Build Coastguard Worker ) {
1041*c2e18aaaSAndroid Build Coastguard Worker let partition_dir = tmp_root.join(partition_name);
1042*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir(&partition_dir).expect("should have created directory partition_dir");
1043*c2e18aaaSAndroid Build Coastguard Worker // First create all empty directories.
1044*c2e18aaaSAndroid Build Coastguard Worker for dir in directories {
1045*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(partition_dir.join(dir))
1046*c2e18aaaSAndroid Build Coastguard Worker .unwrap_or_else(|_| panic!("Should have created {dir} in {tmp_root:?}"));
1047*c2e18aaaSAndroid Build Coastguard Worker }
1048*c2e18aaaSAndroid Build Coastguard Worker for (file_name, file_content) in files {
1049*c2e18aaaSAndroid Build Coastguard Worker // Create parent dirs, in case they are needed.
1050*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(partition_dir.join(file_name).parent().unwrap()).unwrap();
1051*c2e18aaaSAndroid Build Coastguard Worker fs::write(partition_dir.join(file_name), file_content).expect("Trouble writing file");
1052*c2e18aaaSAndroid Build Coastguard Worker }
1053*c2e18aaaSAndroid Build Coastguard Worker for (symlink_name, target) in symlinks {
1054*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(partition_dir.join(symlink_name).parent().unwrap()).unwrap();
1055*c2e18aaaSAndroid Build Coastguard Worker create_symlink(&PathBuf::from(target), symlink_name, &partition_dir);
1056*c2e18aaaSAndroid Build Coastguard Worker }
1057*c2e18aaaSAndroid Build Coastguard Worker }
1058*c2e18aaaSAndroid Build Coastguard Worker
1059*c2e18aaaSAndroid Build Coastguard Worker // Create a symlink in `directory` named `link_name` that points to `target`.
1060*c2e18aaaSAndroid Build Coastguard Worker // Returns the absolute path to the created symlink.
create_symlink(target: &Path, link_name: &str, directory: &Path) -> PathBuf1061*c2e18aaaSAndroid Build Coastguard Worker fn create_symlink(target: &Path, link_name: &str, directory: &Path) -> PathBuf {
1062*c2e18aaaSAndroid Build Coastguard Worker fs::soft_link(target, directory.join(link_name))
1063*c2e18aaaSAndroid Build Coastguard Worker .unwrap_or_else(|e| println!("Could not symlink to {:?} {:?}", directory, e));
1064*c2e18aaaSAndroid Build Coastguard Worker
1065*c2e18aaaSAndroid Build Coastguard Worker directory.join(Path::new(link_name))
1066*c2e18aaaSAndroid Build Coastguard Worker }
1067*c2e18aaaSAndroid Build Coastguard Worker
file_metadata(digest: &str) -> FileMetadata1068*c2e18aaaSAndroid Build Coastguard Worker fn file_metadata(digest: &str) -> FileMetadata {
1069*c2e18aaaSAndroid Build Coastguard Worker FileMetadata { file_type: FileType::File, digest: digest.to_string(), ..Default::default() }
1070*c2e18aaaSAndroid Build Coastguard Worker }
1071*c2e18aaaSAndroid Build Coastguard Worker
link_metadata(target: &str) -> FileMetadata1072*c2e18aaaSAndroid Build Coastguard Worker fn link_metadata(target: &str) -> FileMetadata {
1073*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
1074*c2e18aaaSAndroid Build Coastguard Worker file_type: FileType::Symlink,
1075*c2e18aaaSAndroid Build Coastguard Worker digest: target.to_string(),
1076*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
1077*c2e18aaaSAndroid Build Coastguard Worker }
1078*c2e18aaaSAndroid Build Coastguard Worker }
1079*c2e18aaaSAndroid Build Coastguard Worker
dir_metadata() -> FileMetadata1080*c2e18aaaSAndroid Build Coastguard Worker fn dir_metadata() -> FileMetadata {
1081*c2e18aaaSAndroid Build Coastguard Worker FileMetadata { file_type: FileType::Directory, ..Default::default() }
1082*c2e18aaaSAndroid Build Coastguard Worker }
1083*c2e18aaaSAndroid Build Coastguard Worker
1084*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): a bunch more tests:
1085*c2e18aaaSAndroid Build Coastguard Worker }
1086