• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

VolumeGraphics / havocompare / 14858735505

06 May 2025 11:40AM UTC coverage: 81.833% (-2.1%) from 83.973%
14858735505

push

github

Adinata Wijaya
add file exists checker

176 of 262 new or added lines in 3 files covered. (67.18%)

25 existing lines in 8 files now uncovered.

2919 of 3567 relevant lines covered (81.83%)

2563.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.37
/src/file_exist.rs
1
use crate::report::{DiffDetail, Difference};
2
use schemars_derive::JsonSchema;
3
use serde::{Deserialize, Serialize};
4
use std::path;
5
use std::path::{Path, PathBuf};
6
use thiserror::Error;
7
use tracing::error;
8

9
#[derive(Debug, Error)]
10
/// Errors during html / plain text checking
11
pub enum Error {
12
    #[error("Failed to remove path's prefix")]
13
    StripPrefixError(#[from] path::StripPrefixError),
14
}
15

16
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
17
pub struct FileExistConfig {
18
    pub mode: Mode,
19
}
20

21
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
22
pub enum Mode {
23
    Both, // check if entry is missing in actual, and/or if entry exists in actual but not in nominal
24
    Nominal, // check only if entry is missing in actual, ignoring entries that exist in actual but not in nominal
25
}
26

27
pub(crate) fn compare_paths<P: AsRef<Path>>(
5✔
28
    nominal: P,
5✔
29
    actual: P,
5✔
30
    nominal_entries: &[PathBuf],
5✔
31
    actual_entries: &[PathBuf],
5✔
32
    config: &FileExistConfig,
5✔
33
) -> Result<Difference, Error> {
5✔
34
    let nominal_path = nominal.as_ref();
5✔
35
    let actual_path = actual.as_ref();
5✔
36

5✔
37
    let mut difference = Difference::new_for_file(nominal_path, actual_path);
5✔
38

5✔
39
    //remove root paths!
5✔
40
    let nominal_entries: Result<Vec<_>, path::StripPrefixError> = nominal_entries
5✔
41
        .iter()
5✔
42
        .map(|path| path.strip_prefix(nominal_path))
29✔
43
        .collect();
5✔
44
    let nominal_entries = nominal_entries?;
5✔
45

46
    let actual_entries: Result<Vec<_>, path::StripPrefixError> = actual_entries
5✔
47
        .iter()
5✔
48
        .map(|path| path.strip_prefix(actual_path))
29✔
49
        .collect();
5✔
50
    let actual_entries = actual_entries?;
5✔
51

52
    let mut is_the_same = true;
5✔
53
    if matches!(config.mode, Mode::Both | Mode::Nominal) {
5✔
54
        nominal_entries.iter().for_each(|entry| {
29✔
55
            let detail = if let Some(f) = actual_entries.iter().find(|a| *a == entry) {
99✔
56
                (f.to_string_lossy().to_string(), false)
27✔
57
            } else {
58
                error!("{:?} doesn't exists in the actual folder", entry);
2✔
59
                is_the_same = false;
2✔
60
                ("".to_owned(), true)
2✔
61
            };
62

63
            difference.push_detail(DiffDetail::File {
29✔
64
                nominal: entry.to_string_lossy().to_string(),
29✔
65
                actual: detail.0,
29✔
66
                error: detail.1,
29✔
67
            });
29✔
68
        });
29✔
NEW
69
    }
×
70

71
    if matches!(config.mode, Mode::Both) {
5✔
72
        actual_entries.iter().for_each(|entry| {
17✔
73
            if !nominal_entries.iter().any(|n| n == entry) {
56✔
74
                difference.push_detail(DiffDetail::File {
1✔
75
                    nominal: "".to_owned(),
1✔
76
                    actual: entry.to_string_lossy().to_string(),
1✔
77
                    error: true,
1✔
78
                });
1✔
79

1✔
80
                error!("Additional entry {:?} found in the actual folder", entry);
1✔
81
                is_the_same = false;
1✔
82
            }
16✔
83
        });
17✔
84
    }
2✔
85

86
    if !is_the_same {
5✔
87
        difference.error();
3✔
88
    }
3✔
89

90
    Ok(difference)
5✔
91
}
5✔
92

93
#[cfg(test)]
94

95
mod test {
96
    use super::*;
97

98
    #[test]
99
    fn test_compare_directories() {
1✔
100
        let nominal_dir = tempfile::Builder::new()
1✔
101
            .prefix("my-nominal")
1✔
102
            // .keep(true)
1✔
103
            .rand_bytes(1)
1✔
104
            .tempdir_in("tests")
1✔
105
            .expect("");
1✔
106

1✔
107
        std::fs::create_dir_all(nominal_dir.path().join("dir/a/aa")).expect("");
1✔
108
        std::fs::create_dir_all(nominal_dir.path().join("dir/b")).expect("");
1✔
109
        std::fs::create_dir_all(nominal_dir.path().join("dir/c")).expect("");
1✔
110

1✔
111
        let actual_dir = tempfile::Builder::new()
1✔
112
            .prefix("my-actual")
1✔
113
            // .keep(true)
1✔
114
            .rand_bytes(1)
1✔
115
            .tempdir_in("tests")
1✔
116
            .expect("");
1✔
117

1✔
118
        std::fs::create_dir_all(actual_dir.path().join("dir/a/aa")).expect("");
1✔
119
        std::fs::create_dir_all(actual_dir.path().join("dir/b")).expect("");
1✔
120
        std::fs::create_dir_all(actual_dir.path().join("dir/c")).expect("");
1✔
121

1✔
122
        let pattern_include = ["**/*/"];
1✔
123
        let pattern_exclude: Vec<String> = Vec::new();
1✔
124

1✔
125
        let nominal_entries =
1✔
126
            crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude).expect("");
1✔
127
        let actual_entries =
1✔
128
            crate::get_files(&actual_dir, &pattern_include, &pattern_exclude).expect("");
1✔
129

1✔
130
        let result = compare_paths(
1✔
131
            nominal_dir.path(),
1✔
132
            actual_dir.path(),
1✔
133
            &nominal_entries,
1✔
134
            &actual_entries,
1✔
135
            &FileExistConfig { mode: Mode::Both },
1✔
136
        )
1✔
137
        .expect("");
1✔
138

1✔
139
        assert!(!result.is_error);
1✔
140

141
        std::fs::create_dir_all(actual_dir.path().join("dir/d")).expect("");
1✔
142

1✔
143
        let nominal_entries =
1✔
144
            crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude).expect("");
1✔
145
        let actual_entries =
1✔
146
            crate::get_files(&actual_dir, &pattern_include, &pattern_exclude).expect("");
1✔
147

1✔
148
        let result = compare_paths(
1✔
149
            nominal_dir.path(),
1✔
150
            actual_dir.path(),
1✔
151
            &nominal_entries,
1✔
152
            &actual_entries,
1✔
153
            &FileExistConfig { mode: Mode::Both },
1✔
154
        )
1✔
155
        .expect("");
1✔
156

1✔
157
        assert!(result.is_error);
1✔
158

159
        let result = compare_paths(
1✔
160
            nominal_dir.path(),
1✔
161
            actual_dir.path(),
1✔
162
            &nominal_entries,
1✔
163
            &actual_entries,
1✔
164
            &FileExistConfig {
1✔
165
                mode: Mode::Nominal,
1✔
166
            },
1✔
167
        )
1✔
168
        .expect("");
1✔
169

1✔
170
        assert!(!result.is_error);
1✔
171

172
        std::fs::create_dir_all(nominal_dir.path().join("dir/d")).expect("");
1✔
173
        std::fs::create_dir_all(nominal_dir.path().join("dir/e")).expect("");
1✔
174

1✔
175
        let nominal_entries =
1✔
176
            crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude).expect("");
1✔
177
        let actual_entries =
1✔
178
            crate::get_files(&actual_dir, &pattern_include, &pattern_exclude).expect("");
1✔
179

1✔
180
        let result = compare_paths(
1✔
181
            nominal_dir.path(),
1✔
182
            actual_dir.path(),
1✔
183
            &nominal_entries,
1✔
184
            &actual_entries,
1✔
185
            &FileExistConfig { mode: Mode::Both },
1✔
186
        )
1✔
187
        .expect("");
1✔
188

1✔
189
        assert!(result.is_error);
1✔
190

191
        let result = compare_paths(
1✔
192
            nominal_dir.path(),
1✔
193
            actual_dir.path(),
1✔
194
            &nominal_entries,
1✔
195
            &actual_entries,
1✔
196
            &FileExistConfig {
1✔
197
                mode: Mode::Nominal,
1✔
198
            },
1✔
199
        )
1✔
200
        .expect("");
1✔
201

1✔
202
        assert!(result.is_error);
1✔
203
    }
1✔
204
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc