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

loot / loot-condition-interpreter / 12774031983

14 Jan 2025 06:21PM UTC coverage: 88.996% (+0.03%) from 88.97%
12774031983

push

github

Ortham
Force-install cbindgen in CI

3688 of 4144 relevant lines covered (89.0%)

15.42 hits per line

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

94.88
/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, tuple};
9
use nom::{Err, IResult};
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
        ))(input)
15✔
26
    }
15✔
27
}
28

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

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

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

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

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

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

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

80
    let (remaining_input, (path, _, version, _, comparator)) = map_err(parser)(input)?;
15✔
81

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

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

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

102
    let (remaining_input, (path, _, crc)) = parser(input)?;
13✔
103

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

111
fn parse_non_regex_path(input: &str) -> ParsingResult<PathBuf> {
33✔
112
    let (remaining_input, path) = map(is_not(INVALID_NON_REGEX_PATH_CHARS), |path: &str| {
33✔
113
        PathBuf::from(path)
33✔
114
    })(input)?;
33✔
115

116
    if is_in_game_path(&path) {
33✔
117
        Ok((remaining_input, path))
26✔
118
    } else {
119
        Err(not_in_game_directory(input, path))
7✔
120
    }
121
}
33✔
122

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

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

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

7✔
139
    let parent_path = PathBuf::from(parent_path_slice);
7✔
140

7✔
141
    if !is_in_game_path(&parent_path) {
7✔
142
        return Err(not_in_game_directory(input, parent_path));
×
143
    }
7✔
144

145
    let regex = parse_regex(regex_slice)?.1;
7✔
146

147
    Ok((remaining_input, (parent_path, regex)))
6✔
148
}
10✔
149

150
fn parse_regex_filename(input: &str) -> ParsingResult<Regex> {
2✔
151
    map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex)(input)
2✔
152
}
2✔
153

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

