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

loot / loot-condition-interpreter / 14090628365

26 Mar 2025 06:11PM UTC coverage: 91.048% (-0.2%) from 91.296%
14090628365

push

github

Ortham
Update versions and changelog for v5.3.0

4943 of 5429 relevant lines covered (91.05%)

15.87 hits per line

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

98.67
/src/function/eval.rs
1
use std::collections::HashMap;
2
use std::ffi::OsStr;
3
use std::fs::{read_dir, DirEntry, File};
4
use std::hash::Hasher;
5
use std::io::{BufRead, BufReader};
6
use std::path::{Path, PathBuf};
7

8
use esplugin::ParseOptions;
9
use regex::Regex;
10

11
use super::path::{has_plugin_file_extension, normalise_file_name, resolve_path};
12
use super::version::Version;
13
use super::{ComparisonOperator, Function};
14
use crate::{Error, GameType, State};
15

16
fn evaluate_file_path(state: &State, file_path: &Path) -> Result<bool, Error> {
16✔
17
    Ok(resolve_path(state, file_path).exists())
16✔
18
}
16✔
19

20
fn is_match(game_type: GameType, regex: &Regex, file_name: &OsStr) -> bool {
70✔
21
    normalise_file_name(game_type, file_name)
70✔
22
        .to_str()
70✔
23
        .map(|s| regex.is_match(s))
70✔
24
        .unwrap_or(false)
70✔
25
}
70✔
26

27
fn evaluate_dir_entries_from_base_paths<'a>(
22✔
28
    base_path_iter: impl Iterator<Item = &'a PathBuf>,
22✔
29
    parent_path: &Path,
22✔
30
    mut evaluator: impl FnMut(DirEntry) -> bool,
22✔
31
) -> Result<bool, Error> {
22✔
32
    for base_path in base_path_iter {
39✔
33
        let parent_path = base_path.join(parent_path);
27✔
34
        let dir_iterator = match read_dir(&parent_path) {
27✔
35
            Ok(i) => i,
25✔
36
            Err(_) => return Ok(false),
2✔
37
        };
38

39
        for entry in dir_iterator {
238✔
40
            let entry = entry.map_err(|e| Error::IoError(parent_path.to_path_buf(), e))?;
221✔
41
            if evaluator(entry) {
221✔
42
                return Ok(true);
8✔
43
            }
213✔
44
        }
45
    }
46

47
    Ok(false)
12✔
48
}
22✔
49

50
fn evaluate_dir_entries(
22✔
51
    state: &State,
22✔
52
    parent_path: &Path,
22✔
53
    evaluator: impl FnMut(DirEntry) -> bool,
22✔
54
) -> Result<bool, Error> {
22✔
55
    match state.game_type {
22✔
56
        GameType::OpenMW => evaluate_dir_entries_from_base_paths(
1✔
57
            state
1✔
58
                .additional_data_paths
1✔
59
                .iter()
1✔
60
                .rev()
1✔
61
                .chain(std::iter::once(&state.data_path)),
1✔
62
            parent_path,
1✔
63
            evaluator,
1✔
64
        ),
1✔
65
        _ => evaluate_dir_entries_from_base_paths(
21✔
66
            state
21✔
67
                .additional_data_paths
21✔
68
                .iter()
21✔
69
                .chain(std::iter::once(&state.data_path)),
21✔
70
            parent_path,
21✔
71
            evaluator,
21✔
72
        ),
21✔
73
    }
74
}
22✔
75

76
fn evaluate_file_regex(state: &State, parent_path: &Path, regex: &Regex) -> Result<bool, Error> {
5✔
77
    let evaluator = |entry: DirEntry| is_match(state.game_type, regex, &entry.file_name());
24✔
78

79
    evaluate_dir_entries(state, parent_path, evaluator)
5✔
80
}
5✔
81

82
fn evaluate_file_size(state: &State, path: &Path, size: u64) -> Result<bool, Error> {
5✔
83
    std::fs::metadata(resolve_path(state, path))
5✔
84
        .map(|m| m.len() == size)
5✔
85
        .or(Ok(false))
5✔
86
}
5✔
87

88
fn evaluate_readable(state: &State, path: &Path) -> Result<bool, Error> {
6✔
89
    if path.is_dir() {
6✔
90
        Ok(read_dir(resolve_path(state, path)).is_ok())
1✔
91
    } else {
92
        Ok(File::open(resolve_path(state, path)).is_ok())
5✔
93
    }
94
}
6✔
95

96
fn evaluate_is_executable(state: &State, path: &Path) -> Result<bool, Error> {
5✔
97
    Ok(Version::is_readable(&resolve_path(state, path)))
5✔
98
}
5✔
99

100
fn evaluate_many(state: &State, parent_path: &Path, regex: &Regex) -> Result<bool, Error> {
6✔
101
    // Share the found_one state across all data paths because they're all
6✔
102
    // treated as if they were merged into one directory.
6✔
103
    let mut found_one = false;
6✔
104
    let evaluator = |entry: DirEntry| {
46✔
105
        if is_match(state.game_type, regex, &entry.file_name()) {
46✔
106
            if found_one {
7✔
107
                true
3✔
108
            } else {
109
                found_one = true;
4✔
110
                false
4✔
111
            }
112
        } else {
113
            false
39✔
114
        }
115
    };
46✔
116

117
    evaluate_dir_entries(state, parent_path, evaluator)
6✔
118
}
6✔
119

120
fn evaluate_active_path(state: &State, path: &Path) -> Result<bool, Error> {
3✔
121
    Ok(path
3✔
122
        .to_str()
3✔
123
        .map(|s| state.active_plugins.contains(&s.to_lowercase()))
3✔
124
        .unwrap_or(false))
3✔
125
}
3✔
126

127
fn evaluate_active_regex(state: &State, regex: &Regex) -> Result<bool, Error> {
2✔
128
    Ok(state.active_plugins.iter().any(|p| regex.is_match(p)))
2✔
129
}
2✔
130

131
fn parse_plugin(state: &State, file_path: &Path) -> Option<esplugin::Plugin> {
10✔
132
    use esplugin::GameId;
133

134
    let game_id = match state.game_type {
10✔
135
        GameType::OpenMW => return None,
1✔
136
        GameType::Morrowind => GameId::Morrowind,
×
137
        GameType::Oblivion => GameId::Oblivion,
9✔
138
        GameType::Skyrim => GameId::Skyrim,
×
139
        GameType::SkyrimSE | GameType::SkyrimVR => GameId::SkyrimSE,
×
140
        GameType::Fallout3 => GameId::Fallout3,
×
141
        GameType::FalloutNV => GameId::FalloutNV,
×
142
        GameType::Fallout4 | GameType::Fallout4VR => GameId::Fallout4,
×
143
        GameType::Starfield => GameId::Starfield,
×
144
    };
145

146
    let path = resolve_path(state, file_path);
9✔
147

9✔
148
    let mut plugin = esplugin::Plugin::new(game_id, &path);
9✔
149

9✔
150
    if plugin.parse_file(ParseOptions::header_only()).is_ok() {
9✔
151
        Some(plugin)
5✔
152
    } else {
153
        None
4✔
154
    }
155
}
10✔
156

157
fn evaluate_is_master(state: &State, file_path: &Path) -> Result<bool, Error> {
5✔
158
    Ok(parse_plugin(state, file_path)
5✔
159
        .map(|plugin| plugin.is_master_file())
5✔
160
        .unwrap_or(false))
5✔
161
}
5✔
162

163
fn evaluate_many_active(state: &State, regex: &Regex) -> Result<bool, Error> {
3✔
164
    let mut found_one = false;
3✔
165
    for active_plugin in &state.active_plugins {
8✔
166
        if regex.is_match(active_plugin) {
6✔
167
            if found_one {
3✔
168
                return Ok(true);
1✔
169
            } else {
2✔
170
                found_one = true;
2✔
171
            }
2✔
172
        }
3✔
173
    }
174

175
    Ok(false)
2✔
176
}
3✔
177

178
fn lowercase(path: &Path) -> Option<String> {
12✔
179
    path.to_str().map(str::to_lowercase)
12✔
180
}
12✔
181

182
fn evaluate_checksum(state: &State, file_path: &Path, crc: u32) -> Result<bool, Error> {
8✔
183
    if let Ok(reader) = state.crc_cache.read() {
8✔
184
        if let Some(key) = lowercase(file_path) {
8✔
185
            if let Some(cached_crc) = reader.get(&key) {
8✔
186
                return Ok(*cached_crc == crc);
1✔
187
            }
7✔
188
        }
×
189
    }
×
190

191
    let path = resolve_path(state, file_path);
7✔
192

7✔
193
    if !path.is_file() {
7✔
194
        return Ok(false);
3✔
195
    }
4✔
196

4✔
197
    let io_error_mapper = |e| Error::IoError(file_path.to_path_buf(), e);
4✔
198
    let file = File::open(path).map_err(io_error_mapper)?;
4✔
199
    let mut reader = BufReader::new(file);
4✔
200
    let mut hasher = crc32fast::Hasher::new();
4✔
201

202
    let mut buffer = reader.fill_buf().map_err(io_error_mapper)?;
4✔
203
    while !buffer.is_empty() {
8✔
204
        hasher.write(buffer);
4✔
205
        let length = buffer.len();
4✔
206
        reader.consume(length);
4✔
207

4✔
208
        buffer = reader.fill_buf().map_err(io_error_mapper)?;
4✔
209
    }
210

211
    let calculated_crc = hasher.finalize();
4✔
212
    let mut writer = state.crc_cache.write().unwrap_or_else(|mut e| {
4✔
213
        **e.get_mut() = HashMap::new();
×
214
        state.crc_cache.clear_poison();
×
215
        e.into_inner()
×
216
    });
4✔
217

218
    if let Some(key) = lowercase(file_path) {
4✔
219
        writer.insert(key, calculated_crc);
4✔
220
    }
4✔
221

222
    Ok(calculated_crc == crc)
4✔
223
}
8✔
224

