Skip to content

Commit

Permalink
Add start of GUI tests for clippy lints page
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Nov 7, 2024
1 parent f712eb5 commit 7c2307d
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 0 deletions.
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"})

0 comments on commit 7c2307d

Please sign in to comment.