-
-
Notifications
You must be signed in to change notification settings - Fork 14k
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
tests.nixpkgs-check-by-name: Implement gradual empty arg check migration #272395
Changes from 5 commits
b8e4d55
e98d228
a6ba4ca
d487a97
bb08bfc
53b43ce
413dd9c
79618ff
74e8b38
fc2d269
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
use crate::nixpkgs_problem::NixpkgsProblem; | ||
use crate::structure; | ||
use crate::validation::{self, Validation::Success}; | ||
use crate::Version; | ||
use crate::version; | ||
use std::path::Path; | ||
|
||
use anyhow::Context; | ||
|
@@ -39,11 +39,10 @@ enum AttributeVariant { | |
/// of the form `callPackage <package_file> { ... }`. | ||
/// See the `eval.nix` file for how this is achieved on the Nix side | ||
pub fn check_values( | ||
version: Version, | ||
nixpkgs_path: &Path, | ||
package_names: Vec<String>, | ||
eval_accessible_paths: Vec<&Path>, | ||
) -> validation::Result<()> { | ||
eval_accessible_paths: &Vec<&Path>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One "rusty" nit is to have the various things accepting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could even be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The |
||
) -> validation::Result<version::Nixpkgs> { | ||
// Write the list of packages we need to check into a temporary JSON file. | ||
// This can then get read by the Nix evaluation. | ||
let attrs_file = NamedTempFile::new().context("Failed to create a temporary file")?; | ||
|
@@ -110,54 +109,67 @@ pub fn check_values( | |
String::from_utf8_lossy(&result.stdout) | ||
))?; | ||
|
||
Ok(validation::sequence_(package_names.iter().map( | ||
|package_name| { | ||
Ok( | ||
validation::sequence(package_names.iter().map(|package_name| { | ||
infinisil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let relative_package_file = structure::relative_file_for_package(package_name); | ||
let absolute_package_file = nixpkgs_path.join(&relative_package_file); | ||
|
||
if let Some(attribute_info) = actual_files.get(package_name) { | ||
let valid = match &attribute_info.variant { | ||
AttributeVariant::AutoCalled => true, | ||
let check_result = if !attribute_info.is_derivation { | ||
NixpkgsProblem::NonDerivation { | ||
relative_package_file: relative_package_file.clone(), | ||
package_name: package_name.clone(), | ||
} | ||
.into() | ||
} else { | ||
Success(()) | ||
}; | ||
|
||
let check_result = check_result.and(match &attribute_info.variant { | ||
AttributeVariant::AutoCalled => Success(version::Attribute { | ||
empty_non_auto_called: version::EmptyNonAutoCalled::Valid, | ||
}), | ||
AttributeVariant::CallPackage { path, empty_arg } => { | ||
let correct_file = if let Some(call_package_path) = path { | ||
absolute_package_file == *call_package_path | ||
} else { | ||
false | ||
}; | ||
// Only check for the argument to be non-empty if the version is V1 or | ||
// higher | ||
let non_empty = if version >= Version::V1 { | ||
!empty_arg | ||
} else { | ||
true | ||
}; | ||
correct_file && non_empty | ||
} | ||
AttributeVariant::Other => false, | ||
}; | ||
|
||
if !valid { | ||
NixpkgsProblem::WrongCallPackage { | ||
relative_package_file: relative_package_file.clone(), | ||
package_name: package_name.clone(), | ||
if correct_file { | ||
Success(version::Attribute { | ||
empty_non_auto_called: if *empty_arg { | ||
version::EmptyNonAutoCalled::Invalid | ||
} else { | ||
version::EmptyNonAutoCalled::Valid | ||
}, | ||
}) | ||
} else { | ||
NixpkgsProblem::WrongCallPackage { | ||
relative_package_file: relative_package_file.clone(), | ||
package_name: package_name.clone(), | ||
} | ||
.into() | ||
} | ||
} | ||
.into() | ||
} else if !attribute_info.is_derivation { | ||
NixpkgsProblem::NonDerivation { | ||
AttributeVariant::Other => NixpkgsProblem::WrongCallPackage { | ||
relative_package_file: relative_package_file.clone(), | ||
package_name: package_name.clone(), | ||
} | ||
.into() | ||
} else { | ||
Success(()) | ||
} | ||
.into(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current organization isn't bad at all and I would definitely accept it. You could make a method on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I would also lean towards splitting this up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like an interesting idea, though I'd like to defer that to a future PR, since it will touch a bunch more parts of the code. |
||
}); | ||
|
||
check_result.map(|value| (package_name.clone(), value)) | ||
} else { | ||
NixpkgsProblem::UndefinedAttr { | ||
relative_package_file: relative_package_file.clone(), | ||
package_name: package_name.clone(), | ||
} | ||
.into() | ||
} | ||
}, | ||
))) | ||
})) | ||
.map(|elems| version::Nixpkgs { | ||
attributes: elems.into_iter().collect(), | ||
}), | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,14 @@ mod references; | |
mod structure; | ||
mod utils; | ||
mod validation; | ||
mod version; | ||
|
||
use crate::structure::check_structure; | ||
use crate::validation::Validation::Failure; | ||
use crate::validation::Validation::Success; | ||
use crate::version::Nixpkgs; | ||
use anyhow::Context; | ||
use clap::{Parser, ValueEnum}; | ||
use clap::Parser; | ||
use colored::Colorize; | ||
use std::io; | ||
use std::path::{Path, PathBuf}; | ||
|
@@ -21,25 +23,20 @@ use std::process::ExitCode; | |
pub struct Args { | ||
/// Path to nixpkgs | ||
nixpkgs: PathBuf, | ||
/// The version of the checks | ||
/// Increasing this may cause failures for a Nixpkgs that succeeded before | ||
/// TODO: Remove default once Nixpkgs CI passes this argument | ||
#[arg(long, value_enum, default_value_t = Version::V0)] | ||
version: Version, | ||
} | ||
|
||
/// The version of the checks | ||
#[derive(Debug, Clone, PartialEq, PartialOrd, ValueEnum)] | ||
pub enum Version { | ||
/// Initial version | ||
V0, | ||
/// Empty argument check | ||
V1, | ||
/// Path to the base Nixpkgs to compare against | ||
#[arg(long)] | ||
base: Option<PathBuf>, | ||
} | ||
|
||
fn main() -> ExitCode { | ||
let args = Args::parse(); | ||
match check_nixpkgs(&args.nixpkgs, args.version, vec![], &mut io::stderr()) { | ||
match process( | ||
args.base.as_deref(), | ||
&args.nixpkgs, | ||
&vec![], | ||
&mut io::stderr(), | ||
) { | ||
Ok(true) => { | ||
eprintln!("{}", "Validated successfully".green()); | ||
ExitCode::SUCCESS | ||
|
@@ -55,10 +52,11 @@ fn main() -> ExitCode { | |
} | ||
} | ||
|
||
/// Checks whether the pkgs/by-name structure in Nixpkgs is valid. | ||
/// Does the actual work. This is the abstraction used both by `main` and the tests. | ||
/// | ||
/// # Arguments | ||
/// - `nixpkgs_path`: The path to the Nixpkgs to check | ||
/// - `base_nixpkgs`: The path to the base Nixpkgs to compare against | ||
/// - `main_nixpkgs`: The path to the main Nixpkgs to check | ||
/// - `eval_accessible_paths`: | ||
/// Extra paths that need to be accessible to evaluate Nixpkgs using `restrict-eval`. | ||
/// This is used to allow the tests to access the mock-nixpkgs.nix file | ||
|
@@ -68,33 +66,25 @@ fn main() -> ExitCode { | |
/// - `Err(e)` if an I/O-related error `e` occurred. | ||
/// - `Ok(false)` if there are problems, all of which will be written to `error_writer`. | ||
/// - `Ok(true)` if there are no problems | ||
pub fn check_nixpkgs<W: io::Write>( | ||
nixpkgs_path: &Path, | ||
version: Version, | ||
eval_accessible_paths: Vec<&Path>, | ||
pub fn process<W: io::Write>( | ||
base_nixpkgs: Option<&Path>, | ||
main_nixpkgs: &Path, | ||
eval_accessible_paths: &Vec<&Path>, | ||
infinisil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
error_writer: &mut W, | ||
) -> anyhow::Result<bool> { | ||
let nixpkgs_path = nixpkgs_path.canonicalize().context(format!( | ||
"Nixpkgs path {} could not be resolved", | ||
nixpkgs_path.display() | ||
))?; | ||
|
||
let check_result = if !nixpkgs_path.join(utils::BASE_SUBPATH).exists() { | ||
eprintln!( | ||
"Given Nixpkgs path does not contain a {} subdirectory, no check necessary.", | ||
utils::BASE_SUBPATH | ||
); | ||
Success(()) | ||
} else { | ||
match check_structure(&nixpkgs_path)? { | ||
Failure(errors) => Failure(errors), | ||
Success(package_names) => | ||
// Only if we could successfully parse the structure, we do the evaluation checks | ||
{ | ||
eval::check_values(version, &nixpkgs_path, package_names, eval_accessible_paths)? | ||
} | ||
let main_result = check_nixpkgs(main_nixpkgs, eval_accessible_paths)?; | ||
let check_result = main_result.result_map(|nixpkgs_version| { | ||
if let Some(base) = base_nixpkgs { | ||
check_nixpkgs(base, eval_accessible_paths)?.result_map(|base_nixpkgs_version| { | ||
Ok(Nixpkgs::compare(base_nixpkgs_version, nixpkgs_version)) | ||
}) | ||
} else { | ||
Ok(Nixpkgs::compare( | ||
version::Nixpkgs::default(), | ||
nixpkgs_version, | ||
)) | ||
} | ||
}; | ||
})?; | ||
|
||
match check_result { | ||
Failure(errors) => { | ||
|
@@ -103,15 +93,40 @@ pub fn check_nixpkgs<W: io::Write>( | |
} | ||
Ok(false) | ||
} | ||
Success(_) => Ok(true), | ||
Success(()) => Ok(true), | ||
} | ||
} | ||
|
||
/// Checks whether the pkgs/by-name structure in Nixpkgs is valid, | ||
/// and returns to which degree it's valid for checks with increased strictness. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what this sentence is trying to communicate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now updated, hope this helps: 413dd9c#diff-fc467c6e2889ff0305612175c09cd06401123d3d5e8b6cfd1e85bec22586197eL102-R104 |
||
pub fn check_nixpkgs( | ||
nixpkgs_path: &Path, | ||
eval_accessible_paths: &Vec<&Path>, | ||
infinisil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> validation::Result<version::Nixpkgs> { | ||
Ok({ | ||
let nixpkgs_path = nixpkgs_path.canonicalize().context(format!( | ||
"Nixpkgs path {} could not be resolved", | ||
nixpkgs_path.display() | ||
))?; | ||
|
||
if !nixpkgs_path.join(utils::BASE_SUBPATH).exists() { | ||
eprintln!( | ||
"Given Nixpkgs path does not contain a {} subdirectory, no check necessary.", | ||
utils::BASE_SUBPATH | ||
); | ||
infinisil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Success(version::Nixpkgs::default()) | ||
} else { | ||
check_structure(&nixpkgs_path)?.result_map(|package_names| | ||
// Only if we could successfully parse the structure, we do the evaluation checks | ||
eval::check_values(&nixpkgs_path, package_names, eval_accessible_paths))? | ||
} | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::check_nixpkgs; | ||
use crate::process; | ||
use crate::utils; | ||
use crate::Version; | ||
use anyhow::Context; | ||
use std::fs; | ||
use std::path::Path; | ||
|
@@ -197,10 +212,17 @@ mod tests { | |
fn test_nixpkgs(name: &str, path: &Path, expected_errors: &str) -> anyhow::Result<()> { | ||
let extra_nix_path = Path::new("tests/mock-nixpkgs.nix"); | ||
|
||
let base_path = path.join("base"); | ||
let base_nixpkgs = if base_path.exists() { | ||
Some(base_path.as_path()) | ||
} else { | ||
None | ||
}; | ||
|
||
// We don't want coloring to mess up the tests | ||
let writer = temp_env::with_var("NO_COLOR", Some("1"), || -> anyhow::Result<_> { | ||
let mut writer = vec![]; | ||
check_nixpkgs(&path, Version::V1, vec![&extra_nix_path], &mut writer) | ||
process(base_nixpkgs, &path, &vec![&extra_nix_path], &mut writer) | ||
.context(format!("Failed test case {name}"))?; | ||
Ok(writer) | ||
})?; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If not specified, ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Also had to fix the behavior in that case (though it doesn't matter much): 53b43ce