diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 51eac58c971eb..6b6df619720b9 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -14,6 +14,7 @@ use crate::directives::directive_names::{ KNOWN_DIRECTIVE_NAMES, KNOWN_HTMLDOCCK_DIRECTIVE_NAMES, KNOWN_JSONDOCCK_DIRECTIVE_NAMES, }; pub(crate) use crate::directives::file::FileDirectives; +use crate::directives::handlers::DIRECTIVE_HANDLERS_MAP; use crate::directives::line::{DirectiveLine, line_directive}; use crate::directives::needs::CachedNeedsConditions; use crate::edition::{Edition, parse_edition}; @@ -26,6 +27,7 @@ mod auxiliary; mod cfg; mod directive_names; mod file; +mod handlers; mod line; mod needs; #[cfg(test)] @@ -369,269 +371,9 @@ impl TestProps { return; } - use directives::*; - - config.push_name_value_directive( - ln, - ERROR_PATTERN, - &mut self.error_patterns, - |r| r, - ); - config.push_name_value_directive( - ln, - REGEX_ERROR_PATTERN, - &mut self.regex_error_patterns, - |r| r, - ); - - config.push_name_value_directive(ln, DOC_FLAGS, &mut self.doc_flags, |r| r); - - fn split_flags(flags: &str) -> Vec { - // Individual flags can be single-quoted to preserve spaces; see - // . - flags - .split('\'') - .enumerate() - .flat_map(|(i, f)| { - if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() } - }) - .map(move |s| s.to_owned()) - .collect::>() - } - - if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) { - let flags = split_flags(&flags); - for (i, flag) in flags.iter().enumerate() { - if flag == "--edition" || flag.starts_with("--edition=") { - panic!("you must use `//@ edition` to configure the edition"); - } - if (flag == "-C" - && flags.get(i + 1).is_some_and(|v| v.starts_with("incremental="))) - || flag.starts_with("-Cincremental=") - { - panic!( - "you must use `//@ incremental` to enable incremental compilation" - ); - } - } - self.compile_flags.extend(flags); - } - - if let Some(range) = parse_edition_range(config, ln) { - self.edition = Some(range.edition_to_test(config.edition)); - } - - config.parse_and_update_revisions(ln, &mut self.revisions); - - if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) { - self.run_flags.extend(split_flags(&flags)); - } - - if self.pp_exact.is_none() { - self.pp_exact = config.parse_pp_exact(ln); - } - - config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice); - config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs); - config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut self.unique_doc_out_dir); - - config.set_name_directive(ln, FORCE_HOST, &mut self.force_host); - config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout); - config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results); - config.set_name_directive( - ln, - DONT_CHECK_COMPILER_STDOUT, - &mut self.dont_check_compiler_stdout, - ); - config.set_name_directive( - ln, - DONT_CHECK_COMPILER_STDERR, - &mut self.dont_check_compiler_stderr, - ); - config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic); - - if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) { - self.pretty_mode = m; - } - - config.set_name_directive( - ln, - PRETTY_COMPARE_ONLY, - &mut self.pretty_compare_only, - ); - - // Call a helper method to deal with aux-related directives. - parse_and_update_aux(config, ln, &mut self.aux); - - config.push_name_value_directive( - ln, - EXEC_ENV, - &mut self.exec_env, - Config::parse_env, - ); - config.push_name_value_directive( - ln, - UNSET_EXEC_ENV, - &mut self.unset_exec_env, - |r| r.trim().to_owned(), - ); - config.push_name_value_directive( - ln, - RUSTC_ENV, - &mut self.rustc_env, - Config::parse_env, - ); - config.push_name_value_directive( - ln, - UNSET_RUSTC_ENV, - &mut self.unset_rustc_env, - |r| r.trim().to_owned(), - ); - config.push_name_value_directive( - ln, - FORBID_OUTPUT, - &mut self.forbid_output, - |r| r, - ); - config.set_name_directive( - ln, - CHECK_TEST_LINE_NUMBERS_MATCH, - &mut self.check_test_line_numbers_match, - ); - - self.update_pass_mode(ln, config); - self.update_fail_mode(ln, config); - - config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass); - - if let Some(NormalizeRule { kind, regex, replacement }) = - config.parse_custom_normalization(ln) - { - let rule_tuple = (regex, replacement); - match kind { - NormalizeKind::Stdout => self.normalize_stdout.push(rule_tuple), - NormalizeKind::Stderr => self.normalize_stderr.push(rule_tuple), - NormalizeKind::Stderr32bit => { - if config.target_cfg().pointer_width == 32 { - self.normalize_stderr.push(rule_tuple); - } - } - NormalizeKind::Stderr64bit => { - if config.target_cfg().pointer_width == 64 { - self.normalize_stderr.push(rule_tuple); - } - } - } - } - - if let Some(code) = config - .parse_name_value_directive(ln, FAILURE_STATUS) - .and_then(|code| code.trim().parse::().ok()) - { - self.failure_status = Some(code); - } - - config.set_name_directive( - ln, - DONT_CHECK_FAILURE_STATUS, - &mut self.dont_check_failure_status, - ); - - config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix); - config.set_name_directive( - ln, - RUSTFIX_ONLY_MACHINE_APPLICABLE, - &mut self.rustfix_only_machine_applicable, - ); - config.set_name_value_directive( - ln, - ASSEMBLY_OUTPUT, - &mut self.assembly_output, - |r| r.trim().to_string(), - ); - config.set_name_directive( - ln, - STDERR_PER_BITWIDTH, - &mut self.stderr_per_bitwidth, - ); - config.set_name_directive(ln, INCREMENTAL, &mut self.incremental); - - // Unlike the other `name_value_directive`s this needs to be handled manually, - // because it sets a `bool` flag. - if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) { - let known_bug = known_bug.trim(); - if known_bug == "unknown" - || known_bug.split(',').all(|issue_ref| { - issue_ref - .trim() - .split_once('#') - .filter(|(_, number)| { - number.chars().all(|digit| digit.is_numeric()) - }) - .is_some() - }) - { - self.known_bug = true; - } else { - panic!( - "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." - ); - } - } else if config.parse_name_directive(ln, KNOWN_BUG) { - panic!( - "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." - ); - } - - config.set_name_value_directive( - ln, - TEST_MIR_PASS, - &mut self.mir_unit_test, - |s| s.trim().to_string(), - ); - config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base); - - if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) { - self.llvm_cov_flags.extend(split_flags(&flags)); - } - - if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) { - self.filecheck_flags.extend(split_flags(&flags)); - } - - config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut self.no_auto_check_cfg); - - self.update_add_core_stubs(ln, config); - - if let Some(flags) = - config.parse_name_value_directive(ln, CORE_STUBS_COMPILE_FLAGS) - { - let flags = split_flags(&flags); - for flag in &flags { - if flag == "--edition" || flag.starts_with("--edition=") { - panic!("you must use `//@ edition` to configure the edition"); - } - } - self.core_stubs_compile_flags.extend(flags); + if let Some(handler) = DIRECTIVE_HANDLERS_MAP.get(ln.name) { + handler.handle(config, ln, self); } - - if let Some(err_kind) = - config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS) - { - self.dont_require_annotations - .insert(ErrorKind::expect_from_user_str(err_kind.trim())); - } - - config.set_name_directive( - ln, - DISABLE_GDB_PRETTY_PRINTERS, - &mut self.disable_gdb_pretty_printers, - ); - config.set_name_directive( - ln, - COMPARE_OUTPUT_BY_LINES, - &mut self.compare_output_by_lines, - ); }, ); @@ -1716,3 +1458,14 @@ impl EditionRange { } } } + +fn split_flags(flags: &str) -> Vec { + // Individual flags can be single-quoted to preserve spaces; see + // . + flags + .split('\'') + .enumerate() + .flat_map(|(i, f)| if i % 2 == 1 { vec![f] } else { f.split_whitespace().collect() }) + .map(move |s| s.to_owned()) + .collect::>() +} diff --git a/src/tools/compiletest/src/directives/handlers.rs b/src/tools/compiletest/src/directives/handlers.rs new file mode 100644 index 0000000000000..b530a09a106e7 --- /dev/null +++ b/src/tools/compiletest/src/directives/handlers.rs @@ -0,0 +1,368 @@ +use std::collections::HashMap; +use std::sync::{Arc, LazyLock}; + +use crate::common::Config; +use crate::directives::{ + DirectiveLine, NormalizeKind, NormalizeRule, TestProps, parse_and_update_aux, + parse_edition_range, split_flags, +}; +use crate::errors::ErrorKind; + +pub(crate) static DIRECTIVE_HANDLERS_MAP: LazyLock> = + LazyLock::new(make_directive_handlers_map); + +#[derive(Clone)] +pub(crate) struct Handler { + handler_fn: Arc) + Send + Sync>, +} + +impl Handler { + pub(crate) fn handle(&self, config: &Config, line: &DirectiveLine<'_>, props: &mut TestProps) { + (self.handler_fn)(HandlerArgs { config, line, props }) + } +} + +struct HandlerArgs<'a> { + config: &'a Config, + line: &'a DirectiveLine<'a>, + props: &'a mut TestProps, +} + +/// Intermediate data structure, used for defining a list of handlers. +struct NamedHandler { + names: Vec<&'static str>, + handler: Handler, +} + +/// Helper function to create a simple handler, so that changes can be made +/// to the handler struct without disturbing existing handler declarations. +fn handler( + name: &'static str, + handler_fn: impl Fn(&Config, &DirectiveLine<'_>, &mut TestProps) + Send + Sync + 'static, +) -> NamedHandler { + multi_handler(&[name], handler_fn) +} + +/// Associates the same handler function with multiple directive names. +fn multi_handler( + names: &[&'static str], + handler_fn: impl Fn(&Config, &DirectiveLine<'_>, &mut TestProps) + Send + Sync + 'static, +) -> NamedHandler { + NamedHandler { + names: names.to_owned(), + handler: Handler { + handler_fn: Arc::new(move |args| handler_fn(args.config, args.line, args.props)), + }, + } +} + +fn make_directive_handlers_map() -> HashMap<&'static str, Handler> { + use crate::directives::directives::*; + + // FIXME(Zalathar): Now that most directive-processing has been extracted + // into individual handlers, there should be many opportunities to simplify + // these handlers, e.g. by getting rid of now-redundant name checks. + + let handlers: Vec = vec![ + handler(ERROR_PATTERN, |config, ln, props| { + config.push_name_value_directive(ln, ERROR_PATTERN, &mut props.error_patterns, |r| r); + }), + handler(REGEX_ERROR_PATTERN, |config, ln, props| { + config.push_name_value_directive( + ln, + REGEX_ERROR_PATTERN, + &mut props.regex_error_patterns, + |r| r, + ); + }), + handler(DOC_FLAGS, |config, ln, props| { + config.push_name_value_directive(ln, DOC_FLAGS, &mut props.doc_flags, |r| r); + }), + handler(COMPILE_FLAGS, |config, ln, props| { + if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) { + let flags = split_flags(&flags); + for (i, flag) in flags.iter().enumerate() { + if flag == "--edition" || flag.starts_with("--edition=") { + panic!("you must use `//@ edition` to configure the edition"); + } + if (flag == "-C" + && flags.get(i + 1).is_some_and(|v| v.starts_with("incremental="))) + || flag.starts_with("-Cincremental=") + { + panic!("you must use `//@ incremental` to enable incremental compilation"); + } + } + props.compile_flags.extend(flags); + } + }), + handler("edition", |config, ln, props| { + if let Some(range) = parse_edition_range(config, ln) { + props.edition = Some(range.edition_to_test(config.edition)); + } + }), + handler("revisions", |config, ln, props| { + config.parse_and_update_revisions(ln, &mut props.revisions); + }), + handler(RUN_FLAGS, |config, ln, props| { + if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) { + props.run_flags.extend(split_flags(&flags)); + } + }), + handler("pp-exact", |config, ln, props| { + if props.pp_exact.is_none() { + props.pp_exact = config.parse_pp_exact(ln); + } + }), + handler(SHOULD_ICE, |config, ln, props| { + config.set_name_directive(ln, SHOULD_ICE, &mut props.should_ice); + }), + handler(BUILD_AUX_DOCS, |config, ln, props| { + config.set_name_directive(ln, BUILD_AUX_DOCS, &mut props.build_aux_docs); + }), + handler(UNIQUE_DOC_OUT_DIR, |config, ln, props| { + config.set_name_directive(ln, UNIQUE_DOC_OUT_DIR, &mut props.unique_doc_out_dir); + }), + handler(FORCE_HOST, |config, ln, props| { + config.set_name_directive(ln, FORCE_HOST, &mut props.force_host); + }), + handler(CHECK_STDOUT, |config, ln, props| { + config.set_name_directive(ln, CHECK_STDOUT, &mut props.check_stdout); + }), + handler(CHECK_RUN_RESULTS, |config, ln, props| { + config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut props.check_run_results); + }), + handler(DONT_CHECK_COMPILER_STDOUT, |config, ln, props| { + config.set_name_directive( + ln, + DONT_CHECK_COMPILER_STDOUT, + &mut props.dont_check_compiler_stdout, + ); + }), + handler(DONT_CHECK_COMPILER_STDERR, |config, ln, props| { + config.set_name_directive( + ln, + DONT_CHECK_COMPILER_STDERR, + &mut props.dont_check_compiler_stderr, + ); + }), + handler(NO_PREFER_DYNAMIC, |config, ln, props| { + config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut props.no_prefer_dynamic); + }), + handler(PRETTY_MODE, |config, ln, props| { + if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) { + props.pretty_mode = m; + } + }), + handler(PRETTY_COMPARE_ONLY, |config, ln, props| { + config.set_name_directive(ln, PRETTY_COMPARE_ONLY, &mut props.pretty_compare_only); + }), + multi_handler( + &[AUX_BUILD, AUX_BIN, AUX_CRATE, PROC_MACRO, AUX_CODEGEN_BACKEND], + |config, ln, props| { + // Call a helper method to deal with aux-related directives. + parse_and_update_aux(config, ln, &mut props.aux); + }, + ), + handler(EXEC_ENV, |config, ln, props| { + config.push_name_value_directive(ln, EXEC_ENV, &mut props.exec_env, Config::parse_env); + }), + handler(UNSET_EXEC_ENV, |config, ln, props| { + config.push_name_value_directive(ln, UNSET_EXEC_ENV, &mut props.unset_exec_env, |r| { + r.trim().to_owned() + }); + }), + handler(RUSTC_ENV, |config, ln, props| { + config.push_name_value_directive( + ln, + RUSTC_ENV, + &mut props.rustc_env, + Config::parse_env, + ); + }), + handler(UNSET_RUSTC_ENV, |config, ln, props| { + config.push_name_value_directive( + ln, + UNSET_RUSTC_ENV, + &mut props.unset_rustc_env, + |r| r.trim().to_owned(), + ); + }), + handler(FORBID_OUTPUT, |config, ln, props| { + config.push_name_value_directive(ln, FORBID_OUTPUT, &mut props.forbid_output, |r| r); + }), + handler(CHECK_TEST_LINE_NUMBERS_MATCH, |config, ln, props| { + config.set_name_directive( + ln, + CHECK_TEST_LINE_NUMBERS_MATCH, + &mut props.check_test_line_numbers_match, + ); + }), + multi_handler(&["check-pass", "build-pass", "run-pass"], |config, ln, props| { + props.update_pass_mode(ln, config); + }), + multi_handler( + &["check-fail", "build-fail", "run-fail", "run-crash", "run-fail-or-crash"], + |config, ln, props| { + props.update_fail_mode(ln, config); + }, + ), + handler(IGNORE_PASS, |config, ln, props| { + config.set_name_directive(ln, IGNORE_PASS, &mut props.ignore_pass); + }), + multi_handler( + &[ + "normalize-stdout", + "normalize-stderr", + "normalize-stderr-32bit", + "normalize-stderr-64bit", + ], + |config, ln, props| { + if let Some(NormalizeRule { kind, regex, replacement }) = + config.parse_custom_normalization(ln) + { + let rule_tuple = (regex, replacement); + match kind { + NormalizeKind::Stdout => props.normalize_stdout.push(rule_tuple), + NormalizeKind::Stderr => props.normalize_stderr.push(rule_tuple), + NormalizeKind::Stderr32bit => { + if config.target_cfg().pointer_width == 32 { + props.normalize_stderr.push(rule_tuple); + } + } + NormalizeKind::Stderr64bit => { + if config.target_cfg().pointer_width == 64 { + props.normalize_stderr.push(rule_tuple); + } + } + } + } + }, + ), + handler(FAILURE_STATUS, |config, ln, props| { + if let Some(code) = config + .parse_name_value_directive(ln, FAILURE_STATUS) + .and_then(|code| code.trim().parse::().ok()) + { + props.failure_status = Some(code); + } + }), + handler(DONT_CHECK_FAILURE_STATUS, |config, ln, props| { + config.set_name_directive( + ln, + DONT_CHECK_FAILURE_STATUS, + &mut props.dont_check_failure_status, + ); + }), + handler(RUN_RUSTFIX, |config, ln, props| { + config.set_name_directive(ln, RUN_RUSTFIX, &mut props.run_rustfix); + }), + handler(RUSTFIX_ONLY_MACHINE_APPLICABLE, |config, ln, props| { + config.set_name_directive( + ln, + RUSTFIX_ONLY_MACHINE_APPLICABLE, + &mut props.rustfix_only_machine_applicable, + ); + }), + handler(ASSEMBLY_OUTPUT, |config, ln, props| { + config.set_name_value_directive(ln, ASSEMBLY_OUTPUT, &mut props.assembly_output, |r| { + r.trim().to_string() + }); + }), + handler(STDERR_PER_BITWIDTH, |config, ln, props| { + config.set_name_directive(ln, STDERR_PER_BITWIDTH, &mut props.stderr_per_bitwidth); + }), + handler(INCREMENTAL, |config, ln, props| { + config.set_name_directive(ln, INCREMENTAL, &mut props.incremental); + }), + handler(KNOWN_BUG, |config, ln, props| { + // Unlike the other `name_value_directive`s this needs to be handled manually, + // because it sets a `bool` flag. + if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) { + let known_bug = known_bug.trim(); + if known_bug == "unknown" + || known_bug.split(',').all(|issue_ref| { + issue_ref + .trim() + .split_once('#') + .filter(|(_, number)| number.chars().all(|digit| digit.is_numeric())) + .is_some() + }) + { + props.known_bug = true; + } else { + panic!( + "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." + ); + } + } else if config.parse_name_directive(ln, KNOWN_BUG) { + panic!( + "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`." + ); + } + }), + handler(TEST_MIR_PASS, |config, ln, props| { + config.set_name_value_directive(ln, TEST_MIR_PASS, &mut props.mir_unit_test, |s| { + s.trim().to_string() + }); + }), + handler(REMAP_SRC_BASE, |config, ln, props| { + config.set_name_directive(ln, REMAP_SRC_BASE, &mut props.remap_src_base); + }), + handler(LLVM_COV_FLAGS, |config, ln, props| { + if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) { + props.llvm_cov_flags.extend(split_flags(&flags)); + } + }), + handler(FILECHECK_FLAGS, |config, ln, props| { + if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) { + props.filecheck_flags.extend(split_flags(&flags)); + } + }), + handler(NO_AUTO_CHECK_CFG, |config, ln, props| { + config.set_name_directive(ln, NO_AUTO_CHECK_CFG, &mut props.no_auto_check_cfg); + }), + handler(ADD_CORE_STUBS, |config, ln, props| { + props.update_add_core_stubs(ln, config); + }), + handler(CORE_STUBS_COMPILE_FLAGS, |config, ln, props| { + if let Some(flags) = config.parse_name_value_directive(ln, CORE_STUBS_COMPILE_FLAGS) { + let flags = split_flags(&flags); + for flag in &flags { + if flag == "--edition" || flag.starts_with("--edition=") { + panic!("you must use `//@ edition` to configure the edition"); + } + } + props.core_stubs_compile_flags.extend(flags); + } + }), + handler(DONT_REQUIRE_ANNOTATIONS, |config, ln, props| { + if let Some(err_kind) = config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS) + { + props + .dont_require_annotations + .insert(ErrorKind::expect_from_user_str(err_kind.trim())); + } + }), + handler(DISABLE_GDB_PRETTY_PRINTERS, |config, ln, props| { + config.set_name_directive( + ln, + DISABLE_GDB_PRETTY_PRINTERS, + &mut props.disable_gdb_pretty_printers, + ); + }), + handler(COMPARE_OUTPUT_BY_LINES, |config, ln, props| { + config.set_name_directive( + ln, + COMPARE_OUTPUT_BY_LINES, + &mut props.compare_output_by_lines, + ); + }), + ]; + + handlers + .into_iter() + .flat_map(|NamedHandler { names, handler }| { + names.into_iter().map(move |name| (name, Handler::clone(&handler))) + }) + .collect() +} diff --git a/src/tools/compiletest/src/directives/tests.rs b/src/tools/compiletest/src/directives/tests.rs index 98249e69601be..48f6d25831a80 100644 --- a/src/tools/compiletest/src/directives/tests.rs +++ b/src/tools/compiletest/src/directives/tests.rs @@ -1,14 +1,33 @@ +use std::collections::{BTreeSet, HashSet}; + use camino::Utf8Path; use semver::Version; use crate::common::{Config, Debugger, TestMode}; use crate::directives::{ - AuxProps, DirectivesCache, EarlyProps, Edition, EditionRange, FileDirectives, - extract_llvm_version, extract_version_range, iter_directives, line_directive, parse_edition, - parse_normalize_rule, + AuxProps, DIRECTIVE_HANDLERS_MAP, DirectivesCache, EarlyProps, Edition, EditionRange, + FileDirectives, KNOWN_DIRECTIVE_NAMES, extract_llvm_version, extract_version_range, + iter_directives, line_directive, parse_edition, parse_normalize_rule, }; use crate::executor::{CollectedTestDesc, ShouldPanic}; +/// All directive handlers should have a name that is also in `KNOWN_DIRECTIVE_NAMES`. +#[test] +fn handler_names() { + let known_directive_names = KNOWN_DIRECTIVE_NAMES.iter().copied().collect::>(); + + let unknown_names = DIRECTIVE_HANDLERS_MAP + .keys() + .copied() + .filter(|name| !known_directive_names.contains(name)) + .collect::>(); + + assert!( + unknown_names.is_empty(), + "Directive handler names not in `directive_names.rs`: {unknown_names:#?}" + ); +} + fn make_test_description( config: &Config, name: String,