diff --git a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/Sandbox.kt b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/Sandbox.kt index ada56ea3..583a3249 100644 --- a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/Sandbox.kt +++ b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/Sandbox.kt @@ -33,7 +33,6 @@ import com.alibaba.opensandbox.sandbox.domain.services.Health import com.alibaba.opensandbox.sandbox.domain.services.Metrics import com.alibaba.opensandbox.sandbox.domain.services.Sandboxes import com.alibaba.opensandbox.sandbox.infrastructure.factory.AdapterFactory -import okhttp3.ConnectionPool import org.slf4j.LoggerFactory import java.time.Duration import java.time.OffsetDateTime @@ -145,12 +144,16 @@ class Sandbox internal constructor( * Creates a sandbox instance with the provided configuration. * * @param imageSpec Container image specification + * @param entrypoint Sandbox entrypoint command * @param env Environment variables (optional) * @param metadata Metadata for the sandbox (optional) - * @param timeout Sandbox timeout in seconds + * @param timeout Sandbox timeout (automatic termination time) + * @param readyTimeout Timeout for waiting for sandbox readiness * @param resource Resource limits (optional) * @param connectionConfig Connection configuration * @param healthCheck Custom health check function (optional) + * @param healthCheckPollingInterval Polling interval for readiness/health check + * @param extensions Optional extension parameters for server-side customized behaviors * @return Fully configured and ready Sandbox instance * @throws SandboxException if sandbox creation or initialization fails */ @@ -165,7 +168,7 @@ class Sandbox internal constructor( connectionConfig: ConnectionConfig, healthCheck: ((Sandbox) -> Boolean)? = null, healthCheckPollingInterval: Duration, - connectionPool: ConnectionPool? = null, + extensions: Map, ): Sandbox { logger.info("Start creating sandbox with image: {} (timeout: {}s)", imageSpec.image, timeout.seconds) @@ -184,6 +187,7 @@ class Sandbox internal constructor( metadata, timeout, resource, + extensions, ) sandboxId = response.id @@ -631,6 +635,14 @@ class Sandbox internal constructor( */ private val metadata = mutableMapOf() + /** + * Optional extension parameters for server-side custom behaviors. + * + * This map is treated as opaque and is sent to the server as-is. + * Prefer namespaced keys (e.g. `storage.id`) to avoid collisions. + */ + private val extensions = mutableMapOf() + /** * Lifecycle config */ @@ -804,6 +816,47 @@ class Sandbox internal constructor( return this } + /** + * Adds a single extension parameter. + * + * Extensions are opaque client-side and are passed through to the server. + * Prefer stable, namespaced keys (e.g. `storage.id`). + * + * @throws InvalidArgumentException if [key] is blank + */ + fun extension( + key: String, + value: String, + ): Builder { + if (key.isBlank()) { + throw InvalidArgumentException( + message = "Extension key cannot be blank", + ) + } + extensions[key] = value + return this + } + + /** + * Adds multiple extension parameters. + * + * Extensions are opaque client-side and are passed through to the server. + */ + fun extensions(extensions: Map): Builder { + this.extensions.putAll(extensions) + return this + } + + /** + * Configures extension parameters using a fluent configuration block. + * + * Extensions are opaque client-side and are passed through to the server. + */ + fun extensions(configure: MutableMap.() -> Unit): Builder { + extensions.configure() + return this + } + /** * Sets the sandbox timeout (automatic termination time). * @@ -897,6 +950,7 @@ class Sandbox internal constructor( timeout = timeout, readyTimeout = readyTimeout, resource = resource, + extensions = extensions, connectionConfig = connectionConfig ?: ConnectionConfig.builder().build(), healthCheckPollingInterval = healthCheckPollingInterval, healthCheck = healthCheck, diff --git a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/domain/services/Sandboxes.kt b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/domain/services/Sandboxes.kt index 1b113388..19294f31 100644 --- a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/domain/services/Sandboxes.kt +++ b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/domain/services/Sandboxes.kt @@ -36,8 +36,14 @@ interface Sandboxes { /** * Creates a new sandbox with the specified configuration. * - * @param spec Image specification for the sandbox - * @return Sandbox create response + * @param spec Container image specification for provisioning the sandbox + * @param entrypoint The command to run as the sandbox's main process (e.g. `["python", "/app/main.py"]`) + * @param env Environment variables injected into the sandbox runtime + * @param metadata User-defined metadata used for management and filtering + * @param timeout Sandbox lifetime. The server may terminate the sandbox when it expires + * @param resource Runtime resource limits (e.g. cpu/memory). Exact semantics are server-defined + * @param extensions Opaque extension parameters passed through to the server as-is. Prefer namespaced keys + * @return Sandbox creation response containing the sandbox id */ fun createSandbox( spec: SandboxImageSpec, @@ -46,6 +52,7 @@ interface Sandboxes { metadata: Map, timeout: Duration, resource: Map, + extensions: Map, ): SandboxCreateResponse /** diff --git a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/converter/SandboxModelConverter.kt b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/converter/SandboxModelConverter.kt index 950f49fe..a0d18a29 100644 --- a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/converter/SandboxModelConverter.kt +++ b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/converter/SandboxModelConverter.kt @@ -73,6 +73,7 @@ internal object SandboxModelConverter { metadata: Map, timeout: Duration, resource: Map, + extensions: Map, ): CreateSandboxRequest { return CreateSandboxRequest( image = spec.toApiImageSpec(), @@ -81,6 +82,7 @@ internal object SandboxModelConverter { metadata = metadata, timeout = timeout.seconds.toInt(), resourceLimits = resource, + extensions = extensions, ) } diff --git a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapter.kt b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapter.kt index 6e61ec18..209a123e 100644 --- a/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapter.kt +++ b/sdks/sandbox/kotlin/sandbox/src/main/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapter.kt @@ -57,11 +57,21 @@ internal class SandboxesAdapter( metadata: Map, timeout: Duration, resource: Map, + extensions: Map, ): SandboxCreateResponse { logger.info("Creating sandbox with image: {}", spec.image) return try { - val createRequest = SandboxModelConverter.toApiCreateSandboxRequest(spec, entrypoint, env, metadata, timeout, resource) + val createRequest = + SandboxModelConverter.toApiCreateSandboxRequest( + spec = spec, + entrypoint = entrypoint, + env = env, + metadata = metadata, + timeout = timeout, + resource = resource, + extensions = extensions, + ) val apiResponse = api.sandboxesPost(createRequest) val response = apiResponse.toSandboxCreateResponse() diff --git a/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapterTest.kt b/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapterTest.kt index bd05434a..3070b654 100644 --- a/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapterTest.kt +++ b/sdks/sandbox/kotlin/sandbox/src/test/kotlin/com/alibaba/opensandbox/sandbox/infrastructure/adapters/service/SandboxesAdapterTest.kt @@ -26,8 +26,12 @@ import okhttp3.mockwebserver.MockWebServer import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import java.time.Duration import java.util.UUID @@ -76,6 +80,7 @@ class SandboxesAdapterTest { // Execute val spec = SandboxImageSpec.builder().image("ubuntu:latest").build() + val extensions = mapOf("storage.id" to "abc123", "debug" to "true") val result = sandboxesAdapter.createSandbox( spec = spec, @@ -84,12 +89,21 @@ class SandboxesAdapterTest { metadata = mapOf("meta" to "data"), timeout = Duration.ofSeconds(600), resource = mapOf("cpu" to "1"), + extensions = extensions, ) // Verify request val request = mockWebServer.takeRequest() assertEquals("POST", request.method) assertEquals("/sandboxes", request.path) + val requestBody = request.body.readUtf8() + assertTrue(requestBody.isNotBlank(), "request body should not be blank") + + val payload = Json.parseToJsonElement(requestBody).jsonObject + val gotExtensions = payload["extensions"]?.jsonObject + assertNotNull(gotExtensions, "extensions should be present in createSandbox request") + assertEquals("abc123", gotExtensions!!["storage.id"]!!.jsonPrimitive.content) + assertEquals("true", gotExtensions["debug"]!!.jsonPrimitive.content) // Verify response assertEquals(UUID.fromString("550e8400-e29b-41d4-a716-446655440000"), result.id) diff --git a/tests/java/build.gradle.kts b/tests/java/build.gradle.kts index d7d71030..e1e4ec4c 100644 --- a/tests/java/build.gradle.kts +++ b/tests/java/build.gradle.kts @@ -23,8 +23,9 @@ group = "com.alibaba.opensandbox" version = "1.0.0" java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } } repositories { diff --git a/tests/java/settings.gradle.kts b/tests/java/settings.gradle.kts index 22f3a783..105f66c7 100644 --- a/tests/java/settings.gradle.kts +++ b/tests/java/settings.gradle.kts @@ -15,3 +15,7 @@ */ rootProject.name = "opensandbox-java-e2e-tests" + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0") +} diff --git a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/BaseE2ETest.java b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/BaseE2ETest.java index 56f1ed92..93c67565 100644 --- a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/BaseE2ETest.java +++ b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/BaseE2ETest.java @@ -109,14 +109,14 @@ protected static void assertEndpointHasPort(String endpoint, int expectedPort) { endpoint.endsWith("/" + expectedPort), "endpoint route must end with /" + expectedPort + ": " + endpoint); String prefix = endpoint.split("/", 2)[0]; - assertFalse(prefix.isBlank(), "missing domain in endpoint: " + endpoint); + assertFalse(prefix.trim().isEmpty(), "missing domain in endpoint: " + endpoint); return; } int idx = endpoint.lastIndexOf(':'); assertTrue(idx > 0, "missing host:port in endpoint: " + endpoint); String host = endpoint.substring(0, idx); String port = endpoint.substring(idx + 1); - assertFalse(host.isBlank(), "missing host in endpoint: " + endpoint); + assertFalse(host.trim().isEmpty(), "missing host in endpoint: " + endpoint); assertTrue(port.matches("\\d+"), "non-numeric port in endpoint: " + endpoint); assertEquals(expectedPort, Integer.parseInt(port), "endpoint port mismatch: " + endpoint); } diff --git a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/CodeInterpreterE2ETest.java b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/CodeInterpreterE2ETest.java index b0d2c49b..3314ce2d 100644 --- a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/CodeInterpreterE2ETest.java +++ b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/CodeInterpreterE2ETest.java @@ -30,9 +30,7 @@ import com.alibaba.opensandbox.sandbox.domain.models.sandboxes.SandboxMetrics; import java.time.Duration; import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import org.junit.jupiter.api.*; import org.slf4j.Logger; @@ -58,6 +56,13 @@ public class CodeInterpreterE2ETest extends BaseE2ETest { private Sandbox sandbox; private CodeInterpreter codeInterpreter; + private static Map createResourceMap() { + Map map = new HashMap<>(); + map.put("cpu", "2"); + map.put("memory", "4Gi"); + return map; + } + private static void assertTerminalEventContract( List initEvents, List completedEvents, @@ -65,7 +70,7 @@ private static void assertTerminalEventContract( String executionId) { assertEquals(1, initEvents.size(), "init event must exist exactly once"); assertNotNull(initEvents.get(0).getId()); - assertFalse(initEvents.get(0).getId().isBlank()); + assertFalse(initEvents.get(0).getId().trim().isEmpty()); assertEquals(executionId, initEvents.get(0).getId()); assertRecentTimestampMs(initEvents.get(0).getTimestamp(), 180_000); assertTrue( @@ -78,7 +83,7 @@ private static void assertTerminalEventContract( } if (!errors.isEmpty()) { assertNotNull(errors.get(0).getName()); - assertFalse(errors.get(0).getName().isBlank()); + assertFalse(errors.get(0).getName().trim().isEmpty()); assertNotNull(errors.get(0).getValue()); assertRecentTimestampMs(errors.get(0).getTimestamp(), 180_000); } @@ -89,12 +94,13 @@ void setup() { sandbox = Sandbox.builder() .connectionConfig(sharedConnectionConfig) - .entrypoint(List.of("/opt/opensandbox/code-interpreter.sh")) + .entrypoint( + Collections.singletonList("/opt/opensandbox/code-interpreter.sh")) .image(getSandboxImage()) - .resource(java.util.Map.of("cpu", "2", "memory", "4Gi")) + .resource(createResourceMap()) .timeout(Duration.ofMinutes(15)) .readyTimeout(Duration.ofSeconds(60)) - .metadata(java.util.Map.of("tag", "e2e-code-interpreter")) + .metadata(Collections.singletonMap("tag", "e2e-code-interpreter")) .env("E2E_TEST", "true") .env("GO_VERSION", "1.25") .env("JAVA_VERSION", "21") @@ -249,7 +255,7 @@ void testJavaCodeExecution() { assertNotNull(simpleResult); assertNotNull(simpleResult.getId()); - assertFalse(simpleResult.getId().isBlank()); + assertFalse(simpleResult.getId().trim().isEmpty()); assertEquals("4", simpleResult.getResult().get(0).getText()); assertTerminalEventContract(initEvents, completedEvents, errors, simpleResult.getId()); assertTrue(errors.isEmpty()); diff --git a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxE2ETest.java b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxE2ETest.java index d0b9b9c7..63c63909 100644 --- a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxE2ETest.java +++ b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxE2ETest.java @@ -102,7 +102,7 @@ private static void assertTerminalEventContract( String executionId) { assertEquals(1, initEvents.size(), "Execution must have exactly one init event"); assertNotNull(initEvents.get(0).getId()); - assertFalse(initEvents.get(0).getId().isBlank()); + assertFalse(initEvents.get(0).getId().trim().isEmpty()); assertEquals(executionId, initEvents.get(0).getId(), "init.id must match execution.id"); assertRecentTimestampMs(initEvents.get(0).getTimestamp(), 120_000); @@ -121,7 +121,7 @@ private static void assertTerminalEventContract( } if (hasError) { assertNotNull(errors.get(0).getName()); - assertFalse(errors.get(0).getName().isBlank()); + assertFalse(errors.get(0).getName().trim().isEmpty()); assertNotNull(errors.get(0).getValue()); assertRecentTimestampMs(errors.get(0).getTimestamp(), 180_000); } @@ -142,7 +142,7 @@ void testSandboxLifecycleAndHealth() { assertNotNull(info.getCreatedAt()); assertNotNull(info.getExpiresAt()); assertTrue(info.getExpiresAt().isAfter(info.getCreatedAt())); - assertEquals(List.of("tail", "-f", "/dev/null"), info.getEntrypoint()); + assertEquals(Arrays.asList("tail", "-f", "/dev/null"), info.getEntrypoint()); Duration duration = Duration.between(info.getCreatedAt(), info.getExpiresAt()); assertTrue(duration.compareTo(Duration.ofMinutes(1)) >= 0); @@ -261,7 +261,7 @@ void testBasicCommandExecution() { assertNotNull(echoResult); assertNotNull(echoResult.getId()); - assertFalse(echoResult.getId().isBlank()); + assertFalse(echoResult.getId().trim().isEmpty()); assertNull(echoResult.getError()); assertEquals(1, echoResult.getLogs().getStdout().size()); assertEquals("Hello OpenSandbox E2E", echoResult.getLogs().getStdout().get(0).getText()); @@ -316,7 +316,7 @@ void testBasicCommandExecution() { Execution failResult = sandbox.commands().run(failRequest); assertNotNull(failResult); assertNotNull(failResult.getId()); - assertFalse(failResult.getId().isBlank()); + assertFalse(failResult.getId().trim().isEmpty()); assertNotNull(failResult.getError()); assertEquals("CommandExecError", failResult.getError().getName()); assertTrue(failResult.getLogs().getStderr().size() > 0); @@ -350,9 +350,10 @@ void testBasicFilesystemOperations() { WriteEntry dirEntry1 = WriteEntry.builder().path(testDir1).mode(755).build(); WriteEntry dirEntry2 = WriteEntry.builder().path(testDir2).mode(644).build(); - sandbox.files().createDirectories(List.of(dirEntry1, dirEntry2)); + sandbox.files().createDirectories(Arrays.asList(dirEntry1, dirEntry2)); - Map dirInfo = sandbox.files().readFileInfo(List.of(testDir1, testDir2)); + Map dirInfo = + sandbox.files().readFileInfo(Arrays.asList(testDir1, testDir2)); assertEquals(testDir1, dirInfo.get(testDir1).getPath()); assertEquals(755, dirInfo.get(testDir1).getMode()); assertTimesClose( @@ -392,7 +393,7 @@ void testBasicFilesystemOperations() { .mode(755) .build(); - sandbox.files().write(List.of(writeEntry1, writeEntry2, writeEntry3)); + sandbox.files().write(Arrays.asList(writeEntry1, writeEntry2, writeEntry3)); String readContent1 = sandbox.files().readFile(testFile1, StandardCharsets.UTF_8.name(), null); @@ -403,7 +404,13 @@ void testBasicFilesystemOperations() { String readContent2 = new String(readBytes2, StandardCharsets.UTF_8); try (java.io.InputStream inputStream = sandbox.files().readStream(testFile3, null)) { - byte[] streamBytes = inputStream.readAllBytes(); + java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + byte[] streamBytes = buffer.toByteArray(); String readContent3 = new String(streamBytes, StandardCharsets.UTF_8); // Verify content matches original for all files @@ -420,7 +427,7 @@ void testBasicFilesystemOperations() { throw new RuntimeException("Failed to read stream", e); } - List allTestFiles = List.of(testFile1, testFile2, testFile3); + List allTestFiles = Arrays.asList(testFile1, testFile2, testFile3); Map fileInfoMap = sandbox.files().readFileInfo(allTestFiles); long expectedSize = testContent.getBytes(StandardCharsets.UTF_8).length; @@ -456,7 +463,7 @@ void testBasicFilesystemOperations() { for (EntryInfo e : sandbox.files().search(searchAllEntry)) { found.add(e.getPath()); } - assertEquals(Set.of(testFile1, testFile2, testFile3), found); + assertEquals(new HashSet<>(Arrays.asList(testFile1, testFile2, testFile3)), found); SetPermissionEntry permEntry1 = SetPermissionEntry.builder() @@ -472,11 +479,11 @@ void testBasicFilesystemOperations() { .owner("nobody") .group("nogroup") .build(); - sandbox.files().setPermissions(List.of(permEntry1, permEntry2)); + sandbox.files().setPermissions(Arrays.asList(permEntry1, permEntry2)); // Verify permission changes for both files in single call Map updatedInfoMap = - sandbox.files().readFileInfo(List.of(testFile1, testFile2)); + sandbox.files().readFileInfo(Arrays.asList(testFile1, testFile2)); EntryInfo updatedInfo1 = updatedInfoMap.get(testFile1); EntryInfo updatedInfo2 = updatedInfoMap.get(testFile2); @@ -494,7 +501,8 @@ void testBasicFilesystemOperations() { assertEquals( "nogroup", updatedInfo2.getGroup(), "testFile2 group should be updated to nogroup"); - EntryInfo beforeUpdate = sandbox.files().readFileInfo(List.of(testFile1)).get(testFile1); + EntryInfo beforeUpdate = + sandbox.files().readFileInfo(Collections.singletonList(testFile1)).get(testFile1); String updatedContent1 = testContent + "\nAppended line to file1"; String updatedContent2 = testContent + "\nAppended line to file2"; try { @@ -505,14 +513,15 @@ void testBasicFilesystemOperations() { WriteEntry.builder().path(testFile1).data(updatedContent1).mode(644).build(); WriteEntry updateEntry2 = WriteEntry.builder().path(testFile2).data(updatedContent2).mode(755).build(); - sandbox.files().write(List.of(updateEntry1, updateEntry2)); + sandbox.files().write(Arrays.asList(updateEntry1, updateEntry2)); String newContent1 = sandbox.files().readFile(testFile1, "UTF-8", null); String newContent2 = sandbox.files().readFile(testFile2, "UTF-8", null); assertEquals(updatedContent1, newContent1); assertEquals(updatedContent2, newContent2); - EntryInfo afterUpdate = sandbox.files().readFileInfo(List.of(testFile1)).get(testFile1); + EntryInfo afterUpdate = + sandbox.files().readFileInfo(Collections.singletonList(testFile1)).get(testFile1); assertEquals( updatedContent1.getBytes(StandardCharsets.UTF_8).length, afterUpdate.getSize()); assertModifiedUpdated(beforeUpdate.getModifiedAt(), afterUpdate.getModifiedAt(), 1, 1000); @@ -525,7 +534,7 @@ void testBasicFilesystemOperations() { } sandbox.files() .replaceContents( - List.of( + Collections.singletonList( ContentReplaceEntry.builder() .path(testFile1) .oldContent("Appended line to file1") @@ -534,30 +543,33 @@ void testBasicFilesystemOperations() { String replaced = sandbox.files().readFile(testFile1, "UTF-8", null); assertTrue(replaced.contains("Replaced line in file1")); assertFalse(replaced.contains("Appended line to file1")); - EntryInfo afterReplace = sandbox.files().readFileInfo(List.of(testFile1)).get(testFile1); + EntryInfo afterReplace = + sandbox.files().readFileInfo(Collections.singletonList(testFile1)).get(testFile1); assertModifiedUpdated(beforeReplace.getModifiedAt(), afterReplace.getModifiedAt(), 1, 1000); // Move file3 String movedPath = testDir2 + "/moved_file3.txt"; sandbox.files() - .moveFiles(List.of(MoveEntry.builder().src(testFile3).dest(movedPath).build())); + .moveFiles( + Collections.singletonList( + MoveEntry.builder().src(testFile3).dest(movedPath).build())); String moved = new String(sandbox.files().readByteArray(movedPath, null), StandardCharsets.UTF_8); assertEquals(testContent, moved); assertThrows(Exception.class, () -> sandbox.files().readByteArray(testFile3, null)); // Delete file2 - sandbox.files().deleteFiles(List.of(testFile2)); + sandbox.files().deleteFiles(Collections.singletonList(testFile2)); assertThrows(Exception.class, () -> sandbox.files().readFile(testFile2, "UTF-8", null)); Set after = new HashSet<>(); for (EntryInfo e : sandbox.files().search(SearchEntry.builder().path(testDir1).pattern("*").build())) { after.add(e.getPath()); } - assertEquals(Set.of(testFile1), after); + assertEquals(Collections.singleton(testFile1), after); // Delete directories - sandbox.files().deleteDirectories(List.of(testDir1, testDir2)); + sandbox.files().deleteDirectories(Arrays.asList(testDir1, testDir2)); Execution verify = sandbox.commands() .run( diff --git a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxManagerE2ETest.java b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxManagerE2ETest.java index 23e9b49a..846926a3 100644 --- a/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxManagerE2ETest.java +++ b/tests/java/src/test/java/com/alibaba/opensandbox/e2e/SandboxManagerE2ETest.java @@ -51,6 +51,28 @@ public class SandboxManagerE2ETest extends BaseE2ETest { private Sandbox s3; private String tag; + private static Map createMetadata(String tag, String team, String env) { + Map map = new HashMap<>(); + map.put("tag", tag); + map.put("team", team); + map.put("env", env); + return map; + } + + private static Map createMetadataSimple(String tag, String env) { + Map map = new HashMap<>(); + map.put("tag", tag); + map.put("env", env); + return map; + } + + private static Map createMetadataTwoKeys(String tag, String key, String value) { + Map map = new HashMap<>(); + map.put("tag", tag); + map.put(key, value); + return map; + } + @BeforeAll void setup() throws InterruptedException { sandboxManager = SandboxManager.builder().connectionConfig(sharedConnectionConfig).build(); @@ -66,7 +88,7 @@ void setup() throws InterruptedException { .resource(resourceMap) .timeout(Duration.ofMinutes(5)) .readyTimeout(Duration.ofSeconds(60)) - .metadata(Map.of("tag", tag, "team", "t1", "env", "prod")) + .metadata(createMetadata(tag, "t1", "prod")) .env("E2E_TEST", "true") .healthCheckPollingInterval(Duration.ofMillis(500)) .build(); @@ -77,7 +99,7 @@ void setup() throws InterruptedException { .resource(resourceMap) .timeout(Duration.ofMinutes(5)) .readyTimeout(Duration.ofSeconds(60)) - .metadata(Map.of("tag", tag, "team", "t1", "env", "dev")) + .metadata(createMetadata(tag, "t1", "dev")) .env("E2E_TEST", "true") .healthCheckPollingInterval(Duration.ofMillis(500)) .build(); @@ -88,7 +110,7 @@ void setup() throws InterruptedException { .resource(resourceMap) .timeout(Duration.ofMinutes(5)) .readyTimeout(Duration.ofSeconds(60)) - .metadata(Map.of("tag", tag, "env", "prod")) + .metadata(createMetadataSimple(tag, "prod")) .env("E2E_TEST", "true") .healthCheckPollingInterval(Duration.ofMillis(500)) .build(); @@ -112,7 +134,7 @@ void setup() throws InterruptedException { @AfterAll void teardown() { - for (Sandbox s : List.of(s1, s2, s3)) { + for (Sandbox s : Arrays.asList(s1, s2, s3)) { if (s == null) continue; try { s.kill(); @@ -139,7 +161,7 @@ void testStatesFilterOrLogic() { SandboxFilter filter = SandboxFilter.builder() .states("Running", "Paused") - .metadata(Map.of("tag", tag)) + .metadata(Collections.singletonMap("tag", tag)) .pageSize(50) .build(); PagedSandboxInfos infos = sandboxManager.listSandboxInfos(filter); @@ -147,13 +169,14 @@ void testStatesFilterOrLogic() { for (SandboxInfo info : infos.getSandboxInfos()) { ids.add(info.getId()); } - assertTrue(ids.containsAll(Set.of(s1.getId(), s2.getId(), s3.getId()))); + assertTrue( + ids.containsAll(new HashSet<>(Arrays.asList(s1.getId(), s2.getId(), s3.getId())))); PagedSandboxInfos pausedOnly = sandboxManager.listSandboxInfos( SandboxFilter.builder() .states("Paused") - .metadata(Map.of("tag", tag)) + .metadata(Collections.singletonMap("tag", tag)) .pageSize(50) .build()); Set pausedIds = new HashSet<>(); @@ -168,7 +191,7 @@ void testStatesFilterOrLogic() { sandboxManager.listSandboxInfos( SandboxFilter.builder() .states("Running") - .metadata(Map.of("tag", tag)) + .metadata(Collections.singletonMap("tag", tag)) .pageSize(50) .build()); Set runningIds = new HashSet<>(); @@ -188,7 +211,7 @@ void testMetadataFilterAndLogic() { PagedSandboxInfos tagAndTeam = sandboxManager.listSandboxInfos( SandboxFilter.builder() - .metadata(Map.of("tag", tag, "team", "t1")) + .metadata(createMetadataTwoKeys(tag, "team", "t1")) .pageSize(50) .build()); Set ids = new HashSet<>(); @@ -202,7 +225,7 @@ void testMetadataFilterAndLogic() { PagedSandboxInfos tagTeamEnv = sandboxManager.listSandboxInfos( SandboxFilter.builder() - .metadata(Map.of("tag", tag, "team", "t1", "env", "prod")) + .metadata(createMetadata(tag, "t1", "prod")) .pageSize(50) .build()); Set ids2 = new HashSet<>(); @@ -216,7 +239,7 @@ void testMetadataFilterAndLogic() { PagedSandboxInfos tagEnv = sandboxManager.listSandboxInfos( SandboxFilter.builder() - .metadata(Map.of("tag", tag, "env", "prod")) + .metadata(createMetadataSimple(tag, "prod")) .pageSize(50) .build()); Set ids3 = new HashSet<>(); @@ -230,11 +253,13 @@ void testMetadataFilterAndLogic() { PagedSandboxInfos noneMatch = sandboxManager.listSandboxInfos( SandboxFilter.builder() - .metadata(Map.of("tag", tag, "team", "t2")) + .metadata(createMetadataTwoKeys(tag, "team", "t2")) .pageSize(50) .build()); for (SandboxInfo info : noneMatch.getSandboxInfos()) { - assertFalse(Set.of(s1.getId(), s2.getId(), s3.getId()).contains(info.getId())); + assertFalse( + new HashSet<>(Arrays.asList(s1.getId(), s2.getId(), s3.getId())) + .contains(info.getId())); } }