Skip to content

Run a standard library program on Lua - #1242

Merged
Frotty merged 4 commits into
masterfrom
test/execute-stdlib-on-lua
Aug 16, 2026
Merged

Run a standard library program on Lua#1242
Frotty merged 4 commits into
masterfrom
test/execute-stdlib-on-lua

Conversation

@Frotty

Copy link
Copy Markdown
Member

Nothing in this repository has ever executed a standard library program on Lua. Every test putting the library on that target compiles only — which is why fastHashMapAgainstTheStandardLibraryLua asserted on emitted shape rather than on a result, and it is how the empty-allocation bug in #1239 survived: the paths which would have caught it are compiled and never run.

It turned out to be four natives.

What was missing

Three packages could not initialise, each on one undefined global:

NativePackage
StringHashColors
LocationVectors
TimerStartGameTimer

StringCase made a fourth once the program itself ran. The wrapped message (Could not initialize package GameTimer.) hides this; the underlying error is on the line above it, which is where I should have looked the first time.

The implementations

Byte-accurate rather than approximations, since a test comparing two targets is worth nothing if they disagree by construction:

  • StringHash is the same Bob Jenkins lookup2 as Wc3StringHash, over bytes, with the same normalisation. It is a second transcription of one algorithm, which can drift, so agreesWithTheLuaRuntimeImplementation pins both against a fixed corpus — ascii, a path with backslashes, a multibyte character and the empty string. A change parting the two fails there.
  • StringCase folds only ascii letters, matching the interpreter: the bytes of a multibyte character are not letters, and folding one rewrites the character.
  • Location is a plain pair; nothing in a test reads terrain from one.
  • TimerStart keeps what it was started with and never fires. A test drives its own program rather than waiting on game time, and a package which starts a timer at init only needs the call to succeed. Making timers actually fire needs a model of game time, which is a design question rather than more stubs — worth saying plainly, because this is the one stub that is not equivalent to the real thing.

The harness

Success is read off stdout. Without the library, a program gets the generated fallback for testSuccess, which prints. With the library it gets the library's own, which is empty — so a test on that target could only ever be reported as not having succeeded, whatever it actually did. The harness now installs a printing one after loading the script.

This is the part that matters beyond the natives: it was not possible for a standard library Lua test to pass.

Result

fastHashMapAgainstTheStandardLibraryLua executes now instead of being compiled and assumed, and the container works there. Full suite green.

