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

loot / loot-condition-interpreter / 14679793593

26 Apr 2025 09:10AM UTC coverage: 91.6% (+0.02%) from 91.583%
14679793593

push

github

Ortham
Deny a lot of extra lints and fix their errors

337 of 376 new or added lines in 10 files covered. (89.63%)

111 existing lines in 7 files now uncovered.

4907 of 5357 relevant lines covered (91.6%)

15.84 hits per line

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

99.87
/src/function/version.rs
1
use std::cmp::Ordering;
2
use std::path::Path;
3

4
use pelite::resources::version_info::VersionInfo;
5
use pelite::resources::{FindError, Resources};
6

7
use crate::error::Error;
8

9
#[derive(Clone, Debug)]
10
enum ReleaseId {
11
    Numeric(u32),
12
    NonNumeric(String),
13
}
14

15
impl<'a> From<&'a str> for ReleaseId {
16
    fn from(string: &'a str) -> Self {
798✔
17
        string.trim().parse().map_or_else(
798✔
18
            |_| ReleaseId::NonNumeric(string.to_lowercase()),
798✔
19
            ReleaseId::Numeric,
798✔
20
        )
798✔
21
    }
798✔
22
}
23

24
fn are_numeric_values_equal(n: u32, s: &str) -> bool {
6✔
25
    // The values can only be equal if the trimmed string can be wholly
6✔
26
    // converted to the same u32 value.
6✔
27
    match s.trim().parse() {
6✔
28
        Ok(n2) => n == n2,
4✔
29
        Err(_) => false,
2✔
30
    }
31
}
6✔
32

33
impl PartialEq for ReleaseId {
34
    fn eq(&self, other: &Self) -> bool {
209✔
35
        match (self, other) {
209✔
36
            (Self::Numeric(n1), Self::Numeric(n2)) => n1 == n2,
196✔
37
            (Self::NonNumeric(s1), Self::NonNumeric(s2)) => s1 == s2,
7✔
38
            (Self::Numeric(n), Self::NonNumeric(s)) | (Self::NonNumeric(s), Self::Numeric(n)) => {
3✔
39
                are_numeric_values_equal(*n, s)
6✔
40
            }
41
        }
42
    }
209✔
43
}
44

45
// This is like u32::from_str_radix(), but stops instead of erroring when it
46
// encounters a non-digit character. It also doesn't support signs.
47
fn u32_from_str(id: &str) -> (Option<u32>, usize) {
12✔
48
    // Conversion can fail even with only ASCII digits because of overflow, so
49
    // take that into account.
50
    if let Some((digits, remainder)) = id.split_once(|c: char| !c.is_ascii_digit()) {
28✔
51
        if digits.is_empty() {
10✔
52
            (None, id.len())
2✔
53
        } else {
54
            (digits.trim().parse().ok(), remainder.len() + 1)
8✔
55
        }
56
    } else {
57
        (id.trim().parse().ok(), 0)
2✔
58
    }
59
}
12✔
60

61
fn compare_heterogeneous_ids(lhs_number: u32, rhs_string: &str) -> Option<Ordering> {
12✔
62
    match u32_from_str(rhs_string) {
12✔
63
        (Some(rhs_number), remaining_slice_length) => {
10✔
64
            match lhs_number.partial_cmp(&rhs_number) {
10✔
65
                // If not all bytes were digits, treat the non-numeric ID as
66
                // greater.
67
                Some(Ordering::Equal) if remaining_slice_length > 0 => Some(Ordering::Less),
6✔
68
                order => order,
6✔
69
            }
70
        }
71
        // If there are no digits to compare, numeric values are
72
        // always less than non-numeric values.
73
        (None, _) => Some(Ordering::Less),
2✔
74
    }
75
}
12✔
76

77
impl PartialOrd for ReleaseId {
78
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219✔
79
        match (self, other) {
219✔
80
            (Self::Numeric(n1), Self::Numeric(n2)) => n1.partial_cmp(n2),
198✔
81
            (Self::NonNumeric(s1), Self::NonNumeric(s2)) => s1.partial_cmp(s2),
9✔
82
            (Self::Numeric(n), Self::NonNumeric(s)) => compare_heterogeneous_ids(*n, s),
6✔
83
            (Self::NonNumeric(s), Self::Numeric(n)) => {
6✔
84
                compare_heterogeneous_ids(*n, s).map(Ordering::reverse)
6✔
85
            }
86
        }
87
    }
219✔
88
}
89

90
#[derive(Clone, Debug, PartialEq, PartialOrd)]
91
enum PreReleaseId {
92
    Numeric(u32),
93
    NonNumeric(String),
94
}
95

96
impl<'a> From<&'a str> for PreReleaseId {
97
    fn from(string: &'a str) -> Self {
80✔
98
        string.trim().parse().map_or_else(
80✔
99
            |_| PreReleaseId::NonNumeric(string.to_lowercase()),
80✔
100
            PreReleaseId::Numeric,
80✔
101
        )
80✔
102
    }
80✔
103
}
104

105
#[derive(Debug)]
106
pub(super) struct Version {
107
    release_ids: Vec<ReleaseId>,
108
    pre_release_ids: Vec<PreReleaseId>,
109
}
110

111
impl Version {
112
    pub(super) fn read_file_version(file_path: &Path) -> Result<Option<Self>, Error> {
6✔
113
        Self::read_version(file_path, |v| {
6✔
114
            v.fixed().map(|f| {
3✔
115
                format!(
3✔
116
                    "{}.{}.{}.{}",
3✔
117
                    f.dwFileVersion.Major,
3✔
118
                    f.dwFileVersion.Minor,
3✔
119
                    f.dwFileVersion.Patch,
3✔
120
                    f.dwFileVersion.Build
3✔
121
                )
3✔
122
            })
3✔
123
        })
6✔
124
    }
6✔
125

126
    pub(super) fn read_product_version(file_path: &Path) -> Result<Option<Self>, Error> {
9✔
127
        Self::read_version(file_path, |v| {
9✔
128
            v.translation()
5✔
129
                .first()
5✔
130
                .and_then(|language| v.value(*language, "ProductVersion"))
5✔
131
        })
9✔
132
    }
9✔
133

134
    pub(super) fn is_readable(file_path: &Path) -> bool {
5✔
135
        Self::read_version(file_path, |_| None).is_ok()
5✔
136
    }
5✔
137

138
    fn read_version<F: Fn(VersionInfo) -> Option<String>>(
20✔
139
        file_path: &Path,
20✔
140
        formatter: F,
20✔
141
    ) -> Result<Option<Self>, Error> {
20✔
142
        #[cfg(any(windows, unix))]
143
        let result = {
15✔
144
            let file_map = pelite::FileMap::open(file_path)
20✔
145
                .map_err(|e| Error::IoError(file_path.to_path_buf(), e))?;
20✔
146

147
            get_pe_version_info(file_map.as_ref()).map(formatter)
15✔
148
        };
149

150
        #[cfg(not(any(windows, unix)))]
151
        let result = {
152
            let bytes =
153
                std::fs::read(file_path).map_err(|e| Error::IoError(file_path.to_path_buf(), e))?;
154

155
            get_pe_version_info(&bytes).map(formatter)
156
        };
157

158
        match result {
6✔
159
            Ok(s) => Ok(s.map(Version::from)),
9✔
160
            Err(FindError::NotFound) => Ok(None),
2✔
161
            Err(e) => Err(Error::PeParsingError(file_path.to_path_buf(), Box::new(e))),
4✔
162
        }
163
    }
20✔
164
}
165

