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

loot / loot-condition-interpreter / 13038120828

29 Jan 2025 06:38PM UTC coverage: 89.746% (+0.4%) from 89.302%
13038120828

push

github

Ortham
Update versions and changelog for v5.0.0

4070 of 4535 relevant lines covered (89.75%)

15.52 hits per line

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

94.92
/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::hex_digit1;
7
use nom::combinator::{map, map_parser, value};
8
use nom::sequence::delimited;
9
use nom::{Err, IResult, Parser};
10
use regex::{Regex, RegexBuilder};
11

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

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

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

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

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

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

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

68
fn parse_version_args(input: &str) -> ParsingResult<(PathBuf, String, ComparisonOperator)> {
15✔
69
    let version_parser = map(
15✔
70
        delimited(tag("\""), is_not("\""), tag("\"")),
15✔
71
        |version: &str| version.to_string(),
15✔
72
    );
15✔
73

15✔
74
    let parser = (
15✔
75
        parse_path,
15✔
76
        whitespace(tag(",")),
15✔
77
        version_parser,
15✔
78
        whitespace(tag(",")),
15✔
79
        ComparisonOperator::parse,
15✔
80
    );
15✔
81

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

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

91
fn parse_crc(input: &str) -> ParsingResult<u32> {
13✔
92
    u32::from_str_radix(input, 16)
13✔
93
        .map(|c| ("", c))
13✔
94
        .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input)))
13✔
95
}
13✔
96

97
fn parse_checksum_args(input: &str) -> ParsingResult<(PathBuf, u32)> {
13✔
98
    let mut parser = (
13✔
99
        map_err(parse_path),
13✔
100
        map_err(whitespace(tag(","))),
13✔
101
        map_parser(hex_digit1, parse_crc),
13✔
102
    );
13✔
103

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

106
    if is_in_game_path(&path) {
12✔
107
        Ok((remaining_input, (path, crc)))
11✔
108
    } else {
109
        Err(not_in_game_directory(input, path))
1✔
110
    }
111
}
13✔
112

113
fn parse_non_regex_path(input: &str) -> ParsingResult<PathBuf> {
35✔
114
    let (remaining_input, path) = map(is_not(INVALID_NON_REGEX_PATH_CHARS), |path: &str| {
35✔
115
        PathBuf::from(path)
35✔
116
    })
35✔
117
    .parse(input)?;
35✔
118

119
    if is_in_game_path(&path) {
35✔
120
        Ok((remaining_input, path))
27✔
121
    } else {
122
        Err(not_in_game_directory(input, path))
8✔
123
    }
124
}
35✔
125

126
/// Parse a string that is a path where the last component is a regex string
127
/// that may contain characters that are invalid in paths but valid in regex.
128
fn parse_regex_path(input: &str) -> ParsingResult<(PathBuf, Regex)> {
10✔
129
    let (remaining_input, string) = is_not(INVALID_REGEX_PATH_CHARS)(input)?;
10✔
130

131
    if string.ends_with('/') {
10✔
132
        return Err(Err::Failure(
3✔
133
            ParsingErrorKind::PathEndsInADirectorySeparator(string.into()).at(input),
3✔
134
        ));
3✔
135
    }
7✔
136

7✔
137
    let (parent_path_slice, regex_slice) = string
7✔
138
        .rfind('/')
7✔
139
        .map(|i| (&string[..i], &string[i + 1..]))
7✔
140
        .unwrap_or_else(|| (".", string));
7✔
141

7✔
142
    let parent_path = PathBuf::from(parent_path_slice);
7✔
143

7✔
144
    if !is_in_game_path(&parent_path) {
7✔
145
        return Err(not_in_game_directory(input, parent_path));
×
146
    }
7✔
147

148
    let regex = parse_regex(regex_slice)?.1;
7✔
149

150
    Ok((remaining_input, (parent_path, regex)))
6✔
151
}
10✔
152

153
fn parse_regex_filename(input: &str) -> ParsingResult<Regex> {
2✔
154
    map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex).parse(input)
2✔
155
}
2✔
156

