use forma_server::assets::try_load_manifest;
use forma_server::{load_ir_modules_strict, IrLoadError, IrModules, RenderMode};
use rust_embed::Embed;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::mpsc;
use std::time::Duration;
#[derive(Embed)]
#[folder = "tests/fixtures/modules/good/"]
struct Good;
#[derive(Embed)]
#[folder = "tests/fixtures/modules/flipped/"]
struct Flipped;
#[derive(Embed)]
#[folder = "tests/fixtures/modules/truncated/"]
struct Truncated;
fn good_module() -> Vec<u8> {
use forma_ir::parser::test_helpers::{
build_minimal_ir, encode_close_tag, encode_open_tag, encode_text,
};
let mut ops = vec![0x0B, 0, 0]; ops.extend_from_slice(&encode_open_tag(0, &[]));
ops.extend_from_slice(&encode_text(1));
ops.extend_from_slice(&encode_close_tag(0));
ops.extend_from_slice(&[0x0C, 0, 0]); build_minimal_ir(
&["p", "hello", "Hello"],
&[],
&ops,
&[(0, 0x01, 0x01, 2, 0, &[])],
)
}
fn flip(mut bytes: Vec<u8>, at: usize, mask: u8) -> Vec<u8> {
bytes[at] ^= mask;
bytes
}
fn manifest_json(bad: &[(&str, &str)]) -> String {
let route = |ir: Option<&str>| {
let ir = ir.map_or(String::new(), |f| format!(r#","ir":"{f}""#));
format!(
r#"{{"js":["app.js"],"css":[],"fonts":[],"total_size_br":0,"budget_warn_threshold":0{ir}}}"#
)
};
let mut routes = vec![
format!(r#""/":{}"#, route(Some("page.0f0f.ir"))),
format!(r#""/app":{}"#, route(None)),
];
for (path, file) in bad {
routes.push(format!(r#""{path}":{}"#, route(Some(file))));
}
format!(
"{{\"version\":1,\"build_hash\":\"fixture\",\"assets\":{{}},\"routes\":{{{}}}}}\n",
routes.join(",")
)
}
fn fixture_files() -> Vec<(&'static str, &'static str, Vec<u8>)> {
let good = good_module();
let flipped = [
("/magic", "magic.ir", flip(good.clone(), 0, 0x01)),
("/version", "version.ir", flip(good.clone(), 4, 0x01)),
("/section", "section.ir", flip(good.clone(), 16, 0x80)),
("/opcode", "opcode.ir", flip(good.clone(), 51, 0x80)),
("/hash", "hash.ir", flip(good.clone(), 8, 0x01)),
];
let truncated = [
("/header", "header.ir", good[..10].to_vec()),
("/tail", "tail.ir", good[..good.len() - 1].to_vec()),
];
let mut files = vec![
("good", "manifest.json", manifest_json(&[]).into_bytes()),
("good", "page.0f0f.ir", good.clone()),
];
let routes: Vec<_> = flipped.iter().map(|(r, f, _)| (*r, *f)).collect();
files.push((
"flipped",
"manifest.json",
manifest_json(&routes).into_bytes(),
));
files.push(("flipped", "page.0f0f.ir", good.clone()));
for (_, file, bytes) in flipped {
files.push(("flipped", file, bytes));
}
let routes: Vec<_> = truncated.iter().map(|(r, f, _)| (*r, *f)).collect();
files.push((
"truncated",
"manifest.json",
manifest_json(&routes).into_bytes(),
));
files.push(("truncated", "page.0f0f.ir", good));
for (_, file, bytes) in truncated {
files.push(("truncated", file, bytes));
}
files
}
fn fixture_path(set: &str, file: &str) -> String {
format!(
"{}/tests/fixtures/modules/{set}/{file}",
env!("CARGO_MANIFEST_DIR")
)
}
#[test]
#[ignore = "writes the fixtures; run by hand after changing the generator"]
fn regenerate_module_fixtures() {
for (set, file, bytes) in fixture_files() {
let path = fixture_path(set, file);
std::fs::create_dir_all(std::path::Path::new(&path).parent().expect("dir"))
.expect("fixture dir");
std::fs::write(&path, bytes).expect("fixture written");
}
}
#[test]
fn module_fixtures_are_what_their_generator_writes() {
for (set, file, bytes) in fixture_files() {
let on_disk = std::fs::read(fixture_path(set, file)).unwrap_or_else(|e| {
panic!("{set}/{file} missing ({e}); run regenerate_module_fixtures")
});
assert_eq!(on_disk, bytes, "{set}/{file} differs from its generator");
}
}
fn error_for<'a>(errors: &'a [IrLoadError], route: &str) -> &'a IrLoadError {
let named: Vec<_> = errors.iter().filter(|e| e.route == route).collect();
assert_eq!(named.len(), 1, "one error for {route}: {errors:?}");
named[0]
}
#[test]
fn a_flipped_byte_fails_the_default_loader_naming_route_file_and_code() {
let manifest = try_load_manifest::<Flipped>().expect("manifest");
let errors = load_ir_modules_strict::<Flipped>(&manifest).expect_err("bad modules refuse");
for (route, file, code, cause) in [
("/magic", "magic.ir", "LOAD-102", "bad magic"),
(
"/version",
"version.ir",
"LOAD-102",
"unsupported IR version: 3",
),
("/section", "section.ir", "LOAD-103", "out of bounds"),
("/opcode", "opcode.ir", "LOAD-103", "invalid opcode: 0x81"),
] {
let error = error_for(&errors, route);
assert_eq!(error.ir_file, file);
assert_eq!(error.code(), Some(code), "{error}");
assert!(error.reason.contains(cause), "{error}");
let message = error.to_string();
assert!(
message.contains(route) && message.contains(file) && message.contains(code),
"the message names route, file and code: {message}"
);
}
assert_eq!(errors.len(), 4, "{errors:?}");
for route in ["/", "/app", "/hash"] {
assert!(
errors.iter().all(|e| e.route != route),
"{route}: {errors:?}"
);
}
}
#[test]
fn a_truncated_module_fails_the_default_loader_naming_route_file_and_code() {
let manifest = try_load_manifest::<Truncated>().expect("manifest");
let errors = load_ir_modules_strict::<Truncated>(&manifest).expect_err("refuses");
assert_eq!(errors.len(), 2, "{errors:?}");
for (route, file, cause) in [
("/header", "header.ir", "buffer too short"),
("/tail", "tail.ir", "out of bounds"),
] {
let error = error_for(&errors, route);
assert_eq!(error.ir_file, file);
assert_eq!(error.code(), Some("LOAD-103"), "{error}");
assert!(error.reason.contains(cause), "{error}");
}
}
#[test]
fn a_route_declared_ssr_false_needs_no_module() {
let manifest = try_load_manifest::<Good>().expect("manifest");
let (modes, modules) = load_ir_modules_strict::<Good>(&manifest).expect("all good");
assert_eq!(modes.get("/"), Some(&RenderMode::Phase2SsrReconcile));
assert!(modules.contains_key("/"));
assert_eq!(modes.get("/app"), None);
assert!(!modules.contains_key("/app"));
}
#[test]
fn ir_modules_load_refuses_exactly_what_the_strict_loader_refuses() {
fn sorted(mut errors: Vec<IrLoadError>) -> Vec<IrLoadError> {
errors.sort_by(|a, b| a.route.cmp(&b.route));
errors
}
let flipped = try_load_manifest::<Flipped>().expect("manifest");
assert_eq!(
sorted(IrModules::<Flipped>::load(&flipped).expect_err("refused")),
sorted(load_ir_modules_strict::<Flipped>(&flipped).expect_err("refused"))
);
let truncated = try_load_manifest::<Truncated>().expect("manifest");
assert_eq!(
sorted(IrModules::<Truncated>::load(&truncated).expect_err("refused")),
sorted(load_ir_modules_strict::<Truncated>(&truncated).expect_err("refused"))
);
let good = try_load_manifest::<Good>().expect("manifest");
let modules = IrModules::<Good>::load(&good).expect("loads");
let (_, strict) = load_ir_modules_strict::<Good>(&good).expect("loads");
let page = modules.get("/").expect("the good page");
assert_eq!(page.opcodes, strict["/"].opcodes);
assert!(modules.get("/app").is_none(), "ssr: false has no module");
}
#[test]
#[allow(deprecated)]
fn the_deprecated_lenient_loader_behaves_as_at_0_2_1() {
let manifest = try_load_manifest::<Flipped>().expect("manifest");
let (modes, modules) = forma_server::load_ir_modules::<Flipped>(&manifest);
for route in ["/magic", "/version", "/section", "/opcode"] {
assert_eq!(
modes.get(route),
Some(&RenderMode::Phase1ClientMount),
"{route}"
);
assert!(!modules.contains_key(route), "{route}");
}
for route in ["/", "/hash"] {
assert_eq!(
modes.get(route),
Some(&RenderMode::Phase2SsrReconcile),
"{route}"
);
assert!(modules.contains_key(route), "{route}");
}
assert_eq!(modes.get("/app"), None);
assert_eq!(modes.len(), 6);
}
#[derive(Debug, Clone, Copy)]
enum Loader {
Default,
Lenient,
}
#[derive(Debug)]
#[allow(dead_code)] enum StartError {
Manifest(String),
Modules(Vec<IrLoadError>),
Io(std::io::Error),
}
fn serve<A: Embed>(addr: SocketAddr, loader: Loader) -> Result<(), StartError> {
let manifest = try_load_manifest::<A>().map_err(StartError::Manifest)?;
let modules = match loader {
Loader::Default => {
load_ir_modules_strict::<A>(&manifest)
.map_err(StartError::Modules)?
.1
}
#[allow(deprecated)]
Loader::Lenient => forma_server::load_ir_modules::<A>(&manifest).1,
};
let listener = TcpListener::bind(addr).map_err(StartError::Io)?;
let (mut stream, _) = listener.accept().map_err(StartError::Io)?;
write!(stream, "{} server-rendered pages", modules.len()).map_err(StartError::Io)?;
Ok(())
}
fn free_addr() -> SocketAddr {
let probe = TcpListener::bind("127.0.0.1:0").expect("an ephemeral port");
probe.local_addr().expect("its address")
}
fn connect(addr: SocketAddr) -> Option<TcpStream> {
for _ in 0..40 {
if let Ok(stream) = TcpStream::connect_timeout(&addr, Duration::from_millis(50)) {
return Some(stream);
}
std::thread::sleep(Duration::from_millis(50));
}
None
}
fn spawn<A: Embed + 'static>(
addr: SocketAddr,
loader: Loader,
) -> mpsc::Receiver<Result<(), StartError>> {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let _ = tx.send(serve::<A>(addr, loader));
});
rx
}
#[test]
fn with_a_bad_module_the_fixture_server_binds_no_port() {
let addr = free_addr();
let started = spawn::<Flipped>(addr, Loader::Default);
match started.recv_timeout(Duration::from_secs(5)) {
Ok(Err(StartError::Modules(errors))) => {
let routes: Vec<_> = errors.iter().map(|e| e.route.as_str()).collect();
assert_eq!(errors.len(), 4, "{routes:?}");
}
other => panic!("expected a refused start, got {other:?}"),
}
assert!(
TcpStream::connect_timeout(&addr, Duration::from_millis(200)).is_err(),
"a refused server must not have bound {addr}"
);
}
#[test]
fn the_same_server_binds_and_serves_with_good_modules_or_the_lenient_loader() {
for (set, loader, expected) in [
("good", Loader::Default, "1 server-rendered pages"),
("flipped", Loader::Lenient, "2 server-rendered pages"),
] {
let addr = free_addr();
let started = match set {
"good" => spawn::<Good>(addr, loader),
_ => spawn::<Flipped>(addr, loader),
};
let mut stream = connect(addr).unwrap_or_else(|| panic!("{set}: nothing bound {addr}"));
let mut reply = String::new();
stream.read_to_string(&mut reply).expect("reply");
assert_eq!(reply, expected, "{set} with {loader:?}");
assert!(matches!(
started.recv_timeout(Duration::from_secs(5)),
Ok(Ok(()))
));
}
}