blob: 65971d78f59fb4a682386337919329342d069bd6 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Java integration for the `Foo.java` test class.
//!
//! There are pourover descriptors for each member of `Foo`. Keep these in sync with changes to
//! `Foo.java`.
//!
//! Compilation is done at runtime. There isn't a way to create a build.rs for an integration test.
//! use [`compile_foo`] to get the class bytes and load the class using
//! [`jni::JNIEnv::define_class`].
use pourover::desc::*;
use std::error::Error;
use std::process::Command;
pub const CLASS_DESC: &str = "com/example/Foo";
pub static FOO: ClassDesc = ClassDesc::new(CLASS_DESC);
pub static FIELD: FieldDesc = FOO.field("foo", "I");
pub static STATIC_FIELD: StaticFieldDesc = FOO.static_field("sfoo", "J");
pub static CONSTRUCTOR: MethodDesc = FOO.constructor("(I)V");
pub static METHOD: MethodDesc = FOO.method("mfoo", "()Z");
pub static STATIC_METHOD: StaticMethodDesc = FOO.static_method("smfoo", "()I");
pub static NATIVE_METHOD: MethodDesc = FOO.method("nativeReturnsInt", "(I)I");
pub static NATIVE_OBJECT_METHOD: MethodDesc =
FOO.method("nativeReturnsObject", "(I)Ljava/lang/String;");
/// Quick test to ensure that Java is available on the system. The version will be logged to
/// stdout.
#[test]
fn has_java() -> Result<(), Box<dyn Error>> {
Command::new("java").arg("--version").status()?;
Ok(())
}
/// Compile `Foo.java` and return the class file's bytes.
#[track_caller]
pub fn compile_foo() -> Result<Vec<u8>, Box<dyn Error>> {
// Avoid concurrent tests colliding on the file system by adding a hash of the call-site to the
// temp file path
let caller_hash = {
use core::hash::{Hash, Hasher};
let caller = core::panic::Location::caller();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
caller.hash(&mut hasher);
hasher.finish()
};
// Create a temporary dir to generate class files
let mut tmp = std::env::temp_dir();
tmp.push(format!("test-java-build-{caller_hash:016x}"));
if let Err(err) = std::fs::create_dir(&tmp) {
println!("error creating {}: {}", tmp.display(), &err);
}
// Compile Foo.java into the temp dir
Command::new("javac")
.args(["--release", "8"])
.arg("-d")
.arg(&tmp)
.arg("tests/Foo.java")
.status()?;
// Read the class file bytes
let class = {
let mut class_file = tmp.clone();
class_file.push("com/example/Foo.class");
std::fs::read(&class_file)?
};
// Clean up the temp dir. If an error occurs before this we will leave the temp dir on the
// filesystem. That should work better for debugging than cleaning up in all cases.
std::fs::remove_dir_all(&tmp)?;
Ok(class)
}