157
impl Function {
158
    pub fn parse(input: &str) -> ParsingResult<Function> {
83✔
159
        alt((
83✔
160
            map(
83✔
161
                delimited(
83✔
162
                    map_err(tag("file(\"")),
83✔
163
                    parse_non_regex_path,
83✔
164
                    map_err(tag("\")")),
83✔
165
                ),
83✔
166
                Function::FilePath,
83✔
167
            ),
83✔
168
            map(
83✔
169
                delimited(
83✔
170
                    map_err(tag("file(\"")),
83✔
171
                    parse_regex_path,
83✔
172
                    map_err(tag("\")")),
83✔
173
                ),
83✔
174
                |(path, regex)| Function::FileRegex(path, regex),
83✔
175
            ),
83✔
176
            map(
83✔
177
                delimited(
83✔
178
                    map_err(tag("readable(\"")),
83✔
179
                    parse_non_regex_path,
83✔
180
                    map_err(tag("\")")),
83✔
181
                ),
83✔
182
                Function::Readable,
83✔
183
            ),
83✔
184
            map(
83✔
185
                delimited(
83✔
186
                    map_err(tag("is_executable(\"")),
83✔
187
                    parse_non_regex_path,
83✔
188
                    map_err(tag("\")")),
83✔
189
                ),
83✔
190
                Function::IsExecutable,
83✔
191
            ),
83✔
192
            map(
83✔
193
                delimited(
83✔
194
                    map_err(tag("active(\"")),
83✔
195
                    parse_non_regex_path,
83✔
196
                    map_err(tag("\")")),
83✔
197
                ),
83✔
198
                Function::ActivePath,
83✔
199
            ),
83✔
200
            map(
83✔
201
                delimited(
83✔
202
                    map_err(tag("active(\"")),
83✔
203
                    parse_regex_filename,
83✔
204
                    map_err(tag("\")")),
83✔
205
                ),
83✔
206
                Function::ActiveRegex,
83✔
207
            ),
83✔
208
            map(
83✔
209
                delimited(
83✔
210
                    map_err(tag("is_master(\"")),
83✔
211
                    parse_non_regex_path,
83✔
212
                    map_err(tag("\")")),
83✔
213
                ),
83✔
214
                Function::IsMaster,
83✔
215
            ),
83✔
216
            map(
83✔
217
                delimited(
83✔
218
                    map_err(tag("many(\"")),
83✔
219
                    parse_regex_path,
83✔
220
                    map_err(tag("\")")),
83✔
221
                ),
83✔
222
                |(path, regex)| Function::Many(path, regex),
83✔
223
            ),
83✔
224
            map(
83✔
225
                delimited(
83✔
226
                    map_err(tag("many_active(\"")),
83✔
227
                    parse_regex_filename,
83✔
228
                    map_err(tag("\")")),
83✔
229
                ),
83✔
230
                Function::ManyActive,
83✔
231
            ),
83✔
232
            map(
83✔
233
                delimited(
83✔
234
                    map_err(tag("version(")),
83✔
235
                    parse_version_args,
83✔
236
                    map_err(tag(")")),
83✔
237
                ),
83✔
238
                |(path, version, comparator)| Function::Version(path, version, comparator),
83✔
239
            ),
83✔
240
            map(
83✔
241
                delimited(
83✔
242
                    map_err(tag("product_version(")),
83✔
243
                    parse_version_args,
83✔
244
                    map_err(tag(")")),
83✔
245
                ),
83✔
246
                |(path, version, comparator)| Function::ProductVersion(path, version, comparator),
83✔
247
            ),
83✔
248
            map(
83✔
249
                delimited(
83✔
250
                    map_err(tag("checksum(")),
83✔
251
                    parse_checksum_args,
83✔
252
                    map_err(tag(")")),
83✔
253
                ),
83✔
254
                |(path, crc)| Function::Checksum(path, crc),
83✔
255
            ),
83✔
256
        ))
83✔
257
        .parse(input)
83✔
258
    }
83✔
259
}
260