225
fn lowercase_filename(path: &Path) -> Option<String> {
23✔
226
    path.file_name()
23✔
227
        .and_then(OsStr::to_str)
23✔
228
        .map(str::to_lowercase)
23✔
229
}
23✔
230

231
fn get_version(state: &State, file_path: &Path) -> Result<Option<Version>, Error> {
35✔
232
    if !file_path.is_file() {
35✔
233
        return Ok(None);
12✔
234
    }
23✔
235

236
    if let Some(key) = lowercase_filename(file_path) {
23✔
237
        if let Some(version) = state.plugin_versions.get(&key) {
23✔
238
            return Ok(Some(Version::from(version.as_str())));
16✔
239
        }
7✔
240
    }
×
241

242
    if has_plugin_file_extension(state.game_type, file_path) {
7✔
243
        Ok(None)
6✔
244
    } else {
245
        Version::read_file_version(file_path)
1✔
246
    }
247
}
35✔
248

249
fn get_product_version(file_path: &Path) -> Result<Option<Version>, Error> {
5✔
250
    if file_path.is_file() {
5✔
251
        Version::read_product_version(file_path)
3✔
252
    } else {
253
        Ok(None)
2✔
254
    }
255
}
5✔
256

257
fn compare_versions(
20✔
258
    actual_version: Version,
20✔
259
    comparator: ComparisonOperator,
20✔
260
    given_version: &str,
20✔
261
) -> bool {
20✔
262
    let given_version = Version::from(given_version);
20✔
263

20✔
264
    match comparator {
20✔
265
        ComparisonOperator::Equal => actual_version == given_version,
5✔
266
        ComparisonOperator::NotEqual => actual_version != given_version,
4✔
267
        ComparisonOperator::LessThan => actual_version < given_version,
2✔
268
        ComparisonOperator::GreaterThan => actual_version > given_version,
3✔
269
        ComparisonOperator::LessThanOrEqual => actual_version <= given_version,
3✔
270
        ComparisonOperator::GreaterThanOrEqual => actual_version >= given_version,
3✔
271
    }
272
}
20✔
273

274
fn evaluate_version<F>(
36✔
275
    state: &State,
36✔
276
    file_path: &Path,
36✔
277
    given_version: &str,
36✔
278
    comparator: ComparisonOperator,
36✔
279
    read_version: F,
36✔
280
) -> Result<bool, Error>
36✔
281
where
36✔
282
    F: Fn(&State, &Path) -> Result<Option<Version>, Error>,
36✔
283
{
36✔
284
    let file_path = resolve_path(state, file_path);
36✔
285
    let actual_version = match read_version(state, &file_path)? {
36✔
286
        Some(v) => v,
18✔
287
        None => {
288
            return Ok(comparator == ComparisonOperator::NotEqual
18✔
289
                || comparator == ComparisonOperator::LessThan
15✔
290
                || comparator == ComparisonOperator::LessThanOrEqual);
12✔
291
        }
292
    };
293

294
    Ok(compare_versions(actual_version, comparator, given_version))
18✔
295
}
36✔
296

297
fn evaluate_filename_version(
9✔
298
    state: &State,
9✔
299
    parent_path: &Path,
9✔
300
    regex: &Regex,
9✔
301
    version: &str,
9✔
302
    comparator: ComparisonOperator,
9✔
303
) -> Result<bool, Error> {
9✔
304
    let evaluator = |entry: DirEntry| {
79✔
305
        normalise_file_name(state.game_type, &entry.file_name())
79✔
306
            .to_str()
79✔
307
            .and_then(|s| regex.captures(s))
79✔
308
            .and_then(|c| c.get(1))
79✔
309
            .map(|m| Version::from(m.as_str()))
79✔
310
            .map(|v| compare_versions(v, comparator, version))
79✔
311
            .unwrap_or(false)
79✔
312
    };
79✔
313

314
    evaluate_dir_entries(state, parent_path, evaluator)
9✔
315
}
9✔
316

317
fn evaluate_description_contains(
5✔
318
    state: &State,
5✔
319
    file_path: &Path,
5✔
320
    regex: &Regex,
5✔
321
) -> Result<bool, Error> {
5✔
322
    Ok(parse_plugin(state, file_path)
5✔
323
        .and_then(|plugin| plugin.description().unwrap_or(None))
5✔
324
        .map(|description| regex.is_match(&description))
5✔
325
        .unwrap_or(false))
5✔
326
}
5✔
327

328
impl Function {
329
    pub fn eval(&self, state: &State) -> Result<bool, Error> {
117✔
330
        if self.is_slow() {
117✔
331
            if let Ok(reader) = state.condition_cache.read() {
101✔
332
                if let Some(cached_result) = reader.get(self) {
101✔
333
                    return Ok(*cached_result);
3✔
334
                }
98✔
335
            }
×
336
        }
16✔
337

338
        let result = match self {
114✔
339
            Function::FilePath(f) => evaluate_file_path(state, f),
16✔
340
            Function::FileRegex(p, r) => evaluate_file_regex(state, p, r),
5✔
341
            Function::FileSize(p, s) => evaluate_file_size(state, p, *s),
5✔
342
            Function::Readable(p) => evaluate_readable(state, p),
6✔
343
            Function::IsExecutable(p) => evaluate_is_executable(state, p),
5✔
344
            Function::ActivePath(p) => evaluate_active_path(state, p),
3✔
345
            Function::ActiveRegex(r) => evaluate_active_regex(state, r),
2✔
346
            Function::IsMaster(p) => evaluate_is_master(state, p),
5✔
347
            Function::Many(p, r) => evaluate_many(state, p, r),
6✔
348
            Function::ManyActive(r) => evaluate_many_active(state, r),
3✔
349
            Function::Checksum(path, crc) => evaluate_checksum(state, path, *crc),
8✔
350
            Function::Version(p, v, c) => evaluate_version(state, p, v, *c, get_version),
35✔
351
            Function::ProductVersion(p, v, c) => {
1✔
352
                evaluate_version(state, p, v, *c, |_, p| get_product_version(p))
1✔
353
            }
354
            Function::FilenameVersion(p, r, v, c) => evaluate_filename_version(state, p, r, v, *c),
9✔
355
            Function::DescriptionContains(p, r) => evaluate_description_contains(state, p, r),
5✔
356
        };
357

358
        if self.is_slow() {
114✔
359
            if let Ok(function_result) = result {
98✔
360
                let mut writer = state.condition_cache.write().unwrap_or_else(|mut e| {
98✔
361
                    **e.get_mut() = HashMap::new();
×
362
                    state.condition_cache.clear_poison();
×
363
                    e.into_inner()
×
364
                });
98✔
365

98✔
366
                writer.insert(self.clone(), function_result);
98✔
367
            }
98✔
368
        }
16✔
369

370
        result
114✔
371
    }
117✔
372

373
    /// Some functions are faster to evaluate than to look their result up in
374
    /// the cache, as the data they operate on are already cached separately and
375
    /// the operation is simple.
376
    fn is_slow(&self) -> bool {
231✔
377
        use Function::*;
378
        !matches!(
199✔
379
            self,
231✔
380
            ActivePath(_) | ActiveRegex(_) | ManyActive(_) | Checksum(_, _)
381
        )
382
    }
231✔
383
}
384

