feat: add Java Platform, e2e test stubs

This commit is contained in:
Orion Kindel 2023-04-15 22:24:43 -05:00
parent f00dd85502
commit af1f249d9a
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
10 changed files with 151 additions and 65 deletions

View File

@ -26,6 +26,14 @@ lazy val root = project
"java.sources" -> baseDirectory.value.toGlob / "src" / "main" / "java" / ** / "*.java",
"glue.sources" -> baseDirectory.value.toGlob / "glue" / "src" / ** / "*.rs"
),
path := Map(
"glue.base" -> (baseDirectory.value / "glue").toString,
"glue.target" -> (baseDirectory.value / "target" / "glue" / "debug").toString,
"java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString
),
Test / javaOptions ++= Seq(
"-Djava.library.path="++path.value("glue.target"),
),
Compile / doc / javacOptions ++= Seq(
"--enable-preview",
"--release",
@ -38,11 +46,6 @@ lazy val root = project
"-Xlint:unchecked",
"-Xlint:deprecation"
),
path := Map(
"glue.base" -> (baseDirectory.value / "glue").toString,
"glue.target" -> (baseDirectory.value / "target" / "glue" / "debug").toString,
"java.classTarget" -> (baseDirectory.value / "target" / "scala-3.2.2" / "classes").toString
),
ejectHeaders := {
val files =
FileTreeView.default.iterator(glob.value("java.sources")).foldLeft("") {

8
glue/Cargo.lock generated
View File

@ -570,6 +570,8 @@ name = "toad-java-glue"
version = "0.1.0"
dependencies = [
"jni",
"log",
"naan",
"nb",
"no-std-net",
"tinyvec",
@ -580,10 +582,11 @@ dependencies = [
[[package]]
name = "toad-jni"
version = "0.9.1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef61b22c3478d481635b9eb67496d4dc18f22b8321961aca5dc2084f5253ccb7"
checksum = "8e6f9b7ff8462ec97df69ef2bb01b485626fb43ce62a8fe33c6179f3537a9d38"
dependencies = [
"embedded-time",
"jni",
"nb",
"no-std-net",
@ -591,6 +594,7 @@ dependencies = [
"toad",
"toad-array 0.5.0",
"toad-len",
"toad-msg",
"toad-stem",
]

View File

@ -15,7 +15,9 @@ e2e = []
jni = "0.21.1"
nb = "1"
toad = "0.17.3"
toad-jni = "0.9.1"
toad-jni = "0.10.1"
no-std-net = "0.6"
toad-msg = "0.18.1"
tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]}
naan = "0.1.32"
log = "0.4.17"

View File

@ -10,6 +10,7 @@ pub use retry_strategy::RetryStrategy;
use toad::platform::Platform;
use toad::retry::{Attempts, Strategy};
use toad::time::Millis;
use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel};
use toad_jni::java::{self, Object};
use crate::mem::{Shared, SharedMemoryRegion};
@ -18,9 +19,10 @@ use crate::Runtime;
pub struct Toad(java::lang::Object);
impl Toad {
pub fn new(e: &mut java::Env, cfg: Config) -> Self {
static CTOR: java::Constructor<Toad, fn(Config)> = java::Constructor::new();
CTOR.invoke(e, cfg)
pub fn new(e: &mut java::Env, cfg: Config, channel: PeekableDatagramChannel) -> Self {
static CTOR: java::Constructor<Toad, fn(Config, PeekableDatagramChannel)> =
java::Constructor::new();
CTOR.invoke(e, cfg, channel)
}
pub fn poll_req(&self, e: &mut java::Env) -> Option<msg::ref_::Message> {
@ -34,8 +36,8 @@ impl Toad {
CONFIG.invoke(e, self)
}
fn init_impl(e: &mut java::Env, cfg: Config) -> i64 {
let r = || Runtime::try_new(cfg.addr(e), cfg.to_toad(e)).unwrap();
fn init_impl(e: &mut java::Env, cfg: Config, channel: PeekableDatagramChannel) -> i64 {
let r = || Runtime::new(cfg.to_toad(e), channel);
unsafe { crate::mem::Shared::init(r).addr() as i64 }
}
@ -49,7 +51,8 @@ impl Toad {
},
| Err(nb::Error::WouldBlock) => java::util::Optional::<msg::ref_::Message>::empty(e),
| Err(nb::Error::Other(err)) => {
e.throw(format!("{:?}", err)).unwrap();
let err = err.downcast_ref(e).to_local(e);
e.throw(jni::objects::JThrowable::from(err)).unwrap();
java::util::Optional::<msg::ref_::Message>::empty(e)
},
}
@ -70,11 +73,6 @@ impl java::Class for Config {
}
impl Config {
pub fn addr(&self, e: &mut java::Env) -> SocketAddr {
static ADDRESS: java::Field<Config, java::net::InetSocketAddress> = java::Field::new("addr");
ADDRESS.get(e, self).to_std(e)
}
pub fn concurrency(&self, e: &mut java::Env) -> u8 {
static RUNTIME_CONFIG_CONCURRENCY: java::Field<Config, ffi::u8> =
java::Field::new("concurrency");
@ -86,9 +84,8 @@ impl Config {
RUNTIME_CONFIG_MSG.invoke(e, self)
}
pub fn new(e: &mut java::Env, c: toad::config::Config, addr: SocketAddr) -> Self {
static CTOR: java::Constructor<Config, fn(java::net::InetSocketAddress, ffi::u8, Msg)> =
java::Constructor::new();
pub fn new(e: &mut java::Env, c: toad::config::Config) -> Self {
static CTOR: java::Constructor<Config, fn(ffi::u8, Msg)> = java::Constructor::new();
let con = Con::new(e,
c.msg.con.unacked_retry_strategy,
@ -104,9 +101,7 @@ impl Config {
let concurrency = ffi::u8::from_rust(e, c.max_concurrent_requests);
let address = java::net::InetSocketAddress::from_std(e, addr);
let jcfg = CTOR.invoke(e, address, concurrency, msg);
let jcfg = CTOR.invoke(e, concurrency, msg);
jcfg
}
@ -264,20 +259,20 @@ impl Non {
pub extern "system" fn Java_dev_toad_Toad_defaultConfigImpl<'local>(mut env: java::Env<'local>,
_: JClass<'local>)
-> jobject {
Config::new(&mut env,
toad::config::Config::default(),
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5683)).yield_to_java(&mut env)
Config::new(&mut env, toad::config::Config::default()).yield_to_java(&mut env)
}
#[no_mangle]
pub extern "system" fn Java_dev_toad_Toad_init<'local>(mut e: java::Env<'local>,
_: JClass<'local>,
channel: JObject<'local>,
cfg: JObject<'local>)
-> i64 {
let e = &mut e;
let cfg = java::lang::Object::from_local(e, cfg).upcast_to::<Config>(e);
let channel = java::lang::Object::from_local(e, channel).upcast_to::<DatagramChannel>(e);
Toad::init_impl(e, cfg)
Toad::init_impl(e, cfg, channel.peekable())
}
#[no_mangle]

View File

@ -119,10 +119,10 @@ pub extern "system" fn Java_dev_toad_msg_ref_Message_payload<'local>(mut env: ja
}
#[no_mangle]
pub extern "system" fn Java_dev_toad_msg_ref_Message_type<'local>(mut e: java::Env<'local>,
_: JClass<'local>,
addr: i64)
-> jobject {
pub extern "system" fn Java_dev_toad_msg_ref_Message_typ<'local>(mut e: java::Env<'local>,
_: JClass<'local>,
addr: i64)
-> jobject {
let msg = unsafe {
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
.unwrap()

View File

@ -1,18 +1,25 @@
use std::time::{Duration, Instant};
use no_std_net::SocketAddr;
use toad::config::Config;
use toad::net::Addrd;
use toad::platform::Platform;
use toad_jni::java::net::StandardProtocolFamily::INet;
use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel};
use toad_jni::java::{self, Object, Signature};
use toad_msg::alloc::Message;
use toad_msg::{Code, Id, Token, Type};
use crate::{dev, Runtime};
type RustRuntime =
toad::std::Platform<toad::std::dtls::N, toad::step::runtime::std::Runtime<toad::std::dtls::N>>;
#[non_exhaustive]
struct State {
pub runtime: dev::toad::Toad,
pub env: java::Env<'static>,
pub client: crate::Runtime,
pub client: RustRuntime,
pub srv_addr: SocketAddr,
}
@ -20,17 +27,15 @@ fn init() -> State {
let mut _env = crate::test::init();
let env = &mut _env;
let cfg =
dev::toad::Config::new(env,
Config::default(),
std::net::SocketAddr::new(std::net::Ipv4Addr::UNSPECIFIED.into(), 5683));
let runtime = dev::toad::Toad::new(env, cfg);
let client = Runtime::try_new("0.0.0.0:5684", Default::default()).unwrap();
let cfg = dev::toad::Config::new(env, Config::default());
let sock = <PeekableDatagramChannel as toad::net::Socket>::bind(no_std_net::SocketAddr::new(no_std_net::Ipv4Addr::LOCALHOST.into(), 5683)).unwrap();
let runtime = dev::toad::Toad::new(env, cfg, sock);
let client = RustRuntime::try_new("127.0.0.1:5684", Default::default()).unwrap();
State { runtime,
env: _env,
client,
srv_addr: "0.0.0.0:5683".parse().unwrap() }
srv_addr: "127.0.0.1:5683".parse().unwrap() }
}
fn runtime_poll_req(State { runtime,
@ -43,7 +48,14 @@ fn runtime_poll_req(State { runtime,
let request = Message::new(Type::Con, Code::GET, Id(0), Token(Default::default()));
client.send_msg(Addrd(request, *srv_addr)).unwrap();
assert!(runtime.poll_req(env).is_some());
let start = Instant::now();
loop {
if Instant::now() - start > Duration::from_millis(10000) {
panic!("timed out waiting for DatagramChannel to receive message");
} else if runtime.poll_req(env).is_some() {
break;
}
}
}
#[test]

View File

@ -8,11 +8,16 @@ use mem::SharedMemoryRegion;
mod runtime {
use std::collections::BTreeMap;
use toad::platform::Effect;
use toad::std::{dtls, Platform};
use toad::step::runtime::std::Runtime as DefaultSteps;
use toad::config::Config;
use toad::net::Addrd;
use toad::platform::{Effect, Platform};
use toad::req::Req;
use toad::resp::Resp;
use toad::step::runtime::Runtime as DefaultSteps;
use toad_jni::java::io::IOException;
use toad_jni::java::lang::Throwable;
use toad_jni::java::nio::channels::PeekableDatagramChannel;
use toad_msg::{OptValue, OptNumber};
use toad_msg::{OptNumber, OptValue};
#[derive(Clone, Copy, Debug)]
pub struct PlatformTypes;
@ -27,7 +32,49 @@ mod runtime {
type Effects = Vec<Effect<Self>>;
}
pub type Runtime = Platform<dtls::N, DefaultSteps<dtls::N>>;
type Steps = DefaultSteps<PlatformTypes, naan::hkt::Vec, naan::hkt::BTreeMap>;
pub struct Runtime {
steps: Steps,
config: Config,
channel: PeekableDatagramChannel,
clock: toad::std::Clock,
}
impl Runtime {
pub fn new(config: Config, channel: PeekableDatagramChannel) -> Self {
Self { steps: Default::default(),
config,
channel,
clock: toad::std::Clock::new() }
}
}
impl Platform<Steps> for Runtime {
type Types = PlatformTypes;
type Error = IOException;
fn log(&self, level: log::Level, msg: toad::todo::String<1000>) -> Result<(), Self::Error> {
println!("[{}]: {}", level, msg.as_str());
Ok(())
}
fn config(&self) -> toad::config::Config {
self.config
}
fn steps(&self) -> &Steps {
&self.steps
}
fn socket(&self) -> &PeekableDatagramChannel {
&self.channel
}
fn clock(&self) -> &toad::std::Clock {
&self.clock
}
}
}
pub use runtime::Runtime;
@ -59,21 +106,35 @@ pub mod e2e;
#[cfg(test)]
pub mod test {
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use std::process::Command;
use std::sync::Once;
use jni::{InitArgsBuilder, JavaVM};
use toad::config::Config;
use toad::retry::Strategy;
use toad::time::Millis;
use toad_jni::java;
use toad_jni::java::{self, Class, ResultExt};
use crate::dev;
pub fn init<'a>() -> java::Env<'a> {
static INIT: Once = Once::new();
INIT.call_once(|| {
let repo_root = Command::new("git").arg("rev-parse")
.arg("--show-toplevel")
.output()
.unwrap();
assert!(repo_root.status.success());
let lib_path = String::from_utf8(repo_root.stdout).unwrap()
.trim()
.to_string();
let lib_path = PathBuf::from(lib_path).join("target/glue/debug");
let jvm =
JavaVM::new(InitArgsBuilder::new().option("-Djava.library.path=/home/orion/src/toad-lib/toad-java/target/glue/debug/")
JavaVM::new(InitArgsBuilder::new().option(format!("-Djava.library.path={}",
lib_path.to_string_lossy()))
.option("-Djava.class.path=../target/scala-3.2.2/classes")
.option("--enable-preview")
.build()
@ -81,8 +142,13 @@ pub mod test {
toad_jni::global::init_with(jvm);
});
toad_jni::global::jvm().attach_current_thread_permanently()
.unwrap()
let mut env = toad_jni::global::jvm().attach_current_thread_permanently()
.unwrap();
env.call_static_method(crate::dev::toad::Toad::PATH, "loadNativeLib", "()V", &[])
.unwrap_java(&mut env);
env
}
#[test]
@ -97,9 +163,7 @@ pub mod test {
let mut e = init();
let e = &mut e;
let r = dev::toad::Config::new(e,
Config::default(),
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5683));
let r = dev::toad::Config::new(e, Config::default());
assert_eq!(r.to_toad(e), Config::default());
}

View File

@ -25,6 +25,10 @@ public final class Toad implements AutoCloseable {
}
static {
Toad.loadNativeLib();
}
static void loadNativeLib() {
System.loadLibrary("toad_java_glue");
}
@ -32,25 +36,25 @@ public final class Toad implements AutoCloseable {
final Config config;
final DatagramChannel channel;
static native long init(Config o);
static native long init(DatagramChannel chan, Config o);
native Optional<dev.toad.msg.ref.Message> pollReq(long ptr);
static native Optional<dev.toad.msg.ref.Message> pollReq(long ptr);
native Optional<dev.toad.msg.ref.Message> pollResp(
static native Optional<dev.toad.msg.ref.Message> pollResp(
long ptr,
dev.toad.msg.Token t,
InetSocketAddress n
);
Optional<dev.toad.msg.ref.Message> pollReq() {
return this.pollReq(this.ptr.addr());
public Optional<dev.toad.msg.ref.Message> pollReq() {
return Toad.pollReq(this.ptr.addr());
}
Optional<dev.toad.msg.ref.Message> pollResp(
public Optional<dev.toad.msg.ref.Message> pollResp(
dev.toad.msg.Token regarding,
InetSocketAddress from
) {
return this.pollResp(this.ptr.addr(), regarding, from);
return Toad.pollResp(this.ptr.addr(), regarding, from);
}
public static BuilderRequiresSocket builder() {
@ -60,7 +64,7 @@ public final class Toad implements AutoCloseable {
Toad(Config o, DatagramChannel channel) {
this.config = o;
this.channel = channel;
this.ptr = Ptr.register(this.getClass(), this.init(o));
this.ptr = Ptr.register(this.getClass(), this.init(this.channel, o));
}
public Config config() {

View File

@ -30,7 +30,7 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable {
static native Code code(long addr);
static native Type type(long addr);
static native Type typ(long addr);
static native dev.toad.msg.ref.Option[] opts(long addr);
@ -63,7 +63,7 @@ public final class Message implements dev.toad.msg.Message, AutoCloseable {
}
public Type type() {
return this.type(this.ptr.addr());
return this.typ(this.ptr.addr());
}
public dev.toad.msg.ref.Option[] optionRefs() {

View File

@ -3,6 +3,8 @@ import mock.java.nio.channels.Mock;
class E2E extends munit.FunSuite {
test("foo") {
val mock = Mock.Channel();
val mock = Mock.Channel()
val toad = Toad.builder.channel(mock).build
val req = Option.apply(toad.pollReq.get)
}
}