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

loot / loot-condition-interpreter / 13353612957

16 Feb 2025 09:13AM UTC coverage: 91.413% (+0.2%) from 91.254%
13353612957

push

github

Ortham
Update versions and changelog for v5.1.0

4929 of 5392 relevant lines covered (91.41%)

15.8 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

210
    let calculated_crc = hasher.finalize();
4✔
211
    if let Ok(mut writer) = state.crc_cache.write() {
4✔
212
        if let Some(key) = lowercase(file_path) {
4✔
213
            writer.insert(key, calculated_crc);
4✔
214
        }
4✔
215
    }
×
216

217
    Ok(calculated_crc == crc)
4✔
218
}
8✔
219

220
fn lowercase_filename(path: &Path) -> Option<String> {
23✔
221
    path.file_name()
23✔
222
        .and_then(OsStr::to_str)
23✔
223
        .map(str::to_lowercase)
23✔
224
}
23✔
225

226
fn get_version(state: &State, file_path: &Path) -> Result<Option<Version>, Error> {
35✔
227
    if !file_path.is_file() {
35✔
228
        return Ok(None);
12✔
229
    }
23✔
230

231
    if let Some(key) = lowercase_filename(file_path) {
23✔
232
        if let Some(version) = state.plugin_versions.get(&key) {
23✔
233
            return Ok(Some(Version::from(version.as_str())));
16✔
234
        }
7✔
235
    }
×
236

237
    if has_plugin_file_extension(state.game_type, file_path) {
7✔
238
        Ok(None)
6✔
239
    } else {
240
        Version::read_file_version(file_path)
1✔
241
    }
242
}
35✔
243

244
fn get_product_version(file_path: &Path) -> Result<Option<Version>, Error> {
5✔
245
    if file_path.is_file() {
5✔
246
        Version::read_product_version(file_path)
3✔
247
    } else {
248
        Ok(None)
2✔
249
    }
250
}
5✔
251

252
fn compare_versions(
20✔
253
    actual_version: Version,
20✔
254
    comparator: ComparisonOperator,
20✔
255
    given_version: &str,
20✔
256
) -> bool {
20✔
257
    let given_version = Version::from(given_version);
20✔
258

20✔
259
    match comparator {
20✔
260
        ComparisonOperator::Equal => actual_version == given_version,
5✔
261
        ComparisonOperator::NotEqual => actual_version != given_version,
4✔
262
        ComparisonOperator::LessThan => actual_version < given_version,
2✔
263
        ComparisonOperator::GreaterThan => actual_version > given_version,
3✔
264
        ComparisonOperator::LessThanOrEqual => actual_version <= given_version,
3✔
265
        ComparisonOperator::GreaterThanOrEqual => actual_version >= given_version,
3✔
266
    }
267
}
20✔
268

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

289
    Ok(compare_versions(actual_version, comparator, given_version))
18✔
290
}
36✔
291

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

309
    evaluate_dir_entries(state, parent_path, evaluator)
9✔
310
}
9✔
311

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

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

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

353
        if self.is_slow() {
114✔
354
            if let Ok(function_result) = result {
98✔
355
                if let Ok(mut writer) = state.condition_cache.write() {
98✔
356
                    writer.insert(self.clone(), function_result);
98✔
357
                }
98✔
358
            }
×
359
        }
16✔
360

361
        result
114✔
362
    }
117✔
363

364
    /// Some functions are faster to evaluate than to look their result up in
365
    /// the cache, as the data they operate on are already cached separately and
366
    /// the operation is simple.
367
    fn is_slow(&self) -> bool {
231✔
368
        use Function::*;
369
        !matches!(
199✔
370
            self,
231✔
371
            ActivePath(_) | ActiveRegex(_) | ManyActive(_) | Checksum(_, _)
372
        )
373
    }
231✔
374
}
375