385
#[cfg(test)]
386
mod tests {
387
    use super::*;
388

389
    use std::fs::{copy, create_dir, remove_file};
390
    use std::path::PathBuf;
391
    use std::sync::RwLock;
392

393
    use regex::RegexBuilder;
394
    use tempfile::tempdir;
395

396
    use crate::GameType;
397

398
    fn state<T: Into<PathBuf>>(data_path: T) -> State {
55✔
399
        state_with_active_plugins(data_path, &[])
55✔
400
    }
55✔
401

402
    fn state_with_active_plugins<T: Into<PathBuf>>(data_path: T, active_plugins: &[&str]) -> State {
63✔
403
        state_with_data(data_path, Vec::default(), active_plugins, &[])
63✔
404
    }
63✔
405

406
    fn state_with_versions<T: Into<PathBuf>>(
23✔
407
        data_path: T,
23✔
408
        plugin_versions: &[(&str, &str)],
23✔
409
    ) -> State {
23✔
410
        state_with_data(data_path, Vec::default(), &[], plugin_versions)
23✔
411
    }
23✔
412

413
    fn state_with_data<T: Into<PathBuf>>(
93✔
414
        data_path: T,
93✔
415
        additional_data_paths: Vec<T>,
93✔
416
        active_plugins: &[&str],
93✔
417
        plugin_versions: &[(&str, &str)],
93✔
418
    ) -> State {
93✔
419
        let data_path = data_path.into();
93✔
420
        if !data_path.exists() {
93✔
421
            create_dir(&data_path).unwrap();
15✔
422
        }
78✔
423

424
        let additional_data_paths = additional_data_paths
93✔
425
            .into_iter()
93✔
426
            .map(|data_path| {
93✔
427
                let data_path: PathBuf = data_path.into();
9✔
428
                if !data_path.exists() {
9✔
429
                    create_dir(&data_path).unwrap();
×
430
                }
9✔
431
                data_path
9✔
432
            })
93✔
433
            .collect();
93✔
434

93✔
435
        State {
93✔
436
            game_type: GameType::Oblivion,
93✔
437
            data_path,
93✔
438
            additional_data_paths,
93✔
439
            active_plugins: active_plugins.iter().map(|s| s.to_lowercase()).collect(),
93✔
440
            crc_cache: RwLock::default(),
93✔
441
            plugin_versions: plugin_versions
93✔
442
                .iter()
93✔
443
                .map(|(p, v)| (p.to_lowercase(), v.to_string()))
93✔
444
                .collect(),
93✔
445
            condition_cache: RwLock::default(),
93✔
446
        }
93✔
447
    }
93✔
448

449
    fn regex(string: &str) -> Regex {
30✔
450
        RegexBuilder::new(string)
30✔
451
            .case_insensitive(true)
30✔
452
            .build()
30✔
453
            .unwrap()
30✔
454
    }
30✔
455

456
    #[cfg(not(windows))]
457
    fn make_path_unreadable(path: &Path) {
3✔
458
        use std::os::unix::fs::PermissionsExt;
459

460
        let mut permissions = std::fs::metadata(&path).unwrap().permissions();
3✔
461
        permissions.set_mode(0o200);
3✔
462
        std::fs::set_permissions(&path, permissions).unwrap();
3✔
463
    }
3✔
464

465
    #[test]
466
    fn evaluate_dir_entries_should_check_additional_paths_in_order_then_data_path() {
1✔
467
        let state = state_with_data(
1✔
468
            "./tests/testing-plugins/SkyrimSE",
1✔
469
            vec![
1✔
470
                "./tests/testing-plugins/Oblivion",
1✔
471
                "./tests/testing-plugins/Skyrim",
1✔
472
            ],
1✔
473
            &[],
1✔
474
            &[],
1✔
475
        );
1✔
476

1✔
477
        let mut paths = Vec::new();
1✔
478
        let evaluator = |entry: DirEntry| {
36✔
479
            if entry.file_name() == "Blank.esp" {
36✔
480
                paths.push(
3✔
481
                    entry
3✔
482
                        .path()
3✔
483
                        .parent()
3✔
484
                        .unwrap()
3✔
485
                        .parent()
3✔
486
                        .unwrap()
3✔
487
                        .to_path_buf(),
3✔
488
                );
3✔
489
            }
33✔
490
            false
36✔
491
        };
36✔
492
        let result = evaluate_dir_entries(&state, Path::new("Data"), evaluator).unwrap();
1✔
493

1✔
494
        assert!(!result);
1✔
495
        assert_eq!(
1✔
496
            vec![
1✔
497
                state.additional_data_paths[0].clone(),
1✔
498
                state.additional_data_paths[1].clone(),
1✔
499
                state.data_path,
1✔
500
            ],
1✔
501
            paths
1✔
502
        );
1✔
503
    }
1✔
504

505
    #[test]
506
    fn evaluate_dir_entries_should_check_additional_paths_in_reverse_order_then_data_path_for_openmw(
1✔
507
    ) {
1✔
508
        let mut state = state_with_data(
1✔
509
            "./tests/testing-plugins/SkyrimSE",
1✔
510
            vec![
1✔
511
                "./tests/testing-plugins/Oblivion",
1✔
512
                "./tests/testing-plugins/Skyrim",
1✔
513
            ],
1✔
514
            &[],
1✔
515
            &[],
1✔
516
        );
1✔
517
        state.game_type = GameType::OpenMW;
1✔
518

1✔
519
        let mut paths = Vec::new();
1✔
520
        let evaluator = |entry: DirEntry| {
36✔
521
            if entry.file_name() == "Blank.esp" {
36✔
522
                paths.push(
3✔
523
                    entry
3✔
524
                        .path()
3✔
525
                        .parent()
3✔
526
                        .unwrap()
3✔
527
                        .parent()
3✔
528
                        .unwrap()
3✔
529
                        .to_path_buf(),
3✔
530
                );
3✔
531
            }
33✔
532
            false
36✔
533
        };
36✔
534
        let result = evaluate_dir_entries(&state, Path::new("Data"), evaluator).unwrap();
1✔
535

1✔
536
        assert!(!result);
1✔
537
        assert_eq!(
1✔
538
            vec![
1✔
539
                state.additional_data_paths[1].clone(),
1✔
540
                state.additional_data_paths[0].clone(),
1✔
541
                state.data_path,
1✔
542
            ],
1✔
543
            paths
1✔
544
        );
1✔
545
    }
1✔
546

547
    #[test]
548
    fn function_file_path_eval_should_return_true_if_the_file_exists_relative_to_the_data_path() {
1✔
549
        let function = Function::FilePath(PathBuf::from("Cargo.toml"));
1✔
550
        let state = state(".");
1✔
551

1✔
552
        assert!(function.eval(&state).unwrap());
1✔
553
    }
1✔
554

555
    #[test]
556
    fn function_file_path_eval_should_return_true_if_given_a_plugin_that_is_ghosted() {
1✔
557
        let tmp_dir = tempdir().unwrap();
1✔
558
        let data_path = tmp_dir.path().join("Data");
1✔
559
        let state = state(data_path);
1✔
560

1✔
561
        copy(
1✔
562
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
563
            state.data_path.join("Blank.esp.ghost"),
1✔
564
        )
1✔
565
        .unwrap();
1✔
566

1✔
567
        let function = Function::FilePath(PathBuf::from("Blank.esp"));
1✔
568

1✔
569
        assert!(function.eval(&state).unwrap());
1✔
570
    }
1✔
571

572
    #[test]
573
    fn function_file_path_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
574
        let tmp_dir = tempdir().unwrap();
1✔
575
        let data_path = tmp_dir.path().join("Data");
1✔
576
        let state = state(data_path);
1✔
577

1✔
578
        copy(
1✔
579
            Path::new("Cargo.toml"),
1✔
580
            state.data_path.join("Cargo.toml.ghost"),
1✔
581
        )
1✔
582
        .unwrap();
1✔
583

1✔
584
        let function = Function::FilePath(PathBuf::from("Cargo.toml"));
1✔
585

1✔
586
        assert!(!function.eval(&state).unwrap());
1✔
587
    }
1✔
588

589
    #[test]
590
    fn function_file_path_eval_should_return_false_if_the_file_does_not_exist() {
1✔
591
        let function = Function::FilePath(PathBuf::from("missing"));
1✔
592
        let state = state(".");
1✔
593

1✔
594
        assert!(!function.eval(&state).unwrap());
1✔
595
    }
1✔
596

597
    #[test]
598
    fn function_file_regex_eval_should_be_false_if_no_directory_entries_match() {
1✔
599
        let function = Function::FileRegex(PathBuf::from("."), regex("missing"));
1✔
600
        let state = state(".");
1✔
601

1✔
602
        assert!(!function.eval(&state).unwrap());
1✔
603
    }
1✔
604

605
    #[test]
606
    fn function_file_regex_eval_should_be_false_if_the_parent_path_part_is_not_a_directory() {
1✔
607
        let function = Function::FileRegex(PathBuf::from("missing"), regex("Cargo.*"));
1✔
608
        let state = state(".");
1✔
609

1✔
610
        assert!(!function.eval(&state).unwrap());
1✔
611
    }
1✔
612

613
    #[test]
614
    fn function_file_regex_eval_should_be_true_if_a_directory_entry_matches() {
1✔
615
        let function = Function::FileRegex(
1✔
616
            PathBuf::from("tests/testing-plugins/Oblivion/Data"),
1✔
617
            regex("Blank\\.esp"),
1✔
618
        );
1✔
619
        let state = state(".");
1✔
620

1✔
621
        assert!(function.eval(&state).unwrap());
1✔
622
    }
1✔
623

624
    #[test]
625
    fn function_file_regex_eval_should_trim_ghost_plugin_extension_before_matching_against_regex() {
1✔
626
        let tmp_dir = tempdir().unwrap();
1✔
627
        let data_path = tmp_dir.path().join("Data");
1✔
628
        let state = state(data_path);
1✔
629

1✔
630
        copy(
1✔
631
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
632
            state.data_path.join("Blank.esm.ghost"),
1✔
633
        )
1✔
634
        .unwrap();
1✔
635

1✔
636
        let function = Function::FileRegex(PathBuf::from("."), regex("^Blank\\.esm$"));
1✔
637

1✔
638
        assert!(function.eval(&state).unwrap());
1✔
639
    }
1✔
640

641
    #[test]
642
    fn function_file_regex_eval_should_check_all_configured_data_paths() {
1✔
643
        let function = Function::FileRegex(PathBuf::from("Data"), regex("Blank\\.esp"));
1✔
644
        let state = state_with_data("./src", vec!["./tests/testing-plugins/Oblivion"], &[], &[]);
1✔
645

1✔
646
        assert!(function.eval(&state).unwrap());
1✔
647
    }
