zed/script/check-gpui-bench-feature-isolation
Anthony Eid 58d03b0137 benchmarks: Stop requesting multi_buffer's test-support feature
Adds a narrow `benchmarks` feature to `multi_buffer`, gating
`MultiBuffer::build_simple_for_benchmarks`/`build_random_for_benchmarks` as
production-faithful equivalents of `build_simple`/`build_random` that do not
require any crate's test-support. `display_map`/`editor_render` now call the
new functions instead, and `benchmarks` requests `multi_buffer/benchmarks`
instead of `multi_buffer/test-support`.

`build_random_for_benchmarks` is a dedicated duplicate of
`randomly_edit_excerpts`, substituting a vendored `BenchmarkRandomCharIter`
for `util::RandomCharIter` (the only test-support-only dependency it has),
guarded against drift by a new parity test asserting byte-identical output
against `build_random` for the same rng sequence.

Extends script/check-gpui-bench-feature-isolation to prove the direct
benchmarks -> multi_buffer/test-support edge is gone and that multi_buffer's
benchmarks feature itself resolves no test-support.
2026-08-21 18:07:52 -04:00

209 lines
9.4 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# `gpui`'s `bench` feature must stay usable without pulling in `test-support`,
# so that `#[gpui::bench]` consumers only compile production-capable code
# rather than transitively depending on test-only APIs. This walks the
# resolved feature graph for `gpui` built with only `bench` enabled and fails
# if `test-support` shows up anywhere in it.
cd "$(dirname "$0")/.."
output=$(cargo tree \
--offline \
--package gpui \
--no-default-features \
--features bench \
--edges features \
--invert gpui)
if echo "${output}" | grep --quiet 'gpui feature "test-support"'; then
echo "gpui's \"bench\" feature pulls in \"test-support\":"
echo "${output}"
exit 1
fi
# The real `crates/benchmarks` package requests `current_headless_renderer()`
# through `gpui_platform`, which on macOS delegates to `gpui_macos` and
# `gpui_apple` for the real Metal-backed headless renderer. Each of those
# crates gates that renderer behind its own `test-support` feature as well as
# a narrower `bench` feature, so `benchmarks` must resolve only `bench` on
# them: otherwise `#[gpui::bench]` consumers in `benchmarks` would compile
# whichever crate's interactive-only test doubles alongside their production
# code, same as the `gpui`-level problem this script already guards against.
# `gpui_macos`/`gpui_apple` only exist in the dependency graph on macOS, since
# their crate roots are `#![cfg(target_os = "macos")]`, so this half of the
# check only runs there.
if [[ "$(uname -s)" == "Darwin" ]]; then
for platform_crate in gpui_platform gpui_macos gpui_apple; do
output=$(cargo tree \
--offline \
--package benchmarks \
--edges features \
--invert "${platform_crate}")
if echo "${output}" | grep --quiet "${platform_crate} feature \"test-support\""; then
echo "benchmarks pulls \"test-support\" into \"${platform_crate}\":"
echo "${output}"
exit 1
fi
done
fi
# `crates/benchmarks/Cargo.toml` used to also directly request `test-support`
# from `agent`, `editor`, `language_model`, `lsp`, and `project`, entirely to
# build the `edit_file_tool` benchmark's `TestAppContext`/`FakeFs`/
# `FakeLspAdapter`/`FakeLanguageModel`/`Project::test` harness. That benchmark
# now lives in its own `edit_file_tool_benchmarks` package (see its crate
# docs), so `benchmarks` no longer needs any test-only surface from those five
# crates at all. Unlike the `theme`/`util` check below, this checks the whole
# resolved graph rather than only a direct edge: nothing else `benchmarks`
# depends on has a legitimate reason to reach `test-support` through any of
# them either.
for isolated_crate in agent editor language_model project; do
# `agent` is no longer in `benchmarks`' resolved graph at all (not merely
# test-support-free), so `cargo tree --invert` fails to find it; that is
# the strongest possible form of isolation, so treat it as a pass rather
# than an error.
if ! output=$(cargo tree \
--offline \
--package benchmarks \
--edges features \
--invert "${isolated_crate}" 2>&1); then
if echo "${output}" | grep --quiet 'did not match any packages'; then
continue
fi
echo "failed to resolve \"${isolated_crate}\" in benchmarks' dependency graph:"
echo "${output}"
exit 1
fi
if echo "${output}" | grep --quiet "${isolated_crate} feature \"test-support\""; then
echo "benchmarks pulls \"test-support\" into \"${isolated_crate}\":"
echo "${output}"
exit 1
fi
done
# `lsp` and `language` are not part of the loop below: `markdown_renderer`
# (the only benchmark left that needs either) directly calls test-only APIs
# with no production equivalent — `LanguageRegistry::test`,
# `language::rust_lang` — independent of `edit_file_tool`. `language/
# test-support` enables both `lsp/test-support` and `settings/test-support`
# directly, so both keep showing up transitively despite `benchmarks` no
# longer requesting either itself. This is real, tracked debt, not a gap in
# this script: `theme`, `util`, `settings`, and `multi_buffer` are the crates
# whose benchmark usage turned out to need no test-only API at all
# (`theme::LoadThemes`, `theme_settings::init`), one narrow enough to vendor
# into `benchmarks::bench_utils` instead (`util::RandomCharIter`, a
# synthetic-text generator with no production analogue), or one narrow
# enough to move behind its own dedicated feature instead of `test-support`
# (`settings`'s and `multi_buffer`'s `benchmarks` features, gating
# `SettingsStore::benchmarks`/`benchmark_settings` and `MultiBuffer::
# build_simple_for_benchmarks`/`build_random_for_benchmarks` respectively —
# see `crates/settings/src/settings_file.rs` and
# `crates/multi_buffer/src/multi_buffer.rs`, which keep their fixtures
# byte-identical to the `test-support` versions' so switching does not change
# what a benchmark measures). So `benchmarks` no longer needs a direct
# `test-support` edge to any of the four. This checks only for a *direct*
# edge from `benchmarks` to each crate's `test-support` feature, not for the
# feature's absence from the whole resolved graph, since `language` keeps
# populating `settings/test-support` (transitively) until it gets the same
# treatment; `multi_buffer` is checked separately below since nothing else
# `benchmarks` depends on populates its `test-support` feature at all
# anymore.
for foundational_crate in theme util settings; do
output=$(cargo tree \
--offline \
--package benchmarks \
--edges features \
--charset ascii \
--invert "${foundational_crate}")
# Cargo tree fully expands a given (package, feature-set) node only the
# first time it appears, printing every later re-visit as a bare `(*)`
# back-reference instead of repeating its dependents. So the crate's
# un-suffixed "test-support" feature line is where its direct dependents
# are actually listed; checking the couple of lines after it for a
# "benchmarks" edge is enough to catch a direct request without also
# matching the expected indirect one from `language`.
if echo "${output}" \
| grep -A2 "[|\`]-- ${foundational_crate} feature \"test-support\"\$" \
| grep --quiet 'benchmarks v'; then
echo "benchmarks directly requests \"${foundational_crate}\"'s \"test-support\" feature:"
echo "${output}"
exit 1
fi
done
# The loop above proves `benchmarks` no longer *requests* `settings`'
# `test-support`; this proves it still gets the deterministic settings it
# needs some other way, so a future edit can't silently drop the feature
# request and have `display_map`/`editor_render`/`markdown_renderer` start
# reading plain default settings (different font/size/theme) without any
# isolation check catching it.
output=$(cargo tree \
--offline \
--package benchmarks \
--edges features \
--invert settings)
if ! echo "${output}" | grep --quiet 'settings feature "benchmarks"'; then
echo "benchmarks no longer requests settings' \"benchmarks\" feature:"
echo "${output}"
exit 1
fi
# `crates/benchmarks/Cargo.toml` used to directly request `multi_buffer`'s
# `test-support` feature, entirely to reach `MultiBuffer::build_simple`/
# `build_random` for `display_map`/`editor_render`'s fixtures. `multi_buffer`
# now exposes `build_simple_for_benchmarks`/`build_random_for_benchmarks`
# behind its own narrow `benchmarks` feature instead (see
# `crates/multi_buffer/Cargo.toml` and `crates/multi_buffer/src/
# multi_buffer.rs`), so unlike `theme`/`util`/`settings` above, this checks
# `multi_buffer`'s `test-support` feature is absent from `benchmarks`' whole
# resolved graph, not just as a direct edge: nothing else `benchmarks`
# depends on has a legitimate reason to reach it either.
output=$(cargo tree \
--offline \
--package benchmarks \
--edges features \
--invert multi_buffer)
if echo "${output}" | grep --quiet 'multi_buffer feature "test-support"'; then
echo "benchmarks pulls multi_buffer's \"test-support\" feature:"
echo "${output}"
exit 1
fi
# The check above proves `benchmarks` no longer reaches `multi_buffer`'s
# `test-support` feature; this proves `multi_buffer`'s own `benchmarks`
# feature (which `build_simple_for_benchmarks`/`build_random_for_benchmarks`
# live behind) doesn't itself resolve to any dependency's `test-support`,
# the same guarantee the top of this script proves for `gpui`'s `bench`
# feature.
output=$(cargo tree \
--offline \
--package multi_buffer \
--no-default-features \
--features benchmarks \
--edges features \
--invert multi_buffer)
if echo "${output}" | grep --quiet 'test-support'; then
echo "multi_buffer's \"benchmarks\" feature pulls in \"test-support\":"
echo "${output}"
exit 1
fi
# `edit_file_tool_benchmarks` is the isolated package `edit_file_tool` moved
# into. It is intentionally *not* held to the isolation bar above, including
# the `settings` checks just above: it still needs `test-support` from
# `agent`, `editor`, `language`, `language_model`, `lsp`, `project`, and
# `settings` (via `SettingsStore::update_user_settings`, which has no
# production-capable equivalent for mutating already-loaded settings
# content) for the same harness this script used to document against
# `benchmarks`. That is tracked debt toward eventually having no benchmark
# depend on `test-support` at all, not a gap this script hides — confirm the
# package still exists and builds rather than silently skipping it.
if ! cargo tree --offline --package edit_file_tool_benchmarks >/dev/null; then
echo "edit_file_tool_benchmarks package is missing or fails to resolve"
exit 1
fi