Fork of daniellemaywood.uk/gleam — Wasm codegen work
51 kB
1630 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use std::collections::HashMap;
5
6use camino::{Utf8Path, Utf8PathBuf};
7use ecow::EcoString;
8use hexpm::version::Version;
9use pretty_assertions::assert_eq;
10
11use gleam_core::{
12 Error,
13 build::Runtime,
14 config::{DenoConfig, DenoFlag, Docs, ErlangConfig, JavaScriptConfig},
15 manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource},
16 paths::ProjectPaths,
17 requirement::Requirement,
18};
19
20use crate::dependencies::*;
21
22#[test]
23fn list_manifest_format() {
24 let mut buffer = vec![];
25 let manifest = Manifest {
26 requirements: HashMap::new(),
27 packages: vec![
28 ManifestPackage {
29 name: "root".into(),
30 version: Version::parse("1.0.0").unwrap(),
31 build_tools: ["gleam".into()].into(),
32 otp_app: None,
33 requirements: vec![],
34 source: ManifestPackageSource::Hex {
35 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
36 },
37 },
38 ManifestPackage {
39 name: "aaa".into(),
40 version: Version::new(0, 4, 2),
41 build_tools: ["rebar3".into(), "make".into()].into(),
42 otp_app: Some("aaa_app".into()),
43 requirements: vec!["zzz".into(), "gleam_stdlib".into()],
44 source: ManifestPackageSource::Hex {
45 outer_checksum: Base16Checksum(vec![3, 22]),
46 },
47 },
48 ManifestPackage {
49 name: "zzz".into(),
50 version: Version::new(0, 4, 0),
51 build_tools: ["mix".into()].into(),
52 otp_app: None,
53 requirements: vec![],
54 source: ManifestPackageSource::Hex {
55 outer_checksum: Base16Checksum(vec![3, 22]),
56 },
57 },
58 ],
59 };
60 list_manifest_packages(&mut buffer, manifest).unwrap();
61 assert_eq!(
62 std::str::from_utf8(&buffer).unwrap(),
63 "Package Version
64------- -------
65root 1.0.0
66aaa 0.4.2
67zzz 0.4.0
68"
69 )
70}
71
72#[test]
73fn tree_format() {
74 let mut buffer = vec![];
75 let manifest = Manifest {
76 requirements: HashMap::new(),
77 packages: vec![
78 ManifestPackage {
79 name: "deps_proj".into(),
80 version: Version::parse("1.0.0").unwrap(),
81 build_tools: [].into(),
82 otp_app: None,
83 requirements: vec!["gleam_regexp".into(), "gleam_stdlib".into()],
84 source: ManifestPackageSource::Hex {
85 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
86 },
87 },
88 ManifestPackage {
89 name: "gleam_stdlib".into(),
90 version: Version::new(0, 52, 0),
91 build_tools: ["rebar3".into(), "make".into()].into(),
92 otp_app: Some("aaa_app".into()),
93 requirements: vec![],
94 source: ManifestPackageSource::Hex {
95 outer_checksum: Base16Checksum(vec![3, 22]),
96 },
97 },
98 ManifestPackage {
99 name: "gleam_regexp".into(),
100 version: Version::new(1, 0, 0),
101 build_tools: ["mix".into()].into(),
102 otp_app: None,
103 requirements: vec!["gleam_stdlib".into()],
104 source: ManifestPackageSource::Hex {
105 outer_checksum: Base16Checksum(vec![3, 22]),
106 },
107 },
108 ],
109 };
110
111 let options = TreeOptions {
112 package: None,
113 invert: None,
114 };
115
116 let root_package_name = EcoString::from("deps_proj");
117
118 list_package_and_dependencies_tree(
119 &mut buffer,
120 options,
121 manifest.packages.clone(),
122 root_package_name,
123 )
124 .unwrap();
125 assert_eq!(
126 std::str::from_utf8(&buffer).unwrap(),
127 r#"deps_proj v1.0.0
128├── gleam_regexp v1.0.0
129│ └── gleam_stdlib v0.52.0
130└── gleam_stdlib v0.52.0
131"#
132 )
133}
134
135#[test]
136fn tree_package_format() {
137 let mut buffer = vec![];
138 let manifest = Manifest {
139 requirements: HashMap::new(),
140 packages: vec![
141 ManifestPackage {
142 name: "gleam_stdlib".into(),
143 version: Version::new(0, 52, 0),
144 build_tools: ["rebar3".into(), "make".into()].into(),
145 otp_app: Some("aaa_app".into()),
146 requirements: vec![],
147 source: ManifestPackageSource::Hex {
148 outer_checksum: Base16Checksum(vec![3, 22]),
149 },
150 },
151 ManifestPackage {
152 name: "deps_proj".into(),
153 version: Version::parse("1.0.0").unwrap(),
154 build_tools: [].into(),
155 otp_app: None,
156 requirements: vec!["gleam_stdlib".into(), "gleam_regexp".into()],
157 source: ManifestPackageSource::Hex {
158 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
159 },
160 },
161 ManifestPackage {
162 name: "gleam_regexp".into(),
163 version: Version::new(1, 0, 0),
164 build_tools: ["mix".into()].into(),
165 otp_app: None,
166 requirements: vec!["gleam_stdlib".into()],
167 source: ManifestPackageSource::Hex {
168 outer_checksum: Base16Checksum(vec![3, 22]),
169 },
170 },
171 ],
172 };
173 let options = TreeOptions {
174 package: Some("gleam_regexp".to_string()),
175 invert: None,
176 };
177
178 let root_package_name = EcoString::from("deps_proj");
179
180 list_package_and_dependencies_tree(
181 &mut buffer,
182 options,
183 manifest.packages.clone(),
184 root_package_name,
185 )
186 .unwrap();
187 assert_eq!(
188 std::str::from_utf8(&buffer).unwrap(),
189 r#"gleam_regexp v1.0.0
190└── gleam_stdlib v0.52.0
191"#
192 )
193}
194
195#[test]
196fn tree_invert_format() {
197 let mut buffer = vec![];
198 let manifest = Manifest {
199 requirements: HashMap::new(),
200 packages: vec![
201 ManifestPackage {
202 name: "gleam_stdlib".into(),
203 version: Version::new(0, 52, 0),
204 build_tools: ["rebar3".into(), "make".into()].into(),
205 otp_app: Some("aaa_app".into()),
206 requirements: vec![],
207 source: ManifestPackageSource::Hex {
208 outer_checksum: Base16Checksum(vec![3, 22]),
209 },
210 },
211 ManifestPackage {
212 name: "deps_proj".into(),
213 version: Version::parse("1.0.0").unwrap(),
214 build_tools: [].into(),
215 otp_app: None,
216 requirements: vec!["gleam_stdlib".into(), "gleam_regexp".into()],
217 source: ManifestPackageSource::Hex {
218 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
219 },
220 },
221 ManifestPackage {
222 name: "gleam_regexp".into(),
223 version: Version::new(1, 0, 0),
224 build_tools: ["mix".into()].into(),
225 otp_app: None,
226 requirements: vec!["gleam_stdlib".into()],
227 source: ManifestPackageSource::Hex {
228 outer_checksum: Base16Checksum(vec![3, 22]),
229 },
230 },
231 ],
232 };
233 let options = TreeOptions {
234 package: None,
235 invert: Some("gleam_stdlib".to_string()),
236 };
237
238 let root_package_name = EcoString::from("deps_proj");
239
240 list_package_and_dependencies_tree(
241 &mut buffer,
242 options,
243 manifest.packages.clone(),
244 root_package_name,
245 )
246 .unwrap();
247 assert_eq!(
248 std::str::from_utf8(&buffer).unwrap(),
249 r#"gleam_stdlib v0.52.0
250├── deps_proj v1.0.0
251└── gleam_regexp v1.0.0
252 └── deps_proj v1.0.0
253"#
254 )
255}
256
257#[test]
258fn list_tree_invalid_package_format() {
259 let mut buffer = vec![];
260 let manifest = Manifest {
261 requirements: HashMap::new(),
262 packages: vec![
263 ManifestPackage {
264 name: "gleam_stdlib".into(),
265 version: Version::new(0, 52, 0),
266 build_tools: ["rebar3".into(), "make".into()].into(),
267 otp_app: Some("aaa_app".into()),
268 requirements: vec![],
269 source: ManifestPackageSource::Hex {
270 outer_checksum: Base16Checksum(vec![3, 22]),
271 },
272 },
273 ManifestPackage {
274 name: "gleam_regexp".into(),
275 version: Version::new(1, 0, 0),
276 build_tools: ["mix".into()].into(),
277 otp_app: None,
278 requirements: vec!["gleam_stdlib".into()],
279 source: ManifestPackageSource::Hex {
280 outer_checksum: Base16Checksum(vec![3, 22]),
281 },
282 },
283 ManifestPackage {
284 name: "root".into(),
285 version: Version::parse("1.0.0").unwrap(),
286 build_tools: [].into(),
287 otp_app: None,
288 requirements: vec!["gleam_regexp".into(), "gleam_stdlib".into()],
289 source: ManifestPackageSource::Hex {
290 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
291 },
292 },
293 ],
294 };
295 let options = TreeOptions {
296 package: Some("zzzzzz".to_string()),
297 invert: None,
298 };
299
300 let root_package_name = EcoString::from("deps_proj");
301
302 list_package_and_dependencies_tree(
303 &mut buffer,
304 options,
305 manifest.packages.clone(),
306 root_package_name,
307 )
308 .unwrap();
309 assert_eq!(
310 std::str::from_utf8(&buffer).unwrap(),
311 r#"Package not found. Please check the package name.
312"#
313 )
314}
315
316#[test]
317fn parse_gleam_add_specifier_invalid_semver() {
318 assert!(parse_gleam_add_specifier("some_package@1.2.3.4").is_err());
319}
320
321#[test]
322fn parse_gleam_add_specifier_non_numeric_version() {
323 assert!(parse_gleam_add_specifier("some_package@not_a_version").is_err());
324}
325
326#[test]
327fn parse_gleam_add_specifier_default() {
328 let provided = "some_package";
329 let expected = Requirement::hex(">= 0.0.0").unwrap();
330 let (package, version) = parse_gleam_add_specifier(provided).unwrap();
331 assert_eq!(version, expected);
332 assert_eq!("some_package", package);
333}
334
335#[test]
336fn parse_gleam_add_specifier_major_only() {
337 let provided = "wobble@1";
338 let expected = Requirement::hex(">= 1.0.0 and < 2.0.0").unwrap();
339 let (package, version) = parse_gleam_add_specifier(provided).unwrap();
340 assert_eq!(version, expected);
341 assert_eq!("wobble", package);
342}
343
344#[test]
345fn parse_gleam_add_specifier_major_and_minor() {
346 let provided = "wibble@1.2";
347 let expected = Requirement::hex(">= 1.2.0 and < 2.0.0").unwrap();
348 let (package, version) = parse_gleam_add_specifier(provided).unwrap();
349 assert_eq!(version, expected);
350 assert_eq!("wibble", package);
351}
352
353#[test]
354fn parse_gleam_add_specifier_major_minor_and_patch() {
355 let provided = "bobble@1.2.3";
356 let expected = Requirement::hex("1.2.3").unwrap();
357 let (package, version) = parse_gleam_add_specifier(provided).unwrap();
358 assert_eq!(version, expected);
359 assert_eq!("bobble", package);
360}
361
362#[test]
363fn missing_local_packages() {
364 let manifest = Manifest {
365 requirements: HashMap::new(),
366 packages: vec![
367 ManifestPackage {
368 name: "root".into(),
369 version: Version::parse("1.0.0").unwrap(),
370 build_tools: ["gleam".into()].into(),
371 otp_app: None,
372 requirements: vec![],
373 source: ManifestPackageSource::Hex {
374 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
375 },
376 },
377 ManifestPackage {
378 name: "local1".into(),
379 version: Version::parse("1.0.0").unwrap(),
380 build_tools: ["gleam".into()].into(),
381 otp_app: None,
382 requirements: vec![],
383 source: ManifestPackageSource::Hex {
384 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]),
385 },
386 },
387 ManifestPackage {
388 name: "local2".into(),
389 version: Version::parse("3.0.0").unwrap(),
390 build_tools: ["gleam".into()].into(),
391 otp_app: None,
392 requirements: vec![],
393 source: ManifestPackageSource::Hex {
394 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]),
395 },
396 },
397 ],
398 };
399 let mut extra = LocalPackages {
400 packages: [
401 ("local2".into(), Version::parse("2.0.0").unwrap()),
402 ("local3".into(), Version::parse("3.0.0").unwrap()),
403 ]
404 .into(),
405 }
406 .missing_local_packages(&manifest, "root");
407 extra.sort();
408 assert_eq!(
409 extra,
410 [
411 &ManifestPackage {
412 name: "local1".into(),
413 version: Version::parse("1.0.0").unwrap(),
414 build_tools: ["gleam".into()].into(),
415 otp_app: None,
416 requirements: vec![],
417 source: ManifestPackageSource::Hex {
418 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]),
419 },
420 },
421 &ManifestPackage {
422 name: "local2".into(),
423 version: Version::parse("3.0.0").unwrap(),
424 build_tools: ["gleam".into()].into(),
425 otp_app: None,
426 requirements: vec![],
427 source: ManifestPackageSource::Hex {
428 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]),
429 },
430 },
431 ]
432 )
433}
434
435#[test]
436fn extra_local_packages() {
437 let mut extra = LocalPackages {
438 packages: [
439 ("local1".into(), Version::parse("1.0.0").unwrap()),
440 ("local2".into(), Version::parse("2.0.0").unwrap()),
441 ("local3".into(), Version::parse("3.0.0").unwrap()),
442 ]
443 .into(),
444 }
445 .extra_local_packages(&Manifest {
446 requirements: HashMap::new(),
447 packages: vec![
448 ManifestPackage {
449 name: "local1".into(),
450 version: Version::parse("1.0.0").unwrap(),
451 build_tools: ["gleam".into()].into(),
452 otp_app: None,
453 requirements: vec![],
454 source: ManifestPackageSource::Hex {
455 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]),
456 },
457 },
458 ManifestPackage {
459 name: "local2".into(),
460 version: Version::parse("3.0.0").unwrap(),
461 build_tools: ["gleam".into()].into(),
462 otp_app: None,
463 requirements: vec![],
464 source: ManifestPackageSource::Hex {
465 outer_checksum: Base16Checksum(vec![4, 5]),
466 },
467 },
468 ],
469 });
470 extra.sort();
471 assert_eq!(
472 extra,
473 [
474 ("local2".into(), Version::new(2, 0, 0)),
475 ("local3".into(), Version::new(3, 0, 0)),
476 ]
477 )
478}
479
480#[test]
481fn provide_wrong_package() {
482 let mut provided = HashMap::new();
483 let project_paths = crate::project_paths_at_current_directory_without_toml();
484 let result = provide_local_package(
485 "wrong_name".into(),
486 Utf8Path::new("./test/hello_world"),
487 Utf8Path::new("./"),
488 &project_paths,
489 &mut provided,
490 &mut vec!["root".into(), "subpackage".into()],
491 );
492 match result {
493 Err(Error::WrongDependencyProvided {
494 expected, found, ..
495 }) => {
496 assert_eq!(expected, "wrong_name");
497 assert_eq!(found, "hello_world");
498 }
499 _ => {
500 panic!("Expected WrongDependencyProvided error")
501 }
502 }
503}
504
505#[test]
506fn provide_existing_package() {
507 let mut provided = HashMap::new();
508 let project_paths = crate::project_paths_at_current_directory_without_toml();
509
510 let result = provide_local_package(
511 "hello_world".into(),
512 Utf8Path::new("./test/hello_world"),
513 Utf8Path::new("./"),
514 &project_paths,
515 &mut provided,
516 &mut vec!["root".into(), "subpackage".into()],
517 );
518 assert_eq!(
519 result,
520 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap())
521 );
522
523 let result = provide_local_package(
524 "hello_world".into(),
525 Utf8Path::new("./test/hello_world"),
526 Utf8Path::new("./"),
527 &project_paths,
528 &mut provided,
529 &mut vec!["root".into(), "subpackage".into()],
530 );
531 assert_eq!(
532 result,
533 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap())
534 );
535}
536
537#[test]
538fn provide_conflicting_package() {
539 let mut provided = HashMap::new();
540 let project_paths = crate::project_paths_at_current_directory_without_toml();
541 let result = provide_local_package(
542 "hello_world".into(),
543 Utf8Path::new("./test/hello_world"),
544 Utf8Path::new("./"),
545 &project_paths,
546 &mut provided,
547 &mut vec!["root".into(), "subpackage".into()],
548 );
549 assert_eq!(
550 result,
551 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap())
552 );
553
554 let result = provide_package(
555 "hello_world".into(),
556 Utf8PathBuf::from("./test/other"),
557 ProvidedPackageSource::Local {
558 path: Utf8Path::new("./test/other").to_path_buf(),
559 },
560 &project_paths,
561 &mut provided,
562 &mut vec!["root".into(), "subpackage".into()],
563 );
564 match result {
565 Err(Error::ProvidedDependencyConflict { package, .. }) => {
566 assert_eq!(package, "hello_world");
567 }
568 _ => {
569 panic!("Expected ProvidedDependencyConflict error")
570 }
571 }
572}
573
574#[test]
575fn provided_is_absolute() {
576 let mut provided = HashMap::new();
577 let project_paths = crate::project_paths_at_current_directory_without_toml();
578 let result = provide_local_package(
579 "hello_world".into(),
580 Utf8Path::new("./test/hello_world"),
581 Utf8Path::new("./"),
582 &project_paths,
583 &mut provided,
584 &mut vec!["root".into(), "subpackage".into()],
585 );
586 assert_eq!(
587 result,
588 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap())
589 );
590 let package = provided.get("hello_world").unwrap().clone();
591 match package.source {
592 ProvidedPackageSource::Local { path } => {
593 assert!(path.is_absolute())
594 }
595 _ => {
596 panic!("Provide_local_package provided a package that is not local!")
597 }
598 }
599}
600
601#[test]
602fn provided_recursive() {
603 let mut provided = HashMap::new();
604 let project_paths = crate::project_paths_at_current_directory_without_toml();
605 let result = provide_local_package(
606 "hello_world".into(),
607 Utf8Path::new("./test/hello_world"),
608 Utf8Path::new("./"),
609 &project_paths,
610 &mut provided,
611 &mut vec!["root".into(), "hello_world".into(), "subpackage".into()],
612 );
613 assert_eq!(
614 result,
615 Err(Error::PackageCycle {
616 packages: vec!["subpackage".into(), "hello_world".into()],
617 })
618 )
619}
620
621#[test]
622fn provided_local_to_hex() {
623 let provided_package = ProvidedPackage {
624 version: Version::new(1, 0, 0),
625 source: ProvidedPackageSource::Local {
626 path: "canonical/path/to/package".into(),
627 },
628 requirements: [
629 (
630 "req_1".into(),
631 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
632 ),
633 (
634 "req_2".into(),
635 hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
636 ),
637 ]
638 .into(),
639 };
640
641 let hex_package = hexpm::Package {
642 name: "package".into(),
643 repository: "local".into(),
644 releases: vec![hexpm::Release {
645 version: Version::new(1, 0, 0),
646 retirement_status: None,
647 outer_checksum: vec![],
648 meta: (),
649 requirements: [
650 (
651 "req_1".into(),
652 hexpm::Dependency {
653 requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
654 optional: false,
655 app: None,
656 repository: None,
657 },
658 ),
659 (
660 "req_2".into(),
661 hexpm::Dependency {
662 requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
663 optional: false,
664 app: None,
665 repository: None,
666 },
667 ),
668 ]
669 .into(),
670 }],
671 };
672
673 assert_eq!(
674 provided_package.to_hex_package(&"package".into()),
675 hex_package
676 );
677}
678
679#[test]
680fn provided_git_to_hex() {
681 let provided_package = ProvidedPackage {
682 version: Version::new(1, 0, 0),
683 source: ProvidedPackageSource::Git {
684 repo: "https://github.com/gleam-lang/gleam.git".into(),
685 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(),
686 path: None,
687 },
688 requirements: [
689 (
690 "req_1".into(),
691 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
692 ),
693 (
694 "req_2".into(),
695 hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
696 ),
697 ]
698 .into(),
699 };
700
701 let hex_package = hexpm::Package {
702 name: "package".into(),
703 repository: "local".into(),
704 releases: vec![hexpm::Release {
705 version: Version::new(1, 0, 0),
706 retirement_status: None,
707 outer_checksum: vec![],
708 meta: (),
709 requirements: [
710 (
711 "req_1".into(),
712 hexpm::Dependency {
713 requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
714 optional: false,
715 app: None,
716 repository: None,
717 },
718 ),
719 (
720 "req_2".into(),
721 hexpm::Dependency {
722 requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
723 optional: false,
724 app: None,
725 repository: None,
726 },
727 ),
728 ]
729 .into(),
730 }],
731 };
732
733 assert_eq!(
734 provided_package.to_hex_package(&"package".into()),
735 hex_package
736 );
737}
738
739#[test]
740fn provided_local_to_manifest() {
741 let provided_package = ProvidedPackage {
742 version: Version::new(1, 0, 0),
743 source: ProvidedPackageSource::Local {
744 path: "canonical/path/to/package".into(),
745 },
746 requirements: [
747 (
748 "req_1".into(),
749 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
750 ),
751 (
752 "req_2".into(),
753 hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
754 ),
755 ]
756 .into(),
757 };
758
759 let manifest_package = ManifestPackage {
760 name: "package".into(),
761 version: Version::new(1, 0, 0),
762 otp_app: None,
763 build_tools: vec!["gleam".into()],
764 requirements: vec!["req_1".into(), "req_2".into()],
765 source: ManifestPackageSource::Local {
766 path: "canonical/path/to/package".into(),
767 },
768 };
769
770 assert_eq!(
771 provided_package.to_manifest_package("package"),
772 manifest_package
773 );
774}
775
776#[test]
777fn provided_git_to_manifest() {
778 let provided_package = ProvidedPackage {
779 version: Version::new(1, 0, 0),
780 source: ProvidedPackageSource::Git {
781 repo: "https://github.com/gleam-lang/gleam.git".into(),
782 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(),
783 path: None,
784 },
785 requirements: [
786 (
787 "req_1".into(),
788 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(),
789 ),
790 (
791 "req_2".into(),
792 hexpm::version::Range::new("== 1.0.0".into()).unwrap(),
793 ),
794 ]
795 .into(),
796 };
797
798 let manifest_package = ManifestPackage {
799 name: "package".into(),
800 version: Version::new(1, 0, 0),
801 otp_app: None,
802 build_tools: vec!["gleam".into()],
803 requirements: vec!["req_1".into(), "req_2".into()],
804 source: ManifestPackageSource::Git {
805 repo: "https://github.com/gleam-lang/gleam.git".into(),
806 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(),
807 path: None,
808 },
809 };
810
811 assert_eq!(
812 provided_package.to_manifest_package("package"),
813 manifest_package
814 );
815}
816
817#[test]
818fn validate_git_dependency_path_accepts_subdir() {
819 assert!(
820 validate_git_dependency_path(
821 "package",
822 Utf8Path::new("subdir"),
823 "https://github.com/gleam-lang/gleam.git"
824 )
825 .is_ok()
826 );
827}
828
829#[test]
830fn validate_git_dependency_path_accepts_nested_subdir() {
831 assert!(
832 validate_git_dependency_path(
833 "package",
834 Utf8Path::new("packages/subdir"),
835 "https://github.com/gleam-lang/gleam.git"
836 )
837 .is_ok()
838 );
839}
840
841#[test]
842fn validate_git_dependency_path_rejects_parent_traversal() {
843 let result = validate_git_dependency_path(
844 "package",
845 Utf8Path::new("../escape"),
846 "https://github.com/gleam-lang/gleam.git",
847 );
848 assert!(matches!(
849 result,
850 Err(Error::GitDependencyPathNotFound { .. })
851 ));
852}
853
854#[test]
855fn validate_git_dependency_path_rejects_nested_parent_traversal() {
856 let result = validate_git_dependency_path(
857 "package",
858 Utf8Path::new("packages/../../escape"),
859 "https://github.com/gleam-lang/gleam.git",
860 );
861 assert!(matches!(
862 result,
863 Err(Error::GitDependencyPathNotFound { .. })
864 ));
865}
866
867#[test]
868fn validate_git_dependency_path_rejects_absolute_path() {
869 let result = validate_git_dependency_path(
870 "package",
871 Utf8Path::new("/etc/passwd"),
872 "https://github.com/gleam-lang/gleam.git",
873 );
874 assert!(matches!(
875 result,
876 Err(Error::GitDependencyPathNotFound { .. })
877 ));
878}
879
880#[test]
881fn git_repo_dir_name_produces_expected_name() {
882 assert_eq!(
883 git_repo_dir_name("https://github.com/gleam-lang/gleam.git", "main"),
884 "https-github.com-gleam-lang-gleam-edfec3bb6bbeb6c1"
885 );
886}
887
888#[test]
889fn git_repo_dir_name_different_refs_produce_different_names() {
890 assert_eq!(
891 git_repo_dir_name("https://github.com/gleam-lang/gleam.git", "main"),
892 "https-github.com-gleam-lang-gleam-edfec3bb6bbeb6c1"
893 );
894 assert_eq!(
895 git_repo_dir_name("https://github.com/gleam-lang/gleam.git", "v1.0.0"),
896 "https-github.com-gleam-lang-gleam-90203c669cc91eee"
897 );
898}
899
900#[test]
901fn verified_requirements_equality_with_canonicalized_paths() {
902 let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory");
903 let temp_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf())
904 .expect("Path should be valid UTF-8");
905
906 let sub_dir = temp_path.join("subdir");
907 std::fs::create_dir(&sub_dir).expect("Failed to create a subdir");
908 let file_path = sub_dir.join("file.txt");
909 fs::write(&file_path, "content").expect("Failed to write to file");
910
911 let canonical_path = std::fs::canonicalize(&file_path).expect("Failed to canonicalize path");
912 let relative_path = temp_path.join("./subdir/../subdir/./file.txt");
913
914 let requirements1 = HashMap::from([(
915 EcoString::from("dep1"),
916 Requirement::Path {
917 path: Utf8PathBuf::from(canonical_path.to_str().expect("Path should be valid UTF-8")),
918 },
919 )]);
920
921 let requirements2 = HashMap::from([(
922 EcoString::from("dep1"),
923 Requirement::Path {
924 path: Utf8PathBuf::from(relative_path.to_string()),
925 },
926 )]);
927
928 assert!(
929 is_same_requirements(&requirements1, &requirements2, &temp_path)
930 .expect("Requirements should be the same")
931 );
932}
933
934#[test]
935fn test_path_dependency_config_updates() {
936 let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory");
937 let root_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf())
938 .expect("Path should be valid UTF-8");
939 let paths = ProjectPaths::new(root_path.clone());
940 let dep_path = root_path.join("dep");
941 std::fs::create_dir_all(&dep_path).expect("Failed to create dependency directory");
942 let build_packages_dir = root_path.join("build").join("packages");
943 std::fs::create_dir_all(&build_packages_dir)
944 .expect("Failed to create build/packages directory");
945
946 let config = "name = \"dep\"
947version = \"1.0.0\"
948
949[dependencies]
950";
951 let dep_config_path = dep_path.join("gleam.toml");
952 fs::write(&dep_config_path, config).expect("Failed to write to manifest file");
953
954 let requirements = HashMap::from([(
955 EcoString::from("dep"),
956 Requirement::Path {
957 path: Utf8PathBuf::from("dep"),
958 },
959 )]);
960
961 // Initial check testing
962
963 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
964 assert!(!unchanged, "fresh always needs resolution");
965
966 let fingerprint_path = build_packages_dir.join("dep.config_fingerprint");
967 assert!(fingerprint_path.exists(), "fingerprint must exist");
968
969 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
970 assert!(unchanged, "is unchanged");
971
972 // Set fingerprint mtime to some time in the past. This causes the mtime check to fail,
973 // so it moves on to checking the fingerprint itself.
974 let past = std::time::SystemTime::now() - std::time::Duration::from_secs(10);
975 let past = filetime::FileTime::from_system_time(past);
976 filetime::set_file_mtime(&fingerprint_path, past).unwrap();
977
978 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
979 assert!(unchanged, "mtime is outdated, but content has not changed");
980
981 // Writing new content means the mtime and the fingerprint checks will fail.
982 let config = "name = \"dep\"
983version = \"1.0.0\"
984
985[dependencies]
986blah = \">= 1.0.0\"
987";
988 fs::write(&dep_config_path, config).unwrap();
989
990 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
991 assert!(!unchanged, "content has changed");
992
993 // Run again, to ensure that the fingerprint has been updated.
994 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
995 assert!(unchanged, "no changes since last run");
996
997 // Test that mtime is checked first, and that content is not checked when the mtime
998 // is still valid. We do this by having having content that would fail the fingerprint
999 // check, but having a fresh mtime so it never gets checked.
1000 // This can never happen in reality as you can't update the content without updating
1001 // the mtime, but we create the situation in this test to verify the short-circuiting
1002 // behaviour.
1003 let config = "name = \"dep\"
1004version = \"1.0.0\"
1005
1006[dependencies]
1007blah = \">= 1.0.0\"
1008wub = \">= 1.0.0\"
1009";
1010 fs::write(&dep_config_path, config).unwrap();
1011 let future = std::time::SystemTime::now() + std::time::Duration::from_secs(10);
1012 let future = filetime::FileTime::from_system_time(future);
1013 filetime::set_file_mtime(&fingerprint_path, future).unwrap();
1014
1015 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap();
1016 assert!(unchanged, "fingerprint is outdated, but mtime is not");
1017}
1018
1019fn create_testable_unlock_manifest(
1020 packages: Vec<(EcoString, Version, Vec<EcoString>)>,
1021 requirements: Vec<(EcoString, EcoString)>,
1022) -> Manifest {
1023 let manifest_packages = packages
1024 .into_iter()
1025 .map(|(name, version, requirements)| ManifestPackage {
1026 name,
1027 version,
1028 build_tools: vec!["gleam".into()],
1029 otp_app: None,
1030 requirements,
1031 source: ManifestPackageSource::Hex {
1032 outer_checksum: Base16Checksum(vec![]),
1033 },
1034 })
1035 .collect();
1036
1037 let root_requirements = requirements
1038 .into_iter()
1039 .map(|(name, range)| {
1040 (
1041 name,
1042 Requirement::Hex {
1043 version: hexpm::version::Range::new(range.into()).unwrap(),
1044 },
1045 )
1046 })
1047 .collect();
1048
1049 Manifest {
1050 packages: manifest_packages,
1051 requirements: root_requirements,
1052 }
1053}
1054
1055#[test]
1056fn test_unlock_package() {
1057 let mut locked = HashMap::from([
1058 ("package_a".into(), Version::new(1, 0, 0)),
1059 ("package_b".into(), Version::new(2, 0, 0)),
1060 ("package_c".into(), Version::new(3, 0, 0)),
1061 ("package_d".into(), Version::new(4, 0, 0)),
1062 ]);
1063
1064 let packages = vec![
1065 (
1066 "package_a".into(),
1067 Version::new(1, 0, 0),
1068 vec!["package_b".into()],
1069 ),
1070 (
1071 "package_b".into(),
1072 Version::new(2, 0, 0),
1073 vec!["package_c".into()],
1074 ),
1075 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1076 ("package_d".into(), Version::new(4, 0, 0), vec![]),
1077 ];
1078
1079 let manifest = create_testable_unlock_manifest(packages, Vec::new());
1080
1081 let packages_to_unlock = vec!["package_a".into()];
1082 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1083
1084 assert!(!locked.contains_key("package_a"));
1085 assert!(!locked.contains_key("package_b"));
1086 assert!(!locked.contains_key("package_c"));
1087 assert!(locked.contains_key("package_d"));
1088}
1089
1090#[test]
1091fn test_unlock_package_without_manifest() {
1092 let mut locked = HashMap::from([
1093 ("package_a".into(), Version::new(1, 0, 0)),
1094 ("package_b".into(), Version::new(2, 0, 0)),
1095 ("package_c".into(), Version::new(3, 0, 0)),
1096 ]);
1097
1098 let packages_to_unlock = vec!["package_a".into()];
1099 unlock_packages(&mut locked, &packages_to_unlock, None).unwrap();
1100
1101 assert!(!locked.contains_key("package_a"));
1102 assert!(locked.contains_key("package_b"));
1103 assert!(locked.contains_key("package_c"));
1104}
1105
1106#[test]
1107fn test_unlock_nonexistent_package() {
1108 let initial_locked = HashMap::from([
1109 ("package_a".into(), Version::new(1, 0, 0)),
1110 ("package_b".into(), Version::new(2, 0, 0)),
1111 ]);
1112
1113 let packages = vec![
1114 (
1115 "package_a".into(),
1116 Version::new(1, 0, 0),
1117 vec!["package_b".into()],
1118 ),
1119 ("package_b".into(), Version::new(2, 0, 0), vec![]),
1120 ];
1121
1122 let manifest = create_testable_unlock_manifest(packages, Vec::new());
1123
1124 let packages_to_unlock = vec!["nonexistent_package".into()];
1125 let mut locked = initial_locked.clone();
1126 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1127
1128 assert_eq!(
1129 initial_locked, locked,
1130 "Locked packages should remain unchanged"
1131 );
1132}
1133
1134#[test]
1135fn test_unlock_multiple_packages() {
1136 let mut locked = HashMap::from([
1137 ("package_a".into(), Version::new(1, 0, 0)),
1138 ("package_b".into(), Version::new(2, 0, 0)),
1139 ("package_c".into(), Version::new(3, 0, 0)),
1140 ("package_d".into(), Version::new(4, 0, 0)),
1141 ("package_e".into(), Version::new(5, 0, 0)),
1142 ]);
1143
1144 let packages = vec![
1145 (
1146 "package_a".into(),
1147 Version::new(1, 0, 0),
1148 vec!["package_b".into()],
1149 ),
1150 (
1151 "package_b".into(),
1152 Version::new(2, 0, 0),
1153 vec!["package_c".into()],
1154 ),
1155 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1156 (
1157 "package_d".into(),
1158 Version::new(4, 0, 0),
1159 vec!["package_e".into()],
1160 ),
1161 ("package_e".into(), Version::new(5, 0, 0), vec![]),
1162 ];
1163
1164 let manifest = create_testable_unlock_manifest(packages, Vec::new());
1165
1166 let packages_to_unlock = vec!["package_a".into(), "package_d".into()];
1167 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1168
1169 assert!(!locked.contains_key("package_a"));
1170 assert!(!locked.contains_key("package_b"));
1171 assert!(!locked.contains_key("package_c"));
1172 assert!(!locked.contains_key("package_d"));
1173 assert!(!locked.contains_key("package_e"));
1174}
1175
1176#[test]
1177fn test_unlock_packages_empty_input() {
1178 let initial_locked = HashMap::from([
1179 ("package_a".into(), Version::new(1, 0, 0)),
1180 ("package_b".into(), Version::new(2, 0, 0)),
1181 ]);
1182
1183 let packages = vec![
1184 (
1185 "package_a".into(),
1186 Version::new(1, 0, 0),
1187 vec!["package_b".into()],
1188 ),
1189 ("package_b".into(), Version::new(2, 0, 0), vec![]),
1190 ];
1191
1192 let manifest = create_testable_unlock_manifest(packages, Vec::new());
1193
1194 let packages_to_unlock: Vec<EcoString> = vec![];
1195 let mut locked = initial_locked.clone();
1196 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1197
1198 assert_eq!(
1199 initial_locked, locked,
1200 "Locked packages should remain unchanged when no packages are specified to unlock"
1201 );
1202}
1203
1204#[test]
1205fn test_unlock_package_preserve_shared_deps() {
1206 let mut locked = HashMap::from([
1207 ("package_a".into(), Version::new(1, 0, 0)),
1208 ("package_b".into(), Version::new(2, 0, 0)),
1209 ("package_c".into(), Version::new(3, 0, 0)),
1210 ]);
1211
1212 let packages = vec![
1213 (
1214 "package_a".into(),
1215 Version::new(1, 0, 0),
1216 vec!["package_c".into()],
1217 ),
1218 (
1219 "package_b".into(),
1220 Version::new(2, 0, 0),
1221 vec!["package_c".into()],
1222 ),
1223 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1224 ];
1225
1226 let manifest = create_testable_unlock_manifest(packages, Vec::new());
1227
1228 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()];
1229 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1230
1231 assert!(!locked.contains_key("package_a"));
1232 assert!(locked.contains_key("package_b"));
1233 assert!(locked.contains_key("package_c"));
1234}
1235
1236#[test]
1237fn test_unlock_package_with_root_dep() {
1238 let mut locked = HashMap::from([
1239 ("package_a".into(), Version::new(1, 0, 0)),
1240 ("package_b".into(), Version::new(2, 0, 0)),
1241 ("package_c".into(), Version::new(3, 0, 0)),
1242 ]);
1243
1244 let packages = vec![
1245 (
1246 "package_a".into(),
1247 Version::new(1, 0, 0),
1248 vec!["package_b".into()],
1249 ),
1250 (
1251 "package_b".into(),
1252 Version::new(2, 0, 0),
1253 vec!["package_c".into()],
1254 ),
1255 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1256 ];
1257
1258 let requirements = vec![("package_b".into(), ">= 2.0.0".into())];
1259
1260 let manifest = create_testable_unlock_manifest(packages, requirements);
1261
1262 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()];
1263 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1264
1265 assert!(!locked.contains_key("package_a"));
1266 assert!(locked.contains_key("package_b"));
1267 assert!(locked.contains_key("package_c"));
1268}
1269
1270#[test]
1271fn test_unlock_root_dep_package() {
1272 let mut locked = HashMap::from([
1273 ("package_a".into(), Version::new(1, 0, 0)),
1274 ("package_b".into(), Version::new(2, 0, 0)),
1275 ("package_c".into(), Version::new(3, 0, 0)),
1276 ]);
1277
1278 let packages = vec![
1279 (
1280 "package_a".into(),
1281 Version::new(1, 0, 0),
1282 vec!["package_b".into()],
1283 ),
1284 ("package_b".into(), Version::new(2, 0, 0), vec![]),
1285 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1286 ];
1287
1288 let requirements = vec![("package_a".into(), ">= 1.0.0".into())];
1289
1290 let manifest = create_testable_unlock_manifest(packages, requirements);
1291
1292 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()];
1293 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1294
1295 assert!(!locked.contains_key("package_a"));
1296 assert!(!locked.contains_key("package_b"));
1297 assert!(locked.contains_key("package_c"));
1298}
1299
1300#[test]
1301fn test_unlock_package_with_and_without_root_dep() {
1302 let mut locked = HashMap::from([
1303 ("package_a".into(), Version::new(1, 0, 0)),
1304 ("package_b".into(), Version::new(2, 0, 0)),
1305 ("package_c".into(), Version::new(3, 0, 0)),
1306 ]);
1307
1308 let packages = vec![
1309 (
1310 "package_a".into(),
1311 Version::new(1, 0, 0),
1312 vec!["package_b".into(), "package_c".into()],
1313 ),
1314 ("package_b".into(), Version::new(2, 0, 0), vec![]),
1315 ("package_c".into(), Version::new(3, 0, 0), vec![]),
1316 ];
1317
1318 let requirements = vec![("package_b".into(), ">= 2.0.0".into())];
1319
1320 let manifest = create_testable_unlock_manifest(packages, requirements);
1321
1322 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()];
1323 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap();
1324
1325 assert!(!locked.contains_key("package_a"));
1326 assert!(locked.contains_key("package_b"));
1327 assert!(!locked.contains_key("package_c"));
1328}
1329
1330fn manifest_package(name: &str, version: &str, requirements: Vec<EcoString>) -> ManifestPackage {
1331 ManifestPackage {
1332 name: name.into(),
1333 version: Version::parse(version).unwrap(),
1334 build_tools: ["gleam".into()].into(),
1335 otp_app: None,
1336 requirements,
1337 source: ManifestPackageSource::Hex {
1338 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]),
1339 },
1340 }
1341}
1342
1343fn package_config(
1344 dependencies: HashMap<EcoString, Requirement>,
1345 dev_dependencies: HashMap<EcoString, Requirement>,
1346) -> PackageConfig {
1347 PackageConfig {
1348 name: "the_package".into(),
1349 version: Version::parse("1.0.0").unwrap(),
1350 gleam_version: None,
1351 licences: vec![],
1352 description: "".into(),
1353 documentation: Docs { pages: vec![] },
1354 dependencies,
1355 dev_dependencies,
1356 repository: None,
1357 links: vec![],
1358 erlang: ErlangConfig {
1359 application_start_module: None,
1360 application_start_argument: None,
1361 extra_applications: vec![],
1362 },
1363 javascript: JavaScriptConfig {
1364 typescript_declarations: false,
1365 source_maps: false,
1366 runtime: Runtime::NodeJs,
1367 deno: DenoConfig {
1368 allow_env: DenoFlag::AllowAll,
1369 allow_sys: true,
1370 allow_hrtime: true,
1371 allow_net: DenoFlag::AllowAll,
1372 allow_ffi: true,
1373 allow_read: DenoFlag::AllowAll,
1374 allow_run: DenoFlag::AllowAll,
1375 allow_write: DenoFlag::AllowAll,
1376 allow_all: true,
1377 unstable: true,
1378 location: None,
1379 },
1380 },
1381 target: Target::Erlang,
1382 internal_modules: None,
1383 }
1384}
1385
1386#[test]
1387fn test_remove_do_nothing() {
1388 let config = package_config(
1389 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]),
1390 HashMap::from([("b".into(), Requirement::hex("~>2.0").unwrap())]),
1391 );
1392
1393 let mut manifest = Manifest {
1394 requirements: HashMap::from([
1395 ("a".into(), Requirement::hex("~>1.0").unwrap()),
1396 ("b".into(), Requirement::hex("~>2.0").unwrap()),
1397 ]),
1398 packages: vec![
1399 manifest_package("a", "1.0.0", vec![]),
1400 manifest_package("b", "2.0.8", vec![]),
1401 ],
1402 };
1403
1404 let manifest_copy = manifest.clone();
1405
1406 remove_extra_requirements(&config, &mut manifest).unwrap();
1407
1408 assert_eq!(manifest.requirements, manifest_copy.requirements);
1409 assert_eq!(manifest.packages, manifest_copy.packages);
1410}
1411
1412#[test]
1413fn test_remove_simple() {
1414 let config = package_config(HashMap::new(), HashMap::new());
1415
1416 let mut manifest = Manifest {
1417 requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]),
1418 packages: vec![manifest_package("a", "1.0.0", vec![])],
1419 };
1420
1421 remove_extra_requirements(&config, &mut manifest).unwrap();
1422
1423 assert_eq!(manifest.requirements, config.dependencies);
1424 assert_eq!(manifest.packages, vec![]);
1425}
1426
1427#[test]
1428fn test_remove_package_with_transitive_dependencies() {
1429 let config = package_config(HashMap::new(), HashMap::new());
1430
1431 let mut manifest = Manifest {
1432 requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]),
1433 packages: vec![
1434 manifest_package("a", "1.0.0", vec!["b".into()]),
1435 manifest_package("b", "1.2.3", vec!["c".into()]),
1436 manifest_package("c", "2.0.0", vec![]),
1437 ],
1438 };
1439
1440 remove_extra_requirements(&config, &mut manifest).unwrap();
1441
1442 assert_eq!(manifest.requirements, config.dependencies);
1443 assert_eq!(manifest.packages, vec![]);
1444}
1445
1446#[test]
1447fn test_remove_package_with_shared_transitive_dependencies() {
1448 let config = package_config(
1449 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]),
1450 HashMap::new(),
1451 );
1452
1453 let mut manifest = Manifest {
1454 requirements: HashMap::from([
1455 ("a".into(), Requirement::hex("~>1.0").unwrap()),
1456 ("b".into(), Requirement::hex("~>1.0").unwrap()),
1457 ]),
1458 packages: vec![
1459 manifest_package("a", "1.0.0", vec!["c".into()]),
1460 manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]),
1461 manifest_package("c", "2.0.0", vec![]),
1462 manifest_package("d", "0.1.0", vec![]),
1463 ],
1464 };
1465
1466 remove_extra_requirements(&config, &mut manifest).unwrap();
1467
1468 assert_eq!(manifest.requirements, config.dependencies);
1469 assert_eq!(
1470 manifest.packages,
1471 vec![
1472 manifest_package("a", "1.0.0", vec!["c".into()]),
1473 manifest_package("c", "2.0.0", vec![]),
1474 ]
1475 );
1476}
1477
1478#[test]
1479fn test_remove_package_that_is_also_a_transitive_dependency() {
1480 let config = package_config(
1481 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]),
1482 HashMap::new(),
1483 );
1484
1485 let mut manifest = Manifest {
1486 requirements: HashMap::from([
1487 ("a".into(), Requirement::hex("~>1.0").unwrap()),
1488 ("b".into(), Requirement::hex("~>1.0").unwrap()),
1489 ]),
1490 packages: vec![
1491 manifest_package("a", "1.0.0", vec!["b".into(), "c".into()]),
1492 manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]),
1493 manifest_package("c", "2.0.0", vec![]),
1494 manifest_package("d", "0.1.0", vec![]),
1495 ],
1496 };
1497
1498 let manifest_copy = manifest.clone();
1499
1500 remove_extra_requirements(&config, &mut manifest).unwrap();
1501
1502 assert_eq!(manifest.requirements, config.dependencies);
1503 assert_eq!(manifest.packages, manifest_copy.packages);
1504}
1505
1506#[test]
1507fn test_pretty_print_major_versions_available() {
1508 let versions = vec![
1509 (
1510 "very_long_package_name".to_string(),
1511 (Version::new(18, 382, 43), Version::new(19, 0, 38)),
1512 ),
1513 (
1514 "gleam_stdlib".to_string(),
1515 (Version::new(0, 45, 0), Version::new(1, 0, 0)),
1516 ),
1517 (
1518 "short_name".to_string(),
1519 (Version::new(1, 0, 0), Version::new(2, 0, 0)),
1520 ),
1521 ]
1522 .into_iter()
1523 .collect();
1524
1525 let output = pretty_print_major_versions_available(versions);
1526
1527 insta::assert_snapshot!(output);
1528}
1529
1530#[test]
1531fn test_pretty_print_version_updates() {
1532 let versions = vec![
1533 (
1534 "gleam_stdlib".to_string(),
1535 (Version::new(0, 45, 0), Version::new(0, 46, 0)),
1536 ),
1537 (
1538 "wisp".to_string(),
1539 (Version::new(2, 1, 0), Version::new(2, 1, 1)),
1540 ),
1541 (
1542 "very_long_package_name".to_string(),
1543 (Version::new(12, 12, 12), Version::new(120, 12, 12)),
1544 ),
1545 ]
1546 .into_iter()
1547 .collect();
1548
1549 let output = pretty_print_version_updates(versions);
1550
1551 insta::assert_snapshot!(output);
1552}
1553
1554#[test]
1555fn test_pretty_print_outdated_versions() {
1556 let versions = vec![
1557 (
1558 "gleam_stdlib".to_string(),
1559 (Version::new(0, 45, 0), Version::new(0, 46, 0)),
1560 ),
1561 (
1562 "wisp".to_string(),
1563 (Version::new(2, 1, 0), Version::new(2, 1, 1)),
1564 ),
1565 (
1566 "very_long_package_name".to_string(),
1567 (Version::new(12, 12, 12), Version::new(120, 12, 12)),
1568 ),
1569 ]
1570 .into_iter()
1571 .collect();
1572
1573 let output = pretty_print_outdated_versions(12, versions);
1574
1575 insta::assert_snapshot!(output);
1576}
1577
1578#[test]
1579fn test_pretty_print_outdated_versions_no_updates() {
1580 let versions: HashMap<String, (Version, Version)> = HashMap::new();
1581
1582 let output = pretty_print_outdated_versions(12, versions);
1583
1584 insta::assert_snapshot!(output);
1585}
1586
1587#[test]
1588fn test_ensure_packages_exist_locally_all_present() {
1589 let manifest = Manifest {
1590 requirements: HashMap::new(),
1591 packages: vec![
1592 manifest_package("package_a", "1.0.0", vec![]),
1593 manifest_package("package_b", "2.0.0", vec![]),
1594 manifest_package("package_c", "3.0.0", vec![]),
1595 ],
1596 };
1597
1598 let packages_to_check = vec!["package_a".into(), "package_b".into()];
1599 let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check);
1600
1601 assert!(result.is_ok(), "All packages exist, should return Ok");
1602}
1603
1604#[test]
1605fn test_ensure_packages_exist_locally_some_missing() {
1606 let manifest = Manifest {
1607 requirements: HashMap::new(),
1608 packages: vec![
1609 manifest_package("package_a", "1.0.0", vec![]),
1610 manifest_package("package_b", "2.0.0", vec![]),
1611 ],
1612 };
1613
1614 let packages_to_check = vec![
1615 "package_a".into(),
1616 "package_b".into(),
1617 "missing_package".into(),
1618 "another_missing".into(),
1619 ];
1620 let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check);
1621
1622 match result {
1623 Err(Error::PackagesToUpdateNotExist { packages }) => {
1624 assert_eq!(packages.len(), 2);
1625 assert!(packages.contains(&"missing_package".into()));
1626 assert!(packages.contains(&"another_missing".into()));
1627 }
1628 _ => panic!("Expected PackagesToUpdateNotExist error"),
1629 }
1630}