166
fn get_pe_version_info(bytes: &[u8]) -> Result<VersionInfo, FindError> {
15✔
167
    get_pe_resources(bytes)?.version_info()
15✔
168
}
15✔
169

170
fn get_pe_resources(bytes: &[u8]) -> Result<Resources, pelite::Error> {
15✔
171
    use pelite::pe64;
172
    match pe64::PeFile::from_bytes(bytes) {
15✔
173
        Ok(file) => {
2✔
174
            use pelite::pe64::Pe;
175

176
            file.resources()
2✔
177
        }
178
        Err(pelite::Error::PeMagic) => {
179
            use pelite::pe32::{Pe, PeFile};
180

181
            PeFile::from_bytes(bytes)?.resources()
9✔
182
        }
183
        Err(e) => Err(e),
4✔
184
    }
185
}
15✔
186

187
fn is_separator(c: char) -> bool {
1,671✔
188
    c == '-' || c == ' ' || c == ':' || c == '_'
1,671✔
189
}
1,671✔
190

191
fn is_pre_release_separator(c: char) -> bool {
220✔
192
    c == '.' || is_separator(c)
220✔
193
}
220✔
194

195
fn split_version_string(string: &str) -> (&str, &str) {
287✔
196
    // Special case for strings of the form "0, 1, 2, 3", which are used in
197
    // OBSE and SKSE, and which should be interpreted as "0.1.2.3".
198
    if let Ok(regex) = regex::Regex::new("\\d+, \\d+, \\d+, \\d+") {
287✔
199
        if regex.is_match(string) {
287✔
200
            return (string, "");
1✔
201
        }
286✔
UNCOV
202
    }
×
203

204
    string.split_once(is_separator).unwrap_or((string, ""))
286✔
205
}
287✔
206

207
impl<T: AsRef<str>> From<T> for Version {
208
    fn from(string: T) -> Self {
287✔
209
        let (release, pre_release) = split_version_string(trim_metadata(string.as_ref()));
287✔
210

287✔
211
        Version {
287✔
212
            release_ids: release.split(['.', ',']).map(ReleaseId::from).collect(),
287✔
213
            pre_release_ids: pre_release
287✔
214
                .split_terminator(is_pre_release_separator)
287✔
215
                .map(PreReleaseId::from)
287✔
216
                .collect(),
287✔
217
        }
287✔
218
    }
287✔
219
}
220

221
fn trim_metadata(version: &str) -> &str {
287✔
222
    if version.is_empty() {
287✔
223
        "0"
8✔
224
    } else if let Some((prefix, _)) = version.split_once('+') {
279✔
225
        prefix
10✔
226
    } else {
227
        version
269✔
228
    }
229
}
287✔
230

231
impl PartialOrd for Version {
232
    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
78✔
233
        let (self_release_ids, other_release_ids) =
78✔
234
            pad_release_ids(&self.release_ids, &other.release_ids);
78✔
235

78✔
236
        match self_release_ids.partial_cmp(&other_release_ids) {
78✔
237
            Some(Ordering::Equal) | None => {
238
                match (
35✔
239
                    self.pre_release_ids.is_empty(),
35✔
240
                    other.pre_release_ids.is_empty(),
35✔
241
                ) {
35✔
242
                    (true, false) => Some(Ordering::Greater),
1✔
243
                    (false, true) => Some(Ordering::Less),
1✔
244
                    _ => self.pre_release_ids.partial_cmp(&other.pre_release_ids),
33✔
245
                }
246
            }
247
            r => r,
43✔
248
        }
249
    }
78✔
250
}
251

252
impl PartialEq for Version {
253
    fn eq(&self, other: &Version) -> bool {
59✔
254
        let (self_release_ids, other_release_ids) =
59✔
255
            pad_release_ids(&self.release_ids, &other.release_ids);
59✔
256

59✔
257
        self_release_ids == other_release_ids && self.pre_release_ids == other.pre_release_ids
59✔
258
    }
59✔
259
}
260

261
fn pad_release_ids(ids1: &[ReleaseId], ids2: &[ReleaseId]) -> (Vec<ReleaseId>, Vec<ReleaseId>) {
137✔
262
    let mut ids1 = ids1.to_vec();
137✔
263
    let mut ids2 = ids2.to_vec();
137✔
264

137✔
265
    match ids1.len().cmp(&ids2.len()) {
137✔
266
        Ordering::Less => ids1.resize(ids2.len(), ReleaseId::Numeric(0)),
6✔
267
        Ordering::Greater => ids2.resize(ids1.len(), ReleaseId::Numeric(0)),
6✔
268
        Ordering::Equal => {}
125✔
269
    }
270

271
    (ids1, ids2)
137✔
272
}
137✔
273

