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
10 changes: 7 additions & 3 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}

strategy:
matrix:
java: [ 11, 17, 21 ]
os: [ ubuntu-latest ]
java: [ 11, 17, 21, 25 ]
include:
- os: windows-latest
java: 25

steps:
- name: Checkout
Expand All @@ -26,4 +30,4 @@ jobs:
cache: 'maven'

- name: Build and test
run: mvn clean verify -U
run: mvn clean verify -U ${{ matrix.os != 'windows-latest' && '-Pdocker' || '' }}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.dflib.jjava.distro;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -13,18 +14,20 @@
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public abstract class ContainerizedKernelCase {

private static final Logger LOGGER = LoggerFactory.getLogger(ContainerizedKernelCase.class);

protected static final GenericContainer<?> container;
protected static GenericContainer<?> container;
protected static final String WORKING_DIRECTORY = "/test";
protected static final String CONTAINER_KERNELSPEC = "/usr/share/jupyter/kernels/java";
protected static final String CONTAINER_RESOURCES = WORKING_DIRECTORY + "/resources";
Expand All @@ -34,20 +37,13 @@ public abstract class ContainerizedKernelCase {
private static final String FS_KERNELSPEC = "../kernelspec/java";
private static final String FS_RESOURCES = "src/test/resources";

static {
container = new GenericContainer<>(BASE_IMAGE)
.withWorkingDirectory(WORKING_DIRECTORY)
.withCopyToContainer(MountableFile.forHostPath(FS_KERNELSPEC), CONTAINER_KERNELSPEC)
.withCopyToContainer(MountableFile.forHostPath(FS_RESOURCES), CONTAINER_RESOURCES)
.withCommand("bash", "-c", getStartupCommand())
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.waitingFor(Wait.forSuccessfulCommand(getSuccessfulCommand()))
.withStartupTimeout(Duration.ofMinutes(1));
container.start();
}
private static final String TESTS_ENABLED_PROPERTY = "docker.tests.enabled";

@BeforeAll
static void compileSources() throws IOException, InterruptedException {
static void setUp() throws IOException, InterruptedException {
initializeContainer();
Assumptions.assumeTrue(container != null, "Docker tests are disabled. Enable with -Pdocker");

String source = "$(find " + CONTAINER_RESOURCES + "/src -name '*.java')";
Container.ExecResult compileResult = executeInContainer("javac -d " + TEST_CLASSPATH + " " + source);

Expand All @@ -68,9 +64,29 @@ protected static Container.ExecResult executeInKernel(String snippet) throws IOE
}

protected static Container.ExecResult executeInKernel(String snippet, Map<String, String> env) throws IOException, InterruptedException {
String snippet64 = Base64.getEncoder().encodeToString(snippet.getBytes());
String jupyterCommand = venvCommand("jupyter console --kernel=java --simple-prompt");
String[] containerCommand = new String[]{"bash", "-c", "echo \"" + snippet64 + "\" | base64 -d | " + jupyterCommand};
long snippetLines = snippet.lines().count();
String snippetEscaped = snippet.replace("\\", "\\\\").replace("\"", "\\\"");
String snippetFeeding = Arrays.stream(snippetEscaped.split("\n"))
.flatMap(line -> Stream.of(
"p.expect(r'In \\[\\d+\\]:')",
"p.sendline(\"" + line + "\")"
))
.collect(Collectors.joining("\n"));

String pexpectScript = String.join("\n",
"import pexpect, sys, os, time",
"env = os.environ.copy()",
"env['PROMPT_TOOLKIT_NO_CPR'] = '1'",
"env['TERM'] = 'dumb'",
"p=pexpect.spawn('" + venvCommand("jupyter") + "', "
+ "['console', '--kernel=java', '--no-confirm-exit'], "
+ "env=env, timeout=60, encoding='utf-8')",
"p.logfile_read = sys.stdout",
snippetFeeding,
"p.expect(r'In \\[" + (snippetLines + 1) + "\\]:')",
"p.close(force=True)"
);
String[] containerCommand = new String[]{venvCommand("python"), "-c", pexpectScript};
Container.ExecResult execResult = container.execInContainer(ExecConfig.builder()
.envVars(env)
.command(containerCommand)
Expand All @@ -80,17 +96,33 @@ protected static Container.ExecResult executeInKernel(String snippet, Map<String
LOGGER.info("env = {}", env);
LOGGER.info("snippet = {}", snippet);
LOGGER.info("exitCode = {}", execResult.getExitCode());
LOGGER.debug("stderr = {}", execResult.getStderr());
LOGGER.debug("stdout = {}", execResult.getStdout());
LOGGER.debug("stderr = {}", execResult.getStderr());
return execResult;
}

private static void initializeContainer() {
if (container != null || !"true".equals(System.getProperty(TESTS_ENABLED_PROPERTY))) {
return;
}

container = new GenericContainer<>(BASE_IMAGE)
.withWorkingDirectory(WORKING_DIRECTORY)
.withCopyToContainer(MountableFile.forHostPath(FS_KERNELSPEC), CONTAINER_KERNELSPEC)
.withCopyToContainer(MountableFile.forHostPath(FS_RESOURCES), CONTAINER_RESOURCES)
.withCommand("bash", "-c", getStartupCommand())
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.waitingFor(Wait.forSuccessfulCommand(getSuccessfulCommand()))
.withStartupTimeout(Duration.ofMinutes(1));
container.start();
}

private static String getStartupCommand() {
return String.join(" && ",
"apt-get update",
"apt-get install --no-install-recommends -y python3 python3-pip python3-venv",
"apt-get install --no-install-recommends -y python3 python3-pip python3-venv curl",
"python3 -m venv ./venv",
venvCommand("pip install jupyter-console --progress-bar off"),
venvCommand("pip install jupyter-console pexpect --progress-bar off"),
"tail -f /dev/null"
);
}
Expand Down
55 changes: 32 additions & 23 deletions jjava-distro/src/test/java/org/dflib/jjava/distro/KernelEnvIT.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package org.dflib.jjava.distro;

import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Container;

import java.util.Map;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class KernelEnvIT extends ContainerizedKernelCase {
class KernelEnvIT extends ContainerizedKernelCase {

@Test
public void compilerOpts() throws Exception {
void compilerOpts() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_COMPILER_OPTS, "-source 9");
String snippet = "var value = 1;";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), CoreMatchers.allOf(
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), allOf(
containsString("| var value = 1;"),
containsString(Runtime.version().feature() == 11
? "'var' is a restricted local variable type"
Expand All @@ -27,78 +29,85 @@ public void compilerOpts() throws Exception {
}

@Test
public void timeout() throws Exception {
void timeout() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_TIMEOUT, "3000");
String snippet = "Thread.sleep(5000);";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), CoreMatchers.allOf(
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), allOf(
containsString("| " + snippet),
containsString("Evaluation timed out after 3000 milliseconds.")
));
}

@Test
public void classpath() throws Exception {
void classpath() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_CLASSPATH, TEST_CLASSPATH);
String snippet = String.join("\n",
"import org.dflib.jjava.Dummy;",
"Dummy.class.getName()"
"\"className = \" + Dummy.class.getName();"
);
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString("org.dflib.jjava.Dummy"));
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString("className = org.dflib.jjava.Dummy"));
}

@Test
public void startUpScriptsPath() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_STARTUP_SCRIPTS_PATH, CONTAINER_RESOURCES + "/test-ping.jshell");
void startUpScriptsPath() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_STARTUP_SCRIPTS_PATH, CONTAINER_RESOURCES + "/test-ping.jshell");
String snippet = "ping()";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), not(containsString("|")));
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString("pong!"));
}

