forked from vercel/turborepo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a very early version of the next-dev test runner. I'm opening this early to get thoughts from folks re: the direction of the design and implementation. Fixes vercel#204 Currently it: * Discovers integration test fixtures from the filesystem. Right now these are expected to be single files that get bundled and will eventually include assertions. This is powered by the test-generator crate, which allows us not to have to manually enumerate each case. We could consider using this for the node-file-trace tests as well. * Starts the dev server on a free port and opens a headless browser to its root. The browser control is implemented with the https://crates.io/crates/chromiumoxide crate, which expects Chrome or Chromium to already be available. Eventually it will: * [x] Implement a minimal test environment loaded in the browser so that assertions can be run there from bundled code. * [x] Report back the results of these assertions to rust, where we can pass/fail cargo tests with those results. In the future it could: * Possibly include snapshot-style tests to assert on transformed results. This could be in the form of fixture directories instead of files cc @jridgewell * Support expressing special configuration of turbopack in a fixture, possibly as another file in the fixture directory. * [x] ~Possibly support distributing tests to a pool of open browsers instead of opening and closing for each test.~ Test Plan: See next PRs
- Loading branch information
1 parent
5b6c244
commit f0747ed
Showing
13 changed files
with
764 additions
and
186 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import * as jest from 'jest-circus-browser/dist/umd/jest-circus.js' | ||
import expect from 'expect/build-es5/index.js' | ||
|
||
globalThis.__jest__ = jest | ||
globalThis.expect = expect | ||
globalThis.describe = jest.describe | ||
globalThis.it = jest.it |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
#![cfg(test)] | ||
extern crate test_generator; | ||
|
||
use std::{net::SocketAddr, path::Path}; | ||
|
||
use chromiumoxide::browser::{Browser, BrowserConfig}; | ||
use futures::StreamExt; | ||
use next_dev::{register, NextDevServerBuilder}; | ||
use serde::Deserialize; | ||
use test_generator::test_resources; | ||
use turbo_tasks::TurboTasks; | ||
use turbo_tasks_memory::MemoryBackend; | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct JestRunResult { | ||
test_results: Vec<JestTestResult>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct JestTestResult { | ||
test_path: Vec<String>, | ||
errors: Vec<String>, | ||
} | ||
|
||
#[test_resources("crates/next-dev/tests/integration/*/*/*")] | ||
#[tokio::main] | ||
async fn test(resource: &str) { | ||
register(); | ||
let path = Path::new(resource) | ||
// test_resources matches and returns relative paths from the workspace root, | ||
// but pwd in cargo tests is the crate under test. | ||
.strip_prefix("crates/next-dev") | ||
.unwrap(); | ||
assert!(path.exists(), "{} does not exist", resource); | ||
|
||
assert!( | ||
path.is_dir(), | ||
"{} is not a directory. Integration tests must be directories.", | ||
path.to_str().unwrap() | ||
); | ||
|
||
if path.ends_with("__skipped__") { | ||
// "Skip" directories named `__skipped__`, which include test directories to | ||
// skip. These tests are not considered truly skipped by `cargo test`, but they | ||
// are not run. | ||
return; | ||
} | ||
|
||
let test_entry = path.join("index.js"); | ||
assert!( | ||
test_entry.exists(), | ||
"Test entry {} must exist.", | ||
test_entry.to_str().unwrap() | ||
); | ||
|
||
let server = NextDevServerBuilder::new() | ||
.turbo_tasks(TurboTasks::new(MemoryBackend::new())) | ||
.project_dir("tests".into()) | ||
.entry_asset("harness.js".into()) | ||
.entry_asset( | ||
test_entry | ||
.strip_prefix("tests") | ||
.unwrap() | ||
.to_str() | ||
.unwrap() | ||
.replace('\\', "/"), | ||
) | ||
.eager_compile(false) | ||
.hostname("127.0.0.1".parse().unwrap()) | ||
.port(portpicker::pick_unused_port().unwrap()) | ||
.build() | ||
.await | ||
.unwrap(); | ||
|
||
println!("server started at http://{}", server.addr); | ||
|
||
tokio::select! { | ||
r = run_browser_test(server.addr) => r.unwrap(), | ||
r = server.future => r.unwrap(), | ||
}; | ||
} | ||
|
||
async fn create_browser() -> Result<Browser, Box<dyn std::error::Error>> { | ||
let (browser, mut handler) = Browser::launch(BrowserConfig::builder().build()?).await?; | ||
// See https://crates.io/crates/chromiumoxide | ||
tokio::task::spawn(async move { | ||
loop { | ||
let _ = handler.next().await.unwrap(); | ||
} | ||
}); | ||
|
||
Ok(browser) | ||
} | ||
|
||
async fn run_browser_test(addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>> { | ||
let browser = create_browser().await?; | ||
|
||
let page = browser.new_page(format!("http://{}", addr)).await?; | ||
|
||
let run_result: JestRunResult = page | ||
.evaluate("__jest__.run()") | ||
.await | ||
.unwrap() | ||
.into_value() | ||
.unwrap(); | ||
|
||
assert!( | ||
!run_result.test_results.is_empty(), | ||
"Expected one or more tests to run." | ||
); | ||
|
||
let mut messages = vec![]; | ||
for test_result in run_result.test_results { | ||
// It's possible to fail multiple tests across these tests, | ||
// so collect them and fail the respective test in Rust with | ||
// an aggregate message. | ||
if !test_result.errors.is_empty() { | ||
messages.push(format!( | ||
"\"{}\":\n{}", | ||
test_result.test_path[1..].join(" > "), | ||
test_result.errors.join("\n") | ||
)); | ||
} | ||
} | ||
|
||
if !messages.is_empty() { | ||
panic!( | ||
"Failed with error(s) in the following test(s):\n\n{}", | ||
messages.join("\n\n--\n") | ||
) | ||
} | ||
|
||
Ok(()) | ||
} |
19 changes: 19 additions & 0 deletions
19
crates/next-dev/tests/integration/turbopack/basic/simple/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
it('runs sync tests', () => { | ||
expect(true).toBe(true) | ||
}) | ||
|
||
it('runs async tests', async () => { | ||
await Promise.resolve() | ||
expect(true).toBe(true) | ||
}) | ||
|
||
describe('nested describe', () => { | ||
it('runs sync tests', () => { | ||
expect(true).toBe(true) | ||
}) | ||
|
||
it('runs async tests', async () => { | ||
await Promise.resolve() | ||
expect(true).toBe(true) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"private": true, | ||
"devDependencies": { | ||
"expect": "^24.5.0", | ||
"jest-circus-browser": "^1.0.7" | ||
}, | ||
"installConfig": { | ||
"hoistingLimits": "workspaces" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.