Skip to content
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

Add start of GUI tests for clippy lints page #13618

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
bless = "test --config env.RUSTC_BLESS='1'"
uitest = "test --test compile-test"
uibless = "bless --test compile-test"
guitest = "test --test gui"
dev = "run --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --"
lintcheck = "run --package lintcheck --bin lintcheck --manifest-path lintcheck/Cargo.toml -- "
collect-metadata = "test --test compile-test --config env.COLLECT_METADATA='1'"
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/clippy_mq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ env:
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
BROWSER_UI_TEST_VERSION: '0.18.2'

defaults:
run:
Expand Down Expand Up @@ -76,6 +77,14 @@ jobs:
rustup set default-host ${{ matrix.host }}
rustup show active-toolchain

- name: Install npm
uses: actions/setup-node@v3
with:
node-version: 20

- name: Install browser-ui-test
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"

# Run
- name: Build
run: cargo build --tests --features internal
Expand Down Expand Up @@ -113,6 +122,9 @@ jobs:
env:
OS: ${{ runner.os }}

- name: Test clippy lints page
run: cargo guitest

metadata_collection:
needs: changelog
runs-on: ubuntu-latest
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/clippy_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ env:
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
BROWSER_UI_TEST_VERSION: '0.18.2'

concurrency:
# For a given workflow, if we push to the same PR, cancel all previous builds on that PR.
Expand All @@ -29,6 +30,14 @@ jobs:
- name: Install toolchain
run: rustup show active-toolchain

- name: Install npm
uses: actions/setup-node@v3
with:
node-version: 20

- name: Install browser-ui-test
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"

# Run
- name: Build
run: cargo build --tests --features internal
Expand Down Expand Up @@ -57,6 +66,9 @@ jobs:
env:
OS: ${{ runner.os }}

- name: Test clippy lints page
run: cargo guitest

# We need to have the "conclusion" job also on PR CI, to make it possible
# to add PRs to a merge queue.
conclusion:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ helper.txt

# mdbook generated output
/book/book

