diff --git a/CHANGELOG.md b/CHANGELOG.md index 1531e740..9c9163c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Wally Changelog ## Unreleased Changes +* Added --locked flag for the install subcommand ([#119]) + +[#119]: https://github.com/UpliftGames/wally/pull/119 ## 0.3.2 (2023-06-05) * Added private field to package manifest ([#9])([#76]) diff --git a/README.md b/README.md index b2af9628..2406a464 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ Parity with: Installs all packages. `--locked` matches `cargo XXX --locked`, which will error if there is not an up-to-date lockfile. Intended for use on CI machines. -(locked is a planned feature and not yet implemented) Parity with: * `npm install` with no arguments diff --git a/src/commands/init.rs b/src/commands/init.rs index 411b1e18..0b5ef6b7 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -35,7 +35,8 @@ impl InitSubcommand { match fs_err::metadata(&manifest_path) { Ok(_) => bail!( - "There is already a Wally project in this directory. Manifest file ({}) already exists.", + "There is already a Wally project in this directory. Manifest file ({}) already \ + exists.", MANIFEST_FILE_NAME ), @@ -43,7 +44,11 @@ impl InitSubcommand { if err.kind() == std::io::ErrorKind::NotFound { // Perfect! This is the state that we want } else { - bail!("Error accessing manifest file ({}): {}", MANIFEST_FILE_NAME, err); + bail!( + "Error accessing manifest file ({}): {}", + MANIFEST_FILE_NAME, + err + ); } } } diff --git a/src/commands/install.rs b/src/commands/install.rs index a6fb5bef..3ef98de6 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -1,18 +1,22 @@ use std::collections::BTreeSet; + +use std::io::Write; use std::path::PathBuf; use std::time::Duration; -use crossterm::style::{Color, SetForegroundColor}; +use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor}; use indicatif::{ProgressBar, ProgressStyle}; + use structopt::StructOpt; use crate::installation::InstallationContext; -use crate::lockfile::{LockPackage, Lockfile}; +use crate::lockfile::Lockfile; use crate::manifest::Manifest; use crate::package_id::PackageId; use crate::package_source::{PackageSource, PackageSourceMap, Registry, TestRegistry}; use crate::resolution::resolve; +use super::utils::{generate_dependency_changes, render_update_difference}; use super::GlobalOptions; /// Install all of the dependencies of this project. @@ -21,6 +25,10 @@ pub struct InstallSubcommand { /// Path to the project to install dependencies for. #[structopt(long = "project-path", default_value = ".")] pub project_path: PathBuf, + + /// Flag to error if the lockfile does not match with the latest dependencies. + #[structopt(long = "locked")] + pub locked: bool, } impl InstallSubcommand { @@ -43,29 +51,67 @@ impl InstallSubcommand { let mut package_sources = PackageSourceMap::new(default_registry); package_sources.add_fallbacks()?; - let mut try_to_use = BTreeSet::new(); - for package in lockfile.packages { - match package { - LockPackage::Registry(registry_package) => { - try_to_use.insert(PackageId::new( - registry_package.name, - registry_package.version, - )); - } - LockPackage::Git(_) => {} + let try_to_use = lockfile.as_ids().collect(); + + let progress = ProgressBar::new(0).with_style( + ProgressStyle::with_template("{spinner:.cyan}{wide_msg}")?.tick_chars("⠁⠈⠐⠠⠄⠂ "), + ); + + progress.enable_steady_tick(Duration::from_millis(100)); + + if self.locked { + progress.println(format!( + "{} Verifying {}lockfile is up-to-date...", + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset) + )); + + let latest_graph = resolve(&manifest, &BTreeSet::new(), &package_sources)?; + + if try_to_use != latest_graph.activated { + progress.finish_and_clear(); + + let old_dependencies = &try_to_use; + + let changes = + generate_dependency_changes(old_dependencies, &latest_graph.activated); + let mut error_output = Vec::new(); + + writeln!( + error_output, + "{} The Lockfile is out of date and wasn't changed due to --locked{}", + SetForegroundColor(Color::Yellow), + SetForegroundColor(Color::Reset) + )?; + + render_update_difference(&changes, &mut error_output)?; + + writeln!( + error_output, + "{}{} Suggestion{}{} try running wally update", + SetAttribute(Attribute::Bold), + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset), + SetAttribute(Attribute::Reset) + )?; + + anyhow::bail!(String::from_utf8(error_output) + .expect("output from render_update_difference should always be utf-8")); } - } - let progress = ProgressBar::new(0) - .with_style( - ProgressStyle::with_template("{spinner:.cyan}{wide_msg}")?.tick_chars("⠁⠈⠐⠠⠄⠂ "), - ) - .with_message(format!( - "{} Resolving {}packages...", + progress.println(format!( + "{} Verified {}lockfile is up-to-date...{}", SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Green), SetForegroundColor(Color::Reset) )); - progress.enable_steady_tick(Duration::from_millis(100)); + } + + progress.println(format!( + "{} Resolving {}packages...", + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset) + )); let resolved = resolve(&manifest, &try_to_use, &package_sources)?; @@ -76,8 +122,8 @@ impl InstallSubcommand { resolved.activated.len() - 1 )); - let lockfile = Lockfile::from_resolve(&resolved); - lockfile.save(&self.project_path)?; + let new_lockfile = Lockfile::from_resolve(&resolved); + new_lockfile.save(&self.project_path)?; progress.println(format!( "{} Generated {}lockfile", diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d16ae071..1af65781 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -7,6 +7,7 @@ mod package; mod publish; mod search; mod update; +mod utils; pub use init::InitSubcommand; pub use install::InstallSubcommand; diff --git a/src/commands/snapshots/libwally__commands__utils__test__snapshot_output_when_no_changes.snap b/src/commands/snapshots/libwally__commands__utils__test__snapshot_output_when_no_changes.snap new file mode 100644 index 00000000..7701bb07 --- /dev/null +++ b/src/commands/snapshots/libwally__commands__utils__test__snapshot_output_when_no_changes.snap @@ -0,0 +1,6 @@ +--- +source: src/commands/utils.rs +expression: "String::from_utf8(writer).unwrap()" +--- + No Dependency changes + diff --git a/src/commands/update.rs b/src/commands/update.rs index e23e0078..36c4cd62 100644 --- a/src/commands/update.rs +++ b/src/commands/update.rs @@ -1,5 +1,5 @@ use std::collections::BTreeSet; -use std::convert::TryInto; + use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; @@ -16,6 +16,8 @@ use crossterm::style::{Attribute, Color, SetAttribute, SetForegroundColor}; use indicatif::{ProgressBar, ProgressStyle}; use structopt::StructOpt; +use super::utils::{generate_dependency_changes, render_update_difference}; + /// Update all of the dependencies of this project. #[derive(Debug, StructOpt)] pub struct UpdateSubcommand { @@ -98,9 +100,11 @@ impl UpdateSubcommand { progress.enable_steady_tick(Duration::from_millis(100)); progress.suspend(|| { - let dependency_changes = - generate_depedency_changes(&lockfile.as_ids().collect(), &resolved_graph.activated); - render_update_difference(&dependency_changes); + let dependency_changes = generate_dependency_changes( + &lockfile.as_ids().collect(), + &resolved_graph.activated, + ); + render_update_difference(&dependency_changes, &mut std::io::stdout()).unwrap(); }); Lockfile::from_resolve(&resolved_graph).save(&self.project_path)?; @@ -178,131 +182,3 @@ impl FromStr for PackageSpec { } } } - -enum DependencyChange { - Added(PackageId), - Removed(PackageId), - Updated { from: PackageId, to: PackageId }, - Downgrade { from: PackageId, to: PackageId }, -} - -fn generate_depedency_changes( - old_dependencies: &BTreeSet, - new_dependencies: &BTreeSet, -) -> Vec { - let added_packages = new_dependencies.difference(old_dependencies); - let removed_packages = old_dependencies.difference(new_dependencies); - let changed_dependencies: BTreeSet<&PackageName> = added_packages - .clone() - .chain(removed_packages.clone()) - .map(|package| package.name()) - .collect(); - - let mut dependency = Vec::new(); - - for dependency_name in changed_dependencies { - let matching_packages_removed = removed_packages - .clone() - .filter(|x| *x.name() == *dependency_name); - let matching_packages_added = added_packages - .clone() - .filter(|x| *x.name() == *dependency_name); - - match ( - matching_packages_added.clone().count(), - matching_packages_removed.clone().count(), - ) { - (1, 1) => { - // We know for certain that there is only one item in the iterator. - let package_added = matching_packages_added.last().unwrap(); - let package_removed = matching_packages_removed.last().unwrap(); - - if package_added.gt(package_removed) { - dependency.push(DependencyChange::Updated { - from: package_removed.clone(), - to: package_added.clone(), - }); - } else { - dependency.push(DependencyChange::Downgrade { - from: package_added.clone(), - to: package_removed.clone(), - }); - } - } - (0, 1) => { - // We know for certain that there is only one item in the iterator. - let package_removed = matching_packages_removed.last().unwrap(); - dependency.push(DependencyChange::Removed(package_removed.clone())); - } - (1, 0) => { - // We know for certain that there is only one item in the iterator. - let package_added = matching_packages_added.last().unwrap(); - dependency.push(DependencyChange::Added(package_added.clone())); - } - (0, 0) => panic!("Impossible for the package name {} to not be removed or added if found in earlier.", dependency_name), - (_, _) => { - dependency.extend(matching_packages_added.map(|package| DependencyChange::Added(package.clone()))); - dependency.extend(matching_packages_removed.map(|package| DependencyChange::Removed(package.clone()))); - } - } - } - - dependency -} - -fn render_update_difference(dependency_changes: &[DependencyChange]) { - if dependency_changes.is_empty() { - return println!( - "{} No Dependency changes{}", - SetForegroundColor(Color::DarkGreen), - SetForegroundColor(Color::Reset) - ); - } - - println!( - "{} Dependency changes{}", - SetForegroundColor(Color::DarkGreen), - SetForegroundColor(Color::Reset) - ); - - for dependency_change in dependency_changes { - match dependency_change { - DependencyChange::Added(package_id) => { - println!( - "{} Added {}{} v{}", - SetForegroundColor(Color::DarkGreen), - SetForegroundColor(Color::Reset), - package_id.name(), - package_id.version() - ); - } - DependencyChange::Removed(package_id) => { - println!( - "{} Removed {}{} v{}", - SetForegroundColor(Color::DarkRed), - SetForegroundColor(Color::Reset), - package_id.name(), - package_id.version() - ); - } - DependencyChange::Updated { from, to } => { - println!( - "{} Updated {}{} from v{} to v{}", - SetForegroundColor(Color::DarkCyan), - SetForegroundColor(Color::Reset), - from.name(), - from.version(), - to.version() - ); - } - DependencyChange::Downgrade { from, to } => println!( - "{} Downgraded {}{} from v{} to v{}", - SetForegroundColor(Color::DarkYellow), - SetForegroundColor(Color::Reset), - from.name(), - from.version(), - to.version() - ), - } - } -} diff --git a/src/commands/utils.rs b/src/commands/utils.rs new file mode 100644 index 00000000..f20a3718 --- /dev/null +++ b/src/commands/utils.rs @@ -0,0 +1,287 @@ +use crate::{package_id::PackageId, package_name::PackageName}; +use crossterm::style::{Color, SetForegroundColor}; +use serde::Serialize; +use std::{collections::BTreeSet, io::Write}; + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub(crate) enum DependencyChange { + Added(PackageId), + Removed(PackageId), + Updated { from: PackageId, to: PackageId }, + Downgraded { from: PackageId, to: PackageId }, +} + +pub(crate) fn generate_dependency_changes( + old_dependencies: &BTreeSet, + new_dependencies: &BTreeSet, +) -> Vec { + let changed_dependencies: BTreeSet<&PackageName> = old_dependencies + .symmetric_difference(new_dependencies) + .map(|changed_package| changed_package.name()) + .collect(); + + let mut dependency = Vec::new(); + + for changed_dependency_name in changed_dependencies { + let match_package_ids_by_name = + |maybe_matching: &&PackageId| maybe_matching.name() == changed_dependency_name; + + let mut old_matches = old_dependencies.iter().filter(match_package_ids_by_name); + let total_old_matches = old_matches.clone().count(); + + let mut new_matches = new_dependencies.iter().filter(match_package_ids_by_name); + let total_new_matches = new_matches.clone().count(); + + // If there's more than one new or old matches, then we do the simple route of listing the exact versions removed/added. + if total_new_matches > 1 || total_old_matches > 1 { + dependency + .extend(old_matches.map(|package| DependencyChange::Removed(package.clone()))); + dependency.extend(new_matches.map(|package| DependencyChange::Added(package.clone()))); + } else { + // Otherwise, we can try being more specific about what changed. + dependency.push( + match (old_matches.next().cloned(), new_matches.next().cloned()) { + (Some(old), Some(new)) if old.le(&new) => { + DependencyChange::Updated { from: old, to: new } + } + (Some(old), Some(new)) => DependencyChange::Downgraded { from: old, to: new }, + + // Or, there's been a singular removal/addition. + (Some(old), None) => DependencyChange::Removed(old), + (None, Some(new)) => DependencyChange::Added(new), + (None, None) => panic!( + "Impossible for the package name {} to not be removed or added if found \ + in earlier.", + changed_dependency_name + ), + }, + ) + } + } + + dependency +} + +pub(crate) fn render_update_difference( + dependency_changes: &[DependencyChange], + writer: &mut impl Write, +) -> anyhow::Result<()> { + if dependency_changes.is_empty() { + writeln!( + writer, + "{} No Dependency changes{}", + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset) + )?; + + return Ok(()); + } + + writeln!( + writer, + "{} Dependency changes{}", + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset) + )?; + + for dependency_change in dependency_changes { + match dependency_change { + DependencyChange::Added(package_id) => writeln!( + writer, + "{} Added {}{} v{}", + SetForegroundColor(Color::DarkGreen), + SetForegroundColor(Color::Reset), + package_id.name(), + package_id.version() + ), + DependencyChange::Removed(package_id) => writeln!( + writer, + "{} Removed {}{} v{}", + SetForegroundColor(Color::DarkRed), + SetForegroundColor(Color::Reset), + package_id.name(), + package_id.version() + ), + DependencyChange::Updated { from, to } => writeln!( + writer, + "{} Updated {}{} from v{} to v{}", + SetForegroundColor(Color::DarkCyan), + SetForegroundColor(Color::Reset), + from.name(), + from.version(), + to.version() + ), + DependencyChange::Downgraded { from, to } => writeln!( + writer, + "{} Downgraded {}{} from v{} to v{}", + SetForegroundColor(Color::DarkYellow), + SetForegroundColor(Color::Reset), + from.name(), + from.version(), + to.version() + ), + }? + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use std::{collections::BTreeSet, str::FromStr}; + + use super::{generate_dependency_changes, render_update_difference}; + use insta::assert_snapshot; + + macro_rules! package_id { + ($package_id:literal) => { + crate::package_id::PackageId::from_str($package_id).unwrap() + }; + } + + macro_rules! added_change { + ($changes:expr, $package_id:literal) => { + $changes + .iter() + .any(|change| *change == super::DependencyChange::Added(package_id!($package_id))) + }; + } + + macro_rules! removed_change { + ($changes:expr, $package_id:literal) => { + $changes + .iter() + .any(|change| *change == super::DependencyChange::Removed(package_id!($package_id))) + }; + } + + macro_rules! upgraded_change { + ($changes:expr, $from_package_id:literal, $to_package_id:literal) => { + $changes.iter().any(|change| { + *change + == super::DependencyChange::Updated { + from: package_id!($from_package_id), + to: package_id!($to_package_id), + } + }) + }; + } + + macro_rules! downgraded_change { + ($changes:expr, $from_package_id:literal, $to_package_id:literal) => { + $changes.iter().any(|change| { + *change + == super::DependencyChange::Downgraded { + from: package_id!($from_package_id), + to: package_id!($to_package_id), + } + }) + }; + } + + #[test] + fn generate_no_changes_if_same() { + let dependencies = BTreeSet::from([ + package_id!("biff/package-a@1.1.1"), + package_id!("biff/package-b@1.2.1"), + ]); + + let changes = generate_dependency_changes(&dependencies, &dependencies); + + assert!(changes.is_empty(), "Expected no changes.") + } + + #[test] + fn generate_correct_changes() { + let old_dependencies = BTreeSet::from([ + package_id!("biff/unchanged-package@1.0.0"), + package_id!("biff/removed-package@1.2.1"), + package_id!("biff/upgraded-package@1.2.1"), + package_id!("biff/downgraded-package@1.2.1"), + ]); + + let new_dependencies = BTreeSet::from([ + package_id!("biff/unchanged-package@1.0.0"), + package_id!("biff/added-package@3.1.1"), + package_id!("biff/upgraded-package@1.2.4"), + package_id!("biff/downgraded-package@0.0.1"), + ]); + + let changes = generate_dependency_changes(&old_dependencies, &new_dependencies); + + assert!(!changes.is_empty(), "Expected changes."); + assert!(changes.len() == 4, "Expected four changes."); + assert!( + added_change!(changes, "biff/added-package@3.1.1"), + "Expected biff/added-package to be added." + ); + assert!( + removed_change!(changes, "biff/removed-package@1.2.1"), + "Expected biff/remove-package to be removed." + ); + assert!( + upgraded_change!( + changes, + "biff/upgraded-package@1.2.1", + "biff/upgraded-package@1.2.4" + ), + "Expected biff/upgraded-package to be upgraded." + ); + assert!( + downgraded_change!( + changes, + "biff/downgraded-package@1.2.1", + "biff/downgraded-package@0.0.1" + ), + "Expected biff/downgraded-package to be downgraded." + ); + } + + #[test] + fn decompose_upgrades_and_downgrades_if_multiple() { + let old_dependencies = BTreeSet::from([ + package_id!("biff/package-a@1.0.0"), + package_id!("biff/package-b@1.0.0"), + ]); + + let new_dependencies = BTreeSet::from([ + package_id!("biff/package-a@2.0.0"), + package_id!("biff/package-a@3.0.0"), + package_id!("biff/package-b@0.5.0"), + package_id!("biff/package-b@0.5.1"), + ]); + + let changes = generate_dependency_changes(&old_dependencies, &new_dependencies); + + assert!(!changes.is_empty(), "Expected changes."); + assert!(changes.len() == 6, "Expected only six changes."); + assert!( + removed_change!(changes, "biff/package-a@1.0.0"), + "Expected package-a@1.0.0 to be removed." + ); + assert!( + removed_change!(changes, "biff/package-b@1.0.0"), + "Expected package-b@1.0.0 to be removed." + ); + assert!( + added_change!(changes, "biff/package-a@2.0.0") + && added_change!(changes, "biff/package-a@3.0.0"), + "Upgrades decomposed." + ); + assert!( + added_change!(changes, "biff/package-b@0.5.0") + && added_change!(changes, "biff/package-b@0.5.1"), + "Upgrades decomposed." + ) + } + + #[test] + fn snapshot_output_when_no_changes() { + let changes = Vec::new(); + + let mut writer = Vec::new(); + render_update_difference(&changes, &mut writer).unwrap(); + + assert_snapshot!(String::from_utf8(writer).unwrap()); + } +} diff --git a/src/package_contents.rs b/src/package_contents.rs index b9bd8d45..da2250d0 100644 --- a/src/package_contents.rs +++ b/src/package_contents.rs @@ -64,8 +64,8 @@ impl PackageContents { if project_name != package_name { log::info!( "The project and package names are mismatched. The project name in \ - `default.project.json` has been renamed to '{}' in the uploaded package \ - to match the name provided by `wally.toml`", + `default.project.json` has been renamed to '{}' in the uploaded \ + package to match the name provided by `wally.toml`", package_name ); diff --git a/src/package_name.rs b/src/package_name.rs index ecc4cf4e..33d674dd 100644 --- a/src/package_name.rs +++ b/src/package_name.rs @@ -53,7 +53,8 @@ fn validate_scope(scope: &str) -> anyhow::Result<()> { ensure!( only_valid_chars, - "package scope '{}' is invalid (scopes can only contain lowercase characters, digits and '-')", + "package scope '{}' is invalid (scopes can only contain lowercase characters, digits and \ + '-')", scope ); ensure!(scope.len() > 0, "package scopes cannot be empty"); @@ -72,7 +73,8 @@ fn validate_name(name: &str) -> anyhow::Result<()> { ensure!( only_valid_chars, - "package name '{}' is invalid (names can only contain lowercase characters, digits and '-')", + "package name '{}' is invalid (names can only contain lowercase characters, digits and \ + '-')", name ); ensure!(name.len() > 0, "package names cannot be empty"); diff --git a/src/resolution.rs b/src/resolution.rs index 8d9871fa..988b00a8 100644 --- a/src/resolution.rs +++ b/src/resolution.rs @@ -273,8 +273,8 @@ pub fn resolve( if conflicting.is_empty() { bail!( - "No packages were found that matched ({req_realm:?}) {req}.\ - \nAre you sure this is a {req_realm:?} dependency?", + "No packages were found that matched ({req_realm:?}) {req}.\nAre you sure this is \ + a {req_realm:?} dependency?", req_realm = dependency_request.request_realm, req = dependency_request.package_req, ); @@ -285,9 +285,9 @@ pub fn resolve( .collect(); bail!( - "All possible candidates for package {req} ({req_realm:?}) \ - conflicted with other packages that were already installed. \ - These packages were previously selected: {conflicting}", + "All possible candidates for package {req} ({req_realm:?}) conflicted with other \ + packages that were already installed. These packages were previously selected: \ + {conflicting}", req = dependency_request.package_req, req_realm = dependency_request.request_realm, conflicting = conflicting_debug.join(", "), diff --git a/test-projects/diamond-graph/root/latest/default.project.json b/test-projects/diamond-graph/root/latest/default.project.json new file mode 100644 index 00000000..a66ff578 --- /dev/null +++ b/test-projects/diamond-graph/root/latest/default.project.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "tree": { + "Packages": { + "$path": "Packages" + }, + "$path": "src" + } +} \ No newline at end of file diff --git a/test-projects/diamond-graph/root/latest/src/init.lua b/test-projects/diamond-graph/root/latest/src/init.lua new file mode 100644 index 00000000..fc684338 --- /dev/null +++ b/test-projects/diamond-graph/root/latest/src/init.lua @@ -0,0 +1,4 @@ +local A = require(script.Parent.A) +local B = require(script.Parent.B) + +return `{A} {B}` \ No newline at end of file diff --git a/test-projects/diamond-graph/root/latest/wally.lock b/test-projects/diamond-graph/root/latest/wally.lock new file mode 100644 index 00000000..630b4047 --- /dev/null +++ b/test-projects/diamond-graph/root/latest/wally.lock @@ -0,0 +1,28 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "diamond-graph/direct-dependency-a" +version = "0.1.1" +dependencies = [["Indirect", "diamond-graph/indirect-dependency-a@0.1.1"]] + +[[package]] +name = "diamond-graph/direct-dependency-b" +version = "0.1.0" +dependencies = [["Indirect", "diamond-graph/indirect-dependency-a@0.2.1"]] + +[[package]] +name = "diamond-graph/indirect-dependency-a" +version = "0.1.1" +dependencies = [] + +[[package]] +name = "diamond-graph/indirect-dependency-a" +version = "0.2.1" +dependencies = [] + +[[package]] +name = "diamond-graph/root" +version = "0.1.0" +dependencies = [["A", "diamond-graph/direct-dependency-a@0.1.1"], ["B", "diamond-graph/direct-dependency-b@0.1.0"]] diff --git a/test-projects/diamond-graph/root/latest/wally.toml b/test-projects/diamond-graph/root/latest/wally.toml new file mode 100644 index 00000000..9b77c47d --- /dev/null +++ b/test-projects/diamond-graph/root/latest/wally.toml @@ -0,0 +1,10 @@ +[package] +name = "diamond-graph/root" +version = "0.1.0" +license = "MIT" +realm = "server" +registry = "test-registries/primary-registry" + +[server-dependencies] +A = "diamond-graph/direct-dependency-a@0.1.0" +B = "diamond-graph/direct-dependency-b@0.1.0" \ No newline at end of file diff --git a/tests/integration/install.rs b/tests/integration/install.rs index 35f37b01..fa102fdf 100644 --- a/tests/integration/install.rs +++ b/tests/integration/install.rs @@ -4,45 +4,77 @@ use std::path::Path; #[test] fn minimal() { - run_test("minimal"); + run_install_test("minimal"); } #[test] fn one_dependency() { - run_test("one-dependency"); + run_install_test("one-dependency"); } #[test] fn transitive_dependency() { - run_test("transitive-dependency"); + run_install_test("transitive-dependency"); } #[test] fn private_with_public_dependency() { - run_test("private-with-public-dependency"); + run_install_test("private-with-public-dependency"); } #[test] fn dev_dependency() { - run_test("dev-dependency"); + run_install_test("dev-dependency"); } #[test] fn dev_dependency_also_required_as_non_dev() { - run_test("dev-dependency-also-required-as-non-dev"); + run_install_test("dev-dependency-also-required-as-non-dev"); } #[test] fn cross_realm_dependency() { - run_test("cross-realm-dependency"); + run_install_test("cross-realm-dependency"); } #[test] fn cross_realm_explicit_dependency() { - run_test("cross-realm-explicit-dependency"); + run_install_test("cross-realm-explicit-dependency"); } -fn run_test(name: &str) -> TempProject { +#[test] +fn locked_pass() { + let result = run_locked_install("diamond-graph/root/latest"); + + assert!(result.is_ok(), "Should pass without any problems"); +} + +#[test] +fn locked_catches_dated_packages() { + let result = run_locked_install("diamond-graph/root/dated"); + assert!(result.is_err(), "Should fail!"); +} + +fn run_locked_install(name: &str) -> Result<(), anyhow::Error> { + let source_project = + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/test-projects",)).join(name); + + let project = TempProject::new(&source_project).unwrap(); + + Args { + global: GlobalOptions { + test_registry: true, + ..Default::default() + }, + subcommand: Subcommand::Install(InstallSubcommand { + project_path: project.path().to_owned(), + locked: true, + }), + } + .run() +} + +fn run_install_test(name: &str) -> TempProject { let source_project = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/test-projects",)).join(name); @@ -55,6 +87,7 @@ fn run_test(name: &str) -> TempProject { }, subcommand: Subcommand::Install(InstallSubcommand { project_path: project.path().to_owned(), + locked: false, }), };