Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-cli / src / dependencies / tests.rs
61 kB 1911 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 git: HashMap::new(), 406 } 407 .missing_local_packages(&manifest, "root"); 408 extra.sort(); 409 assert_eq!( 410 extra, 411 [ 412 &ManifestPackage { 413 name: "local1".into(), 414 version: Version::parse("1.0.0").unwrap(), 415 build_tools: ["gleam".into()].into(), 416 otp_app: None, 417 requirements: vec![], 418 source: ManifestPackageSource::Hex { 419 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), 420 }, 421 }, 422 &ManifestPackage { 423 name: "local2".into(), 424 version: Version::parse("3.0.0").unwrap(), 425 build_tools: ["gleam".into()].into(), 426 otp_app: None, 427 requirements: vec![], 428 source: ManifestPackageSource::Hex { 429 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), 430 }, 431 }, 432 ] 433 ) 434} 435 436#[test] 437fn extra_local_packages() { 438 let mut extra = LocalPackages { 439 packages: [ 440 ("local1".into(), Version::parse("1.0.0").unwrap()), 441 ("local2".into(), Version::parse("2.0.0").unwrap()), 442 ("local3".into(), Version::parse("3.0.0").unwrap()), 443 ] 444 .into(), 445 git: HashMap::new(), 446 } 447 .extra_local_packages(&Manifest { 448 requirements: HashMap::new(), 449 packages: vec![ 450 ManifestPackage { 451 name: "local1".into(), 452 version: Version::parse("1.0.0").unwrap(), 453 build_tools: ["gleam".into()].into(), 454 otp_app: None, 455 requirements: vec![], 456 source: ManifestPackageSource::Hex { 457 outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), 458 }, 459 }, 460 ManifestPackage { 461 name: "local2".into(), 462 version: Version::parse("3.0.0").unwrap(), 463 build_tools: ["gleam".into()].into(), 464 otp_app: None, 465 requirements: vec![], 466 source: ManifestPackageSource::Hex { 467 outer_checksum: Base16Checksum(vec![4, 5]), 468 }, 469 }, 470 ], 471 }); 472 extra.sort(); 473 assert_eq!( 474 extra, 475 [ 476 ("local2".into(), Version::new(2, 0, 0)), 477 ("local3".into(), Version::new(3, 0, 0)), 478 ] 479 ) 480} 481 482#[test] 483fn provide_wrong_package() { 484 let mut provided = HashMap::new(); 485 let project_paths = crate::project_paths_at_current_directory_without_toml(); 486 let result = provide_local_package( 487 "wrong_name".into(), 488 Utf8Path::new("./test/hello_world"), 489 Utf8Path::new("./"), 490 &project_paths, 491 &mut provided, 492 &mut vec!["root".into(), "subpackage".into()], 493 ); 494 match result { 495 Err(Error::WrongDependencyProvided { 496 expected, found, .. 497 }) => { 498 assert_eq!(expected, "wrong_name"); 499 assert_eq!(found, "hello_world"); 500 } 501 _ => { 502 panic!("Expected WrongDependencyProvided error") 503 } 504 } 505} 506 507#[test] 508fn provide_existing_package() { 509 let mut provided = HashMap::new(); 510 let project_paths = crate::project_paths_at_current_directory_without_toml(); 511 512 let result = provide_local_package( 513 "hello_world".into(), 514 Utf8Path::new("./test/hello_world"), 515 Utf8Path::new("./"), 516 &project_paths, 517 &mut provided, 518 &mut vec!["root".into(), "subpackage".into()], 519 ); 520 assert_eq!( 521 result, 522 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 523 ); 524 525 let result = provide_local_package( 526 "hello_world".into(), 527 Utf8Path::new("./test/hello_world"), 528 Utf8Path::new("./"), 529 &project_paths, 530 &mut provided, 531 &mut vec!["root".into(), "subpackage".into()], 532 ); 533 assert_eq!( 534 result, 535 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 536 ); 537} 538 539#[test] 540fn provide_conflicting_package() { 541 let mut provided = HashMap::new(); 542 let project_paths = crate::project_paths_at_current_directory_without_toml(); 543 let result = provide_local_package( 544 "hello_world".into(), 545 Utf8Path::new("./test/hello_world"), 546 Utf8Path::new("./"), 547 &project_paths, 548 &mut provided, 549 &mut vec!["root".into(), "subpackage".into()], 550 ); 551 assert_eq!( 552 result, 553 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 554 ); 555 556 let result = provide_package( 557 "hello_world".into(), 558 Utf8PathBuf::from("./test/other"), 559 SourceContext::Local { 560 path: Utf8Path::new("./test/other").to_path_buf(), 561 }, 562 &project_paths, 563 &mut provided, 564 &mut vec!["root".into(), "subpackage".into()], 565 ); 566 match result { 567 Err(Error::ProvidedDependencyConflict { package, .. }) => { 568 assert_eq!(package, "hello_world"); 569 } 570 _ => { 571 panic!("Expected ProvidedDependencyConflict error") 572 } 573 } 574} 575 576#[test] 577fn provided_is_absolute() { 578 let mut provided = HashMap::new(); 579 let project_paths = crate::project_paths_at_current_directory_without_toml(); 580 let result = provide_local_package( 581 "hello_world".into(), 582 Utf8Path::new("./test/hello_world"), 583 Utf8Path::new("./"), 584 &project_paths, 585 &mut provided, 586 &mut vec!["root".into(), "subpackage".into()], 587 ); 588 assert_eq!( 589 result, 590 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 591 ); 592 let package = provided.get("hello_world").unwrap().clone(); 593 match package.source { 594 ProvidedPackageSource::Local { path } => { 595 assert!(path.is_absolute()) 596 } 597 _ => { 598 panic!("Provide_local_package provided a package that is not local!") 599 } 600 } 601} 602 603#[test] 604fn provided_recursive() { 605 let mut provided = HashMap::new(); 606 let project_paths = crate::project_paths_at_current_directory_without_toml(); 607 let result = provide_local_package( 608 "hello_world".into(), 609 Utf8Path::new("./test/hello_world"), 610 Utf8Path::new("./"), 611 &project_paths, 612 &mut provided, 613 &mut vec!["root".into(), "hello_world".into(), "subpackage".into()], 614 ); 615 assert_eq!( 616 result, 617 Err(Error::PackageCycle { 618 packages: vec!["subpackage".into(), "hello_world".into()], 619 }) 620 ) 621} 622 623#[test] 624fn provided_local_to_hex() { 625 let provided_package = ProvidedPackage { 626 version: Version::new(1, 0, 0), 627 source: ProvidedPackageSource::Local { 628 path: "canonical/path/to/package".into(), 629 }, 630 requirements: [ 631 ( 632 "req_1".into(), 633 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 634 ), 635 ( 636 "req_2".into(), 637 hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 638 ), 639 ] 640 .into(), 641 }; 642 643 let hex_package = hexpm::Package { 644 name: "package".into(), 645 repository: "local".into(), 646 releases: vec![hexpm::Release { 647 version: Version::new(1, 0, 0), 648 retirement_status: None, 649 outer_checksum: vec![], 650 meta: (), 651 requirements: [ 652 ( 653 "req_1".into(), 654 hexpm::Dependency { 655 requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 656 optional: false, 657 app: None, 658 repository: None, 659 }, 660 ), 661 ( 662 "req_2".into(), 663 hexpm::Dependency { 664 requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 665 optional: false, 666 app: None, 667 repository: None, 668 }, 669 ), 670 ] 671 .into(), 672 }], 673 }; 674 675 assert_eq!( 676 provided_package.to_hex_package(&"package".into()), 677 hex_package 678 ); 679} 680 681#[test] 682fn provided_git_to_hex() { 683 let provided_package = ProvidedPackage { 684 version: Version::new(1, 0, 0), 685 source: ProvidedPackageSource::Git { 686 repo: "https://github.com/gleam-lang/gleam.git".into(), 687 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 688 path: None, 689 }, 690 requirements: [ 691 ( 692 "req_1".into(), 693 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 694 ), 695 ( 696 "req_2".into(), 697 hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 698 ), 699 ] 700 .into(), 701 }; 702 703 let hex_package = hexpm::Package { 704 name: "package".into(), 705 repository: "local".into(), 706 releases: vec![hexpm::Release { 707 version: Version::new(1, 0, 0), 708 retirement_status: None, 709 outer_checksum: vec![], 710 meta: (), 711 requirements: [ 712 ( 713 "req_1".into(), 714 hexpm::Dependency { 715 requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 716 optional: false, 717 app: None, 718 repository: None, 719 }, 720 ), 721 ( 722 "req_2".into(), 723 hexpm::Dependency { 724 requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 725 optional: false, 726 app: None, 727 repository: None, 728 }, 729 ), 730 ] 731 .into(), 732 }], 733 }; 734 735 assert_eq!( 736 provided_package.to_hex_package(&"package".into()), 737 hex_package 738 ); 739} 740 741#[test] 742fn provided_local_to_manifest() { 743 let provided_package = ProvidedPackage { 744 version: Version::new(1, 0, 0), 745 source: ProvidedPackageSource::Local { 746 path: "canonical/path/to/package".into(), 747 }, 748 requirements: [ 749 ( 750 "req_1".into(), 751 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 752 ), 753 ( 754 "req_2".into(), 755 hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 756 ), 757 ] 758 .into(), 759 }; 760 761 let manifest_package = ManifestPackage { 762 name: "package".into(), 763 version: Version::new(1, 0, 0), 764 otp_app: None, 765 build_tools: vec!["gleam".into()], 766 requirements: vec!["req_1".into(), "req_2".into()], 767 source: ManifestPackageSource::Local { 768 path: "canonical/path/to/package".into(), 769 }, 770 }; 771 772 assert_eq!( 773 provided_package.to_manifest_package("package"), 774 manifest_package 775 ); 776} 777 778#[test] 779fn provided_git_to_manifest() { 780 let provided_package = ProvidedPackage { 781 version: Version::new(1, 0, 0), 782 source: ProvidedPackageSource::Git { 783 repo: "https://github.com/gleam-lang/gleam.git".into(), 784 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 785 path: None, 786 }, 787 requirements: [ 788 ( 789 "req_1".into(), 790 hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), 791 ), 792 ( 793 "req_2".into(), 794 hexpm::version::Range::new("== 1.0.0".into()).unwrap(), 795 ), 796 ] 797 .into(), 798 }; 799 800 let manifest_package = ManifestPackage { 801 name: "package".into(), 802 version: Version::new(1, 0, 0), 803 otp_app: None, 804 build_tools: vec!["gleam".into()], 805 requirements: vec!["req_1".into(), "req_2".into()], 806 source: ManifestPackageSource::Git { 807 repo: "https://github.com/gleam-lang/gleam.git".into(), 808 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 809 path: None, 810 }, 811 }; 812 813 assert_eq!( 814 provided_package.to_manifest_package("package"), 815 manifest_package 816 ); 817} 818 819#[test] 820fn provided_git_path_package_resolves_repo_relative_path() { 821 let repo_root = fs::canonicalise(Utf8Path::new("./test")).unwrap(); 822 let result = resolve_git_path_package( 823 &"hello_world".into(), 824 Utf8Path::new("../hello_world"), 825 &"https://github.com/gleam-lang/wibble.git".into(), 826 &repo_root.join("hello_world"), 827 &repo_root, 828 ); 829 assert_eq!( 830 result, 831 Ok((repo_root.join("hello_world"), "hello_world".into())) 832 ); 833} 834 835#[test] 836fn provided_git_path_package_child_uses_canonical_parent_location() { 837 let tmp = tempfile::tempdir().unwrap(); 838 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 839 let repo = base.join("repo"); 840 fs::mkdir(&repo.join("packages").join("package_a")).unwrap(); 841 fs::mkdir(&repo.join("packages").join("package_b")).unwrap(); 842 fs::write( 843 &repo.join("packages").join("package_a").join("gleam.toml"), 844 r#" 845name = "package_a" 846version = "0.1.0" 847 848[dependencies] 849package_b = { path = "../package_b" } 850"#, 851 ) 852 .unwrap(); 853 fs::write( 854 &repo.join("packages").join("package_b").join("gleam.toml"), 855 r#" 856name = "package_b" 857version = "0.2.0" 858"#, 859 ) 860 .unwrap(); 861 862 let mut provided = HashMap::new(); 863 let project_paths = crate::project_paths_at_current_directory_without_toml(); 864 let repo_root = fs::canonicalise(&repo).unwrap(); 865 let result = provide_package( 866 "package_a".into(), 867 fs::canonicalise(&repo.join("packages").join("package_a")).unwrap(), 868 SourceContext::Git { 869 repo: "https://github.com/gleam-lang/wibble.git".into(), 870 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 871 path: Some("packages/package_a".into()), 872 repo_root: &repo_root, 873 }, 874 &project_paths, 875 &mut provided, 876 &mut vec!["root".into()], 877 ); 878 assert_eq!( 879 result, 880 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 881 ); 882 let package = provided.get("package_b").unwrap(); 883 assert_eq!( 884 package.source, 885 ProvidedPackageSource::Git { 886 repo: "https://github.com/gleam-lang/wibble.git".into(), 887 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 888 path: Some("packages/package_b".into()), 889 } 890 ); 891} 892 893#[test] 894fn provided_git_path_package_to_repo_root_has_no_path() { 895 let tmp = tempfile::tempdir().unwrap(); 896 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 897 let repo = base.join("repo"); 898 fs::mkdir(&repo.join("backends").join("package_a")).unwrap(); 899 fs::write( 900 &repo.join("gleam.toml"), 901 r#" 902name = "package_b" 903version = "0.2.0" 904"#, 905 ) 906 .unwrap(); 907 fs::write( 908 &repo.join("backends").join("package_a").join("gleam.toml"), 909 r#" 910name = "package_a" 911version = "0.1.0" 912 913[dependencies] 914package_b = { path = "../.." } 915"#, 916 ) 917 .unwrap(); 918 919 let mut provided = HashMap::new(); 920 let project_paths = crate::project_paths_at_current_directory_without_toml(); 921 let repo_root = fs::canonicalise(&repo).unwrap(); 922 let result = provide_package( 923 "package_a".into(), 924 fs::canonicalise(&repo.join("backends").join("package_a")).unwrap(), 925 SourceContext::Git { 926 repo: "https://github.com/gleam-lang/wibble.git".into(), 927 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 928 path: Some("backends/package_a".into()), 929 repo_root: &repo_root, 930 }, 931 &project_paths, 932 &mut provided, 933 &mut vec!["root".into()], 934 ); 935 assert_eq!( 936 result, 937 Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) 938 ); 939 // A path dependency resolving to the repository root must match how the 940 // same package is provided when declared directly as a git dependency 941 // with no path (`None`), otherwise it is reported as a conflict. 942 let package = provided.get("package_b").unwrap(); 943 assert_eq!( 944 package.source, 945 ProvidedPackageSource::Git { 946 repo: "https://github.com/gleam-lang/wibble.git".into(), 947 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 948 path: None, 949 } 950 ); 951} 952 953#[test] 954fn provided_git_path_package_rejects_missing_directory() { 955 let tmp = tempfile::tempdir().unwrap(); 956 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 957 let repo = base.join("repo"); 958 fs::mkdir(&repo.join("parent")).unwrap(); 959 960 let repo_root = fs::canonicalise(&repo).unwrap(); 961 let result = resolve_git_path_package( 962 &"missing".into(), 963 Utf8Path::new("../missing"), 964 &"https://github.com/gleam-lang/wibble.git".into(), 965 &repo_root.join("parent"), 966 &repo_root, 967 ); 968 assert_eq!( 969 result, 970 Err(Error::GitDependencyPathNotFound { 971 package: "missing".into(), 972 path: "../missing".into(), 973 repo: "https://github.com/gleam-lang/wibble.git".into(), 974 }) 975 ); 976} 977 978#[test] 979fn resolve_git_subdir_accepts_directory_inside_repo() { 980 let tmp = tempfile::tempdir().unwrap(); 981 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 982 let clone = base.join("clone"); 983 fs::mkdir(&clone.join("sub")).unwrap(); 984 985 let expected = fs::canonicalise(&clone).unwrap().join("sub"); 986 assert_eq!( 987 resolve_git_subdir(&clone, Utf8Path::new("sub")), 988 Some(expected) 989 ); 990} 991 992#[test] 993fn resolve_git_subdir_rejects_missing_directory() { 994 let tmp = tempfile::tempdir().unwrap(); 995 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 996 let clone = base.join("clone"); 997 fs::mkdir(&clone).unwrap(); 998 999 assert_eq!(resolve_git_subdir(&clone, Utf8Path::new("missing")), None); 1000} 1001 1002#[cfg(unix)] 1003#[test] 1004fn resolve_git_subdir_rejects_symlink_escaping_repo() { 1005 let tmp = tempfile::tempdir().unwrap(); 1006 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1007 let clone = base.join("clone"); 1008 let outside = base.join("outside"); 1009 fs::mkdir(&clone).unwrap(); 1010 fs::mkdir(&outside).unwrap(); 1011 std::os::unix::fs::symlink(outside.as_std_path(), clone.join("escape").as_std_path()).unwrap(); 1012 1013 assert_eq!(resolve_git_subdir(&clone, Utf8Path::new("escape")), None); 1014} 1015 1016#[test] 1017fn provided_git_path_package_rejects_escaping_path() { 1018 let tmp = tempfile::tempdir().unwrap(); 1019 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1020 let repo = base.join("repo"); 1021 let outside = base.join("outside"); 1022 fs::mkdir(&repo.join("parent")).unwrap(); 1023 fs::mkdir(&outside).unwrap(); 1024 1025 let repo_root = fs::canonicalise(&repo).unwrap(); 1026 let result = resolve_git_path_package( 1027 &"package_a".into(), 1028 Utf8Path::new("../../outside"), 1029 &"https://github.com/gleam-lang/wibble.git".into(), 1030 &repo_root.join("parent"), 1031 &repo_root, 1032 ); 1033 assert_eq!( 1034 result, 1035 Err(Error::GitDependencyPathNotFound { 1036 package: "package_a".into(), 1037 path: "../../outside".into(), 1038 repo: "https://github.com/gleam-lang/wibble.git".into(), 1039 }) 1040 ); 1041} 1042 1043#[cfg(unix)] 1044#[test] 1045fn provided_git_path_package_rejects_symlink_escaping_repo() { 1046 let tmp = tempfile::tempdir().unwrap(); 1047 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1048 let repo = base.join("repo"); 1049 let outside = base.join("outside"); 1050 fs::mkdir(&repo.join("parent")).unwrap(); 1051 fs::mkdir(&outside).unwrap(); 1052 std::os::unix::fs::symlink(outside.as_std_path(), repo.join("escape").as_std_path()).unwrap(); 1053 1054 let repo_root = fs::canonicalise(&repo).unwrap(); 1055 let result = resolve_git_path_package( 1056 &"escape".into(), 1057 Utf8Path::new("../escape"), 1058 &"https://github.com/gleam-lang/wibble.git".into(), 1059 &repo_root.join("parent"), 1060 &repo_root, 1061 ); 1062 assert_eq!( 1063 result, 1064 Err(Error::GitDependencyPathNotFound { 1065 package: "escape".into(), 1066 path: "../escape".into(), 1067 repo: "https://github.com/gleam-lang/wibble.git".into(), 1068 }) 1069 ); 1070} 1071 1072#[test] 1073fn git_repo_dir_name_uses_repo_basename_and_hash_of_full_url() { 1074 assert_eq!( 1075 git_repo_dir_name("https://github.com/gleam-lang/gleam"), 1076 "gleam-4d40009bf5eb0110" 1077 ); 1078 assert_eq!( 1079 git_repo_dir_name("https://github.com/gleam-lang/gleam.git"), 1080 "gleam-ea2aeaa3761e8bc6" 1081 ); 1082 assert_eq!( 1083 git_repo_dir_name("git@github.com:gleam-lang/gleam.git"), 1084 "gleam-102c9e1e5cf87965" 1085 ); 1086 assert_eq!( 1087 git_repo_dir_name("C:\\src\\gleam.git"), 1088 "gleam-ab03c1f252f639da" 1089 ); 1090} 1091 1092#[test] 1093fn git_staging_path_is_clone_path_with_staging_suffix() { 1094 let paths = ProjectPaths::new("/app".into()); 1095 let repo = "https://github.com/gleam-lang/gleam.git"; 1096 assert_eq!( 1097 paths.build_git_repo(&git_repo_dir_name(repo)), 1098 Utf8PathBuf::from("/app/build/git/gleam-ea2aeaa3761e8bc6") 1099 ); 1100 assert_eq!( 1101 git_staging_path(&paths, repo, "wibble"), 1102 Utf8PathBuf::from("/app/build/git/gleam-ea2aeaa3761e8bc6-wibble-staging") 1103 ); 1104} 1105 1106#[test] 1107fn git_checkout_cleanup_deletes_staging_directory() { 1108 let tmp = tempfile::tempdir().unwrap(); 1109 let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1110 let staging_path = base.join("clone-staging"); 1111 fs::mkdir(&staging_path).unwrap(); 1112 fs::write(&staging_path.join("gleam.toml"), "name = \"wibble\"").unwrap(); 1113 1114 let checkout = GitCheckout::Staged { 1115 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 1116 staging_path: staging_path.clone(), 1117 }; 1118 assert_eq!(checkout.cleanup(), Ok(())); 1119 1120 assert!(!staging_path.exists()); 1121} 1122 1123#[test] 1124fn git_checkout_cleanup_without_staging_is_noop() { 1125 let checkout = GitCheckout::InPlace { 1126 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 1127 }; 1128 assert_eq!(checkout.cleanup(), Ok(())); 1129} 1130 1131#[test] 1132fn remove_unused_git_clones_sweeps_unexpected_directories() { 1133 let tmp = tempfile::tempdir().unwrap(); 1134 let root = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1135 let paths = ProjectPaths::new(root); 1136 let repo = "https://github.com/gleam-lang/gleam.git"; 1137 1138 let expected_clone = paths.build_git_repo(&git_repo_dir_name(repo)); 1139 let stale_clone = paths.build_git_repo("other-0011223344556677"); 1140 let stale_staging = paths.build_git_repo(&format!("{}-staging", git_repo_dir_name(repo))); 1141 fs::mkdir(&expected_clone).unwrap(); 1142 fs::mkdir(&stale_clone).unwrap(); 1143 fs::mkdir(&stale_staging).unwrap(); 1144 1145 let manifest = Manifest { 1146 requirements: HashMap::new(), 1147 packages: vec![ManifestPackage { 1148 name: "wibble".into(), 1149 version: Version::new(1, 0, 0), 1150 build_tools: ["gleam".into()].into(), 1151 otp_app: None, 1152 requirements: vec![], 1153 source: ManifestPackageSource::Git { 1154 repo: repo.into(), 1155 commit: "95cd2c2f45907e5571e9b5fcdfb27ff35cdcdd29".into(), 1156 path: Some("wibble".into()), 1157 }, 1158 }], 1159 }; 1160 1161 assert_eq!(remove_unused_git_clones(&paths, &manifest), Ok(())); 1162 1163 assert!(expected_clone.is_dir()); 1164 assert!(!stale_clone.exists()); 1165 assert!(!stale_staging.exists()); 1166} 1167 1168#[test] 1169fn remove_unused_git_clones_missing_directory_is_noop() { 1170 let tmp = tempfile::tempdir().unwrap(); 1171 let root = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).unwrap(); 1172 let paths = ProjectPaths::new(root); 1173 let manifest = Manifest { 1174 requirements: HashMap::new(), 1175 packages: vec![], 1176 }; 1177 1178 assert_eq!(remove_unused_git_clones(&paths, &manifest), Ok(())); 1179} 1180 1181#[test] 1182fn verified_requirements_equality_with_canonicalized_paths() { 1183 let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory"); 1184 let temp_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf()) 1185 .expect("Path should be valid UTF-8"); 1186 1187 let sub_dir = temp_path.join("subdir"); 1188 std::fs::create_dir(&sub_dir).expect("Failed to create a subdir"); 1189 let file_path = sub_dir.join("file.txt"); 1190 fs::write(&file_path, "content").expect("Failed to write to file"); 1191 1192 let canonical_path = std::fs::canonicalize(&file_path).expect("Failed to canonicalize path"); 1193 let relative_path = temp_path.join("./subdir/../subdir/./file.txt"); 1194 1195 let requirements1 = HashMap::from([( 1196 EcoString::from("dep1"), 1197 Requirement::Path { 1198 path: Utf8PathBuf::from(canonical_path.to_str().expect("Path should be valid UTF-8")), 1199 }, 1200 )]); 1201 1202 let requirements2 = HashMap::from([( 1203 EcoString::from("dep1"), 1204 Requirement::Path { 1205 path: Utf8PathBuf::from(relative_path.to_string()), 1206 }, 1207 )]); 1208 1209 assert!( 1210 is_same_requirements(&requirements1, &requirements2, &temp_path) 1211 .expect("Requirements should be the same") 1212 ); 1213} 1214 1215#[test] 1216fn test_path_dependency_config_updates() { 1217 let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory"); 1218 let root_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf()) 1219 .expect("Path should be valid UTF-8"); 1220 let paths = ProjectPaths::new(root_path.clone()); 1221 let dep_path = root_path.join("dep"); 1222 std::fs::create_dir_all(&dep_path).expect("Failed to create dependency directory"); 1223 let build_packages_dir = root_path.join("build").join("packages"); 1224 std::fs::create_dir_all(&build_packages_dir) 1225 .expect("Failed to create build/packages directory"); 1226 1227 let config = "name = \"dep\" 1228version = \"1.0.0\" 1229 1230[dependencies] 1231"; 1232 let dep_config_path = dep_path.join("gleam.toml"); 1233 fs::write(&dep_config_path, config).expect("Failed to write to manifest file"); 1234 1235 let requirements = HashMap::from([( 1236 EcoString::from("dep"), 1237 Requirement::Path { 1238 path: Utf8PathBuf::from("dep"), 1239 }, 1240 )]); 1241 1242 // Initial check testing 1243 1244 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1245 assert!(!unchanged, "fresh always needs resolution"); 1246 1247 let fingerprint_path = build_packages_dir.join("dep.config_fingerprint"); 1248 assert!(fingerprint_path.exists(), "fingerprint must exist"); 1249 1250 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1251 assert!(unchanged, "is unchanged"); 1252 1253 // Set fingerprint mtime to some time in the past. This causes the mtime check to fail, 1254 // so it moves on to checking the fingerprint itself. 1255 let past = std::time::SystemTime::now() - std::time::Duration::from_secs(10); 1256 let past = filetime::FileTime::from_system_time(past); 1257 filetime::set_file_mtime(&fingerprint_path, past).unwrap(); 1258 1259 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1260 assert!(unchanged, "mtime is outdated, but content has not changed"); 1261 1262 // Writing new content means the mtime and the fingerprint checks will fail. 1263 let config = "name = \"dep\" 1264version = \"1.0.0\" 1265 1266[dependencies] 1267blah = \">= 1.0.0\" 1268"; 1269 fs::write(&dep_config_path, config).unwrap(); 1270 1271 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1272 assert!(!unchanged, "content has changed"); 1273 1274 // Run again, to ensure that the fingerprint has been updated. 1275 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1276 assert!(unchanged, "no changes since last run"); 1277 1278 // Test that mtime is checked first, and that content is not checked when the mtime 1279 // is still valid. We do this by having having content that would fail the fingerprint 1280 // check, but having a fresh mtime so it never gets checked. 1281 // This can never happen in reality as you can't update the content without updating 1282 // the mtime, but we create the situation in this test to verify the short-circuiting 1283 // behaviour. 1284 let config = "name = \"dep\" 1285version = \"1.0.0\" 1286 1287[dependencies] 1288blah = \">= 1.0.0\" 1289wub = \">= 1.0.0\" 1290"; 1291 fs::write(&dep_config_path, config).unwrap(); 1292 let future = std::time::SystemTime::now() + std::time::Duration::from_secs(10); 1293 let future = filetime::FileTime::from_system_time(future); 1294 filetime::set_file_mtime(&fingerprint_path, future).unwrap(); 1295 1296 let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); 1297 assert!(unchanged, "fingerprint is outdated, but mtime is not"); 1298} 1299 1300fn create_testable_unlock_manifest( 1301 packages: Vec<(EcoString, Version, Vec<EcoString>)>, 1302 requirements: Vec<(EcoString, EcoString)>, 1303) -> Manifest { 1304 let manifest_packages = packages 1305 .into_iter() 1306 .map(|(name, version, requirements)| ManifestPackage { 1307 name, 1308 version, 1309 build_tools: vec!["gleam".into()], 1310 otp_app: None, 1311 requirements, 1312 source: ManifestPackageSource::Hex { 1313 outer_checksum: Base16Checksum(vec![]), 1314 }, 1315 }) 1316 .collect(); 1317 1318 let root_requirements = requirements 1319 .into_iter() 1320 .map(|(name, range)| { 1321 ( 1322 name, 1323 Requirement::Hex { 1324 version: hexpm::version::Range::new(range.into()).unwrap(), 1325 }, 1326 ) 1327 }) 1328 .collect(); 1329 1330 Manifest { 1331 packages: manifest_packages, 1332 requirements: root_requirements, 1333 } 1334} 1335 1336#[test] 1337fn test_unlock_package() { 1338 let mut locked = HashMap::from([ 1339 ("package_a".into(), Version::new(1, 0, 0)), 1340 ("package_b".into(), Version::new(2, 0, 0)), 1341 ("package_c".into(), Version::new(3, 0, 0)), 1342 ("package_d".into(), Version::new(4, 0, 0)), 1343 ]); 1344 1345 let packages = vec![ 1346 ( 1347 "package_a".into(), 1348 Version::new(1, 0, 0), 1349 vec!["package_b".into()], 1350 ), 1351 ( 1352 "package_b".into(), 1353 Version::new(2, 0, 0), 1354 vec!["package_c".into()], 1355 ), 1356 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1357 ("package_d".into(), Version::new(4, 0, 0), vec![]), 1358 ]; 1359 1360 let manifest = create_testable_unlock_manifest(packages, Vec::new()); 1361 1362 let packages_to_unlock = vec!["package_a".into()]; 1363 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1364 1365 assert!(!locked.contains_key("package_a")); 1366 assert!(!locked.contains_key("package_b")); 1367 assert!(!locked.contains_key("package_c")); 1368 assert!(locked.contains_key("package_d")); 1369} 1370 1371#[test] 1372fn test_unlock_package_without_manifest() { 1373 let mut locked = HashMap::from([ 1374 ("package_a".into(), Version::new(1, 0, 0)), 1375 ("package_b".into(), Version::new(2, 0, 0)), 1376 ("package_c".into(), Version::new(3, 0, 0)), 1377 ]); 1378 1379 let packages_to_unlock = vec!["package_a".into()]; 1380 unlock_packages(&mut locked, &packages_to_unlock, None).unwrap(); 1381 1382 assert!(!locked.contains_key("package_a")); 1383 assert!(locked.contains_key("package_b")); 1384 assert!(locked.contains_key("package_c")); 1385} 1386 1387#[test] 1388fn test_unlock_nonexistent_package() { 1389 let initial_locked = HashMap::from([ 1390 ("package_a".into(), Version::new(1, 0, 0)), 1391 ("package_b".into(), Version::new(2, 0, 0)), 1392 ]); 1393 1394 let packages = vec![ 1395 ( 1396 "package_a".into(), 1397 Version::new(1, 0, 0), 1398 vec!["package_b".into()], 1399 ), 1400 ("package_b".into(), Version::new(2, 0, 0), vec![]), 1401 ]; 1402 1403 let manifest = create_testable_unlock_manifest(packages, Vec::new()); 1404 1405 let packages_to_unlock = vec!["nonexistent_package".into()]; 1406 let mut locked = initial_locked.clone(); 1407 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1408 1409 assert_eq!( 1410 initial_locked, locked, 1411 "Locked packages should remain unchanged" 1412 ); 1413} 1414 1415#[test] 1416fn test_unlock_multiple_packages() { 1417 let mut locked = HashMap::from([ 1418 ("package_a".into(), Version::new(1, 0, 0)), 1419 ("package_b".into(), Version::new(2, 0, 0)), 1420 ("package_c".into(), Version::new(3, 0, 0)), 1421 ("package_d".into(), Version::new(4, 0, 0)), 1422 ("package_e".into(), Version::new(5, 0, 0)), 1423 ]); 1424 1425 let packages = vec![ 1426 ( 1427 "package_a".into(), 1428 Version::new(1, 0, 0), 1429 vec!["package_b".into()], 1430 ), 1431 ( 1432 "package_b".into(), 1433 Version::new(2, 0, 0), 1434 vec!["package_c".into()], 1435 ), 1436 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1437 ( 1438 "package_d".into(), 1439 Version::new(4, 0, 0), 1440 vec!["package_e".into()], 1441 ), 1442 ("package_e".into(), Version::new(5, 0, 0), vec![]), 1443 ]; 1444 1445 let manifest = create_testable_unlock_manifest(packages, Vec::new()); 1446 1447 let packages_to_unlock = vec!["package_a".into(), "package_d".into()]; 1448 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1449 1450 assert!(!locked.contains_key("package_a")); 1451 assert!(!locked.contains_key("package_b")); 1452 assert!(!locked.contains_key("package_c")); 1453 assert!(!locked.contains_key("package_d")); 1454 assert!(!locked.contains_key("package_e")); 1455} 1456 1457#[test] 1458fn test_unlock_packages_empty_input() { 1459 let initial_locked = HashMap::from([ 1460 ("package_a".into(), Version::new(1, 0, 0)), 1461 ("package_b".into(), Version::new(2, 0, 0)), 1462 ]); 1463 1464 let packages = vec![ 1465 ( 1466 "package_a".into(), 1467 Version::new(1, 0, 0), 1468 vec!["package_b".into()], 1469 ), 1470 ("package_b".into(), Version::new(2, 0, 0), vec![]), 1471 ]; 1472 1473 let manifest = create_testable_unlock_manifest(packages, Vec::new()); 1474 1475 let packages_to_unlock: Vec<EcoString> = vec![]; 1476 let mut locked = initial_locked.clone(); 1477 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1478 1479 assert_eq!( 1480 initial_locked, locked, 1481 "Locked packages should remain unchanged when no packages are specified to unlock" 1482 ); 1483} 1484 1485#[test] 1486fn test_unlock_package_preserve_shared_deps() { 1487 let mut locked = HashMap::from([ 1488 ("package_a".into(), Version::new(1, 0, 0)), 1489 ("package_b".into(), Version::new(2, 0, 0)), 1490 ("package_c".into(), Version::new(3, 0, 0)), 1491 ]); 1492 1493 let packages = vec![ 1494 ( 1495 "package_a".into(), 1496 Version::new(1, 0, 0), 1497 vec!["package_c".into()], 1498 ), 1499 ( 1500 "package_b".into(), 1501 Version::new(2, 0, 0), 1502 vec!["package_c".into()], 1503 ), 1504 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1505 ]; 1506 1507 let manifest = create_testable_unlock_manifest(packages, Vec::new()); 1508 1509 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()]; 1510 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1511 1512 assert!(!locked.contains_key("package_a")); 1513 assert!(locked.contains_key("package_b")); 1514 assert!(locked.contains_key("package_c")); 1515} 1516 1517#[test] 1518fn test_unlock_package_with_root_dep() { 1519 let mut locked = HashMap::from([ 1520 ("package_a".into(), Version::new(1, 0, 0)), 1521 ("package_b".into(), Version::new(2, 0, 0)), 1522 ("package_c".into(), Version::new(3, 0, 0)), 1523 ]); 1524 1525 let packages = vec![ 1526 ( 1527 "package_a".into(), 1528 Version::new(1, 0, 0), 1529 vec!["package_b".into()], 1530 ), 1531 ( 1532 "package_b".into(), 1533 Version::new(2, 0, 0), 1534 vec!["package_c".into()], 1535 ), 1536 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1537 ]; 1538 1539 let requirements = vec![("package_b".into(), ">= 2.0.0".into())]; 1540 1541 let manifest = create_testable_unlock_manifest(packages, requirements); 1542 1543 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()]; 1544 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1545 1546 assert!(!locked.contains_key("package_a")); 1547 assert!(locked.contains_key("package_b")); 1548 assert!(locked.contains_key("package_c")); 1549} 1550 1551#[test] 1552fn test_unlock_root_dep_package() { 1553 let mut locked = HashMap::from([ 1554 ("package_a".into(), Version::new(1, 0, 0)), 1555 ("package_b".into(), Version::new(2, 0, 0)), 1556 ("package_c".into(), Version::new(3, 0, 0)), 1557 ]); 1558 1559 let packages = vec![ 1560 ( 1561 "package_a".into(), 1562 Version::new(1, 0, 0), 1563 vec!["package_b".into()], 1564 ), 1565 ("package_b".into(), Version::new(2, 0, 0), vec![]), 1566 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1567 ]; 1568 1569 let requirements = vec![("package_a".into(), ">= 1.0.0".into())]; 1570 1571 let manifest = create_testable_unlock_manifest(packages, requirements); 1572 1573 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()]; 1574 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1575 1576 assert!(!locked.contains_key("package_a")); 1577 assert!(!locked.contains_key("package_b")); 1578 assert!(locked.contains_key("package_c")); 1579} 1580 1581#[test] 1582fn test_unlock_package_with_and_without_root_dep() { 1583 let mut locked = HashMap::from([ 1584 ("package_a".into(), Version::new(1, 0, 0)), 1585 ("package_b".into(), Version::new(2, 0, 0)), 1586 ("package_c".into(), Version::new(3, 0, 0)), 1587 ]); 1588 1589 let packages = vec![ 1590 ( 1591 "package_a".into(), 1592 Version::new(1, 0, 0), 1593 vec!["package_b".into(), "package_c".into()], 1594 ), 1595 ("package_b".into(), Version::new(2, 0, 0), vec![]), 1596 ("package_c".into(), Version::new(3, 0, 0), vec![]), 1597 ]; 1598 1599 let requirements = vec![("package_b".into(), ">= 2.0.0".into())]; 1600 1601 let manifest = create_testable_unlock_manifest(packages, requirements); 1602 1603 let packages_to_unlock: Vec<EcoString> = vec!["package_a".into()]; 1604 unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); 1605 1606 assert!(!locked.contains_key("package_a")); 1607 assert!(locked.contains_key("package_b")); 1608 assert!(!locked.contains_key("package_c")); 1609} 1610 1611fn manifest_package(name: &str, version: &str, requirements: Vec<EcoString>) -> ManifestPackage { 1612 ManifestPackage { 1613 name: name.into(), 1614 version: Version::parse(version).unwrap(), 1615 build_tools: ["gleam".into()].into(), 1616 otp_app: None, 1617 requirements, 1618 source: ManifestPackageSource::Hex { 1619 outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), 1620 }, 1621 } 1622} 1623 1624fn package_config( 1625 dependencies: HashMap<EcoString, Requirement>, 1626 dev_dependencies: HashMap<EcoString, Requirement>, 1627) -> PackageConfig { 1628 PackageConfig { 1629 name: "the_package".into(), 1630 version: Version::parse("1.0.0").unwrap(), 1631 gleam_version: None, 1632 licences: vec![], 1633 description: "".into(), 1634 documentation: Docs { pages: vec![] }, 1635 dependencies, 1636 dev_dependencies, 1637 repository: None, 1638 links: vec![], 1639 erlang: ErlangConfig { 1640 application_start_module: None, 1641 application_start_argument: None, 1642 extra_applications: vec![], 1643 }, 1644 javascript: JavaScriptConfig { 1645 typescript_declarations: false, 1646 source_maps: false, 1647 runtime: Runtime::NodeJs, 1648 deno: DenoConfig { 1649 allow_env: DenoFlag::AllowAll, 1650 allow_sys: true, 1651 allow_hrtime: true, 1652 allow_net: DenoFlag::AllowAll, 1653 allow_ffi: true, 1654 allow_read: DenoFlag::AllowAll, 1655 allow_run: DenoFlag::AllowAll, 1656 allow_write: DenoFlag::AllowAll, 1657 allow_all: true, 1658 unstable: true, 1659 location: None, 1660 }, 1661 }, 1662 target: Target::Erlang, 1663 internal_modules: None, 1664 } 1665} 1666 1667#[test] 1668fn test_remove_do_nothing() { 1669 let config = package_config( 1670 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), 1671 HashMap::from([("b".into(), Requirement::hex("~>2.0").unwrap())]), 1672 ); 1673 1674 let mut manifest = Manifest { 1675 requirements: HashMap::from([ 1676 ("a".into(), Requirement::hex("~>1.0").unwrap()), 1677 ("b".into(), Requirement::hex("~>2.0").unwrap()), 1678 ]), 1679 packages: vec![ 1680 manifest_package("a", "1.0.0", vec![]), 1681 manifest_package("b", "2.0.8", vec![]), 1682 ], 1683 }; 1684 1685 let manifest_copy = manifest.clone(); 1686 1687 remove_extra_requirements(&config, &mut manifest).unwrap(); 1688 1689 assert_eq!(manifest.requirements, manifest_copy.requirements); 1690 assert_eq!(manifest.packages, manifest_copy.packages); 1691} 1692 1693#[test] 1694fn test_remove_simple() { 1695 let config = package_config(HashMap::new(), HashMap::new()); 1696 1697 let mut manifest = Manifest { 1698 requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), 1699 packages: vec![manifest_package("a", "1.0.0", vec![])], 1700 }; 1701 1702 remove_extra_requirements(&config, &mut manifest).unwrap(); 1703 1704 assert_eq!(manifest.requirements, config.dependencies); 1705 assert_eq!(manifest.packages, vec![]); 1706} 1707 1708#[test] 1709fn test_remove_package_with_transitive_dependencies() { 1710 let config = package_config(HashMap::new(), HashMap::new()); 1711 1712 let mut manifest = Manifest { 1713 requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), 1714 packages: vec![ 1715 manifest_package("a", "1.0.0", vec!["b".into()]), 1716 manifest_package("b", "1.2.3", vec!["c".into()]), 1717 manifest_package("c", "2.0.0", vec![]), 1718 ], 1719 }; 1720 1721 remove_extra_requirements(&config, &mut manifest).unwrap(); 1722 1723 assert_eq!(manifest.requirements, config.dependencies); 1724 assert_eq!(manifest.packages, vec![]); 1725} 1726 1727#[test] 1728fn test_remove_package_with_shared_transitive_dependencies() { 1729 let config = package_config( 1730 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), 1731 HashMap::new(), 1732 ); 1733 1734 let mut manifest = Manifest { 1735 requirements: HashMap::from([ 1736 ("a".into(), Requirement::hex("~>1.0").unwrap()), 1737 ("b".into(), Requirement::hex("~>1.0").unwrap()), 1738 ]), 1739 packages: vec![ 1740 manifest_package("a", "1.0.0", vec!["c".into()]), 1741 manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]), 1742 manifest_package("c", "2.0.0", vec![]), 1743 manifest_package("d", "0.1.0", vec![]), 1744 ], 1745 }; 1746 1747 remove_extra_requirements(&config, &mut manifest).unwrap(); 1748 1749 assert_eq!(manifest.requirements, config.dependencies); 1750 assert_eq!( 1751 manifest.packages, 1752 vec![ 1753 manifest_package("a", "1.0.0", vec!["c".into()]), 1754 manifest_package("c", "2.0.0", vec![]), 1755 ] 1756 ); 1757} 1758 1759#[test] 1760fn test_remove_package_that_is_also_a_transitive_dependency() { 1761 let config = package_config( 1762 HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), 1763 HashMap::new(), 1764 ); 1765 1766 let mut manifest = Manifest { 1767 requirements: HashMap::from([ 1768 ("a".into(), Requirement::hex("~>1.0").unwrap()), 1769 ("b".into(), Requirement::hex("~>1.0").unwrap()), 1770 ]), 1771 packages: vec![ 1772 manifest_package("a", "1.0.0", vec!["b".into(), "c".into()]), 1773 manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]), 1774 manifest_package("c", "2.0.0", vec![]), 1775 manifest_package("d", "0.1.0", vec![]), 1776 ], 1777 }; 1778 1779 let manifest_copy = manifest.clone(); 1780 1781 remove_extra_requirements(&config, &mut manifest).unwrap(); 1782 1783 assert_eq!(manifest.requirements, config.dependencies); 1784 assert_eq!(manifest.packages, manifest_copy.packages); 1785} 1786 1787#[test] 1788fn test_pretty_print_major_versions_available() { 1789 let versions = vec![ 1790 ( 1791 "very_long_package_name".to_string(), 1792 (Version::new(18, 382, 43), Version::new(19, 0, 38)), 1793 ), 1794 ( 1795 "gleam_stdlib".to_string(), 1796 (Version::new(0, 45, 0), Version::new(1, 0, 0)), 1797 ), 1798 ( 1799 "short_name".to_string(), 1800 (Version::new(1, 0, 0), Version::new(2, 0, 0)), 1801 ), 1802 ] 1803 .into_iter() 1804 .collect(); 1805 1806 let output = pretty_print_major_versions_available(versions); 1807 1808 insta::assert_snapshot!(output); 1809} 1810 1811#[test] 1812fn test_pretty_print_version_updates() { 1813 let versions = vec![ 1814 ( 1815 "gleam_stdlib".to_string(), 1816 (Version::new(0, 45, 0), Version::new(0, 46, 0)), 1817 ), 1818 ( 1819 "wisp".to_string(), 1820 (Version::new(2, 1, 0), Version::new(2, 1, 1)), 1821 ), 1822 ( 1823 "very_long_package_name".to_string(), 1824 (Version::new(12, 12, 12), Version::new(120, 12, 12)), 1825 ), 1826 ] 1827 .into_iter() 1828 .collect(); 1829 1830 let output = pretty_print_version_updates(versions); 1831 1832 insta::assert_snapshot!(output); 1833} 1834 1835#[test] 1836fn test_pretty_print_outdated_versions() { 1837 let versions = vec![ 1838 ( 1839 "gleam_stdlib".to_string(), 1840 (Version::new(0, 45, 0), Version::new(0, 46, 0)), 1841 ), 1842 ( 1843 "wisp".to_string(), 1844 (Version::new(2, 1, 0), Version::new(2, 1, 1)), 1845 ), 1846 ( 1847 "very_long_package_name".to_string(), 1848 (Version::new(12, 12, 12), Version::new(120, 12, 12)), 1849 ), 1850 ] 1851 .into_iter() 1852 .collect(); 1853 1854 let output = pretty_print_outdated_versions(12, versions); 1855 1856 insta::assert_snapshot!(output); 1857} 1858 1859#[test] 1860fn test_pretty_print_outdated_versions_no_updates() { 1861 let versions: HashMap<String, (Version, Version)> = HashMap::new(); 1862 1863 let output = pretty_print_outdated_versions(12, versions); 1864 1865 insta::assert_snapshot!(output); 1866} 1867 1868#[test] 1869fn test_ensure_packages_exist_locally_all_present() { 1870 let manifest = Manifest { 1871 requirements: HashMap::new(), 1872 packages: vec![ 1873 manifest_package("package_a", "1.0.0", vec![]), 1874 manifest_package("package_b", "2.0.0", vec![]), 1875 manifest_package("package_c", "3.0.0", vec![]), 1876 ], 1877 }; 1878 1879 let packages_to_check = vec!["package_a".into(), "package_b".into()]; 1880 let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check); 1881 1882 assert!(result.is_ok(), "All packages exist, should return Ok"); 1883} 1884 1885#[test] 1886fn test_ensure_packages_exist_locally_some_missing() { 1887 let manifest = Manifest { 1888 requirements: HashMap::new(), 1889 packages: vec![ 1890 manifest_package("package_a", "1.0.0", vec![]), 1891 manifest_package("package_b", "2.0.0", vec![]), 1892 ], 1893 }; 1894 1895 let packages_to_check = vec![ 1896 "package_a".into(), 1897 "package_b".into(), 1898 "missing_package".into(), 1899 "another_missing".into(), 1900 ]; 1901 let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check); 1902 1903 match result { 1904 Err(Error::PackagesToUpdateNotExist { packages }) => { 1905 assert_eq!(packages.len(), 2); 1906 assert!(packages.contains(&"missing_package".into())); 1907 assert!(packages.contains(&"another_missing".into())); 1908 } 1909 _ => panic!("Expected PackagesToUpdateNotExist error"), 1910 } 1911}