# GUI tests
node_modules
package-lock.json
package.json
2 changes: 2 additions & 0 deletions book/src/development/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ cargo uitest
TESTNAME="test_" cargo uitest
# only run dogfood tests
cargo dev dogfood
# only run GUI tests (clippy lints page)
cargo guitest
```

If the output of a [UI test] differs from the expected output, you can update
Expand Down
110 changes: 110 additions & 0 deletions tests/gui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// This test ensures that the clippy lints page is working as expected.

use std::ffi::OsStr;
use std::fs::read_to_string;
use std::path::Path;
use std::process::Command;
use std::time::SystemTime;

fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> {
let mut command = Command::new("npm");
command.arg("list").arg("--parseable").arg("--long").arg("--depth=0");
if global {
command.arg("--global");
}
let stdout = command.output().expect("`npm` command not found").stdout;
let lines = String::from_utf8_lossy(&stdout);
lines
.lines()
.find_map(|l| l.split(':').nth(1)?.strip_prefix("browser-ui-test@"))
.map(std::borrow::ToOwned::to_owned)
}

fn get_available_browser_ui_test_version() -> Option<String> {
get_available_browser_ui_test_version_inner(false).or_else(|| get_available_browser_ui_test_version_inner(true))
}

fn expected_browser_ui_test_version() -> String {
let content =
read_to_string(".github/workflows/clippy.yml").expect("failed to read `.github/workflows/clippy.yml`");
for line in content.lines() {
let line = line.trim();
if let Some(version) = line.strip_prefix("BROWSER_UI_TEST_VERSION:") {
return version.trim().replace('\'', "");
}
}
panic!("failed to retrieved `browser-ui-test` version");
}

fn mtime(path: impl AsRef<Path>) -> SystemTime {
let path = path.as_ref();
if path.is_dir() {
path.read_dir()
.into_iter()
.flatten()
.flatten()
.map(|entry| mtime(entry.path()))
.max()
.unwrap_or(SystemTime::UNIX_EPOCH)
} else {
path.metadata()
.and_then(|metadata| metadata.modified())
.unwrap_or(SystemTime::UNIX_EPOCH)
}
}

#[test]
fn check_clippy_lints_page() {
// do not run this test inside the upstream rustc repo.
if option_env!("RUSTC_TEST_SUITE").is_some() {
return;
}
let browser_ui_test_version = expected_browser_ui_test_version();
match get_available_browser_ui_test_version() {
Some(version) => {
if version != browser_ui_test_version {
eprintln!(
"⚠️ Installed version of browser-ui-test (`{version}`) is different than the \
one used in the CI (`{browser_ui_test_version}`) You can install this version \
using `npm update browser-ui-test` or by using `npm install browser-ui-test\
@{browser_ui_test_version}`",
);
}
},
None => {
panic!(
"`browser-ui-test` is not installed. You can install this package using `npm \
update browser-ui-test` or by using `npm install browser-ui-test\
@{browser_ui_test_version}`",
);
},
}

// We build the lints page only if needed.
let index_time = mtime("util/gh-pages/index.html");

if (index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html"))
&& !Command::new("cargo")
.arg("collect-metadata")
.status()
.is_ok_and(|status| status.success())
{
panic!("failed to run `cargo collect-metadata`");
}

let current_dir = std::env::current_dir()
.expect("failed to retrieve current directory")
.join("util/gh-pages/index.html");
let current_dir = format!("file://{}", current_dir.display());
let mut command = Command::new("npx");
command
.arg("browser-ui-test")
.args(["--variable", "DOC_PATH", current_dir.as_str()])
.args(["--test-folder", "tests/gui"]);
if std::env::var_os("DISABLE_HEADLESS_TEST").is_some_and(|value| value == OsStr::new("1")) {
command.arg("--no-headless");
}

// Then we run the GUI tests on it.
assert!(command.status().is_ok_and(|status| status.success()));
}
19 changes: 19 additions & 0 deletions tests/gui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
The tests present here are used to test the clippy lints page. The
goal is to prevent unsound/unexpected GUI (breaking) changes.

This is using the [browser-ui-test] framework to do so. It works as follows:

It wraps [puppeteer] to send commands to a web browser in order to navigate and
test what's being currently displayed in the web page.

You can find more information and its documentation in its [repository][browser-ui-test].

If you don't want to run in headless mode (helpful to debug sometimes), you can use
`DISABLE_HEADLESS_TEST=1`:

```bash
$ DISABLE_HEADLESS_TEST=1 cargo guitest
```

[browser-ui-test]: https://github.com/GuillaumeGomez/browser-UI-test/
[puppeteer]: https://pptr.dev/
22 changes: 22 additions & 0 deletions tests/gui/hash.goml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This GUI test ensures that when the URL has a hash, it will open the target lint.

go-to: |DOC_PATH|
// First we ensure that by default, the lint is not displayed.
assert-css: ("#alloc_instead_of_core .lint-docs", {"display": "none"})
// First we move the mouse cursor to the lint to make the anchor appear.
move-cursor-to: "#alloc_instead_of_core"
// We wait for the anchor to be visible.
wait-for-css-false: ("#alloc_instead_of_core .anchor", {"display": "none"})
click: "#alloc_instead_of_core .anchor"
// Clicking on the anchor should have two effects:
// 1. Change the URL hash.
// 2. Open the lint.
wait-for-css: ("#alloc_instead_of_core .lint-docs", {"display": "block"})
wait-for-document-property: {"location"."hash": "#alloc_instead_of_core"}

// Now we reload the page. The lint should still be open since the hash is
// targetting it.
go-to: |DOC_PATH| + "#alloc_instead_of_core"
wait-for-css: ("#alloc_instead_of_core .lint-docs", {"display": "block"})
// Other lints should not be expanded.
wait-for-css: ("#absolute_paths .lint-docs", {"display": "none"})
48 changes: 48 additions & 0 deletions tests/gui/no-js.goml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This GUI test checks the lints page works as expected when JS is disabled.
javascript: false // disabling javascript
go-to: |DOC_PATH|

define-function: (
"check-expanded-collapsed",
[display, content],
block {
wait-for-css: ("#absolute_paths > .lint-docs", {"display": |display|})
assert-css: ("#absolute_paths .label-doc-folding::before", {"content": |content|})
},
)

define-function: (
"check-expand-collapse-action",
[selector],
block {
// We confirm it's collapsed.
call-function: ("check-expanded-collapsed", {
"display": "none",
"content": '"+"',
})
// We click on the item to expand it.
click: |selector|
// We confirm it's expanded.
call-function: ("check-expanded-collapsed", {
"display": "block",
"content": '"−"',
})
// We collapse it again.
click: |selector|
// We confirm it's collapsed again.
call-function: ("check-expanded-collapsed", {
"display": "none",
"content": '"+"',
})
},
)

// First we check that we can expand/collapse a lint by clicking on the lint.
call-function: ("check-expand-collapse-action", {"selector": "#lint-absolute_paths"})
// Then we check the expand/collapse works when clicking on the +/- button.
call-function: ("check-expand-collapse-action", {"selector": "#absolute_paths .label-doc-folding"})

// Checking click on the anchor changes the location hash.
assert-document-property: {"location"."hash": ""}
click: "#absolute_paths .panel-title .anchor"
assert-document-property: {"location"."hash": "#absolute_paths"}
15 changes: 15 additions & 0 deletions tests/gui/search.goml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This test ensures that the search is filtering lints correctly.
go-to: |DOC_PATH|

assert-css: ("#absurd_extreme_comparisons", {"display": "block"})
assert-css: ("#absolute_paths", {"display": "block"})
assert-css: ("#join_absolute_paths", {"display": "block"})

// We update the search.
write-into: ("#search-input", "absolute_paths")

// `absolute_paths` and `join_absolute_path` should still be visible, but
// not `absurde_extreme_comparisons`.
wait-for-css: ("#absurd_extreme_comparisons", {"display": "none"})
assert-css: ("#absolute_paths", {"display": "block"})
assert-css: ("#join_absolute_paths", {"display": "block"})