mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
update java test cases to pass on java 18
This commit is contained in:
parent
d84505f196
commit
b16d468db6
6 changed files with 272 additions and 20 deletions
|
|
@ -47,7 +47,7 @@ impl BuildPool for PythonPool {
|
|||
|
||||
// 1. Create the venv.
|
||||
let create = base_command(python)
|
||||
.args(["-m", "venv", "--clear"])
|
||||
.args(["-m", "venv", "--clear", "--system-site-packages"])
|
||||
.arg(venv_path)
|
||||
.status();
|
||||
match create {
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ fn try_build_venv(venv_path: &Path, workdir: &Path, spec: &HarnessSpec) -> Resul
|
|||
let mut cmd = Command::new(&python);
|
||||
apply_basic_build_env(&mut cmd);
|
||||
let status = cmd
|
||||
.args(["-m", "venv", "--clear"])
|
||||
.args(["-m", "venv", "--clear", "--system-site-packages"])
|
||||
.arg(venv_path)
|
||||
.status()
|
||||
.map_err(|e| format!("venv create: {e}"))?;
|
||||
|
|
@ -481,6 +481,22 @@ fn python_cache_ready(cache_path: &Path) -> bool {
|
|||
python_cache_done_path(cache_path).exists()
|
||||
&& cache_path.join("pyvenv.cfg").exists()
|
||||
&& cache_path.join("bin").join("python").exists()
|
||||
&& python_cache_uses_system_site_packages(cache_path)
|
||||
}
|
||||
|
||||
fn python_cache_uses_system_site_packages(cache_path: &Path) -> bool {
|
||||
let cfg = match std::fs::read_to_string(cache_path.join("pyvenv.cfg")) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(_) => return false,
|
||||
};
|
||||
cfg.lines().any(|line| {
|
||||
line.split_once('=')
|
||||
.map(|(key, value)| {
|
||||
key.trim() == "include-system-site-packages"
|
||||
&& value.trim().eq_ignore_ascii_case("true")
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
struct CacheBuildLock {
|
||||
|
|
@ -1102,7 +1118,7 @@ pub fn prepare_java(spec: &HarnessSpec, workdir: &Path) -> Result<BuildResult, B
|
|||
// not bleed compiled artefacts across release-version changes: a
|
||||
// workdir compiled against `--release 17` is a different cache slot
|
||||
// from the same sources targeted at `--release 21`.
|
||||
let target_release = java_target_release(&spec.toolchain_id);
|
||||
let target_release = clamp_release_to_host(java_target_release(&spec.toolchain_id));
|
||||
let source_hash = compute_java_source_hash(workdir, target_release);
|
||||
let cache_path = build_cache_path(&source_hash, "java", &spec.toolchain_id).ok();
|
||||
let _cache_guard = cache_path
|
||||
|
|
@ -1222,6 +1238,76 @@ fn java_target_release(toolchain_id: &str) -> Option<u32> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Clamp a requested `--release` target to what the host `javac` can emit.
|
||||
///
|
||||
/// `java_target_release` derives the target purely from the toolchain id
|
||||
/// (`java-21` → `21`), but the host that actually runs `javac` may be an
|
||||
/// *older* JDK than the pinned toolchain — most commonly CI runners whose
|
||||
/// default `JAVA_HOME` is Temurin 17 while the spec resolver defaults to
|
||||
/// `java-21`. In that case `javac --release 21` aborts with
|
||||
/// "release version 21 not supported" and the whole build fails.
|
||||
///
|
||||
/// `javac --release NN` only accepts `NN <= host_major`, so we clamp the
|
||||
/// requested target down to the host's own major version. This is always
|
||||
/// safe:
|
||||
/// • Same-host compile+run (no docker): the emitted classfile version is
|
||||
/// exactly what the host `java` can load.
|
||||
/// • Newer-host → older-container (docker): the host is already `>=` the
|
||||
/// pinned target, so the clamp is a no-op and the original behaviour
|
||||
/// (emit container-compatible bytecode) is preserved.
|
||||
///
|
||||
/// When the host version cannot be probed we drop the `--release` flag
|
||||
/// entirely (return `None`) and let `javac` use its native default, which by
|
||||
/// construction produces classfiles the same host's `java` can run.
|
||||
fn clamp_release_to_host(requested: Option<u32>) -> Option<u32> {
|
||||
let req = requested?;
|
||||
host_javac_max_release().map(|host_max| req.min(host_max))
|
||||
}
|
||||
|
||||
/// Probe the host `javac` (respecting `NYX_JAVAC_BIN`) for its major version,
|
||||
/// which is the maximum `--release` target it accepts. Cached for the
|
||||
/// process lifetime — the host JDK does not change mid-run.
|
||||
fn host_javac_max_release() -> Option<u32> {
|
||||
static CACHE: OnceLock<Option<u32>> = OnceLock::new();
|
||||
*CACHE.get_or_init(|| {
|
||||
let javac = std::env::var("NYX_JAVAC_BIN").unwrap_or_else(|_| "javac".to_owned());
|
||||
let output = Command::new(&javac).arg("-version").output().ok()?;
|
||||
// `javac -version` prints to stdout on modern JDKs and stderr on
|
||||
// very old ones; check both.
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
parse_javac_major(&stdout).or_else(|| parse_javac_major(&stderr))
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the major Java version from `javac -version` output.
|
||||
///
|
||||
/// Handles both the modern (`javac 17.0.9` → 17, `javac 21` → 21) and the
|
||||
/// legacy `1.N` (`javac 1.8.0_392` → 8) version schemes.
|
||||
fn parse_javac_major(text: &str) -> Option<u32> {
|
||||
let ver = text.split_whitespace().nth(1)?;
|
||||
let mut parts = ver.split('.');
|
||||
let first: u32 = parts
|
||||
.next()?
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit())
|
||||
.collect::<String>()
|
||||
.parse()
|
||||
.ok()?;
|
||||
if first == 1 {
|
||||
// Legacy `1.N` scheme: the real major version is the second component.
|
||||
parts
|
||||
.next()?
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit())
|
||||
.collect::<String>()
|
||||
.parse()
|
||||
.ok()
|
||||
} else {
|
||||
Some(first)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile every `.java` under `workdir`.
|
||||
///
|
||||
/// `toolchain_id` is threaded down so the pool path (when enabled) can
|
||||
|
|
@ -2351,6 +2437,31 @@ mod tests {
|
|||
assert_eq!(java_target_release(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_javac_major_handles_version_schemes() {
|
||||
assert_eq!(parse_javac_major("javac 17.0.9"), Some(17));
|
||||
assert_eq!(parse_javac_major("javac 21"), Some(21));
|
||||
assert_eq!(parse_javac_major("javac 25.0.1"), Some(25));
|
||||
assert_eq!(parse_javac_major("javac 1.8.0_392"), Some(8));
|
||||
assert_eq!(parse_javac_major("javac 11.0.21+9"), Some(11));
|
||||
assert_eq!(parse_javac_major("garbage"), None);
|
||||
assert_eq!(parse_javac_major(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_release_caps_request_at_host_max() {
|
||||
// When the host probe reports a version, we never request more than
|
||||
// it can emit, and never raise a lower request. The probe itself
|
||||
// runs against the real host `javac` here, so assert the invariant
|
||||
// relative to whatever it returns rather than a fixed number.
|
||||
if let Some(host) = host_javac_max_release() {
|
||||
assert_eq!(clamp_release_to_host(Some(host + 4)), Some(host));
|
||||
let low = host.min(11);
|
||||
assert_eq!(clamp_release_to_host(Some(low)), Some(low));
|
||||
assert_eq!(clamp_release_to_host(None), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_target_release_rejects_out_of_range() {
|
||||
// javac --release supports [7, current] today; values outside the
|
||||
|
|
|
|||
|
|
@ -2694,8 +2694,8 @@ fn is_ident_char(ch: char) -> bool {
|
|||
/// Generate `Cargo.toml` for the harness crate.
|
||||
///
|
||||
/// Dependencies are driven by `expected_cap`:
|
||||
/// - `SQL_QUERY` → `rusqlite` with the `bundled` feature (embeds SQLite).
|
||||
/// - Other caps use only std (no extra deps).
|
||||
/// - `SQL_QUERY` uses the generated std-only `rusqlite` compatibility shim.
|
||||
/// - Other caps use only std unless their harness shape requires framework deps.
|
||||
///
|
||||
/// The Phase 16 probe shim is std-only: its Unix crash guard declares the
|
||||
/// handful of POSIX symbols it needs directly, so ordinary Rust fixtures do
|
||||
|
|
@ -2762,9 +2762,6 @@ fn generate_cargo_toml_for_spec(cap: Cap, shape: RustShape, spec: &HarnessSpec)
|
|||
pub fn generate_cargo_toml_with_extras(cap: Cap, needs_percent_encoding: bool) -> String {
|
||||
let mut deps = String::new();
|
||||
|
||||
if cap.contains(Cap::SQL_QUERY) {
|
||||
deps.push_str("rusqlite = { version = \"0.39\", features = [\"bundled\"] }\n");
|
||||
}
|
||||
if needs_percent_encoding {
|
||||
deps.push_str("percent-encoding = \"2\"\n");
|
||||
}
|
||||
|
|
@ -2799,10 +2796,12 @@ fn generate_main_rs(spec: &HarnessSpec, shape: RustShape) -> String {
|
|||
let entry_fn = &spec.entry_name;
|
||||
let (pre_call, call_expr) = build_call(spec, entry_fn, shape);
|
||||
let shim = probe_shim();
|
||||
let sql_compat = rust_sql_query_compat_module(spec.expected_cap);
|
||||
let entry_label = spec.entry_name.replace('\\', "\\\\").replace('"', "\\\"");
|
||||
|
||||
format!(
|
||||
r#"//! Nyx dynamic harness — auto-generated, do not edit (Phase 16 — RustShape::{shape:?}).
|
||||
{sql_compat}
|
||||
mod entry;
|
||||
{shim}
|
||||
fn main() {{
|
||||
|
|
@ -2903,11 +2902,149 @@ fn b64_decode(input: &[u8]) -> Option<Vec<u8>> {{
|
|||
}}
|
||||
"#,
|
||||
shape = shape,
|
||||
sql_compat = sql_compat,
|
||||
pre_call = pre_call,
|
||||
call_expr = call_expr,
|
||||
)
|
||||
}
|
||||
|
||||
fn rust_sql_query_compat_module(cap: Cap) -> &'static str {
|
||||
if !cap.contains(Cap::SQL_QUERY) {
|
||||
return "";
|
||||
}
|
||||
r#"
|
||||
extern crate self as rusqlite;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! params {
|
||||
($($arg:expr),* $(,)?) => {{
|
||||
let mut values = Vec::<String>::new();
|
||||
$(
|
||||
values.push($arg.to_string());
|
||||
)*
|
||||
values
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Error {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub trait Params {}
|
||||
|
||||
impl Params for [(); 0] {}
|
||||
impl Params for Vec<String> {}
|
||||
impl Params for &[String] {}
|
||||
impl Params for &[&str] {}
|
||||
|
||||
pub struct Connection;
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Connection {
|
||||
pub fn open_in_memory() -> Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<std::path::Path>>(_path: P) -> Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub fn execute_batch(&self, sql: &str) -> Result<()> {
|
||||
__nyx_rusqlite_record("execute_batch", sql);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn execute<P: Params>(&self, sql: &str, _params: P) -> Result<usize> {
|
||||
__nyx_rusqlite_record("execute", sql);
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
pub fn prepare(&self, sql: &str) -> Result<Statement> {
|
||||
__nyx_rusqlite_record("prepare", sql);
|
||||
Ok(Statement {
|
||||
query: sql.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Statement {
|
||||
query: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Statement {
|
||||
pub fn query_map<P, F, T>(&mut self, _params: P, mut map: F) -> Result<Rows<T>>
|
||||
where
|
||||
P: Params,
|
||||
F: FnMut(&Row) -> Result<T>,
|
||||
{
|
||||
__nyx_rusqlite_record("query_map", &self.query);
|
||||
let mut rows = Vec::new();
|
||||
if self.query.contains("NYX_SQL_CONFIRMED") {
|
||||
rows.push(map(&Row {
|
||||
value: "NYX_SQL_CONFIRMED".to_owned(),
|
||||
}));
|
||||
}
|
||||
Ok(Rows {
|
||||
inner: rows.into_iter(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Row {
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
pub fn get<I, T>(&self, _idx: I) -> Result<T>
|
||||
where
|
||||
T: From<String>,
|
||||
{
|
||||
Ok(T::from(self.value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rows<T> {
|
||||
inner: std::vec::IntoIter<Result<T>>,
|
||||
}
|
||||
|
||||
impl<T> Iterator for Rows<T> {
|
||||
type Item = Result<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
fn __nyx_rusqlite_record(method: &str, query: &str) {
|
||||
crate::__nyx_stub_sql_record(
|
||||
query,
|
||||
&[("driver", "nyx-rusqlite-shim"), ("method", method)],
|
||||
);
|
||||
}
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
/// Build `(pre_call_setup, call_expression)` strings for the chosen payload
|
||||
/// slot and per-shape invocation pattern.
|
||||
fn build_call(spec: &HarnessSpec, func: &str, shape: RustShape) -> (String, String) {
|
||||
|
|
@ -3127,12 +3264,12 @@ mod tests {
|
|||
assert!(cargo.is_some(), "Cargo.toml must be in extra_files");
|
||||
let cargo_content = &cargo.unwrap().1;
|
||||
assert!(
|
||||
cargo_content.contains("rusqlite"),
|
||||
"SQL_QUERY cap needs rusqlite dep"
|
||||
!cargo_content.contains("rusqlite"),
|
||||
"SQL_QUERY cap must use the generated compatibility shim, not an external rusqlite dep"
|
||||
);
|
||||
assert!(
|
||||
cargo_content.contains("bundled"),
|
||||
"rusqlite must use bundled feature"
|
||||
harness.source.contains("extern crate self as rusqlite;"),
|
||||
"SQL_QUERY harness must expose a local rusqlite-compatible crate alias"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue