use std::{ffi::OsString, path::PathBuf};
use anyhow::{Context, anyhow, bail};
use clap::{ArgAction, Args, FromArgMatches, Parser, Subcommand};
use midenup::{channel, commands, config, manifest, miden_wrapper, options};
pub use self::config::Config;
#[derive(Debug, Parser)]
#[command(name = "midenup")]
#[command(multicall(true))]
#[command(author, about = "The Miden toolchain installer", long_about = None)]
pub struct Midenup {
#[command(subcommand)]
behavior: Behavior,
}
#[derive(Debug, Subcommand)]
enum Behavior {
Midenup {
#[command(flatten)]
config: GlobalArgs,
#[command(subcommand)]
command: Option<Commands>,
},
#[command(external_subcommand)]
Miden(Vec<OsString>),
}
#[derive(Debug, Subcommand)]
enum Commands {
Init,
Install {
#[arg(required(true), value_name = "CHANNEL", value_parser)]
channel: channel::UserChannel,
#[clap(flatten)]
options: options::InstallationOptions,
},
Uninstall {
#[arg(required(true), value_name = "CHANNEL", value_parser)]
channel: channel::UserChannel,
},
#[command(subcommand)]
Show(commands::ShowCommand),
Set {
#[arg(required(true), value_name = "CHANNEL", value_parser)]
channel: channel::UserChannel,
},
Override {
#[arg(required(true), value_name = "CHANNEL", value_parser)]
channel: channel::UserChannel,
},
Update {
#[clap(verbatim_doc_comment)]
#[arg(value_name = "CHANNEL", value_parser)]
channel: Option<channel::UserChannel>,
#[clap(flatten)]
options: options::UpdateOptions,
},
}
const MIDENUP_MANIFEST_URI_ENV: &str = "MIDENUP_MANIFEST_URI";
#[derive(Debug, Args)]
struct GlobalArgs {
#[arg(long, hide(true), value_name = "DIR", env = "MIDENUP_HOME")]
midenup_home: Option<PathBuf>,
#[arg(
long,
hide(true),
value_name = "FILE",
env = MIDENUP_MANIFEST_URI_ENV,
default_value = manifest::Manifest::PUBLISHED_MANIFEST_URI
)]
manifest_uri: String,
#[clap(env = "MIDENUP_DEBUG_MODE", action = ArgAction::Set, default_value = "false", hide = true)]
debug: bool,
#[clap(short, long, action, default_value_t = false)]
verbose: bool,
#[clap(short = 'V', long, action, default_value_t = false)]
version: bool,
}
impl Commands {
fn execute(
&self,
config: &config::Config,
local_manifest: &mut manifest::Manifest,
) -> anyhow::Result<()> {
match &self {
Self::Init => commands::init(config),
Self::Install { channel, options } => {
let Some(channel) = config.manifest.get_channel(channel) else {
bail!("channel '{}' doesn't exist or is unavailable", channel);
};
commands::install(config, channel, local_manifest, options)
},
Self::Uninstall { channel, .. } => {
let Some(channel) = config.manifest.get_channel(channel) else {
bail!("channel '{}' doesn't exist or is unavailable", channel);
};
commands::uninstall(config, channel, local_manifest)
},
Self::Update { channel, options } => {
commands::update(config, channel.as_ref(), local_manifest, options)
},
Self::Show(cmd) => cmd.execute(config, local_manifest),
Self::Set { channel } => commands::set(config, channel),
Self::Override { channel } => commands::r#override(config, channel),
}
}
}
fn main() -> anyhow::Result<()> {
curl::init();
let cli = <Midenup as clap::CommandFactory>::command();
let matches = cli.get_matches();
let cli = Midenup::from_arg_matches(&matches).map_err(|err| err.exit()).unwrap();
let config = match cli.behavior {
Behavior::Miden(_) => {
let midenup_home = std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.map(|dir| dir.join("midenup"))
.or_else(|| dirs::data_dir().map(|dir| dir.join("midenup")))
.or_else(|| {
std::env::home_dir()
.map(|home| home.join(".local").join("share"))
})
.ok_or_else(||
anyhow!("Failed to set midenup directory.\
Consider setting a value for XDG_DATA_HOME in your shell's profile"
)
)?;
let manifest_uri = std::env::var(MIDENUP_MANIFEST_URI_ENV)
.unwrap_or(manifest::Manifest::PUBLISHED_MANIFEST_URI.to_string());
config::Config::init(midenup_home, manifest_uri, false)?
},
Behavior::Midenup { ref config, .. } => {
let midenup_home = config
.midenup_home
.clone()
.or_else(|| {
std::env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.map(|dir| dir.join("midenup"))
})
.or_else(|| dirs::data_dir().map(|dir| dir.join("midenup")))
.or_else(|| {
std::env::home_dir()
.map(|home| home.join(".local").join("share"))
})
.ok_or_else(||
anyhow!("Failed to set midenup directory.\
Consider setting a value for XDG_DATA_HOME in your shell's profile"
)
)?;
config::Config::init(midenup_home, &config.manifest_uri, config.debug)?
},
};
let mut local_manifest = {
let local_manifest_path = config.midenup_home.join("manifest").with_extension("json");
let local_manifest_uri = format!(
"file://{}",
local_manifest_path.to_str().context("Couldn't convert miden directory")?,
);
match manifest::Manifest::load_from(local_manifest_uri) {
Ok(manifest) => Ok(manifest),
Err(manifest::ManifestError::Empty | manifest::ManifestError::Missing(_)) => {
Ok(manifest::Manifest::default())
},
Err(err) => Err(err),
}
.context("Error parsing local manifest")
}?;
let result = match cli.behavior {
Behavior::Miden(argv) => miden_wrapper::miden_wrapper(argv, &config, &mut local_manifest),
Behavior::Midenup {
config: ref global_args,
command: subcommand,
} => {
if global_args.version {
println!("{}", miden_wrapper::display_version(&config));
Ok(())
} else if let Some(subcommand) = subcommand {
subcommand.execute(&config, &mut local_manifest)
} else {
bail!("no subcommand provided. Run `midenup --help` for usage information.")
}
},
};
config.update_opt_symlinks(&config)?;
result
}
#[cfg(test)]
mod tests {
use std::{fs::OpenOptions, path::Path};
use midenup::{
channel::{self, InstalledFile},
config, manifest, miden_wrapper, utils, version,
};
use tempdir::TempDir;
use crate::*;
#[macro_export]
macro_rules! full_path_manifest {
($file:expr) => {
concat!("file://", full_path!($file))
};
}
#[macro_export]
macro_rules! full_path {
($file:expr) => {
concat!(env!("CARGO_MANIFEST_DIR"), "/", $file)
};
}
type LocalManifest = manifest::Manifest;
fn test_setup(midenup_home: &Path, manifest_uri: &str) -> (LocalManifest, config::Config) {
let local_manifest = {
let local_manifest_path = midenup_home.join("manifest").with_extension("json");
let local_manifest_uri = format!(
"file://{}",
local_manifest_path.to_str().expect("Couldn't convert miden directory"),
);
match manifest::Manifest::load_from(local_manifest_uri) {
Ok(manifest) => Ok(manifest),
Err(manifest::ManifestError::Empty | manifest::ManifestError::Missing(_)) => {
Ok(manifest::Manifest::default())
},
Err(err) => Err(err),
}
.unwrap_or_else(|_| {
panic!("Failed to parse manifest {}", local_manifest_path.display())
})
};
let config = config::Config::init(midenup_home.to_path_buf().clone(), manifest_uri, true)
.unwrap_or_else(|err| {
panic!(
"Failed to construct config from manifest {} and midenup_home at {}.
Error: {}",
manifest_uri,
midenup_home.display(),
err,
)
});
(local_manifest, config)
}
struct TestEnvironment {
#[allow(dead_code)]
tmp_dir: TempDir,
midenup_dir: PathBuf,
#[allow(dead_code)]
cargo_home: PathBuf,
present_working_dir: PathBuf,
}
fn environment_setup(test_name: &str) -> TestEnvironment {
let tmp_dir = tempdir::TempDir::new(&format!("midenup-{test_name}"))
.expect("Couldn't create temp-dir");
let tmp_present_working_directory = tmp_dir.path().join("test-working-directory");
let tmp_midenup_home = tmp_dir.path().join("midenup");
let tmp_cargo_home = tmp_dir.path().join("cargo");
std::fs::create_dir(&tmp_present_working_directory).unwrap();
unsafe {
std::env::set_var("CARGO_HOME", &tmp_cargo_home);
}
std::env::set_current_dir(&tmp_present_working_directory).unwrap_or_else(|err| {
panic!(
"Failed to switch to {}, because of {err}",
tmp_present_working_directory.display()
)
});
TestEnvironment {
tmp_dir,
midenup_dir: tmp_midenup_home,
cargo_home: tmp_cargo_home,
present_working_dir: tmp_present_working_directory,
}
}
fn get_full_command(argv: Vec<OsString>) -> String {
argv.clone()
.iter()
.map(|arg| format!("{} ", arg.clone().into_string().unwrap()))
.collect::<String>()
}
#[test]
fn integration_install_uninstall_test() {
let test_name = "integration_install_uninstall_test";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let cargo_home = test_env.cargo_home;
let midenup_home = tmp_home.join("midenup");
const FILE: &str = full_path_manifest!(
"tests/data/integration_install_uninstall_test/channel-manifest.json"
);
let (mut local_manifest, config) = test_setup(&midenup_home, FILE);
let toolchain_dir = midenup_home.join("toolchains");
let command = Midenup::try_parse_from(["midenup", "init"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
assert!(midenup_home.exists());
assert!(toolchain_dir.exists());
assert!(cargo_home.join("bin").join("miden").exists());
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let latest_toolchain = toolchain_dir.join("0.16.0");
assert!(latest_toolchain.exists());
let stable_dir = toolchain_dir.join("stable");
assert!(stable_dir.exists());
assert!(stable_dir.is_symlink());
let stable_toolchain =
std::fs::read_link(&stable_dir).expect("Failed to read stable symlink");
assert_eq!(stable_toolchain.file_name(), latest_toolchain.file_name());
let command = Midenup::try_parse_from(["midenup", "install", "0.15.0"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midenup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let older_toolchain = toolchain_dir.join("0.15.0");
assert!(older_toolchain.exists());
assert!(stable_dir.exists());
assert!(latest_toolchain.exists());
let installed_toolchains = ["0.15.0", "0.16.0"].iter().map(|version| {
semver::Version::parse(version)
.unwrap_or_else(|_| panic!("Failed to turn {version} into semver::Version"))
});
local_manifest
.get_channels()
.map(|channel| channel.name.clone())
.eq(installed_toolchains);
let command = Midenup::try_parse_from(["midenup", "uninstall", "0.16.0"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
assert!(!latest_toolchain.exists());
assert!(!stable_dir.exists());
assert!(older_toolchain.exists());
let installed_toolchains = ["0.15.0"].iter().map(|version| {
semver::Version::parse(version)
.unwrap_or_else(|_| panic!("Failed to turn {version} into semver::Version"))
});
local_manifest
.get_channels()
.map(|channel| channel.name.clone())
.eq(installed_toolchains);
}
#[test]
fn integration_miden_test() {
let test_name = "integration_miden_test";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let cargo_home = test_env.cargo_home;
let midenup_home = tmp_home.join("midenup");
const FILE: &str =
full_path_manifest!("tests/data/integration_miden_test/channel-manifest.json");
let (mut local_manifest, config) = test_setup(&midenup_home, FILE);
let toolchain_dir = midenup_home.join("toolchains");
let command = Midenup::try_parse_from(["miden", "client", "--version"]).unwrap();
let Behavior::Miden(argv) = command.behavior else {
panic!("Error while parsing test command. Expected Midne Behavior, got Midenup");
};
miden_wrapper::miden_wrapper(argv.clone(), &config, &mut local_manifest)
.unwrap_or_else(|err| panic!("Failed to run: {} Error: {err}", get_full_command(argv)));
assert!(midenup_home.exists());
assert!(toolchain_dir.exists());
assert!(cargo_home.join("bin").join("miden").exists());
let latest_toolchain = toolchain_dir.join("0.16.0");
assert!(latest_toolchain.exists());
let stable_dir = toolchain_dir.join("stable");
assert!(stable_dir.exists());
assert!(stable_dir.is_symlink());
let command = Midenup::try_parse_from(["midenup", "override", "0.15.0"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let command = Midenup::try_parse_from(["miden", "client", "--version"]).unwrap();
let Behavior::Miden(argv) = command.behavior else {
panic!("Error while parsing test command. Expected Midne Behavior, got Midenup");
};
miden_wrapper::miden_wrapper(argv.clone(), &config, &mut local_manifest)
.unwrap_or_else(|err| panic!("Failed to run: {} Error: {err}", get_full_command(argv)));
let older_toolchain = toolchain_dir.join("0.15.0");
assert!(older_toolchain.exists());
let command = Midenup::try_parse_from(["midenup", "set", "0.14.0"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let command = Midenup::try_parse_from(["miden", "client", "--version"]).unwrap();
let Behavior::Miden(argv) = command.behavior else {
panic!("Error while parsing test command. Expected Midne Behavior, got Midenup");
};
miden_wrapper::miden_wrapper(argv.clone(), &config, &mut local_manifest)
.unwrap_or_else(|err| panic!("Failed to run: {} Error: {err}", get_full_command(argv)));
let oldest_toolchain = toolchain_dir.join("0.14.0");
assert!(oldest_toolchain.exists());
let installed_toolchains = ["0.14.0", "0.15.0", "0.16.0"].iter().map(|version| {
semver::Version::parse(version)
.unwrap_or_else(|_| panic!("Failed to turn {version} into semver::Version"))
});
local_manifest
.get_channels()
.map(|channel| channel.name.clone())
.eq(installed_toolchains);
}
#[test]
fn integration_miden_toolchain_toml() {
let test_name = "integration_miden_toolchain_toml";
let test_env = environment_setup(test_name);
let pwd = test_env.present_working_dir;
let tmp_home = test_env.midenup_dir;
let tmp_home_path = tmp_home;
let midenup_home = tmp_home_path.join("midenup");
const FILE: &str = full_path_manifest!(
"tests/data/integration_miden_toolchain_toml/channel-manifest.json"
);
let (mut local_manifest, config) = test_setup(&midenup_home, FILE);
let command = Midenup::try_parse_from(["midenup", "set", "0.16.0"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to initialize midenup");
let miden_toolchain_file = pwd.join("miden-toolchain.toml");
assert!(miden_toolchain_file.exists());
let toolchain_with_components =
full_path!("tests/data/integration_miden_toolchain_toml/miden-toolchain-1.toml");
std::fs::copy(toolchain_with_components, &miden_toolchain_file).unwrap();
let command = Midenup::try_parse_from(["miden", "help", "toolchain"]).unwrap();
let Behavior::Miden(argv) = command.behavior else {
panic!("Error while parsing test command. Expected Miden Behavior, got Midenup");
};
miden_wrapper::miden_wrapper(argv.clone(), &config, &mut local_manifest)
.unwrap_or_else(|err| panic!("Failed to run: {} Error: {err}", get_full_command(argv)));
let toolchain_dir = midenup_home.join("toolchains");
assert!(toolchain_dir.exists());
let installed_channel =
local_manifest.get_channel_by_name(&semver::Version::new(0, 16, 0)).unwrap();
assert!(installed_channel.components.len() == 1);
let toolchain_with_components =
full_path!("tests/data/integration_miden_toolchain_toml/miden-toolchain-2.toml");
std::fs::copy(toolchain_with_components, miden_toolchain_file).unwrap();
let command = Midenup::try_parse_from(["miden", "help", "toolchain"]).unwrap();
let Behavior::Miden(argv) = command.behavior else {
panic!("Error while parsing test command. Expected Miden Behavior, got Midenup");
};
miden_wrapper::miden_wrapper(argv.clone(), &config, &mut local_manifest)
.unwrap_or_else(|err| panic!("Failed to run: {} Error: {err}", get_full_command(argv)));
let installed_channel = local_manifest
.get_channel_by_name(&semver::Version::new(0, 16, 0))
.unwrap()
.clone();
assert_eq!(installed_channel.components.len(), 4);
let command = Midenup::try_parse_from(["midenup", "update", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
assert_eq!(installed_channel.components.len(), 4);
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let installed_channel =
local_manifest.get_channel_by_name(&semver::Version::new(0, 16, 0)).unwrap();
assert_eq!(installed_channel.components.len(), 6);
}
#[test]
fn integration_update_test() {
let test_name = "integration_update_test";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let midenup_home = tmp_home.join("midenup");
let manifest: &str =
full_path_manifest!("tests/data/integration_update_test/channel-manifest-1.json");
let (mut local_manifest, config) = test_setup(&midenup_home, manifest);
let toolchain_dir = midenup_home.join("toolchains");
let command = Midenup::try_parse_from(["midenup", "init"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to initialize midenup");
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let manifest: &str =
full_path_manifest!("tests/data/integration_update_test/channel-manifest-2.json");
let (_, config) = test_setup(&midenup_home, manifest);
let command = Midenup::try_parse_from(["midenup", "update", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to update stable");
let older_toolchain = toolchain_dir.join("0.14.0");
assert!(older_toolchain.exists());
let newer_toolchain = toolchain_dir.join("0.15.0");
assert!(newer_toolchain.exists());
let stable_dir = toolchain_dir.join("stable");
assert!(stable_dir.exists());
assert!(stable_dir.is_symlink());
let stable_toolchain = std::fs::read_link(stable_dir.as_path())
.expect("Couldn't obtain directory where the stable directory is pointing to");
assert_eq!(stable_toolchain, newer_toolchain);
let manifest: &str =
full_path_manifest!("tests/data/integration_update_test/channel-manifest-3.json");
let (_, config) = test_setup(&midenup_home, manifest);
let command = Midenup::try_parse_from(["midenup", "update"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to perform global update");
assert!(stable_dir.exists());
assert!(stable_dir.is_symlink());
let stable_toolchain = std::fs::read_link(stable_dir.as_path())
.expect("Couldn't obtain directory where the stable directory is pointing to");
assert_eq!(stable_toolchain, newer_toolchain);
let toolchain_0_15_0 = toolchain_dir.join("0.15.0");
let vm_exe_v15 = toolchain_0_15_0.join("bin").join("miden-vm");
let command = std::process::Command::new(vm_exe_v15).arg("--version").output().unwrap();
assert_eq!(String::from_utf8(command.stdout).unwrap(), "miden-vm 0.16.2\n");
assert!(!toolchain_0_15_0.join("lib").join("base.masp").exists());
let std_version = &local_manifest
.get_channel(&channel::UserChannel::Version(semver::Version::new(0, 14, 0)))
.expect("Couldn't find toolchain 0.14.0 in local manifest")
.get_component("std")
.expect("Couldn't find std library despite being listed in manifest.")
.version;
matches!(std_version, version::Authority::Git { .. });
let toolchain_0_14_0 = toolchain_dir.join("0.14.0");
let vm_exe_v14 = toolchain_0_14_0.join("bin").join("miden");
let command = std::process::Command::new(vm_exe_v14).arg("--version").output().unwrap();
assert_eq!(String::from_utf8(command.stdout).unwrap(), "Miden 0.13.0\n");
let client_v14 = toolchain_0_14_0.join("bin").join("miden-client");
assert!(client_v14.exists());
let command = Midenup::try_parse_from(["midenup", "update", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to perform global update");
let newest_toolchain = toolchain_dir.join("0.16.0");
assert!(newest_toolchain.exists());
let stable_toolchain = std::fs::read_link(stable_dir.as_path())
.expect("Couldn't obtain directory where the stable directory is pointing to");
assert_eq!(stable_toolchain, newest_toolchain);
}
#[test]
fn integration_install_stable() {
let test_name = "integration_install_stable";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let midenup_home = tmp_home.join("midenup");
const FILE: &str = full_path_manifest!("manifest/channel-manifest.json");
let (mut local_manifest, config) = test_setup(&midenup_home, FILE);
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midenup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let manifest = midenup_home.join("manifest").with_extension("json");
assert!(manifest.exists());
let stable_dir = midenup_home.join("toolchains").join("stable");
assert!(stable_dir.exists());
assert!(stable_dir.is_symlink());
let stable_channel = local_manifest
.get_latest_stable()
.expect("No stable channel found; despite having installed stable");
assert_eq!(stable_channel.alias, Some(channel::ChannelAlias::Stable));
assert_eq!(
local_manifest
.get_channels()
.next()
.expect(
"ERROR: The local_manifest in the filesystem has no alias, when it should \
have stable alias"
)
.alias
.as_ref()
.expect(
"ERROR: The installed stable toolchain should be marked as stable in the \
local manifest"
),
&channel::ChannelAlias::Stable
);
}
#[test]
fn integration_install_from_non_cargo() {
let test_name = "integration_install_from_non_cargo";
let test_env = environment_setup(test_name);
let miden_vm_clone_path = test_env.present_working_dir.join("miden_vm");
{
let miden_vm_repo = "https://github.com/0xMiden/miden-vm.git";
let vm_release_16 = "fc368686bd1e6e171a51a1a5b365ef5400e4b8d5";
utils::git::clone_specific_revision(miden_vm_repo, vm_release_16, &miden_vm_clone_path)
.unwrap();
};
let midenup_home = test_env.midenup_dir;
let manifest: &str = full_path_manifest!(
"tests/data/integration_install_from_non_cargo/channel-manifest-1.json"
);
let (mut local_manifest, config) = test_setup(&midenup_home, manifest);
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let (time_when_installed, hash_when_installed) = {
let stable_channel = local_manifest
.get_latest_stable()
.expect("No stable channel found; despite having installed stable")
.clone();
let vm_from_path = stable_channel.get_component("vm").unwrap();
let last_modification = match vm_from_path.version {
version::Authority::Path { last_modification, .. } => last_modification.unwrap(),
_ => panic!(
"Failed to recognize miden_vm's Authority as Path, despite being installed \
like so."
),
};
let client_from_git = stable_channel.get_component("client").unwrap();
let revision = match &client_from_git.version {
version::Authority::Git {
target: version::GitTarget::Revision { hash },
..
} => hash.clone(),
authority => panic!(
"Failed to recognize miden_client's Authority as Git, despite being installed \
like so. Found: {authority}"
),
};
(last_modification, revision)
};
let command = Midenup::try_parse_from(["midenup", "update"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to update");
let (new_time, new_revision) = {
let stable_channel = local_manifest
.get_latest_stable()
.expect("No stable channel found; despite having installed stable")
.clone();
let vm_from_path = stable_channel.get_component("vm").unwrap();
let last_modification = match vm_from_path.version {
version::Authority::Path { last_modification, .. } => last_modification.unwrap(),
_ => panic!(
"Failed to recognize miden_vm's Authority as Path, despite being installed \
like so."
),
};
let client_from_git = stable_channel.get_component("client").unwrap();
let revision = match &client_from_git.version {
version::Authority::Git {
target: version::GitTarget::Revision { hash },
..
} => hash.clone(),
authority => panic!(
"Failed to recognize miden_client's Authority as Git, despite being installed \
like so. Found: {authority}"
),
};
(last_modification, revision)
};
assert_eq!(new_time, time_when_installed);
assert_eq!(new_revision, hash_when_installed);
let manifest: &str = full_path_manifest!(
"tests/data/integration_install_from_non_cargo/channel-manifest-2.json"
);
let (_, config) = test_setup(&midenup_home, manifest);
{
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(miden_vm_clone_path.join("miden-vm/").join("trigger-update"))
.unwrap();
}
let command = Midenup::try_parse_from(["midenup", "update", "--path-update=all"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to update");
let (new_time, new_revision) = {
let stable_channel = local_manifest
.get_latest_stable()
.expect("No stable channel found; despite having installed stable")
.clone();
let vm_from_path = stable_channel.get_component("vm").unwrap();
let last_modification = match vm_from_path.version {
version::Authority::Path { last_modification, .. } => last_modification.unwrap(),
_ => panic!(
"Failed to recognize miden_vm's Authority as Path, despite being installed \
like so."
),
};
let client_from_git = stable_channel.get_component("client").unwrap();
let revision = match &client_from_git.version {
version::Authority::Git {
target: version::GitTarget::Revision { hash },
..
} => hash.clone(),
authority => panic!(
"Failed to recognize miden_client's Authority as Git, despite being installed \
like so. Found: {authority}"
),
};
(last_modification, revision)
};
assert!(new_time > time_when_installed);
assert_ne!(new_revision, hash_when_installed);
}
#[test]
#[should_panic]
fn midenup_catches_installation_failure() {
let test_name = "midenup_catches_installation_failure";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let midenup_home = tmp_home.join("midenup");
const FILE_PRE_UPDATE: &str = full_path_manifest!(
"tests/data/unit_test_manifest_additional/manifest-uncompilable-midenc.json"
);
let (mut local_manifest, config) = test_setup(&midenup_home, FILE_PRE_UPDATE);
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midneup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let manifest = midenup_home.join("manifest").with_extension("json");
assert!(manifest.exists());
}
#[test]
fn integration_migration_test() {
let test_name = "integration_migration_test";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let midenup_home = tmp_home.join("midenup");
let toolchain_dir = midenup_home.join("toolchains");
let manifest: &str =
full_path_manifest!("tests/data/integration_migration_test/channel-manifest-1.json");
let (mut local_manifest, config) = test_setup(&midenup_home, manifest);
let command = Midenup::try_parse_from(["midenup", "init"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midenup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to initialize midenup");
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midenup Behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
assert!(toolchain_dir.join("0.20.3").join("bin").join("miden-vm").exists());
assert!(toolchain_dir.join("0.20.3").join("bin").join("miden-client").exists());
assert!(toolchain_dir.join("0.20.3").join("lib").join("core.masp").exists());
let manifest: &str =
full_path_manifest!("tests/data/integration_migration_test/channel-manifest-2.json");
let (_, config) = test_setup(&midenup_home, manifest);
let command = Midenup::try_parse_from(["midenup", "update"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Error while parsing test command. Expected Midenup Behavior, got Miden");
};
command
.execute(&config, &mut local_manifest)
.expect("Failed to perform global update");
assert!(toolchain_dir.join("0.13.0").exists());
assert!(toolchain_dir.join("0.13.0").join("installation-successful").exists());
assert!(!toolchain_dir.join("0.20.3").exists());
let stable_symlink = toolchain_dir.join("stable");
assert!(stable_symlink.exists(), "stable symlink should exist after migration");
let symlink_target =
std::fs::read_link(&stable_symlink).expect("stable should be a symlink");
assert_eq!(
symlink_target,
toolchain_dir.join("0.13.0"),
"stable symlink should point to the migrated channel"
);
}
#[test]
fn integration_test_components_are_runnable() {
let test_name = "integration_test_components";
let test_env = environment_setup(test_name);
let tmp_home = test_env.midenup_dir;
let midenup_home = tmp_home.join("midenup");
const FILE: &str = full_path_manifest!("manifest/channel-manifest.json");
let (mut local_manifest, config) = test_setup(&midenup_home, FILE);
let command = Midenup::try_parse_from(["midenup", "install", "stable"]).unwrap();
let Behavior::Midenup { command: Some(command), .. } = command.behavior else {
panic!("Expected Midenup behavior, got Miden");
};
command.execute(&config, &mut local_manifest).expect("Failed to install stable");
let stable_channel = local_manifest
.get_latest_stable()
.expect("No stable channel found after installing stable")
.clone();
println!("Installed: {}", stable_channel);
for component in &stable_channel.components {
let component_type = component.get_installed_file();
if matches!(component_type, InstalledFile::Library { .. }) {
continue;
}
if matches!(component_type, InstalledFile::Executable { alias_only: true, .. }) {
continue;
}
let argv: Vec<OsString> =
vec!["miden".into(), "help".into(), component.name.as_ref().into()];
miden_wrapper::miden_wrapper(argv, &config, &mut local_manifest).unwrap_or_else(
|err| {
panic!(
"Component '{}' is not runnable through the 'miden' interface: {}",
component.name, err
)
},
);
}
}
}