274
#[cfg(test)]
275
mod tests {
276
    fn is_cmp_eq(lhs: &super::Version, rhs: &super::Version) -> bool {
15✔
277
        lhs.partial_cmp(rhs).unwrap().is_eq()
15✔
278
    }
15✔
279

280
    mod release_ids {
281
        use super::super::*;
282

283
        #[test]
284
        fn eq_should_compare_equality_of_u32_values() {
1✔
285
            assert_eq!(ReleaseId::Numeric(1), ReleaseId::Numeric(1));
1✔
286
            assert_ne!(ReleaseId::Numeric(1), ReleaseId::Numeric(0));
1✔
287
        }
1✔
288

289
        #[test]
290
        fn eq_should_compare_equality_of_string_values() {
1✔
291
            assert_eq!(
1✔
292
                ReleaseId::NonNumeric("abcd".into()),
1✔
293
                ReleaseId::NonNumeric("abcd".into())
1✔
294
            );
1✔
295
            assert_ne!(
1✔
296
                ReleaseId::NonNumeric("abcd".into()),
1✔
297
                ReleaseId::NonNumeric("abce".into())
1✔
298
            );
1✔
299
        }
1✔
300

301
        #[test]
302
        fn eq_should_convert_string_values_to_u32_before_comparing_against_a_u32_value() {
1✔
303
            assert_eq!(ReleaseId::Numeric(123), ReleaseId::NonNumeric("123".into()));
1✔
304
            assert_eq!(
1✔
305
                ReleaseId::Numeric(123),
1✔
306
                ReleaseId::NonNumeric(" 123 ".into())
1✔
307
            );
1✔
308

309
            assert_ne!(
1✔
310
                ReleaseId::Numeric(123),
1✔
311
                ReleaseId::NonNumeric("1two3".into())
1✔
312
            );
1✔
313

314
            assert_eq!(ReleaseId::NonNumeric("123".into()), ReleaseId::Numeric(123));
1✔
315
            assert_eq!(
1✔
316
                ReleaseId::NonNumeric(" 123 ".into()),
1✔
317
                ReleaseId::Numeric(123)
1✔
318
            );
1✔
319

320
            assert_ne!(
1✔
321
                ReleaseId::NonNumeric("1two3".into()),
1✔
322
                ReleaseId::Numeric(123)
1✔
323
            );
1✔
324
        }
1✔
325

326
        #[test]
327
        fn cmp_should_compare_u32_values() {
1✔
328
            let cmp = ReleaseId::Numeric(1).partial_cmp(&ReleaseId::Numeric(1));
1✔
329
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
330

331
            let cmp = ReleaseId::Numeric(1).partial_cmp(&ReleaseId::Numeric(2));
1✔
332
            assert_eq!(Some(Ordering::Less), cmp);
1✔
333

334
            let cmp = ReleaseId::Numeric(2).partial_cmp(&ReleaseId::Numeric(1));
1✔
335
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
336
        }
1✔
337

338
        #[test]
339
        fn cmp_should_compare_string_values() {
1✔
340
            let cmp = ReleaseId::NonNumeric("alpha".into())
1✔
341
                .partial_cmp(&ReleaseId::NonNumeric("alpha".into()));
1✔
342
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
343

344
            let cmp = ReleaseId::NonNumeric("alpha".into())
1✔
345
                .partial_cmp(&ReleaseId::NonNumeric("beta".into()));
1✔
346
            assert_eq!(Some(Ordering::Less), cmp);
1✔
347

348
            let cmp = ReleaseId::NonNumeric("beta".into())
1✔
349
                .partial_cmp(&ReleaseId::NonNumeric("alpha".into()));
1✔
350
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
351
        }
1✔
352

353
        #[test]
354
        fn cmp_should_treat_strings_with_no_leading_digits_as_always_greater_than_u32s() {
1✔
355
            let cmp = ReleaseId::Numeric(123).partial_cmp(&ReleaseId::NonNumeric("one23".into()));
1✔
356
            assert_eq!(Some(Ordering::Less), cmp);
1✔
357

358
            let cmp = ReleaseId::NonNumeric("one23".into()).partial_cmp(&ReleaseId::Numeric(123));
1✔
359
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
360
        }
1✔
361

362
        #[test]
363
        fn cmp_should_compare_leading_digits_in_strings_against_u32s_and_use_the_result_if_it_is_not_equal(
1✔
364
        ) {
1✔
365
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("78b".into()));
1✔
366
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
367

368
            let cmp = ReleaseId::NonNumeric("78b".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
369
            assert_eq!(Some(Ordering::Less), cmp);
1✔
370
        }
1✔
371

372
        #[test]
373
        fn cmp_should_compare_leading_digits_in_strings_against_u32s_and_use_the_result_if_it_is_equal_and_there_are_no_non_digit_characters(
1✔
374
        ) {
1✔
375
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("86".into()));
1✔
376
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
377

378
            let cmp = ReleaseId::NonNumeric("86".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
379
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
380
        }
1✔
381

382
        #[test]
383
        fn cmp_should_compare_leading_digits_in_strings_against_u32s_and_treat_the_u32_as_less_if_the_result_is_equal_and_there_are_non_digit_characters(
1✔
384
        ) {
1✔
385
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("86b".into()));
1✔
386
            assert_eq!(Some(Ordering::Less), cmp);
1✔
387

388
            let cmp = ReleaseId::NonNumeric("86b".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
389
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
390
        }
1✔
391
    }
392

393
    mod constructors {
394
        use super::super::*;
395

396
        #[test]
397
        fn version_read_file_version_should_read_the_file_version_field_of_a_32_bit_executable() {
1✔
398
            let version = Version::read_file_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
399
                .unwrap()
1✔
400
                .unwrap();
1✔
401

1✔
402
            assert_eq!(
1✔
403
                version.release_ids,
1✔
404
                vec![
1✔
405
                    ReleaseId::Numeric(0),
1✔
406
                    ReleaseId::Numeric(18),
1✔
407
                    ReleaseId::Numeric(2),
1✔
408
                    ReleaseId::Numeric(0),
1✔
409
                ]
1✔
410
            );
1✔
411
            assert!(version.pre_release_ids.is_empty());
1✔
412
        }
1✔
413

414
        #[test]
415
        fn version_read_file_version_should_read_the_file_version_field_of_a_64_bit_executable() {
1✔
416
            let version = Version::read_file_version(Path::new("tests/libloot_win64/loot.dll"))
1✔
417
                .unwrap()
1✔
418
                .unwrap();
1✔
419

1✔
420
            assert_eq!(
1✔
421
                version.release_ids,
1✔
422
                vec![
1✔
423
                    ReleaseId::Numeric(0),
1✔
424
                    ReleaseId::Numeric(18),
1✔
425
                    ReleaseId::Numeric(2),
1✔
426
                    ReleaseId::Numeric(0),
1✔
427
                ]
1✔
428
            );
1✔
429
            assert!(version.pre_release_ids.is_empty());
1✔
430
        }
1✔
431

432
        #[test]
433
        fn version_read_file_version_should_error_with_path_if_path_does_not_exist() {
1✔
434
            let error = Version::read_file_version(Path::new("missing")).unwrap_err();
1✔
435

1✔
436
            assert!(error
1✔
437
                .to_string()
1✔
438
                .starts_with("An error was encountered while accessing the path \"missing\":"));
1✔
439
        }
1✔
440

441
        #[test]
442
        fn version_read_file_version_should_error_with_path_if_the_file_is_not_an_executable() {
1✔
443
            let error = Version::read_file_version(Path::new("Cargo.toml")).unwrap_err();
1✔
444

1✔
445
            assert_eq!("An error was encountered while reading the version fields of \"Cargo.toml\": unknown magic number", error.to_string());
1✔
446
        }
1✔
447

448
        #[test]
449
        fn version_read_file_version_should_return_none_if_there_is_no_version_info() {
1✔
450
            let version =
1✔
451
                Version::read_file_version(Path::new("tests/loot_api_python/loot_api.pyd"))
1✔
452
                    .unwrap();
1✔
453

1✔
454
            assert!(version.is_none());
1✔
455
        }
1✔
456

457
        #[test]
458
        fn version_read_product_version_should_read_the_file_version_field_of_a_32_bit_executable()
1✔
459
        {
1✔
460
            let version = Version::read_product_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
461
                .unwrap()
1✔
462
                .unwrap();
1✔
463

1✔
464
            assert_eq!(
1✔
465
                version.release_ids,
1✔
466
                vec![
1✔
467
                    ReleaseId::Numeric(0),
1✔
468
                    ReleaseId::Numeric(18),
1✔
469
                    ReleaseId::Numeric(2)
1✔
470
                ]
1✔
471
            );
1✔
472
            assert!(version.pre_release_ids.is_empty());
1✔
473
        }
1✔
474

475
        #[test]
476
        fn version_read_product_version_should_read_the_file_version_field_of_a_64_bit_executable()
1✔
477
        {
1✔
478
            let version = Version::read_product_version(Path::new("tests/libloot_win64/loot.dll"))
1✔
479
                .unwrap()
1✔
480
                .unwrap();
1✔
481

1✔
482
            assert_eq!(
1✔
483
                version.release_ids,
1✔
484
                vec![
1✔
485
                    ReleaseId::Numeric(0),
1✔
486
                    ReleaseId::Numeric(18),
1✔
487
                    ReleaseId::Numeric(2),
1✔
488
                ]
1✔
489
            );
1✔
490
            assert!(version.pre_release_ids.is_empty());
1✔
491
        }
1✔
492

493
        #[test]
494
        fn version_read_product_version_should_find_non_us_english_version_strings() {
1✔
495
            let tmp_dir = tempfile::tempdir().unwrap();
1✔
496
            let dll_path = tmp_dir.path().join("loot.ru.dll");
1✔
497

1✔
498
            let mut dll_bytes = std::fs::read("tests/libloot_win32/loot.dll").unwrap();
1✔
499

1✔
500
            // Set the version info block's language code to 1049 (Russian).
1✔
501
            dll_bytes[0x0053_204A] = b'1'; // This changes VersionInfo.strings.Language.lang_id
1✔
502
            dll_bytes[0x0053_216C] = 0x19; // This changes VersionInfo.langs.Language.lang_id
1✔
503

1✔
504
            std::fs::write(&dll_path, dll_bytes).unwrap();
1✔
505

1✔
506
            let version = Version::read_product_version(&dll_path).unwrap().unwrap();
1✔
507

1✔
508
            assert_eq!(
1✔
509
                version.release_ids,
1✔
510
                vec![
1✔
511
                    ReleaseId::Numeric(0),
1✔
512
                    ReleaseId::Numeric(18),
1✔
513
                    ReleaseId::Numeric(2)
1✔
514
                ]
1✔
515
            );
1✔
516
            assert!(version.pre_release_ids.is_empty());
1✔
517
        }
1✔
518

519
        #[test]
520
        fn version_read_product_version_should_error_with_path_if_path_does_not_exist() {
1✔
521
            let error = Version::read_product_version(Path::new("missing")).unwrap_err();
1✔
522

1✔
523
            assert!(error
1✔
524
                .to_string()
1✔
525
                .starts_with("An error was encountered while accessing the path \"missing\":"));
1✔
526
        }
1✔
527

528
        #[test]
529
        fn version_read_product_version_should_error_with_path_if_the_file_is_not_an_executable() {
1✔
530
            let error = Version::read_product_version(Path::new("Cargo.toml")).unwrap_err();
1✔
531

1✔
532
            assert_eq!("An error was encountered while reading the version fields of \"Cargo.toml\": unknown magic number", error.to_string());
1✔
533
        }
1✔
534

535
        #[test]
536
        fn version_read_product_version_should_return_none_if_there_is_no_version_info() {
1✔
537
            let version =
1✔
538
                Version::read_product_version(Path::new("tests/loot_api_python/loot_api.pyd"))
1✔
539
                    .unwrap();
1✔
540

1✔
541
            assert!(version.is_none());
1✔
542
        }
1✔
543
    }
544

545
    mod empty {
546
        use super::super::*;
547
        #[test]
548
        fn version_eq_an_empty_string_should_equal_an_empty_string() {
1✔
549
            assert_eq!(Version::from(""), Version::from(""));
1✔
550
        }
1✔
551

552
        #[test]
553
        fn version_eq_an_empty_string_should_equal_a_version_of_zero() {
1✔
554
            assert_eq!(Version::from(""), Version::from("0"));
1✔
555
            assert_eq!(Version::from("0"), Version::from(""));
1✔
556
        }
1✔
557

558
        #[test]
559
        fn version_eq_an_empty_string_should_not_equal_a_non_zero_version() {
1✔
560
            assert_ne!(Version::from(""), Version::from("5"));
1✔
561
            assert_ne!(Version::from("5"), Version::from(""));
1✔
562
        }
1✔
563

564
        #[test]
565
        fn version_partial_cmp_an_empty_string_should_be_less_than_a_non_zero_version() {
1✔
566
            assert!(Version::from("") < Version::from("1"));
1✔
567
            assert!(Version::from("1") > Version::from(""));
1✔
568
        }
1✔
569
    }
570

571
    mod numeric {
572
        use super::super::*;
573

574
        #[test]
575
        fn version_eq_a_non_empty_string_should_equal_itself() {
1✔
576
            assert_eq!(Version::from("5"), Version::from("5"));
1✔
577
        }
1✔
578

579
        #[test]
580
        fn version_eq_single_digit_versions_should_compare_digits() {
1✔
581
            assert_eq!(Version::from("5"), Version::from("5"));
1✔
582

583
            assert_ne!(Version::from("4"), Version::from("5"));
1✔
584
            assert_ne!(Version::from("5"), Version::from("4"));
1✔
585
        }
1✔
586

587
        #[test]
588
        fn version_partial_cmp_single_digit_versions_should_compare_digits() {
1✔
589
            assert!(Version::from("4") < Version::from("5"));
1✔
590
            assert!(Version::from("5") > Version::from("4"));
1✔
591
        }
1✔
592

593
        #[test]
594
        fn version_eq_numeric_versions_should_compare_numbers() {
1✔
595
            assert_ne!(Version::from("5"), Version::from("10"));
1✔
596
            assert_ne!(Version::from("10"), Version::from("5"));
1✔
597
        }
1✔
598

599
        #[test]
600
        fn version_partial_cmp_numeric_versions_should_compare_numbers() {
1✔
601
            assert!(Version::from("5") < Version::from("10"));
1✔
602
            assert!(Version::from("10") > Version::from("5"));
1✔
603
        }
1✔
604
    }
605

606
    mod semver {
607
        use super::super::*;
608
        use super::is_cmp_eq;
609

610
        #[test]
611
        fn version_eq_should_compare_patch_numbers() {
1✔
612
            assert_eq!(Version::from("0.0.5"), Version::from("0.0.5"));
1✔
613

614
            assert_ne!(Version::from("0.0.5"), Version::from("0.0.10"));
1✔
615
            assert_ne!(Version::from("0.0.10"), Version::from("0.0.5"));
1✔
616
        }
1✔
617

618
        #[test]
619
        fn version_partial_cmp_should_compare_patch_numbers() {
1✔
620
            assert!(Version::from("0.0.5") < Version::from("0.0.10"));
1✔
621
            assert!(Version::from("0.0.10") > Version::from("0.0.5"));
1✔
622
        }
1✔
623

624
        #[test]
625
        fn version_eq_should_compare_minor_numbers() {
1✔
626
            assert_eq!(Version::from("0.5.0"), Version::from("0.5.0"));
1✔
627

628
            assert_ne!(Version::from("0.5.0"), Version::from("0.10.0"));
1✔
629
            assert_ne!(Version::from("0.10.0"), Version::from("0.5.0"));
1✔
630
        }
1✔
631

632
        #[test]
633
        fn version_partial_cmp_should_compare_minor_numbers() {
1✔
634
            assert!(Version::from("0.5.0") < Version::from("0.10.0"));
1✔
635
            assert!(Version::from("0.10.0") > Version::from("0.5.0"));
1✔
636
        }
1✔
637

638
        #[test]
639
        fn version_partial_cmp_minor_numbers_should_take_precedence_over_patch_numbers() {
1✔
640
            assert!(Version::from("0.5.10") < Version::from("0.10.5"));
1✔
641
            assert!(Version::from("0.10.5") > Version::from("0.5.10"));
1✔
642
        }
1✔
643

644
        #[test]
645
        fn version_eq_should_compare_major_numbers() {
1✔
646
            assert_eq!(Version::from("5.0.0"), Version::from("5.0.0"));
1✔
647

648
            assert_ne!(Version::from("5.0.0"), Version::from("10.0.0"));
1✔
649
            assert_ne!(Version::from("10.0.0"), Version::from("5.0.0"));
1✔
650
        }
1✔
651

652
        #[test]
653
        fn version_partial_cmp_should_compare_major_numbers() {
1✔
654
            assert!(Version::from("5.0.0") < Version::from("10.0.0"));
1✔
655
            assert!(Version::from("10.0.0") > Version::from("5.0.0"));
1✔
656
        }
1✔
657

658
        #[test]
659
        fn version_partial_cmp_major_numbers_should_take_precedence_over_minor_numbers() {
1✔
660
            assert!(Version::from("5.10.0") < Version::from("10.5.0"));
1✔
661
            assert!(Version::from("10.5.0") > Version::from("5.10.0"));
1✔
662
        }
1✔
663

664
        #[test]
665
        fn version_partial_cmp_major_numbers_should_take_precedence_over_patch_numbers() {
1✔
666
            assert!(Version::from("5.0.10") < Version::from("10.0.5"));
1✔
667
            assert!(Version::from("10.0.5") > Version::from("5.0.10"));
1✔
668
        }
1✔
669

670
        #[test]
671
        fn version_eq_should_consider_versions_that_differ_by_the_presence_of_a_pre_release_id_to_be_not_equal(
1✔
672
        ) {
1✔
673
            assert_ne!(Version::from("1.0.0"), Version::from("1.0.0-alpha"));
1✔
674
        }
1✔
675

676
        #[test]
677
        fn version_partial_cmp_should_treat_the_absence_of_a_pre_release_id_as_greater_than_its_presence(
1✔
678
        ) {
1✔
679
            assert!(Version::from("1.0.0-alpha") < Version::from("1.0.0"));
1✔
680
            assert!(Version::from("1.0.0") > Version::from("1.0.0-alpha"));
1✔
681
        }
1✔
682

683
        #[test]
684
        fn version_eq_should_compare_pre_release_identifiers() {
1✔
685
            assert_eq!(
1✔
686
                Version::from("0.0.5-5.alpha"),
1✔
687
                Version::from("0.0.5-5.alpha")
1✔
688
            );
1✔
689

690
            assert_ne!(
1✔
691
                Version::from("0.0.5-5.alpha"),
1✔
692
                Version::from("0.0.5-10.beta")
1✔
693
            );
1✔
694
            assert_ne!(
1✔
695
                Version::from("0.0.5-10.beta"),
1✔
696
                Version::from("0.0.5-5.alpha")
1✔
697
            );
1✔
698
        }
1✔
699

700
        #[test]
701
        fn version_partial_cmp_should_compare_numeric_pre_release_ids_numerically() {
1✔
702
            assert!(Version::from("0.0.5-5") < Version::from("0.0.5-10"));
1✔
703
            assert!(Version::from("0.0.5-10") > Version::from("0.0.5-5"));
1✔
704
        }
1✔
705

706
        #[test]
707
        fn version_partial_cmp_should_compare_non_numeric_pre_release_ids_lexically() {
1✔
708
            assert!(Version::from("0.0.5-a") < Version::from("0.0.5-b"));
1✔
709
            assert!(Version::from("0.0.5-b") > Version::from("0.0.5-a"));
1✔
710
        }
1✔
711

712
        #[test]
713
        fn version_partial_cmp_numeric_pre_release_ids_should_be_less_than_than_non_numeric_ids() {
1✔
714
            assert!(Version::from("0.0.5-9") < Version::from("0.0.5-a"));
1✔
715
            assert!(Version::from("0.0.5-a") > Version::from("0.0.5-9"));
1✔
716

717
            assert!(Version::from("0.0.5-86") < Version::from("0.0.5-78b"));
1✔
718
            assert!(Version::from("0.0.5-78b") > Version::from("0.0.5-86"));
1✔
719
        }
1✔
720

721
        #[test]
722
        fn version_partial_cmp_earlier_pre_release_ids_should_take_precedence_over_later_ids() {
1✔
723
            assert!(Version::from("0.0.5-5.10") < Version::from("0.0.5-10.5"));
1✔
724
            assert!(Version::from("0.0.5-10.5") > Version::from("0.0.5-5.10"));
1✔
725
        }
1✔
726

727
        #[test]
728
        fn version_partial_cmp_a_version_with_more_pre_release_ids_is_greater() {
1✔
729
            assert!(Version::from("0.0.5-5") < Version::from("0.0.5-5.0"));
1✔
730
            assert!(Version::from("0.0.5-5.0") > Version::from("0.0.5-5"));
1✔
731
        }
1✔
732

733
        #[test]
734
        fn version_partial_cmp_release_ids_should_take_precedence_over_pre_release_ids() {
1✔
735
            assert!(Version::from("0.0.5-10") < Version::from("0.0.10-5"));
1✔
736
            assert!(Version::from("0.0.10-5") > Version::from("0.0.5-10"));
1✔
737
        }
1✔
738

739
        #[test]
740
        fn version_eq_should_ignore_metadata() {
1✔
741
            assert_eq!(Version::from("0.0.1+alpha"), Version::from("0.0.1+beta"));
1✔
742
        }
1✔
743

744
        #[test]
745
        fn version_partial_cmp_should_ignore_metadata() {
1✔
746
            assert!(is_cmp_eq(
1✔
747
                &Version::from("0.0.1+alpha"),
1✔
748
                &Version::from("0.0.1+1")
1✔
749
            ));
1✔
750
            assert!(is_cmp_eq(
1✔
751
                &Version::from("0.0.1+1"),
1✔
752
                &Version::from("0.0.1+alpha")
1✔
753
            ));
1✔
754

755
            assert!(is_cmp_eq(
1✔
756
                &Version::from("0.0.1+2"),
1✔
757
                &Version::from("0.0.1+1")
1✔
758
            ));
1✔
759
            assert!(is_cmp_eq(
1✔
760
                &Version::from("0.0.1+1"),
1✔
761
                &Version::from("0.0.1+2")
1✔
762
            ));
1✔
763
        }
1✔
764
    }
765

766
    mod extensions {
767
        use super::super::*;
768
        use super::is_cmp_eq;
769

770
        #[test]
771
        fn version_from_should_parse_comma_separated_versions() {
1✔
772
            // OBSE and SKSE use version string fields of the form "0, 2, 0, 12".
1✔
773
            let version = Version::from("0, 2, 0, 12");
1✔
774

1✔
775
            assert_eq!(
1✔
776
                version.release_ids,
1✔
777
                vec![
1✔
778
                    ReleaseId::Numeric(0),
1✔
779
                    ReleaseId::Numeric(2),
1✔
780
                    ReleaseId::Numeric(0),
1✔
781
                    ReleaseId::Numeric(12),
1✔
782
                ]
1✔
783
            );
1✔
784
            assert!(version.pre_release_ids.is_empty());
1✔
785
        }
1✔
786

787
        #[test]
788
        fn version_eq_should_ignore_leading_zeroes_in_major_version_numbers() {
1✔
789
            assert_eq!(Version::from("05.0.0"), Version::from("5.0.0"));
1✔
790
            assert_eq!(Version::from("5.0.0"), Version::from("05.0.0"));
1✔
791
        }
1✔
792

793
        #[test]
794
        fn version_partial_cmp_should_ignore_leading_zeroes_in_major_version_numbers() {
1✔
795
            assert!(is_cmp_eq(&Version::from("05.0.0"), &Version::from("5.0.0")));
1✔
796
            assert!(is_cmp_eq(&Version::from("5.0.0"), &Version::from("05.0.0")));
1✔
797
        }
1✔
798

799
        #[test]
800
        fn version_eq_should_ignore_leading_zeroes_in_minor_version_numbers() {
1✔
801
            assert_eq!(Version::from("0.05.0"), Version::from("0.5.0"));
1✔
802
            assert_eq!(Version::from("0.5.0"), Version::from("0.05.0"));
1✔
803
        }
1✔
804

805
        #[test]
806
        fn version_partial_cmp_should_ignore_leading_zeroes_in_minor_version_numbers() {
1✔
807
            assert!(is_cmp_eq(&Version::from("0.05.0"), &Version::from("0.5.0")));
1✔
808
            assert!(is_cmp_eq(&Version::from("0.5.0"), &Version::from("0.05.0")));
1✔
809
        }
1✔
810

811
        #[test]
812
        fn version_eq_should_ignore_leading_zeroes_in_patch_version_numbers() {
1✔
813
            assert_eq!(Version::from("0.0.05"), Version::from("0.0.5"));
1✔
814
            assert_eq!(Version::from("0.0.5"), Version::from("0.0.05"));
1✔
815
        }
1✔
816

817
        #[test]
818
        fn version_partial_cmp_should_ignore_leading_zeroes_in_patch_version_numbers() {
1✔
819
            assert!(is_cmp_eq(&Version::from("0.0.05"), &Version::from("0.0.5")));
1✔
820
            assert!(is_cmp_eq(&Version::from("0.0.5"), &Version::from("0.0.05")));
1✔
821
        }
1✔
822

823
        #[test]
824
        fn version_eq_should_ignore_leading_zeroes_in_numeric_pre_release_ids() {
1✔
825
            assert_eq!(Version::from("0.0.5-05"), Version::from("0.0.5-5"));
1✔
826
            assert_eq!(Version::from("0.0.5-5"), Version::from("0.0.5-05"));
1✔
827
        }
1✔
828

829
        #[test]
830
        fn version_partial_cmp_should_ignore_leading_zeroes_in_numeric_pre_release_ids() {
1✔
831
            assert!(is_cmp_eq(
1✔
832
                &Version::from("0.0.5-05"),
1✔
833
                &Version::from("0.0.5-5")
1✔
834
            ));
1✔
835
            assert!(is_cmp_eq(
1✔
836
                &Version::from("0.0.5-5"),
1✔
837
                &Version::from("0.0.5-05")
1✔
838
            ));
1✔
839
        }
1✔
840

841
        #[test]
842
        fn version_eq_should_compare_an_equal_but_arbitrary_number_of_version_numbers() {
1✔
843
            assert_eq!(Version::from("1.0.0.1.0.0"), Version::from("1.0.0.1.0.0"));
1✔
844

845
            assert_ne!(Version::from("1.0.0.0.0.0"), Version::from("1.0.0.0.0.1"));
1✔
846
            assert_ne!(Version::from("1.0.0.0.0.1"), Version::from("1.0.0.0.0.0"));
1✔
847
        }
1✔
848

849
        #[test]
850
        fn version_partial_cmp_should_compare_an_equal_but_arbitrary_number_of_version_numbers() {
1✔
851
            assert!(is_cmp_eq(
1✔
852
                &Version::from("1.0.0.1.0.0"),
1✔
853
                &Version::from("1.0.0.1.0.0")
1✔
854
            ));
1✔
855

856
            assert!(Version::from("1.0.0.0.0.0") < Version::from("1.0.0.0.0.1"));
1✔
857
            assert!(Version::from("1.0.0.0.0.1") > Version::from("1.0.0.0.0.0"));
1✔
858
        }
1✔
859

860
        #[test]
861
        fn version_eq_non_numeric_release_ids_should_be_compared_lexically() {
1✔
862
            assert_eq!(Version::from("1.0.0a"), Version::from("1.0.0a"));
1✔
863

864
            assert_ne!(Version::from("1.0.0a"), Version::from("1.0.0b"));
1✔
865
            assert_ne!(Version::from("1.0.0b"), Version::from("1.0.0a"));
1✔
866
        }
1✔
867

868
        #[test]
869
        fn version_partial_cmp_non_numeric_release_ids_should_be_compared_lexically() {
1✔
870
            assert!(Version::from("1.0.0a") < Version::from("1.0.0b"));
1✔
871
            assert!(Version::from("1.0.0b") > Version::from("1.0.0a"));
1✔
872
        }
1✔
873

874
        #[test]
875
        fn version_partial_cmp_numeric_and_non_numeric_release_ids_should_be_compared_by_leading_numeric_values_first(
1✔
876
        ) {
1✔
877
            assert!(Version::from("0.78b") < Version::from("0.86"));
1✔
878
            assert!(Version::from("0.86") > Version::from("0.78b"));
1✔
879
        }
1✔
880

881
        #[test]
882
        fn version_partial_cmp_non_numeric_release_ids_should_be_greater_than_release_ids() {
1✔
883
            assert!(Version::from("1.0.0") < Version::from("1.0.0a"));
1✔
884
            assert!(Version::from("1.0.0a") > Version::from("1.0.0"));
1✔
885
        }
1✔
886

887
        #[test]
888
        fn version_partial_cmp_any_release_id_may_be_non_numeric() {
1✔
889
            assert!(Version::from("1.0.0alpha.2") < Version::from("1.0.0beta.2"));
1✔
890
            assert!(Version::from("1.0.0beta.2") > Version::from("1.0.0alpha.2"));
1✔
891
        }
1✔
892

893
        #[test]
894
        fn version_eq_should_compare_release_ids_case_insensitively() {
1✔
895
            assert_eq!(Version::from("1.0.0A"), Version::from("1.0.0a"));
1✔
896
            assert_eq!(Version::from("1.0.0a"), Version::from("1.0.0A"));
1✔
897
        }
1✔
898

899
        #[test]
900
        fn version_partial_cmp_should_compare_release_ids_case_insensitively() {
1✔
901
            assert!(Version::from("1.0.0a") < Version::from("1.0.0B"));
1✔
902
            assert!(Version::from("1.0.0B") > Version::from("1.0.0a"));
1✔
903
        }
1✔
904

905
        #[test]
906
        fn version_eq_should_compare_pre_release_ids_case_insensitively() {
1✔
907
            assert_eq!(Version::from("1.0.0-Alpha"), Version::from("1.0.0-alpha"));
1✔
908
            assert_eq!(Version::from("1.0.0-alpha"), Version::from("1.0.0-Alpha"));
1✔
909
        }
1✔
910

911
        #[test]
912
        fn version_partial_cmp_should_compare_pre_release_ids_case_insensitively() {
1✔
913
            assert!(Version::from("1.0.0-alpha") < Version::from("1.0.0-Beta"));
1✔
914
            assert!(Version::from("1.0.0-Beta") > Version::from("1.0.0-alpha"));
1✔
915
        }
1✔
916

917
        #[test]
918
        fn version_eq_should_pad_release_id_vecs_to_equal_length_with_zeroes() {
1✔
919
            assert_eq!(Version::from("1-beta"), Version::from("1.0.0-beta"));
1✔
920
            assert_eq!(Version::from("1.0.0-beta"), Version::from("1-beta"));
1✔
921

922
            assert_eq!(Version::from("0.0.0.1"), Version::from("0.0.0.1.0.0"));
1✔
923
            assert_eq!(Version::from("0.0.0.1.0.0"), Version::from("0.0.0.1"));
1✔
924

925
            assert_ne!(Version::from("1.0.0.0"), Version::from("1.0.0.0.0.1"));
1✔
926
            assert_ne!(Version::from("1.0.0.0.0.1"), Version::from("1.0.0.0"));
1✔
927
        }
1✔
928

929
        #[test]
930
        fn version_partial_cmp_should_pad_release_id_vecs_to_equal_length_with_zeroes() {
1✔
931
            assert!(Version::from("1.0.0.0.0.0") < Version::from("1.0.0.1"));
1✔
932
            assert!(Version::from("1.0.0.1") > Version::from("1.0.0.0.0.0"));
1✔
933

934
            assert!(Version::from("1.0.0.0") < Version::from("1.0.0.0.0.1"));
1✔
935
            assert!(Version::from("1.0.0.0.0.1") > Version::from("1.0.0.0"));
1✔
936

937
            assert!(is_cmp_eq(
1✔
938
                &Version::from("1.0.0.0.0.0"),
1✔
939
                &Version::from("1.0.0.0")
1✔
940
            ));
1✔
941
            assert!(is_cmp_eq(
1✔
942
                &Version::from("1.0.0.0"),
1✔
943
                &Version::from("1.0.0.0.0.0")
1✔
944
            ));
1✔
945
        }
1✔
946

947
        #[test]
948
        fn version_from_should_treat_space_as_separator_between_release_and_pre_release_ids() {
1✔
949
            let version = Version::from("1.0.0 alpha");
1✔
950
            assert_eq!(
1✔
951
                version.release_ids,
1✔
952
                vec![
1✔
953
                    ReleaseId::Numeric(1),
1✔
954
                    ReleaseId::Numeric(0),
1✔
955
                    ReleaseId::Numeric(0)
1✔
956
                ]
1✔
957
            );
1✔
958
            assert_eq!(
1✔
959
                version.pre_release_ids,
1✔
960
                vec![PreReleaseId::NonNumeric("alpha".into())]
1✔
961
            );
1✔
962
        }
1✔
963

964
        #[test]
965
        fn version_from_should_treat_colon_as_separator_between_release_and_pre_release_ids() {
1✔
966
            let version = Version::from("1.0.0:alpha");
1✔
967
            assert_eq!(
1✔
968
                version.release_ids,
1✔
969
                vec![
1✔
970
                    ReleaseId::Numeric(1),
1✔
971
                    ReleaseId::Numeric(0),
1✔
972
                    ReleaseId::Numeric(0)
1✔
973
                ]
1✔
974
            );
1✔
975
            assert_eq!(
1✔
976
                version.pre_release_ids,
1✔
977
                vec![PreReleaseId::NonNumeric("alpha".into())]
1✔
978
            );
1✔
979
        }
1✔
980

981
        #[test]
982
        fn version_from_should_treat_underscore_as_separator_between_release_and_pre_release_ids() {
1✔
983
            let version = Version::from("1.0.0_alpha");
1✔
984
            assert_eq!(
1✔
985
                version.release_ids,
1✔
986
                vec![
1✔
987
                    ReleaseId::Numeric(1),
1✔
988
                    ReleaseId::Numeric(0),
1✔
989
                    ReleaseId::Numeric(0)
1✔
990
                ]
1✔
991
            );
1✔
992
            assert_eq!(
1✔
993
                version.pre_release_ids,
1✔
994
                vec![PreReleaseId::NonNumeric("alpha".into())]
1✔
995
            );
1✔
996
        }
1✔
997

998
        #[test]
999
        fn version_from_should_treat_space_as_separator_between_pre_release_ids() {
1✔
1000
            let version = Version::from("1.0.0-alpha 1");
1✔
1001
            assert_eq!(
1✔
1002
                version.release_ids,
1✔
1003
                vec![
1✔
1004
                    ReleaseId::Numeric(1),
1✔
1005
                    ReleaseId::Numeric(0),
1✔
1006
                    ReleaseId::Numeric(0)
1✔
1007
                ]
1✔
1008
            );
1✔
1009
            assert_eq!(
1✔
1010
                version.pre_release_ids,
1✔
1011
                vec![
1✔
1012
                    PreReleaseId::NonNumeric("alpha".into()),
1✔
1013
                    PreReleaseId::Numeric(1)
1✔
1014
                ]
1✔
1015
            );
1✔
1016
        }
1✔
1017

1018
        #[test]
1019
        fn version_from_should_treat_colon_as_separator_between_pre_release_ids() {
1✔
1020
            let version = Version::from("1.0.0-alpha:1");
1✔
1021
            assert_eq!(
1✔
1022
                version.release_ids,
1✔
1023
                vec![
1✔
1024
                    ReleaseId::Numeric(1),
1✔
1025
                    ReleaseId::Numeric(0),
1✔
1026
                    ReleaseId::Numeric(0)
1✔
1027
                ]
1✔
1028
            );
1✔
1029
            assert_eq!(
1✔
1030
                version.pre_release_ids,
1✔
1031
                vec![
1✔
1032
                    PreReleaseId::NonNumeric("alpha".into()),
1✔
1033
                    PreReleaseId::Numeric(1)
1✔
1034
                ]
1✔
1035
            );
1✔
1036
        }
1✔
1037

1038
        #[test]
1039
        fn version_from_should_treat_underscore_as_separator_between_pre_release_ids() {
1✔
1040
            let version = Version::from("1.0.0-alpha_1");
1✔
1041
            assert_eq!(
1✔
1042
                version.release_ids,
1✔
1043
                vec![
1✔
1044
                    ReleaseId::Numeric(1),
1✔
1045
                    ReleaseId::Numeric(0),
1✔
1046
                    ReleaseId::Numeric(0)
1✔
1047
                ]
1✔
1048
            );
1✔
1049
            assert_eq!(
1✔
1050
                version.pre_release_ids,
1✔
1051
                vec![
1✔
1052
                    PreReleaseId::NonNumeric("alpha".into()),
1✔
1053
                    PreReleaseId::Numeric(1)
1✔
1054
                ]
1✔
1055
            );
1✔
1056
        }
1✔
1057

1058
        #[test]
1059
        fn version_from_should_treat_dash_as_separator_between_pre_release_ids() {
1✔
1060
            let version = Version::from("1.0.0-alpha-1");
1✔
1061
            assert_eq!(
1✔
1062
                version.release_ids,
1✔
1063
                vec![
1✔
1064
                    ReleaseId::Numeric(1),
1✔
1065
                    ReleaseId::Numeric(0),
1✔
1066
                    ReleaseId::Numeric(0)
1✔
1067
                ]
1✔
1068
            );
1✔
1069
            assert_eq!(
1✔
1070
                version.pre_release_ids,
1✔
1071
                vec![
1✔
1072
                    PreReleaseId::NonNumeric("alpha".into()),
1✔
1073
                    PreReleaseId::Numeric(1)
1✔
1074
                ]
1✔
1075
            );
1✔
1076
        }
1✔
1077
    }
1078
}
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