261
#[cfg(test)]
262
mod tests {
263
    use super::*;
264

265
    use std::path::Path;
266

267
    #[test]
268
    fn parse_regex_should_produce_case_insensitive_regex() {
1✔
269
        let (_, regex) = parse_regex("cargo.*").unwrap();
1✔
270

1✔
271
        assert!(regex.is_match("Cargo.toml"));
1✔
272
    }
1✔
273

274
    #[test]
275
    fn parse_regex_should_produce_a_regex_that_does_not_partially_match() {
1✔
276
        let (_, regex) = parse_regex("cargo.").unwrap();
1✔
277

1✔
278
        assert!(!regex.is_match("Cargo.toml"));
1✔
279
    }
1✔
280

281
    #[test]
282
    fn function_parse_should_parse_a_file_path_function() {
1✔
283
        let output = Function::parse("file(\"Cargo.toml\")").unwrap();
1✔
284

1✔
285
        assert!(output.0.is_empty());
1✔
286
        match output.1 {
1✔
287
            Function::FilePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
288
            _ => panic!("Expected a file path function"),
×
289
        }
290
    }
1✔
291

292
    #[test]
293
    fn function_parse_should_error_if_the_file_path_is_outside_the_game_directory() {
1✔
294
        assert!(Function::parse("file(\"../../Cargo.toml\")").is_err());
1✔
295
    }
1✔
296

297
    #[test]
298
    fn function_parse_should_parse_a_file_regex_function_with_no_parent_path() {
1✔
299
        let output = Function::parse("file(\"Cargo.*\")").unwrap();
1✔
300

1✔
301
        assert!(output.0.is_empty());
1✔
302
        match output.1 {
1✔
303
            Function::FileRegex(p, r) => {
1✔
304
                assert_eq!(PathBuf::from("."), p);
1✔
305
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
306
            }
307
            _ => panic!("Expected a file regex function"),
×
308
        }
309
    }
1✔
310

311
    #[test]
312
    fn function_parse_should_parse_a_file_regex_function_with_a_parent_path() {
1✔
313
        let output = Function::parse("file(\"subdir/Cargo.*\")").unwrap();
1✔
314

1✔
315
        assert!(output.0.is_empty());
1✔
316
        match output.1 {
1✔
317
            Function::FileRegex(p, r) => {
1✔
318
                assert_eq!(PathBuf::from("subdir"), p);
1✔
319
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
320
            }
321
            _ => panic!("Expected a file regex function"),
×
322
        }
323
    }
1✔
324

325
    #[test]
326
    fn function_parse_should_error_if_given_a_file_regex_function_ending_in_a_forward_slash() {
1✔
327
        assert!(Function::parse("file(\"sub\\dir/\")").is_err());
1✔
328
    }
1✔
329

330
    #[test]
331
    fn function_parse_should_error_if_the_file_regex_parent_path_is_outside_the_game_directory() {
1✔
332
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
333
    }
1✔
334

335
    #[test]
336
    fn function_parse_should_parse_a_readable_function() {
1✔
337
        let output = Function::parse("readable(\"Cargo.toml\")").unwrap();
1✔
338

1✔
339
        assert!(output.0.is_empty());
1✔
340
        match output.1 {
1✔
341
            Function::Readable(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
342
            _ => panic!("Expected a readable function"),
×
343
        }
344
    }
1✔
345

346
    #[test]
347
    fn function_parse_should_error_if_the_readable_path_is_outside_the_game_directory() {
1✔
348
        assert!(Function::parse("readable(\"../../Cargo.toml\")").is_err());
1✔
349
    }
1✔
350

351
    #[test]
352
    fn function_parse_should_parse_an_is_executable_function() {
1✔
353
        let output = Function::parse("is_executable(\"Cargo.toml\")").unwrap();
1✔
354

1✔
355
        assert!(output.0.is_empty());
1✔
356
        match output.1 {
1✔
357
            Function::IsExecutable(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
358
            _ => panic!("Expected an is_executable function"),
×
359
        }
360
    }
1✔
361

362
    #[test]
363
    fn function_parse_should_error_if_the_is_executable_path_is_outside_the_game_directory() {
1✔
364
        assert!(Function::parse("is_executable(\"../../Cargo.toml\")").is_err());
1✔
365
    }
1✔
366

367
    #[test]
368
    fn function_parse_should_parse_an_active_path_function() {
1✔
369
        let output = Function::parse("active(\"Cargo.toml\")").unwrap();
1✔
370

1✔
371
        assert!(output.0.is_empty());
1✔
372
        match output.1 {
1✔
373
            Function::ActivePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
374
            _ => panic!("Expected an active path function"),
×
375
        }
376
    }
1✔
377

378
    #[test]
379
    fn function_parse_should_error_if_the_active_path_is_outside_the_game_directory() {
1✔
380
        // Trying to check if a path that isn't a plugin in the data folder is
1✔
381
        // active is pointless, but it's not worth having a more specific check.
1✔
382
        assert!(Function::parse("active(\"../../Cargo.toml\")").is_err());
1✔
383
    }
1✔
384

385
    #[test]
386
    fn function_parse_should_parse_an_active_regex_function() {
1✔
387
        let output = Function::parse("active(\"Cargo.*\")").unwrap();
1✔
388

1✔
389
        assert!(output.0.is_empty());
1✔
390
        match output.1 {
1✔
391
            Function::ActiveRegex(r) => {
1✔
392
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
393
            }
394
            _ => panic!("Expected an active regex function"),
×
395
        }
396
    }
1✔
397

398
    #[test]
399
    fn function_parse_should_parse_an_is_master_function() {
1✔
400
        let output = Function::parse("is_master(\"Blank.esm\")").unwrap();
1✔
401

1✔
402
        assert!(output.0.is_empty());
1✔
403
        match output.1 {
1✔
404
            Function::IsMaster(f) => assert_eq!(Path::new("Blank.esm"), f),
1✔
405
            _ => panic!("Expected an is master function"),
×
406
        }
407
    }
1✔
408

409
    #[test]
410
    fn function_parse_should_error_if_the_is_master_path_is_outside_the_game_directory() {
1✔
411
        // Trying to check if a path that isn't a plugin in the data folder is
1✔
412
        // active is pointless, but it's not worth having a more specific check.
1✔
413
        assert!(Function::parse("is_master(\"../../Blank.esm\")").is_err());
1✔
414
    }
1✔
415

416
    #[test]
417
    fn function_parse_should_parse_a_many_function_with_no_parent_path() {
1✔
418
        let output = Function::parse("many(\"Cargo.*\")").unwrap();
1✔
419

1✔
420
        assert!(output.0.is_empty());
1✔
421
        match output.1 {
1✔
422
            Function::Many(p, r) => {
1✔
423
                assert_eq!(PathBuf::from("."), p);
1✔
424
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
425
            }
426
            _ => panic!("Expected a many function"),
×
427
        }
428
    }
1✔
429

430
    #[test]
431
    fn function_parse_should_parse_a_many_function_with_a_parent_path() {
1✔
432
        let output = Function::parse("many(\"subdir/Cargo.*\")").unwrap();
1✔
433

1✔
434
        assert!(output.0.is_empty());
1✔
435
        match output.1 {
1✔
436
            Function::Many(p, r) => {
1✔
437
                assert_eq!(PathBuf::from("subdir"), p);
1✔
438
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
439
            }
440
            _ => panic!("Expected a many function"),
×
441
        }
442
    }
1✔
443

444
    #[test]
445
    fn function_parse_should_error_if_given_a_many_function_ending_in_a_forward_slash() {
1✔
446
        assert!(Function::parse("many(\"subdir/\")").is_err());
1✔
447
    }
1✔
448

449
    #[test]
450
    fn function_parse_should_error_if_the_many_parent_path_is_outside_the_game_directory() {
1✔
451
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
452
    }
1✔
453

454
    #[test]
455
    fn function_parse_should_parse_a_many_active_function() {
1✔
456
        let output = Function::parse("many_active(\"Cargo.*\")").unwrap();
1✔
457

1✔
458
        assert!(output.0.is_empty());
1✔
459
        match output.1 {
1✔
460
            Function::ManyActive(r) => {
1✔
461
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
462
            }
463
            _ => panic!("Expected a many active function"),
×
464
        }
465
    }
1✔
466

467
    #[test]
468
    fn function_parse_should_parse_a_checksum_function() {
1✔
469
        let output = Function::parse("checksum(\"Cargo.toml\", DEADBEEF)").unwrap();
1✔
470

1✔
471
        assert!(output.0.is_empty());
1✔
472
        match output.1 {
1✔
473
            Function::Checksum(path, crc) => {
1✔
474
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
475
                assert_eq!(0xDEADBEEF, crc);
1✔
476
            }
477
            _ => panic!("Expected a checksum function"),
×
478
        }
479
    }
1✔
480

481
    #[test]
482
    fn function_parse_should_error_if_the_checksum_path_is_outside_the_game_directory() {
1✔
483
        assert!(Function::parse("checksum(\"../../Cargo.toml\", DEADBEEF)").is_err());
1✔
484
    }
1✔
485

486
    #[test]
487
    fn function_parse_should_parse_a_version_equals_function() {
1✔
488
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
489

1✔
490
        assert!(output.0.is_empty());
1✔
491
        match output.1 {
1✔
492
            Function::Version(path, version, comparator) => {
1✔
493
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
494
                assert_eq!("1.2", version);
1✔
495
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
496
            }
497
            _ => panic!("Expected a version function"),
×
498
        }
499
    }
1✔
500

501
    #[test]
502
    fn function_parse_should_parse_a_version_not_equals_function() {
1✔
503
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", !=)").unwrap();
1✔
504

1✔
505
        assert!(output.0.is_empty());
1✔
506
        match output.1 {
1✔
507
            Function::Version(path, version, comparator) => {
1✔
508
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
509
                assert_eq!("1.2", version);
1✔
510
                assert_eq!(ComparisonOperator::NotEqual, comparator);
1✔
511
            }
512
            _ => panic!("Expected a version function"),
×
513
        }
514
    }
1✔
515

516
    #[test]
517
    fn function_parse_should_parse_a_version_less_than_function() {
1✔
518
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", <)").unwrap();
1✔
519

1✔
520
        assert!(output.0.is_empty());
1✔
521
        match output.1 {
1✔
522
            Function::Version(path, version, comparator) => {
1✔
523
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
524
                assert_eq!("1.2", version);
1✔
525
                assert_eq!(ComparisonOperator::LessThan, comparator);
1✔
526
            }
527
            _ => panic!("Expected a version function"),
×
528
        }
529
    }
1✔
530

531
    #[test]
532
    fn function_parse_should_parse_a_version_greater_than_function() {
1✔
533
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", >)").unwrap();
1✔
534

1✔
535
        assert!(output.0.is_empty());
1✔
536
        match output.1 {
1✔
537
            Function::Version(path, version, comparator) => {
1✔
538
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
539
                assert_eq!("1.2", version);
1✔
540
                assert_eq!(ComparisonOperator::GreaterThan, comparator);
1✔
541
            }
542
            _ => panic!("Expected a version function"),
×
543
        }
544
    }
