#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use collection_tools :: collection;
use table :: *;
use std ::
{
fmt,
sync,
};
use colored ::Colorize as _;
use process_tools ::process :: *;
use error ::
{
Error,
untyped ::format_err,
};
use crate ::entity ::package ::PackageName;
use std ::result ::Result :: { self, Ok, Err };
#[ derive( Debug, Error ) ]
pub enum TestError
{
#[ error( "Common error: {0}" ) ]
Common( #[ from ] error ::untyped ::Error ),
#[ error( "Path error: {0}" ) ]
Path( #[ from ] PathError ),
}
#[ derive( Debug, Clone, Eq, PartialEq, Ord, PartialOrd, former ::Former ) ]
pub struct TestVariant
{
channel: channel ::Channel,
optimization: optimization ::Optimization,
features: collection ::BTreeSet< String >,
}
impl fmt ::Display for TestVariant
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> fmt ::Result
{
let features = if self.features.is_empty() { " ".to_string() } else { self.features.iter().join( " " ) };
writeln!( f, "{} {} {}", self.optimization, self.channel, features )?;
Ok( () )
}
}
#[ derive( Debug ) ]
pub struct TestPlan
{
packages_plan: Vec< TestPackagePlan >,
}
impl fmt ::Display for TestPlan
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
writeln!( f, "Plan: " )?;
for plan in &self.packages_plan
{
writeln!( f, "{plan}" )?;
}
Ok( () )
}
}
impl TestPlan
{
#[ allow( clippy ::needless_pass_by_value, clippy ::too_many_arguments ) ]
pub fn try_from< 'a >
(
packages: impl core ::iter ::Iterator< Item = WorkspacePackageRef< 'a > >,
channels: &collection ::HashSet< channel ::Channel >,
power: u32,
include_features: Vec< String >,
exclude_features: Vec< String >,
optimizations: &collection ::HashSet< optimization ::Optimization >,
enabled_features: Vec< String >,
with_all_features: bool,
with_none_features: bool,
variants_cap: u32,
)
-> Result< Self, TestError >
{
let mut packages_plan = vec![];
for package in packages
{
packages_plan.push
(
TestPackagePlan ::try_from
(
package,
channels,
power,
include_features.as_slice(),
exclude_features.as_slice(),
optimizations,
enabled_features.as_slice(), with_all_features, with_none_features, variants_cap
)?
);
}
Ok
(
Self
{
packages_plan
}
)
}
}
#[ derive( Debug ) ]
pub struct TestPackagePlan
{
enabled_features: collection ::BTreeSet< String >,
crate_dir: CrateDir,
test_variants: collection ::BTreeSet< TestVariant >,
}
impl fmt ::Display for TestPackagePlan
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
writeln!( f, "Package: {}\nThe tests will be executed using the following configurations: ", self.crate_dir.clone().absolute_path() )?;
let mut all_features = collection ::BTreeSet ::new();
for variant in &self.test_variants
{
let features = variant.features.iter().cloned();
if features.len() == 0
{
all_features.extend( [ "[]".to_string() ] );
}
all_features.extend( features );
}
let mut ff: Vec< _ > = self.enabled_features.iter().cloned().collect();
for feature in all_features
{
if !ff.contains( &feature )
{
ff.push( feature );
}
}
let mut table = Table ::default();
let mut header_row = Row ::new();
header_row.add_cell( "Channel" );
header_row.add_cell( "Opt" );
for feature in &ff
{
header_row.add_cell( feature );
}
table.set_header( header_row );
for variant in &self.test_variants
{
let mut row = Row ::new();
row.add_cell( &variant.channel.to_string() );
row.add_cell( &variant.optimization.to_string() );
let counter = 0;
let flag = true;
generate_features_cells( &mut ff, variant, &mut row, counter, flag, &self.enabled_features );
table.add_row( row );
}
writeln!( f, "{table}" )?;
Ok( () )
}
}
impl TestPackagePlan
{
#[ allow( clippy ::too_many_arguments ) ]
fn try_from
(
package: WorkspacePackageRef< '_ >,
channels: &collection ::HashSet< channel ::Channel >,
power: u32,
include_features: &[ String ],
exclude_features: &[ String ],
optimizations: &collection ::HashSet< optimization ::Optimization >,
enabled_features: &[ String ],
with_all_features: bool,
with_none_features: bool,
variants_cap: u32,
)
-> Result< Self, TestError >
{
let crate_dir = package.crate_dir()?;
let mut test_variants = collection ::BTreeSet ::new();
let features_powerset = features ::features_powerset
(
package,
power as usize,
exclude_features,
include_features,
enabled_features,
with_all_features,
with_none_features,
variants_cap,
)?;
for optimization in optimizations
{
for channel in channels
{
for feature in &features_powerset
{
test_variants.insert
(
TestVariant
{
channel: *channel,
optimization: *optimization,
features: feature.clone(),
}
);
}
}
}
Ok
(
Self
{
enabled_features: enabled_features.iter().cloned().collect(),
crate_dir,
test_variants,
}
)
}
}
fn generate_features_cells
(
ff: &mut Vec< String >,
variant: &TestVariant,
row: &mut Row,
mut counter: usize,
mut flag: bool,
enabled_features: &collection ::BTreeSet< String >
)
{
for feature in ff
{
let mut c = "+";
if variant.features.is_empty() && counter == enabled_features.len() && flag
{
flag = false;
row.add_cell( c );
}
else if variant.features.contains( feature )
{
row.add_cell( c );
}
else
{
c = "";
row.add_cell( c );
}
counter += 1;
}
}
#[ derive( Debug, former ::Former ) ]
pub struct PackageTestOptions< 'a >
{
temp_path: Option< path ::PathBuf >,
plan: &'a TestPackagePlan,
dry: bool,
#[ cfg( feature = "progress_bar" ) ]
with_progress: bool,
#[ cfg( feature = "progress_bar" ) ]
progress_bar: progress_bar ::ProgressBar< 'a >
}
impl PackageTestOptionsFormer< '_ >
{
pub fn option_temp( mut self, value: impl Into< Option< path ::PathBuf > > ) -> Self
{
self.storage.temp_path = value.into();
self
}
}
#[ derive( Debug, former ::Former, Clone ) ]
#[ allow( clippy ::struct_excessive_bools ) ]
pub struct SingleTestOptions
{
channel: channel ::Channel,
optimization: optimization ::Optimization,
#[ former( default = true ) ]
with_default_features: bool,
#[ former( default = false ) ]
with_all_features: bool,
enable_features: collection ::BTreeSet< String >,
temp_directory_path: Option< path ::PathBuf >,
dry: bool,
#[ former( default = true ) ]
backtrace: bool,
}
impl SingleTestOptions
{
fn as_rustup_args( &self ) -> Vec< String >
{
debug_assert!( !self.with_default_features ); debug_assert!( !self.with_all_features ); [ "run".into(), self.channel.to_string(), "cargo".into(), "test".into() ]
.into_iter()
.chain( if self.optimization == optimization ::Optimization ::Release { Some( "--release".into() ) } else { None } )
.chain( if self.with_default_features { None } else { Some( "--no-default-features".into() ) } )
.chain( if self.with_all_features { Some( "--all-features".into() ) } else { None } )
.chain( if self.enable_features.is_empty() { None }
else
{
Some( [ "--features".into(), self.enable_features.iter().join( "," ) ] )
}.into_iter().flatten() )
.chain( self.temp_directory_path.clone().map( | p | vec![ "--target-dir".to_string(), p.to_string_lossy().into() ] ).into_iter().flatten() )
.collect()
}
}
#[ allow( clippy ::needless_pass_by_value ) ]
pub fn run_rec< P >( path: P, options: SingleTestOptions ) -> Result< Report, Report >
where
P: AsRef< path ::Path >
{
let ( program, args ) = ( "rustup", options.as_rustup_args() );
if options.dry
{
Ok
(
Report
{
command: format!( "{program} {}", args.join( " " ) ),
out: String ::new(),
err: String ::new(),
current_path: path.as_ref().to_path_buf(),
error: Ok( () ),
}
)
}
else
{
let envs: std ::collections ::HashMap< String, String > = if options.backtrace
{
[ ( "RUST_BACKTRACE".to_string(), "full".to_string() ) ].into_iter().collect()
}
else { std ::collections ::HashMap ::new() };
Run ::former()
.bin_path( program )
.args( args.into_iter().map( std ::ffi ::OsString ::from ).collect :: < Vec< _ > >() )
.current_path( path.as_ref().to_path_buf() )
.joining_streams( true )
.env_variable( envs )
.run()
}
}
#[ derive( former ::Former ) ]
pub struct TestOptions
{
pub plan: TestPlan,
pub concurrent: u32,
pub temp_path: Option< path ::PathBuf >,
pub dry: bool,
pub with_progress: bool,
}
#[ allow( clippy ::missing_fields_in_debug ) ]
impl fmt ::Debug for TestOptions
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
f.debug_struct( "TestOptions" )
.field( "plan", &self.plan)
.field( "concurrent", &self.concurrent)
.field( "temp_path", &self.temp_path)
.field( "plan", &self.plan)
.finish()
}
}
impl TestOptionsFormer
{
pub fn option_temp( mut self, value: impl Into< Option< path ::PathBuf > > ) -> Self
{
self.storage.temp_path = value.into();
self
}
}
#[ derive( Debug, Default, Clone ) ]
pub struct TestReport
{
pub dry: bool,
pub package_name: PackageName,
pub tests: collection ::BTreeMap< TestVariant, Result< Report, Report > >,
pub enabled_features: collection ::BTreeSet< String >,
}
impl fmt ::Display for TestReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
if self.dry
{
return Ok( () )
}
let mut failed = 0;
let mut success = 0;
let mut all_features = collection ::BTreeSet ::new();
for variant in self.tests.keys()
{
let features = variant.features.iter().cloned();
if features.len() == 0
{
all_features.extend( [ "[]".to_string() ] );
}
all_features.extend( features );
}
let mut ff: Vec< _ > = self.enabled_features.iter().cloned().collect();
for feature in all_features
{
if !ff.contains( &feature )
{
ff.push( feature );
}
}
let mut table = Table ::default();
let mut header_row = Row ::new();
header_row.add_cell( "Result" );
header_row.add_cell( "Channel" );
header_row.add_cell( "Opt" );
for feature in &ff
{
header_row.add_cell( feature );
}
table.set_header( header_row );
writeln!( f, "{} {}\n", "\n=== Module".bold(), self.package_name.bold() )?;
if self.tests.is_empty()
{
writeln!( f, "unlucky" )?;
return Ok( () );
}
for ( variant, result) in &self.tests
{
let mut row = Row ::new();
let result_text = match result
{
Ok( _ ) =>
{
success += 1;
"✅"
},
Err( report ) =>
{
failed += 1;
let mut out = report.out.replace( '\n', "\n " );
out.push( '\n' );
write!( f, " ❌ > {}\n\n{out}", report.command )?;
"❌"
},
};
row.add_cell( result_text );
row.add_cell( &variant.channel.to_string() );
row.add_cell( &variant.optimization.to_string() );
let counter = 0;
let flag = true;
generate_features_cells( &mut ff, variant, &mut row, counter, flag, &self.enabled_features );
table.add_row( row );
}
writeln!( f, "{table}" )?;
writeln!( f, " {}", generate_summary_message( failed, success ) )?;
Ok( () )
}
}
fn generate_summary_message( failed: i32, success: i32 ) -> String
{
if success == failed + success
{
format!( "✅ All passed {success} / {}", failed + success )
}
else
{
format!( "❌ Not all passed {success} / {}", failed + success )
}
}
#[ derive( Debug, Default, Clone ) ]
pub struct TestsReport
{
pub dry: bool,
pub success_reports: Vec< TestReport >,
pub failure_reports: Vec< TestReport >,
}
impl fmt ::Display for TestsReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
if self.success_reports.is_empty() && self.failure_reports.is_empty()
{
writeln!( f, "The tests have not been run." )?;
return Ok( () );
}
if !self.success_reports.is_empty()
{
writeln!( f, "Successful: " )?;
for report in &self.success_reports
{
writeln!( f, "{report}" )?;
}
}
if !self.failure_reports.is_empty()
{
writeln!( f, "Failure: " )?;
for report in &self.failure_reports
{
writeln!( f, "{report}" )?;
}
}
writeln!( f, "Global report" )?;
#[ allow( clippy ::cast_possible_wrap, clippy ::cast_possible_truncation ) ]
writeln!( f, " {}", generate_summary_message( self.failure_reports.len() as i32, self.success_reports.len() as i32 ) )?;
Ok( () )
}
}
pub fn run( options: &PackageTestOptions< '_ > )
-> ResultWithReport< TestReport, TestError >
{
let report = TestReport { dry: options.dry, enabled_features: options.plan.enabled_features.clone(), ..Default ::default() };
let report = sync ::Arc ::new( sync ::Mutex ::new( report ) );
let crate_dir = options.plan.crate_dir.clone();
rayon ::scope
(
| s |
{
for variant in &options.plan.test_variants
{
let TestVariant{ channel, optimization, features } = variant;
let r = report.clone();
let crate_dir = crate_dir.clone();
s.spawn
(
move | _ |
{
let mut args_t = SingleTestOptions ::former()
.channel( *channel )
.optimization( *optimization )
.with_default_features( false )
.enable_features( features.clone() )
.dry( options.dry );
if let Some( p ) = options.temp_path.clone()
{
let path = p.join( path ::unique_folder_name().unwrap() );
std ::fs ::create_dir_all( &path ).unwrap();
args_t = args_t.temp_directory_path( path );
}
#[ cfg( feature = "progress_bar" ) ]
if options.with_progress
{
let _s =
{
let s = options.progress_bar.multi_progress.add( indicatif ::ProgressBar ::new_spinner().with_message( format!( "{variant}" ) ) );
s.enable_steady_tick( std ::time ::Duration ::from_millis( 100 ) );
s
};
}
let args = args_t.form();
let temp_dir = args.temp_directory_path.clone();
let cmd_rep = run_rec( crate_dir, args );
r.lock().unwrap().tests.insert( variant.clone(), cmd_rep );
#[ cfg( feature = "progress_bar" ) ]
if options.with_progress
{
options.progress_bar.progress_bar.inc( 1 );
}
if let Some( path ) = temp_dir
{
std ::fs ::remove_dir_all( path ).unwrap();
}
}
);
}
}
);
let report = sync ::Mutex ::into_inner( sync ::Arc ::into_inner( report ).unwrap() ).unwrap();
let at_least_one_failed = report
.tests
.iter()
.any( | ( _, result ) | result.is_err() );
if at_least_one_failed
{ Err( ( report, format_err!( "Some tests was failed" ).into() ) ) } else { Ok( report ) }
}
pub fn tests_run( args: &TestOptions )
-> ResultWithReport< TestsReport, TestError >
{
#[ cfg( feature = "progress_bar" ) ]
let multi_progress = progress_bar ::MultiProgress ::default();
#[ cfg( feature = "progress_bar" ) ]
let mm = &multi_progress;
let report = TestsReport { dry: args.dry, ..Default ::default() };
let report = sync ::Arc ::new( sync ::Mutex ::new( report ) );
let pool = rayon ::ThreadPoolBuilder ::new().use_current_thread().num_threads( args.concurrent as usize ).build().unwrap();
pool.scope
(
| s |
{
for plan in &args.plan.packages_plan
{
let report = report.clone();
s.spawn
(
move | _ |
{
let test_package_options = PackageTestOptions ::former()
.option_temp( args.temp_path.clone() )
.plan( plan )
.dry( args.dry );
#[ cfg( feature = "progress_bar" ) ]
let test_package_options = test_package_options.with_progress( args.with_progress );
#[ cfg( feature = "progress_bar" ) ]
let test_package_options =
{
test_package_options.progress_bar( mm.progress_bar( plan.test_variants.len() as u64 ) )
};
let options = test_package_options.form();
match run( &options )
{
Ok( r ) =>
{
report.lock().unwrap().success_reports.push( r );
}
Err( ( r, _ ) ) =>
{
report.lock().unwrap().failure_reports.push( r );
}
}
}
);
}
}
);
let report = sync ::Arc ::into_inner( report ).unwrap().into_inner().unwrap();
if report.failure_reports.is_empty()
{
Ok( report )
}
else
{
Err( ( report, format_err!( "Some tests was failed" ).into() ) )
}
}
}
crate ::mod_interface!
{
own use SingleTestOptions;
own use TestVariant;
own use run_rec;
own use TestPlan;
own use TestOptions;
own use TestReport;
own use TestsReport;
own use run;
own use tests_run;
}