From f52b1f4a4d076fc9e17d8341f2358a1522bd2f9e Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 13 Dec 2024 10:10:01 +0100 Subject: [PATCH] Add tracing support to mdtest (#14935) ## Summary This PR extends the mdtest configuration with a `log` setting that can be any of: * `true`: Enables tracing * `false`: Disables tracing (default) * String: An ENV_FILTER similar to `RED_KNOT_LOG` ```toml log = true ``` Closes https://github.com/astral-sh/ruff/issues/13865 ## Test Plan I changed a test and tried `log=true`, `log=false`, and `log=INFO` --- crates/red_knot_python_semantic/Cargo.toml | 4 ++ .../src/python_version.rs | 40 ++++++++++++++++++- crates/red_knot_test/Cargo.toml | 6 +-- crates/red_knot_test/README.md | 2 + crates/red_knot_test/src/config.rs | 29 +++++++++++--- crates/red_knot_test/src/lib.rs | 9 ++++- crates/red_knot_test/src/parser.rs | 30 ++++---------- ...__tests__member_pattern_matching_file.snap | 5 +-- ...member_pattern_matching_hidden_folder.snap | 5 +-- ...ata__tests__package_without_pyproject.snap | 5 +-- ...pace__metadata__tests__single_package.snap | 5 +-- ...__metadata__tests__workspace_excluded.snap | 5 +-- ...e__metadata__tests__workspace_members.snap | 5 +-- crates/ruff_db/src/testing.rs | 4 +- 14 files changed, 94 insertions(+), 60 deletions(-) diff --git a/crates/red_knot_python_semantic/Cargo.toml b/crates/red_knot_python_semantic/Cargo.toml index 74b7fe81d1..6ed49b1f71 100644 --- a/crates/red_knot_python_semantic/Cargo.toml +++ b/crates/red_knot_python_semantic/Cargo.toml @@ -53,5 +53,9 @@ tempfile = { workspace = true } quickcheck = { version = "1.0.3", default-features = false } quickcheck_macros = { version = "1.0.0" } +[features] +serde = ["ruff_db/serde", "dep:serde"] + [lints] workspace = true + diff --git a/crates/red_knot_python_semantic/src/python_version.rs b/crates/red_knot_python_semantic/src/python_version.rs index 37abc8bf50..d698cef763 100644 --- a/crates/red_knot_python_semantic/src/python_version.rs +++ b/crates/red_knot_python_semantic/src/python_version.rs @@ -5,7 +5,6 @@ use std::fmt; /// Unlike the `TargetVersion` enums in the CLI crates, /// this does not necessarily represent a Python version that we actually support. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct PythonVersion { pub major: u8, pub minor: u8, @@ -68,3 +67,42 @@ impl fmt::Display for PythonVersion { write!(f, "{major}.{minor}") } } + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for PythonVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let as_str = String::deserialize(deserializer)?; + + if let Some((major, minor)) = as_str.split_once('.') { + let major = major + .parse() + .map_err(|err| serde::de::Error::custom(format!("invalid major version: {err}")))?; + let minor = minor + .parse() + .map_err(|err| serde::de::Error::custom(format!("invalid minor version: {err}")))?; + + Ok((major, minor).into()) + } else { + let major = as_str.parse().map_err(|err| { + serde::de::Error::custom(format!( + "invalid python-version: {err}, expected: `major.minor`" + )) + })?; + + Ok((major, 0).into()) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for PythonVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} diff --git a/crates/red_knot_test/Cargo.toml b/crates/red_knot_test/Cargo.toml index f66edb14d7..4f974b5a52 100644 --- a/crates/red_knot_test/Cargo.toml +++ b/crates/red_knot_test/Cargo.toml @@ -11,9 +11,9 @@ authors.workspace = true license.workspace = true [dependencies] -red_knot_python_semantic = { workspace = true } +red_knot_python_semantic = { workspace = true, features = ["serde"] } red_knot_vendored = { workspace = true } -ruff_db = { workspace = true } +ruff_db = { workspace = true, features = ["testing"] } ruff_index = { workspace = true } ruff_python_trivia = { workspace = true } ruff_source_file = { workspace = true } @@ -30,7 +30,5 @@ smallvec = { workspace = true } serde = { workspace = true } toml = { workspace = true } -[dev-dependencies] - [lints] workspace = true diff --git a/crates/red_knot_test/README.md b/crates/red_knot_test/README.md index e4e7010d91..9608adab1b 100644 --- a/crates/red_knot_test/README.md +++ b/crates/red_knot_test/README.md @@ -241,6 +241,8 @@ python-version = "3.10" This configuration will apply to all tests in the same section, and all nested sections within that section. Nested sections can override configurations from their parent sections. +See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/red_knot_test/src/config.rs) for the full list of supported configuration options. + ## Documentation of tests Arbitrary Markdown syntax (including of course normal prose paragraphs) is permitted (and ignored by diff --git a/crates/red_knot_test/src/config.rs b/crates/red_knot_test/src/config.rs index 4138347f74..cf677c485c 100644 --- a/crates/red_knot_test/src/config.rs +++ b/crates/red_knot_test/src/config.rs @@ -3,26 +3,45 @@ //! following limited structure: //! //! ```toml +//! log = true # or log = "red_knot=WARN" //! [environment] //! python-version = "3.10" //! ``` use anyhow::Context; +use red_knot_python_semantic::PythonVersion; use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Debug, Default, Clone)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct MarkdownTestConfig { - pub(crate) environment: Environment, + pub(crate) environment: Option, + + pub(crate) log: Option, } impl MarkdownTestConfig { pub(crate) fn from_str(s: &str) -> anyhow::Result { toml::from_str(s).context("Error while parsing Markdown TOML config") } + + pub(crate) fn python_version(&self) -> Option { + self.environment.as_ref().and_then(|env| env.python_version) + } } -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case")] +#[derive(Deserialize, Debug, Default, Clone)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct Environment { - pub(crate) python_version: String, + /// Python version to assume when resolving types. + pub(crate) python_version: Option, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub(crate) enum Log { + /// Enable logging with tracing when `true`. + Bool(bool), + /// Enable logging and only show filters that match the given [env-filter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) + Filter(String), } diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index ff70b036dc..f2a7639e29 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -1,3 +1,4 @@ +use crate::config::Log; use camino::Utf8Path; use colored::Colorize; use parser as test_parser; @@ -7,6 +8,7 @@ use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic}; use ruff_db::files::{system_path_to_file, File, Files}; use ruff_db::parsed::parsed_module; use ruff_db::system::{DbWithTestSystem, SystemPathBuf}; +use ruff_db::testing::{setup_logging, setup_logging_with_filter}; use ruff_source_file::LineIndex; use ruff_text_size::TextSize; use salsa::Setter; @@ -42,9 +44,14 @@ pub fn run(path: &Utf8Path, long_title: &str, short_title: &str, test_name: &str continue; } + let _tracing = test.configuration().log.as_ref().and_then(|log| match log { + Log::Bool(enabled) => enabled.then(setup_logging), + Log::Filter(filter) => setup_logging_with_filter(filter), + }); + Program::get(&db) .set_python_version(&mut db) - .to(test.python_version()); + .to(test.configuration().python_version().unwrap_or_default()); // Remove all files so that the db is in a "fresh" state. db.memory_file_system().remove_all(); diff --git a/crates/red_knot_test/src/parser.rs b/crates/red_knot_test/src/parser.rs index 78206525e5..f26f8c0cb6 100644 --- a/crates/red_knot_test/src/parser.rs +++ b/crates/red_knot_test/src/parser.rs @@ -1,8 +1,7 @@ use std::sync::LazyLock; -use anyhow::{bail, Context}; +use anyhow::bail; use memchr::memchr2; -use red_knot_python_semantic::PythonVersion; use regex::{Captures, Match, Regex}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -74,8 +73,8 @@ impl<'m, 's> MarkdownTest<'m, 's> { self.files.iter() } - pub(crate) fn python_version(&self) -> PythonVersion { - self.section.python_version + pub(crate) fn configuration(&self) -> &MarkdownTestConfig { + &self.section.config } } @@ -125,7 +124,7 @@ struct Section<'s> { title: &'s str, level: u8, parent_id: Option, - python_version: PythonVersion, + config: MarkdownTestConfig, } #[newtype_index] @@ -222,7 +221,7 @@ impl<'s> Parser<'s> { title, level: 0, parent_id: None, - python_version: PythonVersion::default(), + config: MarkdownTestConfig::default(), }); Self { sections, @@ -305,7 +304,7 @@ impl<'s> Parser<'s> { title, level: header_level.try_into()?, parent_id: Some(parent), - python_version: self.sections[parent].python_version, + config: self.sections[parent].config.clone(), }; if self.current_section_files.is_some() { @@ -398,23 +397,8 @@ impl<'s> Parser<'s> { bail!("Multiple TOML configuration blocks in the same section are not allowed."); } - let config = MarkdownTestConfig::from_str(code)?; - let python_version = config.environment.python_version; - - let parts = python_version - .split('.') - .map(str::parse) - .collect::, _>>() - .context(format!( - "Invalid 'python-version' component: '{python_version}'" - ))?; - - if parts.len() != 2 { - bail!("Invalid 'python-version': expected MAJOR.MINOR, got '{python_version}'.",); - } - let current_section = &mut self.sections[self.stack.top()]; - current_section.python_version = PythonVersion::from((parts[0], parts[1])); + current_section.config = MarkdownTestConfig::from_str(code)?; self.current_section_has_config = true; diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap index 224c474480..d4a37a5d96 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap @@ -22,10 +22,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap index ceb6bc5f3b..ab387f9a4c 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap @@ -22,10 +22,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap index 6d371ed504..bbc5c1247c 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap @@ -22,10 +22,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap index cee3bfacd9..4c0f26977b 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap @@ -22,10 +22,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap index 1edb883b05..8429a787eb 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap @@ -35,10 +35,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap index f7b9c6a0da..74e5ced627 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap @@ -48,10 +48,7 @@ WorkspaceMetadata( ], settings: WorkspaceSettings( program: ProgramSettings( - python_version: PythonVersion( - major: 3, - minor: 9, - ), + python_version: "3.9", search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/ruff_db/src/testing.rs b/crates/ruff_db/src/testing.rs index df35d6aec5..cbba5b3cff 100644 --- a/crates/ruff_db/src/testing.rs +++ b/crates/ruff_db/src/testing.rs @@ -158,7 +158,7 @@ impl LoggingBuilder { .parse() .expect("Hardcoded directive to be valid"), ), - hierarchical: true, + hierarchical: false, } } @@ -167,7 +167,7 @@ impl LoggingBuilder { Some(Self { filter, - hierarchical: true, + hierarchical: false, }) }