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