From 9dc586e25dc534d0ada42b7100ed20a5c48d1388 Mon Sep 17 00:00:00 2001 From: Firas RG Date: Sat, 10 Aug 2024 13:51:21 +0100 Subject: [PATCH 1/6] [Testing][JShellAPI] Settings up gradle tasks for testing; ; Note: this includes adding a 1st class for tests; --- JShellAPI/build.gradle | 58 ++++++++++++++++++- .../jshellapi/JShellApiTests.java | 16 +++++ 2 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java diff --git a/JShellAPI/build.gradle b/JShellAPI/build.gradle index 7a2380b..9314dec 100644 --- a/JShellAPI/build.gradle +++ b/JShellAPI/build.gradle @@ -13,14 +13,27 @@ dependencies { implementation 'com.github.docker-java:docker-java-transport-httpclient5:3.3.6' implementation 'com.github.docker-java:docker-java-core:3.3.6' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + configurations { + all { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + exclude group: 'ch.qos.logback', module: 'logback-classic' + exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' + } + } + } + testImplementation gradleTestKit() + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" + } +def imageName = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest'; + jib { from.image = 'eclipse-temurin:21' to { - image = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest' + image = imageName auth { username = System.getenv('ORG_REGISTRY_USER') ?: '' password = System.getenv('ORG_REGISTRY_PASSWORD') ?: '' @@ -36,4 +49,43 @@ shadowJar { archiveBaseName.set('JShellPlaygroundBackend') archiveClassifier.set('') archiveVersion.set('') -} \ No newline at end of file +} + +tasks.register('buildDockerImage') { + group = 'Docker' + description = 'builds jshellwrapper as docker image' + dependsOn jibDockerBuild + doFirst{ + println('creating docker image...') + } + doLast{ + println('docker image is ready for use') + } +} + +tasks.register('removeDockerImage', Exec) { + group = 'Docker' + description = 'removes jshellwrapper image' + commandLine 'docker', 'rmi', '-f', imageName + doLast{ + println('docker image has been removed') + } +} + +tasks.named('test') { + dependsOn tasks.named('buildDockerImage') + + doFirst { + try { + println 'Running JShellAPI tests...' + } catch (Exception e) { + println 'JShellAPI tests failed' + tasks.named('removeDockerImage').get().execute() + throw e + } + } + doLast { + println 'JShellAPI tests completed.' + } + finalizedBy tasks.named('removeDockerImage') +} diff --git a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java new file mode 100644 index 0000000..57254a5 --- /dev/null +++ b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java @@ -0,0 +1,16 @@ +package org.togetherjava.jshellapi; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +// TODO - write some integrations +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class JShellApiTests { + + @Test + public void test() { + assertThat(true).isTrue(); + } +} From c8747e1d316b719e43e327e0ebe67a43a55054ff Mon Sep 17 00:00:00 2001 From: Firas RG Date: Sat, 10 Aug 2024 17:22:58 +0100 Subject: [PATCH 2/6] [Testing][JShellAPI] Setting first integration test for /eval endpoint; Note: this includes changes and improvements in gradle.build files; --- JShellAPI/build.gradle | 63 +++++++------------ .../jshellapi/rest/ApiEndpoints.java | 7 +++ .../jshellapi/service/DockerService.java | 24 +++++-- ...itional-spring-configuration-metadata.json | 8 +++ JShellAPI/src/main/resources/application.yaml | 3 + .../jshellapi/JShellApiTests.java | 52 ++++++++++++++- JShellWrapper/build.gradle | 4 +- .../src/test/java/JShellWrapperTest.java | 20 +++--- build.gradle | 4 ++ 9 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java create mode 100644 JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json diff --git a/JShellAPI/build.gradle b/JShellAPI/build.gradle index 9314dec..6ebd62c 100644 --- a/JShellAPI/build.gradle +++ b/JShellAPI/build.gradle @@ -14,26 +14,18 @@ dependencies { implementation 'com.github.docker-java:docker-java-core:3.3.6' testImplementation('org.springframework.boot:spring-boot-starter-test') { - configurations { - all { - exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' - exclude group: 'ch.qos.logback', module: 'logback-classic' - exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' - } - } + exclude group: 'ch.qos.logback', module: 'logback-classic' } - testImplementation gradleTestKit() + testImplementation 'org.springframework.boot:spring-boot-starter-webflux' annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" } -def imageName = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest'; - jib { from.image = 'eclipse-temurin:21' to { - image = imageName + image = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest' auth { username = System.getenv('ORG_REGISTRY_USER') ?: '' password = System.getenv('ORG_REGISTRY_PASSWORD') ?: '' @@ -51,41 +43,28 @@ shadowJar { archiveVersion.set('') } -tasks.register('buildDockerImage') { - group = 'Docker' - description = 'builds jshellwrapper as docker image' - dependsOn jibDockerBuild - doFirst{ - println('creating docker image...') - } - doLast{ - println('docker image is ready for use') +def jshellWrapperImageName = rootProject.ext.jShellWrapperImageName; + +processResources { + filesMatching('application.yaml') { + expand(jShellWrapperImageName: jshellWrapperImageName) } } -tasks.register('removeDockerImage', Exec) { - group = 'Docker' - description = 'removes jshellwrapper image' - commandLine 'docker', 'rmi', '-f', imageName - doLast{ - println('docker image has been removed') - } + +def taskBuildDockerImage = tasks.register('buildDockerImage') { + group = 'docker' + description = 'builds jshellwrapper as docker image' + dependsOn project(':JShellWrapper').tasks.named('jibDockerBuild') } -tasks.named('test') { - dependsOn tasks.named('buildDockerImage') +def taskRemoveDockerImage = tasks.register('removeDockerImage', Exec) { + group = 'docker' + description = 'removes jshellwrapper image' + commandLine 'docker', 'rmi', '-f', jshellWrapperImageName +} - doFirst { - try { - println 'Running JShellAPI tests...' - } catch (Exception e) { - println 'JShellAPI tests failed' - tasks.named('removeDockerImage').get().execute() - throw e - } - } - doLast { - println 'JShellAPI tests completed.' - } - finalizedBy tasks.named('removeDockerImage') +test { + dependsOn taskBuildDockerImage + finalizedBy taskRemoveDockerImage } diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java new file mode 100644 index 0000000..a0b4bea --- /dev/null +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java @@ -0,0 +1,7 @@ +package org.togetherjava.jshellapi.rest; + +public final class ApiEndpoints { + private ApiEndpoints() {} + + public static final String EVALUATE_CODE_SNIPPET = "/jshell/eval"; +} diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java index a2ffb69..fa1e171 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java @@ -7,9 +7,11 @@ import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientImpl; import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import jakarta.el.PropertyNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Value; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -29,6 +31,9 @@ public class DockerService implements DisposableBean { private final DockerClient client; + @Value("${jshell-wrapper.image-name}") + private String jshellWrapperImageName; + public DockerService(Config config) { DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); @@ -59,22 +64,33 @@ private void cleanupLeftovers(UUID currentId) { public String spawnContainer(long maxMemoryMegs, long cpus, @Nullable String cpuSetCpus, String name, Duration evalTimeout, long sysoutLimit) throws InterruptedException { - String imageName = "togetherjava.org:5001/togetherjava/jshellwrapper"; + String imageName = Optional.ofNullable(this.jshellWrapperImageName) + .orElseThrow(() -> new PropertyNotFoundException( + "unable to find jshellWrapper image name property")); + + String[] imageNameParts = imageName.split(":master"); + + if (imageNameParts.length != 1) { + throw new IllegalArgumentException("invalid jshellWrapper image name"); + } + + String baseImageName = imageNameParts[0]; + boolean presentLocally = client.listImagesCmd() - .withFilter("reference", List.of(imageName)) + .withFilter("reference", List.of(baseImageName)) .exec() .stream() .flatMap(it -> Arrays.stream(it.getRepoTags())) .anyMatch(it -> it.endsWith(":master")); if (!presentLocally) { - client.pullImageCmd(imageName) + client.pullImageCmd(baseImageName) .withTag("master") .exec(new PullImageResultCallback()) .awaitCompletion(5, TimeUnit.MINUTES); } - return client.createContainerCmd(imageName + ":master") + return client.createContainerCmd(baseImageName + ":master") .withHostConfig(HostConfig.newHostConfig() .withAutoRemove(true) .withInit(true) diff --git a/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..6c9bcc1 --- /dev/null +++ b/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,8 @@ +{ + "properties": [ + { + "name": "jshell-wrapper.image-name", + "type": "java.lang.String", + "description": "JShellWrapper image name injected from the top-level gradle build file." + } +] } diff --git a/JShellAPI/src/main/resources/application.yaml b/JShellAPI/src/main/resources/application.yaml index 831580c..83a6e23 100644 --- a/JShellAPI/src/main/resources/application.yaml +++ b/JShellAPI/src/main/resources/application.yaml @@ -20,6 +20,9 @@ jshellapi: dockerResponseTimeout: 60 dockerConnectionTimeout: 60 +jshell-wrapper: + image-name: ${jShellWrapperImageName} + server: error: include-message: always diff --git a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java index 57254a5..434f0e2 100644 --- a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java +++ b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java @@ -1,16 +1,62 @@ package org.togetherjava.jshellapi; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.reactive.server.WebTestClient; + +import org.togetherjava.jshellapi.dto.JShellResult; +import org.togetherjava.jshellapi.rest.ApiEndpoints; + +import java.time.Duration; import static org.assertj.core.api.Assertions.assertThat; -// TODO - write some integrations +/** + * This class holds integration tests for JShellAPI. It depends on gradle building image task, fore + * more information check "test" section in gradle.build file. + * + * @author Firas Regaieg + */ +@ContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class JShellApiTests { + @Autowired + private WebTestClient webTestClient; + + private static final String TEST_EVALUATION_ID = "test"; + private static final String TEST_CODE_INPUT = "2+2"; + private static final String TEST_CODE_EXPECTED_OUTPUT = "4"; + @Test - public void test() { - assertThat(true).isTrue(); + @DisplayName("When posting code snippet, evaluate it then returns successfully result") + public void evaluateCodeSnippetTest() { + + JShellResult result = this.webTestClient.mutate() + .responseTimeout(Duration.ofSeconds(6)) + .build() + .post() + .uri(ApiEndpoints.EVALUATE_CODE_SNIPPET + "/" + TEST_EVALUATION_ID) + .bodyValue(TEST_CODE_INPUT) + .exchange() + .expectStatus() + .isOk() + .expectBody(JShellResult.class) + .value(task -> assertThat(task).isNotNull()) + .returnResult() + .getResponseBody(); + + assertThat(result).isNotNull(); + + boolean isValidResult = result.snippetsResults() + .stream() + .filter(res -> res.result() != null) + .anyMatch(res -> res.result().equals(TEST_CODE_EXPECTED_OUTPUT)); + + assertThat(isValidResult).isTrue(); + } } diff --git a/JShellWrapper/build.gradle b/JShellWrapper/build.gradle index 279e8c0..ddacab9 100644 --- a/JShellWrapper/build.gradle +++ b/JShellWrapper/build.gradle @@ -24,7 +24,7 @@ test { jib { from.image = 'eclipse-temurin:22-alpine' to { - image = 'togetherjava.org:5001/togetherjava/jshellwrapper:master' ?: 'latest' + image = rootProject.ext.jShellWrapperImageName auth { username = System.getenv('ORG_REGISTRY_USER') ?: '' password = System.getenv('ORG_REGISTRY_PASSWORD') ?: '' @@ -41,4 +41,4 @@ shadowJar { archiveBaseName.set('JShellWrapper') archiveClassifier.set('') archiveVersion.set('') -} \ No newline at end of file +} diff --git a/JShellWrapper/src/test/java/JShellWrapperTest.java b/JShellWrapper/src/test/java/JShellWrapperTest.java index 962313f..7863d19 100644 --- a/JShellWrapper/src/test/java/JShellWrapperTest.java +++ b/JShellWrapper/src/test/java/JShellWrapperTest.java @@ -49,12 +49,10 @@ void testHelloWorld() { @Test void testExpressionResult() { - evalTest( - """ - eval - 1 - "Hello world!\"""", - """ + evalTest(""" + eval + 1 + "Hello world!\"""", """ OK 0 OK @@ -67,12 +65,10 @@ void testExpressionResult() { false """); - evalTest( - """ - eval - 1 - 2+2""", - """ + evalTest(""" + eval + 1 + 2+2""", """ OK 0 OK diff --git a/build.gradle b/build.gradle index 81ebaa4..6359690 100644 --- a/build.gradle +++ b/build.gradle @@ -68,3 +68,7 @@ subprojects { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' } } + +ext { + jShellWrapperImageName = 'togetherjava.org:5001/togetherjava/jshellwrapper:master' ?: 'latest' +} From 6ab35c44b78cda32b38f32c5a305104794f7de15 Mon Sep 17 00:00:00 2001 From: Firas RG Date: Sat, 10 Aug 2024 18:22:47 +0100 Subject: [PATCH 3/6] [Testing][JShellAPI] Synchronizing endpoint paths between controllers and test classes --- .../togetherjava/jshellapi/rest/ApiEndpoints.java | 12 +++++++++++- .../jshellapi/rest/JShellController.java | 12 ++++++------ .../org/togetherjava/jshellapi/JShellApiTests.java | 5 ++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java index a0b4bea..889014f 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java @@ -1,7 +1,17 @@ package org.togetherjava.jshellapi.rest; +/** + * This class holds endpoints mentioned in controllers. The main objective is to keep endpoints + * synchronized with testing classes. + * + * @author Firas Regaieg + */ public final class ApiEndpoints { private ApiEndpoints() {} - public static final String EVALUATE_CODE_SNIPPET = "/jshell/eval"; + public static final String BASE = "/jshell"; + public static final String EVALUATE = "/eval"; + public static final String SINGLE_EVALUATE = "/single-eval"; + public static final String SNIPPETS = "/snippets"; + public static final String STARTING_SCRIPT = "/startup_script"; } diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java index 2c60570..d605bc2 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java @@ -15,13 +15,13 @@ import java.util.List; -@RequestMapping("jshell") +@RequestMapping(ApiEndpoints.BASE) @RestController public class JShellController { private JShellSessionService service; private StartupScriptsService startupScriptsService; - @PostMapping("/eval/{id}") + @PostMapping(ApiEndpoints.EVALUATE + "/{id}") public JShellResult eval(@PathVariable String id, @RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException { @@ -32,7 +32,7 @@ public JShellResult eval(@PathVariable String id, "An operation is already running")); } - @PostMapping("/eval") + @PostMapping(ApiEndpoints.EVALUATE) public JShellResultWithId eval(@RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException { JShellService jShellService = service.session(startupScriptId); @@ -42,7 +42,7 @@ public JShellResultWithId eval(@RequestParam(required = false) StartupScriptId s "An operation is already running"))); } - @PostMapping("/single-eval") + @PostMapping(ApiEndpoints.SINGLE_EVALUATE) public JShellResult singleEval(@RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException { JShellService jShellService = service.oneTimeSession(startupScriptId); @@ -51,7 +51,7 @@ public JShellResult singleEval(@RequestParam(required = false) StartupScriptId s "An operation is already running")); } - @GetMapping("/snippets/{id}") + @GetMapping(ApiEndpoints.SNIPPETS + "/{id}") public List snippets(@PathVariable String id, @RequestParam(required = false) boolean includeStartupScript) throws DockerException { validateId(id); @@ -71,7 +71,7 @@ public void delete(@PathVariable String id) throws DockerException { service.deleteSession(id); } - @GetMapping("/startup_script/{id}") + @GetMapping(ApiEndpoints.STARTING_SCRIPT + "/{id}") public String startupScript(@PathVariable StartupScriptId id) { return startupScriptsService.get(id); } diff --git a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java index 434f0e2..e0809a4 100644 --- a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java +++ b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java @@ -35,11 +35,14 @@ public class JShellApiTests { @DisplayName("When posting code snippet, evaluate it then returns successfully result") public void evaluateCodeSnippetTest() { + final String endpoint = + String.join("/", ApiEndpoints.BASE, ApiEndpoints.EVALUATE, TEST_EVALUATION_ID); + JShellResult result = this.webTestClient.mutate() .responseTimeout(Duration.ofSeconds(6)) .build() .post() - .uri(ApiEndpoints.EVALUATE_CODE_SNIPPET + "/" + TEST_EVALUATION_ID) + .uri(endpoint) .bodyValue(TEST_CODE_INPUT) .exchange() .expectStatus() From cccd5f1fa594b2b71b807e6a84a0492620fb1e5a Mon Sep 17 00:00:00 2001 From: Firas RG Date: Fri, 13 Sep 2024 21:27:27 +0100 Subject: [PATCH 4/6] [Testing][JShellAPI] adding a second subtest for same endpoint with additional checks --- .../jshellapi/JShellApiTests.java | 53 ++++++++++++++----- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java index e0809a4..a56d505 100644 --- a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java +++ b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java @@ -8,9 +8,13 @@ import org.springframework.test.web.reactive.server.WebTestClient; import org.togetherjava.jshellapi.dto.JShellResult; +import org.togetherjava.jshellapi.dto.JShellSnippetResult; +import org.togetherjava.jshellapi.dto.SnippetStatus; +import org.togetherjava.jshellapi.dto.SnippetType; import org.togetherjava.jshellapi.rest.ApiEndpoints; import java.time.Duration; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -20,46 +24,67 @@ * * @author Firas Regaieg */ -@ContextConfiguration +@ContextConfiguration(classes = Main.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class JShellApiTests { @Autowired private WebTestClient webTestClient; - private static final String TEST_EVALUATION_ID = "test"; - private static final String TEST_CODE_INPUT = "2+2"; - private static final String TEST_CODE_EXPECTED_OUTPUT = "4"; - @Test @DisplayName("When posting code snippet, evaluate it then returns successfully result") public void evaluateCodeSnippetTest() { + final String testEvalId = "test"; + + // -- performing a first code snippet execution + + final String firstCodeExpression = "int a = 2+2;"; + + final JShellSnippetResult firstCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, + SnippetType.ADDITION, 1, firstCodeExpression, "4"); + final JShellResult firstCodeExpectedResult = + getJShellResultDefaultInstance(firstCodeSnippet); + + assertThat(testEval(testEvalId, firstCodeExpression)).isEqualTo(firstCodeExpectedResult); + + // -- performing a second code snippet execution + + final String secondCodeExpression = "a * 2"; + + final JShellSnippetResult secondCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, + SnippetType.ADDITION, 2, secondCodeExpression, "8"); + + final JShellResult secondCodeExpectedResult = + getJShellResultDefaultInstance(secondCodeSnippet); + + assertThat(testEval(testEvalId, secondCodeExpression)).isEqualTo(secondCodeExpectedResult); + } + + private JShellResult testEval(String testEvalId, String codeInput) { final String endpoint = - String.join("/", ApiEndpoints.BASE, ApiEndpoints.EVALUATE, TEST_EVALUATION_ID); + String.join("/", ApiEndpoints.BASE, ApiEndpoints.EVALUATE, testEvalId); JShellResult result = this.webTestClient.mutate() .responseTimeout(Duration.ofSeconds(6)) .build() .post() .uri(endpoint) - .bodyValue(TEST_CODE_INPUT) + .bodyValue(codeInput) .exchange() .expectStatus() .isOk() .expectBody(JShellResult.class) - .value(task -> assertThat(task).isNotNull()) + .value((JShellResult evalResult) -> assertThat(evalResult).isNotNull()) .returnResult() .getResponseBody(); assertThat(result).isNotNull(); - boolean isValidResult = result.snippetsResults() - .stream() - .filter(res -> res.result() != null) - .anyMatch(res -> res.result().equals(TEST_CODE_EXPECTED_OUTPUT)); - - assertThat(isValidResult).isTrue(); + return result; + } + private static JShellResult getJShellResultDefaultInstance(JShellSnippetResult snippetResult) { + return new JShellResult(List.of(snippetResult), null, false, ""); } } From 3de55c67502851513fc53fb47f1cddf645090484 Mon Sep 17 00:00:00 2001 From: Firas RG Date: Fri, 13 Sep 2024 22:36:34 +0100 Subject: [PATCH 5/6] [Testing][JShellAPI] write documentation about testing approach --- JShellAPI/README.MD | 95 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/JShellAPI/README.MD b/JShellAPI/README.MD index 2dacaa8..f032214 100644 --- a/JShellAPI/README.MD +++ b/JShellAPI/README.MD @@ -101,4 +101,97 @@ The maximum ram allocated per container, in megabytes. ### jshellapi.dockerCPUsUsage The cpu configuration of each container, see [--cpus option of docker](https://docs.docker.com/config/containers/resource_constraints/#cpu). ### jshellapi.schedulerSessionKillScanRate -The rate at which the session killer will check and delete session, in seconds, see [Session timeout](#Session-timeout). \ No newline at end of file +The rate at which the session killer will check and delete session, in seconds, see [Session timeout](#Session-timeout). + +## Testing + +> The work on testing was made in collaboration with [Alathreon](https://github.com/Alathreon) and [Wazei](https://github.com/tj-wazei). I'd like thank both of them for their trust. - FirasRG + +This section outlines the work done to set up the first integration test that evaluates Java code by running it in a [Docker](https://www.docker.com/get-started/) container. The test ensures that the [Eval endpoint](#eval) can execute code within the containerized environment of [**JShellWrapper**](../JShellWrapper). + +### Usage + +```java +@ContextConfiguration(classes = Main.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class JShellApiTests { + + @Autowired + private WebTestClient webTestClient; + + @Test + @DisplayName("When posting code snippet, evaluate it then returns successfully result") + public void evaluateCodeSnippetTest() { + + final String testEvalId = "test"; + + final String firstCodeExpression = "int a = 2+2;"; + + final JShellSnippetResult firstCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, SnippetType.ADDITION, 1, firstCodeExpression, "4"); + + final JShellResult firstCodeExpectedResult = getJShellResultDefaultInstance(firstCodeSnippet); + + assertThat(testEval(testEvalId, firstCodeExpression)).isEqualTo(firstCodeExpectedResult); + + // performing a second code execution test ... + } + // some methods ... +} +``` + +### 1. Java Test Setup + +The [@SpringBootTest](https://docs.spring.io/spring-boot/api/java/org/springframework/boot/test/context/SpringBootTest.html) and [@ContextConfiguration](https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-contextconfiguration.html) annotations are needed to prepare the app to tests, like in a real scenario. + +NOTE: _Test classes must be located under `/src/test/java/{org.togetherjava.jshellapi}`._ + +- The test uses [WebTestClient](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/reactive/server/WebTestClient.html) to make HTTP calls to the target endpoint. +- Multiple API calls are made within the test method, so a utility instance method was created for reuse. +- The test ensures that code is correctly evaluated inside the **JShellWrapper** container. + +### 2. Gradle Configuration for Tests + +The `build.gradle` of this project has been updated to handle **JShellWrapper** Docker image lifecycle during tests. + +- **JShellWrapper Image Name**: the image name is injected from the root [build.gradle](../build.gradle) file, to this project's [build.gradle](build.gradle) file and also to [application.yaml](src/main/resources/application.yaml)! +- **JShellWrapper Docker Image**: The image is built before the tests run. +- **Container & Cleanup**: After the tests finish, the container and image are removed to ensure a clean environment. + +```groovy +def jshellWrapperImageName = rootProject.ext.jShellWrapperImageName; + +processResources { + filesMatching('application.yaml') { + expand(jShellWrapperImageName: jshellWrapperImageName) + } +} + +def taskBuildDockerImage = tasks.register('buildDockerImage') { + group = 'docker' + description = 'builds jshellwrapper as docker image' + dependsOn project(':JShellWrapper').tasks.named('jibDockerBuild') +} + +def taskRemoveDockerImage = tasks.register('removeDockerImage', Exec) { + group = 'docker' + description = 'removes jshellwrapper image' + commandLine 'docker', 'rmi', '-f', jshellWrapperImageName +} + +test { + dependsOn taskBuildDockerImage + finalizedBy taskRemoveDockerImage +} +``` + +Below are the key dependencies that were added or modified in the `build.gradle` file of this project : + +```groovy +testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'ch.qos.logback', module: 'logback-classic' +} +testImplementation 'org.springframework.boot:spring-boot-starter-webflux' +``` + +- The `logback-classic` has been excluded because of an issue encountered when running tests. The issue is typically about a conflict between some dependencies (This solution has been brought based on [a _good_ answer on Stackoverflow](https://stackoverflow.com/a/42641450/10000150)) +- The `spring-boot-starter-webflux` was needed in order to be able to use **WebTestClient**. From 8cc14853900edcf97350d420784ee54c38722cfc Mon Sep 17 00:00:00 2001 From: Firas RG Date: Fri, 20 Sep 2024 20:45:54 +0100 Subject: [PATCH 6/6] [Testing][JShellAPI] apply pr review fixes --- JShellAPI/README.MD | 93 ------------------- JShellAPI/build.gradle | 7 +- .../org/togetherjava/jshellapi/Config.java | 21 ++++- .../jshellapi/rest/ApiEndpoints.java | 5 +- .../jshellapi/service/DockerService.java | 28 ++---- ...itional-spring-configuration-metadata.json | 2 +- JShellAPI/src/main/resources/application.yaml | 4 +- .../jshellapi/JShellApiTests.java | 54 ++++------- .../test/resources/application-testing.yaml | 11 +++ 9 files changed, 70 insertions(+), 155 deletions(-) create mode 100644 JShellAPI/src/test/resources/application-testing.yaml diff --git a/JShellAPI/README.MD b/JShellAPI/README.MD index f032214..72938e2 100644 --- a/JShellAPI/README.MD +++ b/JShellAPI/README.MD @@ -102,96 +102,3 @@ The maximum ram allocated per container, in megabytes. The cpu configuration of each container, see [--cpus option of docker](https://docs.docker.com/config/containers/resource_constraints/#cpu). ### jshellapi.schedulerSessionKillScanRate The rate at which the session killer will check and delete session, in seconds, see [Session timeout](#Session-timeout). - -## Testing - -> The work on testing was made in collaboration with [Alathreon](https://github.com/Alathreon) and [Wazei](https://github.com/tj-wazei). I'd like thank both of them for their trust. - FirasRG - -This section outlines the work done to set up the first integration test that evaluates Java code by running it in a [Docker](https://www.docker.com/get-started/) container. The test ensures that the [Eval endpoint](#eval) can execute code within the containerized environment of [**JShellWrapper**](../JShellWrapper). - -### Usage - -```java -@ContextConfiguration(classes = Main.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class JShellApiTests { - - @Autowired - private WebTestClient webTestClient; - - @Test - @DisplayName("When posting code snippet, evaluate it then returns successfully result") - public void evaluateCodeSnippetTest() { - - final String testEvalId = "test"; - - final String firstCodeExpression = "int a = 2+2;"; - - final JShellSnippetResult firstCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, SnippetType.ADDITION, 1, firstCodeExpression, "4"); - - final JShellResult firstCodeExpectedResult = getJShellResultDefaultInstance(firstCodeSnippet); - - assertThat(testEval(testEvalId, firstCodeExpression)).isEqualTo(firstCodeExpectedResult); - - // performing a second code execution test ... - } - // some methods ... -} -``` - -### 1. Java Test Setup - -The [@SpringBootTest](https://docs.spring.io/spring-boot/api/java/org/springframework/boot/test/context/SpringBootTest.html) and [@ContextConfiguration](https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-contextconfiguration.html) annotations are needed to prepare the app to tests, like in a real scenario. - -NOTE: _Test classes must be located under `/src/test/java/{org.togetherjava.jshellapi}`._ - -- The test uses [WebTestClient](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/reactive/server/WebTestClient.html) to make HTTP calls to the target endpoint. -- Multiple API calls are made within the test method, so a utility instance method was created for reuse. -- The test ensures that code is correctly evaluated inside the **JShellWrapper** container. - -### 2. Gradle Configuration for Tests - -The `build.gradle` of this project has been updated to handle **JShellWrapper** Docker image lifecycle during tests. - -- **JShellWrapper Image Name**: the image name is injected from the root [build.gradle](../build.gradle) file, to this project's [build.gradle](build.gradle) file and also to [application.yaml](src/main/resources/application.yaml)! -- **JShellWrapper Docker Image**: The image is built before the tests run. -- **Container & Cleanup**: After the tests finish, the container and image are removed to ensure a clean environment. - -```groovy -def jshellWrapperImageName = rootProject.ext.jShellWrapperImageName; - -processResources { - filesMatching('application.yaml') { - expand(jShellWrapperImageName: jshellWrapperImageName) - } -} - -def taskBuildDockerImage = tasks.register('buildDockerImage') { - group = 'docker' - description = 'builds jshellwrapper as docker image' - dependsOn project(':JShellWrapper').tasks.named('jibDockerBuild') -} - -def taskRemoveDockerImage = tasks.register('removeDockerImage', Exec) { - group = 'docker' - description = 'removes jshellwrapper image' - commandLine 'docker', 'rmi', '-f', jshellWrapperImageName -} - -test { - dependsOn taskBuildDockerImage - finalizedBy taskRemoveDockerImage -} -``` - -Below are the key dependencies that were added or modified in the `build.gradle` file of this project : - -```groovy -testImplementation('org.springframework.boot:spring-boot-starter-test') { - exclude group: 'ch.qos.logback', module: 'logback-classic' -} -testImplementation 'org.springframework.boot:spring-boot-starter-webflux' -``` - -- The `logback-classic` has been excluded because of an issue encountered when running tests. The issue is typically about a conflict between some dependencies (This solution has been brought based on [a _good_ answer on Stackoverflow](https://stackoverflow.com/a/42641450/10000150)) -- The `spring-boot-starter-webflux` was needed in order to be able to use **WebTestClient**. diff --git a/JShellAPI/build.gradle b/JShellAPI/build.gradle index 6ebd62c..1aedc46 100644 --- a/JShellAPI/build.gradle +++ b/JShellAPI/build.gradle @@ -14,6 +14,9 @@ dependencies { implementation 'com.github.docker-java:docker-java-core:3.3.6' testImplementation('org.springframework.boot:spring-boot-starter-test') { + // `logback-classic` has been excluded because of an issue encountered when running tests. + // It's about a conflict between some dependencies. + // The solution has been brought based on a good answer on Stackoverflow: https://stackoverflow.com/a/42641450/10000150 exclude group: 'ch.qos.logback', module: 'logback-classic' } testImplementation 'org.springframework.boot:spring-boot-starter-webflux' @@ -43,11 +46,13 @@ shadowJar { archiveVersion.set('') } +// -- Gradle testing configuration + def jshellWrapperImageName = rootProject.ext.jShellWrapperImageName; processResources { filesMatching('application.yaml') { - expand(jShellWrapperImageName: jshellWrapperImageName) + expand("JSHELL_WRAPPER_IMAGE_NAME": jshellWrapperImageName) } } diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java index 4c337e9..5935fb5 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java @@ -2,13 +2,28 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; @ConfigurationProperties("jshellapi") public record Config(long regularSessionTimeoutSeconds, long oneTimeSessionTimeoutSeconds, long evalTimeoutSeconds, long evalTimeoutValidationLeeway, int sysOutCharLimit, long maxAliveSessions, int dockerMaxRamMegaBytes, double dockerCPUsUsage, @Nullable String dockerCPUSetCPUs, long schedulerSessionKillScanRateSeconds, - long dockerResponseTimeout, long dockerConnectionTimeout) { + long dockerResponseTimeout, long dockerConnectionTimeout, String jshellWrapperImageName) { + + public static final String JSHELL_WRAPPER_IMAGE_NAME_TAG = ":master"; + + private static boolean checkJShellWrapperImageName(String imageName) { + if (!StringUtils.hasText(imageName) + || !imageName.endsWith(Config.JSHELL_WRAPPER_IMAGE_NAME_TAG)) { + return false; + } + + final String imageNameFirstPart = imageName.split(Config.JSHELL_WRAPPER_IMAGE_NAME_TAG)[0]; + + return StringUtils.hasText(imageNameFirstPart); + } + public Config { if (regularSessionTimeoutSeconds <= 0) throw new IllegalArgumentException("Invalid value " + regularSessionTimeoutSeconds); @@ -35,5 +50,9 @@ public record Config(long regularSessionTimeoutSeconds, long oneTimeSessionTimeo throw new IllegalArgumentException("Invalid value " + dockerResponseTimeout); if (dockerConnectionTimeout <= 0) throw new IllegalArgumentException("Invalid value " + dockerConnectionTimeout); + + if (!checkJShellWrapperImageName(jshellWrapperImageName)) { + throw new IllegalArgumentException("Invalid value " + jshellWrapperImageName); + } } } diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java index 889014f..fa068d6 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/ApiEndpoints.java @@ -1,10 +1,7 @@ package org.togetherjava.jshellapi.rest; /** - * This class holds endpoints mentioned in controllers. The main objective is to keep endpoints - * synchronized with testing classes. - * - * @author Firas Regaieg + * Holds endpoints mentioned in controllers. */ public final class ApiEndpoints { private ApiEndpoints() {} diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java index fa1e171..4d98f22 100644 --- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java +++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java @@ -7,11 +7,9 @@ import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientImpl; import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; -import jakarta.el.PropertyNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.annotation.Value; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; @@ -31,8 +29,7 @@ public class DockerService implements DisposableBean { private final DockerClient client; - @Value("${jshell-wrapper.image-name}") - private String jshellWrapperImageName; + private final String jshellWrapperBaseImageName; public DockerService(Config config) { DefaultDockerClientConfig clientConfig = @@ -45,6 +42,9 @@ public DockerService(Config config) { .build(); this.client = DockerClientImpl.getInstance(clientConfig, httpClient); + this.jshellWrapperBaseImageName = + config.jshellWrapperImageName().split(Config.JSHELL_WRAPPER_IMAGE_NAME_TAG)[0]; + cleanupLeftovers(WORKER_UNIQUE_ID); } @@ -64,33 +64,23 @@ private void cleanupLeftovers(UUID currentId) { public String spawnContainer(long maxMemoryMegs, long cpus, @Nullable String cpuSetCpus, String name, Duration evalTimeout, long sysoutLimit) throws InterruptedException { - String imageName = Optional.ofNullable(this.jshellWrapperImageName) - .orElseThrow(() -> new PropertyNotFoundException( - "unable to find jshellWrapper image name property")); - - String[] imageNameParts = imageName.split(":master"); - - if (imageNameParts.length != 1) { - throw new IllegalArgumentException("invalid jshellWrapper image name"); - } - - String baseImageName = imageNameParts[0]; boolean presentLocally = client.listImagesCmd() - .withFilter("reference", List.of(baseImageName)) + .withFilter("reference", List.of(jshellWrapperBaseImageName)) .exec() .stream() .flatMap(it -> Arrays.stream(it.getRepoTags())) - .anyMatch(it -> it.endsWith(":master")); + .anyMatch(it -> it.endsWith(Config.JSHELL_WRAPPER_IMAGE_NAME_TAG)); if (!presentLocally) { - client.pullImageCmd(baseImageName) + client.pullImageCmd(jshellWrapperBaseImageName) .withTag("master") .exec(new PullImageResultCallback()) .awaitCompletion(5, TimeUnit.MINUTES); } - return client.createContainerCmd(baseImageName + ":master") + return client + .createContainerCmd(jshellWrapperBaseImageName + Config.JSHELL_WRAPPER_IMAGE_NAME_TAG) .withHostConfig(HostConfig.newHostConfig() .withAutoRemove(true) .withInit(true) diff --git a/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 6c9bcc1..4699496 100644 --- a/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/JShellAPI/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1,7 +1,7 @@ { "properties": [ { - "name": "jshell-wrapper.image-name", + "name": "jshellapi.jshellwrapper-imageName", "type": "java.lang.String", "description": "JShellWrapper image name injected from the top-level gradle build file." } diff --git a/JShellAPI/src/main/resources/application.yaml b/JShellAPI/src/main/resources/application.yaml index 83a6e23..5a31b22 100644 --- a/JShellAPI/src/main/resources/application.yaml +++ b/JShellAPI/src/main/resources/application.yaml @@ -20,8 +20,8 @@ jshellapi: dockerResponseTimeout: 60 dockerConnectionTimeout: 60 -jshell-wrapper: - image-name: ${jShellWrapperImageName} + # JShellWrapper related + jshellWrapperImageName: ${JSHELL_WRAPPER_IMAGE_NAME} server: error: diff --git a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java index a56d505..9b20eea 100644 --- a/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java +++ b/JShellAPI/src/test/java/org/togetherjava/jshellapi/JShellApiTests.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.reactive.server.WebTestClient; @@ -19,11 +20,9 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * This class holds integration tests for JShellAPI. It depends on gradle building image task, fore - * more information check "test" section in gradle.build file. - * - * @author Firas Regaieg + * Integrates tests for JShellAPI. */ +@ActiveProfiles("testing") @ContextConfiguration(classes = Main.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class JShellApiTests { @@ -31,42 +30,37 @@ public class JShellApiTests { @Autowired private WebTestClient webTestClient; + @Autowired + private Config testsConfig; + @Test - @DisplayName("When posting code snippet, evaluate it then returns successfully result") + @DisplayName("When posting code snippet, evaluate it then return successfully result") public void evaluateCodeSnippetTest() { final String testEvalId = "test"; - // -- performing a first code snippet execution - - final String firstCodeExpression = "int a = 2+2;"; - - final JShellSnippetResult firstCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, - SnippetType.ADDITION, 1, firstCodeExpression, "4"); - final JShellResult firstCodeExpectedResult = - getJShellResultDefaultInstance(firstCodeSnippet); - - assertThat(testEval(testEvalId, firstCodeExpression)).isEqualTo(firstCodeExpectedResult); - - // -- performing a second code snippet execution + // -- first code snippet eval + executeCodeEvalTest(testEvalId, "int a = 2+2;", 1, "4"); - final String secondCodeExpression = "a * 2"; - - final JShellSnippetResult secondCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, - SnippetType.ADDITION, 2, secondCodeExpression, "8"); + // -- second code snippet eval + executeCodeEvalTest(testEvalId, "a * 2", 2, "8"); + } - final JShellResult secondCodeExpectedResult = - getJShellResultDefaultInstance(secondCodeSnippet); + private void executeCodeEvalTest(String evalId, String codeSnippet, int expectedId, + String expectedResult) { + final JShellSnippetResult jshellCodeSnippet = new JShellSnippetResult(SnippetStatus.VALID, + SnippetType.ADDITION, expectedId, codeSnippet, expectedResult); - assertThat(testEval(testEvalId, secondCodeExpression)).isEqualTo(secondCodeExpectedResult); + assertThat(testEval(evalId, codeSnippet)) + .isEqualTo(new JShellResult(List.of(jshellCodeSnippet), null, false, "")); } private JShellResult testEval(String testEvalId, String codeInput) { final String endpoint = String.join("/", ApiEndpoints.BASE, ApiEndpoints.EVALUATE, testEvalId); - JShellResult result = this.webTestClient.mutate() - .responseTimeout(Duration.ofSeconds(6)) + return this.webTestClient.mutate() + .responseTimeout(Duration.ofSeconds(testsConfig.evalTimeoutSeconds())) .build() .post() .uri(endpoint) @@ -78,13 +72,5 @@ private JShellResult testEval(String testEvalId, String codeInput) { .value((JShellResult evalResult) -> assertThat(evalResult).isNotNull()) .returnResult() .getResponseBody(); - - assertThat(result).isNotNull(); - - return result; - } - - private static JShellResult getJShellResultDefaultInstance(JShellSnippetResult snippetResult) { - return new JShellResult(List.of(snippetResult), null, false, ""); } } diff --git a/JShellAPI/src/test/resources/application-testing.yaml b/JShellAPI/src/test/resources/application-testing.yaml new file mode 100644 index 0000000..f1eb225 --- /dev/null +++ b/JShellAPI/src/test/resources/application-testing.yaml @@ -0,0 +1,11 @@ +jshellapi: + + # Public API Config + regularSessionTimeoutSeconds: 10 + + # Internal config + schedulerSessionKillScanRateSeconds: 6 + + # Docker service config + dockerResponseTimeout: 6 + dockerConnectionTimeout: 6