1✔
648

649
    #[test]
650
    fn function_file_size_eval_should_return_false_if_file_does_not_exist() {
1✔
651
        let function = Function::FileSize("missing.esp".into(), 55);
1✔
652
        let state = state_with_data(
1✔
653
            "./src",
1✔
654
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
655
            &[],
1✔
656
            &[],
1✔
657
        );
1✔
658

1✔
659
        assert!(!function.eval(&state).unwrap());
1✔
660
    }
1✔
661

662
    #[test]
663
    fn function_file_size_eval_should_return_false_if_file_size_is_different() {
1✔
664
        let function = Function::FileSize("Blank.esp".into(), 10);
1✔
665
        let state = state_with_data(
1✔
666
            "./src",
1✔
667
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
668
            &[],
1✔
669
            &[],
1✔
670
        );
1✔
671

1✔
672
        assert!(!function.eval(&state).unwrap());
1✔
673
    }
1✔
674

675
    #[test]
676
    fn function_file_size_eval_should_return_true_if_file_size_is_equal() {
1✔
677
        let function = Function::FileSize("Blank.esp".into(), 55);
1✔
678
        let state = state_with_data(
1✔
679
            "./src",
1✔
680
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
681
            &[],
1✔
682
            &[],
1✔
683
        );
1✔
684

1✔
685
        assert!(function.eval(&state).unwrap());
1✔
686
    }
1✔
687

688
    #[test]
689
    fn function_file_size_eval_should_return_true_if_given_a_plugin_that_is_ghosted() {
1✔
690
        let tmp_dir = tempdir().unwrap();
1✔
691
        let data_path = tmp_dir.path().join("Data");
1✔
692
        let state = state(data_path);
1✔
693

1✔
694
        copy(
1✔
695
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
696
            state.data_path.join("Blank.esp.ghost"),
1✔
697
        )
1✔
698
        .unwrap();
1✔
699

1✔
700
        let function = Function::FileSize(PathBuf::from("Blank.esp"), 55);
1✔
701

1✔
702
        assert!(function.eval(&state).unwrap());
1✔
703
    }
1✔
704

705
    #[test]
706
    fn function_file_size_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
707
        let tmp_dir = tempdir().unwrap();
1✔
708
        let data_path = tmp_dir.path().join("Data");
1✔
709
        let state = state(data_path);
1✔
710

1✔
711
        copy(
1✔
712
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
713
            state.data_path.join("Blank.bsa.ghost"),
1✔
714
        )
1✔
715
        .unwrap();
1✔
716

1✔
717
        let function = Function::FileSize(PathBuf::from("Blank.bsa"), 736);
1✔
718

1✔
719
        assert!(!function.eval(&state).unwrap());
1✔
720
    }
1✔
721

722
    #[test]
723
    fn function_readable_eval_should_be_true_for_a_file_that_can_be_opened_as_read_only() {
1✔
724
        let function = Function::Readable(PathBuf::from("Cargo.toml"));
1✔
725
        let state = state(".");
1✔
726

1✔
727
        assert!(function.eval(&state).unwrap());
1✔
728
    }
1✔
729

730
    #[test]
731
    fn function_readable_eval_should_be_true_for_a_folder_that_can_be_read() {
1✔
732
        let function = Function::Readable(PathBuf::from("tests"));
1✔
733
        let state = state(".");
1✔
734

1✔
735
        assert!(function.eval(&state).unwrap());
1✔
736
    }
1✔
737

738
    #[test]
739
    fn function_readable_eval_should_be_false_for_a_file_that_does_not_exist() {
1✔
740
        let function = Function::Readable(PathBuf::from("missing"));
1✔
741
        let state = state(".");
1✔
742

1✔
743
        assert!(!function.eval(&state).unwrap());
1✔
744
    }
1✔
745

746
    #[cfg(windows)]
747
    #[test]
748
    fn function_readable_eval_should_be_false_for_a_file_that_is_not_readable() {
749
        use std::os::windows::fs::OpenOptionsExt;
750

751
        let tmp_dir = tempdir().unwrap();
752
        let data_path = tmp_dir.path().join("Data");
753
        let state = state(data_path);
754

755
        let relative_path = "unreadable";
756
        let file_path = state.data_path.join(relative_path);
757

758
        // Create a file and open it with exclusive access so that the readable
759
        // function eval isn't able to open the file in read-only mode.
760
        let _file = std::fs::OpenOptions::new()
761
            .write(true)
762
            .create(true)
763
            .truncate(false)
764
            .share_mode(0)
765
            .open(&file_path);
766

767
        assert!(file_path.exists());
768

769
        let function = Function::Readable(PathBuf::from(relative_path));
770

771
        assert!(!function.eval(&state).unwrap());
772
    }
773

774
    #[cfg(not(windows))]
775
    #[test]
776
    fn function_readable_eval_should_be_false_for_a_file_that_is_not_readable() {
1✔
777
        let tmp_dir = tempdir().unwrap();
1✔
778
        let data_path = tmp_dir.path().join("Data");
1✔
779
        let state = state(data_path);
1✔
780

1✔
781
        let relative_path = "unreadable";
1✔
782
        let file_path = state.data_path.join(relative_path);
1✔
783

1✔
784
        std::fs::write(&file_path, "").unwrap();
1✔
785
        make_path_unreadable(&file_path);
1✔
786

1✔
787
        assert!(file_path.exists());
1✔
788

789
        let function = Function::Readable(PathBuf::from(relative_path));
1✔
790

1✔
791
        assert!(!function.eval(&state).unwrap());
1✔
792
    }
1✔
793

794
    #[cfg(windows)]
795
    #[test]
796
    fn function_readable_eval_should_be_false_for_a_folder_that_is_not_readable() {
797
        let data_path = Path::new(r"C:\Program Files");
798
        let state = state(data_path);
799

800
        let relative_path = "WindowsApps";
801

802
        // The WindowsApps directory is so locked down that trying to read its
803
        // metadata fails, but its existence can still be observed by iterating
804
        // over its parent directory's entries.
805
        let entry_exists = state
806
            .data_path
807
            .read_dir()
808
            .unwrap()
809
            .flat_map(|res| res.map(|e| e.file_name()).into_iter())
810
            .any(|name| name == relative_path);
811

812
        assert!(entry_exists);
813

814
        let function = Function::Readable(PathBuf::from(relative_path));
815

816
        assert!(!function.eval(&state).unwrap());
817
    }
818

819
    #[cfg(not(windows))]
820
    #[test]
821
    fn function_readable_eval_should_be_false_for_a_folder_that_is_not_readable() {
1✔
822
        let tmp_dir = tempdir().unwrap();
1✔
823
        let data_path = tmp_dir.path().join("Data");
1✔
824
        let state = state(data_path);
1✔
825

1✔
826
        let relative_path = "unreadable";
1✔
827
        let folder_path = state.data_path.join(relative_path);
1✔
828

1✔
829
        std::fs::create_dir(&folder_path).unwrap();
1✔
830
        make_path_unreadable(&folder_path);
1✔
831

1✔
832
        assert!(folder_path.exists());
1✔
833

834
        let function = Function::Readable(PathBuf::from(relative_path));
1✔
835

1✔
836
        assert!(!function.eval(&state).unwrap());
1✔
837
    }
1✔
838

839
    #[test]
840
    fn function_is_executable_should_be_false_for_a_path_that_does_not_exist() {
1✔
841
        let state = state(".");
1✔
842
        let function = Function::IsExecutable("missing".into());
1✔
843

1✔
844
        assert!(!function.eval(&state).unwrap());
1✔
845
    }
1✔
846

847
    #[test]
848
    fn function_is_executable_should_be_false_for_a_directory() {
1✔
849
        let state = state(".");
1✔
850
        let function = Function::IsExecutable("tests".into());
1✔
851

1✔
852
        assert!(!function.eval(&state).unwrap());
1✔
853
    }
1✔
854

855
    #[cfg(windows)]
856
    #[test]
857
    fn function_is_executable_should_be_false_for_a_file_that_cannot_be_read() {
858
        use std::os::windows::fs::OpenOptionsExt;
859

860
        let tmp_dir = tempdir().unwrap();
861
        let data_path = tmp_dir.path().join("Data");
862
        let state = state(data_path);
863

864
        let relative_path = "unreadable";
865
        let file_path = state.data_path.join(relative_path);
866

867
        // Create a file and open it with exclusive access so that the readable
868
        // function eval isn't able to open the file in read-only mode.
869
        let _file = std::fs::OpenOptions::new()
870
            .write(true)
871
            .create(true)
872
            .truncate(false)
873
            .share_mode(0)
874
            .open(&file_path);
875

876
        assert!(file_path.exists());
877

878
        let function = Function::IsExecutable(PathBuf::from(relative_path));
879

880
        assert!(!function.eval(&state).unwrap());
881
    }
882

883
    #[cfg(not(windows))]
884
    #[test]
