use crate::*;
use crate::jvm::JniError;
use jni_sys::*;
use std::convert::*;
use std::fmt::{self, Debug, Display, Formatter};
use std::ptr::null_mut;
use std::sync::Mutex;
pub type Result<T> = std::result::Result<T, JavaTestError>;
#[derive(Clone)]
pub enum JavaTestError {
Unknown(String),
#[doc(hidden)] _NonExhaustive,
}
impl Display for JavaTestError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
match self {
JavaTestError::Unknown(message) => write!(fmt, "{}", message),
JavaTestError::_NonExhaustive => write!(fmt, "NonExhaustive"),
}
}
}
impl Debug for JavaTestError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
Display::fmt(self, fmt)
}
}
impl<'a> From<&'a str> for JavaTestError {
fn from(value: &'a str) -> Self {
JavaTestError::Unknown(value.to_string())
}
}
impl From<String> for JavaTestError {
fn from(value: String) -> Self {
JavaTestError::Unknown(value)
}
}
#[macro_export] macro_rules! run_test {
( $package:expr, $class:expr, $method:expr ) => {{
$crate::test::run_test_impl(env!("JERK_BUILD_JAR"), $package, $class, $method).unwrap()
}};
}
#[doc(hidden)]
pub fn run_test_impl(jar: &str, package: &str, class: &str, method: &str) -> Result<()> {
let mut reused_vm = false;
{
let mut vm = VM.lock().unwrap();
if vm.is_null() {
**vm = create_java_vm(jar, &mut reused_vm);
}
}
let env = test_thread_env();
if env == null_mut() { return Err("Couldn't initialize Java VM".into()); }
let class_id = format!("{}/{}\0", package.replace(".", "/"), class);
let method_id = format!("{}\0", method);
unsafe {
let class_id = (**env).FindClass.unwrap()(env, class_id.as_ptr() as *const _);
if class_id.is_null() {
panic!(
concat!(
"Failed to find class {package}.{class}. Possible causes:\n",
"{reused_vm_warning}",
" - Typos in the package or class name.\n",
" - The corresponding .jar may have been built with a newer JDK (are you mixing old and new JDKs for 32-bit and 64-bit?)\n",
" - The corresponding .jar may not be have been found (are you using `jerk_build::metabuild()` in your build.rs?)\n",
),
package=package, class=class,
reused_vm_warning = if reused_vm { " - WARNING: jerk::run_test! was unable to start a new JVM / specify a java.class.path. The .jar may not have been loaded!\n" } else { "" },
);
}
let method_id = (**env).GetStaticMethodID.unwrap()(env, class_id, method_id.as_ptr() as *const _, "()V\0".as_ptr() as *const _);
assert_ne!(method_id, null_mut(), "Failed to GetStaticMethodID {}.{}", class, method);
(**env).CallStaticVoidMethodA.unwrap()(env, class_id, method_id, [].as_ptr());
if (**env).ExceptionCheck.unwrap()(env) == JNI_TRUE {
(**env).ExceptionDescribe.unwrap()(env);
(**env).ExceptionClear.unwrap()(env);
Err(format!("{}.{}() threw a Java Exception", class, method).into())
} else {
Ok(())
}
}
}
lazy_static::lazy_static! { static ref JVM : jvm::Library = jvm::Library::get().unwrap(); }
pub fn test_vm() -> *mut JavaVM {
let vm = **VM.lock().unwrap();
debug_assert!(!vm.is_null(), "VM is null, are you trying to access the test_vm outside of a `run_test!`?");
vm
}
lazy_static::lazy_static! { static ref VM : Mutex<ThreadSafe<*mut JavaVM>> = Mutex::new(ThreadSafe(null_mut())); }
pub fn test_thread_env() -> *mut JNIEnv { ENV.with(|e| *e) }
thread_local! { static ENV : *mut JNIEnv = attach_current_thread(); }
fn attach_current_thread() -> *mut JNIEnv {
let vm = test_vm();
let mut env = null_mut();
assert_eq!(JNI_OK, unsafe { (**vm).AttachCurrentThread.unwrap()(vm, &mut env, null_mut()) });
env as *mut _
}
fn create_java_vm(jar: &str, reused_vm: &mut bool) -> *mut JavaVM {
match JVM.create_java_vm(vec![
"-ea".to_string(), "-esa".to_string(), format!("-Djava.class.path={}", jar),
]) {
Err(JniError::EXIST) => {
if let Some(vm) = JVM.get_created_java_vms().unwrap().into_iter().next() {
*reused_vm = true;
vm
} else {
panic!("JNI_EEXIST error creating Java VM, but unable to get an existing VM");
}
},
Err(err) => panic!("JNI error creating Java VM: {}", err),
Ok(vm) => vm
}
}
struct ThreadSafe<T>(pub T);
impl<T> std::ops::Deref for ThreadSafe<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } }
impl<T> std::ops::DerefMut for ThreadSafe<T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } }
unsafe impl<T> Send for ThreadSafe<T> {}
unsafe impl<T> Sync for ThreadSafe<T> {}