Fork of daniellemaywood.uk/gleam — Wasm codegen work
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2026 The Gleam contributors
3
4use std::cmp::Ordering::{Equal, Greater, Less};
5use std::collections::HashMap;
6
7use parser::Error;
8
9use super::{
10 Identifier::{AlphaNumeric, Numeric},
11 *,
12};
13
14// Tests adapted from the tests for Elixir's version module
15
16macro_rules! version_parse_test {
17 ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr,$pre:expr, $build:expr) => {
18 #[test]
19 fn $name() {
20 assert_eq!(
21 Version::parse($input).unwrap(),
22 Version {
23 major: $major,
24 minor: $minor,
25 patch: $patch,
26 pre: $pre,
27 build: $build
28 }
29 );
30 }
31 };
32
33 ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr, $pre:expr) => {
34 #[test]
35 fn $name() {
36 assert_eq!(
37 Version::parse($input).unwrap(),
38 Version {
39 major: $major,
40 minor: $minor,
41 patch: $patch,
42 pre: $pre,
43 build: None
44 }
45 );
46 }
47 };
48
49 ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr) => {
50 #[test]
51 fn $name() {
52 assert_eq!(
53 Version::parse($input).unwrap(),
54 Version {
55 major: $major,
56 minor: $minor,
57 patch: $patch,
58 pre: vec![],
59 build: None
60 }
61 );
62 }
63 };
64}
65
66macro_rules! version_parse_fail_test {
67 ($name:ident, $input:expr) => {
68 #[test]
69 fn $name() {
70 println!("{}", $input);
71 Version::parse($input).unwrap_err();
72 }
73 };
74}
75
76macro_rules! version_parse_print {
77 ($name:ident, $input:expr) => {
78 #[test]
79 fn $name() {
80 assert_eq!($input, Version::parse($input).unwrap().to_string().as_str());
81 }
82 };
83}
84
85version_parse_test!(triplet, "1.2.3", 1, 2, 3);
86
87version_parse_test!(
88 build,
89 "1.4.5+ignore",
90 1,
91 4,
92 5,
93 vec![],
94 Some("ignore".to_string())
95);
96
97version_parse_test!(
98 two_part_build,
99 "0.0.1+sha.0702245",
100 0,
101 0,
102 1,
103 vec![],
104 Some("sha.0702245".to_string())
105);
106
107version_parse_test!(
108 pre,
109 "1.4.5-6-g3318bd5",
110 1,
111 4,
112 5,
113 vec![AlphaNumeric("6-g3318bd5".to_string())]
114);
115
116version_parse_test!(
117 multi_part_pre,
118 "1.4.5-6.7.eight",
119 1,
120 4,
121 5,
122 vec![Numeric(6), Numeric(7), AlphaNumeric("eight".to_string())]
123);
124
125version_parse_test!(
126 pre_and_build,
127 "1.4.5-6-g3318bd5+ignore",
128 1,
129 4,
130 5,
131 vec![AlphaNumeric("6-g3318bd5".to_string())],
132 Some("ignore".to_string())
133);
134
135version_parse_fail_test!(just_a_word, "wibble");
136
137version_parse_fail_test!(just_major, "2");
138
139version_parse_fail_test!(major_dor, "2.");
140
141version_parse_fail_test!(major_minor, "2.3");
142
143version_parse_fail_test!(major_minor_dot, "2.3.");
144
145version_parse_fail_test!(triplet_dash, "2.3.0-");
146
147version_parse_fail_test!(triplet_plus, "2.3.0+");
148
149version_parse_fail_test!(triplet_dot, "2.3.0.");
150
151version_parse_fail_test!(quad, "2.3.0.4");
152
153version_parse_fail_test!(missing_minor, "2.3.-rc.1");
154
155version_parse_fail_test!(missing_minor_with_dot, "2.3.+rc.1");
156
157version_parse_fail_test!(zero_pre, "2.3.0-01");
158
159version_parse_fail_test!(double_zero_pre, "2.3.00-1");
160
161version_parse_fail_test!(double_zero, "2.3.00");
162
163version_parse_fail_test!(leading_zero_minor, "2.03.0");
164
165version_parse_fail_test!(leading_zero_major, "02.3.0");
166
167version_parse_fail_test!(extra_whitespace, "0. 0.0");
168
169version_parse_fail_test!(and_in_version, "0.1.0-andpre");
170
171version_parse_print!(print_triplet, "1.100.1000");
172
173version_parse_print!(print_pre, "1.100.4-dev");
174
175version_parse_print!(print_pre_dot, "1.100.4-dev.1.r.t");
176
177version_parse_print!(print_build, "1.100.4+dev.1.r.t");
178
179version_parse_print!(print_pre_build, "1.100.4-ewfjhwefj.wefw.w.1.ff+dev.1.r.t");
180
181macro_rules! parse_range_test {
182 ($name:ident, $input:expr, $expected:expr) => {
183 #[test]
184 fn $name() {
185 assert_eq!(Version::parse_range($input).unwrap(), $expected);
186 }
187 };
188}
189
190macro_rules! parse_range_fail_test {
191 ($name:ident, $input:expr) => {
192 #[test]
193 fn $name() {
194 Version::parse_range($input).unwrap_err();
195 }
196 };
197}
198
199fn v(a: u32, b: u32, c: u32) -> Version {
200 Version::new(a, b, c)
201}
202
203fn v_(major: u32, minor: u32, patch: u32, pre: Vec<Identifier>, build: Option<String>) -> Version {
204 Version {
205 major,
206 minor,
207 patch,
208 pre,
209 build,
210 }
211}
212
213type PubgrubRange = pubgrub::Range<Version>;
214
215parse_range_test!(leading_space, " 1.2.3", PubgrubRange::singleton(v(1, 2, 3)));
216parse_range_test!(
217 trailing_space,
218 "1.2.3 ",
219 PubgrubRange::singleton(v(1, 2, 3))
220);
221
222parse_range_test!(eq_triplet, "== 1.2.3 ", PubgrubRange::singleton(v(1, 2, 3)));
223
224parse_range_test!(
225 eq_triplet_nospace,
226 "==1.2.3 ",
227 PubgrubRange::singleton(v(1, 2, 3))
228);
229
230parse_range_test!(
231 neq_triplet,
232 "!= 1.2.3",
233 PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 2, 4)))
234);
235
236parse_range_test!(implicit_eq, "2.2.3", PubgrubRange::singleton(v(2, 2, 3)));
237
238parse_range_test!(
239 range_pre_build,
240 "1.2.3-thing+oop",
241 PubgrubRange::singleton(v_(
242 1,
243 2,
244 3,
245 vec![Identifier::AlphaNumeric("thing".to_string())],
246 Some("oop".to_string())
247 ))
248);
249
250parse_range_test!(
251 and,
252 "< 1.2.3 and > 1.0.1",
253 PubgrubRange::strictly_lower_than(v(1, 2, 3))
254 .intersection(&PubgrubRange::higher_than(v(1, 0, 2)))
255);
256
257parse_range_test!(
258 or,
259 "< 1.2.3 or > 1.0.1",
260 PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 0, 2)))
261);
262
263parse_range_test!(gt, "> 1.0.0", PubgrubRange::higher_than(v(1, 0, 1)));
264parse_range_test!(gt_eq, ">= 1.0.0", PubgrubRange::higher_than(v(1, 0, 0)));
265parse_range_test!(lt, "< 1.0.0", PubgrubRange::strictly_lower_than(v(1, 0, 0)));
266parse_range_test!(
267 lt_eq,
268 "<= 1.0.0",
269 PubgrubRange::strictly_lower_than(v(1, 0, 1))
270);
271
272parse_range_test!(
273 pessimistic_pair,
274 "~> 2.2",
275 PubgrubRange::higher_than(v(2, 2, 0))
276 .intersection(&PubgrubRange::strictly_lower_than(v(3, 0, 0)))
277);
278
279parse_range_test!(
280 pessimistic_triplet,
281 "~> 4.6.5",
282 PubgrubRange::higher_than(v(4, 6, 5))
283 .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
284);
285
286parse_range_test!(
287 pessimistic_triplet_pre,
288 "~> 4.6.5-eee",
289 PubgrubRange::higher_than(v_(
290 4,
291 6,
292 5,
293 vec![Identifier::AlphaNumeric("eee".to_string())],
294 None,
295 ))
296 .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
297);
298
299parse_range_test!(
300 pessimistic_triplet_build,
301 "~> 4.6.5+eee",
302 PubgrubRange::higher_than(v_(4, 6, 5, vec![], Some("eee".to_string())))
303 .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
304);
305
306parse_range_test!(
307 greater_or_pessimistic,
308 "> 10.0.0 or ~> 3.0.0",
309 PubgrubRange::higher_than(v(10, 0, 1)).union(
310 &PubgrubRange::higher_than(v(3, 0, 0))
311 .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0)))
312 )
313);
314
315parse_range_test!(
316 pessimistic_or_pessimistic,
317 "~> 1.0.0 or ~> 3.0.0",
318 PubgrubRange::higher_than(v(1, 0, 0))
319 .intersection(&PubgrubRange::strictly_lower_than(v(1, 1, 0)))
320 .union(
321 &PubgrubRange::higher_than(v(3, 0, 0))
322 .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0)))
323 )
324);
325
326parse_range_test!(
327 pessimistic_and_gt,
328 "~> 0.6 and >= 0.6.16",
329 PubgrubRange::higher_than(v(0, 6, 0))
330 .intersection(&PubgrubRange::strictly_lower_than(v(1, 0, 0)))
331 .intersection(&PubgrubRange::higher_than(v(0, 6, 16)))
332);
333
334parse_range_test!(
335 pessimistic_and_gt_pre,
336 "~> 1.0-pre and >= 1.0.0-pre.5",
337 PubgrubRange::higher_than(v_(
338 1,
339 0,
340 0,
341 vec![Identifier::AlphaNumeric("pre".to_string())],
342 None
343 ))
344 .intersection(&PubgrubRange::strictly_lower_than(v(2, 0, 0)))
345 .intersection(&PubgrubRange::higher_than(v_(
346 1,
347 0,
348 0,
349 vec![
350 Identifier::AlphaNumeric("pre".to_string()),
351 Identifier::Numeric(5)
352 ],
353 None
354 )))
355);
356
357parse_range_fail_test!(range_quad, "1.1.1.1");
358parse_range_fail_test!(range_just_major, "1");
359parse_range_fail_test!(range_just_major_minor, "1.1");
360parse_range_fail_test!(alpha_component, "1.1.a");
361
362parse_range_fail_test!(range_word, "wibble");
363parse_range_fail_test!(range_major_dot, "2.");
364parse_range_fail_test!(range_major_minor_dot, "2.3.");
365parse_range_fail_test!(range_triplet_dash, "2.3.0-");
366parse_range_fail_test!(range_triplet_plus, "2.3.0+");
367parse_range_fail_test!(range_triplet_dot, "2.3.0.");
368parse_range_fail_test!(range_dot_dash, "2.3.-rc.1");
369parse_range_fail_test!(range_dot_plus, "2.3.+rc.1");
370parse_range_fail_test!(range_zero_pre, "2.3.0-01");
371parse_range_fail_test!(patch_zerozero_dash, "2.3.00-1");
372parse_range_fail_test!(patch_zerozero, "2.3.00");
373parse_range_fail_test!(minor_leading_zero, "2.03.0");
374parse_range_fail_test!(major_leading_zero, "02.3.0");
375parse_range_fail_test!(triplet_containing_whitespace, "0. 0.0");
376parse_range_fail_test!(dash_amp, "0.1.0-&&pre");
377
378parse_range_fail_test!(or_whitespace_before, "!= 1.2.3or == 1.0.1");
379parse_range_fail_test!(or_whitespace_after, "!= 1.2.3 or== 1.0.1");
380parse_range_fail_test!(and_whitespace_before, "!= 1.2.3and == 1.0.1");
381parse_range_fail_test!(and_whitespace_after, "!= 1.2.3 and== 1.0.1");
382
383parse_range_fail_test!(trailing_and, "1.1.1 and");
384parse_range_fail_test!(trailing_or, "1.1.1 or");
385parse_range_fail_test!(leading_and, "and 1.1.1");
386parse_range_fail_test!(leading_or, "and 1.1.1");
387parse_range_fail_test!(just_and, "and");
388parse_range_fail_test!(just_or, "and");
389
390parse_range_fail_test!(duplicate_eq, "== ==");
391parse_range_fail_test!(just_eq, "==");
392parse_range_fail_test!(empty, "");
393
394parse_range_fail_test!(pessimistic_major, "~> 1");
395
396macro_rules! assert_order {
397 ($name:ident, $left:expr, $ord:expr, $right:expr) => {
398 #[test]
399 fn $name() {
400 let left = Version::parse($left);
401 let right = Version::parse($right);
402 assert_eq!(left.cmp(&right), $ord)
403 }
404 };
405}
406
407assert_order!(ord_same, "1.0.0", Equal, "1.0.0");
408assert_order!(ord_same_build_right, "1.0.0", Equal, "1.0.0+1");
409assert_order!(ord_same_build_left, "1.0.0+1", Equal, "1.0.0");
410assert_order!(ord_same_diff_build, "1.0.0+1", Equal, "1.0.0+2");
411
412assert_order!(ord_diff_build, "1.0.0+2", Equal, "1.0.0+1");
413
414assert_order!(ord_major_greater, "3.0.0", Greater, "2.0.0");
415assert_order!(ord_major_lesser, "1.0.0", Less, "2.0.0");
416
417assert_order!(ord_minor_greater, "1.1.0", Greater, "1.0.0");
418assert_order!(ord_minor_lesser, "1.0.0", Less, "1.1.0");
419
420assert_order!(ord_patch_greater, "1.0.1", Greater, "1.0.0");
421assert_order!(ord_patch_lesser, "1.0.0", Less, "1.0.1");
422
423assert_order!(ord_pre_smaller_than_zero, "1.0.0", Greater, "1.0.0-rc1");
424assert_order!(ord_pre_smaller_than_zero_flip, "1.0.0-rc1", Less, "1.0.0");
425
426assert_order!(ord_pre_rc1_2, "1.0.0-rc1", Less, "1.0.0-rc2");
427
428#[test]
429fn manifest_toml() {
430 let manifest = toml::to_string(
431 &vec![
432 (
433 "gleam_stdlib".to_string(),
434 Version {
435 major: 0,
436 minor: 17,
437 patch: 1,
438 pre: vec![],
439 build: None,
440 },
441 ),
442 (
443 "thingy".to_string(),
444 Version {
445 major: 0,
446 minor: 1,
447 patch: 0,
448 pre: vec![],
449 build: None,
450 },
451 ),
452 ]
453 .into_iter()
454 .collect::<HashMap<String, Version>>(),
455 )
456 .unwrap();
457 let expected1 = r#"thingy = "0.1.0"
458gleam_stdlib = "0.17.1"
459"#;
460 let expected2 = r#"gleam_stdlib = "0.17.1"
461thingy = "0.1.0"
462"#;
463 assert!(manifest == expected1 || manifest == expected2);
464}
465
466#[test]
467fn missing_minor_has_correct_error_type() {
468 assert_eq!(Version::parse("1"), Err(Error::MinorVersionMissing(1)))
469}
470
471#[test]
472fn missing_patch_has_correct_error_type() {
473 assert_eq!(Version::parse("1.2"), Err(Error::PatchVersionMissing(1, 2)))
474}