From 5419af896a6b78767dc237ed05ac9d40f0654e59 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl <4711730+kaklakariada@users.noreply.github.com> Date: Sun, 29 Sep 2024 16:35:58 +0200 Subject: [PATCH] Move to itsallcode org (#2) * Move to itsallcode org * Fix sonar warnings * Add dependency submission workflow --------- Co-authored-by: kaklakariada Co-authored-by: kaklakariada --- .github/workflows/dependency-submission.yml | 23 +++ .vscode/settings.json | 8 +- README.md | 8 +- build.gradle | 7 +- .../org/itsallcode/luava/LowLevelLua.java | 69 ++++++--- .../org/itsallcode/luava/LuaFunction.java | 89 ++++++++--- .../org/itsallcode/luava/LuaInterpreter.java | 30 ++-- .../java/org/itsallcode/luava/LuaStack.java | 138 +++++++++++++++++- .../java/org/itsallcode/luava/LuaTable.java | 16 +- .../itsallcode/luava/LuaInterpreterTest.java | 47 +++++- .../org/itsallcode/luava/LuaStackTest.java | 11 +- src/test/resources/logging.properties | 8 + 12 files changed, 347 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/dependency-submission.yml create mode 100644 src/test/resources/logging.properties diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 0000000..c85266a --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,23 @@ +name: Dependency Submission + +on: + # Only runs on main branch + push: + branches: [ main ] + +permissions: + contents: write # Required for dependency submission + +jobs: + dependency-submission: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 23 + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 diff --git a/.vscode/settings.json b/.vscode/settings.json index bbf66ee..f0f994a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.formatOnSave": true, - "editor.formatOnSaveMode": "modifications", + "editor.formatOnSaveMode": "file", "editor.codeActionsOnSave": { "source.organizeImports": "explicit", "source.generate.finalModifiers": "explicit", @@ -29,7 +29,7 @@ }, "java.compile.nullAnalysis.mode": "automatic", "sonarlint.connectedMode.project": { - "connectionId": "kaklakariada-github", - "projectKey": "luava" + "connectionId": "itsallcode", + "projectKey": "org.itsallcode:luava" } -} +} \ No newline at end of file diff --git a/README.md b/README.md index 7b51097..bcd781c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An experimental Java library for embedding a Lua VM. -[![Build](https://github.com/kaklakariada/java-lua/actions/workflows/build.yml/badge.svg)](https://github.com/kaklakariada/java-lua/actions/workflows/build.yml) +[![Build](https://github.com/itsallcode/luava/actions/workflows/build.yml/badge.svg)](https://github.com/itsallcode/luava/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Aluava&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Aluava) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Aluava&metric=bugs)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Aluava) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Aluava&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Aluava) @@ -15,8 +15,14 @@ An experimental Java library for embedding a Lua VM. [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Aluava&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Aluava) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Aluava&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Aluava) +This project allows executing Lua scripts from a Java application. It uses [Foreign Function & Memory API (JEP 454)](https://openjdk.org/jeps/454) for accessing the Lua C API. + ## Development +### Native Interface + +Build scripts generate native interface classes in `build/generated/sources/jextract` using [Jextract](https://github.com/openjdk/jextract). Scripts download and cache Jextract automatically during the build. + ### Check for Dependency Updates ```sh diff --git a/build.gradle b/build.gradle index feffed2..80cf51a 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ dependencies { java { toolchain { - def javaVersion = project.hasProperty('javaVersion') ? project.getProperty('javaVersion') : 22 + def javaVersion = project.hasProperty('javaVersion') ? project.getProperty('javaVersion') : 23 languageVersion = JavaLanguageVersion.of(javaVersion) } } @@ -41,7 +41,8 @@ jacocoTestReport { sonarqube { properties { property "sonar.host.url", "https://sonarcloud.io" - property "sonar.organization", "kaklakariada-github" + property "sonar.organization", "itsallcode" + property "sonar.exclusions", "build/generated/sources/jextract/**" } } @@ -66,7 +67,7 @@ testing { } } -OperatingSystem currentOs = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem +def currentOs = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem task downloadJextract(type: DownloadTask) { def getOs = { if(currentOs.isMacOsX()) { diff --git a/src/main/java/org/itsallcode/luava/LowLevelLua.java b/src/main/java/org/itsallcode/luava/LowLevelLua.java index 0c6ab05..983517b 100644 --- a/src/main/java/org/itsallcode/luava/LowLevelLua.java +++ b/src/main/java/org/itsallcode/luava/LowLevelLua.java @@ -2,17 +2,19 @@ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.util.function.IntSupplier; +import java.util.logging.Logger; import org.itsallcode.luava.ffi.Lua; import org.itsallcode.luava.ffi.lua_KFunction; -import org.itsallcode.luava.ffi.lua_KFunction.Function; class LowLevelLua implements AutoCloseable { + private static final Logger LOG = Logger.getLogger(LowLevelLua.class.getName()); private final Arena arena; - private final MemorySegment state; + final MemorySegment state; private final LuaStack stack; - private LowLevelLua(final Arena arena, final MemorySegment state) { + LowLevelLua(final Arena arena, final MemorySegment state) { this.arena = arena; this.state = state; this.stack = new LuaStack(state, arena); @@ -24,34 +26,54 @@ static LowLevelLua create() { return new LowLevelLua(arena, state); } + LowLevelLua forState(final MemorySegment newState) { + return new LowLevelLua(arena, newState); + } + void openLibs() { Lua.luaL_openlibs(state); } - void pcall(final int nargs, final int nresults, final int errfunc, final long ctx) { - final Function function = (final MemorySegment l, final int status, final long ctx1) -> { - return 0; - }; - pcall(nargs, nresults, errfunc, ctx, function); + void pcall(final int nargs, final int nresults) { + pcallk(nargs, nresults, 0, 0, null); + } + + void pcall(final int nargs, final int nresults, final int errfunc) { + pcallk(nargs, nresults, errfunc, 0, null); } - void pcall(final int nargs, final int nresults, final int errfunc, final long ctx, + /** + * This function behaves exactly like {@link #pcall(int, int, int, long)}, + * except that it allows the + * called function to yield. + * + * @param nargs + * @param nresults + * @param msgHandler + * @param ctx + * @param upcallFunction + */ + private void pcallk(final int nargs, final int nresults, final int msgHandler, final long ctx, final lua_KFunction.Function upcallFunction) { - final MemorySegment k = lua_KFunction.allocate(upcallFunction, arena); - final int error = Lua.lua_pcallk(state, nargs, nresults, errfunc, ctx, k); - if (error != 0) { - final String message = stack.toString(-1); - stack.pop(1); - throw new FunctionCallException("lua_pcallk", error, message); - } + LOG.info( + () -> "Calling function with " + nargs + " args and " + nresults + " results, msgHandler: " + msgHandler + + " context: " + ctx); + final MemorySegment k = upcallFunction == null ? Lua.NULL() : lua_KFunction.allocate(upcallFunction, arena); + checkStatus("lua_pcallk", () -> Lua.lua_pcallk(state, nargs, nresults, msgHandler, ctx, k)); } void loadString(final String chunk) { - final int error = Lua.luaL_loadstring(state, arena.allocateFrom(chunk)); - if (error != 0) { - final String message = stack.toString(-1); - stack.pop(1); - throw new FunctionCallException("luaL_loadstring", error, message); + checkStatus("luaL_loadstring", () -> Lua.luaL_loadstring(state, arena.allocateFrom(chunk))); + } + + void checkStatus(final String functionName, final IntSupplier nativeFunctionCall) { + final int status = nativeFunctionCall.getAsInt(); + if (status != Lua.LUA_OK()) { + LOG.warning( + () -> "Lua API call '" + functionName + "' failed with status " + status + + ": getting error message..."); + final String message = stack.popString(); + throw new FunctionCallException(functionName, status, message); } } @@ -72,9 +94,8 @@ LuaTable table(final int idx) { return new LuaTable(state, stack, arena, idx); } - public LuaFunction function(final int idx) { - assertType(idx, LuaType.FUNCTION); - return new LuaFunction(state, stack, arena, idx); + public LuaFunction globalFunction(final String name) { + return new LuaFunction(this, name); } private void assertType(final int idx, final LuaType expectedType) { diff --git a/src/main/java/org/itsallcode/luava/LuaFunction.java b/src/main/java/org/itsallcode/luava/LuaFunction.java index 30df166..184f617 100644 --- a/src/main/java/org/itsallcode/luava/LuaFunction.java +++ b/src/main/java/org/itsallcode/luava/LuaFunction.java @@ -1,44 +1,85 @@ package org.itsallcode.luava; -import java.lang.foreign.Arena; +import static java.util.Collections.emptyList; + import java.lang.foreign.MemorySegment; +import java.util.Arrays; +import java.util.List; +import java.util.function.UnaryOperator; +import java.util.logging.Logger; import org.itsallcode.luava.ffi.Lua; +import org.itsallcode.luava.ffi.lua_CFunction; public class LuaFunction { + private static final Logger LOG = Logger.getLogger(LuaFunction.class.getName()); + private final LowLevelLua lua; + private final String name; + private lua_CFunction.Function messageHandler; + private List argumentValues = emptyList(); + private List> resultTypes = emptyList(); - private final MemorySegment state; - private final LuaStack stack; - private final Arena arena; - private final int idx; + LuaFunction(final LowLevelLua lowLevelLua, final String name) { + this.lua = lowLevelLua; + this.name = name; + } - LuaFunction(final MemorySegment state, final LuaStack stack, final Arena arena, final int idx) { - this.state = state; - this.stack = stack; - this.arena = arena; - this.idx = idx; + @SuppressWarnings("java:S923") // Using varargs by intention + public LuaFunction argumentValues(final Object... argumentValues) { + this.argumentValues = Arrays.asList(argumentValues); + return this; } - public void addArgInteger(final int value) { - stack.pushInteger(value); + @SuppressWarnings("java:S923") // Using varargs by intention + public LuaFunction resultTypes(final Class... resultTypes) { + this.resultTypes = Arrays.asList(resultTypes); + return this; } - public void call(final int nargs, final int nresults) { - call(nargs, nresults, 0, 0, Lua.NULL()); + public LuaFunction messageUpdateHandler(final UnaryOperator messageHandler) { + return messageHandler((final MemorySegment l) -> { + LOG.fine("Message handler: popping error message..."); + final String msg = lua.stack().popString(); + final String updatedMsg = messageHandler.apply(msg); + LOG.fine(() -> "Pushing updated error message '" + updatedMsg + "'"); + lua.stack().pushString(updatedMsg); + return Lua.LUA_OK(); + }); } - private void call(final int nargs, final int nresults, final int errfunc, final long ctx, final MemorySegment k) { - final int status = Lua.lua_pcallk(state, nargs, nresults, errfunc, ctx, k); - if (status != Lua.LUA_OK()) { - final String errorMessage = stack.toString(-1); - stack.pop(1); - throw new FunctionCallException("lua_pcallk", status, errorMessage); + public LuaFunction messageHandler(final lua_CFunction.Function messageHandler) { + this.messageHandler = messageHandler; + return this; + } + + public List call() { + final int messageHandlerIdx = getMessageHandlerIdx(); + lua.getGlobal(name); + pushArguments(); + try { + lua.pcall(this.argumentValues.size(), this.resultTypes.size(), messageHandlerIdx); + return getResultValues(); + } finally { + if (this.messageHandler != null) { + LOG.fine("Popping message handler"); + lua.stack().pop(); + } } } - public long getIntegerResult() { - final long value = stack.toInteger(-1); - stack.pop(1); - return value; + private void pushArguments() { + this.argumentValues.forEach(v -> lua.stack().pushObject(v)); + } + + private List getResultValues() { + return this.resultTypes.stream().map(t -> lua.stack().popObject(t)).toList(); + } + + private int getMessageHandlerIdx() { + if (messageHandler == null) { + return 0; + } + lua.stack().pushCFunction(messageHandler); + return lua.stack().getTop(); } } diff --git a/src/main/java/org/itsallcode/luava/LuaInterpreter.java b/src/main/java/org/itsallcode/luava/LuaInterpreter.java index 2d2476c..f0e9350 100644 --- a/src/main/java/org/itsallcode/luava/LuaInterpreter.java +++ b/src/main/java/org/itsallcode/luava/LuaInterpreter.java @@ -1,10 +1,10 @@ package org.itsallcode.luava; -public class LuaInterpreter implements AutoCloseable { +public final class LuaInterpreter implements AutoCloseable { private final LowLevelLua lua; - public LuaInterpreter(final LowLevelLua lua) { + private LuaInterpreter(final LowLevelLua lua) { this.lua = lua; } @@ -18,32 +18,28 @@ public void close() { this.lua.close(); } + LuaStack stack() { + return lua.stack(); + } + public String getGlobalString(final String name) { lua.getGlobal(name); - final String value = lua.stack().toString(-1); - lua.stack().pop(1); - return value; + return lua.stack().popString(); } public long getGlobalInteger(final String name) { lua.getGlobal(name); - final long value = lua.stack().toInteger(-1); - lua.stack().pop(1); - return value; + return lua.stack().popInteger(); } public double getGlobalNumber(final String name) { lua.getGlobal(name); - final double value = lua.stack().toNumber(-1); - lua.stack().pop(1); - return value; + return lua.stack().popNumber(); } public boolean getGlobalBoolean(final String name) { lua.getGlobal(name); - final boolean value = lua.stack().toBoolean(-1); - lua.stack().pop(1); - return value; + return lua.stack().popBoolean(); } public LuaTable getGlobalTable(final String name) { @@ -52,8 +48,7 @@ public LuaTable getGlobalTable(final String name) { } public LuaFunction getGlobalFunction(final String name) { - lua.getGlobal(name); - return lua.function(-1); + return lua.globalFunction(name); } public void setGlobalString(final String name, final String value) { @@ -78,7 +73,6 @@ public void setGlobalBoolean(final String name, final boolean value) { public void exec(final String chunk) { lua.loadString(chunk); - lua.pcall(0, 0, 0, 0); + lua.pcall(0, 0); } - } diff --git a/src/main/java/org/itsallcode/luava/LuaStack.java b/src/main/java/org/itsallcode/luava/LuaStack.java index 04c9764..25d3a11 100644 --- a/src/main/java/org/itsallcode/luava/LuaStack.java +++ b/src/main/java/org/itsallcode/luava/LuaStack.java @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import org.itsallcode.luava.ffi.Lua; +import org.itsallcode.luava.ffi.lua_CFunction; class LuaStack { private final MemorySegment state; @@ -53,21 +54,78 @@ void pushString(final String value) { Lua.lua_pushlstring(state, segment, segment.byteSize() - 1); } - void pop(final int n) { + void pushCFunction(final lua_CFunction.Function fn) { + pushClosure(fn, 0); + } + + private void pushClosure(final lua_CFunction.Function fn, final int n) { + final MemorySegment functionSegment = lua_CFunction.allocate(fn, arena); + Lua.lua_pushcclosure(state, functionSegment, n); + } + + public void pushObject(final Object v) { + switch (v) { + case null: + pushNil(); + return; + case final Integer i: + pushInteger(i); + return; + case final Long l: + pushInteger(l); + return; + case final Float f: + pushNumber(f); + return; + case final Double d: + pushNumber(d); + return; + case final Boolean b: + pushBoolean(b); + return; + case final String s: + pushString(s); + return; + case final lua_CFunction.Function f: + pushCFunction(f); + return; + default: + throw new IllegalArgumentException( + "Unsupported type " + v.getClass().getName() + " of value '" + v + "'"); + } + } + + void pop() { + pop(1); + } + + private void pop(final int n) { setTop(-n - 1); } - void setTop(final int n) { + private void setTop(final int n) { Lua.lua_settop(state, n); } - boolean toBoolean(final int idx) { + private Void popNil() { + pop(); + return null; + } + + private boolean toBoolean(final int idx) { return Lua.lua_toboolean(state, idx) != 0; } - String toString(final int idx) { + boolean popBoolean() { + final boolean bool = toBoolean(-1); + pop(); + return bool; + } + + private String toString(final int idx) { if (!isString(idx)) { - throw new LuaException("Expected string at index " + idx + " but was " + getType(idx)); + throw new LuaException( + "Expected string at index " + idx + " but was " + getType(idx) + ", " + printStack()); } final MemorySegment len = arena.allocateFrom(Lua.size_t, 0); final MemorySegment result = Lua.lua_tolstring(state, idx, len); @@ -81,7 +139,13 @@ String toString(final int idx) { return new String(bytes, 0, length, StandardCharsets.UTF_8); } - double toNumber(final int idx) { + String popString() { + final String value = toString(-1); + pop(); + return value; + } + + private double toNumber(final int idx) { final MemorySegment isNumber = arena.allocateFrom(Lua.C_INT, 0); final double value = Lua.lua_tonumberx(state, idx, isNumber); if (isNumber.get(Lua.C_INT, 0) != 1) { @@ -90,7 +154,13 @@ String toString(final int idx) { return value; } - long toInteger(final int idx) { + double popNumber() { + final double number = toNumber(-1); + pop(); + return number; + } + + private long toInteger(final int idx) { final MemorySegment isNumber = arena.allocateFrom(Lua.C_INT, 0); final long value = Lua.lua_tointegerx(state, idx, isNumber); if (isNumber.get(Lua.C_INT, 0) != 1) { @@ -99,7 +169,61 @@ long toInteger(final int idx) { return value; } + long popInteger() { + final long integer = toInteger(-1); + pop(); + return integer; + } + int getTop() { return Lua.lua_gettop(state); } + + String printStack() { + final StringBuilder b = new StringBuilder(); + final int top = this.getTop(); + b.append("Stack size: " + top + ": "); + for (int idx = 1; idx <= top; idx++) { + b.append(format(idx)); + if (idx < top) { + b.append(", "); + } + } + return b.toString(); + } + + private String format(final int idx) { + final LuaType type = getType(idx); + final String result = "#" + idx + " " + type; + final String value = switch (type) { + case STRING -> toString(idx); + case NUMBER -> String.valueOf(toNumber(idx)); + case BOOLEAN -> String.valueOf(toBoolean(idx)); + default -> ""; + }; + if (!value.isEmpty()) { + return result + ": " + value; + } + return result; + } + + public Object popObject(final Class expectedType) { + final LuaType type = getType(-1); + if (type == LuaType.NIL) { + return popNil(); + } + if (expectedType == Boolean.class) { + return popBoolean(); + } + if (expectedType == Long.class) { + return popInteger(); + } + if (expectedType == Double.class) { + return popNumber(); + } + if (expectedType == String.class) { + return popString(); + } + throw new UnsupportedOperationException("Unsupported Lua type " + expectedType + " / " + type); + } } diff --git a/src/main/java/org/itsallcode/luava/LuaTable.java b/src/main/java/org/itsallcode/luava/LuaTable.java index 36ac9d1..b36f3d0 100644 --- a/src/main/java/org/itsallcode/luava/LuaTable.java +++ b/src/main/java/org/itsallcode/luava/LuaTable.java @@ -20,30 +20,22 @@ public class LuaTable { public String getString(final String key) { getField(key, LuaType.STRING); - final String value = stack.toString(-1); - stack.pop(1); - return value; + return stack.popString(); } public long getInteger(final String key) { getField(key, LuaType.NUMBER); - final long value = stack.toInteger(-1); - stack.pop(1); - return value; + return stack.popInteger(); } public double getNumber(final String key) { getField(key, LuaType.NUMBER); - final double value = stack.toNumber(-1); - stack.pop(1); - return value; + return stack.popNumber(); } public boolean getBoolean(final String key) { getField(key, LuaType.BOOLEAN); - final boolean value = stack.toBoolean(-1); - stack.pop(1); - return value; + return stack.popBoolean(); } private LuaType getFieldType(final String key) { diff --git a/src/test/java/org/itsallcode/luava/LuaInterpreterTest.java b/src/test/java/org/itsallcode/luava/LuaInterpreterTest.java index 25ccfa9..2ff9cf7 100644 --- a/src/test/java/org/itsallcode/luava/LuaInterpreterTest.java +++ b/src/test/java/org/itsallcode/luava/LuaInterpreterTest.java @@ -5,6 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.lang.foreign.MemorySegment; +import java.util.List; + import org.junit.jupiter.api.*; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; @@ -16,6 +19,7 @@ class LuaInterpreterTest { @BeforeEach void setup() { lua = LuaInterpreter.create(); + assertStackSize(0); } @AfterEach @@ -92,22 +96,50 @@ void setGetGlobalBoolean(final boolean value) { @Test void getCallGlobalFunction() { lua.exec("function increment(x) return x+1 end"); - final LuaFunction function = lua.getGlobalFunction("increment"); - function.addArgInteger(42); - function.call(1, 1); - assertThat(function.getIntegerResult(), equalTo(43L)); + assertStackSize(0); + final List result = lua.getGlobalFunction("increment").argumentValues(42).resultTypes(Long.class) + .call(); + assertThat(result.get(0), equalTo(43L)); + } + + @Test + void getCallGlobalFunctionWithtErrorHandler() { + lua.exec("function increment(x) return x+1 end"); + assertStackSize(0); + final List result = lua.getGlobalFunction("increment").argumentValues(42).resultTypes(Long.class) + .messageHandler((final MemorySegment l) -> 0).call(); + assertThat(result.get(0), equalTo(43L)); + assertStackSize(0); } @Test void getCallGlobalFunctionFails() { lua.exec("function increment(x) error('failure') end"); - final LuaFunction function = lua.getGlobalFunction("increment"); - function.addArgInteger(42); - final LuaException exception = assertThrows(LuaException.class, () -> function.call(1, 1)); + assertStackSize(0); + final LuaFunction function = lua.getGlobalFunction("increment").argumentValues(42).resultTypes(Long.class); + final LuaException exception = assertThrows(LuaException.class, function::call); + assertStackSize(0); assertThat(exception.getMessage(), equalTo( "Function 'lua_pcallk' failed with error 2: [string \"function increment(x) error('failure') end\"]:1: failure")); } + @Test + @Disabled("Currently broken") + void getCallGlobalFunctionWithMessageHandler() { + lua.exec("function increment(x) error('failure') end"); + assertStackSize(0); + final LuaFunction function = lua.getGlobalFunction("increment").argumentValues(42).resultTypes(Long.class) + .messageUpdateHandler((final String msg) -> "Updated error: " + msg); + final FunctionCallException exception = assertThrows(FunctionCallException.class, function::call); + assertThat(exception.getMessage(), equalTo( + "Function 'lua_pcallk' failed with error 2: Updated error: [string \"function increment(x) error('failure') end\"]:1: failure")); + assertStackSize(0); + } + + void assertStackSize(final int expectedSize) { + assertThat("stack size", lua.stack().getTop(), equalTo(expectedSize)); + } + @Test void getTableStringValue() { lua.exec("result = { key = 'value' }"); @@ -159,7 +191,6 @@ void getTableStringFailsWrongType() { assertThat(exception.getMessage(), equalTo("Expected TABLE at -1 but got STRING")); } - void assertFails(final Executable executable, final String expectedErrorMessage) { final FunctionCallException exception = assertThrows(FunctionCallException.class, executable); assertThat(exception.getRootError(), equalTo(expectedErrorMessage)); diff --git a/src/test/java/org/itsallcode/luava/LuaStackTest.java b/src/test/java/org/itsallcode/luava/LuaStackTest.java index 112673c..a15e3c0 100644 --- a/src/test/java/org/itsallcode/luava/LuaStackTest.java +++ b/src/test/java/org/itsallcode/luava/LuaStackTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; - class LuaStackTest { private LowLevelLua lua; private LuaStack stack; @@ -27,7 +26,7 @@ void stop() { @ValueSource(strings = { "", "abc", "öäüß", "!§$%&", "String with \0 zero" }) void pushString(final String value) { stack.pushString(value); - final String actual = stack.toString(-1); + final String actual = stack.popString(); assertThat(actual, equalTo(value)); } @@ -40,20 +39,20 @@ void pushNil() { @Test void pushNumber() { stack.pushNumber(3.14); - assertThat(stack.toNumber(-1), equalTo(3.14)); + assertThat(stack.popNumber(), equalTo(3.14)); } @Test void pushInteger() { stack.pushInteger(42); - assertThat(stack.toInteger(-1), equalTo(42L)); + assertThat(stack.popInteger(), equalTo(42L)); } @ParameterizedTest @ValueSource(booleans = { true, false }) void pushBoolean(final boolean value) { stack.pushBoolean(value); - assertThat(stack.toBoolean(-1), equalTo(value)); + assertThat(stack.popBoolean(), equalTo(value)); } @Test @@ -63,7 +62,7 @@ void getTop() { assertThat(stack.getTop(), equalTo(1)); stack.pushBoolean(true); assertThat(stack.getTop(), equalTo(2)); - stack.pop(1); + stack.pop(); assertThat(stack.getTop(), equalTo(1)); } } diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 0000000..210e429 --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1,8 @@ +handlers = java.util.logging.ConsoleHandler +.level = INFO +java.util.logging.ConsoleHandler.level = FINEST +java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.encoding = UTF-8 +java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s - %5$s %6$s%n + +org.itsallcode.luava.level = FINEST