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

loot / loot-condition-interpreter / 13348814213

15 Feb 2025 09:27PM UTC coverage: 90.987%. First build
13348814213

push

github

Ortham
Add filename_version() condition function

Unlike the other functions it will always evaluate to false if the version cannot be extracted from any filenames.

434 of 435 new or added lines in 3 files covered. (99.77%)

4664 of 5126 relevant lines covered (90.99%)

15.81 hits per line

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

95.4
/src/function/parse.rs
1
use std::path::{Component, Path, PathBuf};
2
use std::str;
3

4
use nom::branch::alt;
5
use nom::bytes::complete::{is_not, tag};
6
use nom::character::complete::digit1;
7
use nom::character::complete::hex_digit1;
8
use nom::combinator::{map, map_parser, value};
9
use nom::sequence::delimited;
10
use nom::{Err, IResult, Parser};
11
use regex::{Regex, RegexBuilder};
12

13
use super::{ComparisonOperator, Function};
14
use crate::error::ParsingErrorKind;
15
use crate::{map_err, whitespace, ParsingError, ParsingResult};
16

17
impl ComparisonOperator {
18
    pub fn parse(input: &str) -> IResult<&str, ComparisonOperator> {
17✔
19
        alt((
17✔
20
            value(ComparisonOperator::Equal, tag("==")),
17✔
21
            value(ComparisonOperator::NotEqual, tag("!=")),
17✔
22
            value(ComparisonOperator::LessThanOrEqual, tag("<=")),
17✔
23
            value(ComparisonOperator::GreaterThanOrEqual, tag(">=")),
17✔
24
            value(ComparisonOperator::LessThan, tag("<")),
17✔
25
            value(ComparisonOperator::GreaterThan, tag(">")),
17✔
26
        ))
17✔
27
        .parse(input)
17✔
28
    }
17✔
29
}
30

31
const INVALID_PATH_CHARS: &str = "\":*?<>|";
32
const INVALID_NON_REGEX_PATH_CHARS: &str = "\":*?<>|\\"; // \ is treated as invalid to distinguish regex strings.
33
const INVALID_REGEX_PATH_CHARS: &str = "\"<>";
34

35
fn is_in_game_path(path: &Path) -> bool {
73✔
36
    let mut previous_component = Component::CurDir;
73✔
37
    for component in path.components() {
86✔
38
        match (component, previous_component) {
86✔
39
            (Component::Prefix(_), _) => return false,
×
40
            (Component::RootDir, _) => return false,
×
41
            (Component::ParentDir, Component::ParentDir) => return false,
12✔
42
            (Component::CurDir, _) => continue,
5✔
43
            _ => previous_component = component,
69✔
44
        }
45
    }
46

47
    true
61✔
48
}
73✔
49
fn parse_regex(input: &str) -> ParsingResult<Regex> {
13✔
50
    RegexBuilder::new(&format!("^{}$", input))
13✔
51
        .case_insensitive(true)
13✔
52
        .build()
13✔
53
        .map(|r| ("", r))
13✔
54
        .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
13✔
55
}
13✔
56

57
fn not_in_game_directory(input: &str, path: PathBuf) -> Err<ParsingError<&str>> {
12✔
58
    Err::Failure(ParsingErrorKind::PathIsNotInGameDirectory(path).at(input))
12✔
59
}
12✔
60

61
fn parse_path(input: &str) -> IResult<&str, PathBuf> {
30✔
62
    map(
30✔
63
        delimited(tag("\""), is_not(INVALID_PATH_CHARS), tag("\"")),
30✔
64
        PathBuf::from,
30✔
65
    )
30✔
66
    .parse(input)
30✔
67
}
30✔
68

69
fn parse_size(input: &str) -> ParsingResult<u64> {
2✔
70
    str::parse(input)
2✔
71
        .map(|c| ("", c))
2✔
72
        .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
2✔
73
}
2✔
74

75
fn parse_file_size_args(input: &str) -> ParsingResult<(PathBuf, u64)> {
2✔
76
    let mut parser = (
2✔
77
        map_err(parse_path),
2✔
78
        map_err(whitespace(tag(","))),
2✔
79
        map_parser(digit1, parse_size),
2✔
80
    );
2✔
81

82
    let (remaining_input, (path, _, size)) = parser.parse(input)?;
2✔
83

84
    if is_in_game_path(&path) {
2✔
85
        Ok((remaining_input, (path, size)))
1✔
86
    } else {
87
        Err(not_in_game_directory(input, path))
1✔
88
    }
89
}
2✔
90

