| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Java integration for the `Foo.java` test class. |
| //! |
| //! There are pourover descriptors for each member of `Foo`. Keep these in sync with changes to |
| //! `Foo.java`. |
| //! |
| //! Compilation is done at runtime. There isn't a way to create a build.rs for an integration test. |
| //! use [`compile_foo`] to get the class bytes and load the class using |
| //! [`jni::JNIEnv::define_class`]. |
| |
| use pourover::desc::*; |
| use std::error::Error; |
| use std::process::Command; |
| |
| pub const CLASS_DESC: &str = "com/example/Foo"; |
| pub static FOO: ClassDesc = ClassDesc::new(CLASS_DESC); |
| |
| pub static FIELD: FieldDesc = FOO.field("foo", "I"); |
| pub static STATIC_FIELD: StaticFieldDesc = FOO.static_field("sfoo", "J"); |
| pub static CONSTRUCTOR: MethodDesc = FOO.constructor("(I)V"); |
| pub static METHOD: MethodDesc = FOO.method("mfoo", "()Z"); |
| pub static STATIC_METHOD: StaticMethodDesc = FOO.static_method("smfoo", "()I"); |
| pub static NATIVE_METHOD: MethodDesc = FOO.method("nativeReturnsInt", "(I)I"); |
| pub static NATIVE_OBJECT_METHOD: MethodDesc = |
| FOO.method("nativeReturnsObject", "(I)Ljava/lang/String;"); |
| |
| /// Quick test to ensure that Java is available on the system. The version will be logged to |
| /// stdout. |
| #[test] |
| fn has_java() -> Result<(), Box<dyn Error>> { |
| Command::new("java").arg("--version").status()?; |
| Ok(()) |
| } |
| |
| /// Compile `Foo.java` and return the class file's bytes. |
| #[track_caller] |
| pub fn compile_foo() -> Result<Vec<u8>, Box<dyn Error>> { |
| // Avoid concurrent tests colliding on the file system by adding a hash of the call-site to the |
| // temp file path |
| let caller_hash = { |
| use core::hash::{Hash, Hasher}; |
| let caller = core::panic::Location::caller(); |
| let mut hasher = std::collections::hash_map::DefaultHasher::new(); |
| caller.hash(&mut hasher); |
| hasher.finish() |
| }; |
| |
| // Create a temporary dir to generate class files |
| let mut tmp = std::env::temp_dir(); |
| tmp.push(format!("test-java-build-{caller_hash:016x}")); |
| |
| if let Err(err) = std::fs::create_dir(&tmp) { |
| println!("error creating {}: {}", tmp.display(), &err); |
| } |
| |
| // Compile Foo.java into the temp dir |
| Command::new("javac") |
| .args(["--release", "8"]) |
| .arg("-d") |
| .arg(&tmp) |
| .arg("tests/Foo.java") |
| .status()?; |
| |
| // Read the class file bytes |
| let class = { |
| let mut class_file = tmp.clone(); |
| class_file.push("com/example/Foo.class"); |
| std::fs::read(&class_file)? |
| }; |
| |
| // Clean up the temp dir. If an error occurs before this we will leave the temp dir on the |
| // filesystem. That should work better for debugging than cleaning up in all cases. |
| std::fs::remove_dir_all(&tmp)?; |
| |
| Ok(class) |
| } |