885
    fn function_is_executable_should_be_false_for_a_file_that_cannot_be_read() {
1✔
886
        let tmp_dir = tempdir().unwrap();
1✔
887
        let data_path = tmp_dir.path().join("Data");
1✔
888
        let state = state(data_path);
1✔
889

1✔
890
        let relative_path = "unreadable";
1✔
891
        let file_path = state.data_path.join(relative_path);
1✔
892

1✔
893
        std::fs::write(&file_path, "").unwrap();
1✔
894
        make_path_unreadable(&file_path);
1✔
895

1✔
896
        assert!(file_path.exists());
1✔
897

898
        let function = Function::IsExecutable(PathBuf::from(relative_path));
1✔
899

1✔
900
        assert!(!function.eval(&state).unwrap());
1✔
901
    }
1✔
902

903
    #[test]
904
    fn function_is_executable_should_be_false_for_a_file_that_is_not_an_executable() {
1✔
905
        let state = state(".");
1✔
906
        let function = Function::IsExecutable("Cargo.toml".into());
1✔
907

1✔
908
        assert!(!function.eval(&state).unwrap());
1✔
909
    }
1✔
910

911
    #[test]
912
    fn function_is_executable_should_be_true_for_a_file_that_is_an_executable() {
1✔
913
        let state = state(".");
1✔
914
        let function = Function::IsExecutable("tests/libloot_win32/loot.dll".into());
1✔
915

1✔
916
        assert!(function.eval(&state).unwrap());
1✔
917
    }
1✔
918

919
    #[test]
920
    fn function_active_path_eval_should_be_true_if_the_path_is_an_active_plugin() {
1✔
921
        let function = Function::ActivePath(PathBuf::from("Blank.esp"));
1✔
922
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
923

1✔
924
        assert!(function.eval(&state).unwrap());
1✔
925
    }
1✔
926

927
    #[test]
928
    fn function_active_path_eval_should_be_case_insensitive() {
1✔
929
        let function = Function::ActivePath(PathBuf::from("Blank.esp"));
1✔
930
        let state = state_with_active_plugins(".", &["blank.esp"]);
1✔
931

1✔
932
        assert!(function.eval(&state).unwrap());
1✔
933
    }
1✔
934

935
    #[test]
936
    fn function_active_path_eval_should_be_false_if_the_path_is_not_an_active_plugin() {
1✔
937
        let function = Function::ActivePath(PathBuf::from("inactive.esp"));
1✔
938
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
939

1✔
940
        assert!(!function.eval(&state).unwrap());
1✔
941
    }
1✔
942

943
    #[test]
944
    fn function_active_regex_eval_should_be_true_if_the_regex_matches_an_active_plugin() {
1✔
945
        let function = Function::ActiveRegex(regex("Blank\\.esp"));
1✔
946
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
947

1✔
948
        assert!(function.eval(&state).unwrap());
1✔
949
    }
1✔
950

951
    #[test]
952
    fn function_active_regex_eval_should_be_false_if_the_regex_does_not_match_an_active_plugin() {
1✔
953
        let function = Function::ActiveRegex(regex("inactive\\.esp"));
1✔
954
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
955

1✔
956
        assert!(!function.eval(&state).unwrap());
1✔
957
    }
1✔
958

959
    #[test]
960
    fn function_is_master_eval_should_be_true_if_the_path_is_a_master_plugin() {
1✔
961
        let function = Function::IsMaster(PathBuf::from("Blank.esm"));
1✔
962
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
963

1✔
964
        assert!(function.eval(&state).unwrap());
1✔
965
    }
1✔
966

967
    #[test]
968
    fn function_is_master_eval_should_be_false_if_the_path_is_an_openmw_master_flagged_plugin() {
1✔
969
        let function = Function::IsMaster(PathBuf::from("Blank.esm"));
1✔
970
        let mut state = state("tests/testing-plugins/Morrowind/Data Files");
1✔
971
        state.game_type = GameType::OpenMW;
1✔
972

1✔
973
        assert!(!function.eval(&state).unwrap());
1✔
974
    }
1✔
975

976
    #[test]
977
    fn function_is_master_eval_should_be_false_if_the_path_does_not_exist() {
1✔
978
        let function = Function::IsMaster(PathBuf::from("missing.esp"));
1✔
979
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
980

1✔
981
        assert!(!function.eval(&state).unwrap());
1✔
982
    }
1✔
983

984
    #[test]
985
    fn function_is_master_eval_should_be_false_if_the_path_is_not_a_plugin() {
1✔
986
        let function = Function::IsMaster(PathBuf::from("Cargo.toml"));
1✔
987
        let state = state(".");
1✔
988

1✔
989
        assert!(!function.eval(&state).unwrap());
1✔
990
    }
1✔
991

992
    #[test]
993
    fn function_is_master_eval_should_be_false_if_the_path_is_a_non_master_plugin() {
1✔
994
        let function = Function::IsMaster(PathBuf::from("Blank.esp"));
1✔
995
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
996

1✔
997
        assert!(!function.eval(&state).unwrap());
1✔
998
    }
1✔
999

1000
    #[test]
1001
    fn function_many_eval_should_be_false_if_no_directory_entries_match() {
1✔
1002
        let function = Function::Many(PathBuf::from("."), regex("missing"));
1✔
1003
        let state = state(".");
1✔
1004

1✔
1005
        assert!(!function.eval(&state).unwrap());
1✔
1006
    }
1✔
1007

1008
    #[test]
1009
    fn function_many_eval_should_be_false_if_the_parent_path_part_is_not_a_directory() {
1✔
1010
        let function = Function::Many(PathBuf::from("missing"), regex("Cargo.*"));
1✔
1011
        let state = state(".");
1✔
1012

1✔
1013
        assert!(!function.eval(&state).unwrap());
1✔
1014
    }
1✔
1015

1016
    #[test]
1017
    fn function_many_eval_should_be_false_if_one_directory_entry_matches() {
1✔
1018
        let function = Function::Many(
1✔
1019
            PathBuf::from("tests/testing-plugins/Oblivion/Data"),
1✔
1020
            regex("Blank\\.esp"),
1✔
1021
        );
1✔
1022
        let state = state(".");
1✔
1023

1✔
1024
        assert!(!function.eval(&state).unwrap());
1✔
1025
    }
1✔
1026

1027
    #[test]
1028
    fn function_many_eval_should_be_true_if_more_than_one_directory_entry_matches() {
1✔
1029
        let function = Function::Many(
1✔
1030
            PathBuf::from("tests/testing-plugins/Oblivion/Data"),
1✔
1031
            regex("Blank.*"),
1✔
1032
        );
1✔
1033
        let state = state(".");
1✔
1034

1✔
1035
        assert!(function.eval(&state).unwrap());
1✔
1036
    }
1✔
1037

1038
    #[test]
1039
    fn function_many_eval_should_trim_ghost_plugin_extension_before_matching_against_regex() {
1✔
1040
        let tmp_dir = tempdir().unwrap();
1✔
1041
        let data_path = tmp_dir.path().join("Data");
1✔
1042
        let state = state(data_path);
1✔
1043

1✔
1044
        copy(
1✔
1045
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1046
            state.data_path.join("Blank.esm.ghost"),
1✔
1047
        )
1✔
1048
        .unwrap();
1✔
1049
        copy(
1✔
1050
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
1051
            state.data_path.join("Blank.esp.ghost"),
1✔
1052
        )
1✔
1053
        .unwrap();
1✔
1054

1✔
1055
        let function = Function::Many(PathBuf::from("."), regex("^Blank\\.es(m|p)$"));
1✔
1056

1✔
1057
        assert!(function.eval(&state).unwrap());
1✔
1058
    }
1✔
1059

1060
    #[test]
1061
    fn function_many_eval_should_check_across_all_configured_data_paths() {
1✔
1062
        let function = Function::Many(PathBuf::from("Data"), regex("Blank\\.esp"));
1✔
1063
        let state = state_with_data(
1✔
1064
            "./tests/testing-plugins/Skyrim",
1✔
1065
            vec!["./tests/testing-plugins/Oblivion"],
1✔
1066
            &[],
1✔
1067
            &[],
1✔
1068
        );
1✔
1069

1✔
1070
        assert!(function.eval(&state).unwrap());
1✔
1071
    }
1✔
1072

1073
    #[test]
1074
    fn function_many_active_eval_should_be_true_if_the_regex_matches_more_than_one_active_plugin() {
1✔
1075
        let function = Function::ManyActive(regex("Blank.*"));
1✔
1076
        let state = state_with_active_plugins(".", &["Blank.esp", "Blank.esm"]);
1✔
1077

1✔
1078
        assert!(function.eval(&state).unwrap());
1✔
1079
    }
1✔
1080

1081
    #[test]
1082
    fn function_many_active_eval_should_be_false_if_one_active_plugin_matches() {
1✔
1083
        let function = Function::ManyActive(regex("Blank\\.esp"));
1✔
1084
        let state = state_with_active_plugins(".", &["Blank.esp", "Blank.esm"]);
1✔
1085

1✔
1086
        assert!(!function.eval(&state).unwrap());
1✔
1087
    }
1✔
1088

1089
    #[test]
1090
    fn function_many_active_eval_should_be_false_if_the_regex_does_not_match_an_active_plugin() {
1✔
1091
        let function = Function::ManyActive(regex("inactive\\.esp"));
1✔
1092
        let state = state_with_active_plugins(".", &["Blank.esp", "Blank.esm"]);
1✔
1093

1✔
1094
        assert!(!function.eval(&state).unwrap());
1✔
1095
    }
1✔
1096

1097
    #[test]
