• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wurstscript / WurstScript / 203

18 Oct 2023 02:20PM UTC coverage: 63.758% (+0.3%) from 63.447%
203

push

circleci

web-flow
Update deps, improve performance, JHCR fixes (#1080)

- update dependencies
- update stdlib verison for unit tests
- only apply nullsetting when `-opt` is enabled to save some build time for testing
- minor performance improvements
- make build deterministic
- apply build map config before compilation
- hot code reload now more reliable

17246 of 27049 relevant lines covered (63.76%)

0.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

36.5
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunTests.java
1
package de.peeeq.wurstio.languageserver.requests;
2

3
import com.google.common.base.Preconditions;
4
import com.google.common.collect.Lists;
5
import config.WurstProjectBuildMapData;
6
import config.WurstProjectConfigData;
7
import de.peeeq.wurstio.CompiletimeFunctionRunner;
8
import de.peeeq.wurstio.jassinterpreter.InterpreterException;
9
import de.peeeq.wurstio.jassinterpreter.ReflectionNativeProvider;
10
import de.peeeq.wurstio.languageserver.ModelManager;
11
import de.peeeq.wurstio.languageserver.WFile;
12
import de.peeeq.wurstscript.RunArgs;
13
import de.peeeq.wurstscript.WLogger;
14
import de.peeeq.wurstscript.ast.*;
15
import de.peeeq.wurstscript.attributes.CompileError;
16
import de.peeeq.wurstscript.gui.WurstGui;
17
import de.peeeq.wurstscript.intermediatelang.interpreter.ILInterpreter;
18
import de.peeeq.wurstscript.intermediatelang.interpreter.ProgramState;
19
import de.peeeq.wurstscript.intermediatelang.interpreter.ProgramState.StackTrace;
20
import de.peeeq.wurstscript.jassIm.ImFunction;
21
import de.peeeq.wurstscript.jassIm.ImProg;
22
import de.peeeq.wurstscript.jassinterpreter.TestFailException;
23
import de.peeeq.wurstscript.jassinterpreter.TestSuccessException;
24
import de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum;
25
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
26
import de.peeeq.wurstscript.utils.Utils;
27
import org.eclipse.jdt.annotation.Nullable;
28
import org.eclipse.lsp4j.MessageType;
29

30
import java.io.*;
31
import java.util.ArrayList;
32
import java.util.List;
33
import java.util.Optional;
34
import java.util.concurrent.*;
35

36
import static de.peeeq.wurstio.CompiletimeFunctionRunner.FunctionFlagToRun.CompiletimeFunctions;
37

38
/**
39
 * Created by peter on 05.05.16.
40
 */
41
public class RunTests extends UserRequest<Object> {
42

43
    private final Optional<WFile> filename;
44
    private final int line;
45
    private final int column;
46
    private final Optional<String> testName;
47
    private final int timeoutSeconds;
48

49
    private List<ImFunction> successTests = Lists.newArrayList();
1✔
50
    private List<TestFailure> failTests = Lists.newArrayList();
1✔
51

52
    private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
1✔
53

54
    static public class TestFailure {
55

56
        private ImFunction function;
57
        private final StackTrace stackTrace;
58
        private final String message;
59

60
        public TestFailure(ImFunction function, StackTrace stackTrace, String message) {
×
61
            Preconditions.checkNotNull(function);
×
62
            Preconditions.checkNotNull(stackTrace);
×
63
            Preconditions.checkNotNull(message);
×
64
            this.function = function;
×
65
            this.stackTrace = stackTrace;
×
66
            this.message = message;
×
67
        }
×
68

69
        public StackTrace getStackTrace() {
70
            return stackTrace;
×
71
        }
72

73
        public String getMessage() {
74
            return message;
×
75
        }
76

77
        public ImFunction getFunction() {
78
            return function;
×
79
        }
80

81
        public String getMessageWithStackFrame() {
82
            StringBuilder s = new StringBuilder(message);
×
83
            s.append("\n");
×
84
            stackTrace.appendTo(s);
×
85
            return s.toString();
×
86
        }
87

88
    }
89

90
    public RunTests(Optional<String> filename, int line, int column, Optional<String> testName) {
91
        this(filename, line, column, testName, 20);
1✔
92
    }
1✔
93

94
    public RunTests(Optional<String> filename, int line, int column, Optional<String> testName, int timeoutSeconds) {
1✔
95
        this.filename = filename.map(WFile::create);
1✔
96
        this.line = line;
1✔
97
        this.column = column;
1✔
98
        this.testName = testName;
1✔
99
        this.timeoutSeconds = timeoutSeconds;
1✔
100
    }
1✔
101

102

103
    @Override
104
    public Object execute(ModelManager modelManager) {
105
        if (modelManager.hasErrors()) {
×
106
            throw new RequestFailedException(MessageType.Error, "Fix errors in your code before running tests.\n" + modelManager.getFirstErrorDescription());
×
107
        }
108

109
        WLogger.info("Starting tests " + filename + ", " + line + ", " + column);
×
110
        println("Running unit tests..\n");
×
111

112
        Optional<CompilationUnit> cu = filename.map(modelManager::getCompilationUnit);
×
113
        WLogger.info("test.cu = " + Utils.printElement(cu));
×
114
        Optional<FuncDef> funcToTest = getFunctionToTest(cu);
×
115
        WLogger.info("test.funcToTest = " + Utils.printElement(funcToTest));
×
116

117

118
        ImTranslator translator = translateProg(modelManager);
×
119
        ImProg imProg = translator.getImProg();
×
120
        if (imProg == null) {
×
121
            println("Could not run tests, because program did not compile.\n");
×
122
            return "Could not translate program";
×
123
        }
124

125
        runTests(translator, imProg, funcToTest, cu);
×
126
        return "ok";
×
127
    }
128

129
    public static class TestResult {
130

131
        private final int passedTests;
132
        private final int totalTests;
133

134
        public TestResult(int passedTests, int totalTests) {
1✔
135
            this.passedTests = passedTests;
1✔
136
            this.totalTests = totalTests;
1✔
137
        }
1✔
138

139
        public int getPassedTests() {
140
            return passedTests;
1✔
141
        }
142

143
        public int getTotalTests() {
144
            return totalTests;
1✔
145
        }
146

147
    }
148

149
    public TestResult runTests(ImTranslator translator, ImProg imProg, Optional<FuncDef> funcToTest, Optional<CompilationUnit> cu) {
150
        WurstGui gui = new TestGui();
1✔
151

152
        CompiletimeFunctionRunner cfr = new CompiletimeFunctionRunner(translator, imProg, Optional.empty(), null, gui,
1✔
153
            CompiletimeFunctions, new WurstProjectConfigData(), false, false);
154
        ILInterpreter interpreter = cfr.getInterpreter();
1✔
155
        ProgramState globalState = cfr.getGlobalState();
1✔
156
        if (globalState == null) {
1✔
157
            globalState = new ProgramState(gui, imProg, true);
×
158
        }
159
        if (interpreter == null) {
1✔
160
            interpreter = new ILInterpreter(imProg, gui, Optional.empty(), globalState, false);
×
161
            interpreter.addNativeProvider(new ReflectionNativeProvider(interpreter));
×
162
        }
163

164
        redirectInterpreterOutput(globalState);
1✔
165

166
        // first run compiletime functions
167
        cfr.run();
1✔
168

169
        if (gui.getErrorCount() > 0) {
1✔
170
            for (CompileError compileError : gui.getErrorList()) {
×
171
                println(compileError.toString());
×
172
            }
×
173
            println("There were some problem while running compiletime expressions and functions.");
×
174
            return new TestResult(0, 1);
×
175
        }
176

177
        WLogger.info("Ran compiletime functions");
1✔
178

179

180
        for (ImFunction f : imProg.getFunctions()) {
1✔
181
            if (f.hasFlag(FunctionFlagEnum.IS_TEST)) {
1✔
182
                Element trace = f.attrTrace();
1✔
183

184
                if (cu.isPresent() && !Utils.elementContained(Optional.of(trace), cu.get())) {
1✔
185
                    continue;
×
186
                }
187
                if (funcToTest.isPresent() && trace != funcToTest.get()) {
1✔
188
                    continue;
×
189
                }
190

191

192
                String message = "Running <" + f.attrTrace().attrNearestPackage().tryGetNameDef().getName() + ":"
1✔
193
                        + f.attrTrace().attrErrorPos().getLine() + " - " + f.getName() + ">..";
1✔
194
                println(message);
1✔
195
                WLogger.info(message);
1✔
196
                try {
197
                    @Nullable ILInterpreter finalInterpreter = interpreter;
1✔
198
                    Callable<Void> run = () -> {
1✔
199
                        finalInterpreter.runVoidFunc(f, null);
1✔
200
                        // each test must finish it's own timers (otherwise, we would get strange results)
201
                        finalInterpreter.completeTimers();
1✔
202
                        return null;
1✔
203
                    };
204
                    RunnableFuture<Void> future = new FutureTask<>(run);
1✔
205
                    if (service != null && !service.isShutdown()) {
1✔
206
                        service.shutdownNow();
1✔
207
                    }
208
                    service = Executors.newSingleThreadScheduledExecutor();
1✔
209
                    service.execute(future);
1✔
210
                    try {
211
                        future.get(timeoutSeconds, TimeUnit.SECONDS); // Wait 20 seconds for test to complete
1✔
212
                    } catch (TimeoutException ex) {
×
213
                        future.cancel(true);
×
214
                        throw new TestTimeOutException();
×
215
                    } catch (ExecutionException e) {
×
216
                        throw e.getCause();
×
217
                    }
1✔
218
                    service.shutdown();
1✔
219
                    service.awaitTermination(10, TimeUnit.SECONDS);
1✔
220
                    service = Executors.newSingleThreadScheduledExecutor();
1✔
221
                    if (gui.getErrorCount() > 0) {
1✔
222
                        StringBuilder sb = new StringBuilder();
×
223
                        for (CompileError error : gui.getErrorList()) {
×
224
                            sb.append(error.toString()).append("\n");
×
225
                            println(error.getMessage());
×
226
                        }
×
227
                        gui.clearErrors();
×
228
                        TestFailure failure = new TestFailure(f, interpreter.getStackFrames(), sb.toString());
×
229
                        failTests.add(failure);
×
230
                    } else {
×
231
                        successTests.add(f);
1✔
232
                        println("\tOK!");
1✔
233
                    }
234
                } catch (TestSuccessException e) {
×
235
                    successTests.add(f);
×
236
                    println("\tOK!");
×
237
                } catch (TestFailException e) {
×
238
                    TestFailure failure = new TestFailure(f, interpreter.getStackFrames(), e.getMessage());
×
239
                    failTests.add(failure);
×
240
                    println("\tFAILED assertion:");
×
241
                    println("\t" + failure.getMessageWithStackFrame());
×
242
                } catch (TestTimeOutException e) {
×
243
                    failTests.add(new TestFailure(f, interpreter.getStackFrames(), e.getMessage()));
×
244
                    println("\tFAILED - TIMEOUT (This test did not complete in " + timeoutSeconds + " seconds, it might contain an endless loop)");
×
245
                    println(interpreter.getStackFrames().toString());
×
246
                } catch (InterpreterException e) {
×
247
                    TestFailure failure = new TestFailure(f, interpreter.getStackFrames(), e.getMessage());
×
248
                    failTests.add(failure);
×
249
                    println("\t" + failure.getMessageWithStackFrame());
×
250
                } catch (Throwable e) {
×
251
                    failTests.add(new TestFailure(f, interpreter.getStackFrames(), e.toString()));
×
252
                    println("\tFAILED with exception: " + e.getClass() + " " + e.getLocalizedMessage());
×
253
                    println(interpreter.getStackFrames().toString());
×
254
                    println("Here are some compiler internals, that might help Wurst developers to debug this issue:");
×
255
                    StringWriter sw = new StringWriter();
×
256
                    PrintWriter pw = new PrintWriter(sw);
×
257
                    e.printStackTrace(pw);
×
258
                    String sStackTrace = sw.toString();
×
259
                    println("\t" + e.getLocalizedMessage());
×
260
                    println("\t" + sStackTrace);
×
261
                }
1✔
262
            }
263
        }
1✔
264
        println("Tests succeeded: " + successTests.size() + "/" + (successTests.size() + failTests.size()));
1✔
265
        if (failTests.size() == 0) {
1✔
266
            println(">> All tests have passed successfully!");
1✔
267
        } else {
268
            println(">> " + failTests.size() + " Tests have failed!");
×
269
        }
270
        if (gui.getErrorCount() > 0) {
1✔
271
            println("There were some errors reported while running the tests.");
×
272
            for (CompileError error : gui.getErrorList()) {
×
273
                println(error.toString());
×
274
            }
×
275
        }
276

277
        WLogger.info("finished tests");
1✔
278
        return new TestResult(successTests.size(), successTests.size() + failTests.size());
1✔
279
    }
280

281

282
    private void redirectInterpreterOutput(ProgramState globalState) {
283
        OutputStream os = new OutputStream() {
1✔
284

285
            @Override
286
            public void write(int b) throws IOException {
287
                if (b > 0) {
×
288
                    println("" + (char) b);
×
289
                }
290
            }
×
291

292
            @Override
293
            public void write(byte b[], int off, int len) throws IOException {
294
                println(new String(b, off, len));
×
295
            }
×
296

297

298
        };
299
        globalState.setOutStream(new PrintStream(os));
1✔
300
    }
1✔
301

302
    protected void println(String message) {
303
        print(message);
1✔
304
        print(System.lineSeparator());
1✔
305
    }
1✔
306

307
    protected void print(String message) {
308
        System.err.print(message);
1✔
309
    }
1✔
310

311
    private ImTranslator translateProg(ModelManager modelManager) {
312
        // need to run compiletime functions for running unit tests
313
        RunArgs runArgs = new RunArgs("-runcompiletimefunctions");
×
314
        ImTranslator imTranslator = new ImTranslator(modelManager.getModel(), false, runArgs);
×
315
        // will ignore udg_ variables which are not found
316
        imTranslator.setEclipseMode(true);
×
317
        imTranslator.translateProg();
×
318
        return imTranslator;
×
319
    }
320

321

322
    private Optional<FuncDef> getFunctionToTest(Optional<CompilationUnit> maybeCu) {
323
        if (testName.isPresent()) {
×
324
            int dotPos = testName.get().indexOf(".");
×
325
            String packageName = testName.get().substring(0, dotPos);
×
326
            String funcName = testName.get().substring(dotPos+1);
×
327
            Optional<FuncDef> testFunc = maybeCu.flatMap(cu ->
×
328
                cu.getPackages()
×
329
                .stream()
×
330
                .filter(p -> p.getName().equals(packageName))
×
331
                .flatMap(p -> p.getElements().stream())
×
332
                .filter(e -> e instanceof FuncDef)
×
333
                .map(e -> (FuncDef) e)
×
334
                .filter(f -> f.hasAnnotation("@test"))
×
335
                .filter(f -> f.getName().equals(funcName))
×
336
                .findFirst()
×
337
            );
338

339
            if (testFunc.isPresent()) {
×
340
                return testFunc;
×
341
            }
342
        }
343
        if (!filename.isPresent() || !maybeCu.isPresent() || line < 0) {
×
344
            return Optional.empty();
×
345
        }
346
        Optional<Element> e = Utils.getAstElementAtPos(maybeCu.get(), line, column, false);
×
347
        while (e.isPresent()) {
×
348
            if (e.get() instanceof FuncDef) {
×
349
                return e.map(el -> (FuncDef) el);
×
350
            }
351
            e = e.flatMap(el -> Optional.ofNullable(el.getParent()));
×
352
        }
353
        return null;
×
354
    }
355

356
    public List<TestFailure> getFailTests() {
357
        return failTests;
×
358
    }
359

360
    public class TestGui extends WurstGui {
1✔
361

362
        @Override
363
        public void sendProgress(String whatsRunningNow) {
364
            // ignore
365
        }
1✔
366

367
        @Override
368
        public void sendFinished() {
369
            // ignore
370
        }
×
371

372
        @Override
373
        public void showInfoMessage(String message) {
374
            println(message + "\n");
×
375
        }
×
376

377

378
    }
379

380
    private static class TestTimeOutException extends Throwable {
381

382
        @Override
383
        public String getMessage() {
384
            return "test failed with timeout (This test did not complete in 20 seconds, it might contain an endless loop)";
×
385
        }
386

387
        @Override
388
        public String toString() {
389
            return super.toString();
×
390
        }
391
    }
392

393

394
    public static ScheduledExecutorService getService() {
395
        return service;
×
396
    }
397
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc