Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

library: return required update for each crate #398

Merged
merged 44 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
410152f
library: return required update for each crate
marcoieni Feb 23, 2023
40e6ec2
Merge branch 'main' into crate-report
marcoieni Feb 23, 2023
910ad05
comment
marcoieni Feb 23, 2023
55ab5fc
logic
marcoieni Feb 23, 2023
9e84da2
better explanation
marcoieni Feb 23, 2023
15ff574
all instead of any
marcoieni Feb 25, 2023
8ac3889
Merge branch 'main' into crate-report
marcoieni Feb 25, 2023
41540c5
remove useless required_bump function
marcoieni Feb 25, 2023
1445fb6
process all crates before returning error
marcoieni Feb 25, 2023
6f1bcee
fmt
marcoieni Feb 25, 2023
31e8845
Add missing indent.
obi1kenobi Feb 27, 2023
a13e4fc
Merge branch 'main' into crate-report
marcoieni Mar 9, 2023
b022958
simpler
marcoieni Mar 20, 2023
0963ebd
Merge branch 'main' into crate-report
marcoieni Mar 25, 2023
569abc5
unused import
marcoieni Apr 9, 2023
85a22b1
indent
marcoieni Apr 9, 2023
daa65ff
use Patch instead of None
marcoieni Apr 9, 2023
32a2411
fix
marcoieni Apr 9, 2023
0a5f85b
wip
marcoieni Apr 9, 2023
d40c975
comment
marcoieni Apr 9, 2023
8983c35
Merge branch 'main' into crate-report
obi1kenobi Apr 9, 2023
1f7c19d
Merge branch 'main' into crate-report
marcoieni Apr 25, 2023
f7be80a
introduce detected version
marcoieni Apr 25, 2023
7600401
add detected_bump fn
marcoieni Apr 25, 2023
ee853df
required bump is None
marcoieni Apr 25, 2023
ed546e4
comment
marcoieni Apr 25, 2023
4819566
comment
marcoieni Apr 25, 2023
6ed8621
comment
marcoieni Apr 25, 2023
dacbf3f
comment
marcoieni Apr 25, 2023
f81e17a
wip
marcoieni Apr 25, 2023
6cf3622
add tests
marcoieni Apr 26, 2023
08c2cde
add test output
marcoieni Apr 26, 2023
6fa8703
Merge branch 'main' into crate-report
obi1kenobi May 2, 2023
f18e295
Update test_crates/trait_missing_with_major_bump/new/Cargo.toml
marcoieni May 2, 2023
b55719f
Update test_crates/trait_missing_with_major_bump/old/Cargo.toml
marcoieni May 2, 2023
8920c8f
update test output
marcoieni May 2, 2023
54042b5
remove unuseful file
marcoieni May 3, 2023
dba13c0
Update src/lib.rs
marcoieni May 3, 2023
3a03294
Update src/lib.rs
marcoieni May 3, 2023
9c70539
Update src/lib.rs
marcoieni May 3, 2023
5018e90
comment
marcoieni May 3, 2023
cf47884
crate reports comment
marcoieni May 3, 2023
9cf72ee
add assert
marcoieni May 7, 2023
f24cac5
Merge branch 'main' into crate-report
marcoieni May 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/check_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use trustfall_rustdoc::{VersionedCrate, VersionedIndexedCrate, VersionedRustdocA

use crate::{
query::{ActualSemverUpdate, RequiredSemverUpdate, SemverQuery},
GlobalConfig, ReleaseType,
CrateReport, GlobalConfig, ReleaseType,
};

type QueryResultItem = BTreeMap<Arc<str>, FieldValue>;
Expand Down Expand Up @@ -84,7 +84,7 @@ pub(super) fn run_check_release(
current_crate: VersionedCrate,
baseline_crate: VersionedCrate,
release_type: Option<ReleaseType>,
) -> anyhow::Result<bool> {
) -> anyhow::Result<CrateReport> {
let current_version = current_crate.crate_version();
let baseline_version = baseline_crate.crate_version();

Expand Down Expand Up @@ -315,7 +315,7 @@ pub(super) fn run_check_release(
let message = config
.handlebars()
.render_template(template, &pretty_result)
.with_context(|| "Error instantiating semver query template.")
.context("Error instantiating semver query template.")
.expect("could not materialize template");
colored_ln(config.stdout(), |w| colored!(w, " {}", message,))
.expect("print failed");
Expand Down Expand Up @@ -349,9 +349,9 @@ pub(super) fn run_check_release(
}

let required_bump = if required_versions.contains(&RequiredSemverUpdate::Major) {
"major"
RequiredSemverUpdate::Major
} else if required_versions.contains(&RequiredSemverUpdate::Minor) {
"minor"
RequiredSemverUpdate::Minor
} else {
unreachable!("{:?}", required_versions)
};
Expand All @@ -362,7 +362,7 @@ pub(super) fn run_check_release(
format_args!(
"[{:>8.3}s] semver requires new {} version: {} major and {} minor checks failed",
total_duration.as_secs_f32(),
required_bump,
required_bump.as_str(),
required_versions.iter().filter(|x| *x == &RequiredSemverUpdate::Major).count(),
required_versions.iter().filter(|x| *x == &RequiredSemverUpdate::Minor).count(),
),
Expand All @@ -371,7 +371,10 @@ pub(super) fn run_check_release(
)
.expect("print failed");

Ok(false)
Ok(CrateReport {
required_bump: Some(required_bump.into()),
detected_bump: version_change,
})
} else {
config
.shell_print(
Expand All @@ -387,7 +390,10 @@ pub(super) fn run_check_release(
true,
)
.expect("print failed");
Ok(true)
Ok(CrateReport {
detected_bump: version_change,
required_bump: None,
})
}
}

Expand Down
113 changes: 90 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ use directories::ProjectDirs;
use check_release::run_check_release;
use trustfall_rustdoc::{load_rustdoc, VersionedCrate};

use itertools::Itertools;
use rustdoc_cmd::RustdocCommand;
use semver::Version;
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};
use std::path::{Path, PathBuf};

pub use config::GlobalConfig;
pub use query::{RequiredSemverUpdate, SemverQuery};
pub use query::{ActualSemverUpdate, RequiredSemverUpdate, SemverQuery};

/// Test a release for semver violations.
#[non_exhaustive]
Expand Down Expand Up @@ -320,7 +319,12 @@ impl Check {
let current_loader = self.get_rustdoc_generator(&mut config, &self.current.source)?;
let baseline_loader = self.get_rustdoc_generator(&mut config, &self.baseline.source)?;

let all_outcomes: Vec<anyhow::Result<bool>> = match &self.current.source {
// Create a report for each crate.
// We want to run all the checks, even if one returns `Err`.
let all_outcomes: Vec<anyhow::Result<(String, Option<CrateReport>)>> = match &self
.current
.source
{
obi1kenobi marked this conversation as resolved.
Show resolved Hide resolved
RustdocSource::Rustdoc(_)
| RustdocSource::Revision(_, _)
| RustdocSource::VersionFromRegistry(_) => {
Expand All @@ -337,26 +341,26 @@ impl Check {
ScopeMode::AllowList(lst) => lst.clone(),
};
names
.iter()
.into_iter()
.map(|name| {
let version = None;
let (current_crate, baseline_crate) = generate_versioned_crates(
&mut config,
&rustdoc_cmd,
&*current_loader,
&*baseline_loader,
name,
&name,
version,
)?;

let success = run_check_release(
let report = run_check_release(
&mut config,
name,
&name,
current_crate,
baseline_crate,
self.release_type,
)?;
Ok(success)
Ok((name, Some(report)))
})
.collect()
}
Expand All @@ -383,7 +387,7 @@ impl Check {
format_args!("{crate_name} v{version} (current)"),
)
})?;
Ok(true)
Ok((crate_name.clone(), None))
} else {
let (current_crate, baseline_crate) = generate_versioned_crates(
&mut config,
Expand All @@ -394,35 +398,98 @@ impl Check {
Some(version),
)?;

Ok(run_check_release(
&mut config,
crate_name,
current_crate,
baseline_crate,
self.release_type,
)?)
Ok((
crate_name.clone(),
Some(run_check_release(
&mut config,
crate_name,
current_crate,
baseline_crate,
self.release_type,
)?),
))
}
})
.collect()
}
};
let success = all_outcomes
.into_iter()
.fold_ok(true, std::ops::BitAnd::bitand)?;
let crate_reports: BTreeMap<String, CrateReport> = {
let mut reports = BTreeMap::new();
for outcome in all_outcomes {
let (name, outcome) = outcome?;
if let Some(outcome) = outcome {
reports.insert(name, outcome);
}
}
reports
};

