#![cfg(feature = "bin")]
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
const BURN: &str = env!("CARGO_BIN_EXE_burn");
static DIR_CTR: AtomicU32 = AtomicU32::new(0);
fn tmp_dir(label: &str) -> PathBuf {
let n = DIR_CTR.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let dir = std::env::temp_dir().join(format!("burn_round3_{label}_{pid}_{n}"));
fs::create_dir_all(&dir).unwrap();
dir
}
fn run_script(dir: &Path, name: &str, contents: &[u8]) -> std::process::Output {
let path = dir.join(name);
let mut f = fs::File::create(&path).expect("create script");
f.write_all(contents).expect("write script");
Command::new(BURN)
.env("BURN_QUIET", "1")
.env("BURN_SHARDS", "2")
.arg("-A")
.arg(&path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn burn")
}
fn expect_ok_with_marker(out: &std::process::Output, marker: &str) {
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
out.status.success(),
"exit failure. stdout={stdout} stderr={stderr}"
);
assert!(
stdout.contains(marker),
"missing marker `{marker}`. stdout={stdout} stderr={stderr}"
);
}
#[test]
fn crypto_get_ciphers_includes_aes_gcm() {
let dir = tmp_dir("get_ciphers");
let out = run_script(
&dir,
"main.js",
br#"
const crypto = require('crypto');
const cs = crypto.getCiphers();
if (!Array.isArray(cs)) throw new Error('getCiphers: not an array');
for (const c of ['aes-128-gcm','aes-192-gcm','aes-256-gcm','aes-128-cbc','aes-256-cbc']) {
if (!cs.includes(c)) throw new Error('missing cipher ' + c);
}
console.log('ok');
"#,
);
expect_ok_with_marker(&out, "ok");
}
#[test]
fn os_constants_errno_destructure() {
let dir = tmp_dir("os_errno");
let out = run_script(
&dir,
"main.js",
br#"
const {
constants: {
errno: { EEXIST, EISDIR, EINVAL, ENOTDIR, ENOENT },
signals: { SIGINT, SIGTERM },
priority: { PRIORITY_NORMAL },
}
} = require('os');
if (EEXIST !== 17) throw new Error('EEXIST: ' + EEXIST);
if (EISDIR !== 21) throw new Error('EISDIR: ' + EISDIR);
if (EINVAL !== 22) throw new Error('EINVAL: ' + EINVAL);
if (ENOTDIR !== 20) throw new Error('ENOTDIR: ' + ENOTDIR);
if (ENOENT !== 2) throw new Error('ENOENT: ' + ENOENT);
if (SIGINT !== 2) throw new Error('SIGINT: ' + SIGINT);
if (SIGTERM !== 15) throw new Error('SIGTERM: ' + SIGTERM);
if (PRIORITY_NORMAL !== 0) throw new Error('PRIORITY_NORMAL: ' + PRIORITY_NORMAL);
console.log('ok');
"#,
);
expect_ok_with_marker(&out, "ok");
}
#[test]
fn console_assert_and_warn_exist() {
let dir = tmp_dir("console_full");
let out = run_script(
&dir,
"main.js",
br#"
// Throwing on these calls is what would surface the bug.
console.assert(true, 'should not log');
console.assert(false, 'silenced'); // no-throw, just logs
console.warn('warn-ok');
console.info('info-ok');
console.debug('debug-ok');
console.trace('trace-ok');
console.group(); console.groupEnd();
console.time('t'); console.timeEnd('t');
console.log('marker');
"#,
);
expect_ok_with_marker(&out, "marker");
}
#[test]
fn http_agent_is_constructable_and_subclassable() {
let dir = tmp_dir("http_agent");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
const https = require('https');
const a = new http.Agent({ keepAlive: true, maxSockets: 5 });
if (a.keepAlive !== true) throw new Error('keepAlive');
if (a.maxSockets !== 5) throw new Error('maxSockets');
class Sub extends http.Agent {
constructor(opts) { super(opts); this.tag = 'sub'; }
}
const s = new Sub({ keepAlive: false });
if (s.tag !== 'sub') throw new Error('subclass tag');
if (s.keepAlive !== false) throw new Error('subclass opts');
// https.Agent is a separate constructor
if (typeof https.Agent !== 'function') throw new Error('no https.Agent');
const h = new https.Agent();
if (h.protocol !== 'https:') throw new Error('protocol: ' + h.protocol);
console.log('ok');
"#,
);
expect_ok_with_marker(&out, "ok");
}
#[test]
fn http_client_request_returns_event_emitter() {
let dir = tmp_dir("http_req_ee");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
const req = http.request({ hostname: 'example.com', port: 80, path: '/' }, () => {});
if (typeof req.on !== 'function') throw new Error('req.on');
if (typeof req.end !== 'function') throw new Error('req.end');
// No-throw error registration:
req.on('error', () => {});
req.end();
console.log('ok');
"#,
);
expect_ok_with_marker(&out, "ok");
}
#[test]
fn http_get_supports_url_options_callback_form() {
let dir = tmp_dir("http_get_3arg");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
// 3-arg form must accept an options object without
// calling it. Stub a known-bad URL to keep the test
// hermetic; we only care that the *call* works.
try {
const req = http.get('http://127.0.0.1:1/', { agent: false }, () => {});
if (req && typeof req.on === 'function') console.log('callsig-ok');
} catch (e) {
// Connection-refused is a fine error here. The host
// bridge is sync and this address will not bind. The
// important thing is that we got past the callsig
// dispatch without "is not a function".
if (/refused|connection|host/i.test(String(e && e.message))) {
console.log('callsig-ok');
} else {
console.log('UNEXPECTED:', e && e.message);
}
}
"#,
);
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stdout.contains("callsig-ok")
|| stderr.contains("connection")
|| stderr.contains("refused"),
"callsig regression. stdout={stdout} stderr={stderr}"
);
}
#[test]
fn dynamic_import_resolves_through_require() {
let dir = tmp_dir("dyn_import_basic");
let out = run_script(
&dir,
"main.js",
br#"
async function main() {
const m = await import('path');
if (typeof m.sep !== 'string') throw new Error('m.sep missing');
if (typeof m.default !== 'object') throw new Error('m.default missing');
console.log('ok');
}
main().catch(e => { console.log('FAIL:', e && e.message); process.exit(1); });
"#,
);
expect_ok_with_marker(&out, "ok");
}
#[test]
fn dynamic_import_relative_to_caller_dir() {
let dir = tmp_dir("dyn_import_caller_scope");
fs::write(
dir.join("inner.js"),
b"module.exports = async function () { const s = await import('./sibling'); return s.value || s.default; };\n",
).unwrap();
fs::write(
dir.join("sibling.js"),
b"module.exports = { value: 'sib-loaded' };\n",
)
.unwrap();
let out = run_script(
&dir,
"main.js",
br#"
const inner = require('./inner');
inner().then(v => console.log('GOT:', v),
e => { console.log('FAIL:', e && e.message); process.exit(1); });
"#,
);
expect_ok_with_marker(&out, "GOT: sib-loaded");
}
#[test]
fn require_resolves_subpath_imports_from_package_json() {
let dir = tmp_dir("subpath_imports");
let pkg_dir = dir.join("pkg");
fs::create_dir_all(pkg_dir.join("lib")).unwrap();
fs::write(
pkg_dir.join("package.json"),
br##"{
"name":"pkg",
"main":"./index.js",
"imports": {
"#util": "./lib/util.js",
"#cond": {
"node": "./lib/util.js",
"default": "./lib/util.js"
}
}
}"##,
)
.unwrap();
fs::write(
pkg_dir.join("lib").join("util.js"),
b"module.exports = { id: 'util-from-subpath' };\n",
)
.unwrap();
fs::write(
pkg_dir.join("index.js"),
b"const u = require('#util'); const c = require('#cond'); console.log('GOT:', u.id, c.id);\n",
)
.unwrap();
let out = Command::new(BURN)
.env("BURN_QUIET", "1")
.env("BURN_SHARDS", "2")
.arg("-A")
.arg(pkg_dir.join("index.js"))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn burn");
expect_ok_with_marker(&out, "GOT: util-from-subpath util-from-subpath");
}
#[test]
fn require_subpath_imports_throws_named_error_when_unknown() {
let dir = tmp_dir("subpath_imports_missing");
let pkg_dir = dir.join("pkg");
fs::create_dir_all(&pkg_dir).unwrap();
fs::write(
pkg_dir.join("package.json"),
br##"{ "name": "pkg", "imports": { "#a": "./a.js" } }"##,
)
.unwrap();
fs::write(
pkg_dir.join("index.js"),
b"try { require('#missing'); } catch (e) { console.log('CODE:', e.code, '|MSG:', e.message); }\n",
)
.unwrap();
let out = Command::new(BURN)
.env("BURN_QUIET", "1")
.env("BURN_SHARDS", "2")
.arg("-A")
.arg(pkg_dir.join("index.js"))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn burn");
expect_ok_with_marker(&out, "CODE: ERR_PACKAGE_IMPORT_NOT_DEFINED");
}
#[test]
fn http_incoming_message_emits_data_and_end() {
let dir = tmp_dir("incoming_data_end");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
const req = http.request({ hostname: 'example.com', port: 80, path: '/' }, res => {
let bytes = 0;
res.on('data', chunk => { bytes += (chunk && chunk.length) ? chunk.length : 0; });
res.on('end', () => console.log('END bytes>=0:', bytes >= 0));
res.on('error', e => console.log('UNEXPECTED ERR:', e && e.message));
});
req.end();
"#,
);
expect_ok_with_marker(&out, "END bytes>=0: true");
}
#[test]
fn http_incoming_message_resume_then_attach_end_listener() {
let dir = tmp_dir("incoming_resume");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
const req = http.request({ hostname: 'example.com', port: 80, path: '/' }, res => {
res.resume();
res.on('end', () => console.log('END-OK'));
});
req.end();
"#,
);
expect_ok_with_marker(&out, "END-OK");
}
#[test]
fn http_incoming_message_async_iteration() {
let dir = tmp_dir("incoming_aiter");
let out = run_script(
&dir,
"main.js",
br#"
const http = require('http');
async function run() {
const res = await new Promise(resolve => {
const r = http.request({ hostname: 'example.com', port: 80, path: '/' }, resolve);
r.end();
});
let n = 0;
for await (const chunk of res) n++;
console.log('CHUNKS-PROCESSED:', n >= 0 ? 'ok' : 'bad');
}
run().catch(e => { console.log('FAIL:', e && e.message); process.exit(1); });
"#,
);
expect_ok_with_marker(&out, "CHUNKS-PROCESSED: ok");
}