Auto merge of #139845 - Zalathar:rollup-u5u5y1v, r=Zalathar
Rollup of 17 pull requests Successful merges: - #138374 (Enable contracts for const functions) - #138380 (ci: add runners for vanilla LLVM 20) - #138393 (Allow const patterns of matches to contain pattern types) - #139517 (std: sys: process: uefi: Use NULL stdin by default) - #139554 (std: add Output::exit_ok) - #139660 (compiletest: Add an experimental new executor to replace libtest) - #139669 (Overhaul `AssocItem`) - #139671 (Proc macro span API redesign: Replace proc_macro::SourceFile by Span::{file, local_file}) - #139750 (std/thread: Use default stack size from menuconfig for NuttX) - #139772 (Remove `hir::Map`) - #139785 (Let CStrings be either 1 or 2 byte aligned.) - #139789 (do not unnecessarily leak auto traits in item bounds) - #139791 (drop global where-bounds before merging candidates) - #139798 (normalize: prefer `ParamEnv` over `AliasBound` candidates) - #139822 (Fix: Map EOPNOTSUPP to ErrorKind::Unsupported on Unix) - #139833 (Fix some HIR pretty-printing problems) - #139836 (Basic tests of MPMC receiver cloning) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
f433fa46b0
173 changed files with 2499 additions and 994 deletions
|
|
@ -111,8 +111,8 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
|
|||
// Only suggest if `clone_from`/`clone_into` is explicitly implemented
|
||||
&& resolved_assoc_items.in_definition_order().any(|assoc|
|
||||
match which_trait {
|
||||
CloneTrait::Clone => assoc.name == sym::clone_from,
|
||||
CloneTrait::ToOwned => assoc.name.as_str() == "clone_into",
|
||||
CloneTrait::Clone => assoc.name() == sym::clone_from,
|
||||
CloneTrait::ToOwned => assoc.name().as_str() == "clone_into",
|
||||
}
|
||||
)
|
||||
&& !clone_source_borrows_from_dest(cx, lhs, rhs.span)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -
|
|||
cx.tcx.associated_items(trait_id).find_by_ident_and_kind(
|
||||
cx.tcx,
|
||||
Ident::from_str("Output"),
|
||||
ty::AssocKind::Type,
|
||||
ty::AssocTag::Type,
|
||||
trait_id,
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
|
|||
// a `Target` that is in `self.ty_msrv_map`.
|
||||
if let Some(deref_trait_id) = self.cx.tcx.lang_items().deref_trait()
|
||||
&& implements_trait(self.cx, ty, deref_trait_id, &[])
|
||||
&& let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, "Target")
|
||||
&& let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, sym::Target)
|
||||
&& let Some(msrv) = self.ty_msrv_map.get(&target_ty)
|
||||
&& msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
|
|||
assocs
|
||||
.filter_by_name_unhygienic(constraint.ident.name)
|
||||
.next()
|
||||
.is_some_and(|assoc| assoc.kind == ty::AssocKind::Type)
|
||||
.is_some_and(|assoc| assoc.is_type())
|
||||
})
|
||||
{
|
||||
emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use rustc_hir::{
|
|||
QPath, TraitItemRef, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
|
||||
use rustc_middle::ty::{self, FnSig, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::sym;
|
||||
|
|
@ -288,8 +288,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden
|
|||
.items()
|
||||
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(is_empty))
|
||||
.any(|i| {
|
||||
i.kind == AssocKind::Fn
|
||||
&& i.fn_has_self_parameter
|
||||
i.is_method()
|
||||
&& cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1
|
||||
});
|
||||
|
||||
|
|
@ -466,7 +465,7 @@ fn check_for_is_empty(
|
|||
.inherent_impls(impl_ty)
|
||||
.iter()
|
||||
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
|
||||
.find(|item| item.kind == AssocKind::Fn);
|
||||
.find(|item| item.is_fn());
|
||||
|
||||
let (msg, is_empty_span, self_kind) = match is_empty {
|
||||
None => (
|
||||
|
|
@ -486,7 +485,7 @@ fn check_for_is_empty(
|
|||
None,
|
||||
),
|
||||
Some(is_empty)
|
||||
if !(is_empty.fn_has_self_parameter
|
||||
if !(is_empty.is_method()
|
||||
&& check_is_empty_sig(
|
||||
cx,
|
||||
cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
|
||||
|
|
@ -608,7 +607,7 @@ fn is_empty_array(expr: &Expr<'_>) -> bool {
|
|||
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
/// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
|
||||
fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
|
||||
if item.kind == AssocKind::Fn {
|
||||
if item.is_fn() {
|
||||
let sig = cx.tcx.fn_sig(item.def_id).skip_binder();
|
||||
let ty = sig.skip_binder();
|
||||
ty.inputs().len() == 1
|
||||
|
|
@ -644,7 +643,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
&& cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| {
|
||||
implements_trait(cx, ty, deref_id, &[])
|
||||
&& cx
|
||||
.get_associated_type(ty, deref_id, "Target")
|
||||
.get_associated_type(ty, deref_id, sym::Target)
|
||||
.is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1))
|
||||
}))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
|
|||
&& let Ok(Some(fn_def)) = Instance::try_resolve(cx.tcx, cx.typing_env(), id, args)
|
||||
// find the provided definition of Iterator::last
|
||||
&& let Some(item) = cx.tcx.get_diagnostic_item(sym::Iterator)
|
||||
&& let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name.as_str() == "last")
|
||||
&& let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name().as_str() == "last")
|
||||
// if the resolved method is the same as the provided definition
|
||||
&& fn_def.def_id() == last_def.def_id
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ pub(super) fn check<'tcx>(
|
|||
&& let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id)
|
||||
&& cx.tcx.trait_of_item(method_id) == Some(iter_id)
|
||||
&& let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv)
|
||||
&& let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, "Item")
|
||||
&& let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, sym::Item)
|
||||
&& matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty))
|
||||
{
|
||||
if needs_into_iter
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use rustc_hir::{
|
|||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, AssocKind, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
|
||||
use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -
|
|||
&& let Some(iter_item) = cx.tcx.associated_items(iter_trait).find_by_ident_and_kind(
|
||||
cx.tcx,
|
||||
Ident::with_dummy_span(sym::Item),
|
||||
AssocKind::Type,
|
||||
AssocTag::Type,
|
||||
iter_trait,
|
||||
)
|
||||
&& let args = cx.tcx.mk_args(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))])
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ pub(super) fn check<'tcx>(
|
|||
.iter()
|
||||
.flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg))
|
||||
.find_map(|assoc| {
|
||||
if assoc.fn_has_self_parameter
|
||||
if assoc.is_method()
|
||||
&& cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1
|
||||
{
|
||||
Some(assoc.def_id)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ pub fn check_for_loop_iter(
|
|||
&& let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
|
||||
&& let collection_ty = cx.typeck_results().expr_ty(collection)
|
||||
&& implements_trait(cx, collection_ty, into_iterator_trait_id, &[])
|
||||
&& let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, "Item")
|
||||
&& let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item)
|
||||
&& iter_item_ty == into_iter_item_ty
|
||||
&& let Some(collection_snippet) = collection.span.get_source_text(cx)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ fn check_addr_of_expr(
|
|||
}
|
||||
if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
|
||||
&& implements_trait(cx, receiver_ty, deref_trait_id, &[])
|
||||
&& cx.get_associated_type(receiver_ty, deref_trait_id, "Target") == Some(target_ty)
|
||||
&& cx.get_associated_type(receiver_ty, deref_trait_id, sym::Target) == Some(target_ty)
|
||||
// Make sure that it's actually calling the right `.to_string()`, (#10033)
|
||||
// *or* this is a `Cow::into_owned()` call (which would be the wrong into_owned receiver (str != Cow)
|
||||
// but that's ok for Cow::into_owned specifically)
|
||||
|
|
@ -322,7 +322,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
|
|||
// add `.as_ref()` to the suggestion.
|
||||
let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
|
||||
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
|
||||
&& cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
|
||||
&& cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, sym::Target)
|
||||
!= Some(cx.tcx.types.str_)
|
||||
{
|
||||
".as_ref()"
|
||||
|
|
@ -648,7 +648,7 @@ fn is_to_string_on_string_like<'a>(
|
|||
&& let GenericArgKind::Type(ty) = generic_arg.unpack()
|
||||
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
|
||||
&& let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
|
||||
&& (cx.get_associated_type(ty, deref_trait_id, "Target") == Some(cx.tcx.types.str_)
|
||||
&& (cx.get_associated_type(ty, deref_trait_id, sym::Target) == Some(cx.tcx.types.str_)
|
||||
|| implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()]))
|
||||
{
|
||||
true
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
|
|||
cx,
|
||||
MISSING_TRAIT_METHODS,
|
||||
cx.tcx.def_span(item.owner_id),
|
||||
format!("missing trait method provided by default: `{}`", assoc.name),
|
||||
format!("missing trait method provided by default: `{}`", assoc.name()),
|
||||
|diag| {
|
||||
diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
|
|||
.associated_items(trait_def_id)
|
||||
.in_definition_order()
|
||||
.any(|assoc_item| {
|
||||
if assoc_item.fn_has_self_parameter {
|
||||
if assoc_item.is_method() {
|
||||
let self_ty = cx
|
||||
.tcx
|
||||
.fn_sig(assoc_item.def_id)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use rustc_data_structures::fx::FxHashMap;
|
|||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::AssocKind;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
|
@ -85,8 +84,8 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
cx.tcx
|
||||
.associated_items(did)
|
||||
.in_definition_order()
|
||||
.filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
|
||||
.map(|assoc_item| assoc_item.name)
|
||||
.filter(|assoc_item| assoc_item.is_fn())
|
||||
.map(|assoc_item| assoc_item.name())
|
||||
.collect()
|
||||
} else {
|
||||
BTreeSet::new()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath
|
|||
use rustc_hir_analysis::lower_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::{Ident, kw};
|
||||
use rustc_span::{Span, sym};
|
||||
|
|
@ -322,7 +322,7 @@ impl UnconditionalRecursion {
|
|||
.in_definition_order()
|
||||
// We're not interested in foreign implementations of the `Default` trait.
|
||||
.find(|item| {
|
||||
item.kind == AssocKind::Fn && item.def_id.is_local() && item.name == kw::Default
|
||||
item.is_fn() && item.def_id.is_local() && item.name() == kw::Default
|
||||
})
|
||||
&& let Some(body_node) = cx.tcx.hir_get_if_local(assoc_item.def_id)
|
||||
&& let Some(body_id) = body_node.body_id()
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
|
|||
.is_some()
|
||||
};
|
||||
if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind
|
||||
&& assoc_item.fn_has_self_parameter
|
||||
&& assoc_item.is_method()
|
||||
&& let ImplItemKind::Fn(.., body_id) = &impl_item.kind
|
||||
&& (!cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) || !self.avoid_breaking_exported_api)
|
||||
&& let body = cx.tcx.hir_body(*body_id)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use rustc_middle::mir::interpret::Scalar;
|
|||
use rustc_middle::traits::EvaluationResult;
|
||||
use rustc_middle::ty::layout::ValidityRequirement;
|
||||
use rustc_middle::ty::{
|
||||
self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
|
||||
self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
|
||||
GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
|
||||
TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
|
||||
};
|
||||
|
|
@ -156,7 +156,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'
|
|||
pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Iterator)
|
||||
.and_then(|iter_did| cx.get_associated_type(ty, iter_did, "Item"))
|
||||
.and_then(|iter_did| cx.get_associated_type(ty, iter_did, sym::Item))
|
||||
}
|
||||
|
||||
/// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type
|
||||
|
|
@ -1112,7 +1112,7 @@ pub fn make_projection<'tcx>(
|
|||
let Some(assoc_item) = tcx.associated_items(container_id).find_by_ident_and_kind(
|
||||
tcx,
|
||||
Ident::with_dummy_span(assoc_ty),
|
||||
AssocKind::Type,
|
||||
AssocTag::Type,
|
||||
container_id,
|
||||
) else {
|
||||
debug_assert!(false, "type `{assoc_ty}` not found in `{container_id:?}`");
|
||||
|
|
@ -1345,7 +1345,7 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n
|
|||
.associated_items(did)
|
||||
.filter_by_name_unhygienic(method_name)
|
||||
.next()
|
||||
.filter(|item| item.kind == AssocKind::Fn)
|
||||
.filter(|item| item.as_tag() == AssocTag::Fn)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -414,6 +414,11 @@ pub struct Config {
|
|||
/// cross-compilation scenarios that do not otherwise want/need to `-Zbuild-std`. Used in e.g.
|
||||
/// ABI tests.
|
||||
pub minicore_path: Utf8PathBuf,
|
||||
|
||||
/// If true, run tests with the "new" executor that was written to replace
|
||||
/// compiletest's dependency on libtest. Eventually this will become the
|
||||
/// default, and the libtest dependency will be removed.
|
||||
pub new_executor: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,251 @@
|
|||
//! This module encapsulates all of the code that interacts directly with
|
||||
//! libtest, to execute the collected tests.
|
||||
//!
|
||||
//! This will hopefully make it easier to migrate away from libtest someday.
|
||||
//! This module contains a reimplementation of the subset of libtest
|
||||
//! functionality needed by compiletest.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{BuildHasherDefault, DefaultHasher};
|
||||
use std::num::NonZero;
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
use std::{env, hint, io, mem, panic, thread};
|
||||
|
||||
use crate::common::{Config, TestPaths};
|
||||
|
||||
/// Delegates to libtest to run the list of collected tests.
|
||||
///
|
||||
/// Returns `Ok(true)` if all tests passed, or `Ok(false)` if one or more tests failed.
|
||||
pub(crate) fn execute_tests(config: &Config, tests: Vec<CollectedTest>) -> io::Result<bool> {
|
||||
let opts = test_opts(config);
|
||||
let tests = tests.into_iter().map(|t| t.into_libtest()).collect::<Vec<_>>();
|
||||
mod deadline;
|
||||
mod json;
|
||||
pub(crate) mod libtest;
|
||||
|
||||
test::run_tests_console(&opts, tests)
|
||||
pub(crate) fn run_tests(config: &Config, tests: Vec<CollectedTest>) -> bool {
|
||||
let tests_len = tests.len();
|
||||
let filtered = filter_tests(config, tests);
|
||||
// Iterator yielding tests that haven't been started yet.
|
||||
let mut fresh_tests = (0..).map(TestId).zip(&filtered);
|
||||
|
||||
let concurrency = get_concurrency();
|
||||
assert!(concurrency > 0);
|
||||
let concurrent_capacity = concurrency.min(filtered.len());
|
||||
|
||||
let mut listener = json::Listener::new();
|
||||
let mut running_tests = HashMap::with_capacity_and_hasher(
|
||||
concurrent_capacity,
|
||||
BuildHasherDefault::<DefaultHasher>::new(),
|
||||
);
|
||||
let mut deadline_queue = deadline::DeadlineQueue::with_capacity(concurrent_capacity);
|
||||
|
||||
let num_filtered_out = tests_len - filtered.len();
|
||||
listener.suite_started(filtered.len(), num_filtered_out);
|
||||
|
||||
// Channel used by test threads to report the test outcome when done.
|
||||
let (completion_tx, completion_rx) = mpsc::channel::<TestCompletion>();
|
||||
|
||||
// Unlike libtest, we don't have a separate code path for concurrency=1.
|
||||
// In that case, the tests will effectively be run serially anyway.
|
||||
loop {
|
||||
// Spawn new test threads, up to the concurrency limit.
|
||||
// FIXME(let_chains): Use a let-chain here when stable in bootstrap.
|
||||
'spawn: while running_tests.len() < concurrency {
|
||||
let Some((id, test)) = fresh_tests.next() else { break 'spawn };
|
||||
listener.test_started(test);
|
||||
deadline_queue.push(id, test);
|
||||
let join_handle = spawn_test_thread(id, test, completion_tx.clone());
|
||||
running_tests.insert(id, RunningTest { test, join_handle });
|
||||
}
|
||||
|
||||
// If all running tests have finished, and there weren't any unstarted
|
||||
// tests to spawn, then we're done.
|
||||
if running_tests.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let completion = deadline_queue
|
||||
.read_channel_while_checking_deadlines(&completion_rx, |_id, test| {
|
||||
listener.test_timed_out(test);
|
||||
})
|
||||
.expect("receive channel should never be closed early");
|
||||
|
||||
let RunningTest { test, join_handle } = running_tests.remove(&completion.id).unwrap();
|
||||
if let Some(join_handle) = join_handle {
|
||||
join_handle.join().unwrap_or_else(|_| {
|
||||
panic!("thread for `{}` panicked after reporting completion", test.desc.name)
|
||||
});
|
||||
}
|
||||
|
||||
listener.test_finished(test, &completion);
|
||||
|
||||
if completion.outcome.is_failed() && config.fail_fast {
|
||||
// Prevent any other in-flight threads from panicking when they
|
||||
// write to the completion channel.
|
||||
mem::forget(completion_rx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let suite_passed = listener.suite_finished();
|
||||
suite_passed
|
||||
}
|
||||
|
||||
/// Spawns a thread to run a single test, and returns the thread's join handle.
|
||||
///
|
||||
/// Returns `None` if the test was ignored, so no thread was spawned.
|
||||
fn spawn_test_thread(
|
||||
id: TestId,
|
||||
test: &CollectedTest,
|
||||
completion_tx: mpsc::Sender<TestCompletion>,
|
||||
) -> Option<thread::JoinHandle<()>> {
|
||||
if test.desc.ignore && !test.config.run_ignored {
|
||||
completion_tx
|
||||
.send(TestCompletion { id, outcome: TestOutcome::Ignored, stdout: None })
|
||||
.unwrap();
|
||||
return None;
|
||||
}
|
||||
|
||||
let runnable_test = RunnableTest::new(test);
|
||||
let should_panic = test.desc.should_panic;
|
||||
let run_test = move || run_test_inner(id, should_panic, runnable_test, completion_tx);
|
||||
|
||||
let thread_builder = thread::Builder::new().name(test.desc.name.clone());
|
||||
let join_handle = thread_builder.spawn(run_test).unwrap();
|
||||
Some(join_handle)
|
||||
}
|
||||
|
||||
/// Runs a single test, within the dedicated thread spawned by the caller.
|
||||
fn run_test_inner(
|
||||
id: TestId,
|
||||
should_panic: ShouldPanic,
|
||||
runnable_test: RunnableTest,
|
||||
completion_sender: mpsc::Sender<TestCompletion>,
|
||||
) {
|
||||
let is_capture = !runnable_test.config.nocapture;
|
||||
let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![])));
|
||||
|
||||
if let Some(capture_buf) = &capture_buf {
|
||||
io::set_output_capture(Some(Arc::clone(capture_buf)));
|
||||
}
|
||||
|
||||
let panic_payload = panic::catch_unwind(move || runnable_test.run()).err();
|
||||
|
||||
if is_capture {
|
||||
io::set_output_capture(None);
|
||||
}
|
||||
|
||||
let outcome = match (should_panic, panic_payload) {
|
||||
(ShouldPanic::No, None) | (ShouldPanic::Yes, Some(_)) => TestOutcome::Succeeded,
|
||||
(ShouldPanic::No, Some(_)) => TestOutcome::Failed { message: None },
|
||||
(ShouldPanic::Yes, None) => {
|
||||
TestOutcome::Failed { message: Some("test did not panic as expected") }
|
||||
}
|
||||
};
|
||||
let stdout = capture_buf.map(|mutex| mutex.lock().unwrap_or_else(|e| e.into_inner()).to_vec());
|
||||
|
||||
completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
struct TestId(usize);
|
||||
|
||||
struct RunnableTest {
|
||||
config: Arc<Config>,
|
||||
testpaths: TestPaths,
|
||||
revision: Option<String>,
|
||||
}
|
||||
|
||||
impl RunnableTest {
|
||||
fn new(test: &CollectedTest) -> Self {
|
||||
let config = Arc::clone(&test.config);
|
||||
let testpaths = test.testpaths.clone();
|
||||
let revision = test.revision.clone();
|
||||
Self { config, testpaths, revision }
|
||||
}
|
||||
|
||||
fn run(&self) {
|
||||
__rust_begin_short_backtrace(|| {
|
||||
crate::runtest::run(
|
||||
Arc::clone(&self.config),
|
||||
&self.testpaths,
|
||||
self.revision.as_deref(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
|
||||
#[inline(never)]
|
||||
fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
|
||||
let result = f();
|
||||
|
||||
// prevent this frame from being tail-call optimised away
|
||||
hint::black_box(result)
|
||||
}
|
||||
|
||||
struct RunningTest<'a> {
|
||||
test: &'a CollectedTest,
|
||||
join_handle: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
/// Test completion message sent by individual test threads when their test
|
||||
/// finishes (successfully or unsuccessfully).
|
||||
struct TestCompletion {
|
||||
id: TestId,
|
||||
outcome: TestOutcome,
|
||||
stdout: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
enum TestOutcome {
|
||||
Succeeded,
|
||||
Failed { message: Option<&'static str> },
|
||||
Ignored,
|
||||
}
|
||||
|
||||
impl TestOutcome {
|
||||
fn is_failed(&self) -> bool {
|
||||
matches!(self, Self::Failed { .. })
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies command-line arguments for filtering/skipping tests by name.
|
||||
///
|
||||
/// Adapted from `filter_tests` in libtest.
|
||||
///
|
||||
/// FIXME(#139660): After the libtest dependency is removed, redesign the whole
|
||||
/// filtering system to do a better job of understanding and filtering _paths_,
|
||||
/// instead of being tied to libtest's substring/exact matching behaviour.
|
||||
fn filter_tests(opts: &Config, tests: Vec<CollectedTest>) -> Vec<CollectedTest> {
|
||||
let mut filtered = tests;
|
||||
|
||||
let matches_filter = |test: &CollectedTest, filter_str: &str| {
|
||||
let test_name = &test.desc.name;
|
||||
if opts.filter_exact { test_name == filter_str } else { test_name.contains(filter_str) }
|
||||
};
|
||||
|
||||
// Remove tests that don't match the test filter
|
||||
if !opts.filters.is_empty() {
|
||||
filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
|
||||
}
|
||||
|
||||
// Skip tests that match any of the skip filters
|
||||
if !opts.skip.is_empty() {
|
||||
filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
|
||||
}
|
||||
|
||||
filtered
|
||||
}
|
||||
|
||||
/// Determines the number of tests to run concurrently.
|
||||
///
|
||||
/// Copied from `get_concurrency` in libtest.
|
||||
///
|
||||
/// FIXME(#139660): After the libtest dependency is removed, consider making
|
||||
/// bootstrap specify the number of threads on the command-line, instead of
|
||||
/// propagating the `RUST_TEST_THREADS` environment variable.
|
||||
fn get_concurrency() -> usize {
|
||||
if let Ok(value) = env::var("RUST_TEST_THREADS") {
|
||||
match value.parse::<NonZero<usize>>().ok() {
|
||||
Some(n) => n.get(),
|
||||
_ => panic!("RUST_TEST_THREADS is `{value}`, should be a positive integer."),
|
||||
}
|
||||
} else {
|
||||
thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information needed to create a `test::TestDescAndFn`.
|
||||
|
|
@ -35,45 +264,6 @@ pub(crate) struct CollectedTestDesc {
|
|||
pub(crate) should_panic: ShouldPanic,
|
||||
}
|
||||
|
||||
impl CollectedTest {
|
||||
fn into_libtest(self) -> test::TestDescAndFn {
|
||||
let Self { desc, config, testpaths, revision } = self;
|
||||
let CollectedTestDesc { name, ignore, ignore_message, should_panic } = desc;
|
||||
|
||||
// Libtest requires the ignore message to be a &'static str, so we might
|
||||
// have to leak memory to create it. This is fine, as we only do so once
|
||||
// per test, so the leak won't grow indefinitely.
|
||||
let ignore_message = ignore_message.map(|msg| match msg {
|
||||
Cow::Borrowed(s) => s,
|
||||
Cow::Owned(s) => &*String::leak(s),
|
||||
});
|
||||
|
||||
let desc = test::TestDesc {
|
||||
name: test::DynTestName(name),
|
||||
ignore,
|
||||
ignore_message,
|
||||
source_file: "",
|
||||
start_line: 0,
|
||||
start_col: 0,
|
||||
end_line: 0,
|
||||
end_col: 0,
|
||||
should_panic: should_panic.to_libtest(),
|
||||
compile_fail: false,
|
||||
no_run: false,
|
||||
test_type: test::TestType::Unknown,
|
||||
};
|
||||
|
||||
// This closure is invoked when libtest returns control to compiletest
|
||||
// to execute the test.
|
||||
let testfn = test::DynTestFn(Box::new(move || {
|
||||
crate::runtest::run(config, &testpaths, revision.as_deref());
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
test::TestDescAndFn { desc, testfn }
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether console output should be colored or not.
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub enum ColorConfig {
|
||||
|
|
@ -83,16 +273,6 @@ pub enum ColorConfig {
|
|||
NeverColor,
|
||||
}
|
||||
|
||||
impl ColorConfig {
|
||||
fn to_libtest(self) -> test::ColorConfig {
|
||||
match self {
|
||||
Self::AutoColor => test::ColorConfig::AutoColor,
|
||||
Self::AlwaysColor => test::ColorConfig::AlwaysColor,
|
||||
Self::NeverColor => test::ColorConfig::NeverColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format of the test results output.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum OutputFormat {
|
||||
|
|
@ -105,52 +285,9 @@ pub enum OutputFormat {
|
|||
Json,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
fn to_libtest(self) -> test::OutputFormat {
|
||||
match self {
|
||||
Self::Pretty => test::OutputFormat::Pretty,
|
||||
Self::Terse => test::OutputFormat::Terse,
|
||||
Self::Json => test::OutputFormat::Json,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether test is expected to panic or not.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum ShouldPanic {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl ShouldPanic {
|
||||
fn to_libtest(self) -> test::ShouldPanic {
|
||||
match self {
|
||||
Self::No => test::ShouldPanic::No,
|
||||
Self::Yes => test::ShouldPanic::Yes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_opts(config: &Config) -> test::TestOpts {
|
||||
test::TestOpts {
|
||||
exclude_should_panic: false,
|
||||
filters: config.filters.clone(),
|
||||
filter_exact: config.filter_exact,
|
||||
run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
|
||||
format: config.format.to_libtest(),
|
||||
logfile: None,
|
||||
run_tests: true,
|
||||
bench_benchmarks: true,
|
||||
nocapture: config.nocapture,
|
||||
color: config.color.to_libtest(),
|
||||
shuffle: false,
|
||||
shuffle_seed: None,
|
||||
test_threads: None,
|
||||
skip: config.skip.clone(),
|
||||
list: false,
|
||||
options: test::Options::new(),
|
||||
time_options: None,
|
||||
force_run_in_process: false,
|
||||
fail_fast: config.fail_fast,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
78
src/tools/compiletest/src/executor/deadline.rs
Normal file
78
src/tools/compiletest/src/executor/deadline.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc::{self, RecvError, RecvTimeoutError};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::executor::{CollectedTest, TestId};
|
||||
|
||||
const TEST_WARN_TIMEOUT_S: u64 = 60;
|
||||
|
||||
struct DeadlineEntry<'a> {
|
||||
id: TestId,
|
||||
test: &'a CollectedTest,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
pub(crate) struct DeadlineQueue<'a> {
|
||||
queue: VecDeque<DeadlineEntry<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DeadlineQueue<'a> {
|
||||
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||
Self { queue: VecDeque::with_capacity(capacity) }
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, id: TestId, test: &'a CollectedTest) {
|
||||
let deadline = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S);
|
||||
self.queue.push_back(DeadlineEntry { id, test, deadline });
|
||||
}
|
||||
|
||||
/// Equivalent to `rx.read()`, except that if any test exceeds its deadline
|
||||
/// during the wait, the given callback will also be called for that test.
|
||||
pub(crate) fn read_channel_while_checking_deadlines<T>(
|
||||
&mut self,
|
||||
rx: &mpsc::Receiver<T>,
|
||||
mut on_deadline_passed: impl FnMut(TestId, &CollectedTest),
|
||||
) -> Result<T, RecvError> {
|
||||
loop {
|
||||
let Some(next_deadline) = self.next_deadline() else {
|
||||
// All currently-running tests have already exceeded their
|
||||
// deadline, so do a normal receive.
|
||||
return rx.recv();
|
||||
};
|
||||
let wait_duration = next_deadline.saturating_duration_since(Instant::now());
|
||||
|
||||
let recv_result = rx.recv_timeout(wait_duration);
|
||||
match recv_result {
|
||||
Ok(value) => return Ok(value),
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
// Notify the callback of tests that have exceeded their
|
||||
// deadline, then loop and do annother channel read.
|
||||
for DeadlineEntry { id, test, .. } in self.remove_tests_past_deadline() {
|
||||
on_deadline_passed(id, test);
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => return Err(RecvError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_deadline(&self) -> Option<Instant> {
|
||||
Some(self.queue.front()?.deadline)
|
||||
}
|
||||
|
||||
fn remove_tests_past_deadline(&mut self) -> Vec<DeadlineEntry<'a>> {
|
||||
let now = Instant::now();
|
||||
let mut timed_out = vec![];
|
||||
while let Some(deadline_entry) = pop_front_if(&mut self.queue, |entry| now < entry.deadline)
|
||||
{
|
||||
timed_out.push(deadline_entry);
|
||||
}
|
||||
timed_out
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME(vec_deque_pop_if): Use `VecDeque::pop_front_if` when it is stable in bootstrap.
|
||||
fn pop_front_if<T>(queue: &mut VecDeque<T>, predicate: impl FnOnce(&T) -> bool) -> Option<T> {
|
||||
let first = queue.front()?;
|
||||
if predicate(first) { queue.pop_front() } else { None }
|
||||
}
|
||||
111
src/tools/compiletest/src/executor/json.rs
Normal file
111
src/tools/compiletest/src/executor/json.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! Collects statistics and emits suite/test events as JSON messages, using
|
||||
//! the same JSON format as libtest's JSON formatter.
|
||||
//!
|
||||
//! These messages are then parsed by bootstrap, which replaces them with
|
||||
//! user-friendly terminal output.
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::executor::{CollectedTest, TestCompletion, TestOutcome};
|
||||
|
||||
pub(crate) struct Listener {
|
||||
suite_start: Option<Instant>,
|
||||
passed: usize,
|
||||
failed: usize,
|
||||
ignored: usize,
|
||||
filtered_out: usize,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { suite_start: None, passed: 0, failed: 0, ignored: 0, filtered_out: 0 }
|
||||
}
|
||||
|
||||
fn print_message(&self, message: &serde_json::Value) {
|
||||
println!("{message}");
|
||||
}
|
||||
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
pub(crate) fn suite_started(&mut self, test_count: usize, filtered_out: usize) {
|
||||
self.suite_start = Some(self.now());
|
||||
self.filtered_out = filtered_out;
|
||||
let message = json!({ "type": "suite", "event": "started", "test_count": test_count });
|
||||
self.print_message(&message);
|
||||
}
|
||||
|
||||
pub(crate) fn test_started(&mut self, test: &CollectedTest) {
|
||||
let name = test.desc.name.as_str();
|
||||
let message = json!({ "type": "test", "event": "started", "name": name });
|
||||
self.print_message(&message);
|
||||
}
|
||||
|
||||
pub(crate) fn test_timed_out(&mut self, test: &CollectedTest) {
|
||||
let name = test.desc.name.as_str();
|
||||
let message = json!({ "type": "test", "event": "timeout", "name": name });
|
||||
self.print_message(&message);
|
||||
}
|
||||
|
||||
pub(crate) fn test_finished(&mut self, test: &CollectedTest, completion: &TestCompletion) {
|
||||
let event;
|
||||
let name = test.desc.name.as_str();
|
||||
let mut maybe_message = None;
|
||||
let maybe_stdout = completion.stdout.as_deref().map(String::from_utf8_lossy);
|
||||
|
||||
match completion.outcome {
|
||||
TestOutcome::Succeeded => {
|
||||
self.passed += 1;
|
||||
event = "ok";
|
||||
}
|
||||
TestOutcome::Failed { message } => {
|
||||
self.failed += 1;
|
||||
maybe_message = message;
|
||||
event = "failed";
|
||||
}
|
||||
TestOutcome::Ignored => {
|
||||
self.ignored += 1;
|
||||
maybe_message = test.desc.ignore_message.as_deref();
|
||||
event = "ignored";
|
||||
}
|
||||
};
|
||||
|
||||
// This emits optional fields as `null`, instead of omitting them
|
||||
// completely as libtest does, but bootstrap can parse the result
|
||||
// either way.
|
||||
let json = json!({
|
||||
"type": "test",
|
||||
"event": event,
|
||||
"name": name,
|
||||
"message": maybe_message,
|
||||
"stdout": maybe_stdout,
|
||||
});
|
||||
|
||||
self.print_message(&json);
|
||||
}
|
||||
|
||||
pub(crate) fn suite_finished(&mut self) -> bool {
|
||||
let exec_time = self.suite_start.map(|start| (self.now() - start).as_secs_f64());
|
||||
let suite_passed = self.failed == 0;
|
||||
|
||||
let event = if suite_passed { "ok" } else { "failed" };
|
||||
let message = json!({
|
||||
"type": "suite",
|
||||
"event": event,
|
||||
"passed": self.passed,
|
||||
"failed": self.failed,
|
||||
"ignored": self.ignored,
|
||||
// Compiletest doesn't run any benchmarks, but we still need to set this
|
||||
// field to 0 so that bootstrap's JSON parser can read our message.
|
||||
"measured": 0,
|
||||
"filtered_out": self.filtered_out,
|
||||
"exec_time": exec_time,
|
||||
});
|
||||
|
||||
self.print_message(&message);
|
||||
suite_passed
|
||||
}
|
||||
}
|
||||
111
src/tools/compiletest/src/executor/libtest.rs
Normal file
111
src/tools/compiletest/src/executor/libtest.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! This submodule encapsulates all of the code that actually interacts with
|
||||
//! libtest, so that it can be easily removed after the new executor becomes
|
||||
//! the default.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
|
||||
use crate::common::Config;
|
||||
use crate::executor::{CollectedTest, CollectedTestDesc, ColorConfig, OutputFormat, ShouldPanic};
|
||||
|
||||
/// Delegates to libtest to run the list of collected tests.
|
||||
///
|
||||
/// Returns `Ok(true)` if all tests passed, or `Ok(false)` if one or more tests failed.
|
||||
pub(crate) fn execute_tests(config: &Config, tests: Vec<CollectedTest>) -> io::Result<bool> {
|
||||
let opts = test_opts(config);
|
||||
let tests = tests.into_iter().map(|t| t.into_libtest()).collect::<Vec<_>>();
|
||||
|
||||
test::run_tests_console(&opts, tests)
|
||||
}
|
||||
|
||||
impl CollectedTest {
|
||||
fn into_libtest(self) -> test::TestDescAndFn {
|
||||
let Self { desc, config, testpaths, revision } = self;
|
||||
let CollectedTestDesc { name, ignore, ignore_message, should_panic } = desc;
|
||||
|
||||
// Libtest requires the ignore message to be a &'static str, so we might
|
||||
// have to leak memory to create it. This is fine, as we only do so once
|
||||
// per test, so the leak won't grow indefinitely.
|
||||
let ignore_message = ignore_message.map(|msg| match msg {
|
||||
Cow::Borrowed(s) => s,
|
||||
Cow::Owned(s) => &*String::leak(s),
|
||||
});
|
||||
|
||||
let desc = test::TestDesc {
|
||||
name: test::DynTestName(name),
|
||||
ignore,
|
||||
ignore_message,
|
||||
source_file: "",
|
||||
start_line: 0,
|
||||
start_col: 0,
|
||||
end_line: 0,
|
||||
end_col: 0,
|
||||
should_panic: should_panic.to_libtest(),
|
||||
compile_fail: false,
|
||||
no_run: false,
|
||||
test_type: test::TestType::Unknown,
|
||||
};
|
||||
|
||||
// This closure is invoked when libtest returns control to compiletest
|
||||
// to execute the test.
|
||||
let testfn = test::DynTestFn(Box::new(move || {
|
||||
crate::runtest::run(config, &testpaths, revision.as_deref());
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
test::TestDescAndFn { desc, testfn }
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorConfig {
|
||||
fn to_libtest(self) -> test::ColorConfig {
|
||||
match self {
|
||||
Self::AutoColor => test::ColorConfig::AutoColor,
|
||||
Self::AlwaysColor => test::ColorConfig::AlwaysColor,
|
||||
Self::NeverColor => test::ColorConfig::NeverColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
fn to_libtest(self) -> test::OutputFormat {
|
||||
match self {
|
||||
Self::Pretty => test::OutputFormat::Pretty,
|
||||
Self::Terse => test::OutputFormat::Terse,
|
||||
Self::Json => test::OutputFormat::Json,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShouldPanic {
|
||||
fn to_libtest(self) -> test::ShouldPanic {
|
||||
match self {
|
||||
Self::No => test::ShouldPanic::No,
|
||||
Self::Yes => test::ShouldPanic::Yes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_opts(config: &Config) -> test::TestOpts {
|
||||
test::TestOpts {
|
||||
exclude_should_panic: false,
|
||||
filters: config.filters.clone(),
|
||||
filter_exact: config.filter_exact,
|
||||
run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
|
||||
format: config.format.to_libtest(),
|
||||
logfile: None,
|
||||
run_tests: true,
|
||||
bench_benchmarks: true,
|
||||
nocapture: config.nocapture,
|
||||
color: config.color.to_libtest(),
|
||||
shuffle: false,
|
||||
shuffle_seed: None,
|
||||
test_threads: None,
|
||||
skip: config.skip.clone(),
|
||||
list: false,
|
||||
options: test::Options::new(),
|
||||
time_options: None,
|
||||
force_run_in_process: false,
|
||||
fail_fast: config.fail_fast,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
#![crate_name = "compiletest"]
|
||||
// The `test` crate is the only unstable feature
|
||||
// allowed here, just to share similar code.
|
||||
// Needed by the libtest-based test executor.
|
||||
#![feature(test)]
|
||||
// Needed by the "new" test executor that does not depend on libtest.
|
||||
#![feature(internal_output_capture)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
|
|
@ -202,6 +203,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
|||
"COMMAND",
|
||||
)
|
||||
.reqopt("", "minicore-path", "path to minicore aux library", "PATH")
|
||||
.optflag("n", "new-executor", "enables the new test executor instead of using libtest")
|
||||
.optopt(
|
||||
"",
|
||||
"debugger",
|
||||
|
|
@ -447,6 +449,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
|||
diff_command: matches.opt_str("compiletest-diff-tool"),
|
||||
|
||||
minicore_path: opt_path(matches, "minicore-path"),
|
||||
|
||||
new_executor: matches.opt_present("new-executor"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,10 +574,14 @@ pub fn run_tests(config: Arc<Config>) {
|
|||
|
||||
tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
|
||||
|
||||
// Delegate to libtest to filter and run the big list of structures created
|
||||
// during test discovery. When libtest decides to run a test, it will
|
||||
// return control to compiletest by invoking a closure.
|
||||
let res = crate::executor::execute_tests(&config, tests);
|
||||
// Delegate to the executor to filter and run the big list of test structures
|
||||
// created during test discovery. When the executor decides to run a test,
|
||||
// it will return control to the rest of compiletest by calling `runtest::run`.
|
||||
let res = if config.new_executor {
|
||||
Ok(executor::run_tests(&config, tests))
|
||||
} else {
|
||||
crate::executor::libtest::execute_tests(&config, tests)
|
||||
};
|
||||
|
||||
// Check the outcome reported by libtest.
|
||||
match res {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
|
||||
use intern::Symbol;
|
||||
use proc_macro::bridge::{self, server};
|
||||
use span::{FileId, Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
|
||||
use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
|
||||
use tt::{TextRange, TextSize};
|
||||
|
||||
use crate::server_impl::{literal_kind_to_internal, token_stream::TokenStreamBuilder, TopSubtree};
|
||||
|
|
@ -27,10 +27,6 @@ mod tt {
|
|||
|
||||
type TokenStream = crate::server_impl::TokenStream<Span>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct SourceFile {
|
||||
file_id: FileId,
|
||||
}
|
||||
pub struct FreeFunctions;
|
||||
|
||||
pub struct RaSpanServer {
|
||||
|
|
@ -46,7 +42,6 @@ pub struct RaSpanServer {
|
|||
impl server::Types for RaSpanServer {
|
||||
type FreeFunctions = FreeFunctions;
|
||||
type TokenStream = TokenStream;
|
||||
type SourceFile = SourceFile;
|
||||
type Span = Span;
|
||||
type Symbol = Symbol;
|
||||
}
|
||||
|
|
@ -245,25 +240,17 @@ impl server::TokenStream for RaSpanServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl server::SourceFile for RaSpanServer {
|
||||
fn eq(&mut self, file1: &Self::SourceFile, file2: &Self::SourceFile) -> bool {
|
||||
file1 == file2
|
||||
}
|
||||
fn path(&mut self, _file: &Self::SourceFile) -> String {
|
||||
// FIXME
|
||||
String::new()
|
||||
}
|
||||
fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Span for RaSpanServer {
|
||||
fn debug(&mut self, span: Self::Span) -> String {
|
||||
format!("{:?}", span)
|
||||
}
|
||||
fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
|
||||
SourceFile { file_id: span.anchor.file_id.file_id() }
|
||||
fn file(&mut self, _: Self::Span) -> String {
|
||||
// FIXME
|
||||
String::new()
|
||||
}
|
||||
fn local_file(&mut self, _: Self::Span) -> Option<String> {
|
||||
// FIXME
|
||||
None
|
||||
}
|
||||
fn save_span(&mut self, _span: Self::Span) -> usize {
|
||||
// FIXME, quote is incompatible with third-party tools
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ type Literal = tt::Literal;
|
|||
type Span = tt::TokenId;
|
||||
type TokenStream = crate::server_impl::TokenStream<Span>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SourceFile;
|
||||
pub struct FreeFunctions;
|
||||
|
||||
pub struct TokenIdServer {
|
||||
|
|
@ -37,7 +35,6 @@ pub struct TokenIdServer {
|
|||
impl server::Types for TokenIdServer {
|
||||
type FreeFunctions = FreeFunctions;
|
||||
type TokenStream = TokenStream;
|
||||
type SourceFile = SourceFile;
|
||||
type Span = Span;
|
||||
type Symbol = Symbol;
|
||||
}
|
||||
|
|
@ -223,24 +220,15 @@ impl server::TokenStream for TokenIdServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl server::SourceFile for TokenIdServer {
|
||||
fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool {
|
||||
true
|
||||
}
|
||||
fn path(&mut self, _file: &Self::SourceFile) -> String {
|
||||
String::new()
|
||||
}
|
||||
fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Span for TokenIdServer {
|
||||
fn debug(&mut self, span: Self::Span) -> String {
|
||||
format!("{:?}", span.0)
|
||||
}
|
||||
fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile {
|
||||
SourceFile {}
|
||||
fn file(&mut self, _span: Self::Span) -> String {
|
||||
String::new()
|
||||
}
|
||||
fn local_file(&mut self, _span: Self::Span) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn save_span(&mut self, _span: Self::Span) -> usize {
|
||||
0
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ fn test_fn_like_macro_clone_raw_ident() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(bootstrap))]
|
||||
fn test_fn_like_fn_like_span_join() {
|
||||
assert_expand(
|
||||
"fn_like_span_join",
|
||||
|
|
@ -111,6 +112,7 @@ fn test_fn_like_fn_like_span_join() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(bootstrap))]
|
||||
fn test_fn_like_fn_like_span_ops() {
|
||||
assert_expand(
|
||||
"fn_like_span_ops",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue