Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(bridge): test import and method not found errors #105

Merged
merged 5 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions crates/java-bridge/src/node/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use crate::node::java_class_instance::{JavaClassInstance, CLASS_PROXY_PROPERTY,
use crate::node::java_class_proxy::JavaClassProxy;
use crate::node::java_options::JavaOptions;
use crate::node::stdout_redirect::StdoutRedirect;
use crate::node::util::helpers::{list_files, parse_array_or_string, parse_classpath_args};
use crate::node::util::helpers::{
call_async_method_with_resolver, list_files, parse_array_or_string, parse_classpath_args,
};
use app_state::{stateful, AppStateTrait, MutAppState, MutAppStateLock};
use futures::future;
use java_rs::java_env::JavaEnv;
use java_rs::java_vm::JavaVM;
use java_rs::objects::args::AsJavaArg;
Expand Down Expand Up @@ -102,36 +103,50 @@ impl Java {
/// The imported class will be cached for future use.
#[napi(ts_return_type = "object")]
pub fn import_class(
&mut self,
&self,
env: Env,
class_name: String,
config: Option<ClassConfiguration>,
) -> napi::Result<JsFunction> {
let proxy = MutAppState::<ClassCache>::get_or_insert_default()
let proxy_result = MutAppState::<ClassCache>::get_or_insert_default()
.get_mut()
.get_class_proxy(&self.root_vm, class_name, config)
.map_napi_err(Some(env))?;
JavaClassInstance::create_class_instance(&env, proxy)
.get_class_proxy(&self.root_vm, class_name, config);

// Map the result here in order to release the lock
// on the class cache before mapping the error.
JavaClassInstance::create_class_instance(&env, proxy_result.map_napi_err(Some(env))?)
}

/// Import a java class (async)
/// Will return a promise that resolves to the class instance.
///
/// If the underlying Java throwable should be contained in the error object,
/// set `asyncJavaExceptionObjects` to `true`. This will cause the JavaScript
/// stack trace to be lost. Setting this option in the global config will
/// **not** affect this method, this option has to be set each time this
/// method is called.
///
/// @see importClass
#[napi(ts_return_type = "Promise<object>")]
pub fn import_class_async(
&'static mut self,
&self,
env: Env,
class_name: String,
config: Option<ClassConfiguration>,
) -> napi::Result<JsObject> {
env.execute_tokio_future(
future::lazy(|_| {
let root_vm = self.root_vm.clone();
call_async_method_with_resolver(
env,
config
.as_ref()
.and_then(|c| c.async_java_exception_objects)
.unwrap_or_default(),
move || {
MutAppState::<ClassCache>::get_or_insert_default()
.get_mut()
.get_class_proxy(&self.root_vm, class_name, config)
.map_napi_err(None)
}),
|&mut env, proxy| JavaClassInstance::create_class_instance(&env, proxy),
.get_class_proxy(&root_vm, class_name, config)
},
|env, res| JavaClassInstance::create_class_instance(env, res),
)
}

Expand Down
46 changes: 4 additions & 42 deletions crates/java-bridge/src/node/java_class_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::node::helpers::napi_ext::{load_napi_library, uv_run, uv_run_mode};
use crate::node::interface_proxy::proxies::interface_proxy_exists;
use crate::node::java::Java;
use crate::node::java_class_proxy::JavaClassProxy;
use crate::node::util::helpers::ResultType;
use crate::node::util::helpers::{call_async_method, call_async_method_with_resolver};
use crate::node::util::traits::UnwrapOrEmpty;
use java_rs::java_call_result::JavaCallResult;
use java_rs::java_type::JavaType;
Expand Down Expand Up @@ -328,7 +328,7 @@ impl JavaClassInstance {
let env = proxy.vm.attach_thread().map_napi_err(Some(*ctx.env))?;
let args = call_context_to_java_args(ctx, method.parameter_types(), &env)?;

Self::call_async_method(*ctx.env, proxy, move || {
call_async_method(*ctx.env, proxy, move || {
let args_ref = call_results_to_args(&args);
method.call_static(args_ref.as_slice())
})
Expand Down Expand Up @@ -402,7 +402,7 @@ impl JavaClassInstance {
let env = proxy.vm.attach_thread().map_napi_err(Some(*ctx.env))?;
let args = call_context_to_java_args(ctx, method.parameter_types(), &env)?;

Self::call_async_method(*ctx.env, proxy, move || {
call_async_method(*ctx.env, proxy, move || {
let args_ref = call_results_to_args(&args);
method.call(&obj, args_ref.as_slice())
})
Expand Down Expand Up @@ -440,44 +440,6 @@ impl JavaClassInstance {
)?,
)
}

fn call_async_method<F>(env: Env, proxy: Arc<JavaClassProxy>, func: F) -> napi::Result<JsObject>
where
F: (FnOnce() -> ResultType<JavaCallResult>) + Send + Sync + 'static,
{
Self::call_async_method_with_resolver(
env,
proxy.async_java_exception_objects(),
func,
move |&mut env, res| {
let j_env = proxy.vm.attach_thread().map_napi_err(Some(env))?;
res.to_napi_value(&j_env, &env).map_napi_err(Some(env))
},
)
}

fn call_async_method_with_resolver<F, Res, R>(
env: Env,
async_java_exception_objects: bool,
func: F,
resolver: Res,
) -> napi::Result<JsObject>
where
F: (FnOnce() -> ResultType<R>) + Send + Sync + 'static,
Res: FnOnce(&mut Env, R) -> napi::Result<JsUnknown> + Send + Sync + 'static,
R: Send + Sync + 'static,
{
if async_java_exception_objects {
env.execute_tokio_future(futures::future::lazy(|_| Ok(func())), move |env, res| {
resolver(env, res.map_napi_err(Some(*env))?)
})
} else {
env.execute_tokio_future(
futures::future::lazy(move |_| func().map_napi_err(None)),
resolver,
)
}
}
}

#[js_function(255usize)]
Expand Down Expand Up @@ -521,7 +483,7 @@ fn new_instance(ctx: CallContext) -> napi::Result<JsObject> {
let env = proxy.vm.attach_thread().map_napi_err(Some(*ctx.env))?;
let args = call_context_to_java_args(&ctx, constructor.parameter_types(), &env)?;

JavaClassInstance::call_async_method_with_resolver(
call_async_method_with_resolver(
*ctx.env,
proxy.async_java_exception_objects(),
move || {
Expand Down
45 changes: 44 additions & 1 deletion crates/java-bridge/src/node/util/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use crate::node::extensions::java_call_result_ext::ToNapiValue;
use crate::node::helpers::napi_error::{MapToNapiError, StrIntoNapiError};
use crate::node::java_class_proxy::JavaClassProxy;
use glob::glob;
use napi::{JsString, JsUnknown};
use java_rs::java_call_result::JavaCallResult;
use napi::{Env, JsObject, JsString, JsUnknown, NapiRaw};
use std::error::Error;
use std::sync::Arc;

pub type ResultType<T> = Result<T, Box<dyn Error + Send + Sync>>;

Expand Down Expand Up @@ -72,3 +76,42 @@ pub fn parse_classpath_args(cp: &[String], args: &mut Vec<String>) -> String {
cp.join(separator::CLASSPATH_SEPARATOR)
)
}

pub fn call_async_method<F>(env: Env, proxy: Arc<JavaClassProxy>, func: F) -> napi::Result<JsObject>
where
F: (FnOnce() -> ResultType<JavaCallResult>) + Send + Sync + 'static,
{
call_async_method_with_resolver(
env,
proxy.async_java_exception_objects(),
func,
move |&mut env, res| {
let j_env = proxy.vm.attach_thread().map_napi_err(Some(env))?;
res.to_napi_value(&j_env, &env).map_napi_err(Some(env))
},
)
}

pub fn call_async_method_with_resolver<F, Res, R, V>(
env: Env,
async_java_exception_objects: bool,
func: F,
resolver: Res,
) -> napi::Result<JsObject>
where
F: (FnOnce() -> ResultType<R>) + Send + Sync + 'static,
Res: FnOnce(&mut Env, R) -> napi::Result<V> + Send + Sync + 'static,
R: Send + Sync + 'static,
V: NapiRaw + 'static,
{
if async_java_exception_objects {
env.execute_tokio_future(futures::future::lazy(|_| Ok(func())), move |env, res| {
resolver(env, res.map_napi_err(Some(*env))?)
})
} else {
env.execute_tokio_future(
futures::future::lazy(move |_| func().map_napi_err(None)),
resolver,
)
}
}
4 changes: 3 additions & 1 deletion test/DeleteTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ describe('DeleteTest', () => {
System.gcSync();
const end = getUsedMemory(Runtime);
expect(end).to.be.lessThan(after - 10_000_000);
}).timeout(timeout);
})
.timeout(timeout)
.retries(3);

it('Delete deleted instance', () => {
const JString = importClass<typeof StringClass>('java.lang.String');
Expand Down
55 changes: 55 additions & 0 deletions test/StringTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,59 @@ describe('StringTest', () => {
);
}
});

it('invalid number of arguments', () => {
// @ts-expect-error
expect(() => new JavaString('a', 'b'))
.to.throw()
.and.to.have.property('message')
.which.satisfies((val: string) =>
val.startsWith('No constructor found with matching signature')
);

// @ts-expect-error
expect(() => new JavaString('a', 'b'))
.to.throw()
.and.to.not.have.property('cause');
});

it('import non-existing class', () => {
expect(() => importClass('java.lang.NonExistingClass'))
.to.throw()
.and.to.have.property('message')
.which.satisfies((val: string) =>
val.startsWith(
'java.lang.ClassNotFoundException: java.lang.NonExistingClass'
)
);

expect(() => importClass('java.lang.NonExistingClass'))
.to.throw()
.and.to.have.property('cause')
.which.has.property('printStackTrace')
.which.is.a('function');
});

it('import non-existing class async', async () => {
await expect(
importClassAsync('java.lang.NonExistingClass', {
asyncJavaExceptionObjects: true,
})
)
.to.eventually.be.rejected.and.to.have.property('message')
.which.satisfies((val: string) =>
val.startsWith(
'java.lang.ClassNotFoundException: java.lang.NonExistingClass'
)
);

await expect(
importClassAsync('java.lang.NonExistingClass', {
asyncJavaExceptionObjects: true,
})
)
.to.eventually.be.rejected.and.to.have.property('cause')
.which.has.property('printStackTrace')
.which.is.a('function');
});
});