249
#[cfg(test)]
250
mod tests {
251
    use super::*;
252

253
    use std::path::Path;
254

255
    #[test]
256
    fn parse_regex_should_produce_case_insensitive_regex() {
1✔
257
        let (_, regex) = parse_regex("cargo.*").unwrap();
1✔
258

1✔
259
        assert!(regex.is_match("Cargo.toml"));
1✔
260
    }
1✔
261

262
    #[test]
263
    fn parse_regex_should_produce_a_regex_that_does_not_partially_match() {
1✔
264
        let (_, regex) = parse_regex("cargo.").unwrap();
1✔
265

1✔
266
        assert!(!regex.is_match("Cargo.toml"));
1✔
267
    }
1✔
268

269
    #[test]
270
    fn function_parse_should_parse_a_file_path_function() {
1✔
271
        let output = Function::parse("file(\"Cargo.toml\")").unwrap();
1✔
272

1✔
273
        assert!(output.0.is_empty());
1✔
274
        match output.1 {
1✔
275
            Function::FilePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
276
            _ => panic!("Expected a file path function"),
×
277
        }
278
    }
1✔
279

280
    #[test]
281
    fn function_parse_should_error_if_the_file_path_is_outside_the_game_directory() {
1✔
282
        assert!(Function::parse("file(\"../../Cargo.toml\")").is_err());
1✔
283
    }
1✔
284

285
    #[test]
286
    fn function_parse_should_parse_a_file_regex_function_with_no_parent_path() {
1✔
287
        let output = Function::parse("file(\"Cargo.*\")").unwrap();
1✔
288

1✔
289
        assert!(output.0.is_empty());
1✔
290
        match output.1 {
1✔
291
            Function::FileRegex(p, r) => {
1✔
292
                assert_eq!(PathBuf::from("."), p);
1✔
293
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
294
            }
295
            _ => panic!("Expected a file regex function"),
×
296
        }
297
    }
1✔
298

299
    #[test]
300
    fn function_parse_should_parse_a_file_regex_function_with_a_parent_path() {
1✔
301
        let output = Function::parse("file(\"subdir/Cargo.*\")").unwrap();
1✔
302

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

313
    #[test]
314
    fn function_parse_should_error_if_given_a_file_regex_function_ending_in_a_forward_slash() {
1✔
315
        assert!(Function::parse("file(\"sub\\dir/\")").is_err());
1✔
316
    }
1✔
317

318
    #[test]
319
    fn function_parse_should_error_if_the_file_regex_parent_path_is_outside_the_game_directory() {
1✔
320
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
321
    }
1✔
322

323
    #[test]
324
    fn function_parse_should_parse_a_readable_function() {
1✔
325
        let output = Function::parse("readable(\"Cargo.toml\")").unwrap();
1✔
326

1✔
327
        assert!(output.0.is_empty());
1✔
328
        match output.1 {
1✔
329
            Function::Readable(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
330
            _ => panic!("Expected a file path function"),
×
331
        }
332
    }
1✔
333

334
    #[test]
335
    fn function_parse_should_error_if_the_readable_path_is_outside_the_game_directory() {
1✔
336
        assert!(Function::parse("readable(\"../../Cargo.toml\")").is_err());
1✔
337
    }
1✔
338

339
    #[test]
340
    fn function_parse_should_parse_an_active_path_function() {
1✔
341
        let output = Function::parse("active(\"Cargo.toml\")").unwrap();
1✔
342

1✔
343
        assert!(output.0.is_empty());
1✔
344
        match output.1 {
1✔
345
            Function::ActivePath(f) => assert_eq!(Path::new("Cargo.toml"), f),
1✔
346
            _ => panic!("Expected an active path function"),
×
347
        }
348
    }
1✔
349

350
    #[test]
351
    fn function_parse_should_error_if_the_active_path_is_outside_the_game_directory() {
1✔
352
        // Trying to check if a path that isn't a plugin in the data folder is
1✔
353
        // active is pointless, but it's not worth having a more specific check.
1✔
354
        assert!(Function::parse("active(\"../../Cargo.toml\")").is_err());
1✔
355
    }
1✔
356

357
    #[test]
358
    fn function_parse_should_parse_an_active_regex_function() {
1✔
359
        let output = Function::parse("active(\"Cargo.*\")").unwrap();
1✔
360

1✔
361
        assert!(output.0.is_empty());
1✔
362
        match output.1 {
1✔
363
            Function::ActiveRegex(r) => {
1✔
364
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
365
            }
366
            _ => panic!("Expected an active regex function"),
×
367
        }
368
    }
1✔
369

370
    #[test]
371
    fn function_parse_should_parse_an_is_master_function() {
1✔
372
        let output = Function::parse("is_master(\"Blank.esm\")").unwrap();
1✔
373

1✔
374
        assert!(output.0.is_empty());
1✔
375
        match output.1 {
1✔
376
            Function::IsMaster(f) => assert_eq!(Path::new("Blank.esm"), f),
1✔
377
            _ => panic!("Expected an is master function"),
×
378
        }
379
    }
1✔
380

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

388
    #[test]
389
    fn function_parse_should_parse_a_many_function_with_no_parent_path() {
1✔
390
        let output = Function::parse("many(\"Cargo.*\")").unwrap();
1✔
391

1✔
392
        assert!(output.0.is_empty());
1✔
393
        match output.1 {
1✔
394
            Function::Many(p, r) => {
1✔
395
                assert_eq!(PathBuf::from("."), p);
1✔
396
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
397
            }
398
            _ => panic!("Expected a many function"),
×
399
        }
400
    }
1✔
401

402
    #[test]
403
    fn function_parse_should_parse_a_many_function_with_a_parent_path() {
1✔
404
        let output = Function::parse("many(\"subdir/Cargo.*\")").unwrap();
1✔
405

1✔
406
        assert!(output.0.is_empty());
1✔
407
        match output.1 {
1✔
408
            Function::Many(p, r) => {
1✔
409
                assert_eq!(PathBuf::from("subdir"), p);
1✔
410
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str());
1✔
411
            }
412
            _ => panic!("Expected a many function"),
×
413
        }
414
    }
1✔
415

416
    #[test]
417
    fn function_parse_should_error_if_given_a_many_function_ending_in_a_forward_slash() {
1✔
418
        assert!(Function::parse("many(\"subdir/\")").is_err());
1✔
419
    }
1✔
420

421
    #[test]
422
    fn function_parse_should_error_if_the_many_parent_path_is_outside_the_game_directory() {
1✔
423
        assert!(Function::parse("file(\"../../Cargo.*\")").is_err());
1✔
424
    }
1✔
425

426
    #[test]
427
    fn function_parse_should_parse_a_many_active_function() {
1✔
428
        let output = Function::parse("many_active(\"Cargo.*\")").unwrap();
1✔
429

1✔
430
        assert!(output.0.is_empty());
1✔
431
        match output.1 {
1✔
432
            Function::ManyActive(r) => {
1✔
433
                assert_eq!(Regex::new("^Cargo.*$").unwrap().as_str(), r.as_str())
1✔
434
            }
435
            _ => panic!("Expected a many active function"),
×
436
        }
437
    }
1✔
438

439
    #[test]
440
    fn function_parse_should_parse_a_checksum_function() {
1✔
441
        let output = Function::parse("checksum(\"Cargo.toml\", DEADBEEF)").unwrap();
1✔
442

1✔
443
        assert!(output.0.is_empty());
1✔
444
        match output.1 {
1✔
445
            Function::Checksum(path, crc) => {
1✔
446
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
447
                assert_eq!(0xDEADBEEF, crc);
1✔
448
            }
449
            _ => panic!("Expected a checksum function"),
×
450
        }
451
    }
1✔
452

453
    #[test]
454
    fn function_parse_should_error_if_the_checksum_path_is_outside_the_game_directory() {
1✔
455
        assert!(Function::parse("checksum(\"../../Cargo.toml\", DEADBEEF)").is_err());
1✔
456
    }
1✔
457

458
    #[test]
459
    fn function_parse_should_parse_a_version_equals_function() {
1✔
460
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
461

1✔
462
        assert!(output.0.is_empty());
1✔
463
        match output.1 {
1✔
464
            Function::Version(path, version, comparator) => {
1✔
465
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
466
                assert_eq!("1.2", version);
1✔
467
                assert_eq!(ComparisonOperator::Equal, comparator);
1✔
468
            }
469
            _ => panic!("Expected a version function"),
×
470
        }
471
    }
1✔
472

473
    #[test]
474
    fn function_parse_should_parse_a_version_not_equals_function() {
1✔
475
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", !=)").unwrap();
1✔
476

1✔
477
        assert!(output.0.is_empty());
1✔
478
        match output.1 {
1✔
479
            Function::Version(path, version, comparator) => {
1✔
480
                assert_eq!(Path::new("Cargo.toml"), path);
1✔
481
                assert_eq!("1.2", version);
1✔
482
                assert_eq!(ComparisonOperator::NotEqual, comparator);
1✔
483
            }
484
            _ => panic!("Expected a version function"),
×
485
        }
486
    }
1✔
487

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

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

503
    #[test]
504
    fn function_parse_should_parse_a_version_greater_than_function() {
1✔
505
        let output = Function::parse("version(\"Cargo.toml\", \"1.2\", >)").unwrap();
1✔
506

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

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

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

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

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

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

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

563
    #[test]
564
    fn function_parse_should_error_if_the_version_path_is_outside_the_game_directory() {
1✔
565
        assert!(Function::parse("version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
566
    }
1✔
567

568
    #[test]
569
    fn function_parse_should_parse_a_product_version_equals_function() {
1✔
570
        let output = Function::parse("product_version(\"Cargo.toml\", \"1.2\", ==)").unwrap();
1✔
571

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

583
    #[test]
584
    fn function_parse_should_error_if_the_product_version_path_is_outside_the_game_directory() {
1✔
585
        assert!(Function::parse("product_version(\"../../Cargo.toml\", \"1.2\", ==)").is_err());
1✔
586
    }
1✔
587
}
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