diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java index 566d97fb1..7d3cc78ec 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java @@ -40,7 +40,6 @@ public class ProgramStateIO extends ProgramState { private final Map> createdObjectDefinitionIds = Maps.newLinkedHashMap(); private int id = 0; private final Map objDefinitions = Maps.newLinkedHashMap(); - private PrintStream outStream = System.err; private @Nullable WTS trigStrings = null; private final Optional mapFile; @@ -946,13 +945,4 @@ private Optional getObjectEditingOutputFolder() { return folder; } - @Override - public PrintStream getOutStream() { - return outStream; - } - - @Override - public void setOutStream(PrintStream os) { - outStream = os; - } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/ReflectionNativeProvider.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/ReflectionNativeProvider.java index e25e93af5..a4cf28654 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/ReflectionNativeProvider.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/ReflectionNativeProvider.java @@ -10,10 +10,14 @@ import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; public class ReflectionNativeProvider implements NativesProvider { private final HashMap methodMap = new HashMap<>(); + /** Providers that print to the interpreter output, so that redirecting it reaches them. */ + private final List outputProviders = new ArrayList<>(); public ReflectionNativeProvider(AbstractInterpreter interpreter) { addProvider(new AbilityProvider(interpreter)); @@ -51,6 +55,9 @@ public NativeJassFunction getFunctionPair(String funcName) { } private void addProvider(Provider provider) { + if (provider instanceof OutputProvider) { + outputProviders.add((OutputProvider) provider); + } for (Method method : provider.getClass().getMethods()) { Implements annotation = method.getAnnotation(Implements.class); if (annotation != null) { @@ -103,6 +110,8 @@ public ILconst invoke(String funcname, ILconst[] args) throws NoSuchNativeExcept @Override public void setOutStream(PrintStream outStream) { - + for (OutputProvider provider : outputProviders) { + provider.setOutStream(outStream); + } } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/OutputProvider.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/OutputProvider.java index 4bccfaf72..083953019 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/OutputProvider.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/providers/OutputProvider.java @@ -18,6 +18,10 @@ public OutputProvider(AbstractInterpreter interpreter) { super(interpreter); } + public void setOutStream(PrintStream outStream) { + this.outStream = outStream; + } + public void DisplayTextToForce(IlConstHandle force, ILconstString msg) { outStream.println(msg.getVal()); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java index 61a62d37f..14adae103 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java @@ -29,6 +29,7 @@ import org.eclipse.lsp4j.MessageType; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; import java.util.concurrent.*; @@ -369,13 +370,15 @@ public void write(int b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { if (!compactOutput) { - println(new String(b, off, len)); + println(new String(b, off, len, StandardCharsets.UTF_8)); } } }; - globalState.setOutStream(new PrintStream(os)); + // autoflush, so that the output of a test is delivered while that test is running + // instead of being left in the buffer of the PrintStream + globalState.setOutStream(new PrintStream(os, true, StandardCharsets.UTF_8)); } private String qualifiedTestName(ImFunction f) { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunTestsOutputRedirectTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunTestsOutputRedirectTests.java new file mode 100644 index 000000000..55995800a --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/RunTestsOutputRedirectTests.java @@ -0,0 +1,79 @@ +package tests.wurstscript.tests; + +import de.peeeq.wurstio.WurstCompilerJassImpl; +import de.peeeq.wurstio.languageserver.requests.RunTests; +import de.peeeq.wurstscript.RunArgs; +import de.peeeq.wurstscript.ast.WurstModel; +import de.peeeq.wurstscript.gui.WurstGui; +import de.peeeq.wurstscript.gui.WurstGuiCliImpl; +import de.peeeq.wurstscript.jassIm.ImProg; +import de.peeeq.wurstscript.utils.Utils; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.Optional; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * Tests that RunTests.redirectInterpreterOutput actually captures what the tests print, + * instead of it going to System.err unnoticed. + */ +public class RunTestsOutputRedirectTests extends WurstScriptTest { + + private static final String[] PROGRAM = { + "package test", + "native testFail(string msg)", + "native println(string msg)", + "@test function passingTest()", + " println(\"output of the passing test\")", + "@test function failingTest()", + " println(\"output of the failing test\")", + " testFail(\"assertion message\")", + }; + + @Test + public void interpreterOutputIsRedirected() { + String output = runTests(false); + assertTrue(output.contains("output of the passing test"), output); + assertTrue(output.contains("output of the failing test"), output); + } + + @Test + public void interpreterOutputIsSuppressedWithCompactOutput() { + String output = runTests(true); + assertFalse(output.contains("output of the passing test"), output); + assertFalse(output.contains("output of the failing test"), output); + } + + @Test + public void testResultsAreStillReported() { + String output = runTests(false); + assertTrue(output.contains("Tests succeeded: 1/2"), output); + assertTrue(output.contains("assertion message"), output); + } + + private String runTests(boolean compactOutput) { + RunArgs runArgs = new RunArgs(); + WurstGui gui = new WurstGuiCliImpl(); + WurstCompilerJassImpl compiler = new WurstCompilerJassImpl(null, gui, null, runArgs); + WurstModel model = parseFiles(null, + Collections.singletonList(new CU("test", Utils.join(PROGRAM, "\n") + "\n")), false, compiler); + compiler.checkProg(model); + if (!gui.getErrorList().isEmpty()) { + throw gui.getErrorList().get(0); + } + ImProg imProg = compiler.translateProgToIm(model); + + StringBuilder output = new StringBuilder(); + RunTests runTests = new RunTests(Optional.empty(), 0, 0, Optional.empty(), 20, Optional.empty(), compactOutput) { + @Override + protected void print(String message) { + output.append(message); + } + }; + runTests.runTests(compiler.getImTranslator(), imProg, Optional.empty(), Optional.empty()); + return output.toString(); + } +}