91
fn parse_version(input: &str) -> IResult<&str, String> {
17✔
92
    map(
17✔
93
        delimited(tag("\""), is_not("\""), tag("\"")),
17✔
94
        |version: &str| version.to_string(),
17✔
95
    )
17✔
96
    .parse(input)
17✔
97
}
17✔
98

99
fn parse_version_args(input: &str) -> ParsingResult<(PathBuf, String, ComparisonOperator)> {
15✔
100
    let parser = (
15✔
101
        parse_path,
15✔
102
        whitespace(tag(",")),
15✔
103
        parse_version,
15✔
104
        whitespace(tag(",")),
15✔
105
        ComparisonOperator::parse,
15✔
106
    );
15✔
107

108
    let (remaining_input, (path, _, version, _, comparator)) = map_err(parser).parse(input)?;
15✔
109

110
    if is_in_game_path(&path) {
15✔
111
        Ok((remaining_input, (path, version, comparator)))
13✔
112
    } else {
113
        Err(not_in_game_directory(input, path))
2✔
114
    }
115
}
15✔
116

117
fn parse_filename_version_args(
2✔
118
    input: &str,
2✔
119
) -> ParsingResult<(PathBuf, Regex, String, ComparisonOperator)> {
2✔
120
    let mut parser = (
2✔
121
        delimited(map_err(tag("\"")), parse_regex_path, map_err(tag("\""))),
2✔
122
        map_err(whitespace(tag(","))),
2✔
123
        map_err(parse_version),
2✔
124
        map_err(whitespace(tag(","))),
2✔
125
        map_err(ComparisonOperator::parse),
2✔
126
    );
2✔
127

128
    let (remaining_input, ((path, regex), _, version, _, comparator)) = parser.parse(input)?;
2✔
129

130
    if regex.captures_len() != 2 {
2✔
131
        return Err(Err::Failure(
1✔
132
            ParsingErrorKind::InvalidRegexUnknown.at(input),
1✔
133
        ));
1✔
134
    }
1✔
135

1✔
136
    Ok((remaining_input, (path, regex, version, comparator)))
1✔
137
}
2✔
138

139
fn parse_crc(input: &str) -> ParsingResult<u32> {
13✔
140
    u32::from_str_radix(input, 16)
13✔
141
        .map(|c| ("", c))
13✔
142
        .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
13✔
143
}
13✔
144

145
fn parse_checksum_args(input: &str) -> ParsingResult<(PathBuf, u32)> {
13✔
146
    let mut parser = (
13✔
147
        map_err(parse_path),
13✔
148
        map_err(whitespace(tag(","))),
13✔
149
        map_parser(hex_digit1, parse_crc),
13✔
150
    );
13✔
151

152
    let (remaining_input, (path, _, crc)) = parser.parse(input)?;
13✔
153

154
    if is_in_game_path(&path) {
12✔
155
        Ok((remaining_input, (path, crc)))
11✔
156
    } else {
157
        Err(not_in_game_directory(input, path))
1✔
158
    }
159
}
13✔
160

161
fn parse_non_regex_path(input: &str) -> ParsingResult<PathBuf> {
35✔
162
    let (remaining_input, path) = map(is_not(INVALID_NON_REGEX_PATH_CHARS), |path: &str| {
35✔
163
        PathBuf::from(path)
35✔
164
    })
35✔
165
    .parse(input)?;
35✔
166

167
    if is_in_game_path(&path) {
35✔
168
        Ok((remaining_input, path))
27✔
169
    } else {
170
        Err(not_in_game_directory(input, path))
8✔
171
    }
172
}
35✔
173

