Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

Expand Down
1 change: 0 additions & 1 deletion src/main/java/net/openhft/compiler/CachedCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
}
}


/**
* Compile and load using a specific class loader and writer. The
* compilation result is cached against the loader for future calls.
Expand Down
18 changes: 8 additions & 10 deletions src/test/java/mytest/RuntimeCompileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import net.openhft.compiler.CachedCompiler;
import net.openhft.compiler.CompilerUtils;
import org.junit.Test;
import org.junit.jupiter.api.Test;

import java.net.URL;
import java.net.URLClassLoader;
Expand All @@ -18,10 +18,9 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.*;

public class RuntimeCompileTest {
class RuntimeCompileTest {
private static String code = "package mytest;\n" +
"public class Test implements IntConsumer {\n" +
" public void accept(int num) {\n" +
Expand All @@ -31,7 +30,7 @@ public class RuntimeCompileTest {
"}\n";

@Test
public void outOfBounds() throws Exception {
void outOfBounds() throws Exception {
ClassLoader cl = new URLClassLoader(new URL[0]);
Class<?> aClass = CompilerUtils.CACHED_COMPILER.
loadFromJava(cl, "mytest.Test", code);
Expand All @@ -44,18 +43,17 @@ public void outOfBounds() throws Exception {
}
}

//@Ignore("see https://teamcity.chronicle.software/viewLog.html?buildId=639347&tab=buildResultsDiv&buildTypeId=OpenHFT_BuildAll_BuildJava11compileJava11")
@Test
public void testMultiThread() throws Exception {
void testMultiThread() throws Exception {
StringBuilder largeClass = new StringBuilder("package mytest;\n" +
"public class Test2 implements IntConsumer, java.util.function.IntSupplier {\n" +
" static final java.util.concurrent.atomic.AtomicInteger called = new java.util.concurrent.atomic.AtomicInteger(0);\n" +
" public int getAsInt() { return called.get(); }\n" +
" public void accept(int num) {\n" +
" called.incrementAndGet();\n" +
" }\n");
for (int j=0; j<1_000; j++) {
largeClass.append(" public void accept"+j+"(int num) {\n" +
for (int j = 0; j < 1_000; j++) {
largeClass.append(" public void accept" + j + "(int num) {\n" +
" if ((byte) num != num)\n" +
" throw new IllegalArgumentException();\n" +
" }\n");
Expand All @@ -70,7 +68,7 @@ public void testMultiThread() throws Exception {
final AtomicInteger started = new AtomicInteger(0);
final ExecutorService executor = Executors.newFixedThreadPool(nThreads);
final List<Future<?>> futures = new ArrayList<>();
for (int i=0; i<nThreads; i++) {
for (int i = 0; i < nThreads; i++) {
final int value = i;
futures.add(executor.submit(() -> {
started.incrementAndGet();
Expand Down
28 changes: 14 additions & 14 deletions src/test/java/net/openhft/compiler/AiRuntimeGuardrailsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
*/
package net.openhft.compiler;

import org.junit.Test;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.*;

