use std::{
cmp::Ordering,
error::Error,
fmt,
process::{ExitCode, Termination},
};
use indexmap::IndexMap;
use rand::prelude::*;
use regex::Regex;
use semver::{BuildMetadata, Prerelease, Version, VersionReq};
use serde::Serialize;
use super::regex::{SEMVER_REGEX, generate_any_valid_semver, generate_u64_safe_semver};
#[derive(Serialize, PartialEq)]
pub(crate) struct ValidateResult {
valid: bool,
}
impl ValidateResult {
pub(crate) fn validate(semantic_version: String, small: bool) -> Self {
let pass = if small {
Version::parse(&semantic_version).is_ok()
} else {
Regex::new(super::regex::SEMVER_REGEX)
.unwrap()
.is_match(&semantic_version)
};
Self { valid: pass }
}
}
impl fmt::Display for ValidateResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "valid: {}", self.valid)?;
Ok(())
}
}
impl Termination for ValidateResult {
fn report(self) -> std::process::ExitCode {
if self.valid {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct FilterTestResult {
pass: bool,
}
impl FilterTestResult {
pub(crate) fn filter_test(filter: &VersionReq, semantic_version: &Version) -> FilterTestResult {
filter.matches(semantic_version).into()
}
}
impl Termination for FilterTestResult {
fn report(self) -> std::process::ExitCode {
if self.pass {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
}
impl From<bool> for FilterTestResult {
fn from(value: bool) -> Self {
Self { pass: value }
}
}
impl fmt::Display for FilterTestResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "pass: {}", self.pass)?;
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SelectComponent {
Major,
Minor,
Patch,
PreRelease,
BuildMetadata,
}
#[derive(Serialize, PartialEq)]
pub(crate) struct SelectResult {
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<String>,
#[serde(skip)]
fail_if_not_found: bool,
}
#[derive(Debug)]
struct SelectParseError(String);
impl fmt::Display for SelectParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for SelectParseError {}
impl SelectResult {
pub(crate) fn select(
version: &str,
component: SelectComponent,
small: bool,
fail_if_not_found: bool,
) -> Result<Self, Box<dyn Error>> {
let value = if small {
let v = Version::parse(version).map_err(|e| {
SelectParseError(format!("unable to parse semantic version: {version}: {e}"))
})?;
Self::extract_from_version(&v, component)
} else {
let parsed = parse_semver_components(version).ok_or_else(|| {
SelectParseError(format!(
"version string did not match semver regex: {version}"
))
})?;
Self::extract_from_regex(parsed, component)
};
Ok(Self {
value,
fail_if_not_found,
})
}
fn extract_from_version(v: &Version, component: SelectComponent) -> Option<String> {
match component {
SelectComponent::Major => Some(v.major.to_string()),
SelectComponent::Minor => Some(v.minor.to_string()),
SelectComponent::Patch => Some(v.patch.to_string()),
SelectComponent::PreRelease => {
let s = v.pre.as_str();
if s.is_empty() {
None
} else {
Some(s.to_string())
}
}
SelectComponent::BuildMetadata => {
let s = v.build.as_str();
if s.is_empty() {
None
} else {
Some(s.to_string())
}
}
}
}
fn extract_from_regex(
(major, minor, patch, pre, build): SemverRegexCaptures,
component: SelectComponent,
) -> Option<String> {
match component {
SelectComponent::Major => Some(major),
SelectComponent::Minor => Some(minor),
SelectComponent::Patch => Some(patch),
SelectComponent::PreRelease => pre,
SelectComponent::BuildMetadata => build,
}
}
}
type SemverRegexCaptures = (String, String, String, Option<String>, Option<String>);
fn parse_semver_components(s: &str) -> Option<SemverRegexCaptures> {
let re = Regex::new(SEMVER_REGEX).ok()?;
let cap = re.captures(s)?;
let major = cap.get(1)?.as_str().to_string();
let minor = cap.get(2)?.as_str().to_string();
let patch = cap.get(3)?.as_str().to_string();
let pre = cap
.get(4)
.map(|m| m.as_str().to_string())
.filter(|s| !s.is_empty());
let build = cap
.get(5)
.map(|m| m.as_str().to_string())
.filter(|s| !s.is_empty());
Some((major, minor, patch, pre, build))
}
impl fmt::Display for SelectResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref v) = self.value {
writeln!(f, "{v}")?;
}
Ok(())
}
}
impl Termination for SelectResult {
fn report(self) -> std::process::ExitCode {
if self.fail_if_not_found && self.value.is_none() {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub(crate) enum SegmentType {
Numeric,
Ascii,
}
impl fmt::Display for SegmentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SegmentType::Numeric => write!(f, "Numeric"),
SegmentType::Ascii => write!(f, "Ascii"),
}
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub(crate) struct PreMetaSegment {
kind: SegmentType,
value: String,
}
impl fmt::Display for PreMetaSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})", self.value, self.kind)
}
}
impl From<&str> for PreMetaSegment {
fn from(value: &str) -> Self {
PreMetaSegment {
kind: if value.chars().all(|c| c.is_ascii_digit()) {
SegmentType::Numeric
} else {
SegmentType::Ascii
},
value: value.to_string(),
}
}
}
#[derive(Clone, Serialize, PartialEq)]
pub(crate) struct VersionExplanation {
major: u64,
minor: u64,
patch: u64,
#[serde(skip_serializing_if = "Option::is_none")]
prerelease_string: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "prerelease"))]
prerelease: Option<Vec<PreMetaSegment>>,
#[serde(skip_serializing_if = "Option::is_none")]
build_metadata_string: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "build-metadata"))]
build_metadata: Option<Vec<PreMetaSegment>>,
}
impl From<&Version> for VersionExplanation {
fn from(value: &Version) -> Self {
let prerelease_string = value.pre.as_str();
let (prerelease_string, prerelease) = if prerelease_string.is_empty() {
(None, None)
} else {
(
Some(prerelease_string.to_string()),
Some(
prerelease_string
.split('.')
.map(PreMetaSegment::from)
.collect(),
),
)
};
let build_metadata_string = value.build.as_str();
let (build_metadata_string, build_metadata) = if build_metadata_string.is_empty() {
(None, None)
} else {
(
Some(build_metadata_string.to_string()),
Some(
build_metadata_string
.split('.')
.map(PreMetaSegment::from)
.collect(),
),
)
};
Self {
major: value.major,
minor: value.minor,
patch: value.patch,
prerelease,
prerelease_string,
build_metadata,
build_metadata_string,
}
}
}
impl fmt::Display for VersionExplanation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Major: {}", self.major)?;
writeln!(f, "Minor: {}", self.minor)?;
writeln!(f, "Patch: {}", self.patch)?;
if let (Some(pr_str), Some(pr)) = (&self.prerelease_string, &self.prerelease) {
writeln!(f, "PreRelease: {}", pr_str)?;
for i in pr.iter() {
writeln!(f, "- {i}")?;
}
}
if let (Some(bm_str), Some(bm)) = (&self.build_metadata_string, &self.build_metadata) {
writeln!(f, "Build Metadata: {}", bm_str)?;
for i in bm.iter() {
writeln!(f, "- {i}")?;
}
}
Ok(())
}
}
impl Termination for VersionExplanation {
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct FlatVersionsList {
versions: Vec<Version>,
potentially_ambiguous: bool,
}
impl From<&mut OrderedVersionMap> for FlatVersionsList {
fn from(value: &mut OrderedVersionMap) -> Self {
let mut flat: Vec<Version> = Vec::new();
value.inner.iter_mut().for_each(|vv| flat.append(vv.1));
Self {
versions: flat,
potentially_ambiguous: value.potentially_ambiguous,
}
}
}
impl fmt::Display for FlatVersionsList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for v in self.versions.iter() {
writeln!(f, "{v}")?
}
Ok(())
}
}
impl Termination for FlatVersionsList {
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct FlatStringList {
versions: Vec<String>,
}
impl From<GenerateResult> for FlatStringList {
fn from(value: GenerateResult) -> Self {
Self {
versions: value.into_inner(),
}
}
}
impl fmt::Display for FlatStringList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for v in self.versions.iter() {
writeln!(f, "{v}")?
}
Ok(())
}
}
impl Termination for FlatStringList {
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
#[derive(Serialize)]
pub(crate) struct OrderedVersionMap {
#[serde(rename(serialize = "versions"))]
inner: IndexMap<Version, Vec<Version>>,
potentially_ambiguous: bool,
}
impl OrderedVersionMap {
pub(crate) fn new(
versions: &mut Vec<Version>,
filter: &Option<VersionReq>,
lexical_sorting: bool,
reverse: bool,
) -> Self {
if let Some(filter) = filter {
versions.retain(|v| filter.matches(v));
}
if reverse {
versions.sort_by(|a, b| b.cmp(a));
} else {
versions.sort();
}
let mut ordered_version_map: IndexMap<Version, Vec<Version>> = IndexMap::new();
for version in versions {
let key = version_without_build_metadata(version);
match ordered_version_map.get_mut(&key) {
Some(v) => v.push(version.clone()),
None => {
let new_value = vec![version.clone()];
let map_response = ordered_version_map.insert(key, new_value);
if map_response.is_some() {
panic!("should not have gotten a map response for an empty key")
}
}
}
}
let mut potentially_ambiguous = false;
for (_, v) in ordered_version_map.iter_mut() {
if lexical_sorting {
if reverse {
v.sort_by(|a, b| b.cmp(a));
} else {
v.sort();
}
} else {
v.shuffle(&mut rand::rng());
}
if v.len() > 1 {
potentially_ambiguous = true
}
}
Self {
inner: ordered_version_map,
potentially_ambiguous,
}
}
pub fn potentially_ambiguous(&self) -> bool {
self.potentially_ambiguous
}
}
impl fmt::Display for OrderedVersionMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for key in self.inner.keys() {
writeln!(f, "{key}:")?;
if let Some(vals) = self.inner.get(key) {
for val in vals {
writeln!(f, "\t- {val}")?;
}
}
}
Ok(())
}
}
impl Termination for OrderedVersionMap {
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct ComparisonStatement {
semantic_ordering: SerializableOrdering,
lexical_ordering: SerializableOrdering,
}
impl ComparisonStatement {
pub(crate) fn new(a: &Version, b: &Version) -> Self {
let a_no_build = version_without_build_metadata(a);
let b_no_build = version_without_build_metadata(b);
Self {
semantic_ordering: a_no_build.cmp(&b_no_build).into(),
lexical_ordering: a.cmp(b).into(),
}
}
pub(crate) fn semantic_ordering(&self) -> &SerializableOrdering {
&self.semantic_ordering
}
#[allow(dead_code)]
pub(crate) fn lexical_ordering(&self) -> &SerializableOrdering {
&self.lexical_ordering
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct GenerateResult {
inner: Vec<String>,
}
impl GenerateResult {
pub(crate) fn new(small: bool, count: usize) -> Self {
let inner = if small {
generate_u64_safe_semver(count)
} else {
generate_any_valid_semver(count)
};
GenerateResult { inner }
}
pub(crate) fn into_inner(self) -> Vec<String> {
self.inner.clone()
}
}
impl fmt::Display for GenerateResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in self.inner.iter() {
writeln!(f, "{}", &i)?
}
Ok(())
}
}
#[derive(Serialize, PartialEq)]
pub(crate) struct VersionMutationResult {
pub(crate) mutated_version: Version,
}
impl VersionMutationResult {
pub(crate) fn set(
version: &Version,
major: Option<u64>,
minor: Option<u64>,
patch: Option<u64>,
pre_release: Option<String>,
build_metadata: Option<String>,
) -> Result<VersionMutationResult, Box<dyn Error>> {
let mut response = version.clone();
if let Some(major) = major {
response.major = major
}
if let Some(minor) = minor {
response.minor = minor
}
if let Some(patch) = patch {
response.patch = patch
}
if let Some(pre_release) = pre_release {
response.pre = match Prerelease::new(pre_release.as_str()) {
Ok(p) => p,
Err(e) => return Err(format!("unable to use string as pre-release: {}", e).into()),
};
}
if let Some(build_metadata) = build_metadata {
response.build = match BuildMetadata::new(build_metadata.as_str()) {
Ok(b) => b,
Err(e) => {
return Err(format!("unable to use string as build metadata: {}", e).into());
}
};
}
Ok(VersionMutationResult {
mutated_version: response,
})
}
pub(crate) fn bump(
version: &Version,
major: Option<u64>,
minor: Option<u64>,
patch: Option<u64>,
) -> Result<VersionMutationResult, Box<dyn Error>> {
let mut response = version.clone();
if let Some(major) = major {
response.major = match response.major.checked_add(major) {
Some(m) => m,
None => {
return Err(format!(
"major bump ({} + {}) overflows u64",
response.major, major
)
.into());
}
}
}
if let Some(minor) = minor {
response.minor = match response.minor.checked_add(minor) {
Some(m) => m,
None => {
return Err(format!(
"minor bump ({} + {}) overflows u64",
response.minor, minor
)
.into());
}
}
}
if let Some(patch) = patch {
response.patch = match response.patch.checked_add(patch) {
Some(m) => m,
None => {
return Err(format!(
"patch bump ({} + {}) overflows u64",
response.patch, patch
)
.into());
}
}
}
Ok(VersionMutationResult {
mutated_version: response,
})
}
}
impl fmt::Display for VersionMutationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.mutated_version)
}
}
impl Termination for VersionMutationResult {
fn report(self) -> ExitCode {
ExitCode::SUCCESS
}
}
impl Termination for ComparisonStatement {
fn report(self) -> ExitCode {
match (self.semantic_ordering, self.lexical_ordering) {
(SerializableOrdering::Equal, SerializableOrdering::Equal) => ExitCode::SUCCESS,
(sem, lex) => ExitCode::from(simplify_exit_code(sem, lex)),
}
}
}
fn simplify_exit_code(sem: SerializableOrdering, lex: SerializableOrdering) -> u8 {
100 + (Into::<u8>::into(sem) * 10) + Into::<u8>::into(lex)
}
impl fmt::Display for ComparisonStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Semantically: {:?}\nLexically: {:?}\n",
self.semantic_ordering, self.lexical_ordering
)
}
}
#[derive(Debug, Serialize, PartialEq)]
pub(crate) enum SerializableOrdering {
Less,
Greater,
Equal,
}
impl From<SerializableOrdering> for u8 {
fn from(value: SerializableOrdering) -> Self {
match value {
SerializableOrdering::Less => 0,
SerializableOrdering::Equal => 1,
SerializableOrdering::Greater => 2,
}
}
}
impl From<Ordering> for SerializableOrdering {
fn from(value: Ordering) -> Self {
match value {
Ordering::Less => SerializableOrdering::Less,
Ordering::Equal => SerializableOrdering::Equal,
Ordering::Greater => SerializableOrdering::Greater,
}
}
}
pub(crate) fn version_without_build_metadata(version: &Version) -> Version {
Version {
major: version.major,
minor: version.minor,
patch: version.patch,
pre: version.pre.clone(),
build: BuildMetadata::EMPTY,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_without_build_metadata() {
assert_eq!(
version_without_build_metadata(&Version::parse("0.0.0-123.123+123.123").unwrap()).build,
BuildMetadata::EMPTY
);
}
#[test]
fn test_pre_meta_segment() {
assert_eq!(
PreMetaSegment::from("00000aaaa00000"),
PreMetaSegment {
kind: SegmentType::Ascii,
value: "00000aaaa00000".to_string()
}
);
assert_eq!(
PreMetaSegment::from("00000"),
PreMetaSegment {
kind: SegmentType::Numeric,
value: "00000".to_string()
}
);
assert_eq!(
PreMetaSegment::from("00001"),
PreMetaSegment {
kind: SegmentType::Numeric,
value: "00001".to_string()
}
);
assert_eq!(
PreMetaSegment::from("-00001"),
PreMetaSegment {
kind: SegmentType::Ascii,
value: "-00001".to_string()
}
);
}
#[test]
fn test_ordered_version_map() {
let mut scaffold1 = ["99.0.0", "100.0.0", "0.0.1"]
.iter()
.map(|v| Version::parse(v).unwrap())
.collect();
let test = OrderedVersionMap::new(&mut scaffold1, &None, false, false);
assert!(test.inner.contains_key(&Version::parse("99.0.0").unwrap()));
assert!(test.inner.contains_key(&Version::parse("100.0.0").unwrap()));
assert!(test.inner.contains_key(&Version::parse("0.0.1").unwrap()));
assert!(!test.potentially_ambiguous);
let mut scaffold2: Vec<Version> = vec![
"0.0.0-alpha.0+metadata",
"0.0.0-alpha.0+other.metadata",
"0.0.0-alpha.0+other.metadata.3",
"0.0.1",
"0.0.2",
"0.2.0",
"0.2.99",
"1.0.0-rc.1",
"1.0.0-rc-1",
"1.0.0-rc-2+aaaaaa",
"1.0.0-rc-2+bbbbbb",
"1.0.0-rc-2+cccccc",
"1.0.0-rc-2+dddddd",
"1.0.0-rc-2.0+dddddd",
"1.0.0-rc-2.1+dddddd",
"1.0.0-rc-2+dddddd.0",
"1.0.0-rc-2+dddddd.1",
"1.0.0-rc-2+eeeeee",
"1.0.0+aaaaaa",
"1.0.0",
"99.99.0-rc1.0",
]
.iter()
.map(|v| Version::parse(v).unwrap())
.collect();
let test = OrderedVersionMap::new(&mut scaffold2, &None, false, false);
let test_keys: Vec<Version> = test.inner.keys().cloned().collect();
assert!(test_keys.len() == 12);
assert!(test_keys[0] == Version::parse("0.0.0-alpha.0").unwrap());
assert!(test_keys[test_keys.len() - 1] == Version::parse("99.99.0-rc1.0").unwrap());
assert!(test.potentially_ambiguous);
let test = OrderedVersionMap::new(&mut scaffold2, &None, false, true);
let test_keys: Vec<Version> = test.inner.keys().cloned().collect();
assert!(test_keys.len() == 12);
assert!(test_keys[test_keys.len() - 1] == Version::parse("0.0.0-alpha.0").unwrap());
assert!(test_keys[0] == Version::parse("99.99.0-rc1.0").unwrap());
assert!(test.potentially_ambiguous);
let test = OrderedVersionMap::new(
&mut scaffold2,
&Some(VersionReq::parse("*").unwrap()),
false,
false,
);
let test_keys: Vec<Version> = test.inner.keys().cloned().collect();
assert!(test_keys.len() == 5);
assert!(test_keys[0] == Version::parse("0.0.1").unwrap());
assert!(test_keys[test_keys.len() - 1] == Version::parse("1.0.0").unwrap());
let _ = format!("{}", test);
}
#[test]
fn flat_version_list() {
let mut scaffold: Vec<Version> = vec![
"0.0.0-alpha.0+metadata",
"0.0.0-alpha.0+other.metadata",
"0.0.0-alpha.0+other.metadata.3",
"0.0.1",
"0.0.2",
"0.2.0",
"0.2.99",
"1.0.0-rc.1",
"1.0.0-rc-1",
"1.0.0-rc-2+aaaaaa",
"1.0.0-rc-2+bbbbbb",
"1.0.0-rc-2+cccccc",
"1.0.0-rc-2+dddddd",
"1.0.0-rc-2.0+dddddd",
"1.0.0-rc-2.1+dddddd",
"1.0.0-rc-2+dddddd.0",
"1.0.0-rc-2+dddddd.1",
"1.0.0-rc-2+eeeeee",
"1.0.0+aaaaaa",
"1.0.0",
"99.99.0-rc1.0",
]
.iter()
.map(|v| Version::parse(v).unwrap())
.collect();
let mut test = OrderedVersionMap::new(&mut scaffold, &None, true, false);
let test = FlatVersionsList::from(&mut test);
assert!(test.versions.len() == 21);
assert!(test.versions[0] == Version::parse("0.0.0-alpha.0+metadata").unwrap());
assert!(test.versions[test.versions.len() - 1] == Version::parse("99.99.0-rc1.0").unwrap());
let mut test = OrderedVersionMap::new(&mut scaffold, &None, true, true);
let test = FlatVersionsList::from(&mut test);
assert!(test.versions.len() == 21);
assert!(
test.versions[test.versions.len() - 1]
== Version::parse("0.0.0-alpha.0+metadata").unwrap()
);
assert!(test.versions[0] == Version::parse("99.99.0-rc1.0").unwrap());
let _ = format!("{}", test);
}
#[test]
fn test_version_explanation() {
let test =
VersionExplanation::from(&Version::parse("0.0.0-0.a.b.c.4+0.-1.a.b0.3").unwrap());
assert!(test.major == 0);
assert!(test.minor == 0);
assert!(test.patch == 0);
assert!(test.prerelease.is_some());
let test_prerelease = test.prerelease.as_ref().unwrap();
assert!(test_prerelease.len() == 5);
assert!(test_prerelease[1].kind == SegmentType::Ascii);
assert!(test_prerelease[1].value == "a");
assert!(test_prerelease[test_prerelease.len() - 1].kind == SegmentType::Numeric);
assert!(test_prerelease[test_prerelease.len() - 1].value == "4");
assert!(test.prerelease_string.as_ref().unwrap() == "0.a.b.c.4");
assert!(test.build_metadata.is_some());
let test_build_metadata = test.build_metadata.as_ref().unwrap();
assert!(test_build_metadata.len() == 5);
assert!(test_build_metadata[1].kind == SegmentType::Ascii);
assert!(test_build_metadata[1].value == "-1");
assert!(test_build_metadata[test_build_metadata.len() - 1].kind == SegmentType::Numeric);
assert!(test_build_metadata[test_build_metadata.len() - 1].value == "3");
assert!(test.build_metadata_string.as_ref().unwrap() == "0.-1.a.b0.3");
let _ = format!("{}", test);
}
#[test]
fn test_filter_test_result() {
let test = FilterTestResult::filter_test(
&VersionReq::parse(">1").unwrap(),
&Version::parse("0.0.0").unwrap(),
);
assert!(!test.pass);
assert_eq!(test.report(), ExitCode::FAILURE);
let test = FilterTestResult::filter_test(
&VersionReq::parse(">1").unwrap(),
&Version::parse("2.0.0").unwrap(),
);
assert!(test.pass);
assert_eq!(test.report(), ExitCode::SUCCESS);
let test = FilterTestResult::filter_test(
&VersionReq::parse(">=1").unwrap(),
&Version::parse("1.0.0").unwrap(),
);
assert!(test.pass);
assert_eq!(test.report(), ExitCode::SUCCESS);
let test = FilterTestResult::filter_test(
&VersionReq::parse(">=1").unwrap(),
&Version::parse("1.0.0").unwrap(),
);
let _ = format!("{}", test);
}
#[test]
fn test_validate() {
let test = ValidateResult::validate("0.0.0-x+b".to_string(), true);
assert!(test.valid);
let test = ValidateResult::validate("0.0.0-x+b".to_string(), false);
assert!(test.valid);
let test = ValidateResult::validate("18446744073709551616.0.0-x+b".to_string(), true);
assert!(!test.valid);
let test = ValidateResult::validate("18446744073709551616.0.0-x+b".to_string(), false);
assert!(test.valid);
let test = ValidateResult::validate("0.0.0a".to_string(), true);
assert!(!test.valid);
let test = ValidateResult::validate("0.0.0a".to_string(), false);
assert!(!test.valid);
let _ = format!("{}", test);
}
#[test]
fn test_generate() {
let test = GenerateResult::new(false, 10);
assert_eq!(test.into_inner().len(), 10);
let test = GenerateResult::new(true, 10);
for s in test.into_inner() {
assert!(Version::parse(&s).is_ok())
}
let test = GenerateResult::new(true, 1);
let _ = format!("{}", test);
}
#[test]
fn test_bump_and_set() {
let base_version = Version::new(1, 1, 1);
let set_version = VersionMutationResult::set(
&base_version,
Some(2),
Some(3),
Some(4),
Some("a.b.c".to_string()),
Some("x.y.z".to_string()),
)
.unwrap();
let bmp_version =
VersionMutationResult::bump(&base_version, Some(2), Some(3), Some(4)).unwrap();
assert_eq!(set_version.mutated_version.major, 2);
assert_eq!(bmp_version.mutated_version.major, 3);
assert_eq!(set_version.mutated_version.minor, 3);
assert_eq!(bmp_version.mutated_version.minor, 4);
assert_eq!(set_version.mutated_version.patch, 4);
assert_eq!(bmp_version.mutated_version.patch, 5);
assert_eq!(set_version.mutated_version.pre.as_str(), "a.b.c");
assert_eq!(bmp_version.mutated_version.pre, Prerelease::EMPTY);
assert_eq!(set_version.mutated_version.build.as_str(), "x.y.z");
assert_eq!(bmp_version.mutated_version.build, BuildMetadata::EMPTY);
assert!(
VersionMutationResult::bump(&base_version, Some(u64::MAX), Some(3), Some(4)).is_err()
);
assert!(
VersionMutationResult::bump(&base_version, Some(2), Some(u64::MAX), Some(4)).is_err()
);
assert!(
VersionMutationResult::bump(&base_version, Some(2), Some(3), Some(u64::MAX)).is_err()
);
}
#[test]
fn test_comparison_statement() {
let test = ComparisonStatement::new(
&Version::parse("0.0.0").unwrap(),
&Version::parse("2.0.0").unwrap(),
);
assert_eq!(test.semantic_ordering, SerializableOrdering::Less);
assert_eq!(test.lexical_ordering, SerializableOrdering::Less);
assert_eq!(test.report(), 100.into());
let test = ComparisonStatement::new(
&Version::parse("2.0.0+100").unwrap(),
&Version::parse("2.0.0").unwrap(),
);
assert_eq!(test.semantic_ordering, SerializableOrdering::Equal);
assert_eq!(test.lexical_ordering, SerializableOrdering::Greater);
assert_eq!(test.report(), 112.into());
let test = ComparisonStatement::new(
&Version::parse("2.0.0").unwrap(),
&Version::parse("2.0.0-rc1").unwrap(),
);
assert_eq!(test.semantic_ordering, SerializableOrdering::Greater);
assert_eq!(test.lexical_ordering, SerializableOrdering::Greater);
assert_eq!(test.report(), 122.into());
let test = ComparisonStatement::new(
&Version::parse("2.4.2").unwrap(),
&Version::parse("2.4.2").unwrap(),
);
assert_eq!(test.semantic_ordering, SerializableOrdering::Equal);
assert_eq!(test.lexical_ordering, SerializableOrdering::Equal);
assert_eq!(test.report(), ExitCode::SUCCESS);
let test = ComparisonStatement::new(
&Version::parse("2.4.2").unwrap(),
&Version::parse("2.4.2").unwrap(),
);
let _ = format!("{}", test);
}
}