174
/// Parse a string that is a path where the last component is a regex string
175
/// that may contain characters that are invalid in paths but valid in regex.
176
fn parse_regex_path(input: &str) -> ParsingResult<(PathBuf, Regex)> {
12✔
177
    let (remaining_input, string) = is_not(INVALID_REGEX_PATH_CHARS)(input)?;
12✔
178

179
    if string.ends_with('/') {
12✔
180
        return Err(Err::Failure(
3✔
181
            ParsingErrorKind::PathEndsInADirectorySeparator(string.into()).at(input),
3✔
182
        ));
3✔
183
    }
9✔
184

9✔
185
    let (parent_path_slice, regex_slice) = string
9✔
186
        .rfind('/')
9✔
187
        .map(|i| (&string[..i], &string[i + 1..]))
9✔
188
        .unwrap_or_else(|| (".", string));
9✔
189

9✔
190
    let parent_path = PathBuf::from(parent_path_slice);
9✔
191

9✔
192
    if !is_in_game_path(&parent_path) {
9✔
193
        return Err(not_in_game_directory(input, parent_path));
×
194
    }
9✔
195

196
    let regex = parse_regex(regex_slice)?.1;
9✔
197

198
    Ok((remaining_input, (parent_path, regex)))
8✔
199
}
12✔
200

201
fn parse_regex_filename(input: &str) -> ParsingResult<Regex> {
2✔
202
    map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex).parse(input)
2✔
203
}
2✔
204

205
impl Function {
206
    pub fn parse(input: &str) -> ParsingResult<Function> {
87✔
207
        alt((
87✔
208
            map(
87✔
209
                delimited(
87✔
210
                    map_err(tag("file(\"")),
87✔
211
                    parse_non_regex_path,
87✔
212
                    map_err(tag("\")")),
87✔
213
                ),
87✔
214
                Function::FilePath,
87✔
215
            ),
87✔
216
            map(
87✔
217
                delimited(
87✔
218
                    map_err(tag("file(\"")),
87✔
219
                    parse_regex_path,
87✔
220
                    map_err(tag("\")")),
87✔
221
                ),
87✔
222
                |(path, regex)| Function::FileRegex(path, regex),
87✔
223
            ),
87✔
224
            map(
87✔
225
                delimited(
87✔
226
                    map_err(tag("file_size(")),
87✔
227
                    parse_file_size_args,
87✔
228
                    map_err(tag(")")),
87✔
229
                ),
87✔
230
                |(path, size)| Function::FileSize(path, size),
87✔
231
            ),
87✔
232
            map(
87✔
233
                delimited(
87✔
234
                    map_err(tag("readable(\"")),
87✔
235
                    parse_non_regex_path,
87✔
236
                    map_err(tag("\")")),
87✔
237
                ),
87✔
238
                Function::Readable,
87✔
239
            ),
87✔
240
            map(
87✔
241
                delimited(
87✔
242
                    map_err(tag("is_executable(\"")),
87✔
243
                    parse_non_regex_path,
87✔
244
                    map_err(tag("\")")),
87✔
245
                ),
87✔
246
                Function::IsExecutable,
87✔
247
            ),
87✔
248
            map(
87✔
249
                delimited(
87✔
250
                    map_err(tag("active(\"")),
87✔
251
                    parse_non_regex_path,
87✔
252
                    map_err(tag("\")")),
87✔
253
                ),
87✔
254
                Function::ActivePath,
87✔
255
            ),
87✔
256
            map(
87✔
257
                delimited(
87✔
258
                    map_err(tag("active(\"")),
87✔
259
                    parse_regex_filename,
87✔
260
                    map_err(tag("\")")),
87✔
261
                ),
87✔
262
                Function::ActiveRegex,
87✔
263
            ),
87✔
264
            map(
87✔
265
                delimited(
87✔
266
                    map_err(tag("is_master(\"")),
87✔
267
                    parse_non_regex_path,
87✔
268
                    map_err(tag("\")")),
87✔
269
                ),
87✔
270
                Function::IsMaster,
87✔
271
            ),
87✔
272
            map(
87✔
273
                delimited(
87✔
274
                    map_err(tag("many(\"")),
87✔
275
                    parse_regex_path,
87✔
276
                    map_err(tag("\")")),
87✔
277
                ),
87✔
278
                |(path, regex)| Function::Many(path, regex),
87✔
279
            ),
87✔
280
            map(
87✔
281
                delimited(
87✔
282
                    map_err(tag("many_active(\"")),
87✔
283
                    parse_regex_filename,
87✔
284
                    map_err(tag("\")")),
87✔
285
                ),
87✔
286
                Function::ManyActive,
87✔
287
            ),
87✔
288
            map(
87✔
289
                delimited(
87✔
290
                    map_err(tag("version(")),
87✔
291
                    parse_version_args,
87✔
292
                    map_err(tag(")")),
87✔
293
                ),
87✔
294
                |(path, version, comparator)| Function::Version(path, version, comparator),
87✔
295
            ),
87✔
296
            map(
87✔
297
                delimited(
87✔
298
                    map_err(tag("product_version(")),
87✔
299
                    parse_version_args,
87✔
300
                    map_err(tag(")")),
87✔
301
                ),
87✔
302
                |(path, version, comparator)| Function::ProductVersion(path, version, comparator),
87✔
303
            ),
87✔
304
            map(
87✔
305
                delimited(
87✔
306
                    map_err(tag("filename_version(")),
87✔
307
                    parse_filename_version_args,
87✔
308
                    map_err(tag(")")),
87✔
309
                ),
87✔
310
                |(path, regex, version, comparator)| {
87✔
311
                    Function::FilenameVersion(path, regex, version, comparator)
1✔
312
                },
87✔
313
            ),
87✔
314
            map(
87✔
315
                delimited(
87✔
316
                    map_err(tag("checksum(")),
87✔
317
                    parse_checksum_args,
87✔
318
                    map_err(tag(")")),
87✔
319
                ),
87✔
320
                |(path, crc)| Function::Checksum(path, crc),
87✔
321
            ),
87✔
322
        ))
87✔
323
        .parse(input)
87✔
324
    }
87✔
325
}
326

