Skip to content

[red-knot] Add GitHub PR annotations when mdtests fail in CI #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
code: ${{ steps.check_code.outputs.changed }}
# Flag that is raised when any code that affects the fuzzer is changed
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to red-knot changes.
red_knot: ${{ steps.check_red_knot.outputs.changed }}

# Flag that is set to "true" when code related to the playground changes.
playground: ${{ steps.check_playground.outputs.changed }}
Expand Down Expand Up @@ -166,6 +168,29 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Check if the red-knot code changed
id: check_red_knot
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- \
':Cargo.toml' \
':Cargo.lock' \
':crates/red_knot*/**' \
':crates/ruff_db/**' \
':crates/ruff_annotate_snippets/**' \
':crates/ruff_python_ast/**' \
':crates/ruff_python_parser/**' \
':crates/ruff_python_trivia/**' \
':crates/ruff_source_file/**' \
':crates/ruff_text_size/**' \
':.github/workflows/ci.yaml' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
Expand Down Expand Up @@ -408,6 +433,31 @@ jobs:
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
- run: cargo fuzz build -s none

mdtest-github-format:
name: "red-knot mdtest (GitHub annotations)"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.red_knot == 'true' && github.event_name == 'pull_request' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@v1
- name: "Install cargo insta"
uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2
with:
tool: cargo-insta
- name: "Run mdtest"
shell: bash
env:
NO_COLOR: 1
run: cargo test --features=mdtest_github_output_format -p red_knot_python_semantic --test mdtest

fuzz-parser:
name: "fuzz parser"
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions crates/red_knot_python_semantic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ quickcheck_macros = { version = "1.0.0" }

[features]
serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"]
mdtest_github_output_format = []

[lints]
workspace = true
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
def f3(a1: int, /, *args1: int, **kwargs2: int) -> None: ...
def f4(a2: int, /, *args2: int, **kwargs1: int) -> None: ...

static_assert(is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))
static_assert(not is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))

Check failure on line 146 in crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

View workflow job for this annotation

GitHub Actions / red-knot mdtest (GitHub annotations)

unexpected error: [static-assert-error] "Static assertion error: argument evaluates to `False`"
```

Putting it all together, the following two callables are equivalent:
Expand Down Expand Up @@ -186,7 +186,7 @@
def f3(): ...
def f4() -> None: ...

static_assert(not is_equivalent_to(Callable[[], int], Callable[[], None]))
static_assert(not is_equivalent_to(Callable[[], int], Callable[[], None])) # revealed: int

Check failure on line 189 in crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

View workflow job for this annotation

GitHub Actions / red-knot mdtest (GitHub annotations)

unmatched assertion: revealed: int
static_assert(not is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f3]))
static_assert(not is_equivalent_to(CallableTypeOf[f3], CallableTypeOf[f4]))
static_assert(not is_equivalent_to(CallableTypeOf[f4], CallableTypeOf[f3]))
Expand Down Expand Up @@ -228,6 +228,7 @@
static_assert(not is_equivalent_to(CallableTypeOf[f10], CallableTypeOf[f11]))
static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f10]))
static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f11]))
reveal_type(f9)

Check failure on line 231 in crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

View workflow job for this annotation

GitHub Actions / red-knot mdtest (GitHub annotations)

unexpected error: [revealed-type] "Revealed type is `Literal[f9]`"

Check failure on line 231 in crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md

View workflow job for this annotation

GitHub Actions / red-knot mdtest (GitHub annotations)

used built-in `reveal_type`: add a `# revealed` assertion on this line (original diagnostic: [undefined-reveal] "`reveal_type` used without importing it; this is allowed for debugging convenience but will fail at runtime")
```

When the default value for a parameter is present only in one of the callable type:
Expand Down
8 changes: 8 additions & 0 deletions crates/red_knot_python_semantic/tests/mdtest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use camino::Utf8Path;
use dir_test::{dir_test, Fixture};
use red_knot_test::OutputFormat;

/// See `crates/red_knot_test/README.md` for documentation on these tests.
#[dir_test(
Expand All @@ -18,12 +19,19 @@ fn mdtest(fixture: Fixture<&str>) {

let test_name = test_name("mdtest", absolute_fixture_path);

let output_format = if cfg!(feature = "mdtest_github_output_format") {
OutputFormat::GitHub
} else {
OutputFormat::Cargo
};

red_knot_test::run(
absolute_fixture_path,
relative_fixture_path,
&snapshot_path,
short_title,
&test_name,
output_format,
);
}

Expand Down
51 changes: 41 additions & 10 deletions crates/red_knot_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn run(
snapshot_path: &Utf8Path,
short_title: &str,
test_name: &str,
output_format: OutputFormat,
) {
let source = std::fs::read_to_string(absolute_fixture_path).unwrap();
let suite = match test_parser::parse(short_title, &source) {
Expand All @@ -59,7 +60,10 @@ pub fn run(

if let Err(failures) = run_test(&mut db, relative_fixture_path, snapshot_path, &test) {
any_failures = true;
println!("\n{}\n", test.name().bold().underline());

if output_format.is_cargo() {
println!("\n{}\n", test.name().bold().underline());
}

let md_index = LineIndex::from_source_text(&source);

Expand All @@ -72,21 +76,31 @@ pub fn run(
source_map.to_absolute_line_number(relative_line_number);

for failure in failures {
let line_info =
format!("{relative_fixture_path}:{absolute_line_number}").cyan();
println!(" {line_info} {failure}");
match output_format {
OutputFormat::Cargo => {
let line_info =
format!("{relative_fixture_path}:{absolute_line_number}")
.cyan();
println!(" {line_info} {failure}");
}
OutputFormat::GitHub => println!(
"::error file={absolute_fixture_path},line={absolute_line_number}::{failure}"
),
}
}
}
}

let escaped_test_name = test.name().replace('\'', "\\'");

println!(
"\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}='{escaped_test_name}'",
);
println!(
"{MDTEST_TEST_FILTER}='{escaped_test_name}' cargo test -p red_knot_python_semantic --test mdtest -- {test_name}",
);
if output_format.is_cargo() {
println!(
"\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}='{escaped_test_name}'",
);
println!(
"{MDTEST_TEST_FILTER}='{escaped_test_name}' cargo test -p red_knot_python_semantic --test mdtest -- {test_name}",
);
}
}
}

Expand All @@ -95,6 +109,23 @@ pub fn run(
assert!(!any_failures, "Some tests failed.");
}

/// Defines the format in which mdtest should print an error to the terminal
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
/// The format `cargo test` should use by default.
Cargo,
/// A format that will provide annotations from GitHub Actions
/// if mdtest fails on a PR.
/// See <https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-error-message>
GitHub,
}

impl OutputFormat {
const fn is_cargo(self) -> bool {
matches!(self, OutputFormat::Cargo)
}
}

fn run_test(
db: &mut db::Db,
relative_fixture_path: &Utf8Path,
Expand Down
Loading