1098
    fn function_checksum_eval_should_be_false_if_the_file_does_not_exist() {
1✔
1099
        let function = Function::Checksum(PathBuf::from("missing"), 0x374E2A6F);
1✔
1100
        let state = state(".");
1✔
1101

1✔
1102
        assert!(!function.eval(&state).unwrap());
1✔
1103
    }
1✔
1104

1105
    #[test]
1106
    fn function_checksum_eval_should_be_false_if_the_file_checksum_does_not_equal_the_given_checksum(
1✔
1107
    ) {
1✔
1108
        let function = Function::Checksum(
1✔
1109
            PathBuf::from("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1110
            0xDEADBEEF,
1✔
1111
        );
1✔
1112
        let state = state(".");
1✔
1113

1✔
1114
        assert!(!function.eval(&state).unwrap());
1✔
1115
    }
1✔
1116

1117
    #[test]
1118
    fn function_checksum_eval_should_be_true_if_the_file_checksum_equals_the_given_checksum() {
1✔
1119
        let function = Function::Checksum(
1✔
1120
            PathBuf::from("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1121
            0x374E2A6F,
1✔
1122
        );
1✔
1123
        let state = state(".");
1✔
1124

1✔
1125
        assert!(function.eval(&state).unwrap());
1✔
1126
    }
1✔
1127

1128
    #[test]
1129
    fn function_checksum_eval_should_support_checking_the_crc_of_a_ghosted_plugin() {
1✔
1130
        let tmp_dir = tempdir().unwrap();
1✔
1131
        let data_path = tmp_dir.path().join("Data");
1✔
1132
        let state = state(data_path);
1✔
1133

1✔
1134
        copy(
1✔
1135
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1136
            state.data_path.join("Blank.esm.ghost"),
1✔
1137
        )
1✔
1138
        .unwrap();
1✔
1139

1✔
1140
        let function = Function::Checksum(PathBuf::from("Blank.esm"), 0x374E2A6F);
1✔
1141

1✔
1142
        assert!(function.eval(&state).unwrap());
1✔
1143
    }
1✔
1144

1145
    #[test]
1146
    fn function_checksum_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
1147
        let tmp_dir = tempdir().unwrap();
1✔
1148
        let data_path = tmp_dir.path().join("Data");
1✔
1149
        let state = state(data_path);
1✔
1150

1✔
1151
        copy(
1✔
1152
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
1153
            state.data_path.join("Blank.bsa.ghost"),
1✔
1154
        )
1✔
1155
        .unwrap();
1✔
1156

1✔
1157
        let function = Function::Checksum(PathBuf::from("Blank.bsa"), 0x22AB79D9);
1✔
1158

1✔
1159
        assert!(!function.eval(&state).unwrap());
1✔
1160
    }
1✔
1161

1162
    #[test]
1163
    fn function_checksum_eval_should_be_false_if_given_a_directory_path() {
1✔
1164
        // The given CRC is the CRC-32 of the directory as calculated by 7-zip.
1✔
1165
        let function = Function::Checksum(PathBuf::from("tests/testing-plugins"), 0xC9CD16C3);
1✔
1166
        let state = state(".");
1✔
1167

1✔
1168
        assert!(!function.eval(&state).unwrap());
1✔
1169
    }
1✔
1170

1171
    #[test]
1172
    fn function_checksum_eval_should_cache_and_use_cached_crcs() {
1✔
1173
        let tmp_dir = tempdir().unwrap();
1✔
1174
        let data_path = tmp_dir.path().join("Data");
1✔
1175
        let state = state(data_path);
1✔
1176

1✔
1177
        copy(
1✔
1178
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1179
            state.data_path.join("Blank.esm"),
1✔
1180
        )
1✔
1181
        .unwrap();
1✔
1182

1✔
1183
        let function = Function::Checksum(PathBuf::from("Blank.esm"), 0x374E2A6F);
1✔
1184

1✔
1185
        assert!(function.eval(&state).unwrap());
1✔
1186

1187
        // Change the CRC of the file to test that the cached value is used.
1188
        copy(
1✔
1189
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
1190
            state.data_path.join("Blank.esm"),
1✔
1191
        )
1✔
1192
        .unwrap();
1✔
1193

1✔
1194
        let function = Function::Checksum(PathBuf::from("Blank.esm"), 0x374E2A6F);
1✔
1195

1✔
1196
        assert!(function.eval(&state).unwrap());
1✔
1197
    }
1✔
1198

1199
    #[test]
1200
    fn function_eval_should_cache_results_and_use_cached_results() {
1✔
1201
        let tmp_dir = tempdir().unwrap();
1✔
1202
        let data_path = tmp_dir.path().join("Data");
1✔
1203
        let state = state(data_path);
1✔
1204

1✔
1205
        copy(Path::new("Cargo.toml"), state.data_path.join("Cargo.toml")).unwrap();
1✔
1206

1✔
1207
        let function = Function::FilePath(PathBuf::from("Cargo.toml"));
1✔
1208

1✔
1209
        assert!(function.eval(&state).unwrap());
1✔
1210

1211
        remove_file(state.data_path.join("Cargo.toml")).unwrap();
1✔
1212

1✔
1213
        assert!(function.eval(&state).unwrap());
1✔
1214
    }
1✔
1215

1216
    #[test]
1217
    fn function_version_eval_should_be_true_if_the_path_does_not_exist_and_comparator_is_ne() {
1✔
1218
        let function =
1✔
1219
            Function::Version("missing".into(), "1.0".into(), ComparisonOperator::NotEqual);
1✔
1220
        let state = state(".");
1✔
1221

1✔
1222
        assert!(function.eval(&state).unwrap());
1✔
1223
    }
1✔
1224

1225
    #[test]
1226
    fn function_version_eval_should_be_true_if_the_path_does_not_exist_and_comparator_is_lt() {
1✔
1227
        let function =
1✔
1228
            Function::Version("missing".into(), "1.0".into(), ComparisonOperator::LessThan);
1✔
1229
        let state = state(".");
1✔
1230

1✔
1231
        assert!(function.eval(&state).unwrap());
1✔
1232
    }
1✔
1233

1234
    #[test]
1235
    fn function_version_eval_should_be_true_if_the_path_does_not_exist_and_comparator_is_lteq() {
1✔
1236
        let function = Function::Version(
1✔
1237
            "missing".into(),
1✔
1238
            "1.0".into(),
1✔
1239
            ComparisonOperator::LessThanOrEqual,
1✔
1240
        );
1✔
1241
        let state = state(".");
1✔
1242

1✔
1243
        assert!(function.eval(&state).unwrap());
1✔
1244
    }
1✔
1245

1246
    #[test]
1247
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_eq() {
1✔
1248
        let function = Function::Version("missing".into(), "1.0".into(), ComparisonOperator::Equal);
1✔
1249
        let state = state(".");
1✔
1250

1✔
1251
        assert!(!function.eval(&state).unwrap());
1✔
1252
    }
1✔
1253

1254
    #[test]
1255
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_gt() {
1✔
1256
        let function = Function::Version(
1✔
1257
            "missing".into(),
1✔
1258
            "1.0".into(),
1✔
1259
            ComparisonOperator::GreaterThan,
1✔
1260
        );
1✔
1261
        let state = state(".");
1✔
1262

1✔
1263
        assert!(!function.eval(&state).unwrap());
1✔
1264
    }
1✔
1265

1266
    #[test]
1267
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_gteq() {
1✔
1268
        let function = Function::Version(
1✔
1269
            "missing".into(),
1✔
1270
            "1.0".into(),
1✔
1271
            ComparisonOperator::GreaterThanOrEqual,
1✔
1272
        );
1✔
1273
        let state = state(".");
1✔
1274

1✔
1275
        assert!(!function.eval(&state).unwrap());
1✔
1276
    }
1✔
1277

1278
    #[test]
1279
    fn function_version_eval_should_be_true_if_the_path_is_not_a_file_and_comparator_is_ne() {
1✔
1280
        let function =
1✔
1281
            Function::Version("tests".into(), "1.0".into(), ComparisonOperator::NotEqual);
1✔
1282
        let state = state(".");
1✔
1283

1✔
1284
        assert!(function.eval(&state).unwrap());
1✔
1285
    }
1✔
1286

1287
    #[test]
1288
    fn function_version_eval_should_be_true_if_the_path_is_not_a_file_and_comparator_is_lt() {
1✔
1289
        let function =
1✔
1290
            Function::Version("tests".into(), "1.0".into(), ComparisonOperator::LessThan);
1✔
1291
        let state = state(".");
1✔
1292

1✔
1293
        assert!(function.eval(&state).unwrap());
1✔
1294
    }
1✔
1295

1296
    #[test]
1297
    fn function_version_eval_should_be_true_if_the_path_is_not_a_file_and_comparator_is_lteq() {
1✔
1298
        let function = Function::Version(
1✔
1299
            "tests".into(),
1✔
1300
            "1.0".into(),
1✔
1301
            ComparisonOperator::LessThanOrEqual,
1✔
1302
        );
1✔
1303
        let state = state(".");
1✔
1304

1✔
1305
        assert!(function.eval(&state).unwrap());
1✔
1306
    }
1✔
1307

1308
    #[test]
1309
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_eq() {
1✔
1310
        let function = Function::Version("tests".into(), "1.0".into(), ComparisonOperator::Equal);
1✔
1311
        let state = state(".");
1✔
1312

1✔
1313
        assert!(!function.eval(&state).unwrap());
1✔
1314
    }
1✔
1315

1316
    #[test]
1317
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_gt() {
1✔
1318
        let function = Function::Version(
1✔
1319
            "tests".into(),
1✔
1320
            "1.0".into(),
1✔
1321
            ComparisonOperator::GreaterThan,
1✔
1322
        );
1✔
1323
        let state = state(".");
1✔
1324

1✔
1325
        assert!(!function.eval(&state).unwrap());
1✔
1326
    }
1✔
1327

1328
    #[test]
1329
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_gteq() {
1✔
1330
        let function = Function::Version(
1✔
1331
            "tests".into(),
1✔
1332
            "1.0".into(),
1✔
1333
            ComparisonOperator::GreaterThanOrEqual,
1✔
1334
        );
1✔
1335
        let state = state(".");
1✔
1336

1✔
1337
        assert!(!function.eval(&state).unwrap());
1✔
1338
    }
1✔
1339

1340
    #[test]
1341
    fn function_version_eval_should_treat_a_plugin_with_no_cached_version_as_if_it_did_not_exist() {
1✔
1342
        use self::ComparisonOperator::*;
1343

1344
        let plugin = PathBuf::from("Blank.esm");
1✔
1345
        let version = String::from("1.0");
1✔
1346
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
1347

1✔
1348
        let function = Function::Version(plugin.clone(), version.clone(), NotEqual);
1✔
1349
        assert!(function.eval(&state).unwrap());
1✔
1350
        let function = Function::Version(plugin.clone(), version.clone(), LessThan);
1✔
1351
        assert!(function.eval(&state).unwrap());
1✔
1352
        let function = Function::Version(plugin.clone(), version.clone(), LessThanOrEqual);
1✔
1353
        assert!(function.eval(&state).unwrap());
1✔
1354
        let function = Function::Version(plugin.clone(), version.clone(), Equal);
1✔
1355
        assert!(!function.eval(&state).unwrap());
1✔
1356
        let function = Function::Version(plugin.clone(), version.clone(), GreaterThan);
1✔
1357
        assert!(!function.eval(&state).unwrap());
1✔
1358
        let function = Function::Version(plugin.clone(), version.clone(), GreaterThanOrEqual);
1✔
1359
        assert!(!function.eval(&state).unwrap());
1✔
1360
    }
1✔
1361

1362
    #[test]
1363
    fn function_version_eval_should_be_false_if_versions_are_not_equal_and_comparator_is_eq() {
1✔
1364
        let function = Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::Equal);
1✔
1365
        let state =
1✔
1366
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "1")]);
1✔
1367

