Skip to content

Commit 892596a

Browse files
skletsundmitry-timofeev
authored andcommitted
Update native testkit JNI code for DS [ECR-3445] (#1161)
1 parent 48104ea commit 892596a

File tree

2 files changed

+160
-29
lines changed

2 files changed

+160
-29
lines changed

exonum-java-binding/core/rust/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ mod proto;
5555
mod proxy;
5656
mod runtime;
5757
mod storage;
58-
//mod testkit;
58+
mod testkit;
5959
pub mod utils;
6060

6161
pub use self::handle::{cast_handle, drop_handle, to_handle, Handle};
6262
pub use handle::resource_manager::*;
6363
pub use proxy::*;
6464
pub use runtime::*;
6565
pub use storage::*;
66-
//pub use testkit::*;
66+
pub use testkit::*;
6767

6868
pub use jni::errors::{Error as JniError, ErrorKind as JniErrorKind, Result as JniResult};
6969
pub use jni::Executor;

exonum-java-binding/core/rust/src/testkit/mod.rs

Lines changed: 158 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,38 @@
1616

1717
//! Provides native methods for Java TestKit support.
1818
19-
use self::time_provider::JavaTimeProvider;
19+
use std::{panic, sync::Arc};
20+
2021
use exonum::{
21-
blockchain::Block,
22+
blockchain::{Block, InstanceCollection, InstanceConfig},
2223
crypto::{PublicKey, SecretKey},
2324
helpers::ValidatorId,
24-
messages::{RawTransaction, Signed},
25+
merkledb::BinaryValue,
26+
runtime::InstanceSpec,
2527
};
26-
use exonum_merkledb::BinaryValue;
2728
use exonum_testkit::{TestKit, TestKitBuilder};
28-
use exonum_time::{time_provider::TimeProvider, TimeService};
29-
use handle::{cast_handle, drop_handle, to_handle, Handle};
29+
use exonum_time::{time_provider::TimeProvider, TimeServiceFactory};
3030
use jni::{
3131
objects::{JObject, JValue},
3232
sys::{jboolean, jbyteArray, jobjectArray, jshort},
3333
Executor, JNIEnv,
3434
};
35-
use proxy::ServiceProxy;
36-
use std::{panic, sync::Arc};
35+
36+
use handle::{cast_handle, drop_handle, to_handle, Handle};
3737
use storage::View;
38-
use utils::{unwrap_exc_or, unwrap_exc_or_default};
38+
use utils::{convert_to_string, unwrap_exc_or, unwrap_exc_or_default};
39+
use {JavaRuntimeProxy, JniError, JniResult};
40+
41+
use self::time_provider::JavaTimeProvider;
3942

4043
mod time_provider;
4144

4245
const KEYPAIR_CLASS: &str = "com/exonum/binding/common/crypto/KeyPair";
4346
const KEYPAIR_CTOR_SIGNATURE: &str = "([B[B)Lcom/exonum/binding/common/crypto/KeyPair;";
4447
const EMULATED_NODE_CLASS: &str = "com/exonum/binding/testkit/EmulatedNode";
4548
const EMULATED_NODE_CTOR_SIGNATURE: &str = "(ILcom/exonum/binding/common/crypto/KeyPair;)V";
49+
const SERVICE_SPECS_FIELD_TYPE: &str = "[Lcom/exonum/binding/testkit/ServiceSpec;";
50+
const TIME_PROVIDER_FIELD_TYPE: &str = "Lcom/exonum/binding/testkit/TimeProviderAdapter;";
4651

4752
/// Creates TestKit instance with specified services and wires public API handlers.
4853
/// The caller is responsible for properly destroying TestKit instance and freeing
@@ -54,7 +59,8 @@ pub extern "system" fn Java_com_exonum_binding_testkit_TestKit_nativeCreateTestK
5459
services: jobjectArray,
5560
auditor: jboolean,
5661
validator_count: jshort,
57-
time_provider: JObject,
62+
time_service_spec: JObject,
63+
runtime_adapter: JObject,
5864
) -> Handle {
5965
let res = panic::catch_unwind(|| {
6066
let mut builder = if auditor == jni::sys::JNI_TRUE {
@@ -65,20 +71,18 @@ pub extern "system" fn Java_com_exonum_binding_testkit_TestKit_nativeCreateTestK
6571
builder = builder.with_validators(validator_count as _);
6672
let builder = {
6773
let executor = Executor::new(Arc::new(env.get_java_vm()?));
68-
let num_services = env.get_array_length(services)?;
69-
for i in 0..num_services {
70-
let service = env.get_object_array_element(services, i)?;
71-
let global_ref = env.new_global_ref(service)?;
72-
let service = ServiceProxy::from_global_ref(executor.clone(), global_ref);
73-
builder = builder.with_service(service);
74-
}
7574

76-
// Handle Time Service
77-
if !time_provider.is_null() {
78-
let provider = JavaTimeProvider::new(executor.clone(), time_provider);
79-
builder = builder.with_service(TimeService::with_provider(
80-
Box::new(provider) as Box<dyn TimeProvider>
81-
));
75+
let runtime =
76+
JavaRuntimeProxy::new(executor.clone(), env.new_global_ref(runtime_adapter)?);
77+
builder = builder
78+
.with_additional_runtime(runtime)
79+
// TODO: Rewrite with protobuf: ECR-3689
80+
.with_instances(instance_configs_from_java_array(&env, services)?);
81+
82+
if let Some(instance) =
83+
time_service_instance_from_java(&env, executor.clone(), time_service_spec)?
84+
{
85+
builder = builder.with_rust_service(instance);
8286
}
8387

8488
builder
@@ -156,9 +160,7 @@ pub extern "system" fn Java_com_exonum_binding_testkit_TestKit_nativeCreateBlock
156160
env.auto_local(env.get_object_array_element(transactions, i as _)?);
157161
let serialized_tx: jbyteArray = serialized_tx_object.as_obj().into_inner();
158162
let serialized_tx = env.convert_byte_array(serialized_tx)?;
159-
let transaction: Signed<RawTransaction> =
160-
BinaryValue::from_bytes(serialized_tx.into()).unwrap();
161-
raw_transactions.push(transaction);
163+
raw_transactions.push(BinaryValue::from_bytes(serialized_tx.into()).unwrap());
162164
}
163165
let block = testkit
164166
.create_block_with_transactions(raw_transactions.into_iter())
@@ -202,7 +204,7 @@ fn serialize_block(env: &JNIEnv, block: Block) -> jni::errors::Result<jbyteArray
202204

203205
fn create_java_keypair<'a>(
204206
env: &'a JNIEnv,
205-
keypair: (&PublicKey, &SecretKey),
207+
keypair: (PublicKey, SecretKey),
206208
) -> jni::errors::Result<JValue<'a>> {
207209
let public_key_byte_array: JObject = env.byte_array_from_slice(&keypair.0[..])?.into();
208210
let secret_key_byte_array: JObject = env.byte_array_from_slice(&keypair.1[..])?.into();
@@ -213,3 +215,132 @@ fn create_java_keypair<'a>(
213215
&[secret_key_byte_array.into(), public_key_byte_array.into()],
214216
)
215217
}
218+
219+
// Converts Java array of `TestKitServiceInstances` to vector of `InstanceConfig`.
220+
//
221+
// `TestKitServiceInstances` representation:
222+
// String artifactId;
223+
// byte[] deployArguments;
224+
// ServiceSpec[] serviceSpecs;
225+
fn instance_configs_from_java_array(
226+
env: &JNIEnv,
227+
service_artifact_specs: jobjectArray,
228+
) -> JniResult<Vec<InstanceConfig>> {
229+
let mut instance_configs = vec![];
230+
let num_artifacts = env.get_array_length(service_artifact_specs)?;
231+
for i in 0..num_artifacts {
232+
env.with_local_frame(8, || {
233+
let artifact_spec_obj = env.get_object_array_element(service_artifact_specs, i)?;
234+
235+
let artifact_id = get_field_as_string(env, artifact_spec_obj, "artifactId")?;
236+
let deploy_args: jbyteArray = env
237+
.get_field(artifact_spec_obj, "deployArguments", "[B")?
238+
.l()?
239+
.into_inner();
240+
let deploy_args = env.convert_byte_array(deploy_args)?;
241+
let service_specs_obj: jobjectArray = env
242+
.get_field(artifact_spec_obj, "serviceSpecs", SERVICE_SPECS_FIELD_TYPE)?
243+
.l()?
244+
.into_inner();
245+
// TODO: Avoid deploy arguments duplication after ECR-3690
246+
let configs = parse_service_specs(env, service_specs_obj, artifact_id, deploy_args)?;
247+
instance_configs.extend(configs);
248+
249+
Ok(JObject::null())
250+
})?;
251+
}
252+
Ok(instance_configs)
253+
}
254+
255+
// Converts Java array of `ServiceSpec` instances into vector of `InstanceConfig` for specific artifact.
256+
fn parse_service_specs(
257+
env: &JNIEnv,
258+
specs_array: jobjectArray,
259+
artifact_id: String,
260+
deploy_args: Vec<u8>,
261+
) -> JniResult<Vec<InstanceConfig>> {
262+
let num_specs = env.get_array_length(specs_array)?;
263+
264+
let mut instance_configs = vec![];
265+
for i in 0..num_specs {
266+
env.with_local_frame(8, || {
267+
let service_spec = env.get_object_array_element(specs_array, i)?;
268+
let (spec, config) = parse_instance_spec(&env, service_spec, &artifact_id)?;
269+
let cfg = InstanceConfig::new(spec, Some(deploy_args.to_bytes()), config);
270+
instance_configs.push(cfg);
271+
272+
Ok(JObject::null())
273+
})?;
274+
}
275+
276+
Ok(instance_configs)
277+
}
278+
279+
// Parses the `ServiceSpec` instance.
280+
//
281+
// `ServiceSpec` representation:
282+
// String serviceName;
283+
// int serviceId;
284+
// byte[] configuration;
285+
fn parse_instance_spec(
286+
env: &JNIEnv,
287+
service_spec_obj: JObject,
288+
artifact_id: impl AsRef<str>,
289+
) -> JniResult<(InstanceSpec, Vec<u8>)> {
290+
let (service_id, service_name) = get_service_id_and_name(env, service_spec_obj)?;
291+
let config_params: jbyteArray = env
292+
.get_field(service_spec_obj, "configuration", "[B")?
293+
.l()?
294+
.into_inner();
295+
let config = env.convert_byte_array(config_params)?;
296+
let spec = InstanceSpec::new(service_id, service_name, artifact_id).map_err(|err| {
297+
JniError::from(format!(
298+
"Unable to create instance specification for the service with id {}: {}",
299+
service_id, err
300+
))
301+
})?;
302+
303+
Ok((spec, config))
304+
}
305+
306+
// Creates `InstanceCollection` from `TimeServiceSpec` object.
307+
//
308+
// `TimeServiceSpec`
309+
// TimeProviderAdapter timeProvider;
310+
// String serviceName;
311+
// int serviceId;
312+
fn time_service_instance_from_java(
313+
env: &JNIEnv,
314+
executor: Executor,
315+
time_service_spec: JObject,
316+
) -> JniResult<Option<InstanceCollection>> {
317+
if time_service_spec.is_null() {
318+
return Ok(None);
319+
}
320+
321+
let (service_id, service_name) = get_service_id_and_name(env, time_service_spec)?;
322+
let time_provider = env
323+
.get_field(time_service_spec, "timeProvider", TIME_PROVIDER_FIELD_TYPE)?
324+
.l()?;
325+
326+
let provider = JavaTimeProvider::new(executor.clone(), time_provider);
327+
let factory = TimeServiceFactory::with_provider(Arc::new(provider) as Arc<dyn TimeProvider>);
328+
let instance = InstanceCollection::new(factory).with_instance(service_id, service_name, ());
329+
330+
Ok(Some(instance))
331+
}
332+
333+
// Returns id and name value from corresponding instances of Java objects.
334+
fn get_service_id_and_name(env: &JNIEnv, service_obj: JObject) -> JniResult<(u32, String)> {
335+
let service_id = env.get_field(service_obj, "serviceId", "I")?.i()? as u32;
336+
let service_name = get_field_as_string(env, service_obj, "serviceName")?;
337+
Ok((service_id, service_name))
338+
}
339+
340+
// Returns String value of instance's field.
341+
fn get_field_as_string(env: &JNIEnv, obj: JObject, field_name: &str) -> JniResult<String> {
342+
convert_to_string(
343+
env,
344+
env.get_field(obj, field_name, "Ljava/lang/String;")?.l()?,
345+
)
346+
}

0 commit comments

Comments
 (0)