@Test
public void startUpScript() throws Exception {
void startUpScript() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_STARTUP_SCRIPT, "public String ping() { return \"pong!\"; }");
String snippet = "ping()";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), not(containsString("|")));
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString("pong!"));
}

@Test
public void loadExtensions_Default() throws Exception {
void loadExtensions_Default() throws Exception {
String snippet = "printf(\"Hello, %s!\", \"world\");";
Container.ExecResult snippetResult = executeInKernel(snippet);

assertThat(snippetResult.getStderr(), not(containsString("|")));
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString("Hello, world!"));
}

@Test
public void loadExtensions_Disable() throws Exception {
void loadExtensions_Disable() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_LOAD_EXTENSIONS, "0");
String snippet = "printf(\"Hello, %s!\", \"world\");";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), CoreMatchers.allOf(
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), allOf(
containsString("| " + snippet),
containsString("cannot find symbol")
));
}

@Test
public void jvmOpts() throws Exception {
void jvmOpts() throws Exception {
Map<String, String> env = Map.of(Env.JJAVA_JVM_OPTS, "-Xmx300m");
String snippet = "Runtime.getRuntime().maxMemory()";
Container.ExecResult snippetResult = executeInKernel(snippet, env);

assertThat(snippetResult.getStderr(), not(containsString("|")));
assertEquals(0, snippetResult.getExitCode(), snippetResult.getStdout());
assertThat(snippetResult.getStdout(), not(containsString("|")));
assertThat(snippetResult.getStdout(), containsString(String.valueOf(300 * (int) Math.pow(1024, 2))));
}
}
Loading