376
#[cfg(test)]
377
mod tests {
378
    use super::*;
379

380
    use std::fs::{copy, create_dir, remove_file};
381
    use std::path::PathBuf;
382
    use std::sync::RwLock;
383

384
    use regex::RegexBuilder;
385
    use tempfile::tempdir;
386

387
    use crate::GameType;
388

389
    fn state<T: Into<PathBuf>>(data_path: T) -> State {
55✔
390
        state_with_active_plugins(data_path, &[])
55✔
391
    }
55✔
392

393
    fn state_with_active_plugins<T: Into<PathBuf>>(data_path: T, active_plugins: &[&str]) -> State {
63✔
394
        state_with_data(data_path, Vec::default(), active_plugins, &[])
63✔
395
    }
63✔
396

397
    fn state_with_versions<T: Into<PathBuf>>(
23✔
398
        data_path: T,
23✔
399
        plugin_versions: &[(&str, &str)],
23✔
400
    ) -> State {
23✔
401
        state_with_data(data_path, Vec::default(), &[], plugin_versions)
23✔
402
    }
23✔
403

404
    fn state_with_data<T: Into<PathBuf>>(
93✔
405
        data_path: T,
93✔
406
        additional_data_paths: Vec<T>,
93✔
407
        active_plugins: &[&str],
93✔
408
        plugin_versions: &[(&str, &str)],
93✔
409
    ) -> State {
93✔
410
        let data_path = data_path.into();
93✔
411
        if !data_path.exists() {
93✔
412
            create_dir(&data_path).unwrap();
15✔
413
        }
78✔
414

415
        let additional_data_paths = additional_data_paths
93✔
416
            .into_iter()
93✔
417
            .map(|data_path| {
93✔
418
                let data_path: PathBuf = data_path.into();
9✔
419
                if !data_path.exists() {
9✔
420
                    create_dir(&data_path).unwrap();
×
421
                }
9✔
422
                data_path
9✔
423
            })
93✔
424
            .collect();
93✔
425

93✔
426
        State {
93✔
427
            game_type: GameType::Oblivion,
93✔
428
            data_path,
93✔
429
            additional_data_paths,
93✔
430
            active_plugins: active_plugins.iter().map(|s| s.to_lowercase()).collect(),
93✔
431
            crc_cache: RwLock::default(),
93✔
432
            plugin_versions: plugin_versions
93✔
433
                .iter()
93✔
434
                .map(|(p, v)| (p.to_lowercase(), v.to_string()))
93✔
435
                .collect(),
93✔
436
            condition_cache: RwLock::default(),
93✔
437
        }
93✔
438
    }
93✔
439

440
    fn regex(string: &str) -> Regex {
30✔
441
        RegexBuilder::new(string)
30✔
442
            .case_insensitive(true)
30✔
443
            .build()
30✔
444
            .unwrap()
30✔
445
    }
30✔
446

447
    #[cfg(not(windows))]
448
    fn make_path_unreadable(path: &Path) {
3✔
449
        use std::os::unix::fs::PermissionsExt;
450

451
        let mut permissions = std::fs::metadata(&path).unwrap().permissions();
3✔
452
        permissions.set_mode(0o200);
3✔
453
        std::fs::set_permissions(&path, permissions).unwrap();
3✔
454
    }
3✔
455

456
    #[test]
457
    fn evaluate_dir_entries_should_check_additional_paths_in_order_then_data_path() {
1✔
458
        let state = state_with_data(
1✔
459
            "./tests/testing-plugins/SkyrimSE",
1✔
460
            vec![
1✔
461
                "./tests/testing-plugins/Oblivion",
1✔
462
                "./tests/testing-plugins/Skyrim",
1✔
463
            ],
1✔
464
            &[],
1✔
465
            &[],
1✔
466
        );
1✔
467

1✔
468
        let mut paths = Vec::new();
1✔
469
        let evaluator = |entry: DirEntry| {
36✔
470
            if entry.file_name() == "Blank.esp" {
36✔
471
                paths.push(
3✔
472
                    entry
3✔
473
                        .path()
3✔
474
                        .parent()
3✔
475
                        .unwrap()
3✔
476
                        .parent()
3✔
477
                        .unwrap()
3✔
478
                        .to_path_buf(),
3✔
479
                );
3✔
480
            }
33✔
481
            false
36✔
482
        };
36✔
483
        let result = evaluate_dir_entries(&state, Path::new("Data"), evaluator).unwrap();
1✔
484

1✔
485
        assert!(!result);
1✔
486
        assert_eq!(
1✔
487
            vec![
1✔
488
                state.additional_data_paths[0].clone(),
1✔
489
                state.additional_data_paths[1].clone(),
1✔
490
                state.data_path,
1✔
491
            ],
1✔
492
            paths
1✔
493
        );
1✔
494
    }
1✔
495

496
    #[test]
497
    fn evaluate_dir_entries_should_check_additional_paths_in_reverse_order_then_data_path_for_openmw(
1✔
498
    ) {
1✔
499
        let mut state = state_with_data(
1✔
500
            "./tests/testing-plugins/SkyrimSE",
1✔
501
            vec![
1✔
502
                "./tests/testing-plugins/Oblivion",
1✔
503
                "./tests/testing-plugins/Skyrim",
1✔
504
            ],
1✔
505
            &[],
1✔
506
            &[],
1✔
507
        );
1✔
508
        state.game_type = GameType::OpenMW;
1✔
509

1✔
510
        let mut paths = Vec::new();
1✔
511
        let evaluator = |entry: DirEntry| {
36✔
512
            if entry.file_name() == "Blank.esp" {
36✔
513
                paths.push(
3✔
514
                    entry
3✔
515
                        .path()
3✔
516
                        .parent()
3✔
517
                        .unwrap()
3✔
518
                        .parent()
3✔
519
                        .unwrap()
3✔
520
                        .to_path_buf(),
3✔
521
                );
3✔
522
            }
33✔
523
            false
36✔
524
        };
36✔
525
        let result = evaluate_dir_entries(&state, Path::new("Data"), evaluator).unwrap();
1✔
526

1✔
527
        assert!(!result);
1✔
528
        assert_eq!(
1✔
529
            vec![
1✔
530
                state.additional_data_paths[1].clone(),
1✔
531
                state.additional_data_paths[0].clone(),
1✔
532
                state.data_path,
1✔
533
            ],
1✔
534
            paths
1✔
535
        );
1✔
536
    }
1✔
537

538
    #[test]
539
    fn function_file_path_eval_should_return_true_if_the_file_exists_relative_to_the_data_path() {
1✔
540
        let function = Function::FilePath(PathBuf::from("Cargo.toml"));
1✔
541
        let state = state(".");
1✔
542

1✔
543
        assert!(function.eval(&state).unwrap());
1✔
544
    }
1✔
545

546
    #[test]
547
    fn function_file_path_eval_should_return_true_if_given_a_plugin_that_is_ghosted() {
1✔
548
        let tmp_dir = tempdir().unwrap();
1✔
549
        let data_path = tmp_dir.path().join("Data");
1✔
550
        let state = state(data_path);
1✔
551

1✔
552
        copy(
1✔
553
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
554
            state.data_path.join("Blank.esp.ghost"),
1✔
555
        )
1✔
556
        .unwrap();
1✔
557

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

1✔
560
        assert!(function.eval(&state).unwrap());
1✔
561
    }
1✔
562

563
    #[test]
564
    fn function_file_path_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
565
        let tmp_dir = tempdir().unwrap();
1✔
566
        let data_path = tmp_dir.path().join("Data");
1✔
567
        let state = state(data_path);
1✔
568

1✔
569
        copy(
1✔
570
            Path::new("Cargo.toml"),
1✔
571
            state.data_path.join("Cargo.toml.ghost"),
1✔
572
        )
1✔
573
        .unwrap();
1✔
574

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

1✔
577
        assert!(!function.eval(&state).unwrap());
1✔
578
    }
1✔
579

580
    #[test]
