• 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

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
use pelite::FileMap;
7

8
use crate::error::Error;
9

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

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

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

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

46
// This is like u32::from_str_radix(), but stops instead of erroring when it
47
// encounters a non-digit character. It also doesn't support signs.
48
fn u32_from_str(id: &str) -> (Option<u32>, usize) {
12✔
49
    // Find the index of the first non-digit character. All valid digits are
12✔
50
    // ASCII so treat this as a byte slice.
12✔
51
    let bytes = id.as_bytes();
12✔
52
    let first_non_digit_index = bytes.iter().position(|byte| !byte.is_ascii_digit());
28✔
53

12✔
54
    // Conversion can fail even with only ASCII digits because of overflow, so
12✔
55
    // take that into account.
12✔
56
    match first_non_digit_index {
12✔
57
        // If the first byte is not a digit, there is no number to parse (this
58
        // ignores + and - signs).
59
        Some(0) => (None, id.len()),
2✔
60
        Some(index) => (id[..index].trim().parse().ok(), id.len() - index),
8✔
61
        None => (id.trim().parse().ok(), 0),
2✔
62
    }
63
}
12✔
64

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

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

94
#[derive(Clone, Debug, PartialEq, PartialOrd)]
95
enum PreReleaseId {
96
    Numeric(u32),
97
    NonNumeric(String),
98
}
99

100
impl<'a> From<&'a str> for PreReleaseId {
101
    fn from(string: &'a str) -> Self {
80✔
102
        string
80✔
103
            .trim()
80✔
104
            .parse()
80✔
105
            .map(PreReleaseId::Numeric)
80✔
106
            .unwrap_or_else(|_| PreReleaseId::NonNumeric(string.to_lowercase()))
80✔
107
    }
80✔
108
}
109

110
#[derive(Debug)]
111
pub struct Version {
112
    release_ids: Vec<ReleaseId>,
113
    pre_release_ids: Vec<PreReleaseId>,
114
}
115

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

131
    pub fn read_product_version(file_path: &Path) -> Result<Option<Self>, Error> {
9✔
132
        Self::read_version(file_path, |v| {
9✔
133
            v.translation()
5✔
134
                .first()
5✔
135
                .and_then(|language| v.value(*language, "ProductVersion"))
5✔
136
        })
9✔
137
    }
9✔
138

139
    pub fn is_readable(file_path: &Path) -> bool {
5✔
140
        Self::read_version(file_path, |_| None).is_ok()
5✔
141
    }
5✔
142

143
    fn read_version<F: Fn(&VersionInfo) -> Option<String>>(
20✔
144
        file_path: &Path,
20✔
145
        formatter: F,
20✔
146
    ) -> Result<Option<Self>, Error> {
20✔
147
        let file_map =
15✔
148
            FileMap::open(file_path).map_err(|e| Error::IoError(file_path.to_path_buf(), e))?;
20✔
149

150
        match get_pe_version_info(file_map.as_ref()) {
15✔
151
            Ok(v) => Ok(formatter(&v).map(Version::from)),
9✔
152
            Err(FindError::NotFound) => Ok(None),
2✔
153
            Err(e) => Err(Error::PeParsingError(file_path.to_path_buf(), e.into())),
4✔
154
        }
155
    }
20✔
156
}
157

158
fn get_pe_version_info(bytes: &[u8]) -> Result<VersionInfo, FindError> {
15✔
159
    get_pe_resources(bytes)?.version_info()
15✔
160
}
15✔
161

162
fn get_pe_resources(bytes: &[u8]) -> Result<Resources, pelite::Error> {
15✔
163
    use pelite::pe64;
164
    match pe64::PeFile::from_bytes(bytes) {
15✔
165
        Ok(file) => {
2✔
166
            use pelite::pe64::Pe;
167

168
            file.resources()
2✔
169
        }
170
        Err(pelite::Error::PeMagic) => {
171
            use pelite::pe32::{Pe, PeFile};
172

173
            PeFile::from_bytes(bytes)?.resources()
9✔
174
        }
175
        Err(e) => Err(e),
4✔
176
    }
177
}
15✔
178

179
fn is_separator(c: char) -> bool {
1,667✔
180
    c == '-' || c == ' ' || c == ':' || c == '_'
1,667✔
181
}
1,667✔
182

183
fn is_pre_release_separator(c: char) -> bool {
220✔
184
    c == '.' || is_separator(c)
220✔
185
}
220✔
186

187
fn split_version_string(string: &str) -> (&str, &str) {
283✔
188
    // Special case for strings of the form "0, 1, 2, 3", which are used in
189
    // OBSE and SKSE, and which should be interpreted as "0.1.2.3".
190
    if let Ok(regex) = regex::Regex::new("\\d+, \\d+, \\d+, \\d+") {
283✔
191
        if regex.is_match(string) {
283✔
192
            return (string, "");
1✔
193
        }
282✔
194
    }
×
195

196
    match string.find(is_separator) {
282✔
197
        Some(i) if i + 1 < string.len() => (&string[..i], &string[i + 1..]),
64✔
198
        Some(_) | None => (string, ""),
218✔
199
    }
200
}
283✔
201