public class AiRuntimeGuardrailsTest {
class AiRuntimeGuardrailsTest {

@Test
public void validatorStopsCompilationAndRecordsFailure() {
void validatorStopsCompilationAndRecordsFailure() {
AtomicInteger compileInvocations = new AtomicInteger();
TelemetryProbe telemetry = new TelemetryProbe();
GuardrailedCompilerPipeline pipeline = new GuardrailedCompilerPipeline(
Expand Down Expand Up @@ -51,16 +51,16 @@ public void validatorStopsCompilationAndRecordsFailure() {
fail("Unexpected checked exception: " + unexpected.getMessage());
}

assertEquals("Compilation must not run after validation rejection", 0, compileInvocations.get());
assertEquals(0, compileInvocations.get(), "Compilation must not run after validation rejection");
assertEquals(1, telemetry.compileAttempts("agent-A"));
assertEquals(1, telemetry.validationFailures("agent-A"));
assertEquals(0, telemetry.successes("agent-A"));
assertEquals(0, telemetry.compileFailures("agent-A"));
assertFalse("Latency should not be recorded for rejected source", telemetry.hasLatency("agent-A"));
assertFalse(telemetry.hasLatency("agent-A"), "Latency should not be recorded for rejected source");
}

@Test
public void successfulCompilationRecordsMetrics() throws Exception {
void successfulCompilationRecordsMetrics() throws Exception {
TelemetryProbe telemetry = new TelemetryProbe();
GuardrailedCompilerPipeline pipeline = new GuardrailedCompilerPipeline(
Collections.singletonList(source -> {
Expand All @@ -75,19 +75,19 @@ public void successfulCompilationRecordsMetrics() throws Exception {
Class<?> clazz = pipeline.compile("agent-B", "OkClass",
"public class OkClass { public int add(int a, int b) { return a + b; } }");

assertEquals("agent-B should see exactly one attempt", 1, telemetry.compileAttempts("agent-B"));
assertEquals(1, telemetry.compileAttempts("agent-B"), "agent-B should see exactly one attempt");
assertEquals(0, telemetry.validationFailures("agent-B"));
assertEquals(1, telemetry.successes("agent-B"));
assertEquals(0, telemetry.compileFailures("agent-B"));
assertTrue("Latency must be captured for successful compilation", telemetry.hasLatency("agent-B"));
assertTrue(telemetry.hasLatency("agent-B"), "Latency must be captured for successful compilation");

Object instance = clazz.getDeclaredConstructor().newInstance();
int sum = (int) clazz.getMethod("add", int.class, int.class).invoke(instance, 2, 3);
assertEquals(5, sum);
}

@Test
public void cacheHitDoesNotRecompileButRecordsMetric() throws Exception {
void cacheHitDoesNotRecompileButRecordsMetric() throws Exception {
AtomicInteger rawCompileCount = new AtomicInteger();
TelemetryProbe telemetry = new TelemetryProbe();
GuardrailedCompilerPipeline pipeline = new GuardrailedCompilerPipeline(
Expand All @@ -103,17 +103,17 @@ public void cacheHitDoesNotRecompileButRecordsMetric() throws Exception {
Class<?> first = pipeline.compile("agent-C", "CacheCandidate", source);
Class<?> second = pipeline.compile("agent-C", "CacheCandidate", source);

assertEquals("Underlying compiler should only run once thanks to caching", 1, rawCompileCount.get());
assertEquals(1, rawCompileCount.get(), "Underlying compiler should only run once thanks to caching");
assertEquals(2, telemetry.compileAttempts("agent-C"));
assertEquals(0, telemetry.validationFailures("agent-C"));
assertEquals(1, telemetry.successes("agent-C"));
assertEquals(0, telemetry.compileFailures("agent-C"));
assertEquals("Cache hit count should be tracked", 1, telemetry.cacheHits("agent-C"));
assertEquals(1, telemetry.cacheHits("agent-C"), "Cache hit count should be tracked");
assertTrue(first == second);
}

@Test
public void compilerFailureRecordedSeparately() {
void compilerFailureRecordedSeparately() {
TelemetryProbe telemetry = new TelemetryProbe();
GuardrailedCompilerPipeline pipeline = new GuardrailedCompilerPipeline(
Collections.singletonList(source -> {
Expand All @@ -140,7 +140,7 @@ public void compilerFailureRecordedSeparately() {
assertEquals(0, telemetry.validationFailures("agent-D"));
assertEquals(0, telemetry.successes("agent-D"));
assertEquals(1, telemetry.compileFailures("agent-D"));
assertFalse("Failure should not record cache hits", telemetry.hasCacheHits("agent-D"));
assertFalse(telemetry.hasCacheHits("agent-D"), "Failure should not record cache hits");
}

private static final class GuardrailedCompilerPipeline {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
package net.openhft.compiler;

import org.junit.Test;
import org.junit.jupiter.api.Test;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
Expand All @@ -20,14 +20,14 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.*;

public class CachedCompilerAdditionalTest {
class CachedCompilerAdditionalTest {

@Test
public void compileFromJavaReturnsBytecode() throws Exception {
void compileFromJavaReturnsBytecode() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
assertNotNull("System compiler required", compiler);
assertNotNull(compiler, "System compiler required");

try (StandardJavaFileManager standardManager = compiler.getStandardFileManager(null, null, null)) {
CachedCompiler cachedCompiler = new CachedCompiler(null, null);
Expand All @@ -43,45 +43,45 @@ public void compileFromJavaReturnsBytecode() throws Exception {
}

@Test
public void compileFromJavaReturnsEmptyMapOnFailure() throws Exception {
void compileFromJavaReturnsEmptyMapOnFailure() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
assertNotNull("System compiler required", compiler);
assertNotNull(compiler, "System compiler required");
try (StandardJavaFileManager standardManager = compiler.getStandardFileManager(null, null, null)) {
CachedCompiler cachedCompiler = new CachedCompiler(null, null);
MyJavaFileManager fileManager = new MyJavaFileManager(standardManager);
Map<String, byte[]> classes = cachedCompiler.compileFromJava(
"coverage.Broken",
"package coverage; public class Broken { this does not compile }",
fileManager);
assertTrue("Broken source should not produce classes", classes.isEmpty());
assertTrue(classes.isEmpty(), "Broken source should not produce classes");
}
}

@Test
public void updateFileManagerForClassLoaderInvokesConsumer() throws Exception {
void updateFileManagerForClassLoaderInvokesConsumer() throws Exception {
CachedCompiler compiler = new CachedCompiler(null, null);
ClassLoader loader = new ClassLoader() {
};
compiler.loadFromJava(loader, "coverage.UpdateTarget", "package coverage; public class UpdateTarget {}");

AtomicBoolean invoked = new AtomicBoolean(false);
compiler.updateFileManagerForClassLoader(loader, fm -> invoked.set(true));
assertTrue("Consumer should be invoked when manager exists", invoked.get());
assertTrue(invoked.get(), "Consumer should be invoked when manager exists");
}

@Test
public void updateFileManagerNoOpWhenClassLoaderUnknown() {
void updateFileManagerNoOpWhenClassLoaderUnknown() {
CachedCompiler compiler = new CachedCompiler(null, null);
AtomicBoolean invoked = new AtomicBoolean(false);
compiler.updateFileManagerForClassLoader(new ClassLoader() {
}, fm -> invoked.set(true));
assertTrue("Consumer should not be invoked when manager missing", !invoked.get());
assertTrue(!invoked.get(), "Consumer should not be invoked when manager missing");
}

@Test
public void closeClosesAllManagedFileManagers() throws Exception {
void closeClosesAllManagedFileManagers() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
assertNotNull("System compiler required", compiler);
assertNotNull(compiler, "System compiler required");
CachedCompiler cachedCompiler = new CachedCompiler(null, null);
AtomicBoolean closed = new AtomicBoolean(false);
cachedCompiler.setFileManagerOverride(standard -> new TrackingFileManager(standard, closed));
Expand All @@ -90,11 +90,11 @@ public void closeClosesAllManagedFileManagers() throws Exception {
};
cachedCompiler.loadFromJava(loader, "coverage.CloseTarget", "package coverage; public class CloseTarget {}");
cachedCompiler.close();
assertTrue("Close should propagate to file managers", closed.get());
assertTrue(closed.get(), "Close should propagate to file managers");
}

@Test
public void createDefaultWriterFlushesOnClose() throws Exception {
void createDefaultWriterFlushesOnClose() throws Exception {
Method factory = CachedCompiler.class.getDeclaredMethod("createDefaultWriter");
factory.setAccessible(true);
PrintWriter writer = (PrintWriter) factory.invoke(null);
Expand All @@ -103,7 +103,7 @@ public void createDefaultWriterFlushesOnClose() throws Exception {
}

@Test
public void validateClassNameAllowsDescriptorForms() throws Exception {
void validateClassNameAllowsDescriptorForms() throws Exception {
Method validate = CachedCompiler.class.getDeclaredMethod("validateClassName", String.class);
validate.setAccessible(true);

Expand All @@ -125,7 +125,7 @@ public void validateClassNameAllowsDescriptorForms() throws Exception {
}

@Test
public void safeResolvePreventsPathTraversal() throws Exception {
void safeResolvePreventsPathTraversal() throws Exception {
Method method = CachedCompiler.class.getDeclaredMethod("safeResolve", File.class, String.class);
method.setAccessible(true);
Path root = Files.createTempDirectory("cached-compiler-safe");
Expand All @@ -142,9 +142,9 @@ public void safeResolvePreventsPathTraversal() throws Exception {
}

@Test
public void writesSourceAndClassFilesWhenDirectoriesProvided() throws Exception {
void writesSourceAndClassFilesWhenDirectoriesProvided() throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
assertNotNull("System compiler required", compiler);
assertNotNull(compiler, "System compiler required");

Path sourceDir = Files.createTempDirectory("cached-compiler-src");
Path classDir = Files.createTempDirectory("cached-compiler-classes");
Expand All @@ -160,8 +160,8 @@ public void writesSourceAndClassFilesWhenDirectoriesProvided() throws Exception

Path sourceFile = sourceDir.resolve("coverage/FileOutput.java");
Path classFile = classDir.resolve("coverage/FileOutput.class");
assertTrue("Source file should be emitted", Files.exists(sourceFile));
assertTrue("Class file should be emitted", Files.exists(classFile));
assertTrue(Files.exists(sourceFile), "Source file should be emitted");
assertTrue(Files.exists(classFile), "Class file should be emitted");
byte[] firstBytes = Files.readAllBytes(classFile);

CachedCompiler secondPass = new CachedCompiler(sourceDir.toFile(), classDir.toFile());
Expand All @@ -172,10 +172,10 @@ public void writesSourceAndClassFilesWhenDirectoriesProvided() throws Exception
secondPass.close();

byte[] updatedBytes = Files.readAllBytes(classFile);
assertTrue("Updating the source should change emitted bytecode", !Arrays.equals(firstBytes, updatedBytes));
assertTrue(!Arrays.equals(firstBytes, updatedBytes), "Updating the source should change emitted bytecode");

Path backupFile = classDir.resolve("coverage/FileOutput.class.bak");
assertTrue("Backup should be cleaned up", !Files.exists(backupFile));
assertTrue(!Files.exists(backupFile), "Backup should be cleaned up");
} finally {
deleteRecursively(classDir);
deleteRecursively(sourceDir);
Expand Down
Loading