581
    fn function_file_path_eval_should_return_false_if_the_file_does_not_exist() {
1✔
582
        let function = Function::FilePath(PathBuf::from("missing"));
1✔
583
        let state = state(".");
1✔
584

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

588
    #[test]
589
    fn function_file_regex_eval_should_be_false_if_no_directory_entries_match() {
1✔
590
        let function = Function::FileRegex(PathBuf::from("."), regex("missing"));
1✔
591
        let state = state(".");
1✔
592

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

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

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

604
    #[test]
605
    fn function_file_regex_eval_should_be_true_if_a_directory_entry_matches() {
1✔
606
        let function = Function::FileRegex(
1✔
607
            PathBuf::from("tests/testing-plugins/Oblivion/Data"),
1✔
608
            regex("Blank\\.esp"),
1✔
609
        );
1✔
610
        let state = state(".");
1✔
611

1✔
612
        assert!(function.eval(&state).unwrap());
1✔
613
    }
1✔
614

615
    #[test]
616
    fn function_file_regex_eval_should_trim_ghost_plugin_extension_before_matching_against_regex() {
1✔
617
        let tmp_dir = tempdir().unwrap();
1✔
618
        let data_path = tmp_dir.path().join("Data");
1✔
619
        let state = state(data_path);
1✔
620

1✔
621
        copy(
1✔
622
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
623
            state.data_path.join("Blank.esm.ghost"),
1✔
624
        )
1✔
625
        .unwrap();
1✔
626

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

1✔
629
        assert!(function.eval(&state).unwrap());
1✔
630
    }
1✔
631

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

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

640
    #[test]
641
    fn function_file_size_eval_should_return_false_if_file_does_not_exist() {
1✔
642
        let function = Function::FileSize("missing.esp".into(), 55);
1✔
643
        let state = state_with_data(
1✔
644
            "./src",
1✔
645
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
646
            &[],
1✔
647
            &[],
1✔
648
        );
1✔
649

1✔
650
        assert!(!function.eval(&state).unwrap());
1✔
651
    }
1✔
652

653
    #[test]
654
    fn function_file_size_eval_should_return_false_if_file_size_is_different() {
1✔
655
        let function = Function::FileSize("Blank.esp".into(), 10);
1✔
656
        let state = state_with_data(
1✔
657
            "./src",
1✔
658
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
659
            &[],
1✔
660
            &[],
1✔
661
        );
1✔
662

1✔
663
        assert!(!function.eval(&state).unwrap());
1✔
664
    }
1✔
665

666
    #[test]
667
    fn function_file_size_eval_should_return_true_if_file_size_is_equal() {
1✔
668
        let function = Function::FileSize("Blank.esp".into(), 55);
1✔
669
        let state = state_with_data(
1✔
670
            "./src",
1✔
671
            vec!["./tests/testing-plugins/Oblivion/Data"],
1✔
672
            &[],
1✔
673
            &[],
1✔
674
        );
1✔
675

1✔
676
        assert!(function.eval(&state).unwrap());
1✔
677
    }
1✔
678

679
    #[test]
680
    fn function_file_size_eval_should_return_true_if_given_a_plugin_that_is_ghosted() {
1✔
681
        let tmp_dir = tempdir().unwrap();
1✔
682
        let data_path = tmp_dir.path().join("Data");
1✔
683
        let state = state(data_path);
1✔
684

1✔
685
        copy(
1✔
686
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
687
            state.data_path.join("Blank.esp.ghost"),
1✔
688
        )
1✔
689
        .unwrap();
1✔
690

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

1✔
693
        assert!(function.eval(&state).unwrap());
1✔
694
    }
1✔
695

696
    #[test]
697
    fn function_file_size_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
698
        let tmp_dir = tempdir().unwrap();
1✔
699
        let data_path = tmp_dir.path().join("Data");
1✔
700
        let state = state(data_path);
1✔
701

1✔
702
        copy(
1✔
703
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
704
            state.data_path.join("Blank.bsa.ghost"),
1✔
705
        )
1✔
706
        .unwrap();
1✔
707

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

1✔
710
        assert!(!function.eval(&state).unwrap());
1✔
711
    }
1✔
712

713
    #[test]
714
    fn function_readable_eval_should_be_true_for_a_file_that_can_be_opened_as_read_only() {
1✔
715
        let function = Function::Readable(PathBuf::from("Cargo.toml"));
1✔
716
        let state = state(".");
1✔
717

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

721
    #[test]
722
    fn function_readable_eval_should_be_true_for_a_folder_that_can_be_read() {
1✔
723
        let function = Function::Readable(PathBuf::from("tests"));
1✔
724
        let state = state(".");
1✔
725

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

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

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

737
    #[cfg(windows)]
738
    #[test]
739
    fn function_readable_eval_should_be_false_for_a_file_that_is_not_readable() {
740
        use std::os::windows::fs::OpenOptionsExt;
741

742
        let tmp_dir = tempdir().unwrap();
743
        let data_path = tmp_dir.path().join("Data");
744
        let state = state(data_path);
745

746
        let relative_path = "unreadable";
747
        let file_path = state.data_path.join(relative_path);
748

749
        // Create a file and open it with exclusive access so that the readable
750
        // function eval isn't able to open the file in read-only mode.
751
        let _file = std::fs::OpenOptions::new()
752
            .write(true)
753
            .create(true)
754
            .truncate(false)
755
            .share_mode(0)
756
            .open(&file_path);
757

758
        assert!(file_path.exists());
759

760
        let function = Function::Readable(PathBuf::from(relative_path));
761

762
        assert!(!function.eval(&state).unwrap());
763
    }
764

765
    #[cfg(not(windows))]
766
    #[test]
767
    fn function_readable_eval_should_be_false_for_a_file_that_is_not_readable() {
1✔
768
        let tmp_dir = tempdir().unwrap();
1✔
769
        let data_path = tmp_dir.path().join("Data");
1✔
770
        let state = state(data_path);
1✔
771

1✔
772
        let relative_path = "unreadable";
1✔
773
        let file_path = state.data_path.join(relative_path);
1✔
774

1✔
775
        std::fs::write(&file_path, "").unwrap();
1✔
776
        make_path_unreadable(&file_path);
1✔
777

1✔
778
        assert!(file_path.exists());
1✔
779

780
        let function = Function::Readable(PathBuf::from(relative_path));
1✔
781

1✔
782
        assert!(!function.eval(&state).unwrap());
1✔
783
    }
1✔
784

785
    #[cfg(windows)]
786
    #[test]
787
    fn function_readable_eval_should_be_false_for_a_folder_that_is_not_readable() {
788
        let data_path = Path::new(r"C:\Program Files");
789
        let state = state(data_path);
790

791
        let relative_path = "WindowsApps";
792

793
        // The WindowsApps directory is so locked down that trying to read its
794
        // metadata fails, but its existence can still be observed by iterating
795
        // over its parent directory's entries.
796
        let entry_exists = state
797
            .data_path
798
            .read_dir()
799
            .unwrap()
800
            .flat_map(|res| res.map(|e| e.file_name()).into_iter())
801
            .any(|name| name == relative_path);
802

803
        assert!(entry_exists);
804

805
        let function = Function::Readable(PathBuf::from(relative_path));
806

807
        assert!(!function.eval(&state).unwrap());
808
    }
809

810
    #[cfg(not(windows))]
811
    #[test]
812
    fn function_readable_eval_should_be_false_for_a_folder_that_is_not_readable() {
1✔
813
        let tmp_dir = tempdir().unwrap();
1✔
814
        let data_path = tmp_dir.path().join("Data");
1✔
815
        let state = state(data_path);
1✔
816

1✔
817
        let relative_path = "unreadable";
1✔
818
        let folder_path = state.data_path.join(relative_path);
1✔
819

1✔
820
        std::fs::create_dir(&folder_path).unwrap();
1✔
821
        make_path_unreadable(&folder_path);
1✔
822

1✔
823
        assert!(folder_path.exists());
1✔
824

825
        let function = Function::Readable(PathBuf::from(relative_path));
1✔
826

1✔
827
        assert!(!function.eval(&state).unwrap());
1✔
828
    }
1✔
829

830
    #[test]
831
    fn function_is_executable_should_be_false_for_a_path_that_does_not_exist() {
1✔
832
        let state = state(".");
1✔
833
        let function = Function::IsExecutable("missing".into());
1✔
834

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

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

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

846
    #[cfg(windows)]
847
    #[test]
848
    fn function_is_executable_should_be_false_for_a_file_that_cannot_be_read() {
849
        use std::os::windows::fs::OpenOptionsExt;
850

851
        let tmp_dir = tempdir().unwrap();
852
        let data_path = tmp_dir.path().join("Data");
853
        let state = state(data_path);
854

855
        let relative_path = "unreadable";
856
        let file_path = state.data_path.join(relative_path);
857

858
        // Create a file and open it with exclusive access so that the readable
859
        // function eval isn't able to open the file in read-only mode.
860
        let _file = std::fs::OpenOptions::new()
861
            .write(true)
862
            .create(true)
863
            .truncate(false)
864
            .share_mode(0)
865
            .open(&file_path);
866

867
        assert!(file_path.exists());
868

869
        let function = Function::IsExecutable(PathBuf::from(relative_path));
870

871
        assert!(!function.eval(&state).unwrap());
872
    }
873

874
    #[cfg(not(windows))]
875
    #[test]
876
    fn function_is_executable_should_be_false_for_a_file_that_cannot_be_read() {
1✔
877
        let tmp_dir = tempdir().unwrap();
1✔
878
        let data_path = tmp_dir.path().join("Data");
1✔
879
        let state = state(data_path);
1✔
880

1✔
881
        let relative_path = "unreadable";
1✔
882
        let file_path = state.data_path.join(relative_path);
1✔
883

1✔
884
        std::fs::write(&file_path, "").unwrap();
1✔
885
        make_path_unreadable(&file_path);
1✔
886

1✔
887
        assert!(file_path.exists());
1✔
888

889
        let function = Function::IsExecutable(PathBuf::from(relative_path));
1✔
890

1✔
891
        assert!(!function.eval(&state).unwrap());
1✔
892
    }
1✔
893

894
    #[test]
895
    fn function_is_executable_should_be_false_for_a_file_that_is_not_an_executable() {
1✔
896
        let state = state(".");
1✔
897
        let function = Function::IsExecutable("Cargo.toml".into());
1✔
898

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

902
    #[test]
903
    fn function_is_executable_should_be_true_for_a_file_that_is_an_executable() {
1✔
904
        let state = state(".");
1✔
905
        let function = Function::IsExecutable("tests/libloot_win32/loot.dll".into());
1✔
906

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

910
    #[test]
911
    fn function_active_path_eval_should_be_true_if_the_path_is_an_active_plugin() {
1✔
912
        let function = Function::ActivePath(PathBuf::from("Blank.esp"));
1✔
913
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
914

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

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

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

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

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

934
    #[test]
935
    fn function_active_regex_eval_should_be_true_if_the_regex_matches_an_active_plugin() {
1✔
936
        let function = Function::ActiveRegex(regex("Blank\\.esp"));
1✔
937
        let state = state_with_active_plugins(".", &["Blank.esp"]);
1✔
938

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

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

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

950
    #[test]
951
    fn function_is_master_eval_should_be_true_if_the_path_is_a_master_plugin() {
1✔
952
        let function = Function::IsMaster(PathBuf::from("Blank.esm"));
1✔
953
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
954

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

958
    #[test]
959
    fn function_is_master_eval_should_be_false_if_the_path_is_an_openmw_master_flagged_plugin() {
1✔
960
        let function = Function::IsMaster(PathBuf::from("Blank.esm"));
1✔
961
        let mut state = state("tests/testing-plugins/Morrowind/Data Files");
1✔
962
        state.game_type = GameType::OpenMW;
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_does_not_exist() {
1✔
969
        let function = Function::IsMaster(PathBuf::from("missing.esp"));
1✔
970
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
971

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

975
    #[test]
976
    fn function_is_master_eval_should_be_false_if_the_path_is_not_a_plugin() {
1✔
977
        let function = Function::IsMaster(PathBuf::from("Cargo.toml"));
1✔
978
        let state = state(".");
1✔
979

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

983
    #[test]
984
    fn function_is_master_eval_should_be_false_if_the_path_is_a_non_master_plugin() {
1✔
985
        let function = Function::IsMaster(PathBuf::from("Blank.esp"));
1✔
986
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
987

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

991
    #[test]
992
    fn function_many_eval_should_be_false_if_no_directory_entries_match() {
1✔
993
        let function = Function::Many(PathBuf::from("."), regex("missing"));
1✔
994
        let state = state(".");
1✔
995

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

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

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

1007
    #[test]
1008
    fn function_many_eval_should_be_false_if_one_directory_entry_matches() {
1✔
1009
        let function = Function::Many(
1✔
1010
            PathBuf::from("tests/testing-plugins/Oblivion/Data"),
1✔
1011
            regex("Blank\\.esp"),
1✔
1012
        );
1✔
1013
        let state = state(".");
1✔
1014

1✔
1015
        assert!(!function.eval(&state).unwrap());
1✔
1016
    }
1✔
1017

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

1✔
1026
        assert!(function.eval(&state).unwrap());
1✔
1027
    }
1✔
1028

1029
    #[test]
1030
    fn function_many_eval_should_trim_ghost_plugin_extension_before_matching_against_regex() {
1✔
1031
        let tmp_dir = tempdir().unwrap();
1✔
1032
        let data_path = tmp_dir.path().join("Data");
1✔
1033
        let state = state(data_path);
1✔
1034

1✔
1035
        copy(
1✔
1036
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1037
            state.data_path.join("Blank.esm.ghost"),
1✔
1038
        )
1✔
1039
        .unwrap();
1✔
1040
        copy(
1✔
1041
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esp"),
1✔
1042
            state.data_path.join("Blank.esp.ghost"),
1✔
1043
        )
1✔
1044
        .unwrap();
1✔
1045

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

1✔
1048
        assert!(function.eval(&state).unwrap());
1✔
1049
    }
1✔
1050

1051
    #[test]
1052
    fn function_many_eval_should_check_across_all_configured_data_paths() {
1✔
1053
        let function = Function::Many(PathBuf::from("Data"), regex("Blank\\.esp"));
1✔
1054
        let state = state_with_data(
1✔
1055
            "./tests/testing-plugins/Skyrim",
1✔
1056
            vec!["./tests/testing-plugins/Oblivion"],
1✔
1057
            &[],
1✔
1058
            &[],
1✔
1059
        );
1✔
1060

1✔
1061
        assert!(function.eval(&state).unwrap());
1✔
1062
    }
1✔
1063

1064
    #[test]
1065
    fn function_many_active_eval_should_be_true_if_the_regex_matches_more_than_one_active_plugin() {
1✔
1066
        let function = Function::ManyActive(regex("Blank.*"));
1✔
1067
        let state = state_with_active_plugins(".", &["Blank.esp", "Blank.esm"]);
1✔
1068

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

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

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

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

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

1088
    #[test]
1089
    fn function_checksum_eval_should_be_false_if_the_file_does_not_exist() {
1✔
1090
        let function = Function::Checksum(PathBuf::from("missing"), 0x374E2A6F);
1✔
1091
        let state = state(".");
1✔
1092

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

1096
    #[test]
1097
    fn function_checksum_eval_should_be_false_if_the_file_checksum_does_not_equal_the_given_checksum(
1✔
1098
    ) {
1✔
1099
        let function = Function::Checksum(
1✔
1100
            PathBuf::from("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1101
            0xDEADBEEF,
1✔
1102
        );
1✔
1103
        let state = state(".");
1✔
1104

1✔
1105
        assert!(!function.eval(&state).unwrap());
1✔
1106
    }
1✔
1107

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

1✔
1116
        assert!(function.eval(&state).unwrap());
1✔
1117
    }
1✔
1118

1119
    #[test]
1120
    fn function_checksum_eval_should_support_checking_the_crc_of_a_ghosted_plugin() {
1✔
1121
        let tmp_dir = tempdir().unwrap();
1✔
1122
        let data_path = tmp_dir.path().join("Data");
1✔
1123
        let state = state(data_path);
1✔
1124

1✔
1125
        copy(
1✔
1126
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1127
            state.data_path.join("Blank.esm.ghost"),
1✔
1128
        )
1✔
1129
        .unwrap();
1✔
1130

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

1✔
1133
        assert!(function.eval(&state).unwrap());
1✔
1134
    }
1✔
1135

1136
    #[test]
1137
    fn function_checksum_eval_should_not_check_for_ghosted_non_plugin_file() {
1✔
1138
        let tmp_dir = tempdir().unwrap();
1✔
1139
        let data_path = tmp_dir.path().join("Data");
1✔
1140
        let state = state(data_path);
1✔
1141

1✔
1142
        copy(
1✔
1143
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
1144
            state.data_path.join("Blank.bsa.ghost"),
1✔
1145
        )
1✔
1146
        .unwrap();
1✔
1147

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

1✔
1150
        assert!(!function.eval(&state).unwrap());
1✔
1151
    }
1✔
1152

1153
    #[test]
1154
    fn function_checksum_eval_should_be_false_if_given_a_directory_path() {
1✔
1155
        // The given CRC is the CRC-32 of the directory as calculated by 7-zip.
1✔
1156
        let function = Function::Checksum(PathBuf::from("tests/testing-plugins"), 0xC9CD16C3);
1✔
1157
        let state = state(".");
1✔
1158

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

1162
    #[test]
1163
    fn function_checksum_eval_should_cache_and_use_cached_crcs() {
1✔
1164
        let tmp_dir = tempdir().unwrap();
1✔
1165
        let data_path = tmp_dir.path().join("Data");
1✔
1166
        let state = state(data_path);
1✔
1167

1✔
1168
        copy(
1✔
1169
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1170
            state.data_path.join("Blank.esm"),
1✔
1171
        )
1✔
1172
        .unwrap();
1✔
1173

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

1✔
1176
        assert!(function.eval(&state).unwrap());
1✔
1177

1178
        // Change the CRC of the file to test that the cached value is used.
1179
        copy(
1✔
1180
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.bsa"),
1✔
1181
            state.data_path.join("Blank.esm"),
1✔
1182
        )
1✔
1183
        .unwrap();
1✔
1184

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

1✔
1187
        assert!(function.eval(&state).unwrap());
1✔
1188
    }
1✔
1189

1190
    #[test]
1191
    fn function_eval_should_cache_results_and_use_cached_results() {
1✔
1192
        let tmp_dir = tempdir().unwrap();
1✔
1193
        let data_path = tmp_dir.path().join("Data");
1✔
1194
        let state = state(data_path);
1✔
1195

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

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

1✔
1200
        assert!(function.eval(&state).unwrap());
1✔
1201

1202
        remove_file(state.data_path.join("Cargo.toml")).unwrap();
1✔
1203

1✔
1204
        assert!(function.eval(&state).unwrap());
1✔
1205
    }
1✔
1206

1207
    #[test]
1208
    fn function_version_eval_should_be_true_if_the_path_does_not_exist_and_comparator_is_ne() {
1✔
1209
        let function =
1✔
1210
            Function::Version("missing".into(), "1.0".into(), ComparisonOperator::NotEqual);
1✔
1211
        let state = state(".");
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_lt() {
1✔
1218
        let function =
1✔
1219
            Function::Version("missing".into(), "1.0".into(), ComparisonOperator::LessThan);
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_lteq() {
1✔
1227
        let function = Function::Version(
1✔
1228
            "missing".into(),
1✔
1229
            "1.0".into(),
1✔
1230
            ComparisonOperator::LessThanOrEqual,
1✔
1231
        );
1✔
1232
        let state = state(".");
1✔
1233

1✔
1234
        assert!(function.eval(&state).unwrap());
1✔
1235
    }
1✔
1236

1237
    #[test]
1238
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_eq() {
1✔
1239
        let function = Function::Version("missing".into(), "1.0".into(), ComparisonOperator::Equal);
1✔
1240
        let state = state(".");
1✔
1241

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

1245
    #[test]
1246
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_gt() {
1✔
1247
        let function = Function::Version(
1✔
1248
            "missing".into(),
1✔
1249
            "1.0".into(),
1✔
1250
            ComparisonOperator::GreaterThan,
1✔
1251
        );
1✔
1252
        let state = state(".");
1✔
1253

1✔
1254
        assert!(!function.eval(&state).unwrap());
1✔
1255
    }
1✔
1256

1257
    #[test]
1258
    fn function_version_eval_should_be_false_if_the_path_does_not_exist_and_comparator_is_gteq() {
1✔
1259
        let function = Function::Version(
1✔
1260
            "missing".into(),
1✔
1261
            "1.0".into(),
1✔
1262
            ComparisonOperator::GreaterThanOrEqual,
1✔
1263
        );
1✔
1264
        let state = state(".");
1✔
1265

1✔
1266
        assert!(!function.eval(&state).unwrap());
1✔
1267
    }
1✔
1268

1269
    #[test]
1270
    fn function_version_eval_should_be_true_if_the_path_is_not_a_file_and_comparator_is_ne() {
1✔
1271
        let function =
1✔
1272
            Function::Version("tests".into(), "1.0".into(), ComparisonOperator::NotEqual);
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_lt() {
1✔
1280
        let function =
1✔
1281
            Function::Version("tests".into(), "1.0".into(), ComparisonOperator::LessThan);
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_lteq() {
1✔
1289
        let function = Function::Version(
1✔
1290
            "tests".into(),
1✔
1291
            "1.0".into(),
1✔
1292
            ComparisonOperator::LessThanOrEqual,
1✔
1293
        );
1✔
1294
        let state = state(".");
1✔
1295

1✔
1296
        assert!(function.eval(&state).unwrap());
1✔
1297
    }
1✔
1298

1299
    #[test]
1300
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_eq() {
1✔
1301
        let function = Function::Version("tests".into(), "1.0".into(), ComparisonOperator::Equal);
1✔
1302
        let state = state(".");
1✔
1303

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

1307
    #[test]
1308
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_gt() {
1✔
1309
        let function = Function::Version(
1✔
1310
            "tests".into(),
1✔
1311
            "1.0".into(),
1✔
1312
            ComparisonOperator::GreaterThan,
1✔
1313
        );
1✔
1314
        let state = state(".");
1✔
1315

1✔
1316
        assert!(!function.eval(&state).unwrap());
1✔
1317
    }
1✔
1318

1319
    #[test]
1320
    fn function_version_eval_should_be_false_if_the_path_is_not_a_file_and_comparator_is_gteq() {
1✔
1321
        let function = Function::Version(
1✔
1322
            "tests".into(),
1✔
1323
            "1.0".into(),
1✔
1324
            ComparisonOperator::GreaterThanOrEqual,
1✔
1325
        );
1✔
1326
        let state = state(".");
1✔
1327

1✔
1328
        assert!(!function.eval(&state).unwrap());
1✔
1329
    }
1✔
1330

1331
    #[test]
1332
    fn function_version_eval_should_treat_a_plugin_with_no_cached_version_as_if_it_did_not_exist() {
1✔
1333
        use self::ComparisonOperator::*;
1334

1335
        let plugin = PathBuf::from("Blank.esm");
1✔
1336
        let version = String::from("1.0");
1✔
1337
        let state = state("tests/testing-plugins/Oblivion/Data");
1✔
1338

1✔
1339
        let function = Function::Version(plugin.clone(), version.clone(), NotEqual);
1✔
1340
        assert!(function.eval(&state).unwrap());
1✔
1341
        let function = Function::Version(plugin.clone(), version.clone(), LessThan);
1✔
1342
        assert!(function.eval(&state).unwrap());
1✔
1343
        let function = Function::Version(plugin.clone(), version.clone(), LessThanOrEqual);
1✔
1344
        assert!(function.eval(&state).unwrap());
1✔
1345
        let function = Function::Version(plugin.clone(), version.clone(), Equal);
1✔
1346
        assert!(!function.eval(&state).unwrap());
1✔
1347
        let function = Function::Version(plugin.clone(), version.clone(), GreaterThan);
1✔
1348
        assert!(!function.eval(&state).unwrap());
1✔
1349
        let function = Function::Version(plugin.clone(), version.clone(), GreaterThanOrEqual);
1✔
1350
        assert!(!function.eval(&state).unwrap());
1✔
1351
    }
1✔
1352

1353
    #[test]
1354
    fn function_version_eval_should_be_false_if_versions_are_not_equal_and_comparator_is_eq() {
1✔
1355
        let function = Function::Version("Blank.esm".into(), "5".into(), ComparisonOperator::Equal);
1✔
1356
        let state =
1✔
1357
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "1")]);
1✔
1358

1✔
1359
        assert!(!function.eval(&state).unwrap());
1✔
1360
    }
1✔
1361

1362
    #[test]
1363
    fn function_version_eval_should_be_true_if_versions_are_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", "5")]);
1✔
1367

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1431
        assert!(!function.eval(&state).unwrap());
1✔
1432
    }
1✔
1433

1434
    #[test]
1435
    fn function_version_eval_should_be_false_if_actual_version_is_lt_and_comparator_is_gt() {
1✔
1436
        let function = Function::Version(
1✔
1437
            "Blank.esm".into(),
1✔
1438
            "5".into(),
1✔
1439
            ComparisonOperator::GreaterThan,
1✔
1440
        );
1✔
1441
        let state =
1✔
1442
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1443

1✔
1444
        assert!(!function.eval(&state).unwrap());
1✔
1445
    }
1✔
1446

1447
    #[test]
1448
    fn function_version_eval_should_be_true_if_actual_version_is_gt_and_comparator_is_gt() {
1✔
1449
        let function = Function::Version(
1✔
1450
            "Blank.esm".into(),
1✔
1451
            "5".into(),
1✔
1452
            ComparisonOperator::GreaterThan,
1✔
1453
        );
1✔
1454
        let state =
1✔
1455
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1456

1✔
1457
        assert!(function.eval(&state).unwrap());
1✔
1458
    }
1✔
1459

1460
    #[test]
1461
    fn function_version_eval_should_be_false_if_actual_version_is_gt_and_comparator_is_lteq() {
1✔
1462
        let function = Function::Version(
1✔
1463
            "Blank.esm".into(),
1✔
1464
            "5".into(),
1✔
1465
            ComparisonOperator::LessThanOrEqual,
1✔
1466
        );
1✔
1467
        let state =
1✔
1468
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1469

1✔
1470
        assert!(!function.eval(&state).unwrap());
1✔
1471
    }
1✔
1472

1473
    #[test]
1474
    fn function_version_eval_should_be_true_if_actual_version_is_eq_and_comparator_is_lteq() {
1✔
1475
        let function = Function::Version(
1✔
1476
            "Blank.esm".into(),
1✔
1477
            "5".into(),
1✔
1478
            ComparisonOperator::LessThanOrEqual,
1✔
1479
        );
1✔
1480
        let state =
1✔
1481
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1482

1✔
1483
        assert!(function.eval(&state).unwrap());
1✔
1484
    }
1✔
1485

1486
    #[test]
1487
    fn function_version_eval_should_be_true_if_actual_version_is_lt_and_comparator_is_lteq() {
1✔
1488
        let function = Function::Version(
1✔
1489
            "Blank.esm".into(),
1✔
1490
            "5".into(),
1✔
1491
            ComparisonOperator::LessThanOrEqual,
1✔
1492
        );
1✔
1493
        let state =
1✔
1494
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1495

1✔
1496
        assert!(function.eval(&state).unwrap());
1✔
1497
    }
1✔
1498

1499
    #[test]
1500
    fn function_version_eval_should_be_false_if_actual_version_is_lt_and_comparator_is_gteq() {
1✔
1501
        let function = Function::Version(
1✔
1502
            "Blank.esm".into(),
1✔
1503
            "5".into(),
1✔
1504
            ComparisonOperator::GreaterThanOrEqual,
1✔
1505
        );
1✔
1506
        let state =
1✔
1507
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "4")]);
1✔
1508

1✔
1509
        assert!(!function.eval(&state).unwrap());
1✔
1510
    }
1✔
1511

1512
    #[test]
1513
    fn function_version_eval_should_be_true_if_actual_version_is_eq_and_comparator_is_gteq() {
1✔
1514
        let function = Function::Version(
1✔
1515
            "Blank.esm".into(),
1✔
1516
            "5".into(),
1✔
1517
            ComparisonOperator::GreaterThanOrEqual,
1✔
1518
        );
1✔
1519
        let state =
1✔
1520
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "5")]);
1✔
1521

1✔
1522
        assert!(function.eval(&state).unwrap());
1✔
1523
    }
1✔
1524

1525
    #[test]
1526
    fn function_version_eval_should_be_true_if_actual_version_is_gt_and_comparator_is_gteq() {
1✔
1527
        let function = Function::Version(
1✔
1528
            "Blank.esm".into(),
1✔
1529
            "5".into(),
1✔
1530
            ComparisonOperator::GreaterThanOrEqual,
1✔
1531
        );
1✔
1532
        let state =
1✔
1533
            state_with_versions("tests/testing-plugins/Oblivion/Data", &[("Blank.esm", "6")]);
1✔
1534

1✔
1535
        assert!(function.eval(&state).unwrap());
1✔
1536
    }
1✔
1537

1538
    #[test]
1539
    fn function_version_eval_should_read_executable_file_version() {
1✔
1540
        let function = Function::Version(
1✔
1541
            "loot.dll".into(),
1✔
1542
            "0.18.2.0".into(),
1✔
1543
            ComparisonOperator::Equal,
1✔
1544
        );
1✔
1545
        let state = state("tests/libloot_win32");
1✔
1546

1✔
1547
        assert!(function.eval(&state).unwrap());
1✔
1548
    }
1✔
1549

1550
    #[test]
1551
    fn function_product_version_eval_should_read_executable_product_version() {
1✔
1552
        let function = Function::ProductVersion(
1✔
1553
            "loot.dll".into(),
1✔
1554
            "0.18.2".into(),
1✔
1555
            ComparisonOperator::Equal,
1✔
1556
        );
1✔
1557
        let state = state("tests/libloot_win32");
1✔
1558

1✔
1559
        assert!(function.eval(&state).unwrap());
1✔
1560
    }
1✔
1561

1562
    #[test]
1563
    fn get_product_version_should_return_ok_none_if_the_path_does_not_exist() {
1✔
1564
        assert!(get_product_version(Path::new("missing")).unwrap().is_none());
1✔
1565
    }
1✔
1566

1567
    #[test]
1568
    fn get_product_version_should_return_ok_none_if_the_path_is_not_a_file() {
1✔
1569
        assert!(get_product_version(Path::new("tests")).unwrap().is_none());
1✔
1570
    }
1✔
1571

1572
    #[test]
1573
    fn get_product_version_should_return_ok_some_if_the_path_is_an_executable() {
1✔
1574
        let version = get_product_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
1575
            .unwrap()
1✔
1576
            .unwrap();
1✔
1577

1✔
1578
        assert_eq!(Version::from("0.18.2"), version);
1✔
1579
    }
1✔
1580

1581
    #[test]
1582
    fn get_product_version_should_error_if_the_path_is_not_an_executable() {
1✔
1583
        assert!(get_product_version(Path::new("Cargo.toml")).is_err());
1✔
1584
    }
1✔
1585

1586
    #[test]
1587
    fn function_filename_version_eval_should_be_false_if_no_matching_filenames_exist() {
1✔
1588
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1589

1✔
1590
        let function = Function::FilenameVersion(
1✔
1591
            "".into(),
1✔
1592
            regex("Blank (A+).esm"),
1✔
1593
            "5".into(),
1✔
1594
            ComparisonOperator::Equal,
1✔
1595
        );
1✔
1596

1✔
1597
        assert!(!function.eval(&state).unwrap());
1✔
1598

1599
        let function = Function::FilenameVersion(
1✔
1600
            "".into(),
1✔
1601
            regex("Blank (A+).esm"),
1✔
1602
            "5".into(),
1✔
1603
            ComparisonOperator::NotEqual,
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::LessThan,
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::GreaterThan,
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::LessThanOrEqual,
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::GreaterThanOrEqual,
1✔
1640
        );
1✔
1641

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

1645
    #[test]
1646
    fn function_filename_version_eval_should_be_false_if_filenames_matched_but_no_version_was_captured(
1✔
1647
    ) {
1✔
1648
        // This shouldn't happen in practice because parsing validates that there is one explicit capturing group in the regex.
1✔
1649
        let function = Function::FilenameVersion(
1✔
1650
            "".into(),
1✔
1651
            regex("Blank .+.esm"),
1✔
1652
            "5".into(),
1✔
1653
            ComparisonOperator::GreaterThanOrEqual,
1✔
1654
        );
1✔
1655
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1656

1✔
1657
        assert!(!function.eval(&state).unwrap());
1✔
1658
    }
1✔
1659

1660
    #[test]
1661
    fn function_filename_version_eval_should_be_true_if_the_captured_version_is_eq_and_operator_is_eq(
1✔
1662
    ) {
1✔
1663
        let tmp_dir = tempdir().unwrap();
1✔
1664
        let data_path = tmp_dir.path().join("Data");
1✔
1665
        let state = state(data_path);
1✔
1666

1✔
1667
        copy(
1✔
1668
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1669
            state.data_path.join("Blank 5.esm"),
1✔
1670
        )
1✔
1671
        .unwrap();
1✔
1672

1✔
1673
        let function = Function::FilenameVersion(
1✔
1674
            "".into(),
1✔
1675
            regex("Blank (\\d).esm"),
1✔
1676
            "5".into(),
1✔
1677
            ComparisonOperator::Equal,
1✔
1678
        );
1✔
1679

1✔
1680
        assert!(function.eval(&state).unwrap());
1✔
1681
    }
1✔
1682

1683
    #[test]
1684
    fn function_filename_version_eval_should_be_true_if_the_captured_version_is_not_equal_and_operator_is_not_equal(
1✔
1685
    ) {
1✔
1686
        let tmp_dir = tempdir().unwrap();
1✔
1687
        let data_path = tmp_dir.path().join("Data");
1✔
1688
        let state = state(data_path);
1✔
1689

1✔
1690
        copy(
1✔
1691
            Path::new("tests/testing-plugins/Oblivion/Data/Blank.esm"),
1✔
1692
            state.data_path.join("Blank 4.esm"),
1✔
1693
        )
1✔
1694
        .unwrap();
1✔
1695

1✔
1696
        let function = Function::FilenameVersion(
1✔
1697
            "".into(),
1✔
1698
            regex("Blank (\\d).esm"),
1✔
1699
            "5".into(),
1✔
1700
            ComparisonOperator::NotEqual,
1✔
1701
        );
1✔
1702

1✔
1703
        assert!(function.eval(&state).unwrap());
1✔
1704
    }
1✔
1705

1706
    #[test]
1707
    fn function_description_contains_eval_should_return_false_if_the_file_does_not_exist() {
1✔
1708
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1709

1✔
1710
        let function = Function::DescriptionContains("missing.esp".into(), regex("€ƒ."));
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_is_not_a_plugin() {
1✔
1717
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1718

1✔
1719
        let function = Function::DescriptionContains("Blank.bsa".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_plugin_has_no_description() {
1✔
1726
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1727

1✔
1728
        let function = Function::DescriptionContains("Blank - Different.esm".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_description_does_not_match(
1✔
1735
    ) {
1✔
1736
        let state = state_with_versions("tests/testing-plugins/Oblivion/Data", &[]);
1✔
1737

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

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

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

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

1✔
1750
        assert!(function.eval(&state).unwrap());
1✔
1751
    }
1✔
1752
}
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