diff --git a/Cargo.toml b/Cargo.toml index 5f94e34..93311a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,7 @@ tokio = { version = "1.26", features = [ "full" ] } toml = "0.7.3" topological-sort = "0.2.2" walkdir = "2.3" + +[dev-dependencies] +proptest = "1.6.0" +test-strategy = "0.4.0" diff --git a/src/config/identifier.rs b/src/config/identifier.rs new file mode 100644 index 0000000..759217b --- /dev/null +++ b/src/config/identifier.rs @@ -0,0 +1,356 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::{borrow::Cow, fmt, str::FromStr}; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +macro_rules! ident_newtype { + ($id:ident) => { + impl $id { + /// Creates a new identifier at runtime. + pub fn new>(s: S) -> Result { + ConfigIdent::new(s).map(Self) + } + + /// Creates a new identifier from a static string. + pub fn new_static(s: &'static str) -> Result { + ConfigIdent::new_static(s).map(Self) + } + + /// Creates a new identifier at compile time, panicking if it is + /// invalid. + pub const fn new_const(s: &'static str) -> Self { + Self(ConfigIdent::new_const(s)) + } + + /// Returns the identifier as a string. + #[inline] + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + #[inline] + #[allow(dead_code)] + pub(crate) fn as_ident(&self) -> &ConfigIdent { + &self.0 + } + } + + impl AsRef for $id { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_ref() + } + } + + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl FromStr for $id { + type Err = InvalidConfigIdent; + + fn from_str(s: &str) -> Result { + ConfigIdent::new(s).map(Self) + } + } + }; +} + +/// A unique identifier for a package name. +/// +/// Package names must be: +/// +/// * non-empty +/// * ASCII printable +/// * first character must be a letter +/// * contain only letters, numbers, underscores, and hyphens +/// +/// These generally match the rules of Rust package names. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct PackageName(ConfigIdent); +ident_newtype!(PackageName); + +/// A unique identifier for a service name. +/// +/// Package names must be: +/// +/// * non-empty +/// * ASCII printable +/// * first character must be a letter +/// * contain only letters, numbers, underscores, and hyphens +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct ServiceName(ConfigIdent); +ident_newtype!(ServiceName); + +/// A unique identifier for a target preset. +/// +/// Package names must be: +/// +/// * non-empty +/// * ASCII printable +/// * first character must be a letter +/// * contain only letters, numbers, underscores, and hyphens +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct PresetName(ConfigIdent); +ident_newtype!(PresetName); + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +#[serde(transparent)] +pub(crate) struct ConfigIdent(Cow<'static, str>); + +impl ConfigIdent { + /// Creates a new config identifier at runtime. + pub fn new>(s: S) -> Result { + let s = s.into(); + Self::validate(&s)?; + Ok(Self(Cow::Owned(s))) + } + + /// Creates a new config identifier from a static string. + pub fn new_static(s: &'static str) -> Result { + Self::validate(s)?; + Ok(Self(Cow::Borrowed(s))) + } + + /// Creates a new config identifier at compile time, panicking if the + /// identifier is invalid. + pub const fn new_const(s: &'static str) -> Self { + match Self::validate(s) { + Ok(_) => Self(Cow::Borrowed(s)), + Err(error) => panic!("{}", error.as_static_str()), + } + } + + const fn validate(id: &str) -> Result<(), InvalidConfigIdent> { + if id.is_empty() { + return Err(InvalidConfigIdent::Empty); + } + + let bytes = id.as_bytes(); + if !bytes[0].is_ascii_alphabetic() { + return Err(InvalidConfigIdent::StartsWithNonLetter); + } + + let mut bytes = match bytes { + [_, rest @ ..] => rest, + [] => panic!("already checked that it's non-empty"), + }; + while let [next, rest @ ..] = &bytes { + if !(next.is_ascii_alphanumeric() || *next == b'_' || *next == b'-') { + break; + } + bytes = rest; + } + + if !bytes.is_empty() { + return Err(InvalidConfigIdent::ContainsInvalidCharacters); + } + + Ok(()) + } + + /// Returns the identifier as a string. + #[inline] + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl FromStr for ConfigIdent { + type Err = InvalidConfigIdent; + + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + +impl<'de> Deserialize<'de> for ConfigIdent { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::new(s).map_err(serde::de::Error::custom) + } +} + +impl AsRef for ConfigIdent { + #[inline] + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl std::fmt::Display for ConfigIdent { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.fmt(f) + } +} + +/// Errors that can occur when creating a `ConfigIdent`. +#[derive(Clone, Debug, Error)] +pub enum InvalidConfigIdent { + Empty, + NonAsciiPrintable, + StartsWithNonLetter, + ContainsInvalidCharacters, +} + +impl InvalidConfigIdent { + pub const fn as_static_str(&self) -> &'static str { + match self { + Self::Empty => "config identifier must be non-empty", + Self::NonAsciiPrintable => "config identifier must be ASCII printable", + Self::StartsWithNonLetter => "config identifier must start with a letter", + Self::ContainsInvalidCharacters => { + "config identifier must contain only letters, numbers, underscores, and hyphens" + } + } + } +} + +impl fmt::Display for InvalidConfigIdent { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_static_str().fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use test_strategy::proptest; + + static IDENT_REGEX: &str = r"[a-zA-Z][a-zA-Z0-9_-]*"; + + #[test] + fn valid_identifiers() { + let valid = [ + "a", "ab", "a1", "a_", "a-", "a_b", "a-b", "a1_", "a1-", "a1_b", "a1-b", + ]; + for &id in &valid { + ConfigIdent::new(id).unwrap_or_else(|error| { + panic!( + "ConfigIdent::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + PackageName::new(id).unwrap_or_else(|error| { + panic!( + "PackageName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + ServiceName::new(id).unwrap_or_else(|error| { + panic!( + "ServiceName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + PresetName::new(id).unwrap_or_else(|error| { + panic!( + "PresetName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + } + } + + #[test] + fn invalid_identifiers() { + let invalid = [ + "", "1", "_", "-", "1_", "-a", "_a", "a!", "a ", "a\n", "a\t", "a\r", "a\x7F", "aɑ", + ]; + for &id in &invalid { + ConfigIdent::new(id) + .expect_err(&format!("ConfigIdent::new for {} should have failed", id)); + PackageName::new(id) + .expect_err(&format!("PackageName::new for {} should have failed", id)); + ServiceName::new(id) + .expect_err(&format!("ServiceName::new for {} should have failed", id)); + PresetName::new(id) + .expect_err(&format!("PresetName::new for {} should have failed", id)); + + // Also ensure that deserialization fails. + let json = json!(id); + serde_json::from_value::(json.clone()).expect_err(&format!( + "ConfigIdent deserialization for {} should have failed", + id + )); + serde_json::from_value::(json.clone()).expect_err(&format!( + "PackageName deserialization for {} should have failed", + id + )); + serde_json::from_value::(json.clone()).expect_err(&format!( + "ServiceName deserialization for {} should have failed", + id + )); + serde_json::from_value::(json.clone()).expect_err(&format!( + "PresetName deserialization for {} should have failed", + id + )); + } + } + + #[proptest] + fn valid_identifiers_proptest(#[strategy(IDENT_REGEX)] id: String) { + ConfigIdent::new(&id).unwrap_or_else(|error| { + panic!( + "ConfigIdent::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + PackageName::new(&id).unwrap_or_else(|error| { + panic!( + "PackageName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + ServiceName::new(&id).unwrap_or_else(|error| { + panic!( + "ServiceName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + PresetName::new(&id).unwrap_or_else(|error| { + panic!( + "PresetName::new for {} should have succeeded, but failed with: {:?}", + id, error + ); + }); + } + + #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] + struct AllIdentifiers { + config: ConfigIdent, + package: PackageName, + service: ServiceName, + preset: PresetName, + } + + #[proptest] + fn valid_identifiers_proptest_serde(#[strategy(IDENT_REGEX)] id: String) { + let all = AllIdentifiers { + config: ConfigIdent::new(&id).unwrap(), + package: PackageName::new(&id).unwrap(), + service: ServiceName::new(&id).unwrap(), + preset: PresetName::new(&id).unwrap(), + }; + + let json = serde_json::to_value(&all).unwrap(); + let deserialized: AllIdentifiers = serde_json::from_value(json).unwrap(); + assert_eq!(all, deserialized); + } +} diff --git a/src/config.rs b/src/config/imp.rs similarity index 89% rename from src/config.rs rename to src/config/imp.rs index 10fdb02..c646a8c 100644 --- a/src/config.rs +++ b/src/config/imp.rs @@ -12,10 +12,12 @@ use std::path::Path; use thiserror::Error; use topological_sort::TopologicalSort; +use super::PackageName; + /// Describes a set of packages to act upon. /// /// This structure maps "package name" to "package" -pub struct PackageMap<'a>(pub BTreeMap<&'a String, &'a Package>); +pub struct PackageMap<'a>(pub BTreeMap<&'a PackageName, &'a Package>); // The name of a file which should be created by building a package. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -68,12 +70,12 @@ impl<'a> PackageMap<'a> { /// /// Returns packages in batches that may be built concurrently. pub struct PackageDependencyIter<'a> { - lookup_by_output: BTreeMap, + lookup_by_output: BTreeMap, outputs: TopologicalSort, } impl<'a> Iterator for PackageDependencyIter<'a> { - type Item = Vec<(&'a String, &'a Package)>; + type Item = Vec<(&'a PackageName, &'a Package)>; fn next(&mut self) -> Option { if self.outputs.is_empty() { @@ -99,11 +101,11 @@ impl<'a> Iterator for PackageDependencyIter<'a> { } /// Describes the configuration for a set of packages. -#[derive(Deserialize, Debug)] +#[derive(Clone, Deserialize, Debug)] pub struct Config { /// Packages to be built and installed. #[serde(default, rename = "package")] - pub packages: BTreeMap, + pub packages: BTreeMap, } impl Config { @@ -154,22 +156,24 @@ pub fn parse>(path: P) -> Result { #[cfg(test)] mod test { + use crate::config::ServiceName; + use super::*; #[test] fn test_order() { - let pkg_a_name = String::from("pkg-a"); + let pkg_a_name = PackageName::new_const("pkg-a"); let pkg_a = Package { - service_name: String::from("a"), + service_name: ServiceName::new_const("a"), source: PackageSource::Manual, output: PackageOutput::Tarball, only_for_targets: None, setup_hint: None, }; - let pkg_b_name = String::from("pkg-b"); + let pkg_b_name = PackageName::new_const("pkg-b"); let pkg_b = Package { - service_name: String::from("b"), + service_name: ServiceName::new_const("b"), source: PackageSource::Composite { packages: vec![pkg_a.get_output_file(&pkg_a_name)], }, @@ -198,10 +202,10 @@ mod test { #[test] #[should_panic(expected = "cyclic dependency in package manifest")] fn test_cyclic_dependency() { - let pkg_a_name = String::from("pkg-a"); - let pkg_b_name = String::from("pkg-b"); + let pkg_a_name = PackageName::new_const("pkg-a"); + let pkg_b_name = PackageName::new_const("pkg-b"); let pkg_a = Package { - service_name: String::from("a"), + service_name: ServiceName::new_const("a"), source: PackageSource::Composite { packages: vec![String::from("pkg-b.tar")], }, @@ -210,7 +214,7 @@ mod test { setup_hint: None, }; let pkg_b = Package { - service_name: String::from("b"), + service_name: ServiceName::new_const("b"), source: PackageSource::Composite { packages: vec![String::from("pkg-a.tar")], }, @@ -236,9 +240,9 @@ mod test { #[test] #[should_panic(expected = "Could not find a package which creates 'pkg-b.tar'")] fn test_missing_dependency() { - let pkg_a_name = String::from("pkg-a"); + let pkg_a_name = PackageName::new_const("pkg-a"); let pkg_a = Package { - service_name: String::from("a"), + service_name: ServiceName::new_const("a"), source: PackageSource::Composite { packages: vec![String::from("pkg-b.tar")], }, diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..de4ca23 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +mod identifier; +mod imp; + +pub use identifier::*; +pub use imp::*; diff --git a/src/package.rs b/src/package.rs index c4221ce..ee75b1a 100644 --- a/src/package.rs +++ b/src/package.rs @@ -10,6 +10,7 @@ use crate::archive::{ }; use crate::blob::{self, BLOB}; use crate::cache::{Cache, CacheError}; +use crate::config::{PackageName, ServiceName}; use crate::input::{BuildInput, BuildInputs, MappedPath, TargetDirectory, TargetPackage}; use crate::progress::{NoProgress, Progress}; use crate::target::Target; @@ -168,7 +169,7 @@ pub enum PackageOutput { #[derive(Clone, Deserialize, Debug, PartialEq)] pub struct Package { /// The name of the service name to be used on the target OS. - pub service_name: String, + pub service_name: ServiceName, /// Identifies from where the package originates. /// @@ -193,7 +194,7 @@ pub struct Package { const DEFAULT_VERSION: semver::Version = semver::Version::new(0, 0, 0); async fn new_zone_archive_builder( - package_name: &str, + package_name: &PackageName, output_directory: &Utf8Path, ) -> Result>> { let tarfile = output_directory.join(format!("{}.tar.gz", package_name)); @@ -227,30 +228,46 @@ impl Default for BuildConfig<'_> { impl Package { /// The path of a package once it is built. - pub fn get_output_path(&self, name: &str, output_directory: &Utf8Path) -> Utf8PathBuf { - output_directory.join(self.get_output_file(name)) + pub fn get_output_path(&self, id: &PackageName, output_directory: &Utf8Path) -> Utf8PathBuf { + output_directory.join(self.get_output_file(id)) + } + + /// The path of the service name with respect to the install directory. + pub fn get_output_path_for_service(&self, install_directory: &Utf8Path) -> Utf8PathBuf { + install_directory.join(self.get_output_file_for_service()) } /// The path of a package after it has been "stamped" with a version. - pub fn get_stamped_output_path(&self, name: &str, output_directory: &Utf8Path) -> Utf8PathBuf { + pub fn get_stamped_output_path( + &self, + name: &PackageName, + output_directory: &Utf8Path, + ) -> Utf8PathBuf { output_directory .join("versioned") .join(self.get_output_file(name)) } /// The filename of a package once it is built. - pub fn get_output_file(&self, name: &str) -> String { + pub fn get_output_file(&self, name: &PackageName) -> String { match self.output { PackageOutput::Zone { .. } => format!("{}.tar.gz", name), PackageOutput::Tarball => format!("{}.tar", name), } } + pub fn get_output_file_for_service(&self) -> String { + match self.output { + PackageOutput::Zone { .. } => format!("{}.tar.gz", self.service_name), + PackageOutput::Tarball => format!("{}.tar", self.service_name), + } + } + #[deprecated = "Use 'Package::create', which now takes a 'BuildConfig', and implements 'Default'"] pub async fn create_for_target( &self, target: &Target, - name: &str, + name: &PackageName, output_directory: &Utf8Path, ) -> Result { let build_config = BuildConfig { @@ -263,7 +280,7 @@ impl Package { pub async fn create( &self, - name: &str, + name: &PackageName, output_directory: &Utf8Path, build_config: &BuildConfig<'_>, ) -> Result { @@ -273,7 +290,7 @@ impl Package { pub async fn stamp( &self, - name: &str, + name: &PackageName, output_directory: &Utf8Path, version: &semver::Version, ) -> Result { @@ -346,7 +363,7 @@ impl Package { &self, progress: &impl Progress, target: &Target, - name: &str, + name: &PackageName, output_directory: &Utf8Path, ) -> Result { let config = BuildConfig { @@ -359,7 +376,7 @@ impl Package { async fn create_internal( &self, - name: &str, + name: &PackageName, output_directory: &Utf8Path, config: &BuildConfig<'_>, ) -> Result { @@ -382,7 +399,7 @@ impl Package { // Adds the version file to the archive fn get_version_input( &self, - package_name: &str, + package_name: &PackageName, version: Option<&semver::Version>, ) -> BuildInput { match &self.output { @@ -397,7 +414,7 @@ impl Package { let kvs = vec![ ("v", "1"), ("t", "layer"), - ("pkg", package_name), + ("pkg", package_name.as_ref()), ("version", version), ]; @@ -514,7 +531,7 @@ impl Package { fn get_all_inputs( &self, - package_name: &str, + package_name: &PackageName, target: &Target, output_directory: &Utf8Path, zoned: bool, @@ -559,7 +576,7 @@ impl Package { let dst_directory = match self.output { PackageOutput::Zone { .. } => { let dst = Utf8Path::new("/opt/oxide") - .join(&self.service_name) + .join(self.service_name.as_str()) .join("bin"); inputs.0.extend( zone_get_all_parent_inputs(&dst)? @@ -589,7 +606,7 @@ impl Package { let destination_path = if zoned { zone_archive_path( &Utf8Path::new("/opt/oxide") - .join(&self.service_name) + .join(self.service_name.as_str()) .join(BLOB), )? } else { @@ -597,7 +614,9 @@ impl Package { }; if let Some(s3_blobs) = self.source.blobs() { inputs.0.extend(s3_blobs.iter().map(|blob| { - let from = download_directory.join(&self.service_name).join(blob); + let from = download_directory + .join(self.service_name.as_str()) + .join(blob); let to = destination_path.join(blob); BuildInput::AddBlob { path: MappedPath { from, to }, @@ -608,7 +627,7 @@ impl Package { if let Some(buildomat_blobs) = self.source.buildomat_blobs() { inputs.0.extend(buildomat_blobs.iter().map(|blob| { let from = download_directory - .join(&self.service_name) + .join(self.service_name.as_str()) .join(&blob.artifact); let to = destination_path.join(&blob.artifact); BuildInput::AddBlob { @@ -623,7 +642,7 @@ impl Package { async fn create_zone_package( &self, timer: &mut BuildTimer, - name: &str, + name: &PackageName, output_directory: &Utf8Path, config: &BuildConfig<'_>, ) -> Result { @@ -762,7 +781,7 @@ impl Package { async fn create_tarball_package( &self, - name: &str, + name: &PackageName, output_directory: &Utf8Path, config: &BuildConfig<'_>, ) -> Result { diff --git a/tests/mod.rs b/tests/mod.rs index c961bf7..13d7541 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -12,11 +12,17 @@ mod test { use tar::Archive; use omicron_zone_package::blob::download; - use omicron_zone_package::config; + use omicron_zone_package::config::{self, PackageName, ServiceName}; use omicron_zone_package::package::BuildConfig; use omicron_zone_package::progress::NoProgress; use omicron_zone_package::target::Target; + const MY_PACKAGE: PackageName = PackageName::new_const("my-package"); + + /// The package name called the same as the service name. + const MY_SERVICE_PACKAGE: PackageName = PackageName::new_const("my-service"); + const MY_SERVICE: ServiceName = ServiceName::new_const("my-service"); + fn entry_path<'a, R>(entry: &tar::Entry<'a, R>) -> Utf8PathBuf where R: 'a + Read, @@ -58,19 +64,18 @@ mod test { async fn test_package_as_zone() { // Parse the configuration let cfg = config::parse("tests/service-a/cfg.toml").unwrap(); - let package_name = "my-service"; - let package = cfg.packages.get(package_name).unwrap(); + let package = cfg.packages.get(&MY_SERVICE_PACKAGE).unwrap(); // Create the packaged file let out = camino_tempfile::tempdir().unwrap(); let build_config = BuildConfig::default(); package - .create(package_name, out.path(), &build_config) + .create(&MY_SERVICE_PACKAGE, out.path(), &build_config) .await .unwrap(); // Verify the contents - let path = package.get_output_path(package_name, out.path()); + let path = package.get_output_path_for_service(out.path()); assert!(path.exists()); let gzr = flate2::read::GzDecoder::new(File::open(path).unwrap()); let mut archive = Archive::new(gzr); @@ -97,19 +102,18 @@ mod test { async fn test_rust_package_as_zone() { // Parse the configuration let cfg = config::parse("tests/service-b/cfg.toml").unwrap(); - let package_name = "my-service"; - let package = cfg.packages.get(package_name).unwrap(); + let package = cfg.packages.get(&MY_SERVICE_PACKAGE).unwrap(); // Create the packaged file let out = camino_tempfile::tempdir().unwrap(); let build_config = BuildConfig::default(); package - .create(package_name, out.path(), &build_config) + .create(&MY_SERVICE_PACKAGE, out.path(), &build_config) .await .unwrap(); // Verify the contents - let path = package.get_output_path(package_name, out.path()); + let path = package.get_output_path_for_service(out.path()); assert!(path.exists()); let gzr = flate2::read::GzDecoder::new(File::open(path).unwrap()); let mut archive = Archive::new(gzr); @@ -140,19 +144,18 @@ mod test { async fn test_rust_package_as_tarball() { // Parse the configuration let cfg = config::parse("tests/service-c/cfg.toml").unwrap(); - let package_name = "my-service"; - let package = cfg.packages.get(package_name).unwrap(); + let package = cfg.packages.get(&MY_SERVICE_PACKAGE).unwrap(); // Create the packaged file let out = camino_tempfile::tempdir().unwrap(); let build_config = BuildConfig::default(); package - .create(package_name, out.path(), &build_config) + .create(&MY_SERVICE_PACKAGE, out.path(), &build_config) .await .unwrap(); // Verify the contents - let path = package.get_output_path(package_name, out.path()); + let path = package.get_output_path_for_service(out.path()); assert!(path.exists()); let mut archive = Archive::new(File::open(path).unwrap()); let mut ents = archive.entries().unwrap(); @@ -168,7 +171,7 @@ mod test { // Try stamping it, verify the contents again let expected_semver = semver::Version::new(3, 3, 3); let path = package - .stamp(package_name, out.path(), &expected_semver) + .stamp(&MY_SERVICE_PACKAGE, out.path(), &expected_semver) .await .unwrap(); assert!(path.exists()); @@ -192,22 +195,20 @@ mod test { async fn test_rust_package_with_distinct_service_name() { // Parse the configuration let cfg = config::parse("tests/service-d/cfg.toml").unwrap(); - let package_name = "my-package"; - let service_name = "my-service"; - let package = cfg.packages.get(package_name).unwrap(); + let package = cfg.packages.get(&MY_PACKAGE).unwrap(); - assert_eq!(package.service_name, service_name); + assert_eq!(package.service_name, MY_SERVICE); // Create the packaged file let out = camino_tempfile::tempdir().unwrap(); let build_config = BuildConfig::default(); package - .create(package_name, out.path(), &build_config) + .create(&MY_PACKAGE, out.path(), &build_config) .await .unwrap(); // Verify the contents - let path = package.get_output_path(package_name, out.path()); + let path = package.get_output_path(&MY_PACKAGE, out.path()); assert!(path.exists()); let mut archive = Archive::new(File::open(path).unwrap()); let mut ents = archive.entries().unwrap(); @@ -230,7 +231,13 @@ mod test { let batch = build_order.next().expect("Missing dependency batch"); let mut batch_pkg_names: Vec<_> = batch.iter().map(|(name, _)| *name).collect(); batch_pkg_names.sort(); - assert_eq!(batch_pkg_names, vec!["pkg-1", "pkg-2"]); + assert_eq!( + batch_pkg_names, + vec![ + &PackageName::new_const("pkg-1"), + &PackageName::new_const("pkg-2"), + ] + ); let build_config = BuildConfig::default(); for (package_name, package) in batch { // Create the packaged file @@ -243,17 +250,17 @@ mod test { // Build the composite package let batch = build_order.next().expect("Missing dependency batch"); let batch_pkg_names: Vec<_> = batch.iter().map(|(name, _)| *name).collect(); - let package_name = "pkg-3"; - assert_eq!(batch_pkg_names, vec![package_name]); - let package = cfg.packages.get(package_name).unwrap(); + let package_name = PackageName::new_const("pkg-3"); + assert_eq!(batch_pkg_names, vec![&package_name]); + let package = cfg.packages.get(&package_name).unwrap(); let build_config = BuildConfig::default(); package - .create(package_name, out.path(), &build_config) + .create(&package_name, out.path(), &build_config) .await .unwrap(); // Verify the contents - let path = package.get_output_path(package_name, out.path()); + let path = package.get_output_path(&package_name, out.path()); assert!(path.exists()); let gzr = flate2::read::GzDecoder::new(File::open(path).unwrap()); let mut archive = Archive::new(gzr);