Ok(Report { success })
Ok(Report { crate_reports })
}
}

/// Report of semver check of one crate.
#[non_exhaustive]
#[derive(Debug)]
pub struct CrateReport {
/// Bump between the current version and the baseline one.
detected_bump: ActualSemverUpdate,
/// Minimum bump required to respect semver.
/// For example, if the crate contains breaking changes, this is [`ReleaseType::Major`].
marcoieni marked this conversation as resolved.
Show resolved Hide resolved
/// If no bump is required, this is [`Option::None`].
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this accurate?

Say we ran a semver-check over a crate where the detected bump is ActualSemverUpdate::Major. Would required_bump actually be Some(ReleaseType::Major)?

If no bump is required, the value will be None, but is that the only time? I think there are other situations where the value might be None.

Let's back this up with some tests? Consider making a new integration test file in the tests/ directory to pin down the behavior here.

marcoieni marked this conversation as resolved.
Show resolved Hide resolved
required_bump: Option<ReleaseType>,
}

impl CrateReport {
/// Check if the semver check was successful.
/// `true` if required bump <= detected bump.
pub fn success(&self) -> bool {
match self.required_bump {
None => true,
Some(required_bump) => {
match self.detected_bump {
// If user bumped the major version, any breaking change is accepted.
ActualSemverUpdate::Major => true,
ActualSemverUpdate::Minor => {
matches!(required_bump, ReleaseType::Minor | ReleaseType::Patch)
}
ActualSemverUpdate::Patch | ActualSemverUpdate::NotChanged => {
required_bump == ReleaseType::Patch
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need updating for the changes to the semantics of the struct's fields? It seems to me like some of these could be assert instead of comparisons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I think it should never happen that the update of detected_bump is the same of required_bump, right?
Maybe we can add some warnings in this case. Or do you prefer to write an assert? Sounds scary 😅

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's assert. If our design behaves unexpectedly, it should fail loudly and quickly so we can figure out the failure mode and fix it.

I think required_bump should never be equal to or lesser than detected_bump in this design. If we're wrong about that, there's some serious issue we've overlooked and the outcome shouldn't be trusted anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the assert in 9cf72ee

}
}
}
}

/// Minimum bump required to respect semver.
/// It's [`Option::None`] if no bump is required.
marcoieni marked this conversation as resolved.
Show resolved Hide resolved
pub fn required_bump(&self) -> Option<ReleaseType> {
self.required_bump
}

/// Bump between the current version and the baseline one.
pub fn detected_bump(&self) -> ActualSemverUpdate {
self.detected_bump
}
}

/// Report of the whole analysis.
/// Contains a report for each crate checked.
#[non_exhaustive]
#[derive(Debug)]
pub struct Report {
success: bool,
/// Collection containing the name and the report of each crate checked.
crate_reports: BTreeMap<String, CrateReport>,
}

impl Report {
/// `true` if none of the crates violate semver.
pub fn success(&self) -> bool {
self.success
self.crate_reports.values().all(|report| report.success())
}

pub fn crate_reports(&self) -> &BTreeMap<String, CrateReport> {
obi1kenobi marked this conversation as resolved.
Show resolved Hide resolved
&self.crate_reports
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ impl RequiredSemverUpdate {
}
}

impl From<RequiredSemverUpdate> for ReleaseType {
fn from(value: RequiredSemverUpdate) -> Self {
match value {
RequiredSemverUpdate::Major => Self::Major,
RequiredSemverUpdate::Minor => Self::Minor,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ActualSemverUpdate {
pub enum ActualSemverUpdate {
obi1kenobi marked this conversation as resolved.
Show resolved Hide resolved
Major,
Minor,
Patch,
Expand Down
7 changes: 7 additions & 0 deletions test_crates/trait_missing_with_major_bump/new/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "trait_missing_with_major_bump"
version = "1.0.0"
edition = "2021"

[dependencies]
9 changes: 9 additions & 0 deletions test_crates/trait_missing_with_major_bump/new/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
trait AddedPrivateTrait {}

pub trait AddedPubTrait {}

// This trait stops being unsafe. It is not removed and should not be reported.
pub trait TraitStopsBeingUnsafe {}

// This trait becomes unsafe. It is not removed and should not be reported.
pub unsafe trait TraitBecomesUnsafe {}
7 changes: 7 additions & 0 deletions test_crates/trait_missing_with_major_bump/old/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
publish = false
name = "trait_missing_with_major_bump"
version = "2.0.0"
edition = "2021"

[dependencies]
18 changes: 18 additions & 0 deletions test_crates/trait_missing_with_major_bump/old/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pub trait RemovedTrait {}

pub unsafe trait RemovedUnsafeTrait {}

pub mod my_pub_mod {
pub trait PubUseRemovedTrait {}
}

pub use my_pub_mod::PubUseRemovedTrait;

// This trait stops being unsafe. It is not removed and should not be reported.
pub unsafe trait TraitStopsBeingUnsafe {}

// This trait becomes unsafe. It is not removed and should not be reported.
pub trait TraitBecomesUnsafe {}

// This trait is private. Its removal is not breaking and should not be reported.
trait PrivateTrait {}
43 changes: 43 additions & 0 deletions test_outputs/trait_missing.output.ron
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,49 @@
"visibility_limit": String("public"),
},
],
"./test_crates/trait_missing_with_major_bump/": [
{
"name": String("RemovedTrait"),
"path": List([
String("trait_missing_with_major_bump"),
String("RemovedTrait"),
]),
"span_begin_line": Uint64(1),
"span_filename": String("src/lib.rs"),
"visibility_limit": String("public"),
},
{
"name": String("RemovedUnsafeTrait"),
"path": List([
String("trait_missing_with_major_bump"),
String("RemovedUnsafeTrait"),
]),
"span_begin_line": Uint64(3),
"span_filename": String("src/lib.rs"),
"visibility_limit": String("public"),
},
{
"name": String("PubUseRemovedTrait"),
"path": List([
String("trait_missing_with_major_bump"),
String("my_pub_mod"),
String("PubUseRemovedTrait"),
]),
"span_begin_line": Uint64(6),
"span_filename": String("src/lib.rs"),
"visibility_limit": String("public"),
},
{
"name": String("PubUseRemovedTrait"),
"path": List([
String("trait_missing_with_major_bump"),
String("PubUseRemovedTrait"),
]),
"span_begin_line": Uint64(6),
"span_filename": String("src/lib.rs"),
"visibility_limit": String("public"),
},
],
"./test_crates/trait_unsafe_added/": [
{
"name": String("TraitBecomesPrivateAndUnsafe"),
Expand Down
Loading