1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Java integration for the `Foo.java` test class.
16 //!
17 //! There are pourover descriptors for each member of `Foo`. Keep these in sync with changes to
18 //! `Foo.java`.
19 //!
20 //! Compilation is done at runtime. There isn't a way to create a build.rs for an integration test.
21 //! use [`compile_foo`] to get the class bytes and load the class using
22 //! [`jni::JNIEnv::define_class`].
23 
24 use pourover::desc::*;
25 use std::error::Error;
26 use std::process::Command;
27 
28 pub const CLASS_DESC: &str = "com/example/Foo";
29 pub static FOO: ClassDesc = ClassDesc::new(CLASS_DESC);
30 
31 pub static FIELD: FieldDesc = FOO.field("foo", "I");
32 pub static STATIC_FIELD: StaticFieldDesc = FOO.static_field("sfoo", "J");
33 pub static CONSTRUCTOR: MethodDesc = FOO.constructor("(I)V");
34 pub static METHOD: MethodDesc = FOO.method("mfoo", "()Z");
35 pub static STATIC_METHOD: StaticMethodDesc = FOO.static_method("smfoo", "()I");
36 pub static NATIVE_METHOD: MethodDesc = FOO.method("nativeReturnsInt", "(I)I");
37 pub static NATIVE_OBJECT_METHOD: MethodDesc =
38     FOO.method("nativeReturnsObject", "(I)Ljava/lang/String;");
39 
40 /// Quick test to ensure that Java is available on the system. The version will be logged to
41 /// stdout.
42 #[test]
has_java() -> Result<(), Box<dyn Error>>43 fn has_java() -> Result<(), Box<dyn Error>> {
44     let _ = Command::new("java").arg("--version").status()?;
45     Ok(())
46 }
47 
48 /// Compile `Foo.java` and return the class file's bytes.
49 #[track_caller]
compile_foo() -> Result<Vec<u8>, Box<dyn Error>>50 pub fn compile_foo() -> Result<Vec<u8>, Box<dyn Error>> {
51     // Avoid concurrent tests colliding on the file system by adding a hash of the call-site to the
52     // temp file path
53     let caller_hash = {
54         use core::hash::{Hash, Hasher};
55         let caller = core::panic::Location::caller();
56         let mut hasher = std::collections::hash_map::DefaultHasher::new();
57         caller.hash(&mut hasher);
58         hasher.finish()
59     };
60 
61     // Create a temporary dir to generate class files
62     let mut tmp = std::env::temp_dir();
63     tmp.push(format!("test-java-build-{caller_hash:016x}"));
64 
65     if let Err(err) = std::fs::create_dir(&tmp) {
66         println!("error creating {}: {}", tmp.display(), &err);
67     }
68 
69     // Compile Foo.java into the temp dir
70     let _ = Command::new("javac")
71         .args(["--release", "8"])
72         .arg("-d")
73         .arg(&tmp)
74         .arg("tests/Foo.java")
75         .status()?;
76 
77     // Read the class file bytes
78     let class = {
79         let mut class_file = tmp.clone();
80         class_file.push("com/example/Foo.class");
81         std::fs::read(&class_file)?
82     };
83 
84     // Clean up the temp dir. If an error occurs before this we will leave the temp dir on the
85     // filesystem. That should work better for debugging than cleaning up in all cases.
86     std::fs::remove_dir_all(&tmp)?;
87 
88     Ok(class)
89 }
90