327
#[cfg(test)]
328
mod tests {
329
    use super::*;
330

331
    use std::path::Path;
332

333
    #[test]
334
    fn parse_regex_should_produce_case_insensitive_regex() {
1✔
335
        let (_, regex) = parse_regex("cargo.*").unwrap();
1✔
336

1✔
337
        assert!(regex.is_match("Cargo.toml"));
1✔
338
    }
1✔
339

340
    #[test]
341
    fn parse_regex_should_produce_a_regex_that_does_not_partially_match() {
1✔
342
        let (_, regex) = parse_regex("cargo.").unwrap();
1✔
343

1✔
344
        assert!(!regex.is_match("Cargo.toml"));
1✔
345
    }
1✔
346

347
    #[test]
348
    fn function_parse_should_parse_a_file_path_function() {
1✔
349
        let output = Function::parse("file(\"Cargo.toml\")").unwrap();
1✔
350

1✔
351
        assert!(output.0.is_empty());
1✔
352
        match output.1 {
1✔
353
            Function::FilePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
354
            _ => panic!("Expected a file path function"),
×
355
        }
356
    }
1✔
357

358
    #[test]
359
    fn function_parse_should_error_if_the_file_path_is_outside_the_game_directory() {
1✔
360
        assert!(Function::parse("file(\"../../Cargo.toml\")").is_err());
1✔
361
    }
1✔
362

363
    #[test]
364
    fn function_parse_should_parse_a_file_regex_function_with_no_parent_path() {
1✔
365
        let output = Function::parse("file(\"Cargo.*\")").unwrap();
1✔
366

1✔
367
        assert!(output.0.is_empty());
1✔
368
        match output.1 {
1✔
369
            Function::FileRegex(p, r) => {
1✔
370
                assert_eq!(PathBuf::from("."), p);
1✔
371
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
372
            }
373
            _ => panic!("Expected a file regex function"),
×
374
        }
375
    }
1✔
376

377
    #[test]
378
    fn function_parse_should_parse_a_file_regex_function_with_a_parent_path() {
1✔
379
        let output = Function::parse("file(\"subdir/Cargo.*\")").unwrap();
1✔
380

1✔
381
        assert!(output.0.is_empty());
1✔
382
        match output.1 {
1✔
383
            Function::FileRegex(p, r) => {
1✔
384
                assert_eq!(PathBuf::from("subdir"), p);
1✔
385
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
386
            }
387
            _ => panic!("Expected a file regex function"),
×
388
        }
389
    }
1✔
390

391
    #[test]
392
    fn function_parse_should_error_if_given_a_file_regex_function_ending_in_a_forward_slash() {
1✔
393
        assert!(Function::parse("file(\"sub\\dir/\")").is_err());
1✔
394
    }
1✔
395

396
    #[test]
397
    fn function_parse_should_error_if_the_file_regex_parent_path_is_outside_the_game_directory() {
1✔
398
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
399
    }
1✔
400

401
    #[test]
402
    fn function_parse_should_parse_a_file_size_function() {
1✔
403
        let output = Function::parse("file_size(\"Cargo.toml\", 1234)").unwrap();
1✔
404

1✔
405
        assert!(output.0.is_empty());
1✔
406
        match output.1 {
1✔
407
            Function::FileSize(f, s) => {
1✔
408
                assert_eq!(Path::new("Cargo.toml"), f);
1✔
409
                assert_eq!(1234, s);
1✔
410
            }
411
            _ => panic!("Expected a file size function"),
×
412
        }
413
    }
1✔
414

415
    #[test]
416
    fn function_parse_should_error_if_the_file_size_is_outside_the_game_directory() {
1✔
417
        assert!(Function::parse("file_size(\"../../Cargo.toml\", 1234)").is_err());
1✔
418
    }
1✔
419

420
    #[test]
421
    fn function_parse_should_parse_a_readable_function() {
1✔
422
        let output = Function::parse("readable(\"Cargo.toml\")").unwrap();
1✔
423

1✔
424
        assert!(output.0.is_empty());
1✔
425
        match output.1 {
1✔
426
            Function::Readable(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
427
            _ => panic!("Expected a readable function"),
×
428
        }
429
    }
1✔
430

431
    #[test]
432
    fn function_parse_should_error_if_the_readable_path_is_outside_the_game_directory() {
1✔
433
        assert!(Function::parse("readable(\"../../Cargo.toml\")").is_err());
1✔
434
    }
1✔
435

436
    #[test]
437
    fn function_parse_should_parse_an_is_executable_function() {
1✔
438
        let output = Function::parse("is_executable(\"Cargo.toml\")").unwrap();
1✔
439

1✔
440
        assert!(output.0.is_empty());
1✔
441
        match output.1 {
1✔
442
            Function::IsExecutable(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
443
            _ => panic!("Expected an is_executable function"),
×
444
        }
445
    }
1✔
446

447
    #[test]
448
    fn function_parse_should_error_if_the_is_executable_path_is_outside_the_game_directory() {
1✔
449
        assert!(Function::parse("is_executable(\"../../Cargo.toml\")").is_err());
1✔
450
    }
1✔
451

452
    #[test]
453
    fn function_parse_should_parse_an_active_path_function() {
1✔
454
        let output = Function::parse("active(\"Cargo.toml\")").unwrap();
1✔
455

1✔
456
        assert!(output.0.is_empty());
1✔
457
        match output.1 {
1✔
458
            Function::ActivePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
459
            _ => panic!("Expected an active path function"),
×
460
        }
461
    }
1✔
462

463
    #[test]
464
    fn function_parse_should_error_if_the_active_path_is_outside_the_game_directory() {
1✔
465
        // Trying to check if a path that isn't a plugin in the data folder is
1✔
466
        // active is pointless, but it's not worth having a more specific check.
1✔
467
        assert!(Function::parse("active(\"../../Cargo.toml\")").is_err());
1✔
468
    }
1✔
469

470
    #[test]
471
    fn function_parse_should_parse_an_active_regex_function() {
1✔
472
        let output = Function::parse("active(\"Cargo.*\")").unwrap();
1✔
473

1✔
474
        assert!(output.0.is_empty());
1✔
475
        match output.1 {
1✔
476
            Function::ActiveRegex(r) => {
1✔
477
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
478
            }
479
            _ => panic!("Expected an active regex function"),
×
480
        }
481
    }
1✔
482

483
    #[test]
484
    fn function_parse_should_parse_an_is_master_function() {
1✔
485
        let output = Function::parse("is_master(\"Blank.esm\")").unwrap();
1✔
486

1✔
487
        assert!(output.0.is_empty());
1✔
488
        match output.1 {
1✔
489
            Function::IsMaster(f) => assert_eq!(Path::new("Blank.esm"), f),
1✔
490
            _ => panic!("Expected an is master function"),
×
491
        }
492
    }
1✔
493

494
    #[test]
495
    fn function_parse_should_error_if_the_is_master_path_is_outside_the_game_directory() {
1✔
496
        // Trying to check if a path that isn't a plugin in the data folder is
1✔
497
        // active is pointless, but it's not worth having a more specific check.
1✔
498
        assert!(Function::parse("is_master(\"../../Blank.esm\")").is_err());
1✔
499
    }
1✔
500

501
    #[test]
502
    fn function_parse_should_parse_a_many_function_with_no_parent_path() {
1✔
503
        let output = Function::parse("many(\"Cargo.*\")").unwrap();
1✔
504

1✔
505
        assert!(output.0.is_empty());
1✔
506
        match output.1 {
1✔
507
            Function::Many(p, r) => {
1✔
508
                assert_eq!(PathBuf::from("."), p);
1✔
509
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
510
            }
511
            _ => panic!("Expected a many function"),
×
512
        }
513
    }
1✔
514

515
    #[test]
516
    fn function_parse_should_parse_a_many_function_with_a_parent_path() {
1✔
517
        let output = Function::parse("many(\"subdir/Cargo.*\")").unwrap();
1✔
518

1✔
519
        assert!(output.0.is_empty());
1✔
520
        match output.1 {
1✔
521
            Function::Many(p, r) => {
1✔
522
                assert_eq!(PathBuf::from("subdir"), p);
1✔
523
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
524
            }
525
            _ => panic!("Expected a many function"),
×
526
        }
527
    }
1✔
528

529
    #[test]
530
    fn function_parse_should_error_if_given_a_many_function_ending_in_a_forward_slash() {
1✔
531
        assert!(Function::parse("many(\"subdir/\")").is_err());
1✔
532
    }
1✔
533

534
    #[test]
535
    fn function_parse_should_error_if_the_many_parent_path_is_outside_the_game_directory() {
1✔
536
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
537
    }
1✔
538

539
    #[test]
540
    fn function_parse_should_parse_a_many_active_function() {
1✔
541
        let output = Function::parse("many_active(\"Cargo.*\")").unwrap();
1✔
542

1✔
543
        assert!(output.0.is_empty());
1✔
544
        match output.1 {
1✔
545
            Function::ManyActive(r) => {
1✔
546
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
547
            }
548
            _ => panic!("Expected a many active function"),
×
549
        }
550
    }
1✔
551

552
    #[test]
553
    fn function_parse_should_parse_a_checksum_function() {
1✔
554
        let output = Function::parse("checksum(\"Cargo.toml\", DEADBEEF)").unwrap();
1✔
555

1✔
556
        assert!(output.0.is_empty());
1✔
557
        match output.1 {
1✔
558
            Function::Checksum(path, crc) => {
1✔
559
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
560
                assert_eq!(0xDEADBEEF, crc);
1✔
561
            }
562
            _ => panic!("Expected a checksum function"),
×
563
        }
564
    }
1✔
565

566
    #[test]
567
    fn function_parse_should_error_if_the_checksum_path_is_outside_the_game_directory() {
1✔
568
        assert!(Function::parse("checksum(\"../../Cargo.toml\", DEADBEEF)").is_err());
1✔
569
    }
1✔
570

571
    #[test]
572
    fn function_parse_should_parse_a_version_equals_function() {
1✔
573
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
574

1✔
575
        assert!(output.0.is_empty());
1✔
576
        match output.1 {
1✔
577
            Function::Version(path, version, comparator) => {
1✔
578
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
579
                assert_eq!("1.2", version);
1✔
580
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
581
            }
582
            _ => panic!("Expected a version function"),
×
583
        }
584
    }
1✔
585

586
    #[test]
587
    fn function_parse_should_parse_a_version_not_equals_function() {
1✔
588
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", !=)").unwrap();
1✔
589

1✔
590
        assert!(output.0.is_empty());
1✔
591
        match output.1 {
1✔
592
            Function::Version(path, version, comparator) => {
1✔
593
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
594
                assert_eq!("1.2", version);
1✔
595
                assert_eq!(ComparisonOperator::NotEqual, comparator);
1✔
596
            }
597
            _ => panic!("Expected a version function"),
×
598
        }
599
    }
1✔
600

601
    #[test]
602
    fn function_parse_should_parse_a_version_less_than_function() {
1✔
603
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", <)").unwrap();
1✔
604

1✔
605
        assert!(output.0.is_empty());
1✔
606
        match output.1 {
1✔
607
            Function::Version(path, version, comparator) => {
1✔
608
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
609
                assert_eq!("1.2", version);
1✔
610
                assert_eq!(ComparisonOperator::LessThan, comparator);
1✔
611
            }
612
            _ => panic!("Expected a version function"),
×
613
        }
614
    }
1✔
615

616
    #[test]
617
    fn function_parse_should_parse_a_version_greater_than_function() {
1✔
618
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", >)").unwrap();
1✔
619

1✔
620
        assert!(output.0.is_empty());
1✔
621
        match output.1 {
1✔
622
            Function::Version(path, version, comparator) => {
1✔
623
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
624
                assert_eq!("1.2", version);
1✔
625
                assert_eq!(ComparisonOperator::GreaterThan, comparator);
1✔
626
            }
627
            _ => panic!("Expected a version function"),
×
628
        }
629
    }
