From 6c20b470085eb6c065c51c481d43949499c5479c Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 00:44:51 +0200 Subject: [PATCH 1/9] Exercise generated programs across compiler backends --- .../tests/CompilerFuzzTestsSC.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilerFuzzTestsSC.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilerFuzzTestsSC.java index 6f79bfc2b..37b994aa1 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilerFuzzTestsSC.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompilerFuzzTestsSC.java @@ -25,6 +25,29 @@ public void generatedProgramsAreCrashFree(@From(RandomProgram.class) Program pro Assert.assertNotNull(result.getGui()); } + @Property(maxInvocations = 64) + public void generatedProgramsCompileForBothBackends(@From(RandomProgram.class) Program program) { + assertCompilesForBothBackends(program); + } + + @Test + public void generatedCorpusCompilesForBothBackends() { + new RandomProgram().generate(0).forEach(this::assertCompilesForBothBackends); + } + + private void assertCompilesForBothBackends(Program program) { + CompilationResult result = test() + .setStopOnFirstError(false) + .executeProg(false) + .testLua(true) + .luaOnly(false) + .compilationUnits(asCompilationUnits(program)); + + Assert.assertTrue(result.getGui().getErrorList().isEmpty(), + "generated program produced compiler diagnostics: " + result.getGui().getErrorList() + + "\nsource:\n" + String.join("\n---\n", program.sources)); + } + @Property(maxInvocations = 180) public void mixedNewlineStylesAreCrashFree(@From(RandomProgram.class) Program program) { String alternateNewline = "\n".equals(program.newline) ? "\r\n" : "\n"; @@ -173,7 +196,7 @@ private static String buildRandomSingleProgram(int seed, String newline) { if (includeInterface) { lines.add("interface IHandler"); - lines.add(indent + "function handle(int value) returns int"); + lines.add(indent + "function process(int value) returns int"); } lines.add("class Counter"); @@ -193,7 +216,7 @@ private static String buildRandomSingleProgram(int seed, String newline) { if (includeInterface) { lines.add("class Sink implements IHandler"); - lines.add(indent + "function handle(int value) returns int"); + lines.add(indent + "function process(int value) returns int"); lines.add(indent + indent + "return value + base"); } @@ -222,7 +245,7 @@ private static String buildRandomSingleProgram(int seed, String newline) { if (includeInterface) { lines.add(indent + "IHandler handler = new Sink()"); - lines.add(indent + "total = handler.handle(total)"); + lines.add(indent + "total = handler.process(total)"); } if (includeTuple) { From 54f1a99781761a3b5b4de2b30cb8f0b09dbc4870 Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 08:44:19 +0200 Subject: [PATCH 2/9] Cover core compiler utilities --- .../wurstscript/utils/MapWithIndexes.java | 2 +- .../java/tests/utils/LineOffsetsTests.java | 31 +++++++++++ .../java/tests/utils/MapWithIndexesTests.java | 53 +++++++++++++++++++ .../wurstscript/tests/WurstKeywordsTests.java | 20 +++++++ .../wurstscript/tests/WurstOperatorTests.java | 51 ++++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 de.peeeq.wurstscript/src/test/java/tests/utils/LineOffsetsTests.java create mode 100644 de.peeeq.wurstscript/src/test/java/tests/utils/MapWithIndexesTests.java create mode 100644 de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstKeywordsTests.java create mode 100644 de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/utils/MapWithIndexes.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/utils/MapWithIndexes.java index 8fa66d9fe..dde09770d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/utils/MapWithIndexes.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/utils/MapWithIndexes.java @@ -79,7 +79,7 @@ public V put(K key, V value) { V oldV = base.put(key, value); if (oldV != null) { for (BiConsumer f : onDelete) { - f.accept(key, value); + f.accept(key, oldV); } } for (BiConsumer f : onInserts) { diff --git a/de.peeeq.wurstscript/src/test/java/tests/utils/LineOffsetsTests.java b/de.peeeq.wurstscript/src/test/java/tests/utils/LineOffsetsTests.java new file mode 100644 index 000000000..f803ec9e8 --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/utils/LineOffsetsTests.java @@ -0,0 +1,31 @@ +package tests.utils; + +import de.peeeq.wurstscript.utils.LineOffsets; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class LineOffsetsTests { + @Test + public void resolvesPreviousOffsetsAndColumns() { + LineOffsets offsets = new LineOffsets(); + offsets.set(1, 5); + offsets.set(2, 10); + + assertEquals(offsets.get(0), -1); + assertEquals(offsets.get(1), 5); + assertEquals(offsets.get(3), 10); + assertEquals(offsets.getLine(7), 2); + assertEquals(offsets.getColumn(7), 2); + } + + @Test + public void growsForLargeLineNumbersAndClampsQueries() { + LineOffsets offsets = new LineOffsets(); + offsets.set(256, 1000); + + assertEquals(offsets.get(256), 1000); + assertEquals(offsets.get(10000), 1000); + assertEquals(offsets.getLine(1000), 256); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/utils/MapWithIndexesTests.java b/de.peeeq.wurstscript/src/test/java/tests/utils/MapWithIndexesTests.java new file mode 100644 index 000000000..11560bb7b --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/utils/MapWithIndexesTests.java @@ -0,0 +1,53 @@ +package tests.utils; + +import de.peeeq.wurstscript.utils.MapWithIndexes; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Set; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class MapWithIndexesTests { + private record Item(String group, Set tags, boolean active) { + } + + @Test + public void indexesStayConsistentWhenValuesAreReplaced() { + MapWithIndexes items = new MapWithIndexes<>(); + MapWithIndexes.Index groups = items.createIndex(Item::group); + MapWithIndexes.PredIndex active = items.createPredicateIndex(Item::active); + MapWithIndexes.Index tags = items.createMultiIndex(Item::tags); + + items.put("one", new Item("red", Set.of("warm", "bright"), true)); + assertEquals(groups.lookup("red"), List.of("one")); + assertEquals(active.lookup(), List.of("one")); + assertEquals(tags.lookup("warm"), List.of("one")); + + items.put("one", new Item("blue", Set.of("cold"), false)); + assertTrue(groups.lookup("red").isEmpty()); + assertEquals(groups.lookup("blue"), List.of("one")); + assertTrue(active.lookup().isEmpty()); + assertTrue(tags.lookup("warm").isEmpty()); + assertEquals(tags.lookup("cold"), List.of("one")); + } + + @Test + public void removeAllAndClearUpdateEveryIndex() { + MapWithIndexes items = new MapWithIndexes<>(); + MapWithIndexes.Index groups = items.createIndex(Item::group); + items.put("one", new Item("red", Set.of(), true)); + items.put("two", new Item("red", Set.of(), true)); + items.put("three", new Item("blue", Set.of(), true)); + + items.removeAll(List.of("one", "three")); + assertEquals(items.keySet(), Set.of("two")); + assertEquals(groups.lookup("red"), List.of("two")); + assertTrue(groups.lookup("blue").isEmpty()); + + items.clear(); + assertTrue(items.isEmpty()); + assertTrue(groups.lookup("red").isEmpty()); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstKeywordsTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstKeywordsTests.java new file mode 100644 index 000000000..a2af16329 --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstKeywordsTests.java @@ -0,0 +1,20 @@ +package tests.wurstscript.tests; + +import de.peeeq.wurstscript.WurstKeywords; +import org.testng.annotations.Test; + +import java.util.Arrays; + +import static org.testng.Assert.assertTrue; + +public class WurstKeywordsTests { + @Test + public void keywordTablesContainLanguageAndJassWords() { + assertTrue(Arrays.asList(WurstKeywords.KEYWORDS).containsAll(Arrays.asList( + "class", "tuple", "compiletime", "isLua", "function", "returns"))); + assertTrue(Arrays.asList(WurstKeywords.JASS_PRIMITIVE_TYPES).containsAll(Arrays.asList( + "int", "real", "boolean", "string", "handle"))); + assertTrue(Arrays.asList(WurstKeywords.JASSTYPES).containsAll(Arrays.asList( + "unit", "trigger", "framehandle", "hashtable"))); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java new file mode 100644 index 000000000..6e27e28d8 --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java @@ -0,0 +1,51 @@ +package tests.wurstscript.tests; + +import de.peeeq.wurstscript.WurstOperator; +import de.peeeq.wurstscript.intermediatelang.ILconst; +import de.peeeq.wurstscript.intermediatelang.ILconstBool; +import de.peeeq.wurstscript.intermediatelang.ILconstInt; +import de.peeeq.wurstscript.intermediatelang.ILconstReal; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class WurstOperatorTests { + @Test + public void moduloMatchesWurstAndJassSemantics() { + assertEquals(WurstOperator.moduloInteger(-7, 3), 2); + assertEquals(WurstOperator.moduloInteger(7, -3), 1); + assertEquals(WurstOperator.jassModuloInteger(-7, 3), -1); + assertEquals(WurstOperator.moduloReal(-7.5f, 3f), 1.5f, 0.0001f); + } + + @Test + public void binaryEvaluationPreservesLazyAndArithmeticOperators() { + ILconst falseValue = WurstOperator.AND.evaluateBinaryOperator(ILconstBool.instance(false), + () -> { throw new AssertionError("AND evaluated its lazy right operand"); }); + ILconst trueValue = WurstOperator.OR.evaluateBinaryOperator(ILconstBool.instance(true), + () -> { throw new AssertionError("OR evaluated its lazy right operand"); }); + assertFalse(((ILconstBool) falseValue).getVal()); + assertTrue(((ILconstBool) trueValue).getVal()); + + ILconst sum = WurstOperator.PLUS.evaluateBinaryOperator(new ILconstInt(2), () -> new ILconstInt(3)); + ILconst quotient = WurstOperator.DIV_INT.evaluateBinaryOperator(new ILconstInt(7), () -> new ILconstInt(2)); + ILconst remainder = WurstOperator.MOD_INT.evaluateBinaryOperator(new ILconstInt(-7), () -> new ILconstInt(3)); + ILconst realRemainder = WurstOperator.MOD_REAL.evaluateBinaryOperator(new ILconstReal(-7.5f), + () -> new ILconstReal(3f)); + assertEquals(((ILconstInt) sum).getVal(), 5); + assertEquals(((ILconstInt) quotient).getVal(), 3); + assertEquals(((ILconstInt) remainder).getVal(), 2); + assertEquals(((ILconstReal) realRemainder).getVal(), 1.5f, 0.0001f); + } + + @Test + public void unaryEvaluationAndLazyClassificationAreConsistent() { + assertTrue(WurstOperator.AND.isLazy()); + assertTrue(WurstOperator.OR.isLazy()); + assertFalse(WurstOperator.PLUS.isLazy()); + assertFalse(((ILconstBool) WurstOperator.NOT.evaluateUnaryOperator(ILconstBool.instance(true))).getVal()); + assertEquals(((ILconstInt) WurstOperator.UNARY_MINUS.evaluateUnaryOperator(new ILconstInt(4))).getVal(), -4); + } +} From e8b5d030895c410dd22fcda4493d184740b5f94e Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 08:48:50 +0200 Subject: [PATCH 3/9] Expand core utility coverage --- .../java/tests/utils/CoreUtilitiesTests.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 de.peeeq.wurstscript/src/test/java/tests/utils/CoreUtilitiesTests.java diff --git a/de.peeeq.wurstscript/src/test/java/tests/utils/CoreUtilitiesTests.java b/de.peeeq.wurstscript/src/test/java/tests/utils/CoreUtilitiesTests.java new file mode 100644 index 000000000..ae4e581ed --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/utils/CoreUtilitiesTests.java @@ -0,0 +1,62 @@ +package tests.utils; + +import de.peeeq.wurstscript.utils.Lazy; +import de.peeeq.wurstscript.utils.NotNullList; +import de.peeeq.wurstscript.utils.Pair; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.expectThrows; + +public class CoreUtilitiesTests { + @Test + public void lazySupplierRunsExactlyOnceIncludingNullValues() { + AtomicInteger calls = new AtomicInteger(); + Lazy lazy = Lazy.create(() -> { + calls.incrementAndGet(); + return null; + }); + + assertEquals(lazy.get(), null); + assertEquals(lazy.get(), null); + assertEquals(calls.get(), 1); + } + + @Test + public void notNullListRejectsNullThroughEveryMutationPath() { + NotNullList values = new NotNullList<>(); + values.add("a"); + values.add(1, "b"); + values.addAll(List.of("c", "d")); + values.addAll(1, List.of("x")); + values.set(0, "z"); + assertEquals(values, List.of("z", "x", "b", "c", "d")); + + expectThrows(IllegalArgumentException.class, () -> values.add(null)); + expectThrows(IllegalArgumentException.class, () -> values.add(0, null)); + expectThrows(IllegalArgumentException.class, () -> values.addAll(Arrays.asList("ok", null))); + expectThrows(IllegalArgumentException.class, () -> values.addAll(0, Arrays.asList((String) null))); + expectThrows(IllegalArgumentException.class, () -> values.set(0, null)); + } + + @Test + public void pairProvidesValueEqualityAndAccessors() { + Pair first = Pair.create("value", 7); + Pair equal = Pair.create("value", 7); + Pair different = Pair.create("other", 7); + + assertEquals(first.getA(), "value"); + assertEquals(first.getB(), 7); + assertEquals(first.toString(), "(value, 7)"); + assertEquals(first, equal); + assertEquals(first.hashCode(), equal.hashCode()); + assertFalse(first.equals(different)); + assertEquals(Pair.create(null, 7), Pair.create(null, 7)); + assertFalse(first.equals("value")); + } +} From 14958ea86add4c2fd48bfbe8fab36e4b7f9c4b31 Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 08:53:36 +0200 Subject: [PATCH 4/9] Cover parser and diagnostic core paths --- .../attributes/ErrorHandlerTests.java | 50 +++++++++++++++++++ .../wurstscript/tests/WurstOperatorTests.java | 19 +++++++ .../wurstscript/tests/WurstParserTests.java | 37 ++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 de.peeeq.wurstscript/src/test/java/de/peeeq/wurstscript/attributes/ErrorHandlerTests.java create mode 100644 de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstParserTests.java diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstscript/attributes/ErrorHandlerTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstscript/attributes/ErrorHandlerTests.java new file mode 100644 index 000000000..85a7aa28a --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstscript/attributes/ErrorHandlerTests.java @@ -0,0 +1,50 @@ +package de.peeeq.wurstscript.attributes; + +import de.peeeq.wurstscript.gui.WurstGuiCliImpl; +import de.peeeq.wurstscript.parser.WPos; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +public class ErrorHandlerTests { + @Test + public void tracksErrorsWarningsAndPerFileBuckets() { + WurstGuiCliImpl gui = new WurstGuiCliImpl(); + ErrorHandler handler = new ErrorHandler(gui); + CompileError error = new CompileError(new WPos("one.wurst", null, 1, 1), "bad"); + CompileError warning = new CompileError(new WPos("one.wurst", null, 2, 1), "careful", + CompileError.ErrorType.WARNING); + + handler.sendError(error); + handler.sendError(warning); + + assertEquals(handler.getErrorCount(), 1); + assertEquals(handler.getErrors(), java.util.List.of(error)); + assertEquals(handler.getWarnings(), java.util.List.of(warning)); + assertEquals(handler.getBucketForFile("one.wurst", CompileError.ErrorType.ERROR), java.util.List.of(error)); + assertEquals(handler.getBucketForFile("one.wurst", CompileError.ErrorType.WARNING), java.util.List.of(warning)); + assertEquals(gui.getErrorList().size(), 1); + + handler.removeFromGlobal(error); + handler.removeFromGlobal(warning); + assertTrue(handler.getErrors().isEmpty()); + assertTrue(handler.getWarnings().isEmpty()); + assertEquals(handler.getBucketForFile("one.wurst", CompileError.ErrorType.ERROR), null); + assertEquals(handler.getBucketForFile("one.wurst", CompileError.ErrorType.WARNING), null); + } + + @Test + public void exposesGuiAndUnitTestMode() { + WurstGuiCliImpl gui = new WurstGuiCliImpl(); + ErrorHandler handler = new ErrorHandler(gui); + + assertSame(handler.getGui(), gui); + assertFalse(handler.isUnitTestMode()); + handler.enableUnitTestMode(); + assertTrue(handler.isUnitTestMode()); + assertFalse(handler.isOutputTestSource()); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java index 6e27e28d8..7cfc7c5b2 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstOperatorTests.java @@ -9,7 +9,9 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.expectThrows; public class WurstOperatorTests { @Test @@ -48,4 +50,21 @@ public void unaryEvaluationAndLazyClassificationAreConsistent() { assertFalse(((ILconstBool) WurstOperator.NOT.evaluateUnaryOperator(ILconstBool.instance(true))).getVal()); assertEquals(((ILconstInt) WurstOperator.UNARY_MINUS.evaluateUnaryOperator(new ILconstInt(4))).getVal(), -4); } + + @Test + public void operatorMetadataAndBackendMappingsAreExplicit() { + assertTrue(WurstOperator.PLUS.isBinaryOp()); + assertFalse(WurstOperator.PLUS.isUnaryOp()); + assertTrue(WurstOperator.NOT.isUnaryOp()); + assertFalse(WurstOperator.NOT.isBinaryOp()); + assertEquals(WurstOperator.DIV_REAL.toString(), "/"); + assertEquals(WurstOperator.PLUS.getOverloadingFuncName(), "op_plus"); + assertNotNull(WurstOperator.DIV_INT.jassTranslateBinary()); + assertNotNull(WurstOperator.MOD_REAL.luaTranslateBinary()); + assertNotNull(WurstOperator.UNARY_MINUS.jassTranslateUnary()); + assertNotNull(WurstOperator.NOT.jassTranslateUnary()); + expectThrows(Error.class, () -> WurstOperator.MOD_INT.jassTranslateBinary()); + expectThrows(Error.class, () -> WurstOperator.MOD_INT.luaTranslateBinary()); + expectThrows(Error.class, () -> WurstOperator.PLUS.jassTranslateUnary()); + } } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstParserTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstParserTests.java new file mode 100644 index 000000000..a3f849b4e --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WurstParserTests.java @@ -0,0 +1,37 @@ +package tests.wurstscript.tests; + +import de.peeeq.wurstscript.WurstParser; +import de.peeeq.wurstscript.attributes.ErrorHandler; +import de.peeeq.wurstscript.gui.WurstGuiCliImpl; +import org.testng.annotations.Test; + +import java.io.StringReader; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class WurstParserTests { + @Test + public void parsesWurstJassAndJurstCompilationUnits() { + WurstGuiCliImpl gui = new WurstGuiCliImpl(); + ErrorHandler errors = new ErrorHandler(gui); + WurstParser parser = new WurstParser(errors, gui); + String jassFunction = "function foo takes nothing returns nothing\nendfunction\n"; + + assertNotNull(parser.parse(new StringReader("package Demo\n"), "demo.wurst", false)); + assertNotNull(parser.parseJass(new StringReader(jassFunction), "demo.j", false)); + assertNotNull(parser.parseJurst(new StringReader(jassFunction), "demo.jurst", false)); + assertEquals(errors.getErrorCount(), 0); + } + + @Test + public void canLeaveSyntacticSugarForDownstreamInspection() { + WurstGuiCliImpl gui = new WurstGuiCliImpl(); + ErrorHandler errors = new ErrorHandler(gui); + WurstParser parser = new WurstParser(errors, gui); + parser.setRemoveSugar(false); + + assertNotNull(parser.parse(new StringReader("package Demo\nfunction foo()\n"), "demo.wurst", false)); + assertEquals(errors.getErrorCount(), 0); + } +} From c20fb381b491f0581e3e89e6f96380c0d92368cc Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 09:02:53 +0200 Subject: [PATCH 5/9] test: cover run argument parsing and quiet CI output --- .github/workflows/build.yml | 5 +- de.peeeq.wurstscript/build.gradle | 6 ++ .../tests/wurstscript/tests/RunArgsTests.java | 83 +++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e82c2134..e7e8eab5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,7 @@ jobs: JAVA_TOOL_OPTIONS: >- -XX:+UseCompactObjectHeaders -XX:+UseStringDeduplication + --enable-native-access=ALL-UNNAMED GRADLE_OPTS: -Dorg.gradle.daemon=false defaults: run: @@ -119,9 +120,9 @@ jobs: shell: bash run: | if [[ "${{ runner.os }}" == "Linux" ]]; then - ./gradlew test jacocoTestReport --no-daemon --stacktrace + ./gradlew test jacocoTestReport --no-daemon --stacktrace --quiet else - ./gradlew test --no-daemon --stacktrace + ./gradlew test --no-daemon --stacktrace --quiet fi - name: Upload coverage to Coveralls diff --git a/de.peeeq.wurstscript/build.gradle b/de.peeeq.wurstscript/build.gradle index 29d1ede8f..58c0cdd17 100644 --- a/de.peeeq.wurstscript/build.gradle +++ b/de.peeeq.wurstscript/build.gradle @@ -298,6 +298,12 @@ tasks.register('ensureStdLib', JavaExec) { test { dependsOn 'ensureStdLib' useTestNG() + // Keep CI output focused on failures; compiler/runtime tests intentionally exercise native + // print paths and can otherwise flood the log with expected diagnostic output. + testLogging { + showStandardStreams = false + events 'failed', 'skipped' + } // The suite is a few thousand independent compilations and was running one at a time, so it // took as long as the sum of them. Forks rather than threads: the harness keeps state in diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java new file mode 100644 index 000000000..19586f499 --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java @@ -0,0 +1,83 @@ +package tests.wurstscript.tests; + +import de.peeeq.wurstscript.RunArgs; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.Set; + +public class RunArgsTests { + + @Test + public void parsesFlagsAndValues() { + RunArgs args = new RunArgs( + "-lua", "-opt", "-inline", "-localOptimizations", "-runcompiletimefunctions", + "-testTimeout=7", "-testFilter", "Foo", "-functionSplitLimit", "42", + "-workspaceroot", "project", "-inputmap=source.w3x", "-out", "output.j", + "-lib", "lib", "source.w3x", "helper.wurst"); + + Assert.assertTrue(args.isLua()); + Assert.assertTrue(args.isOptimize()); + Assert.assertTrue(args.isInline()); + Assert.assertTrue(args.isLocalOptimizations()); + Assert.assertTrue(args.runCompiletimeFunctions()); + Assert.assertEquals(args.getTestTimeout(), 7); + Assert.assertEquals(args.getTestFilter().orElseThrow(), "Foo"); + Assert.assertEquals(args.getFunctionSplitLimit(), 42); + Assert.assertEquals(args.getWorkspaceroot(), "project"); + Assert.assertEquals(args.getInputmap(), "source.w3x"); + Assert.assertEquals(args.getOutFile(), "output.j"); + Assert.assertEquals(args.getMapFile(), "source.w3x"); + Assert.assertEquals(args.getFiles(), java.util.List.of("source.w3x", "helper.wurst")); + Assert.assertEquals(args.getAdditionalLibDirs(), java.util.List.of(new File("lib"))); + } + + @Test + public void exposesModeAndSafetyFlags() { + RunArgs args = new RunArgs("-build", "-dev", "-stacktraces", "-nodebug", "-uncheckedDispatch", + "-injectobjects", "-hotreload", "-hotstart", "-noPJass", "-legacyJassChecks", + "-compactOutput", "-measure", "-compiletimeCache", "-noExtractMapScript", "-copyMap", + "-prettyPrint", "-languageServer", "-languageServerAppCdsTrain"); + + Assert.assertTrue(args.isBuild()); + Assert.assertTrue(args.isDevBuild()); + Assert.assertTrue(args.isIncludeStacktraces()); + Assert.assertTrue(args.isNoDebugMessages()); + Assert.assertTrue(args.isUncheckedDispatch()); + Assert.assertFalse(args.isInjectObjects(), "hot reload suppresses object injection"); + Assert.assertTrue(args.isHotReload()); + Assert.assertTrue(args.isHotStartmap()); + Assert.assertTrue(args.isDisablePjass()); + Assert.assertTrue(args.isLegacyJassTypeChecks()); + Assert.assertTrue(args.isCompactOutput()); + Assert.assertTrue(args.isMeasureTimes()); + Assert.assertTrue(args.isCompiletimeCache()); + Assert.assertTrue(args.isNoExtractMapScript()); + Assert.assertTrue(args.isCopyMap()); + Assert.assertTrue(args.isPrettyPrint()); + Assert.assertTrue(args.isLanguageServer()); + Assert.assertTrue(args.isLanguageServerAppCdsTrain()); + } + + @Test + public void supportsProgrammaticMutationAndAdditionalLibraries() { + RunArgs args = new RunArgs(); + args.setMapFile("map.w3x"); + args.setLegacyJassTypeChecks(true); + args.addLibs(Set.of("one", "two")); + args.addLibDirs(Set.of(new File("three"))); + + Assert.assertEquals(args.getMapFile(), "map.w3x"); + Assert.assertTrue(args.isLegacyJassTypeChecks()); + Assert.assertEquals(args.getAdditionalLibDirs().size(), 3); + Assert.assertThrows(() -> args.getAdditionalLibDirs().add(new File("four")), UnsupportedOperationException.class); + } + + @Test + public void rejectsUnknownAndMissingArguments() { + Assert.assertThrows(() -> new RunArgs("-doesNotExist"), RuntimeException.class); + Assert.assertThrows(() -> new RunArgs("-out="), RuntimeException.class); + Assert.assertThrows(() -> new RunArgs("-out"), ArrayIndexOutOfBoundsException.class); + } +} From d5f734f74975a1c700d38679a28b547ea5841eb5 Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 09:03:56 +0200 Subject: [PATCH 6/9] test: cover core utility helpers --- .../tests/wurstscript/utils/UtilsTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java index e1e0af712..f79f20472 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Arrays; public class UtilsTest { @@ -112,6 +113,36 @@ public void joinArrays() { Assert.assertEquals(ar3, Utils.joinArrays(ar1, ar2)); } + @Test + public void pureUtilityHelpers() { + Assert.assertEquals(Utils.size(Arrays.asList("a", "b")), 2); + StringBuilder indented = new StringBuilder(); + Utils.printIndent(indented, 2); + Assert.assertEquals(indented.toString(), "\t\t"); + Assert.assertEquals(Utils.removedDuplicates(Arrays.asList("a", "b", "a")), Arrays.asList("a", "b")); + Assert.assertEquals(Utils.parseInt("123"), 123); + Assert.assertEquals(Utils.parseAsciiInt("'a'"), (int) 'a'); + Assert.assertEquals(Utils.parseOctalInt("17"), 15); + Assert.assertEquals(Utils.parseHexInt("ff", 0), 255); + Assert.assertEquals(Utils.printSep(",", new String[]{"a", "b"}), "a,b"); + Assert.assertTrue(Utils.oneOf("b", "a", "b")); + Assert.assertEquals(Utils.getFirst(Arrays.asList("a", "b")), "a"); + Assert.assertEquals(Utils.getLast(Arrays.asList("a", "b")), "b"); + Assert.assertEquals(Utils.inBorders(0, 5, 3), 3); + Assert.assertEquals(Utils.toFirstUpper("hello"), "Hello"); + Assert.assertEquals(Utils.escapeStringWithoutQuotes("a\n"), "a\\n"); + Assert.assertEquals(Utils.fileName("dir/file.wurst"), "file.wurst"); + Assert.assertEquals(Utils.stripHtml("x"), "x"); + Assert.assertEquals(Utils.repeat('x', 3), "xxx"); + Assert.assertTrue(Utils.isWurstFile("test.wurst")); + Assert.assertTrue(Utils.isWurstFile(new File("test.wurst"))); + Assert.assertEquals(Utils.subList(Arrays.asList(1, 2, 3), 1), Arrays.asList(2, 3)); + Assert.assertEquals(Utils.subList(Arrays.asList(1, 2, 3), 0, 2), Arrays.asList(1, 2)); + Assert.assertEquals(Utils.init(Arrays.asList(1, 2, 3)), Arrays.asList(1, 2)); + Assert.assertEquals(Utils.makeUniqueName("x", n -> n.equals("x_2")), "x_2"); + Assert.assertEquals(Utils.makeUniqueName("x", n -> !n.equals("x") && !n.equals("x_1")), "x_2"); + } + /* TODO utils unit tests @Test public void isJassCode() { From 7a7bb43db92bbb029e59460480b3b3b86082644e Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 09:07:57 +0200 Subject: [PATCH 7/9] fix: correct coverage test compilation --- .../test/java/tests/wurstscript/tests/RunArgsTests.java | 8 ++++---- .../src/test/java/tests/wurstscript/utils/UtilsTest.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java index 19586f499..69b81f4f5 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java @@ -71,13 +71,13 @@ public void supportsProgrammaticMutationAndAdditionalLibraries() { Assert.assertEquals(args.getMapFile(), "map.w3x"); Assert.assertTrue(args.isLegacyJassTypeChecks()); Assert.assertEquals(args.getAdditionalLibDirs().size(), 3); - Assert.assertThrows(() -> args.getAdditionalLibDirs().add(new File("four")), UnsupportedOperationException.class); + Assert.assertThrows(UnsupportedOperationException.class, () -> args.getAdditionalLibDirs().add(new File("four"))); } @Test public void rejectsUnknownAndMissingArguments() { - Assert.assertThrows(() -> new RunArgs("-doesNotExist"), RuntimeException.class); - Assert.assertThrows(() -> new RunArgs("-out="), RuntimeException.class); - Assert.assertThrows(() -> new RunArgs("-out"), ArrayIndexOutOfBoundsException.class); + Assert.assertThrows(RuntimeException.class, () -> new RunArgs("-doesNotExist")); + Assert.assertThrows(RuntimeException.class, () -> new RunArgs("-out=")); + Assert.assertThrows(ArrayIndexOutOfBoundsException.class, () -> new RunArgs("-out")); } } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java index f79f20472..756caf15e 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java @@ -15,6 +15,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Arrays; +import java.io.File; public class UtilsTest { From 71b52d19ec5628dffba949b01c9d940113d9e883 Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 09:16:41 +0200 Subject: [PATCH 8/9] test: cover worklist and deprecate legacy frontends --- .../frotty/jassAttributes/package-info.java | 6 +++- .../frotty/jassValidator/package-info.java | 6 +++- .../peeeq/wurstscript/jurst/package-info.java | 6 +++- .../wurstscript/tests/WorklistTests.java | 34 +++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WorklistTests.java diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassAttributes/package-info.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassAttributes/package-info.java index 0b5f2a9b1..472c80d31 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassAttributes/package-info.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassAttributes/package-info.java @@ -1,3 +1,7 @@ +/** + * Legacy Jass attribute support. + * @deprecated This internal compatibility package is planned for removal. + */ @org.eclipse.jdt.annotation.NonNullByDefault +@Deprecated package de.peeeq.wurstscript.frotty.jassAttributes; - diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassValidator/package-info.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassValidator/package-info.java index 26ade3552..0d0f78fbb 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassValidator/package-info.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/frotty/jassValidator/package-info.java @@ -1,3 +1,7 @@ +/** + * Legacy Jass validation support. + * @deprecated This internal compatibility package is planned for removal. + */ @org.eclipse.jdt.annotation.NonNullByDefault +@Deprecated package de.peeeq.wurstscript.frotty.jassValidator; - diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/jurst/package-info.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/jurst/package-info.java index 40195a446..8dc087830 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/jurst/package-info.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/jurst/package-info.java @@ -1,3 +1,7 @@ +/** + * Legacy Jurst compatibility frontend. Prefer Wurst source for new code. + * @deprecated Jurst is retained only for compatibility and is planned for removal. + */ @org.eclipse.jdt.annotation.NonNullByDefault +@Deprecated package de.peeeq.wurstscript.jurst; - diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WorklistTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WorklistTests.java new file mode 100644 index 000000000..3d3bf456f --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/WorklistTests.java @@ -0,0 +1,34 @@ +package tests.wurstscript.tests; + +import de.peeeq.datastructures.Worklist; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; + +public class WorklistTests { + + @Test + public void deduplicatesPendingItemsAndAllowsRequeueAfterPolling() { + Worklist worklist = new Worklist<>(List.of(1, 2, 1)); + Assert.assertEquals(worklist.size(), 2); + Assert.assertFalse(worklist.isEmpty()); + Assert.assertEquals(worklist.poll().intValue(), 1); + worklist.add(2); // still pending, must not be duplicated + worklist.add(3); + Assert.assertEquals(worklist.poll().intValue(), 2); + worklist.add(1); // already consumed, so requeueing is valid + Assert.assertEquals(worklist.poll().intValue(), 3); + Assert.assertEquals(worklist.poll().intValue(), 1); + Assert.assertTrue(worklist.isEmpty()); + } + + @Test + public void addAllMaintainsFifoOrder() { + Worklist worklist = new Worklist<>(); + worklist.addAll(List.of("a", "b", "a")); + Assert.assertEquals(worklist.poll(), "a"); + Assert.assertEquals(worklist.poll(), "b"); + Assert.assertTrue(worklist.isEmpty()); + } +} From 43c90b904a07bc018800cc96df2708b7f6ca39ca Mon Sep 17 00:00:00 2001 From: Frotty Date: Sun, 30 Aug 2026 09:17:30 +0200 Subject: [PATCH 9/9] fix: align coverage tests with existing APIs --- .../src/test/java/tests/wurstscript/tests/RunArgsTests.java | 3 +-- .../src/test/java/tests/wurstscript/utils/UtilsTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java index 69b81f4f5..d16557160 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunArgsTests.java @@ -37,7 +37,7 @@ public void parsesFlagsAndValues() { public void exposesModeAndSafetyFlags() { RunArgs args = new RunArgs("-build", "-dev", "-stacktraces", "-nodebug", "-uncheckedDispatch", "-injectobjects", "-hotreload", "-hotstart", "-noPJass", "-legacyJassChecks", - "-compactOutput", "-measure", "-compiletimeCache", "-noExtractMapScript", "-copyMap", + "-compactOutput", "-measure", "-compiletimeCache", "-noExtractMapScript", "-prettyPrint", "-languageServer", "-languageServerAppCdsTrain"); Assert.assertTrue(args.isBuild()); @@ -54,7 +54,6 @@ public void exposesModeAndSafetyFlags() { Assert.assertTrue(args.isMeasureTimes()); Assert.assertTrue(args.isCompiletimeCache()); Assert.assertTrue(args.isNoExtractMapScript()); - Assert.assertTrue(args.isCopyMap()); Assert.assertTrue(args.isPrettyPrint()); Assert.assertTrue(args.isLanguageServer()); Assert.assertTrue(args.isLanguageServerAppCdsTrain()); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java index 756caf15e..2176425ae 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/utils/UtilsTest.java @@ -138,7 +138,7 @@ public void pureUtilityHelpers() { Assert.assertTrue(Utils.isWurstFile("test.wurst")); Assert.assertTrue(Utils.isWurstFile(new File("test.wurst"))); Assert.assertEquals(Utils.subList(Arrays.asList(1, 2, 3), 1), Arrays.asList(2, 3)); - Assert.assertEquals(Utils.subList(Arrays.asList(1, 2, 3), 0, 2), Arrays.asList(1, 2)); + Assert.assertEquals(Utils.subList(Arrays.asList(1, 2, 3), 0, 1), Arrays.asList(1, 2)); Assert.assertEquals(Utils.init(Arrays.asList(1, 2, 3)), Arrays.asList(1, 2)); Assert.assertEquals(Utils.makeUniqueName("x", n -> n.equals("x_2")), "x_2"); Assert.assertEquals(Utils.makeUniqueName("x", n -> !n.equals("x") && !n.equals("x_1")), "x_2");