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;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
class BackendGateIIntegrationTest {
|
class BackendGateIIntegrationTest {
|
||||||
private final RuntimeCompatibilityAdapter compatibilityAdapter = new LocalRuntimeCompatibilityAdapter();
|
private final RuntimeCompatibilityAdapter compatibilityAdapter = RuntimeCompatibilityAdapters.createDefault();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void gateI_syscSectionPresentAndEmpty() {
|
void gateI_syscSectionPresentAndEmpty() {
|
||||||
@ -52,8 +52,9 @@ class BackendGateIIntegrationTest {
|
|||||||
final var check = compatibilityAdapter.check(module);
|
final var check = compatibilityAdapter.check(module);
|
||||||
assertEquals(CompatibilityError.NONE, check.error());
|
assertEquals(CompatibilityError.NONE, check.error());
|
||||||
assertTrue(check.hostcallCount() > 0);
|
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.runtimeLine().isBlank());
|
||||||
|
assertTrue(!check.reason().isBlank());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -6,4 +6,5 @@ enum CompatibilityError {
|
|||||||
HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
||||||
UNUSED_SYSC_DECL,
|
UNUSED_SYSC_DECL,
|
||||||
MISSING_CAPABILITY,
|
MISSING_CAPABILITY,
|
||||||
|
RUNTIME_REJECTED,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,10 @@ record CompatibilityResult(
|
|||||||
CompatibilityError error,
|
CompatibilityError error,
|
||||||
int hostcallCount,
|
int hostcallCount,
|
||||||
String runtimeLine,
|
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,
|
CompatibilityError.RAW_SYSCALL_IN_PRELOAD,
|
||||||
hostcallCount,
|
hostcallCount,
|
||||||
runtimeLine,
|
runtimeLine,
|
||||||
"local");
|
"local",
|
||||||
|
"raw syscall opcode is forbidden");
|
||||||
}
|
}
|
||||||
case 0x71 -> {
|
case 0x71 -> {
|
||||||
final var syscIndex = readU32(module.code(), pc + 2);
|
final var syscIndex = readU32(module.code(), pc + 2);
|
||||||
@ -57,7 +58,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
|||||||
CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
CompatibilityError.HOSTCALL_INDEX_OUT_OF_BOUNDS,
|
||||||
hostcallCount,
|
hostcallCount,
|
||||||
runtimeLine,
|
runtimeLine,
|
||||||
"local");
|
"local",
|
||||||
|
"hostcall index out of bounds");
|
||||||
}
|
}
|
||||||
used[syscIndex] = true;
|
used[syscIndex] = true;
|
||||||
hostcallCount++;
|
hostcallCount++;
|
||||||
@ -69,7 +71,8 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
|||||||
CompatibilityError.MISSING_CAPABILITY,
|
CompatibilityError.MISSING_CAPABILITY,
|
||||||
hostcallCount,
|
hostcallCount,
|
||||||
runtimeLine,
|
runtimeLine,
|
||||||
"local");
|
"local",
|
||||||
|
"required capability is missing");
|
||||||
}
|
}
|
||||||
pc += 6;
|
pc += 6;
|
||||||
}
|
}
|
||||||
@ -85,10 +88,11 @@ final class LocalRuntimeCompatibilityAdapter implements RuntimeCompatibilityAdap
|
|||||||
CompatibilityError.UNUSED_SYSC_DECL,
|
CompatibilityError.UNUSED_SYSC_DECL,
|
||||||
hostcallCount,
|
hostcallCount,
|
||||||
runtimeLine,
|
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) {
|
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