1✔
1368
        assert!(!function.eval(&state).unwrap());
1✔
1369
    }
1✔
1370

1371
    #[test]
1372
    fn function_version_eval_should_be_true_if_versions_are_equal_and_comparator_is_eq() {
1✔
1373
        let function = Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::Equal);
1✔
1374
        let state =
1✔
1375
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1376

1✔
1377
        assert!(function.eval(&state).unwrap());
1✔
1378
    }
1✔
1379

1380
    #[test]
1381
    fn function_version_eval_should_be_false_if_versions_are_equal_and_comparator_is_ne() {
1✔
1382
        let function =
1✔
1383
            Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::NotEqual);
1✔
1384
        let state =
1✔
1385
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1386

1✔
1387
        assert!(!function.eval(&state).unwrap());
1✔
1388
    }
1✔
1389

1390
    #[test]
1391
    fn function_version_eval_should_be_true_if_versions_are_not_equal_and_comparator_is_ne() {
1✔
1392
        let function =
1✔
1393
            Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::NotEqual);
1✔
1394
        let state =
1✔
1395
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "1")]);
1✔
1396

1✔
1397
        assert!(function.eval(&state).unwrap());
1✔
1398
    }
1✔
1399

1400
    #[test]
1401
    fn function_version_eval_should_be_false_if_actual_version_is_eq_and_comparator_is_lt() {
1✔
1402
        let function =
1✔
1403
            Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::LessThan);
1✔
1404
        let state =
1✔
1405
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1406

1✔
1407
        assert!(!function.eval(&state).unwrap());
1✔
1408
    }
1✔
1409

1410
    #[test]
1411
    fn function_version_eval_should_be_false_if_actual_version_is_gt_and_comparator_is_lt() {
1✔
1412
        let function =
1✔
1413
            Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::LessThan);
1✔
1414
        let state =
1✔
1415
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1416

1✔
1417
        assert!(!function.eval(&state).unwrap());
1✔
1418
    }
1✔
1419

1420
    #[test]
1421
    fn function_version_eval_should_be_true_if_actual_version_is_lt_and_comparator_is_lt() {
1✔
1422
        let function =
1✔
1423
            Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::NotEqual);
1✔
1424
        let state =
1✔
1425
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "1")]);
1✔
1426

1✔
1427
        assert!(function.eval(&state).unwrap());
1✔
1428
    }
1✔
1429

1430
    #[test]
1431
    fn function_version_eval_should_be_false_if_actual_version_is_eq_and_comparator_is_gt() {
1✔
1432
        let function = Function::Version(
1✔
1433
            "Blank.esm".into(),
1✔
1434
            "5".into(),
1✔
1435
            ComparisonOperator::GreaterThan,
1✔
1436
        );
1✔
1437
        let state =
1✔
1438
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1439

1✔
1440
        assert!(!function.eval(&state).unwrap());
1✔
1441
    }
1✔
1442

1443
    #[test]
1444
    fn function_version_eval_should_be_false_if_actual_version_is_lt_and_comparator_is_gt() {
1✔
1445
        let function = Function::Version(
1✔
1446
            "Blank.esm".into(),
1✔
1447
            "5".into(),
1✔
1448
            ComparisonOperator::GreaterThan,
1✔
1449
        );
1✔
1450
        let state =
1✔
1451
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1452

1✔
1453
        assert!(!function.eval(&state).unwrap());
1✔
1454
    }
1✔
1455

1456
    #[test]
1457
    fn function_version_eval_should_be_true_if_actual_version_is_gt_and_comparator_is_gt() {
1✔
1458
        let function = Function::Version(
1✔
1459
            "Blank.esm".into(),
1✔
1460
            "5".into(),
1✔
1461
            ComparisonOperator::GreaterThan,
1✔
1462
        );
1✔
1463
        let state =
1✔
1464
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1465

1✔
1466
        assert!(function.eval(&state).unwrap());
1✔
1467
    }
1✔
1468

1469
    #[test]
1470
    fn function_version_eval_should_be_false_if_actual_version_is_gt_and_comparator_is_lteq() {
1✔
1471
        let function = Function::Version(
1✔
1472
            "Blank.esm".into(),
1✔
1473
            "5".into(),
1✔
1474
            ComparisonOperator::LessThanOrEqual,
1✔
1475
        );
1✔
1476
        let state =
1✔
1477
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1478

1✔
1479
        assert!(!function.eval(&state).unwrap());
1✔
1480
    }
1✔
1481

1482
    #[test]
1483
    fn function_version_eval_should_be_true_if_actual_version_is_eq_and_comparator_is_lteq() {
1✔
1484
        let function = Function::Version(
1✔
1485
            "Blank.esm".into(),
1✔
1486
            "5".into(),
1✔
1487
            ComparisonOperator::LessThanOrEqual,
1✔
1488
        );
1✔
1489
        let state =
1✔
1490
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1491

1✔
1492
        assert!(function.eval(&state).unwrap());
1✔
1493
    }
1✔
1494

1495
    #[test]
1496
    fn function_version_eval_should_be_true_if_actual_version_is_lt_and_comparator_is_lteq() {
1✔
1497
        let function = Function::Version(
1✔
1498
            "Blank.esm".into(),
1✔
1499
            "5".into(),
1✔
1500
            ComparisonOperator::LessThanOrEqual,
1✔
1501
        );
1✔
1502
        let state =
1✔
1503
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1504

1✔
1505
        assert!(function.eval(&state).unwrap());
1✔
1506
    }
1✔
1507

1508
    #[test]
1509
    fn function_version_eval_should_be_false_if_actual_version_is_lt_and_comparator_is_gteq() {
1✔
1510
        let function = Function::Version(
1✔
1511
            "Blank.esm".into(),
1✔
1512
            "5".into(),
1✔
1513
            ComparisonOperator::GreaterThanOrEqual,
1✔
1514
        );
1✔
1515
        let state =
1✔
1516
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1517

1✔
1518
        assert!(!function.eval(&state).unwrap());
1✔
1519
    }
1✔
1520

1521
    #[test]
1522
    fn function_version_eval_should_be_true_if_actual_version_is_eq_and_comparator_is_gteq() {
1✔
1523
        let function = Function::Version(
1✔
1524
            "Blank.esm".into(),
1✔
1525
            "5".into(),
1✔
1526
            ComparisonOperator::GreaterThanOrEqual,
1✔
1527
        );
1✔
1528
        let state =
1✔
1529
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1530

1✔
1531
        assert!(function.eval(&state).unwrap());
1✔
1532
    }
1✔
1533

1534
    #[test]
1535
    fn function_version_eval_should_be_true_if_actual_version_is_gt_and_comparator_is_gteq() {
1✔
1536
        let function = Function::Version(
1✔
1537
            "Blank.esm".into(),
1✔
1538
            "5".into(),
1✔
1539
            ComparisonOperator::GreaterThanOrEqual,
1✔
1540
        );
1✔
1541
        let state =
1✔
1542
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1543

1✔
1544
        assert!(function.eval(&state).unwrap());
1✔
1545
    }
1✔
1546

1547
    #[test]
1548
    fn function_version_eval_should_read_executable_file_version() {
1✔
1549
        let function = Function::Version(
1✔
1550
            "loot.dll".into(),
1✔
1551
            "0.18.2.0".into(),
1✔
1552
            ComparisonOperator::Equal,
1✔
1553
        );
1✔
1554
        let state = state("tests/libloot_win32");
1✔
1555

1✔
1556
        assert!(function.eval(&state).unwrap());
1✔
1557
    }
1✔
1558

1559
    #[test]
1560
    fn function_product_version_eval_should_read_executable_product_version() {
1✔
1561
        let function = Function::ProductVersion(
1✔
1562
            "loot.dll".into(),
1✔
1563
            "0.18.2".into(),
1✔
1564
            ComparisonOperator::Equal,
1✔
1565
        );
1✔
1566
        let state = state("tests/libloot_win32");
1✔
1567

1✔
1568
        assert!(function.eval(&state).unwrap());
1✔
1569
    }
1✔
1570

1571
    #[test]
1572
    fn get_product_version_should_return_ok_none_if_the_path_does_not_exist() {
1✔
1573
        assert!(get_product_version(Path::new("missing")).unwrap().is_none());
1✔
1574
    }
1✔
1575

1576
    #[test]
1577
    fn get_product_version_should_return_ok_none_if_the_path_is_not_a_file() {
1✔
1578
        assert!(get_product_version(Path::new("tests")).unwrap().is_none());
1✔
1579
    }
1✔
1580

1581
    #[test]
1582
    fn get_product_version_should_return_ok_some_if_the_path_is_an_executable() {
1✔
1583
        let version = get_product_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
1584
            .unwrap()
1✔
1585
            .unwrap();
1✔
1586

1✔
1587
        assert_eq!(Version::from("0.18.2"), version);
1✔
1588
    }
1✔
1589

1590
    #[test]
1591
    fn get_product_version_should_error_if_the_path_is_not_an_executable() {
1✔
1592
        assert!(get_product_version(Path::new("Cargo.toml")).is_err());
1✔
1593
    }
1✔
1594

1595
    #[test]
1596
    fn function_filename_version_eval_should_be_false_if_no_matching_filenames_exist() {
1✔
1597
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1598

1✔
1599
        let function = Function::FilenameVersion(
1✔
1600
            "".into(),
1✔
1601
            regex("Blank (A+).esm"),
1✔
1602
            "5".into(),
1✔
1603
            ComparisonOperator::Equal,
1✔
1604
        );
1✔
1605

1✔
1606
        assert!(!function.eval(&state).unwrap());
1✔
1607

1608
        let function = Function::FilenameVersion(
1✔
1609
            "".into(),
1✔
1610
            regex("Blank (A+).esm"),
1✔
1611
            "5".into(),
1✔
1612
            ComparisonOperator::NotEqual,
1✔
1613
        );
1✔
1614

1✔
1615
        assert!(!function.eval(&state).unwrap());
1✔
1616

1617
        let function = Function::FilenameVersion(
1✔
1618
            "".into(),
1✔
1619
            regex("Blank (A+).esm"),
1✔
1620
            "5".into(),
1✔
1621
            ComparisonOperator::LessThan,
1✔
1622
        );
1✔
1623

1✔
1624
        assert!(!function.eval(&state).unwrap());
1✔
1625

1626
        let function = Function::FilenameVersion(
1✔
1627
            "".into(),
1✔
1628
            regex("Blank (A+).esm"),
1✔
1629
            "5".into(),
1✔
1630
            ComparisonOperator::GreaterThan,
1✔
1631
        );
1✔
1632

1✔
1633
        assert!(!function.eval(&state).unwrap());
1✔
1634

1635
        let function = Function::FilenameVersion(
1✔
1636
            "".into(),
1✔
1637
            regex("Blank (A+).esm"),
1✔
1638
            "5".into(),
1✔
1639
            ComparisonOperator::LessThanOrEqual,
1✔
1640
        );
1✔
1641

1✔
1642
        assert!(!function.eval(&state).unwrap());
1✔
1643

1644
        let function = Function::FilenameVersion(
1✔
1645
            "".into(),
1✔
1646
            regex("Blank (A+).esm"),
1✔
1647
            "5".into(),
1✔
1648
            ComparisonOperator::GreaterThanOrEqual,
1✔
1649
        );
1✔
1650

1✔
1651
        assert!(!function.eval(&state).unwrap());
1✔
1652
    }
1✔
1653

1654
    #[test]
1655
    fn function_filename_version_eval_should_be_false_if_filenames_matched_but_no_version_was_captured(
1✔
1656
    ) {
1✔
1657
        // This shouldn't happen in practice because parsing validates that there is one explicit capturing group in the regex.
1✔
1658
        let function = Function::FilenameVersion(
1✔
1659
            "".into(),
1✔
1660
            regex("Blank .+.esm"),
1✔
1661
            "5".into(),
1✔
1662
            ComparisonOperator::GreaterThanOrEqual,
1✔
1663
        );
1✔
1664
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1665

1✔
1666
        assert!(!function.eval(&state).unwrap());
1✔
1667
    }
1✔
1668

1669
    #[test]
1670
    fn function_filename_version_eval_should_be_true_if_the_captured_version_is_eq_and_operator_is_eq(
1✔
1671
    ) {
1✔
1672
        let tmp_dir = tempdir().unwrap();
1✔
1673
        let data_path = tmp_dir.path().join("Data");
1✔
1674
        let state = state(data_path);
1✔
1675

1✔
1676
        copy(
1✔
1677
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1678
            state.data_path.join("Blank 5.esm"),
1✔
1679
        )
1✔
1680
        .unwrap();
1✔
1681

1✔
1682
        let function = Function::FilenameVersion(
1✔
1683
            "".into(),
1✔
1684
            regex("Blank (\\d).esm"),
1✔
1685
            "5".into(),
1✔
1686
            ComparisonOperator::Equal,
1✔
1687
        );
1✔
1688

1✔
1689
        assert!(function.eval(&state).unwrap());
1✔
1690
    }
1✔
1691

1692
    #[test]
1693
    fn function_filename_version_eval_should_be_true_if_the_captured_version_is_not_equal_and_operator_is_not_equal(
1✔
1694
    ) {
1✔
1695
        let tmp_dir = tempdir().unwrap();
1✔
1696
        let data_path = tmp_dir.path().join("Data");
1✔
1697
        let state = state(data_path);
1✔
1698

1✔
1699
        copy(
1✔
1700
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1701
            state.data_path.join("Blank 4.esm"),
1✔
1702
        )
1✔
1703
        .unwrap();
1✔
1704

1✔
1705
        let function = Function::FilenameVersion(
1✔
1706
            "".into(),
1✔
1707
            regex("Blank (\\d).esm"),
1✔
1708
            "5".into(),
1✔
1709
            ComparisonOperator::NotEqual,
1✔
1710
        );
1✔
1711

1✔
1712
        assert!(function.eval(&state).unwrap());
1✔
1713
    }
1✔
1714

1715
    #[test]
1716
    fn function_description_contains_eval_should_return_false_if_the_file_does_not_exist() {
1✔
1717
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1718

1✔
1719
        let function = Function::DescriptionContains("missing.esp".into(), regex("€ƒ."));
1✔
1720

1✔
1721
        assert!(!function.eval(&state).unwrap());
1✔
1722
    }
1✔
1723

1724
    #[test]
1725
    fn function_description_contains_eval_should_return_false_if_the_file_is_not_a_plugin() {
1✔
1726
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1727

1✔
1728
        let function = Function::DescriptionContains("Blank.bsa".into(), regex("€ƒ."));
1✔
1729

1✔
1730
        assert!(!function.eval(&state).unwrap());
1✔
1731
    }
1✔
1732

1733
    #[test]
1734
    fn function_description_contains_eval_should_return_false_if_the_plugin_has_no_description() {
1✔
1735
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1736

1✔
1737
        let function = Function::DescriptionContains("Blank - Different.esm".into(), regex("€ƒ."));
1✔
1738

1✔
1739
        assert!(!function.eval(&state).unwrap());
1✔
1740
    }
1✔
1741

1742
    #[test]
1743
    fn function_description_contains_eval_should_return_false_if_the_plugin_description_does_not_match(
1✔
1744
    ) {
1✔
1745
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1746

1✔
1747
        let function = Function::DescriptionContains("Blank.esm".into(), regex("€ƒ."));
1✔
1748

1✔
1749
        assert!(!function.eval(&state).unwrap());
1✔
1750
    }
1✔
1751

1752
    #[test]
1753
    fn function_description_contains_eval_should_return_true_if_the_plugin_description_contains_a_match(
1✔
1754
    ) {
1✔
1755
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1756

1✔
1757
        let function = Function::DescriptionContains("Blank.esp".into(), regex("ƒ"));
1✔
1758

1✔
1759
        assert!(function.eval(&state).unwrap());
1✔
1760
    }
1✔
1761
}
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