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", "java.sources" -> baseDirectory.value.toGlob / "src" / "main" / "java" / ** / "*.java",
"glue.sources" -> baseDirectory.value.toGlob / "glue" / "src" / ** / "*.rs" "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( Compile / doc / javacOptions ++= Seq(
"--enable-preview", "--enable-preview",
"--release", "--release",
@ -38,11 +46,6 @@ lazy val root = project
"-Xlint:unchecked", "-Xlint:unchecked",
"-Xlint:deprecation" "-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 := { ejectHeaders := {
val files = val files =
FileTreeView.default.iterator(glob.value("java.sources")).foldLeft("") { 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" version = "0.1.0"
dependencies = [ dependencies = [
"jni", "jni",
"log",
"naan",
"nb", "nb",
"no-std-net", "no-std-net",
"tinyvec", "tinyvec",
@ -580,10 +582,11 @@ dependencies = [
[[package]] [[package]]
name = "toad-jni" name = "toad-jni"
version = "0.9.1" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef61b22c3478d481635b9eb67496d4dc18f22b8321961aca5dc2084f5253ccb7" checksum = "8e6f9b7ff8462ec97df69ef2bb01b485626fb43ce62a8fe33c6179f3537a9d38"
dependencies = [ dependencies = [
"embedded-time",
"jni", "jni",
"nb", "nb",
"no-std-net", "no-std-net",
@ -591,6 +594,7 @@ dependencies = [
"toad", "toad",
"toad-array 0.5.0", "toad-array 0.5.0",
"toad-len", "toad-len",
"toad-msg",
"toad-stem", "toad-stem",
] ]

View File

@ -15,7 +15,9 @@ e2e = []
jni = "0.21.1" jni = "0.21.1"
nb = "1" nb = "1"
toad = "0.17.3" toad = "0.17.3"
toad-jni = "0.9.1" toad-jni = "0.10.1"
no-std-net = "0.6" no-std-net = "0.6"
toad-msg = "0.18.1" toad-msg = "0.18.1"
tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]} 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::platform::Platform;
use toad::retry::{Attempts, Strategy}; use toad::retry::{Attempts, Strategy};
use toad::time::Millis; use toad::time::Millis;
use toad_jni::java::nio::channels::{DatagramChannel, PeekableDatagramChannel};
use toad_jni::java::{self, Object}; use toad_jni::java::{self, Object};
use crate::mem::{Shared, SharedMemoryRegion}; use crate::mem::{Shared, SharedMemoryRegion};
@ -18,9 +19,10 @@ use crate::Runtime;
pub struct Toad(java::lang::Object); pub struct Toad(java::lang::Object);
impl Toad { impl Toad {
pub fn new(e: &mut java::Env, cfg: Config) -> Self { pub fn new(e: &mut java::Env, cfg: Config, channel: PeekableDatagramChannel) -> Self {
static CTOR: java::Constructor<Toad, fn(Config)> = java::Constructor::new(); static CTOR: java::Constructor<Toad, fn(Config, PeekableDatagramChannel)> =
CTOR.invoke(e, cfg) java::Constructor::new();
CTOR.invoke(e, cfg, channel)
} }
pub fn poll_req(&self, e: &mut java::Env) -> Option<msg::ref_::Message> { pub fn poll_req(&self, e: &mut java::Env) -> Option<msg::ref_::Message> {
@ -34,8 +36,8 @@ impl Toad {
CONFIG.invoke(e, self) CONFIG.invoke(e, self)
} }
fn init_impl(e: &mut java::Env, cfg: Config) -> i64 { fn init_impl(e: &mut java::Env, cfg: Config, channel: PeekableDatagramChannel) -> i64 {
let r = || Runtime::try_new(cfg.addr(e), cfg.to_toad(e)).unwrap(); let r = || Runtime::new(cfg.to_toad(e), channel);
unsafe { crate::mem::Shared::init(r).addr() as i64 } 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::WouldBlock) => java::util::Optional::<msg::ref_::Message>::empty(e),
| Err(nb::Error::Other(err)) => { | 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) java::util::Optional::<msg::ref_::Message>::empty(e)
}, },
} }
@ -70,11 +73,6 @@ impl java::Class for Config {
} }
impl 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 { pub fn concurrency(&self, e: &mut java::Env) -> u8 {
static RUNTIME_CONFIG_CONCURRENCY: java::Field<Config, ffi::u8> = static RUNTIME_CONFIG_CONCURRENCY: java::Field<Config, ffi::u8> =
java::Field::new("concurrency"); java::Field::new("concurrency");
@ -86,9 +84,8 @@ impl Config {
RUNTIME_CONFIG_MSG.invoke(e, self) RUNTIME_CONFIG_MSG.invoke(e, self)
} }
pub fn new(e: &mut java::Env, c: toad::config::Config, addr: SocketAddr) -> Self { pub fn new(e: &mut java::Env, c: toad::config::Config) -> Self {
static CTOR: java::Constructor<Config, fn(java::net::InetSocketAddress, ffi::u8, Msg)> = static CTOR: java::Constructor<Config, fn(ffi::u8, Msg)> = java::Constructor::new();
java::Constructor::new();
let con = Con::new(e, let con = Con::new(e,
c.msg.con.unacked_retry_strategy, c.msg.con.unacked_retry_strategy,
@ -104,9 +101,7 @@ impl Config {
let concurrency = ffi::u8::from_rust(e, c.max_concurrent_requests); 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, concurrency, msg);
let jcfg = CTOR.invoke(e, address, concurrency, msg);
jcfg jcfg
} }
@ -264,20 +259,20 @@ impl Non {
pub extern "system" fn Java_dev_toad_Toad_defaultConfigImpl<'local>(mut env: java::Env<'local>, pub extern "system" fn Java_dev_toad_Toad_defaultConfigImpl<'local>(mut env: java::Env<'local>,
_: JClass<'local>) _: JClass<'local>)
-> jobject { -> jobject {
Config::new(&mut env, Config::new(&mut env, toad::config::Config::default()).yield_to_java(&mut env)
toad::config::Config::default(),
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5683)).yield_to_java(&mut env)
} }
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_dev_toad_Toad_init<'local>(mut e: java::Env<'local>, pub extern "system" fn Java_dev_toad_Toad_init<'local>(mut e: java::Env<'local>,
_: JClass<'local>, _: JClass<'local>,
channel: JObject<'local>,
cfg: JObject<'local>) cfg: JObject<'local>)
-> i64 { -> i64 {
let e = &mut e; let e = &mut e;
let cfg = java::lang::Object::from_local(e, cfg).upcast_to::<Config>(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] #[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] #[no_mangle]
pub extern "system" fn Java_dev_toad_msg_ref_Message_type<'local>(mut e: java::Env<'local>, pub extern "system" fn Java_dev_toad_msg_ref_Message_typ<'local>(mut e: java::Env<'local>,
_: JClass<'local>, _: JClass<'local>,
addr: i64) addr: i64)
-> jobject { -> jobject {
let msg = unsafe { let msg = unsafe {
Shared::deref::<toad_msg::alloc::Message>(addr).as_ref() Shared::deref::<toad_msg::alloc::Message>(addr).as_ref()
.unwrap() .unwrap()

View File

@ -1,18 +1,25 @@
use std::time::{Duration, Instant};
use no_std_net::SocketAddr; use no_std_net::SocketAddr;
use toad::config::Config; use toad::config::Config;
use toad::net::Addrd; use toad::net::Addrd;
use toad::platform::Platform; 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_jni::java::{self, Object, Signature};
use toad_msg::alloc::Message; use toad_msg::alloc::Message;
use toad_msg::{Code, Id, Token, Type}; use toad_msg::{Code, Id, Token, Type};
use crate::{dev, Runtime}; use crate::{dev, Runtime};
type RustRuntime =
toad::std::Platform<toad::std::dtls::N, toad::step::runtime::std::Runtime<toad::std::dtls::N>>;
#[non_exhaustive] #[non_exhaustive]
struct State { struct State {
pub runtime: dev::toad::Toad, pub runtime: dev::toad::Toad,
pub env: java::Env<'static>, pub env: java::Env<'static>,
pub client: crate::Runtime, pub client: RustRuntime,
pub srv_addr: SocketAddr, pub srv_addr: SocketAddr,
} }
@ -20,17 +27,15 @@ fn init() -> State {
let mut _env = crate::test::init(); let mut _env = crate::test::init();
let env = &mut _env; let env = &mut _env;
let cfg = let cfg = dev::toad::Config::new(env, Config::default());
dev::toad::Config::new(env, let sock = <PeekableDatagramChannel as toad::net::Socket>::bind(no_std_net::SocketAddr::new(no_std_net::Ipv4Addr::LOCALHOST.into(), 5683)).unwrap();
Config::default(), let runtime = dev::toad::Toad::new(env, cfg, sock);
std::net::SocketAddr::new(std::net::Ipv4Addr::UNSPECIFIED.into(), 5683)); let client = RustRuntime::try_new("127.0.0.1:5684", Default::default()).unwrap();
let runtime = dev::toad::Toad::new(env, cfg);
let client = Runtime::try_new("0.0.0.0:5684", Default::default()).unwrap();
State { runtime, State { runtime,
env: _env, env: _env,
client, 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, 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())); let request = Message::new(Type::Con, Code::GET, Id(0), Token(Default::default()));
client.send_msg(Addrd(request, *srv_addr)).unwrap(); 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] #[test]

View File

@ -8,11 +8,16 @@ use mem::SharedMemoryRegion;
mod runtime { mod runtime {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use toad::platform::Effect; use toad::config::Config;
use toad::std::{dtls, Platform}; use toad::net::Addrd;
use toad::step::runtime::std::Runtime as DefaultSteps; 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_jni::java::nio::channels::PeekableDatagramChannel;
use toad_msg::{OptValue, OptNumber}; use toad_msg::{OptNumber, OptValue};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct PlatformTypes; pub struct PlatformTypes;
@ -27,7 +32,49 @@ mod runtime {
type Effects = Vec<Effect<Self>>; 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; pub use runtime::Runtime;
@ -59,21 +106,35 @@ pub mod e2e;
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use std::process::Command;
use std::sync::Once; use std::sync::Once;
use jni::{InitArgsBuilder, JavaVM}; use jni::{InitArgsBuilder, JavaVM};
use toad::config::Config; use toad::config::Config;
use toad::retry::Strategy; use toad::retry::Strategy;
use toad::time::Millis; use toad::time::Millis;
use toad_jni::java; use toad_jni::java::{self, Class, ResultExt};
use crate::dev; use crate::dev;
pub fn init<'a>() -> java::Env<'a> { pub fn init<'a>() -> java::Env<'a> {
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| { 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 = 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("-Djava.class.path=../target/scala-3.2.2/classes")
.option("--enable-preview") .option("--enable-preview")
.build() .build()
@ -81,8 +142,13 @@ pub mod test {
toad_jni::global::init_with(jvm); toad_jni::global::init_with(jvm);
}); });
toad_jni::global::jvm().attach_current_thread_permanently() let mut env = toad_jni::global::jvm().attach_current_thread_permanently()
.unwrap() .unwrap();
env.call_static_method(crate::dev::toad::Toad::PATH, "loadNativeLib", "()V", &[])
.unwrap_java(&mut env);
env
} }
#[test] #[test]
@ -97,9 +163,7 @@ pub mod test {
let mut e = init(); let mut e = init();
let e = &mut e; let e = &mut e;
let r = dev::toad::Config::new(e, let r = dev::toad::Config::new(e, Config::default());
Config::default(),
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5683));
assert_eq!(r.to_toad(e), Config::default()); assert_eq!(r.to_toad(e), Config::default());
} }

View File

@ -25,6 +25,10 @@ public final class Toad implements AutoCloseable {
} }
static { static {
Toad.loadNativeLib();
}
static void loadNativeLib() {
System.loadLibrary("toad_java_glue"); System.loadLibrary("toad_java_glue");
} }
@ -32,25 +36,25 @@ public final class Toad implements AutoCloseable {
final Config config; final Config config;
final DatagramChannel channel; 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, long ptr,
dev.toad.msg.Token t, dev.toad.msg.Token t,
InetSocketAddress n InetSocketAddress n
); );
Optional<dev.toad.msg.ref.Message> pollReq() { public Optional<dev.toad.msg.ref.Message> pollReq() {
return this.pollReq(this.ptr.addr()); 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, dev.toad.msg.Token regarding,
InetSocketAddress from InetSocketAddress from
) { ) {
return this.pollResp(this.ptr.addr(), regarding, from); return Toad.pollResp(this.ptr.addr(), regarding, from);
} }
public static BuilderRequiresSocket builder() { public static BuilderRequiresSocket builder() {
@ -60,7 +64,7 @@ public final class Toad implements AutoCloseable {
Toad(Config o, DatagramChannel channel) { Toad(Config o, DatagramChannel channel) {
this.config = o; this.config = o;
this.channel = channel; 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() { 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 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); 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() { public Type type() {
return this.type(this.ptr.addr()); return this.typ(this.ptr.addr());
} }
public dev.toad.msg.ref.Option[] optionRefs() { public dev.toad.msg.ref.Option[] optionRefs() {

View File

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