Skip to content

Commit 12783ba

Browse files
committed
Added test for runtime compilation
1 parent addd3cc commit 12783ba

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
set -ex
3+
set -o pipefail
4+
5+
## resolve folder of this script, following all symlinks,
6+
## http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in
7+
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
8+
while [ -h "$SCRIPT_SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
9+
SCRIPT_DIR="$( cd -P "$( dirname "$SCRIPT_SOURCE" )" && pwd )"
10+
SCRIPT_SOURCE="$(readlink "$SCRIPT_SOURCE")"
11+
# if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
12+
[[ $SCRIPT_SOURCE != /* ]] && SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_SOURCE"
13+
done
14+
readonly SCRIPT_DIR="$( cd -P "$( dirname "$SCRIPT_SOURCE" )" && pwd )"
15+
16+
source $SCRIPT_DIR/testlib.bash
17+
18+
parseArguments "$@"
19+
processArguments
20+
setup
21+
tryJreCompilation 2>&1| tee $REPORT_FILE
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//17 and up, originally by [email protected].
2+
3+
import javax.tools.*;
4+
import javax.tools.JavaFileObject.Kind;
5+
import java.net.URI;
6+
import java.util.*;
7+
8+
public class InProcessCompileDemo {
9+
public static void main(String[] args) throws Exception {
10+
String jvmVersion="17";
11+
if (args.length>0) {
12+
jvmVersion=args[0];
13+
}
14+
String className = "Hello";
15+
String sourceCode = """
16+
public class Hello {
17+
public static void main(String[] args) {
18+
System.out.println("Hello from in-memory compiled code!");
19+
}
20+
}
21+
""";
22+
23+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
24+
if (compiler == null) {
25+
System.err.println("❌ No system compiler found — you're probably on a stripped JRE.");
26+
return;
27+
}
28+
System.out.println("✅ Compiler loaded: " + compiler.getClass().getName());
29+
30+
// in-memory source
31+
JavaFileObject source = new StringJavaFileObject(className, sourceCode);
32+
33+
// file manager that captures class bytes in memory
34+
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
35+
StandardJavaFileManager std = compiler.getStandardFileManager(diagnostics, null, null);
36+
MemoryFileManager memFM = new MemoryFileManager(std);
37+
38+
// compile for a specific release (22 here)
39+
JavaCompiler.CompilationTask task = compiler.getTask(
40+
null, memFM, diagnostics,
41+
List.of("--release", jvmVersion), null, List.of(source));
42+
43+
boolean ok = task.call();
44+
diagnostics.getDiagnostics().forEach(System.out::println);
45+
46+
if (!ok) {
47+
System.out.println("❌ Compilation failed");
48+
return;
49+
}
50+
System.out.println("✅ Compilation succeeded");
51+
52+
// load and run main()
53+
ClassLoader loader = new MemoryClassLoader(memFM.getClassBytes());
54+
Class<?> hello = loader.loadClass(className);
55+
hello.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
56+
}
57+
58+
// ===== Helpers =====
59+
60+
// Source stored entirely in memory
61+
static final class StringJavaFileObject extends SimpleJavaFileObject {
62+
private final String code;
63+
StringJavaFileObject(String className, String code) {
64+
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
65+
this.code = code;
66+
}
67+
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; }
68+
}
69+
70+
// Captures compiled class bytes in memory
71+
static final class MemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
72+
private final Map<String, ByteArrayJavaFileObject> classFiles = new HashMap<>();
73+
MemoryFileManager(StandardJavaFileManager fileManager) { super(fileManager); }
74+
@Override
75+
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) {
76+
ByteArrayJavaFileObject file = new ByteArrayJavaFileObject(className, kind);
77+
classFiles.put(className, file);
78+
return file;
79+
}
80+
Map<String, byte[]> getClassBytes() {
81+
Map<String, byte[]> out = new HashMap<>();
82+
for (var e : classFiles.entrySet()) out.put(e.getKey(), e.getValue().getBytes());
83+
return out;
84+
}
85+
}
86+
87+
// Holds class bytes
88+
static final class ByteArrayJavaFileObject extends SimpleJavaFileObject {
89+
private final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
90+
ByteArrayJavaFileObject(String name, Kind kind) {
91+
super(URI.create("mem:///" + name.replace('.', '/') + kind.extension), kind);
92+
}
93+
@Override public java.io.OutputStream openOutputStream() { return out; }
94+
byte[] getBytes() { return out.toByteArray(); }
95+
}
96+
97+
// ClassLoader that defines classes from captured bytes
98+
static final class MemoryClassLoader extends ClassLoader {
99+
private final Map<String, byte[]> classes;
100+
MemoryClassLoader(Map<String, byte[]> classes) {
101+
super(InProcessCompileDemo.class.getClassLoader());
102+
this.classes = classes;
103+
}
104+
@Override
105+
protected Class<?> findClass(String name) throws ClassNotFoundException {
106+
byte[] bytes = classes.get(name);
107+
if (bytes == null) throw new ClassNotFoundException(name);
108+
return defineClass(name, bytes, 0, bytes.length);
109+
}
110+
}
111+
}

containersQa/testlib.bash

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function pretest() {
7979
SKIPPED2="!skipped! no local maven !skipped!"
8080
SKIPPED3="!skipped! broken right now !skipped!"
8181
SKIPPED4="!skipped! jre only testing does not support this feature."
82+
SKIPPED44="!skipped! jdk only testing does not support this feature."
8283
SKIPPED5="!skipped! reproducers security now must be enabled by OTOOL_RUN_SECURITY_REPRODUCERS=true"
8384
SKIPPED6="!skipped! rhel 7 based images do not support this functionality."
8485
SKIPPED7="!skipped! rhel 7 Os version of Podman does not support this functionality."
@@ -100,12 +101,21 @@ function setup() {
100101
# With the addition of a JRE Runtime container we should not run for full jdk features like
101102
# Maven, S2i, Javac.
102103
function skipIfJreExecution() {
104+
# maybe replace OTOOL_jresdk with check on javac command?
103105
if [ "$OTOOL_jresdk" == "jre" ] ; then
104106
echo "$SKIPPED4"
105107
exit
106108
fi
107109
}
108110

111+
function skipIfJdkExecution() {
112+
# maybe replace OTOOL_jresdk with check on javac command?
113+
if [ "$OTOOL_jresdk" == "jdk" ] ; then
114+
echo "$SKIPPED44"
115+
exit
116+
fi
117+
}
118+
109119
# Use this flag to skip tests when executing on a Rhel 7 host. In some cases backend container
110120
# functionality the tests are looking for is not available in that version.
111121
function skipIfRhel7Execution() {
@@ -218,6 +228,10 @@ function runOnBaseDirBash() {
218228
$PD_PROVIDER run -i $HASH bash -c "$1"
219229
}
220230

231+
function runOnBaseDirBashWithMount() {
232+
$PD_PROVIDER run -v=$LIBCQA_SCRIPT_DIR:/testsDir -i $HASH bash -c "$1"
233+
}
234+
221235
function runOnBaseDirBashOtherUser() {
222236
$PD_PROVIDER run -i -u 12324 $HASH bash -c "$1"
223237
}
@@ -1130,3 +1144,16 @@ function assertCryptoProviders() {
11301144
runOnBaseDirBash "$commandProviders" 2>&1| tee $REPORT_FILE
11311145
set -x
11321146
}
1147+
1148+
1149+
function tryJreCompilation() {
1150+
runOnBaseDirBashWithMount "ls /testsDir | grep \\.java$"
1151+
local v=$(runOnBaseDirBashWithMount "java -version " 2>&1 | grep "openjdk version" | head -n 1)
1152+
if echo "$v" | grep '"1.8' || echo "$v" | grep '"11' ; then
1153+
echo '!skipped! Skipping runtime compilation on 8 and 11'
1154+
exit 0
1155+
else
1156+
echo '17+, going on'
1157+
fi
1158+
runOnBaseDirBashWithMount "java /testsDir/InProcessCompileDemo.java"
1159+
}

0 commit comments

Comments
 (0)