Skip to content

Commit 9f5fe77

Browse files
Add start of GUI tests for clippy lints page
1 parent 73bad36 commit 9f5fe77

File tree

10 files changed

+246
-0
lines changed

10 files changed

+246
-0
lines changed

.cargo/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
bless = "test --config env.RUSTC_BLESS='1'"
33
uitest = "test --test compile-test"
44
uibless = "bless --test compile-test"
5+
guitest = "test --test gui"
56
dev = "run --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --"
67
lintcheck = "run --package lintcheck --bin lintcheck --manifest-path lintcheck/Cargo.toml -- "
78
collect-metadata = "test --test compile-test --config env.COLLECT_METADATA='1'"

.github/workflows/clippy.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ env:
2626
NO_FMT_TEST: 1
2727
CARGO_INCREMENTAL: 0
2828
RUSTFLAGS: -D warnings
29+
BROWSER_UI_TEST_VERSION: '0.18.2'
2930

3031
concurrency:
3132
# For a given workflow, if we push to the same PR, cancel all previous builds on that PR.
@@ -46,6 +47,14 @@ jobs:
4647
- name: Install toolchain
4748
run: rustup show active-toolchain
4849

50+
- name: Install npm
51+
uses: actions/setup-node@v3
52+
with:
53+
node-version: 20
54+
55+
- name: Install browser-ui-test
56+
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"
57+
4958
# Run
5059
- name: Build
5160
run: cargo build --tests --features internal
@@ -73,3 +82,6 @@ jobs:
7382
run: .github/driver.sh
7483
env:
7584
OS: ${{ runner.os }}
85+
86+
- name: Test clippy lints page
87+
run: cargo guitest

.github/workflows/clippy_bors.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ env:
1212
NO_FMT_TEST: 1
1313
CARGO_INCREMENTAL: 0
1414
RUSTFLAGS: -D warnings
15+
BROWSER_UI_TEST_VERSION: '0.18.2'
1516

1617
concurrency:
1718
# For a given workflow, if we push to the same branch, cancel all previous builds on that branch.
@@ -84,6 +85,14 @@ jobs:
8485
rustup set default-host ${{ matrix.host }}
8586
rustup show active-toolchain
8687
88+
- name: Install npm
89+
uses: actions/setup-node@v3
90+
with:
91+
node-version: 20
92+
93+
- name: Install browser-ui-test
94+
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"
95+
8796
# Run
8897
- name: Build
8998
run: cargo build --tests --features internal
@@ -121,6 +130,9 @@ jobs:
121130
env:
122131
OS: ${{ runner.os }}
123132

133+
- name: Test clippy lints page
134+
run: cargo guitest
135+
124136
metadata_collection:
125137
needs: changelog
126138
runs-on: ubuntu-latest

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,8 @@ helper.txt
4646

4747
# mdbook generated output
4848
/book/book
49+
50+
# GUI tests
51+
node_modules
52+
package-lock.json
53+
package.json

