1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::*;
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 vm = VM.lock().unwrap();
if vm.is_null() {
**vm = create_java_vm(jar);
}
}
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 _);
assert_ne!(
class_id,
null_mut(),
concat!(
"Failed to FindClass {}.{}. Possible causes:\n",
" - 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,
class
);
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) -> *mut JavaVM {
JVM.create_java_vm(vec![
"-ea".to_string(),
"-esa".to_string(),
format!("-Djava.class.path={}", jar),
]).unwrap()
}
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> {}