implements PR-O4.7
This commit is contained in:
parent
4c52f5de8d
commit
a65018f72c
@ -31,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class BackendGateIIntegrationTest {
|
||||
private final RuntimeCompatibilityAdapter compatibilityAdapter = new LocalRuntimeCompatibilityAdapter();
|
||||
private final RuntimeCompatibilityAdapter compatibilityAdapter = RuntimeCompatibilityAdapters.createDefault();
|
||||
|
||||
@Test
|
||||
void gateI_syscSectionPresentAndEmpty() {
|
||||
@ -52,8 +52,9 @@ class BackendGateIIntegrationTest {
|
||||
final var check = compatibilityAdapter.check(module);
|
||||
assertEquals(CompatibilityError.NONE, check.error());
|
||||
assertTrue(check.hostcallCount() > 0);
|
||||
assertEquals("local", check.adapterMode());
|
||||
assertTrue(Set.of("runtime-backed", "local-fallback", "local").contains(check.adapterMode()));
|
||||
assertTrue(!check.runtimeLine().isBlank());
|
||||
assertTrue(!check.reason().isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -6,4 +6,5 @@ enum CompatibilityError {
|
||||
HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
||||
UNUSED_SYSC_DECL,
|
||||
MISSING_CAPABILITY,
|
||||
RUNTIME_REJECTED,
|
||||
}
|
||||
|
||||
@ -4,5 +4,10 @@ record CompatibilityResult(
|
||||
CompatibilityError error,
|
||||
int hostcallCount,
|
||||
String runtimeLine,
|
||||
String adapterMode) {
|
||||
String adapterMode,
|
||||
String reason) {
|
||||
|
||||
CompatibilityResult {
|
||||
reason = reason == null ? "" : reason;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
||||
CompatibilityError.RAW_SYSCALL_IN_PRELOAD,
|
||||
hostcallCount,
|
||||
runtimeLine,
|
||||
"local");
|
||||
"local",
|
||||
"raw syscall opcode is forbidden");
|
||||
}
|
||||
case 0x71 -> {
|
||||
final var syscIndex = readU32(module.code(), pc + 2);
|
||||
@ -57,7 +58,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
||||
CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
||||
hostcallCount,
|
||||
runtimeLine,
|
||||
"local");
|
||||
"local",
|
||||
"hostcall index out of bounds");
|
||||
}
|
||||
used[syscIndex] = true;
|
||||
hostcallCount++;
|
||||
@ -69,7 +71,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
||||
CompatibilityError.MISSING_CAPABILITY,
|
||||
hostcallCount,
|
||||
runtimeLine,
|
||||
"local");
|
||||
"local",
|
||||
"required capability is missing");
|
||||
}
|
||||
pc += 6;
|
||||
}
|
||||
@ -85,10 +88,11 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
||||
CompatibilityError.UNUSED_SYSC_DECL,
|
||||
hostcallCount,
|
||||
runtimeLine,
|
||||
"local");
|
||||
"local",
|
||||
"unused syscall declaration");
|
||||
}
|
||||
}
|
||||
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local");
|
||||
return new CompatibilityResult(CompatibilityError.NONE, hostcallCount, runtimeLine, "local", "ok");
|
||||
}
|
||||
|
||||
private int readU16(final byte[] code, final int offset) {
|
||||
|
||||
@ -0,0 +1,137 @@
|
||||
package p.studio.compiler.integration;
|
||||
|
||||
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
final class RuntimeBackedCompatibilityAdapter implements RuntimeCompatibilityAdapter {
|
||||
private static final String DEFAULT_RUNTIME_LINE = "runtime-line-unknown";
|
||||
private static final String FALLBACK_MODE = "local-fallback";
|
||||
|
||||
private final RuntimeCompatibilityAdapter fallback;
|
||||
private final Path runtimeRoot;
|
||||
private final String runtimeLine;
|
||||
private final String runtimeCheckCommand;
|
||||
|
||||
RuntimeBackedCompatibilityAdapter(final RuntimeCompatibilityAdapter fallback) {
|
||||
this(
|
||||
fallback == null ? new LocalRuntimeCompatibilityAdapter() : fallback,
|
||||
Path.of("../runtime"),
|
||||
System.getenv("PROMETEU_RUNTIME_CHECK_CMD"));
|
||||
}
|
||||
|
||||
RuntimeBackedCompatibilityAdapter(
|
||||
final RuntimeCompatibilityAdapter fallback,
|
||||
final Path runtimeRoot,
|
||||
final String runtimeCheckCommand) {
|
||||
this.fallback = fallback == null ? new LocalRuntimeCompatibilityAdapter() : fallback;
|
||||
this.runtimeRoot = runtimeRoot == null ? Path.of("../runtime") : runtimeRoot;
|
||||
this.runtimeLine = readRuntimeLine(this.runtimeRoot);
|
||||
this.runtimeCheckCommand = runtimeCheckCommand == null ? "" : runtimeCheckCommand.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompatibilityResult check(final BytecodeModule module) {
|
||||
final var local = fallback.check(module);
|
||||
if (runtimeCheckCommand.isBlank()) {
|
||||
return fallbackWithReason(local, "runtime command not configured");
|
||||
}
|
||||
if (!Files.isDirectory(runtimeRoot)) {
|
||||
return fallbackWithReason(local, "runtime root is unavailable: " + runtimeRoot);
|
||||
}
|
||||
|
||||
Path artifact = null;
|
||||
try {
|
||||
artifact = Files.createTempFile("gate-i-runtime-", ".pbx");
|
||||
Files.write(artifact, module.serialize());
|
||||
final var command = renderCommand(runtimeCheckCommand, artifact);
|
||||
final var process = new ProcessBuilder(List.of("sh", "-lc", command))
|
||||
.directory(runtimeRoot.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
final var output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
|
||||
final var exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
return new CompatibilityResult(
|
||||
CompatibilityError.RUNTIME_REJECTED,
|
||||
local.hostcallCount(),
|
||||
runtimeLine,
|
||||
"runtime-backed",
|
||||
"runtime command rejected artifact (exit=%d): %s".formatted(exitCode, shrink(output)));
|
||||
}
|
||||
return new CompatibilityResult(
|
||||
local.error(),
|
||||
local.hostcallCount(),
|
||||
runtimeLine,
|
||||
"runtime-backed",
|
||||
"runtime command accepted artifact");
|
||||
} catch (IOException | InterruptedException e) {
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return fallbackWithReason(local, "runtime command failed: " + e.getMessage());
|
||||
} finally {
|
||||
if (artifact != null) {
|
||||
try {
|
||||
Files.deleteIfExists(artifact);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CompatibilityResult fallbackWithReason(
|
||||
final CompatibilityResult local,
|
||||
final String reason) {
|
||||
return new CompatibilityResult(
|
||||
local.error(),
|
||||
local.hostcallCount(),
|
||||
runtimeLine,
|
||||
FALLBACK_MODE,
|
||||
reason);
|
||||
}
|
||||
|
||||
private String renderCommand(
|
||||
final String commandTemplate,
|
||||
final Path artifactPath) {
|
||||
final var quotedPath = shellQuote(artifactPath.toAbsolutePath().toString());
|
||||
if (commandTemplate.contains("{artifact}")) {
|
||||
return commandTemplate.replace("{artifact}", quotedPath);
|
||||
}
|
||||
return commandTemplate + " " + quotedPath;
|
||||
}
|
||||
|
||||
private String shellQuote(final String value) {
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'";
|
||||
}
|
||||
|
||||
private static String readRuntimeLine(final Path runtimeRoot) {
|
||||
final var versionFile = runtimeRoot.resolve("VERSION.txt");
|
||||
if (!Files.exists(versionFile)) {
|
||||
return DEFAULT_RUNTIME_LINE;
|
||||
}
|
||||
try {
|
||||
final var content = Files.readString(versionFile).trim();
|
||||
if (content.isBlank()) {
|
||||
return DEFAULT_RUNTIME_LINE;
|
||||
}
|
||||
return "runtime-v" + content;
|
||||
} catch (IOException e) {
|
||||
return DEFAULT_RUNTIME_LINE;
|
||||
}
|
||||
}
|
||||
|
||||
private String shrink(final String output) {
|
||||
if (output == null || output.isBlank()) {
|
||||
return "<no output>";
|
||||
}
|
||||
if (output.length() <= 160) {
|
||||
return output;
|
||||
}
|
||||
return output.substring(0, 160) + "...";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package p.studio.compiler.integration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.backend.bytecode.BytecodeModule;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RuntimeBackedCompatibilityAdapterTest {
|
||||
|
||||
@Test
|
||||
void checkMustFallbackToLocalModeWhenRuntimeCommandIsUnavailable() {
|
||||
final var adapter = new RuntimeBackedCompatibilityAdapter(
|
||||
new LocalRuntimeCompatibilityAdapter(),
|
||||
Path.of("./missing-runtime"),
|
||||
"");
|
||||
final var module = new BytecodeModule(
|
||||
0,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.from(new BytecodeModule.FunctionMeta(0, 2, 0, 0, 0, 1)),
|
||||
codeRet(),
|
||||
null,
|
||||
ReadOnlyList.empty(),
|
||||
ReadOnlyList.empty());
|
||||
|
||||
final var result = adapter.check(module);
|
||||
assertEquals(CompatibilityError.NONE, result.error());
|
||||
assertEquals("local-fallback", result.adapterMode());
|
||||
assertTrue(!result.reason().isBlank());
|
||||
assertTrue(!result.runtimeLine().isBlank());
|
||||
}
|
||||
|
||||
private byte[] codeRet() {
|
||||
final var out = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
|
||||
out.putShort((short) 0x51);
|
||||
return out.array();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.studio.compiler.integration;
|
||||
|
||||
final class RuntimeCompatibilityAdapters {
|
||||
private RuntimeCompatibilityAdapters() {
|
||||
}
|
||||
|
||||
static RuntimeCompatibilityAdapter createDefault() {
|
||||
return new RuntimeBackedCompatibilityAdapter(new LocalRuntimeCompatibilityAdapter());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user