book/src/development/basics.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ cargo uitest
6060
TESTNAME="test_" cargo uitest
6161
# only run dogfood tests
6262
cargo dev dogfood
63+
# only run GUI tests (clippy lints page)
64+
cargo guitest
6365
```
6466

6567
If the output of a [UI test] differs from the expected output, you can update

tests/gui.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// This test ensures that the clippy lints page is working as expected.
2+
3+
use std::ffi::OsStr;
4+
use std::fs::read_to_string;
5+
use std::path::Path;
6+
use std::process::Command;
7+
use std::time::SystemTime;
8+
9+
fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> {
10+
let mut command = Command::new("npm");
11+
command.arg("list").arg("--parseable").arg("--long").arg("--depth=0");
12+
if global {
13+
command.arg("--global");
14+
}
15+
let stdout = command.output().expect("`npm` command not found").stdout;
16+
let lines = String::from_utf8_lossy(&stdout);
17+
lines
18+
.lines()
19+
.find_map(|l| l.split(':').nth(1)?.strip_prefix("browser-ui-test@"))
20+
.map(std::borrow::ToOwned::to_owned)
21+
}
22+
23+
fn get_available_browser_ui_test_version() -> Option<String> {
24+
get_available_browser_ui_test_version_inner(false).or_else(|| get_available_browser_ui_test_version_inner(true))
25+
}
26+
27+
fn expected_browser_ui_test_version() -> String {
28+
let content =
29+
read_to_string(".github/workflows/clippy.yml").expect("failed to read `.github/workflows/clippy.yml`");
30+
for line in content.lines() {
31+
let line = line.trim();
32+
if let Some(version) = line.strip_prefix("BROWSER_UI_TEST_VERSION:") {
33+
return version.trim().replace('\'', "");
34+
}
35+
}
36+
panic!("failed to retrieved `browser-ui-test` version");
37+
}
38+
39+
fn mtime(path: impl AsRef<Path>) -> SystemTime {
40+
let path = path.as_ref();
41+
if path.is_dir() {
42+
path.read_dir()
43+
.into_iter()
44+
.flatten()
45+
.flatten()
46+
.map(|entry| mtime(entry.path()))
47+
.max()
48+
.unwrap_or(SystemTime::UNIX_EPOCH)
49+
} else {
50+
path.metadata()
51+
.and_then(|metadata| metadata.modified())
52+
.unwrap_or(SystemTime::UNIX_EPOCH)
53+
}
54+
}
55+
56+
#[test]
57+
fn check_clippy_lints_page() {
58+
// do not run this test inside the upstream rustc repo.
59+
if option_env!("RUSTC_TEST_SUITE").is_some() {
60+
return;
61+
}
62+
let browser_ui_test_version = expected_browser_ui_test_version();
63+
match get_available_browser_ui_test_version() {
64+
Some(version) => {
65+
if version != browser_ui_test_version {
66+
eprintln!(
67+
"⚠️ Installed version of browser-ui-test (`{version}`) is different than the \
68+
one used in the CI (`{browser_ui_test_version}`) You can install this version \
69+
using `npm update browser-ui-test` or by using `npm install browser-ui-test\
70+
@{browser_ui_test_version}`",
71+
);
72+
}
73+
},
74+
None => {
75+
panic!(
76+
"`browser-ui-test` is not installed. You can install this package using `npm \
77+
update browser-ui-test` or by using `npm install browser-ui-test\
78+
@{browser_ui_test_version}`",
79+
);
80+
},
81+
}
82+
83+
// We build the lints page only if needed.
84+
let index_time = mtime("util/gh-pages/index.html");
85+
86+
if (index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html"))
87+
&& !Command::new("cargo")
88+
.arg("collect-metadata")
89+
.status()
90+
.is_ok_and(|status| status.success())
91+
{
92+
panic!("failed to run `cargo collect-metadata`");
93+
}
94+
95+
let current_dir = std::env::current_dir()
96+
.expect("failed to retrieve current directory")
97+
.join("util/gh-pages/index.html");
98+
let current_dir = format!("file://{}", current_dir.display());
99+
let mut command = Command::new("npx");
100+
command
101+
.arg("browser-ui-test")
102+
.args(["--variable", "DOC_PATH", current_dir.as_str()])
103+
.args(["--test-folder", "tests/gui"]);
104+
if std::env::var_os("DISABLE_HEADLESS_TEST").is_some_and(|value| value == OsStr::new("1")) {
105+
command.arg("--no-headless");
106+
}
107+
108+
// Then we run the GUI tests on it.
109+
assert!(command.status().is_ok_and(|status| status.success()));
110+
}

tests/gui/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
The tests present here are used to test the clippy lints page. The
2+
goal is to prevent unsound/unexpected GUI (breaking) changes.
3+
4+
This is using the [browser-ui-test] framework to do so. It works as follows:
5+
6+
It wraps [puppeteer] to send commands to a web browser in order to navigate and
7+
test what's being currently displayed in the web page.
8+
9+
You can find more information and its documentation in its [repository][browser-ui-test].
10+
11+
If you don't want to run in headless mode (helpful to debug sometimes), you can use
12+
`DISABLE_HEADLESS_TEST=1`:
13+
14+
```bash
15+
$ DISABLE_HEADLESS_TEST=1 cargo guitest
16+
```
17+
18+
[browser-ui-test]: https://github.com/GuillaumeGomez/browser-UI-test/
19+
[puppeteer]: https://pptr.dev/

tests/gui/hash.goml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This GUI test ensures that when the URL has a hash, it will open the target lint.
2+
3+
go-to: |DOC_PATH|
4+
// First we ensure that by default, the lint is not displayed.
5+
assert-css: ("#alloc_instead_of_core .lint-docs", {"display": "none"})
6+
// First we move the mouse cursor to the lint to make the anchor appear.
7+
move-cursor-to: "#alloc_instead_of_core"
8+
// We wait for the anchor to be visible.
9+
wait-for-css-false: ("#alloc_instead_of_core .anchor", {"display": "none"})
10+
click: "#alloc_instead_of_core .anchor"
11+
// Clicking on the anchor should have two effects:
12+
// 1. Change the URL hash.
13+
// 2. Open the lint.
14+
wait-for-css: ("#alloc_instead_of_core .lint-docs", {"display": "block"})
15+
wait-for-document-property: {"location"."hash": "#alloc_instead_of_core"}
16+
17+
// Now we reload the page. The lint should still be open since the hash is
18+
// targetting it.
19+
go-to: |DOC_PATH| + "#alloc_instead_of_core"
20+
wait-for-css: ("#alloc_instead_of_core .lint-docs", {"display": "block"})
21+
// Other lints should not be expanded.
22+
wait-for-css: ("#absolute_paths .lint-docs", {"display": "none"})

tests/gui/no-js.goml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// This GUI test checks the lints page works as expected when JS is disabled.
2+
javascript: false // disabling javascript
3+
go-to: |DOC_PATH|
4+
5+
define-function: (
6+
"check-expanded-collapsed",
7+
[display, content],
8+
block {
9+
wait-for-css: ("#absolute_paths > .lint-docs", {"display": |display|})
10+
assert-css: ("#absolute_paths .label-doc-folding::before", {"content": |content|})
11+
},
12+
)
13+
14+
define-function: (
15+
"check-expand-collapse-action",
16+
[selector],
17+
block {
18+
// We confirm it's collapsed.
19+
call-function: ("check-expanded-collapsed", {
20+
"display": "none",
21+
"content": '"+"',
22+
})
23+
// We click on the item to expand it.
24+
click: |selector|
25+
// We confirm it's expanded.
26+
call-function: ("check-expanded-collapsed", {
27+
"display": "block",
28+
"content": '"−"',
29+
})
30+
// We collapse it again.
31+
click: |selector|
32+
// We confirm it's collapsed again.
33+
call-function: ("check-expanded-collapsed", {
34+
"display": "none",
35+
"content": '"+"',
36+
})
37+
},
38+
)
39+
40+
// First we check that we can expand/collapse a lint by clicking on the lint.
41+
call-function: ("check-expand-collapse-action", {"selector": "#lint-absolute_paths"})
42+
// Then we check the expand/collapse works when clicking on the +/- button.
43+
call-function: ("check-expand-collapse-action", {"selector": "#absolute_paths .label-doc-folding"})
44+
45+
// Checking click on the anchor changes the location hash.
46+
assert-document-property: {"location"."hash": ""}
47+
click: "#absolute_paths .panel-title .anchor"
48+
assert-document-property: {"location"."hash": "#absolute_paths"}

tests/gui/search.goml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This test ensures that the search is filtering lints correctly.
2+
go-to: |DOC_PATH|
3+
4+
assert-css: ("#absurd_extreme_comparisons", {"display": "block"})
5+
assert-css: ("#absolute_paths", {"display": "block"})
6+
assert-css: ("#join_absolute_paths", {"display": "block"})
7+
8+
// We update the search.
9+
write-into: ("#search-input", "absolute_paths")
10+
11+
// `absolute_paths` and `join_absolute_path` should still be visible, but
12+
// not `absurde_extreme_comparisons`.
13+
wait-for-css: ("#absurd_extreme_comparisons", {"display": "none"})
14+
assert-css: ("#absolute_paths", {"display": "block"})
15+
assert-css: ("#join_absolute_paths", {"display": "block"})

0 commit comments

Comments
 (0)