Closes the executed-on-Lua half of backlog item 16 (#1241).

Nothing did. Every test putting the library on that target compiled only,
because three packages could not initialise: Colors, Vectors and GameTimer
each call a native the runtime shim did not define. StringCase made a fourth
once the program itself ran.
That is how the empty allocation in #1239 survived - the paths which would
have caught it were compiled and never executed.
The four natives are byte-accurate rather than approximations, since a test
comparing targets is worth nothing if they disagree by construction:
StringHash is the same lookup2 as Wc3StringHash and pinned against it for a
fixed corpus, StringCase folds only ascii. A timer keeps what it was started
with and never fires: a test drives its own program rather than waiting on
game time, and a package which starts one at init needs the call to succeed.
Success is read off stdout, and a program with the library gets the library's
own testSuccess, which is empty - so such a test could only ever be reported
as not having succeeded, whatever it did. The harness now installs one which
prints, as the generated fallback does without the library.
FastHashMap against the standard library runs on Lua as a result, rather than
being compiled and assumed.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:9bbc4f2650

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// standard library gets a generated fallback which does; one with the library gets
// the library's own, which is empty - so without this, a test on that target can
// only ever be reported as not having succeeded, whatever it did.
chunk.append("testSuccess = function() print('testSuccess') end;");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve termination in the injected success native

Include os.exit() after printing, as the existing Lua native in LuaNatives does and the interpreter's OutputProvider.testSuccess() does by throwing its success sentinel. With the unconditional replacement here, any Lua test that calls testSuccess() before code that fails or loops will now fail or time out after already succeeding, creating backend-specific test behavior.

AGENTS.md reference: AGENTS.md:L217-L221

Useful? React with 👍 / 👎.

Comment on lines +98 to +102
assertEquals(Wc3StringHash.hash(ILconstString.fromText("abc").getVal()), 1043745117);
assertEquals(Wc3StringHash.hash(ILconstString.fromText("Hello World").getVal()), -1563733934);
assertEquals(Wc3StringHash.hash(ILconstString.fromText("Units\\Human\\Footman.mdx").getVal()), 166547459);
assertEquals(Wc3StringHash.hash(ILconstString.fromText("ä").getVal()), 1899444195);
assertEquals(Wc3StringHash.hash(ILconstString.fromText("abcdefghijklmnop").getVal()), 190888648);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exercise the Lua hash in the parity test

Run these inputs through wc3shim.lua rather than comparing only Wc3StringHash.hash(...) with literals. Every assertion here executes the Java implementation, so a change that breaks only the new Lua StringHash leaves this purported drift test green and removes the intended cross-backend validation.

AGENTS.md reference: AGENTS.md:L217-L221

Useful? React with 👍 / 👎.

The injected testSuccess dropped the os.exit() the generated native does, so a
program calling it before something which fails or loops would newly fail or
time out after having already succeeded - a difference between the targets
rather than a fix to one.
The parity test compared the Java hash against numbers written down from a Lua
run, which is not a parity test: a change breaking only the Lua side left it
green. It runs the inputs through the shim now and compares the two, with the
input escaped byte by byte so what Lua hashes is what Java was handed.
@Frotty

Copy link
Copy Markdown
MemberAuthor

Both fixed.

Preserve termination in the injected success native — right, and it is a real behaviour difference rather than a cosmetic one. LuaNatives emits print("testSuccess") os.exit() and the interpreter's OutputProvider.testSuccess() throws its sentinel, so both stop the program. My replacement printed and returned, which would newly fail or time out a test that succeeds and then runs into something that fails or loops. It calls os.exit() now, matching the generated native.

Exercise the Lua hash in the parity test — also right, and the test was misnamed for what it did: every assertion ran the Java implementation against numbers I had pasted in from a Lua run, so a change breaking only the Lua side left it green. That is the case worth catching, and it was the one case it could not catch.

It now runs each input through wc3shim.lua with the bundled interpreter and compares the two implementations directly. The input is escaped byte by byte into the Lua literal, so what the shim hashes is what the Java side was handed rather than whatever the command line did to it on the way through. Inputs cover ascii, both slash forms, mixed case, a two byte character, a three byte one and the empty string.

Full suite green.

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:db333697af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
String script = "dofile('src/test/resources/luaruntime/wc3shim.lua') "
+ "print(StringHash('" + literal + "'))";
Process p = new ProcessBuilder(getLuaExecutable(), "-e", script)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip the parity test when Lua is unavailable

On hosts where the bundled interpreter is incompatible or lacks required shared libraries and no working lua53/lua exists on PATH, getLuaExecutable() throws IllegalStateException here and fails the entire test class. The normal Lua execution path catches this condition and raises SkipException, and the repository explicitly requires Lua tests to skip visibly when no interpreter is found; apply the same handling to this direct invocation.

AGENTS.md reference: AGENTS.md:L106-L110

Useful? React with 👍 / 👎.

The item said the caches being cleared around every test method is what gives
the two compilations the same starting state. That is only half of it: the
before-method reset covers the first compile, and the explicit clearAll between
them covers the second. Naming the wrong one matters when diagnosing, because
removing the in-test reset would invalidate the conclusion rather than explain
the failure.
@Frotty

Copy link
Copy Markdown
MemberAuthor

Fixed. luaHashOf catches the IllegalStateException from getLuaExecutable() and raises SkipException with the reason, so a host without a working interpreter skips visibly rather than failing the class — the same handling as the normal execution path, and nothing in this test is what such a host would be exercising anyway.

One thing to flag about the history rather than leave it to be noticed: commit f78ca81 carries the wrong message. Its content is this skip handling; the message belongs to a change on the backlog branch, and a checkout blocked by an uncommitted file put the two together. The content of every commit here is what it claims to do, but that one's description is not. It squashes away on merge, so master will carry this pull request's title instead — saying so because a reader of the branch would otherwise be misled.

@Frotty

Copy link
Copy Markdown
MemberAuthor

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f78ca81671

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

String out;
try (java.io.BufferedReader r = new java.io.BufferedReader(
new java.io.InputStreamReader(p.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) {
out = r.lines().collect(java.util.stream.Collectors.joining("\n")).trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the Lua timeout before draining output

If the shim or StringHash hangs, r.lines().collect(...) blocks waiting for EOF before execution reaches the 30-second waitFor, so this parity test can hang the test suite indefinitely—the precise failure mode its timeout appears intended to bound. Drain the stream asynchronously (as the main Lua harness does), wait with the timeout, and forcibly terminate the process when it expires.

Useful? React with 👍 / 👎.

It read the process output to the end before waiting with a timeout, so a
shim or a hash which hung never reached the wait: the read blocks until the
stream closes, and a hung process does not close it. The timeout could only
have fired after the hang had already stopped the suite.
Drained on a thread of its own now, waited for with the deadline, and killed
when it expires - the same shape as the main Lua harness, which had this same
bug and this same fix.
@Frotty

Copy link
Copy Markdown
MemberAuthor

Right, and it is the same bug the main Lua harness had — which I fixed earlier in this work and then reintroduced here, in a helper small enough that it did not look like it needed the same care.

r.lines().collect(...) waits for the stream to close, and a hung process never closes it, so execution never reached the waitFor. The 30 seconds could only have elapsed after the hang had already stopped the suite, which is precisely the failure it was there to bound.

Now: drained on a thread of its own, waited for with the deadline, destroyForcibly() when it expires, and the failure names the input rather than leaving a bare timeout. The drain thread is joined afterwards so the output is fully written before it is read.

Parity test still passes on all eight inputs.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit:246e4d6141

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Frotty
Frotty merged commit c58a082 into masterAug 16, 2026
6 checks passed
@Frotty
Frotty deleted the test/execute-stdlib-on-lua branch August 16, 2026 15:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Frotty