202
impl<T: AsRef<str>> From<T> for Version {
203
    fn from(string: T) -> Self {
283✔
204
        let (release, pre_release) = split_version_string(trim_metadata(string.as_ref()));
283✔
205

283✔
206
        Version {
283✔
207
            release_ids: release.split(['.', ',']).map(ReleaseId::from).collect(),
283✔
208
            pre_release_ids: pre_release
283✔
209
                .split_terminator(is_pre_release_separator)
283✔
210
                .map(PreReleaseId::from)
283✔
211
                .collect(),
283✔
212
        }
283✔
213
    }
283✔
214
}
215

216
fn trim_metadata(version: &str) -> &str {
283✔
217
    if version.is_empty() {
283✔
218
        "0"
8✔
219
    } else if let Some(i) = version.find('+') {
275✔
220
        &version[..i]
10✔
221
    } else {
222
        version
265✔
223
    }
224
}
283✔
225

226
impl PartialOrd for Version {
227
    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
78✔
228
        let (self_release_ids, other_release_ids) =
78✔
229
            pad_release_ids(&self.release_ids, &other.release_ids);
78✔
230

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

247
impl PartialEq for Version {
248
    fn eq(&self, other: &Version) -> bool {
57✔
249
        let (self_release_ids, other_release_ids) =
57✔
250
            pad_release_ids(&self.release_ids, &other.release_ids);
57✔
251

57✔
252
        self_release_ids == other_release_ids && self.pre_release_ids == other.pre_release_ids
57✔
253
    }
57✔
254
}
255

256
fn pad_release_ids(ids1: &[ReleaseId], ids2: &[ReleaseId]) -> (Vec<ReleaseId>, Vec<ReleaseId>) {
135✔
257
    let mut ids1 = ids1.to_vec();
135✔
258
    let mut ids2 = ids2.to_vec();
135✔
259

135✔
260
    match ids1.len().cmp(&ids2.len()) {
135✔
261
        Ordering::Less => ids1.resize(ids2.len(), ReleaseId::Numeric(0)),
6✔
262
        Ordering::Greater => ids2.resize(ids1.len(), ReleaseId::Numeric(0)),
6✔
263
        _ => {}
123✔
264
    }
265

266
    (ids1, ids2)
135✔
267
}
135✔
268

269
#[cfg(test)]
270
mod tests {
271
    fn is_cmp_eq(lhs: super::Version, rhs: super::Version) -> bool {
15✔
272
        lhs.partial_cmp(&rhs).unwrap().is_eq()
15✔
273
    }
15✔
274

275
    mod release_ids {
276
        use super::super::*;
277

278
        #[test]
279
        fn eq_should_compare_equality_of_u32_values() {
1✔
280
            assert_eq!(ReleaseId::Numeric(1), ReleaseId::Numeric(1));
1✔
281
            assert_ne!(ReleaseId::Numeric(1), ReleaseId::Numeric(0));
1✔
282
        }
1✔
283

284
        #[test]
285
        fn eq_should_compare_equality_of_string_values() {
1✔
286
            assert_eq!(
1✔
287
                ReleaseId::NonNumeric("abcd".into()),
1✔
288
                ReleaseId::NonNumeric("abcd".into())
1✔
289
            );
1✔
290
            assert_ne!(
1✔
291
                ReleaseId::NonNumeric("abcd".into()),
1✔
292
                ReleaseId::NonNumeric("abce".into())
1✔
293
            );
1✔
294
        }
1✔
295

296
        #[test]
297
        fn eq_should_convert_string_values_to_u32_before_comparing_against_a_u32_value() {
1✔
298
            assert_eq!(ReleaseId::Numeric(123), ReleaseId::NonNumeric("123".into()));
1✔
299
            assert_eq!(
1✔
300
                ReleaseId::Numeric(123),
1✔
301
                ReleaseId::NonNumeric(" 123 ".into())
1✔
302
            );
1✔
303

304
            assert_ne!(
1✔
305
                ReleaseId::Numeric(123),
1✔
306
                ReleaseId::NonNumeric("1two3".into())
1✔
307
            );
1✔
308

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

315
            assert_ne!(
1✔
316
                ReleaseId::NonNumeric("1two3".into()),
1✔
317
                ReleaseId::Numeric(123)
1✔
318
            );
1✔
319
        }
1✔
320

321
        #[test]
322
        fn cmp_should_compare_u32_values() {
1✔
323
            let cmp = ReleaseId::Numeric(1).partial_cmp(&ReleaseId::Numeric(1));
1✔
324
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
325

326
            let cmp = ReleaseId::Numeric(1).partial_cmp(&ReleaseId::Numeric(2));
1✔
327
            assert_eq!(Some(Ordering::Less), cmp);
1✔
328

329
            let cmp = ReleaseId::Numeric(2).partial_cmp(&ReleaseId::Numeric(1));
1✔
330
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
331
        }
1✔
332

333
        #[test]
334
        fn cmp_should_compare_string_values() {
1✔
335
            let cmp = ReleaseId::NonNumeric("alpha".into())
1✔
336
                .partial_cmp(&ReleaseId::NonNumeric("alpha".into()));
1✔
337
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
338

339
            let cmp = ReleaseId::NonNumeric("alpha".into())
1✔
340
                .partial_cmp(&ReleaseId::NonNumeric("beta".into()));
1✔
341
            assert_eq!(Some(Ordering::Less), cmp);
1✔
342

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

348
        #[test]
349
        fn cmp_should_treat_strings_with_no_leading_digits_as_always_greater_than_u32s() {
1✔
350
            let cmp = ReleaseId::Numeric(123).partial_cmp(&ReleaseId::NonNumeric("one23".into()));
1✔
351
            assert_eq!(Some(Ordering::Less), cmp);
1✔
352

353
            let cmp = ReleaseId::NonNumeric("one23".into()).partial_cmp(&ReleaseId::Numeric(123));
1✔
354
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
355
        }
1✔
356

357
        #[test]
358
        fn cmp_should_compare_leading_digits_in_strings_against_u32s_and_use_the_result_if_it_is_not_equal(
1✔
359
        ) {
1✔
360
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("78b".into()));
1✔
361
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
362

363
            let cmp = ReleaseId::NonNumeric("78b".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
364
            assert_eq!(Some(Ordering::Less), cmp);
1✔
365
        }
1✔
366

367
        #[test]
368
        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✔
369
        ) {
1✔
370
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("86".into()));
1✔
371
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
372

373
            let cmp = ReleaseId::NonNumeric("86".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
374
            assert_eq!(Some(Ordering::Equal), cmp);
1✔
375
        }
1✔
376

377
        #[test]
378
        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✔
379
        ) {
1✔
380
            let cmp = ReleaseId::Numeric(86).partial_cmp(&ReleaseId::NonNumeric("86b".into()));
1✔
381
            assert_eq!(Some(Ordering::Less), cmp);
1✔
382

383
            let cmp = ReleaseId::NonNumeric("86b".into()).partial_cmp(&ReleaseId::Numeric(86));
1✔
384
            assert_eq!(Some(Ordering::Greater), cmp);
1✔
385
        }
1✔
386
    }
387

388
    mod constructors {
389
        use super::super::*;
390

391
        #[test]
392
        fn version_read_file_version_should_read_the_file_version_field_of_a_32_bit_executable() {
1✔
393
            let version = Version::read_file_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
394
                .unwrap()
1✔
395
                .unwrap();
1✔
396

1✔
397
            assert_eq!(
1✔
398
                version.release_ids,
1✔
399
                vec![
1✔
400
                    ReleaseId::Numeric(0),
1✔
401
                    ReleaseId::Numeric(18),
1✔
402
                    ReleaseId::Numeric(2),
1✔
403
                    ReleaseId::Numeric(0),
1✔
404
                ]
1✔
405
            );
1✔
406
            assert!(version.pre_release_ids.is_empty());
1✔
407
        }
1✔
408

409
        #[test]
410
        fn version_read_file_version_should_read_the_file_version_field_of_a_64_bit_executable() {
1✔
411
            let version = Version::read_file_version(Path::new("tests/libloot_win64/loot.dll"))
1✔
412
                .unwrap()
1✔
413
                .unwrap();
1✔
414

1✔
415
            assert_eq!(
1✔
416
                version.release_ids,
1✔
417
                vec![
1✔
418
                    ReleaseId::Numeric(0),
1✔
419
                    ReleaseId::Numeric(18),
1✔
420
                    ReleaseId::Numeric(2),
1✔
421
                    ReleaseId::Numeric(0),
1✔
422
                ]
1✔
423
            );
1✔
424
            assert!(version.pre_release_ids.is_empty());
1✔
425
        }
1✔
426

427
        #[test]
428
        fn version_read_file_version_should_error_with_path_if_path_does_not_exist() {
1✔
429
            let error = Version::read_file_version(Path::new("missing")).unwrap_err();
1✔
430

1✔
431
            assert!(error
1✔
432
                .to_string()
1✔
433
                .starts_with("An error was encountered while accessing the path \"missing\":"));
1✔
434
        }
1✔
435

436
        #[test]
437
        fn version_read_file_version_should_error_with_path_if_the_file_is_not_an_executable() {
1✔
438
            let error = Version::read_file_version(Path::new("Cargo.toml")).unwrap_err();
1✔
439

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

443
        #[test]
444
        fn version_read_file_version_should_return_none_if_there_is_no_version_info() {
1✔
445
            let version =
1✔
446
                Version::read_file_version(Path::new("tests/loot_api_python/loot_api.pyd"))
1✔
447
                    .unwrap();
1✔
448

1✔
449
            assert!(version.is_none());
1✔
450
        }
1✔
451

452
        #[test]
453
        fn version_read_product_version_should_read_the_file_version_field_of_a_32_bit_executable()
1✔
454
        {
1✔
455
            let version = Version::read_product_version(Path::new("tests/libloot_win32/loot.dll"))
1✔
456
                .unwrap()
1✔
457
                .unwrap();
1✔
458

1✔
459
            assert_eq!(
1✔
460
                version.release_ids,
1✔
461
                vec![
1✔
462
                    ReleaseId::Numeric(0),
1✔
463
                    ReleaseId::Numeric(18),
1✔
464
                    ReleaseId::Numeric(2)
1✔
465
                ]
1✔
466
            );
1✔
467
            assert!(version.pre_release_ids.is_empty());
1✔
468
        }
1✔
469

470
        #[test]
471
        fn version_read_product_version_should_read_the_file_version_field_of_a_64_bit_executable()
1✔
472
        {
1✔
473
            let version = Version::read_product_version(Path::new("tests/libloot_win64/loot.dll"))
1✔
474
                .unwrap()
1✔
475
                .unwrap();
1✔
476

1✔
477
            assert_eq!(
1✔
478
                version.release_ids,
1✔
479
                vec![
1✔
480
                    ReleaseId::Numeric(0),
1✔
481
                    ReleaseId::Numeric(18),
1✔
482
                    ReleaseId::Numeric(2),
1✔
483
                ]
1✔
484
            );
1✔
485
            assert!(version.pre_release_ids.is_empty());
1✔
486
        }
1✔
487

488
        #[test]
489
        fn version_read_product_version_should_find_non_us_english_version_strings() {
1✔
490
            let tmp_dir = tempfile::tempdir().unwrap();
1✔
491
            let dll_path = tmp_dir.path().join("loot.ru.dll");
1✔
492

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

1✔
495
            // Set the version info block's language code to 1049 (Russian).
1✔
496
            dll_bytes[0x53204A] = b'1'; // This changes VersionInfo.strings.Language.lang_id
1✔
497
            dll_bytes[0x53216C] = 0x19; // This changes VersionInfo.langs.Language.lang_id
1✔
498

1✔
499
            std::fs::write(&dll_path, dll_bytes).unwrap();
1✔
500

1✔
501
            let version = Version::read_product_version(&dll_path).unwrap().unwrap();
1✔
502

1✔
503
            assert_eq!(
1✔
504
                version.release_ids,
1✔
505
                vec![
1✔
506
                    ReleaseId::Numeric(0),
1✔
507
                    ReleaseId::Numeric(18),
1✔
508
                    ReleaseId::Numeric(2)
1✔
509
                ]
1✔
510
            );
1✔
511
            assert!(version.pre_release_ids.is_empty());
1✔
512
        }
1✔
513

514
        #[test]
515
        fn version_read_product_version_should_error_with_path_if_path_does_not_exist() {
1✔
516
            let error = Version::read_product_version(Path::new("missing")).unwrap_err();
1✔
517

1✔
518
            assert!(error
1✔
519
                .to_string()
1✔
520
                .starts_with("An error was encountered while accessing the path \"missing\":"));
1✔
521
        }
1✔
522

523
        #[test]
524
        fn version_read_product_version_should_error_with_path_if_the_file_is_not_an_executable() {
1✔
525
            let error = Version::read_product_version(Path::new("Cargo.toml")).unwrap_err();
1✔
526

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

530
        #[test]
531
        fn version_read_product_version_should_return_none_if_there_is_no_version_info() {
1✔
532
            let version =
1✔
533
                Version::read_product_version(Path::new("tests/loot_api_python/loot_api.pyd"))
1✔
534
                    .unwrap();
1✔
535

1✔
536
            assert!(version.is_none());
1✔
537
        }
1✔
538
    }
539

540
    mod empty {
541
        use super::super::*;
542
        #[test]
543
        fn version_eq_an_empty_string_should_equal_an_empty_string() {
1✔
544
            assert_eq!(Version::from(""), Version::from(""));
1✔
545
        }
1✔
546

547
        #[test]
548
        fn version_eq_an_empty_string_should_equal_a_version_of_zero() {
1✔
549
            assert_eq!(Version::from(""), Version::from("0"));
1✔
550
            assert_eq!(Version::from("0"), Version::from(""));
1✔
551
        }
1✔
552

553
        #[test]
554
        fn version_eq_an_empty_string_should_not_equal_a_non_zero_version() {
1✔
555
            assert_ne!(Version::from(""), Version::from("5"));
1✔
556
            assert_ne!(Version::from("5"), Version::from(""));
1✔
557
        }
1✔
558

559
        #[test]
560
        fn version_partial_cmp_an_empty_string_should_be_less_than_a_non_zero_version() {
1✔
561
            assert!(Version::from("") < Version::from("1"));
1✔
562
            assert!(Version::from("1") > Version::from(""));
1✔
563
        }
1✔
564
    }
565

566
    mod numeric {
567
        use super::super::*;
568

569
        #[test]
570
        fn version_eq_a_non_empty_string_should_equal_itself() {
1✔
571
            assert_eq!(Version::from("5"), Version::from("5"));
1✔
572
        }
1✔
573

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

578
            assert_ne!(Version::from("4"), Version::from("5"));
1✔
579
            assert_ne!(Version::from("5"), Version::from("4"));
1✔
580
        }
1✔
581

582
        #[test]
583
        fn version_partial_cmp_single_digit_versions_should_compare_digits() {
1✔
584
            assert!(Version::from("4") < Version::from("5"));
1✔
585
            assert!(Version::from("5") > Version::from("4"));
1✔
586
        }
1✔
587

588
        #[test]
589
        fn version_eq_numeric_versions_should_compare_numbers() {
1✔
590
            assert_ne!(Version::from("5"), Version::from("10"));
1✔
591
            assert_ne!(Version::from("10"), Version::from("5"));
1✔
592
        }
1✔
593

594
        #[test]
595
        fn version_partial_cmp_numeric_versions_should_compare_numbers() {
1✔
596
            assert!(Version::from("5") < Version::from("10"));
1✔
597
            assert!(Version::from("10") > Version::from("5"));
1✔
598
        }
1✔
599
    }
600

601
    mod semver {
602
        use super::super::*;
603
        use super::is_cmp_eq;
604

605
        #[test]
606
        fn version_eq_should_compare_patch_numbers() {
1✔
607
            assert_eq!(Version::from("0.0.5"), Version::from("0.0.5"));
1✔
608

609
            assert_ne!(Version::from("0.0.5"), Version::from("0.0.10"));
1✔
610
            assert_ne!(Version::from("0.0.10"), Version::from("0.0.5"));
1✔
611
        }
1✔
612

613
        #[test]
614
        fn version_partial_cmp_should_compare_patch_numbers() {
1✔
615
            assert!(Version::from("0.0.5") < Version::from("0.0.10"));
1✔
616
            assert!(Version::from("0.0.10") > Version::from("0.0.5"));
1✔
617
        }
1✔
618

619
        #[test]
620
        fn version_eq_should_compare_minor_numbers() {
1✔
621
            assert_eq!(Version::from("0.5.0"), Version::from("0.5.0"));
1✔
622

623
            assert_ne!(Version::from("0.5.0"), Version::from("0.10.0"));
1✔
624
            assert_ne!(Version::from("0.10.0"), Version::from("0.5.0"));
1✔
625
        }
1✔
626

627
        #[test]
628
        fn version_partial_cmp_should_compare_minor_numbers() {
1✔
629
            assert!(Version::from("0.5.0") < Version::from("0.10.0"));
1✔
630
            assert!(Version::from("0.10.0") > Version::from("0.5.0"));
1✔
631
        }
1✔
632

633
        #[test]
634
        fn version_partial_cmp_minor_numbers_should_take_precedence_over_patch_numbers() {
1✔
635
            assert!(Version::from("0.5.10") < Version::from("0.10.5"));
1✔
636
            assert!(Version::from("0.10.5") > Version::from("0.5.10"));
1✔
637
        }
1✔
638

639
        #[test]
640
        fn version_eq_should_compare_major_numbers() {
1✔
641
            assert_eq!(Version::from("5.0.0"), Version::from("5.0.0"));
1✔
642

643
            assert_ne!(Version::from("5.0.0"), Version::from("10.0.0"));
1✔
644
            assert_ne!(Version::from("10.0.0"), Version::from("5.0.0"));
1✔
645
        }
1✔
646

647
        #[test]
648
        fn version_partial_cmp_should_compare_major_numbers() {
1✔
649
            assert!(Version::from("5.0.0") < Version::from("10.0.0"));
1✔
650
            assert!(Version::from("10.0.0") > Version::from("5.0.0"));
1✔
651
        }
1✔
652

653
        #[test]
654
        fn version_partial_cmp_major_numbers_should_take_precedence_over_minor_numbers() {
1✔
655
            assert!(Version::from("5.10.0") < Version::from("10.5.0"));
1✔
656
            assert!(Version::from("10.5.0") > Version::from("5.10.0"));
1✔
657
        }
1✔
658

659
        #[test]
660
        fn version_partial_cmp_major_numbers_should_take_precedence_over_patch_numbers() {
1✔
661
            assert!(Version::from("5.0.10") < Version::from("10.0.5"));
1✔
662
            assert!(Version::from("10.0.5") > Version::from("5.0.10"));
1✔
663
        }
1✔
664

665
        #[test]
666
        fn version_eq_should_consider_versions_that_differ_by_the_presence_of_a_pre_release_id_to_be_not_equal(
1✔
667
        ) {
1✔
668
            assert_ne!(Version::from("1.0.0"), Version::from("1.0.0-alpha"));
1✔
669
        }
1✔
670

671
        #[test]
672
        fn version_partial_cmp_should_treat_the_absence_of_a_pre_release_id_as_greater_than_its_presence(
1✔
673
        ) {
1✔
674
            assert!(Version::from("1.0.0-alpha") < Version::from("1.0.0"));
1✔
675
            assert!(Version::from("1.0.0") > Version::from("1.0.0-alpha"));
1✔
676
        }
1✔
677

678
        #[test]
679
        fn version_eq_should_compare_pre_release_identifiers() {
1✔
680
            assert_eq!(
1✔
681
                Version::from("0.0.5-5.alpha"),
1✔
682
                Version::from("0.0.5-5.alpha")
1✔
683
            );
1✔
684

685
            assert_ne!(
1✔
686
                Version::from("0.0.5-5.alpha"),
1✔
687
                Version::from("0.0.5-10.beta")
1✔
688
            );
1✔
689
            assert_ne!(
1✔
690
                Version::from("0.0.5-10.beta"),
1✔
691
                Version::from("0.0.5-5.alpha")
1✔
692
            );
1✔
693
        }
1✔
694

695
        #[test]
696
        fn version_partial_cmp_should_compare_numeric_pre_release_ids_numerically() {
1✔
697
            assert!(Version::from("0.0.5-5") < Version::from("0.0.5-10"));
1✔
698
            assert!(Version::from("0.0.5-10") > Version::from("0.0.5-5"));
1✔
699
        }
1✔
700

701
        #[test]
702
        fn version_partial_cmp_should_compare_non_numeric_pre_release_ids_lexically() {
1✔
703
            assert!(Version::from("0.0.5-a") < Version::from("0.0.5-b"));
1✔
704
            assert!(Version::from("0.0.5-b") > Version::from("0.0.5-a"));
1✔
705
        }
1✔
706

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

712
            assert!(Version::from("0.0.5-86") < Version::from("0.0.5-78b"));
1✔
713
            assert!(Version::from("0.0.5-78b") > Version::from("0.0.5-86"));
1✔
714
        }
1✔
715

716
        #[test]
717
        fn version_partial_cmp_earlier_pre_release_ids_should_take_precedence_over_later_ids() {
1✔
718
            assert!(Version::from("0.0.5-5.10") < Version::from("0.0.5-10.5"));
1✔
719
            assert!(Version::from("0.0.5-10.5") > Version::from("0.0.5-5.10"));
1✔
720
        }
1✔
721

722
        #[test]
723
        fn version_partial_cmp_a_version_with_more_pre_release_ids_is_greater() {
1✔
724
            assert!(Version::from("0.0.5-5") < Version::from("0.0.5-5.0"));
1✔
725
            assert!(Version::from("0.0.5-5.0") > Version::from("0.0.5-5"));
1✔
726
        }
1✔
727

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

734
        #[test]
735
        fn version_eq_should_ignore_metadata() {
1✔
736
            assert_eq!(Version::from("0.0.1+alpha"), Version::from("0.0.1+beta"));
1✔
737
        }
1✔
738

739
        #[test]
740
        fn version_partial_cmp_should_ignore_metadata() {
1✔
741
            assert!(is_cmp_eq(
1✔
742
                Version::from("0.0.1+alpha"),
1✔
743
                Version::from("0.0.1+1")
1✔
744
            ));
1✔
745
            assert!(is_cmp_eq(
1✔
746
                Version::from("0.0.1+1"),
1✔
747
                Version::from("0.0.1+alpha")
1✔
748
            ));
1✔
749

750
            assert!(is_cmp_eq(
1✔
751
                Version::from("0.0.1+2"),
1✔
752
                Version::from("0.0.1+1")
1✔
753
            ));
1✔
754
            assert!(is_cmp_eq(
1✔
755
                Version::from("0.0.1+1"),
1✔
756
                Version::from("0.0.1+2")
1✔
757
            ));
1✔
758
        }
1✔
759
    }
760

761
    mod extensions {
762
        use super::super::*;
763
        use super::is_cmp_eq;
764

765
        #[test]
766
        fn version_from_should_parse_comma_separated_versions() {
1✔
767
            // OBSE and SKSE use version string fields of the form "0, 2, 0, 12".
1✔
768
            let version = Version::from("0, 2, 0, 12");
1✔
769

1✔
770
            assert_eq!(
1✔
771
                version.release_ids,
1✔
772
                vec![
1✔
773
                    ReleaseId::Numeric(0),
1✔
774
                    ReleaseId::Numeric(2),
1✔
775
                    ReleaseId::Numeric(0),
1✔
776
                    ReleaseId::Numeric(12),
1✔
777
                ]
1✔
778
            );
1✔
779
            assert!(version.pre_release_ids.is_empty());
1✔
780
        }
1✔
781

782
        #[test]
783
        fn version_eq_should_ignore_leading_zeroes_in_major_version_numbers() {
1✔
784
            assert_eq!(Version::from("05.0.0"), Version::from("5.0.0"));
1✔
785
            assert_eq!(Version::from("5.0.0"), Version::from("05.0.0"));
1✔
786
        }
1✔
787

788
        #[test]
789
        fn version_partial_cmp_should_ignore_leading_zeroes_in_major_version_numbers() {
1✔
790
            assert!(is_cmp_eq(Version::from("05.0.0"), Version::from("5.0.0")));
1✔
791
            assert!(is_cmp_eq(Version::from("5.0.0"), Version::from("05.0.0")));
1✔
792
        }
1✔
793

794
        #[test]
795
        fn version_eq_should_ignore_leading_zeroes_in_minor_version_numbers() {
1✔
796
            assert_eq!(Version::from("0.05.0"), Version::from("0.5.0"));
1✔
797
            assert_eq!(Version::from("0.5.0"), Version::from("0.05.0"));
1✔
798
        }
1✔
799

800
        #[test]
801
        fn version_partial_cmp_should_ignore_leading_zeroes_in_minor_version_numbers() {
1✔
802
            assert!(is_cmp_eq(Version::from("0.05.0"), Version::from("0.5.0")));
1✔
803
            assert!(is_cmp_eq(Version::from("0.5.0"), Version::from("0.05.0")));
1✔
804
        }
1✔
805

806
        #[test]
807
        fn version_eq_should_ignore_leading_zeroes_in_patch_version_numbers() {
1✔
808
            assert_eq!(Version::from("0.0.05"), Version::from("0.0.5"));
1✔
809
            assert_eq!(Version::from("0.0.5"), Version::from("0.0.05"));
1✔
810
        }
1✔
811

812
        #[test]
813
        fn version_partial_cmp_should_ignore_leading_zeroes_in_patch_version_numbers() {
1✔
814
            assert!(is_cmp_eq(Version::from("0.0.05"), Version::from("0.0.5")));
1✔
815
            assert!(is_cmp_eq(Version::from("0.0.5"), Version::from("0.0.05")));
1✔
816
        }
1✔
817

818
        #[test]
819
        fn version_eq_should_ignore_leading_zeroes_in_numeric_pre_release_ids() {
1✔
820
            assert_eq!(Version::from("0.0.5-05"), Version::from("0.0.5-5"));
1✔
821
            assert_eq!(Version::from("0.0.5-5"), Version::from("0.0.5-05"));
1✔
822
        }
1✔
823

824
        #[test]
825
        fn version_partial_cmp_should_ignore_leading_zeroes_in_numeric_pre_release_ids() {
1✔
826
            assert!(is_cmp_eq(
1✔
827
                Version::from("0.0.5-05"),
1✔
828
                Version::from("0.0.5-5")
1✔
829
            ));
1✔
830
            assert!(is_cmp_eq(
1✔
831
                Version::from("0.0.5-5"),
1✔
832
                Version::from("0.0.5-05")
1✔
833
            ));
1✔
834
        }
1✔
835

836
        #[test]
837
        fn version_eq_should_compare_an_equal_but_arbitrary_number_of_version_numbers() {
1✔
838
            assert_eq!(Version::from("1.0.0.1.0.0"), Version::from("1.0.0.1.0.0"));
1✔
839

840
            assert_ne!(Version::from("1.0.0.0.0.0"), Version::from("1.0.0.0.0.1"));
1✔
841
            assert_ne!(Version::from("1.0.0.0.0.1"), Version::from("1.0.0.0.0.0"));
1✔
842
        }
1✔
843

844
        #[test]
845
        fn version_partial_cmp_should_compare_an_equal_but_arbitrary_number_of_version_numbers() {
1✔
846
            assert!(is_cmp_eq(
1✔
847
                Version::from("1.0.0.1.0.0"),
1✔
848
                Version::from("1.0.0.1.0.0")
1✔
849
            ));
1✔
850

851
            assert!(Version::from("1.0.0.0.0.0") < Version::from("1.0.0.0.0.1"));
1✔
852
            assert!(Version::from("1.0.0.0.0.1") > Version::from("1.0.0.0.0.0"));
1✔
853
        }
1✔
854

855
        #[test]
856
        fn version_eq_non_numeric_release_ids_should_be_compared_lexically() {
1✔
857
            assert_eq!(Version::from("1.0.0a"), Version::from("1.0.0a"));
1✔
858

859
            assert_ne!(Version::from("1.0.0a"), Version::from("1.0.0b"));
1✔
860
            assert_ne!(Version::from("1.0.0b"), Version::from("1.0.0a"));
1✔
861
        }
1✔
862

863
        #[test]
864
        fn version_partial_cmp_non_numeric_release_ids_should_be_compared_lexically() {
1✔
865
            assert!(Version::from("1.0.0a") < Version::from("1.0.0b"));
1✔
866
            assert!(Version::from("1.0.0b") > Version::from("1.0.0a"));
1✔
867
        }
1✔
868

869
        #[test]
870
        fn version_partial_cmp_numeric_and_non_numeric_release_ids_should_be_compared_by_leading_numeric_values_first(
1✔
871
        ) {
1✔
872
            assert!(Version::from("0.78b") < Version::from("0.86"));
1✔
873
            assert!(Version::from("0.86") > Version::from("0.78b"));
1✔
874
        }
1✔
875

876
        #[test]
877
        fn version_partial_cmp_non_numeric_release_ids_should_be_greater_than_release_ids() {
1✔
878
            assert!(Version::from("1.0.0") < Version::from("1.0.0a"));
1✔
879
            assert!(Version::from("1.0.0a") > Version::from("1.0.0"));
1✔
880
        }
1✔
881

882
        #[test]
883
        fn version_partial_cmp_any_release_id_may_be_non_numeric() {
1✔
884
            assert!(Version::from("1.0.0alpha.2") < Version::from("1.0.0beta.2"));
1✔
885
            assert!(Version::from("1.0.0beta.2") > Version::from("1.0.0alpha.2"));
1✔
886
        }
1✔
887

888
        #[test]
889
        fn version_eq_should_compare_release_ids_case_insensitively() {
1✔
890
            assert_eq!(Version::from("1.0.0A"), Version::from("1.0.0a"));
1✔
891
            assert_eq!(Version::from("1.0.0a"), Version::from("1.0.0A"));
1✔
892
        }
1✔
893

894
        #[test]
895
        fn version_partial_cmp_should_compare_release_ids_case_insensitively() {
1✔
896
            assert!(Version::from("1.0.0a") < Version::from("1.0.0B"));
1✔
897
            assert!(Version::from("1.0.0B") > Version::from("1.0.0a"));
1✔
898
        }
1✔
899

900
        #[test]
901
        fn version_eq_should_compare_pre_release_ids_case_insensitively() {
1✔
902
            assert_eq!(Version::from("1.0.0-Alpha"), Version::from("1.0.0-alpha"));
1✔
903
            assert_eq!(Version::from("1.0.0-alpha"), Version::from("1.0.0-Alpha"));
1✔
904
        }
1✔
905

906
        #[test]
907
        fn version_partial_cmp_should_compare_pre_release_ids_case_insensitively() {
1✔
908
            assert!(Version::from("1.0.0-alpha") < Version::from("1.0.0-Beta"));
1✔
909
            assert!(Version::from("1.0.0-Beta") > Version::from("1.0.0-alpha"));
1✔
910
        }
1✔
911

912
        #[test]
913
        fn version_eq_should_pad_release_id_vecs_to_equal_length_with_zeroes() {
1✔
914
            assert_eq!(Version::from("1-beta"), Version::from("1.0.0-beta"));
1✔
915
            assert_eq!(Version::from("1.0.0-beta"), Version::from("1-beta"));
1✔
916

917
            assert_eq!(Version::from("0.0.0.1"), Version::from("0.0.0.1.0.0"));
1✔
918
            assert_eq!(Version::from("0.0.0.1.0.0"), Version::from("0.0.0.1"));
1✔
919

920
            assert_ne!(Version::from("1.0.0.0"), Version::from("1.0.0.0.0.1"));
1✔
921
            assert_ne!(Version::from("1.0.0.0.0.1"), Version::from("1.0.0.0"));
1✔
922
        }
1✔
923

924
        #[test]
925
        fn version_partial_cmp_should_pad_release_id_vecs_to_equal_length_with_zeroes() {
1✔
926
            assert!(Version::from("1.0.0.0.0.0") < Version::from("1.0.0.1"));
1✔
927
            assert!(Version::from("1.0.0.1") > Version::from("1.0.0.0.0.0"));
1✔
928

929
            assert!(Version::from("1.0.0.0") < Version::from("1.0.0.0.0.1"));
1✔
930
            assert!(Version::from("1.0.0.0.0.1") > Version::from("1.0.0.0"));
1✔
931

932
            assert!(is_cmp_eq(
1✔
933
                Version::from("1.0.0.0.0.0"),
1✔
934
                Version::from("1.0.0.0")
1✔
935
            ));
1✔
936
            assert!(is_cmp_eq(
1✔
937
                Version::from("1.0.0.0"),
1✔
938
                Version::from("1.0.0.0.0.0")
1✔
939
            ));
1✔
940
        }
1✔
941

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

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

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

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

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

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

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