1✔
630

631
    #[test]
632
    fn function_parse_should_parse_a_version_less_than_or_equal_to_function() {
1✔
633
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", <=)").unwrap();
1✔
634

1✔
635
        assert!(output.0.is_empty());
1✔
636
        match output.1 {
1✔
637
            Function::Version(path, version, comparator) => {
1✔
638
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
639
                assert_eq!("1.2", version);
1✔
640
                assert_eq!(ComparisonOperator::LessThanOrEqual, comparator);
1✔
641
            }
642
            _ => panic!("Expected a version function"),
×
643
        }
644
    }
1✔
645

646
    #[test]
647
    fn function_parse_should_parse_a_version_greater_than_or_equal_to_function() {
1✔
648
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", >=)").unwrap();
1✔
649

1✔
650
        assert!(output.0.is_empty());
1✔
651
        match output.1 {
1✔
652
            Function::Version(path, version, comparator) => {
1✔
653
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
654
                assert_eq!("1.2", version);
1✔
655
                assert_eq!(ComparisonOperator::GreaterThanOrEqual, comparator);
1✔
656
            }
657
            _ => panic!("Expected a version function"),
×
658
        }
659
    }
1✔
660

661
    #[test]
662
    fn function_parse_should_parse_a_version_with_a_path_containing_backslashes() {
1✔
663
        let output = Function::parse("version(\"..\\Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
664

1✔
665
        assert!(output.0.is_empty());
1✔
666
        match output.1 {
1✔
667
            Function::Version(path, version, comparator) => {
1✔
668
                assert_eq!(Path::new("..\\Cargo.toml"), path);
1✔
669
                assert_eq!("1.2", version);
1✔
670
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
671
            }
672
            _ => panic!("Expected a version function"),
×
673
        }
674
    }
1✔
675

676
    #[test]
677
    fn function_parse_should_error_if_the_version_path_is_outside_the_game_directory() {
1✔
678
        assert!(Function::parse("version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
679
    }
1✔
680

681
    #[test]
682
    fn function_parse_should_parse_a_product_version_equals_function() {
1✔
683
        let output = Function::parse("product_version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
684

1✔
685
        assert!(output.0.is_empty());
1✔
686
        match output.1 {
1✔
687
            Function::ProductVersion(path, version, comparator) => {
1✔
688
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
689
                assert_eq!("1.2", version);
1✔
690
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
691
            }
692
            _ => panic!("Expected a product version function"),
×
693
        }
694
    }
1✔
695

696
    #[test]
697
    fn function_parse_should_error_if_the_product_version_path_is_outside_the_game_directory() {
1✔
698
        assert!(Function::parse("product_version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
699
    }
1✔
700

701
    #[test]
702
    fn function_parse_should_parse_a_filename_version_equals_function() {
1✔
703
        let output =
1✔
704
            Function::parse("filename_version(\"subdir/Cargo (.+).toml\", \"1.2\", ==)").unwrap();
1✔
705

1✔
706
        assert!(output.0.is_empty());
1✔
707
        match output.1 {
1✔
708
            Function::FilenameVersion(path, regex, version, comparator) => {
1✔
709
                assert_eq!(PathBuf::from("subdir"), path);
1✔
710
                assert_eq!(
1✔
711
                    Regex::new("^Cargo (.+).toml$").unwrap().as_str(),
1✔
712
                    regex.as_str()
1✔
713
                );
1✔
714
                assert_eq!("1.2", version);
1✔
715
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
716
            }
NEW
717
            _ => panic!("Expected a filename version function"),
×
718
        }
719
    }
1✔
720

721
    #[test]
722
    fn function_parse_should_error_if_the_filename_version_regex_does_not_contain_an_explicit_capture_group(
1✔
723
    ) {
1✔
724
        assert!(
1✔
725
            Function::parse("filename_version(\"subdir/Cargo .+.toml\", \"1.2\", ==)").is_err()
1✔
726
        );
1✔
727
    }
1✔
728
}
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