1✔
545

546
    #[test]
547
    fn function_parse_should_parse_a_version_less_than_or_equal_to_function() {
1✔
548
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", <=)").unwrap();
1✔
549

1✔
550
        assert!(output.0.is_empty());
1✔
551
        match output.1 {
1✔
552
            Function::Version(path, version, comparator) => {
1✔
553
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
554
                assert_eq!("1.2", version);
1✔
555
                assert_eq!(ComparisonOperator::LessThanOrEqual, comparator);
1✔
556
            }
557
            _ => panic!("Expected a version function"),
×
558
        }
559
    }
1✔
560

561
    #[test]
562
    fn function_parse_should_parse_a_version_greater_than_or_equal_to_function() {
1✔
563
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", >=)").unwrap();
1✔
564

1✔
565
        assert!(output.0.is_empty());
1✔
566
        match output.1 {
1✔
567
            Function::Version(path, version, comparator) => {
1✔
568
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
569
                assert_eq!("1.2", version);
1✔
570
                assert_eq!(ComparisonOperator::GreaterThanOrEqual, comparator);
1✔
571
            }
572
            _ => panic!("Expected a version function"),
×
573
        }
574
    }
1✔
575

576
    #[test]
577
    fn function_parse_should_parse_a_version_with_a_path_containing_backslashes() {
1✔
578
        let output = Function::parse("version(\"..\\Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
579

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

591
    #[test]
592
    fn function_parse_should_error_if_the_version_path_is_outside_the_game_directory() {
1✔
593
        assert!(Function::parse("version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
594
    }
1✔
595

596
    #[test]
597
    fn function_parse_should_parse_a_product_version_equals_function() {
1✔
598
        let output = Function::parse("product_version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
599

1✔
600
        assert!(output.0.is_empty());
1✔
601
        match output.1 {
1✔
602
            Function::ProductVersion(path, version, comparator) => {
1✔
603
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
604
                assert_eq!("1.2", version);
1✔
605
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
606
            }
607
            _ => panic!("Expected a product version function"),
×
608
        }
609
    }
1✔
610

611
    #[test]
612
    fn function_parse_should_error_if_the_product_version_path_is_outside_the_game_directory() {
1✔
613
        assert!(Function::parse("product_version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
614
    }
1✔
615
}
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