Add Minecraft 26.x support and version Java artifacts
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { createServer } from "node:http";
|
||||
import { connect } from "node:net";
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, execFile } from "node:child_process";
|
||||
import { mkdir, copyFile, writeFile, readFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import assert from "node:assert/strict";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { datasetSchema } from "../shared/protocol";
|
||||
|
||||
// Disposable, loopback-only Paper test. No game client is launched.
|
||||
@@ -102,8 +104,50 @@ const api = createServer(async (req, res) => {
|
||||
await new Promise<void>((r) => api.listen(0, "127.0.0.1", r));
|
||||
await writeFile(
|
||||
`${directory}/plugins/MineGeo/config.yml`,
|
||||
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: 'fixture-token'\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
|
||||
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: ''\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
|
||||
);
|
||||
// Exercise the shared upload transport against the loopback mock separately.
|
||||
// The deployed plugin always uses its hardcoded production endpoint.
|
||||
const execute = promisify(execFile);
|
||||
let exportDir: string;
|
||||
const pluginJar = path.resolve("public/tools/plugin/minegeo-plugin-0.1.3.jar");
|
||||
await execute("javac", [
|
||||
"-cp",
|
||||
pluginJar,
|
||||
"-d",
|
||||
directory,
|
||||
"tests/java/UploadSmoke.java",
|
||||
]);
|
||||
await writeFile(
|
||||
directory + "/fixture-auth.json",
|
||||
JSON.stringify({ token: "fixture-token" }),
|
||||
);
|
||||
const upload = async (
|
||||
target: string | null,
|
||||
expected: RegExp,
|
||||
fails = false,
|
||||
) => {
|
||||
const args = [
|
||||
"-cp",
|
||||
directory + path.delimiter + pluginJar,
|
||||
"UploadSmoke",
|
||||
"http://127.0.0.1:" + (api.address() as any).port,
|
||||
exportDir,
|
||||
directory + "/fixture-auth.json",
|
||||
];
|
||||
if (target) args.push(target);
|
||||
let output = "",
|
||||
code = 0;
|
||||
try {
|
||||
const result = await execute("java", args, { windowsHide: true });
|
||||
output = result.stdout + result.stderr;
|
||||
} catch (error: any) {
|
||||
output = error.stdout + error.stderr;
|
||||
code = error.code;
|
||||
}
|
||||
assert.equal(code === 0, !fails, output);
|
||||
assert.match(output, expected);
|
||||
};
|
||||
const child = spawn("java", ["-Xmx1536M", "-jar", "paper.jar", "--nogui"], {
|
||||
cwd: directory,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
@@ -165,46 +209,53 @@ const command = async (text: string, pattern: RegExp) => {
|
||||
child.stdin.write(text + "\n");
|
||||
await pending;
|
||||
};
|
||||
const rconCommand = (text: string, expected: RegExp) => new Promise<void>((resolve, reject) => {
|
||||
const socket = connect(25581, "127.0.0.1");
|
||||
let buffer = Buffer.alloc(0);
|
||||
let authenticated = false;
|
||||
const timeout = setTimeout(() => finish(new Error(`RCON timed out waiting for ${expected}`)), 10000);
|
||||
const finish = (error?: Error) => {
|
||||
clearTimeout(timeout);
|
||||
socket.destroy();
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
};
|
||||
const packet = (id: number, type: number, body: string) => {
|
||||
const payload = Buffer.from(body);
|
||||
const frame = Buffer.alloc(payload.length + 14);
|
||||
frame.writeInt32LE(payload.length + 10, 0);
|
||||
frame.writeInt32LE(id, 4);
|
||||
frame.writeInt32LE(type, 8);
|
||||
payload.copy(frame, 12);
|
||||
return frame;
|
||||
};
|
||||
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
|
||||
socket.on("error", finish);
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
|
||||
const length = buffer.readInt32LE(0);
|
||||
const frame = buffer.subarray(0, length + 4);
|
||||
buffer = buffer.subarray(length + 4);
|
||||
const id = frame.readInt32LE(4);
|
||||
const type = frame.readInt32LE(8);
|
||||
if (id === -1) return finish(new Error("RCON authentication failed"));
|
||||
if (id === 1 && type === 2 && !authenticated) {
|
||||
authenticated = true;
|
||||
socket.write(packet(2, 2, text));
|
||||
} else if (id === 2 && expected.test(frame.toString("utf8", 12, length + 2))) {
|
||||
return finish();
|
||||
const rconCommand = (text: string, expected: RegExp) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const socket = connect(25581, "127.0.0.1");
|
||||
let buffer = Buffer.alloc(0);
|
||||
let authenticated = false;
|
||||
const timeout = setTimeout(
|
||||
() => finish(new Error(`RCON timed out waiting for ${expected}`)),
|
||||
10000,
|
||||
);
|
||||
const finish = (error?: Error) => {
|
||||
clearTimeout(timeout);
|
||||
socket.destroy();
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
};
|
||||
const packet = (id: number, type: number, body: string) => {
|
||||
const payload = Buffer.from(body);
|
||||
const frame = Buffer.alloc(payload.length + 14);
|
||||
frame.writeInt32LE(payload.length + 10, 0);
|
||||
frame.writeInt32LE(id, 4);
|
||||
frame.writeInt32LE(type, 8);
|
||||
payload.copy(frame, 12);
|
||||
return frame;
|
||||
};
|
||||
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
|
||||
socket.on("error", finish);
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
|
||||
const length = buffer.readInt32LE(0);
|
||||
const frame = buffer.subarray(0, length + 4);
|
||||
buffer = buffer.subarray(length + 4);
|
||||
const id = frame.readInt32LE(4);
|
||||
const type = frame.readInt32LE(8);
|
||||
if (id === -1) return finish(new Error("RCON authentication failed"));
|
||||
if (id === 1 && type === 2 && !authenticated) {
|
||||
authenticated = true;
|
||||
socket.write(packet(2, 2, text));
|
||||
} else if (
|
||||
id === 2 &&
|
||||
expected.test(frame.toString("utf8", 12, length + 2))
|
||||
) {
|
||||
return finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
try {
|
||||
await wait(/Done \(/, 0, 180000);
|
||||
await command("minegeo help", /MineGeo commands:/);
|
||||
@@ -227,7 +278,7 @@ try {
|
||||
let relative = (
|
||||
await readFile(`${directory}/plugins/MineGeo/latest-export.txt`, "utf8")
|
||||
).trim();
|
||||
const exportDir = `${directory}/plugins/MineGeo/${relative}`;
|
||||
exportDir = `${directory}/plugins/MineGeo/${relative}`;
|
||||
const local = datasetSchema.parse(
|
||||
JSON.parse(await readFile(`${exportDir}/manifest.json`, "utf8")),
|
||||
);
|
||||
@@ -243,44 +294,46 @@ try {
|
||||
(p) => p.x >= -32 && p.x < 32 && p.z >= -32 && p.z < 32,
|
||||
),
|
||||
);
|
||||
await command("minegeo upload", /Upload failed: Upload paused/);
|
||||
await command("minegeo upload", /Upload complete\. Open/);
|
||||
await upload(null, /Upload paused/, true);
|
||||
await upload(null, /queueing panorama processing|Already uploaded/);
|
||||
assert.equal(creates, 1);
|
||||
assert.equal(finalizes, 1);
|
||||
assert.equal(heads, 1);
|
||||
await command("minegeo upload", /Upload complete\. Open/);
|
||||
await upload(null, /queueing panorama processing|Already uploaded/);
|
||||
assert.equal(finalizes, 1);
|
||||
received = Buffer.alloc(0);
|
||||
expectedServer = "wrong.example.com";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/Wrong capture server/,
|
||||
true,
|
||||
);
|
||||
assert.equal(creates, 1, "Mismatch must stop before creating a draft");
|
||||
expectedServer = "PLAY.REGRESSION.EXAMPLE.COM.:25565";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(manifest.contributionTo, "00000000-0000-4000-8000-000000000024");
|
||||
assert.equal(manifest.viewpoints.length, 12);
|
||||
assert.equal(creates, 2);
|
||||
expectedServer = "wrong.example.com";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/Wrong capture server/,
|
||||
true,
|
||||
);
|
||||
assert.equal(creates, 2, "A cached selection must recheck the server");
|
||||
expectedServer = null;
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(creates, 2, "Cleared server permits an idempotent retry");
|
||||
assert.equal(finalizes, 2);
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(creates, 2);
|
||||
await command("minegeo export Random Positions", /Export complete:/);
|
||||
@@ -321,7 +374,7 @@ try {
|
||||
"repeated upload is idempotent",
|
||||
"cancellation preserves completed export",
|
||||
"24 random positions change on the next export",
|
||||
"contribution command caps at 12 and retries idempotently",
|
||||
"shared upload client caps contributions at 12 and retries idempotently",
|
||||
"wrong server blocked before upload, including cached retry",
|
||||
"owner override/default port and cleared address supported",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user