From db4418ae1dff6f4818e9e5dbf294110c67c0dee7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 1 Oct 2023 21:37:04 +0200 Subject: [PATCH] Refactor test runner 1. Deprecate WhileClassCompilation annotation, introduce EachTestCompiledSeparately instead 2. Add annotations to enabled/disable tests for particular backends --- .../junit/BaseWebAssemblyPlatformSupport.java | 68 ++ .../org/teavm/junit/BrowserRunStrategy.java | 125 ++- .../org/teavm/junit/CPlatformSupport.java | 96 +++ .../org/teavm/junit/CompilePostProcessor.java | 23 + .../java/org/teavm/junit/CompileResult.java | 25 + .../junit/EachTestCompiledSeparately.java | 26 + .../org/teavm/junit/JSPlatformSupport.java | 150 ++++ .../java/org/teavm/junit/OnlyPlatform.java | 27 + .../java/org/teavm/junit/PropertyNames.java | 37 + .../java/org/teavm/junit/SkipPlatform.java | 27 + .../java/org/teavm/junit/TeaVMTestRunner.java | 767 +++--------------- .../junit/{RunKind.java => TestPlatform.java} | 10 +- .../org/teavm/junit/TestPlatformSupport.java | 186 +++++ .../main/java/org/teavm/junit/TestRun.java | 6 +- .../main/java/org/teavm/junit/TestUtil.java | 104 +++ .../org/teavm/junit/WasiPlatformSupport.java | 69 ++ .../junit/WebAssemblyPlatformSupport.java | 92 +++ .../teavm/junit/WholeClassCompilation.java | 1 + 18 files changed, 1187 insertions(+), 652 deletions(-) create mode 100644 tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/CompilePostProcessor.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/CompileResult.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/EachTestCompiledSeparately.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/OnlyPlatform.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/PropertyNames.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/SkipPlatform.java rename tools/junit/src/main/java/org/teavm/junit/{RunKind.java => TestPlatform.java} (86%) create mode 100644 tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/TestUtil.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/WasiPlatformSupport.java create mode 100644 tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java diff --git a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java new file mode 100644 index 000000000..5ebdc4367 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static org.teavm.junit.PropertyNames.SOURCE_DIRS; +import java.io.File; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.teavm.backend.wasm.WasmRuntimeType; +import org.teavm.backend.wasm.WasmTarget; +import org.teavm.backend.wasm.generate.DirectorySourceFileResolver; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.ReferenceCache; +import org.teavm.vm.TeaVM; + +abstract class BaseWebAssemblyPlatformSupport extends TestPlatformSupport { + public BaseWebAssemblyPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + super(classSource, referenceCache); + } + + @Override + String getExtension() { + return ".wasm"; + } + + protected abstract WasmRuntimeType getRuntimeType(); + + @Override + CompileResult compile(Consumer additionalProcessing, String baseName, + TeaVMTestConfiguration configuration, File path) { + Supplier targetSupplier = () -> { + WasmTarget target = new WasmTarget(); + target.setRuntimeType(getRuntimeType()); + var sourceDirs = System.getProperty(SOURCE_DIRS); + if (sourceDirs != null) { + var dirs = new ArrayList(); + for (var tokenizer = new StringTokenizer(sourceDirs, Character.toString(File.pathSeparatorChar)); + tokenizer.hasMoreTokens();) { + var dir = new File(tokenizer.nextToken()); + if (dir.isDirectory()) { + dirs.add(dir); + } + } + if (!dirs.isEmpty()) { + target.setSourceFileResolver(new DirectorySourceFileResolver(dirs)); + } + } + return target; + }; + return compile(configuration, targetSupplier, TestNativeEntryPoint.class.getName(), path, + ".wasm", null, false, additionalProcessing, baseName); + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java index f4ed56c6e..6b850ed97 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java @@ -15,11 +15,13 @@ */ package org.teavm.junit; +import static org.teavm.junit.PropertyNames.JS_DECODE_STACK; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -28,6 +30,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -38,6 +42,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Function; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -55,7 +60,7 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; class BrowserRunStrategy implements TestRunStrategy { - private boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true")); + private boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true")); private final File baseDir; private final String type; private final Function browserRunner; @@ -409,4 +414,122 @@ class BrowserRunStrategy implements TestRunStrategy { } } } + + static Process customBrowser(String url) { + System.out.println("Open link to run tests: " + url + "?logging=true"); + return null; + } + + static Process chromeBrowser(String url) { + return browserTemplate("chrome", url, (profile, params) -> { + addChromeCommand(params); + params.addAll(Arrays.asList( + "--headless", + "--disable-gpu", + "--remote-debugging-port=9222", + "--no-first-run", + "--user-data-dir=" + profile + )); + }); + } + + static Process firefoxBrowser(String url) { + return browserTemplate("firefox", url, (profile, params) -> { + addFirefoxCommand(params); + params.addAll(Arrays.asList( + "--headless", + "--profile", + profile + )); + }); + } + + private static void addChromeCommand(List params) { + if (isMacos()) { + params.add("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); + } else if (isWindows()) { + params.add("cmd.exe"); + params.add("start"); + params.add("/C"); + params.add("chrome"); + } else { + params.add("google-chrome-stable"); + } + } + + private static void addFirefoxCommand(List params) { + if (isMacos()) { + params.add("/Applications/Firefox.app/Contents/MacOS/firefox"); + return; + } + if (isWindows()) { + params.add("cmd.exe"); + params.add("/C"); + params.add("start"); + } + params.add("firefox"); + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().startsWith("windows"); + } + + private static boolean isMacos() { + return System.getProperty("os.name").toLowerCase().startsWith("mac"); + } + + private static Process browserTemplate(String name, String url, BiConsumer> paramsBuilder) { + File temp; + try { + temp = File.createTempFile("teavm", "teavm"); + temp.delete(); + temp.mkdirs(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> deleteDir(temp))); + System.out.println("Running " + name + " with user data dir: " + temp.getAbsolutePath()); + List params = new ArrayList<>(); + paramsBuilder.accept(temp.getAbsolutePath(), params); + params.add(url); + ProcessBuilder pb = new ProcessBuilder(params.toArray(new String[0])); + Process process = pb.start(); + logStream(process.getInputStream(), name + " stdout"); + logStream(process.getErrorStream(), name + " stderr"); + new Thread(() -> { + try { + System.out.println(name + " process terminated with code: " + process.waitFor()); + } catch (InterruptedException e) { + // ignore + } + }); + return process; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void logStream(InputStream stream, String name) { + new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + System.out.println(name + ": " + line); + } + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + private static void deleteDir(File dir) { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + deleteDir(file); + } else { + file.delete(); + } + } + dir.delete(); + } } diff --git a/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java new file mode 100644 index 000000000..307f6698a --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/CPlatformSupport.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static org.teavm.junit.PropertyNames.C_COMPILER; +import static org.teavm.junit.PropertyNames.C_ENABLED; +import static org.teavm.junit.PropertyNames.C_LINE_NUMBERS; +import static org.teavm.junit.PropertyNames.OPTIMIZED; +import static org.teavm.junit.TestUtil.resourceToFile; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import org.teavm.backend.c.CTarget; +import org.teavm.backend.c.generate.CNameProvider; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.ReferenceCache; +import org.teavm.vm.TeaVM; + +class CPlatformSupport extends TestPlatformSupport { + CPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + super(classSource, referenceCache); + } + + @Override + TestRunStrategy createRunStrategy(File outputDir) { + String cCommand = System.getProperty(C_COMPILER); + if (cCommand != null) { + return new CRunStrategy(cCommand); + } + return null; + } + + @Override + TestPlatform getPlatform() { + return TestPlatform.C; + } + + @Override + String getPath() { + return "c"; + } + + @Override + String getExtension() { + return ""; + } + + @Override + List> getConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.getBoolean(C_ENABLED)) { + configurations.add(TeaVMTestConfiguration.C_DEFAULT); + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.C_OPTIMIZED); + } + } + return configurations; + } + + @Override + CompileResult compile(Consumer additionalProcessing, String baseName, + TeaVMTestConfiguration configuration, File path) { + CompilePostProcessor postBuild = (vm, file) -> { + try { + resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"), + Collections.emptyMap()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + return compile(configuration, this::createCTarget, TestNativeEntryPoint.class.getName(), path, ".c", + postBuild, true, additionalProcessing, baseName); + } + + private CTarget createCTarget() { + CTarget cTarget = new CTarget(new CNameProvider()); + cTarget.setLineNumbersGenerated(Boolean.parseBoolean(System.getProperty(C_LINE_NUMBERS, "false"))); + return cTarget; + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/CompilePostProcessor.java b/tools/junit/src/main/java/org/teavm/junit/CompilePostProcessor.java new file mode 100644 index 000000000..894930c64 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/CompilePostProcessor.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import java.io.File; +import org.teavm.vm.TeaVM; + +interface CompilePostProcessor { + void process(TeaVM vm, File targetFile); +} diff --git a/tools/junit/src/main/java/org/teavm/junit/CompileResult.java b/tools/junit/src/main/java/org/teavm/junit/CompileResult.java new file mode 100644 index 000000000..776242598 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/CompileResult.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import java.io.File; + +class CompileResult { + boolean success = true; + String errorMessage; + File file; + Throwable throwable; +} diff --git a/tools/junit/src/main/java/org/teavm/junit/EachTestCompiledSeparately.java b/tools/junit/src/main/java/org/teavm/junit/EachTestCompiledSeparately.java new file mode 100644 index 000000000..919de4a73 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/EachTestCompiledSeparately.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EachTestCompiledSeparately { +} diff --git a/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java new file mode 100644 index 000000000..352d2096b --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/JSPlatformSupport.java @@ -0,0 +1,150 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.teavm.junit.PropertyNames.JS_DECODE_STACK; +import static org.teavm.junit.PropertyNames.JS_ENABLED; +import static org.teavm.junit.PropertyNames.JS_RUNNER; +import static org.teavm.junit.PropertyNames.MINIFIED; +import static org.teavm.junit.PropertyNames.OPTIMIZED; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.debugging.information.DebugInformation; +import org.teavm.debugging.information.DebugInformationBuilder; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ReferenceCache; +import org.teavm.vm.TeaVM; + +class JSPlatformSupport extends TestPlatformSupport { + JSPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + super(classSource, referenceCache); + } + + @Override + TestRunStrategy createRunStrategy(File outputDir) { + String runStrategyName = System.getProperty(JS_RUNNER); + if (runStrategyName != null) { + switch (runStrategyName) { + case "browser": + return new BrowserRunStrategy(outputDir, "JAVASCRIPT", BrowserRunStrategy::customBrowser); + case "browser-chrome": + return new BrowserRunStrategy(outputDir, "JAVASCRIPT", BrowserRunStrategy::chromeBrowser); + case "browser-firefox": + return new BrowserRunStrategy(outputDir, "JAVASCRIPT", BrowserRunStrategy::firefoxBrowser); + case "none": + return null; + default: + throw new RuntimeException("Unknown run strategy: " + runStrategyName); + } + } + return null; + } + + @Override + TestPlatform getPlatform() { + return TestPlatform.JAVASCRIPT; + } + + @Override + String getPath() { + return "js"; + } + + @Override + String getExtension() { + return ".js"; + } + + @Override + List> getConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) { + configurations.add(TeaVMTestConfiguration.JS_DEFAULT); + if (Boolean.getBoolean(MINIFIED)) { + configurations.add(TeaVMTestConfiguration.JS_MINIFIED); + } + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.JS_OPTIMIZED); + } + } + return configurations; + } + + @Override + CompileResult compile(Consumer additionalProcessing, String baseName, + TeaVMTestConfiguration configuration, File path) { + boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true")); + var debugEmitter = new DebugInformationBuilder(new ReferenceCache()); + Supplier targetSupplier = () -> { + JavaScriptTarget target = new JavaScriptTarget(); + target.setStrict(true); + if (decodeStack) { + target.setDebugEmitter(debugEmitter); + target.setStackTraceIncluded(true); + } + return target; + }; + CompilePostProcessor postBuild = null; + if (decodeStack) { + postBuild = (vm, file) -> { + DebugInformation debugInfo = debugEmitter.getDebugInformation(); + File sourceMapsFile = new File(file.getPath() + ".map"); + File debugFile = new File(file.getPath() + ".teavmdbg"); + try { + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), UTF_8)) { + writer.write("\n//# sourceMappingURL="); + writer.write(sourceMapsFile.getName()); + } + + try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream(sourceMapsFile), UTF_8)) { + debugInfo.writeAsSourceMaps(sourceMapsOut, "", file.getPath()); + } + + try (OutputStream out = new FileOutputStream(debugFile)) { + debugInfo.write(out); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + } + return compile(configuration, targetSupplier, TestJsEntryPoint.class.getName(), path, ".js", + postBuild, false, additionalProcessing, baseName); + } + + @Override + void additionalOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + htmlOutput(outputPath, outputPathForMethod, configuration, reference, "teavm-run-test.html"); + } + + @Override + void additionalSingleTestOutput(File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + htmlSingleTestOutput(outputPathForMethod, configuration, "teavm-run-test.html"); + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/OnlyPlatform.java b/tools/junit/src/main/java/org/teavm/junit/OnlyPlatform.java new file mode 100644 index 000000000..3ae33839d --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/OnlyPlatform.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface OnlyPlatform { + TestPlatform[] value(); +} diff --git a/tools/junit/src/main/java/org/teavm/junit/PropertyNames.java b/tools/junit/src/main/java/org/teavm/junit/PropertyNames.java new file mode 100644 index 000000000..8824743b4 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/PropertyNames.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +final class PropertyNames { + static final String PATH_PARAM = "teavm.junit.target"; + static final String JS_RUNNER = "teavm.junit.js.runner"; + static final String WASM_RUNNER = "teavm.junit.wasm.runner"; + static final String THREAD_COUNT = "teavm.junit.threads"; + static final String JS_ENABLED = "teavm.junit.js"; + static final String JS_DECODE_STACK = "teavm.junit.js.decodeStack"; + static final String C_ENABLED = "teavm.junit.c"; + static final String WASM_ENABLED = "teavm.junit.wasm"; + static final String WASI_ENABLED = "teavm.junit.wasi"; + static final String WASI_RUNNER = "teavm.junit.wasi.runner"; + static final String C_COMPILER = "teavm.junit.c.compiler"; + static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers"; + static final String MINIFIED = "teavm.junit.minified"; + static final String OPTIMIZED = "teavm.junit.optimized"; + static final String SOURCE_DIRS = "teavm.junit.sourceDirs"; + + private PropertyNames() { + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/SkipPlatform.java b/tools/junit/src/main/java/org/teavm/junit/SkipPlatform.java new file mode 100644 index 000000000..45f85e766 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/SkipPlatform.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface SkipPlatform { + TestPlatform[] value(); +} diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 8ac400511..2729c714a 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -15,17 +15,16 @@ */ package org.teavm.junit; -import static java.nio.charset.StandardCharsets.UTF_8; +import static org.teavm.junit.PropertyNames.PATH_PARAM; +import static org.teavm.junit.TestUtil.getOutputFile; import java.io.BufferedOutputStream; -import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -39,13 +38,10 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.StringTokenizer; -import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; import junit.framework.TestCase; -import org.apache.commons.io.IOUtils; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; @@ -54,20 +50,6 @@ import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.InitializationError; -import org.teavm.backend.c.CTarget; -import org.teavm.backend.c.generate.CNameProvider; -import org.teavm.backend.javascript.JavaScriptTarget; -import org.teavm.backend.wasm.WasmRuntimeType; -import org.teavm.backend.wasm.WasmTarget; -import org.teavm.backend.wasm.generate.DirectorySourceFileResolver; -import org.teavm.callgraph.CallGraph; -import org.teavm.debugging.information.DebugInformation; -import org.teavm.debugging.information.DebugInformationBuilder; -import org.teavm.dependency.DependencyAnalyzerFactory; -import org.teavm.dependency.FastDependencyAnalyzer; -import org.teavm.dependency.PreciseDependencyAnalyzer; -import org.teavm.diagnostics.DefaultProblemTextConsumer; -import org.teavm.diagnostics.Problem; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; @@ -81,11 +63,7 @@ import org.teavm.model.PreOptimizingClassHolderSource; import org.teavm.model.ReferenceCache; import org.teavm.model.ValueType; import org.teavm.parsing.ClasspathClassHolderSource; -import org.teavm.tooling.TeaVMProblemRenderer; -import org.teavm.vm.DirectoryBuildTarget; import org.teavm.vm.TeaVM; -import org.teavm.vm.TeaVMBuilder; -import org.teavm.vm.TeaVMOptimizationLevel; import org.teavm.vm.TeaVMTarget; public class TeaVMTestRunner extends Runner implements Filterable { @@ -101,22 +79,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { static final String JUNIT4_AFTER = "org.junit.After"; static final String TESTNG_AFTER = "org.testng.annotations.AfterMethod"; static final String TESTNG_PROVIDER = "org.testng.annotations.DataProvider"; - private static final String PATH_PARAM = "teavm.junit.target"; - private static final String JS_RUNNER = "teavm.junit.js.runner"; - private static final String WASM_RUNNER = "teavm.junit.wasm.runner"; - private static final String THREAD_COUNT = "teavm.junit.threads"; - private static final String JS_ENABLED = "teavm.junit.js"; - static final String JS_DECODE_STACK = "teavm.junit.js.decodeStack"; - private static final String C_ENABLED = "teavm.junit.c"; - private static final String WASM_ENABLED = "teavm.junit.wasm"; - private static final String WASI_ENABLED = "teavm.junit.wasi"; - private static final String WASI_RUNNER = "teavm.junit.wasi.runner"; - private static final String C_COMPILER = "teavm.junit.c.compiler"; - private static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers"; - private static final String MINIFIED = "teavm.junit.minified"; - private static final String OPTIMIZED = "teavm.junit.optimized"; - private static final String FAST_ANALYSIS = "teavm.junit.fastAnalysis"; - private static final String SOURCE_DIRS = "teavm.junit.sourceDirs"; private Class testClass; private boolean isWholeClassCompilation; @@ -125,11 +87,13 @@ public class TeaVMTestRunner extends Runner implements Filterable { private Description suiteDescription; private static File outputDir; private Map descriptions = new HashMap<>(); - private static Map runners = new HashMap<>(); + private static Map runners = new HashMap<>(); private List filteredChildren; private static ReferenceCache referenceCache = new ReferenceCache(); private boolean classCompilationOk; private List runsInCurrentClass = new ArrayList<>(); + private static List> platforms = new ArrayList<>(); + private List> participatingPlatforms = new ArrayList<>(); static { classLoader = TeaVMTestRunner.class.getClassLoader(); @@ -140,55 +104,14 @@ public class TeaVMTestRunner extends Runner implements Filterable { outputDir = new File(outputPath); } - String runStrategyName = System.getProperty(JS_RUNNER); - if (runStrategyName != null) { - TestRunStrategy jsRunStrategy; - switch (runStrategyName) { - case "browser": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::customBrowser); - break; - case "browser-chrome": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::chromeBrowser); - break; - case "browser-firefox": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::firefoxBrowser); - break; - case "none": - jsRunStrategy = null; - break; - default: - throw new RuntimeException("Unknown run strategy: " + runStrategyName); - } - runners.put(RunKind.JAVASCRIPT, jsRunStrategy); - } + platforms.add(new JSPlatformSupport(classSource, referenceCache)); + platforms.add(new WebAssemblyPlatformSupport(classSource, referenceCache)); + platforms.add(new WasiPlatformSupport(classSource, referenceCache)); + platforms.add(new CPlatformSupport(classSource, referenceCache)); - String cCommand = System.getProperty(C_COMPILER); - if (cCommand != null) { - runners.put(RunKind.C, new CRunStrategy(cCommand)); - } - String wasiCommand = System.getProperty(WASI_RUNNER); - if (wasiCommand != null) { - runners.put(RunKind.WASI, new WasiRunStrategy(wasiCommand)); - } - - runStrategyName = System.getProperty(WASM_RUNNER); - if (runStrategyName != null) { - TestRunStrategy wasmRunStrategy; - switch (runStrategyName) { - case "browser": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::customBrowser); - break; - case "chrome": - case "browser-chrome": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::chromeBrowser); - break; - case "browser-firefox": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::firefoxBrowser); - break; - default: - throw new RuntimeException("Unknown run strategy: " + runStrategyName); - } - runners.put(RunKind.WASM, wasmRunStrategy); + for (var platform : platforms) { + var runStrategy = platform.createRunStrategy(outputDir); + runners.put(platform.getPlatform(), runStrategy); } for (var strategy : runners.values()) { @@ -206,126 +129,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { this.testClass = testClass; } - private static Process customBrowser(String url) { - System.out.println("Open link to run tests: " + url + "?logging=true"); - return null; - } - - private static Process chromeBrowser(String url) { - return browserTemplate("chrome", url, (profile, params) -> { - addChromeCommand(params); - params.addAll(Arrays.asList( - "--headless", - "--disable-gpu", - "--remote-debugging-port=9222", - "--no-first-run", - "--user-data-dir=" + profile - )); - }); - } - - private static Process firefoxBrowser(String url) { - return browserTemplate("firefox", url, (profile, params) -> { - addFirefoxCommand(params); - params.addAll(Arrays.asList( - "--headless", - "--profile", - profile - )); - }); - } - - private static void addChromeCommand(List params) { - if (isMacos()) { - params.add("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); - } else if (isWindows()) { - params.add("cmd.exe"); - params.add("start"); - params.add("/C"); - params.add("chrome"); - } else { - params.add("google-chrome-stable"); - } - } - - private static void addFirefoxCommand(List params) { - if (isMacos()) { - params.add("/Applications/Firefox.app/Contents/MacOS/firefox"); - return; - } - if (isWindows()) { - params.add("cmd.exe"); - params.add("/C"); - params.add("start"); - } - params.add("firefox"); - } - - private static boolean isWindows() { - return System.getProperty("os.name").toLowerCase().startsWith("windows"); - } - - private static boolean isMacos() { - return System.getProperty("os.name").toLowerCase().startsWith("mac"); - } - - private static Process browserTemplate(String name, String url, BiConsumer> paramsBuilder) { - File temp; - try { - temp = File.createTempFile("teavm", "teavm"); - temp.delete(); - temp.mkdirs(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> deleteDir(temp))); - System.out.println("Running " + name + " with user data dir: " + temp.getAbsolutePath()); - List params = new ArrayList<>(); - paramsBuilder.accept(temp.getAbsolutePath(), params); - int tabs = Integer.parseInt(System.getProperty(THREAD_COUNT, "1")); - for (int i = 0; i < tabs; ++i) { - params.add(url); - } - ProcessBuilder pb = new ProcessBuilder(params.toArray(new String[0])); - Process process = pb.start(); - logStream(process.getInputStream(), name + " stdout"); - logStream(process.getErrorStream(), name + " stderr"); - new Thread(() -> { - try { - System.out.println(name + " process terminated with code: " + process.waitFor()); - } catch (InterruptedException e) { - // ignore - } - }); - return process; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static void logStream(InputStream stream, String name) { - new Thread(() -> { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { - while (true) { - String line = reader.readLine(); - if (line == null) { - break; - } - System.out.println(name + ": " + line); - } - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - } - - private static void deleteDir(File dir) { - for (File file : dir.listFiles()) { - if (file.isDirectory()) { - deleteDir(file); - } else { - file.delete(); - } - } - dir.delete(); - } @Override public Description getDescription() { @@ -340,11 +143,17 @@ public class TeaVMTestRunner extends Runner implements Filterable { @Override public void run(RunNotifier notifier) { + for (var platform : platforms) { + if (!platform.getConfigurations().isEmpty()) { + participatingPlatforms.add(platform); + } + } + List children = getFilteredChildren(); var description = getDescription(); notifier.fireTestStarted(description); - isWholeClassCompilation = testClass.isAnnotationPresent(WholeClassCompilation.class); + isWholeClassCompilation = !testClass.isAnnotationPresent(EachTestCompiledSeparately.class); if (isWholeClassCompilation) { classCompilationOk = compileWholeClass(children, notifier); } @@ -402,45 +211,69 @@ public class TeaVMTestRunner extends Runner implements Filterable { } private boolean compileWholeClass(List children, RunNotifier notifier) { - File outputPath = getOutputPathForClass(); - boolean hasErrors = false; Description description = getDescription(); - for (TeaVMTestConfiguration configuration : getJavaScriptConfigurations()) { - CompileResult result = compileToJs(wholeClass(children), "classTest", configuration, outputPath); - if (!result.success) { - hasErrors = true; - notifier.fireTestFailure(createFailure(description, result)); + for (var platformSupport : participatingPlatforms) { + if (!compileClassForPlatform(platformSupport, children, description, notifier)) { + return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private boolean compileClassForPlatform(TestPlatformSupport platform, List children, + Description description, RunNotifier notifier) { + if (hasChildrenToRun(children, platform.getPlatform())) { + for (var configuration : platform.getConfigurations()) { + var path = getOutputPathForClass(platform); + var castPlatform = (TestPlatformSupport) platform; + var castConfiguration = (TeaVMTestConfiguration) configuration; + var result = castPlatform.compile(wholeClass(children, platform.getPlatform()), "classTest", + castConfiguration, path); + if (!result.success) { + notifier.fireTestFailure(createFailure(description, result)); + return false; + } + } + } + return true; + } + + private boolean isPlatformPresent(AnnotatedElement declaration, TestPlatform platform) { + var skipPlatform = declaration.getAnnotation(SkipPlatform.class); + if (skipPlatform != null) { + for (var toSkip : skipPlatform.value()) { + if (toSkip == platform) { + return false; + } } } - for (TeaVMTestConfiguration configuration : getCConfigurations()) { - CompileResult result = compileToC(wholeClass(children), "classTest", configuration, outputPath); - if (!result.success) { - hasErrors = true; - notifier.fireTestFailure(createFailure(description, result)); + var onlyPlatform = declaration.getAnnotation(OnlyPlatform.class); + if (onlyPlatform != null) { + for (var allowedPlatform : onlyPlatform.value()) { + if (allowedPlatform == platform) { + return true; + } } + return false; } - for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { - CompileResult result = compileToWasm(WasmRuntimeType.TEAVM, wholeClass(children), "classTest", - configuration, outputPath); - if (!result.success) { - hasErrors = true; - notifier.fireTestFailure(createFailure(description, result)); - } - } + return true; + } - for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { - CompileResult result = compileToWasm(WasmRuntimeType.WASI, wholeClass(children), "classTest", - configuration, outputPath); - if (!result.success) { - hasErrors = true; - notifier.fireTestFailure(createFailure(description, result)); - } - } + private boolean hasChildrenToRun(List children, TestPlatform platform) { + return isPlatformPresent(testClass, platform) + && children.stream().anyMatch(child -> isPlatformPresent(child, platform)); + } - return !hasErrors; + private List filterChildren(List children, TestPlatform platform) { + return children.stream().filter(child -> isPlatformPresent(child, platform)).collect(Collectors.toList()); + } + + private boolean shouldRunChild(Method child, TestPlatform platform) { + return isPlatformPresent(testClass, platform) && isPlatformPresent(child, platform); } private void runChild(Method child, RunNotifier notifier) { @@ -496,123 +329,49 @@ public class TeaVMTestRunner extends Runner implements Filterable { } private void prepareTestsFromWholeClass(Method child, List runs) { - File outputPath = getOutputPathForClass(); - File outputPathForMethod = getOutputPath(child); MethodDescriptor descriptor = getDescriptor(child); MethodReference reference = new MethodReference(child.getDeclaringClass().getName(), descriptor); - File testFilePath = getOutputPath(child); - testFilePath.mkdirs(); - - Map properties = new HashMap<>(); - for (var configuration : getJavaScriptConfigurations()) { - File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js"); - runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString())); - File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", "../" + testPath.getName()); - properties.put("IDENTIFIER", reference.toString()); - try { - resourceToFile("teavm-run-test.html", htmlPath, properties); - } catch (IOException e) { - throw new RuntimeException(e); + for (var platform : participatingPlatforms) { + if (shouldRunChild(child, platform.getPlatform())) { + var outputPath = getOutputPathForClass(platform); + var outputPathForMethod = getOutputPath(child, platform); + for (var configuration : platform.getConfigurations()) { + var testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, + platform.getExtension()); + runs.add(createTestRun(configuration, testPath, child, platform.getPlatform(), + reference.toString())); + platform.additionalOutput(outputPath, outputPathForMethod, configuration, reference); + } } } - - for (var configuration : getWasmConfigurations()) { - File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); - runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString())); - File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", "../" + testPath.getName()); - properties.put("IDENTIFIER", reference.toString()); - try { - resourceToFile("teavm-run-test-wasm.html", htmlPath, properties); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (var configuration : getWasiConfigurations()) { - File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); - runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString())); - File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", "../" + testPath.getName()); - properties.put("IDENTIFIER", reference.toString()); - } - - for (var configuration : getCConfigurations()) { - File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c"); - runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString())); - } } private void prepareCompiledTest(Method child, RunNotifier notifier, List runs) { + MethodDescriptor descriptor = getDescriptor(child); + MethodReference reference = new MethodReference(child.getDeclaringClass().getName(), descriptor); + try { - File outputPath = getOutputPath(child); - - Map properties = new HashMap<>(); - for (var configuration : getJavaScriptConfigurations()) { - CompileResult compileResult = compileToJs(singleTest(child), "test", configuration, outputPath); - TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.JAVASCRIPT); - if (run != null) { - runs.add(run); - - File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".js"); - File htmlPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", testPath.getName()); - properties.put("IDENTIFIER", ""); - - try { - resourceToFile("teavm-run-test.html", htmlPath, properties); - } catch (IOException e) { - throw new RuntimeException(e); + for (var platform : participatingPlatforms) { + if (shouldRunChild(child, platform.getPlatform())) { + File outputPath = getOutputPath(child, platform); + for (var configuration : platform.getConfigurations()) { + @SuppressWarnings("unchecked") + var castPlatform = (TestPlatformSupport) platform; + @SuppressWarnings("unchecked") + var castConfig = (TeaVMTestConfiguration) configuration; + var compileResult = castPlatform.compile(singleTest(child), "test", castConfig, outputPath); + var run = prepareRun(configuration, child, compileResult, notifier, platform.getPlatform()); + if (run != null) { + runs.add(run); + platform.additionalSingleTestOutput(outputPath, configuration, reference); + } } } } - - for (var configuration : getCConfigurations()) { - CompileResult compileResult = compileToC(singleTest(child), "test", configuration, outputPath); - TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.C); - if (run != null) { - runs.add(run); - } - } - - for (var configuration : getWasmConfigurations()) { - CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child), - "test", configuration, outputPath); - TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM); - if (run != null) { - runs.add(run); - - File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".wasm"); - File htmlPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".html"); - properties.put("SCRIPT", testPath.getName()); - properties.put("IDENTIFIER", ""); - - try { - resourceToFile("teavm-run-test-wasm.html", htmlPath, properties); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - for (var configuration : getWasiConfigurations()) { - CompileResult compileResult = compileToWasm(WasmRuntimeType.WASI, singleTest(child), "test", - configuration, outputPath); - TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI); - if (run != null) { - runs.add(run); - - File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".wasm"); - properties.put("SCRIPT", testPath.getName()); - properties.put("IDENTIFIER", ""); - } - } } catch (Throwable e) { notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFinished(describeChild(child)); - return; } } @@ -939,7 +698,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { } private TestRun prepareRun(TeaVMTestConfiguration configuration, Method child, CompileResult result, - RunNotifier notifier, RunKind kind) { + RunNotifier notifier, TestPlatform kind) { Description description = describeChild(child); if (!result.success) { @@ -951,7 +710,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { return createTestRun(configuration, result.file, child, kind, null); } - private TestRun createTestRun(TeaVMTestConfiguration configuration, File file, Method child, RunKind kind, + private TestRun createTestRun(TeaVMTestConfiguration configuration, File file, Method child, TestPlatform kind, String argument) { return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child, file.getName(), kind, argument); @@ -984,107 +743,21 @@ public class TeaVMTestRunner extends Runner implements Filterable { return true; } - private File getOutputPath(Method method) { + private File getOutputPath(Method method, TestPlatformSupport platform) { File path = outputDir; - path = new File(path, testClass.getName().replace('.', '/')); + path = new File(new File(path, platform.getPath()), testClass.getName().replace('.', '/')); path = new File(path, method.getName()); path.mkdirs(); return path; } - private File getOutputPathForClass() { + private File getOutputPathForClass(TestPlatformSupport platform) { File path = outputDir; - path = new File(path, testClass.getName().replace('.', '/')); + path = new File(new File(path, platform.getPath()), testClass.getName().replace('.', '/')); path.mkdirs(); return path; } - private CompileResult compileToJs(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { - boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true")); - DebugInformationBuilder debugEmitter = new DebugInformationBuilder(new ReferenceCache()); - Supplier targetSupplier = () -> { - JavaScriptTarget target = new JavaScriptTarget(); - target.setStrict(true); - if (decodeStack) { - target.setDebugEmitter(debugEmitter); - target.setStackTraceIncluded(true); - } - return target; - }; - CompilePostProcessor postBuild = null; - if (decodeStack) { - postBuild = (vm, file) -> { - DebugInformation debugInfo = debugEmitter.getDebugInformation(); - File sourceMapsFile = new File(file.getPath() + ".map"); - File debugFile = new File(file.getPath() + ".teavmdbg"); - try { - try (Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), UTF_8)) { - writer.write("\n//# sourceMappingURL="); - writer.write(sourceMapsFile.getName()); - } - - try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream(sourceMapsFile), UTF_8)) { - debugInfo.writeAsSourceMaps(sourceMapsOut, "", file.getPath()); - } - - try (OutputStream out = new FileOutputStream(debugFile)) { - debugInfo.write(out); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - } - return compile(configuration, targetSupplier, TestJsEntryPoint.class.getName(), path, ".js", - postBuild, false, additionalProcessing, baseName); - } - - private CompileResult compileToC(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { - CompilePostProcessor postBuild = (vm, file) -> { - try { - resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"), - Collections.emptyMap()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - return compile(configuration, this::createCTarget, TestNativeEntryPoint.class.getName(), path, ".c", - postBuild, true, additionalProcessing, baseName); - } - - private CTarget createCTarget() { - CTarget cTarget = new CTarget(new CNameProvider()); - cTarget.setLineNumbersGenerated(Boolean.parseBoolean(System.getProperty(C_LINE_NUMBERS, "false"))); - return cTarget; - } - - private CompileResult compileToWasm(WasmRuntimeType runtimeType, Consumer additionalProcessing, - String baseName, TeaVMTestConfiguration configuration, File path) { - Supplier targetSupplier = () -> { - WasmTarget target = new WasmTarget(); - target.setRuntimeType(runtimeType); - var sourceDirs = System.getProperty(SOURCE_DIRS); - if (sourceDirs != null) { - var dirs = new ArrayList(); - for (var tokenizer = new StringTokenizer(sourceDirs, Character.toString(File.pathSeparatorChar)); - tokenizer.hasMoreTokens();) { - var dir = new File(tokenizer.nextToken()); - if (dir.isDirectory()) { - dirs.add(dir); - } - } - if (!dirs.isEmpty()) { - target.setSourceFileResolver(new DirectorySourceFileResolver(dirs)); - } - } - return target; - }; - return compile(configuration, targetSupplier, TestNativeEntryPoint.class.getName(), path, - ".wasm", null, false, additionalProcessing, baseName); - } - private Consumer singleTest(Method method) { ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName()); MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method)); @@ -1097,13 +770,13 @@ public class TeaVMTestRunner extends Runner implements Filterable { }; } - private Consumer wholeClass(List methods) { + private Consumer wholeClass(List methods, TestPlatform platform) { return vm -> { Properties properties = new Properties(); applyProperties(testClass, properties); vm.setProperties(properties); List methodReferences = new ArrayList<>(); - for (Method method : methods) { + for (Method method : filterChildren(methods, platform)) { if (isIgnored(method)) { continue; } @@ -1143,139 +816,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { return cls.getAnnotations().get(name); } - - private CompileResult compile(TeaVMTestConfiguration configuration, - Supplier targetSupplier, String entryPoint, File path, String extension, - CompilePostProcessor postBuild, boolean separateDir, - Consumer additionalProcessing, String baseName) { - CompileResult result = new CompileResult(); - - File outputFile = getOutputFile(path, baseName, configuration.getSuffix(), separateDir, extension); - result.file = outputFile; - - ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader(); - - T target = targetSupplier.get(); - configuration.apply(target); - - DependencyAnalyzerFactory dependencyAnalyzerFactory = PreciseDependencyAnalyzer::new; - boolean fastAnalysis = Boolean.parseBoolean(System.getProperty(FAST_ANALYSIS)); - if (fastAnalysis) { - dependencyAnalyzerFactory = FastDependencyAnalyzer::new; - } - - try { - TeaVM vm = new TeaVMBuilder(target) - .setClassLoader(classLoader) - .setClassSource(classSource) - .setReferenceCache(referenceCache) - .setDependencyAnalyzerFactory(dependencyAnalyzerFactory) - .build(); - - configuration.apply(vm); - additionalProcessing.accept(vm); - vm.installPlugins(); - - new TestExceptionPlugin().install(vm); - - vm.entryPoint(entryPoint); - - if (fastAnalysis) { - vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); - vm.addVirtualMethods(m -> true); - } - if (!outputFile.getParentFile().exists()) { - outputFile.getParentFile().mkdirs(); - } - vm.build(new DirectoryBuildTarget(outputFile.getParentFile()), outputFile.getName()); - if (!vm.getProblemProvider().getProblems().isEmpty()) { - result.success = false; - result.errorMessage = buildErrorMessage(vm); - } else { - if (postBuild != null) { - postBuild.process(vm, outputFile); - } - } - - return result; - } catch (Exception e) { - result = new CompileResult(); - result.success = false; - result.throwable = e; - return result; - } - } - - private File getOutputFile(File path, String baseName, String suffix, boolean separateDir, String extension) { - StringBuilder simpleName = new StringBuilder(); - simpleName.append(baseName); - if (!suffix.isEmpty()) { - if (!separateDir) { - simpleName.append('-').append(suffix); - } - } - File outputFile; - if (separateDir) { - outputFile = new File(new File(path, simpleName.toString()), "test" + extension); - } else { - simpleName.append(extension); - outputFile = new File(path, simpleName.toString()); - } - - return outputFile; - } - - interface CompilePostProcessor { - void process(TeaVM vm, File targetFile); - } - - private List> getJavaScriptConfigurations() { - List> configurations = new ArrayList<>(); - if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) { - configurations.add(TeaVMTestConfiguration.JS_DEFAULT); - if (Boolean.getBoolean(MINIFIED)) { - configurations.add(TeaVMTestConfiguration.JS_MINIFIED); - } - if (Boolean.getBoolean(OPTIMIZED)) { - configurations.add(TeaVMTestConfiguration.JS_OPTIMIZED); - } - } - return configurations; - } - - private List> getWasmConfigurations() { - List> configurations = new ArrayList<>(); - if (Boolean.getBoolean(WASM_ENABLED)) { - configurations.add(TeaVMTestConfiguration.WASM_DEFAULT); - if (Boolean.getBoolean(OPTIMIZED)) { - configurations.add(TeaVMTestConfiguration.WASM_OPTIMIZED); - } - } - return configurations; - } - - private List> getWasiConfigurations() { - List> configurations = new ArrayList<>(); - if (Boolean.getBoolean(WASI_ENABLED)) { - configurations.add(TeaVMTestConfiguration.WASM_DEFAULT); - if (Boolean.getBoolean(OPTIMIZED)) { - configurations.add(TeaVMTestConfiguration.WASM_OPTIMIZED); - } - } - return configurations; - } - - private List> getCConfigurations() { - List> configurations = new ArrayList<>(); - if (Boolean.getBoolean(C_ENABLED)) { - configurations.add(TeaVMTestConfiguration.C_DEFAULT); - if (Boolean.getBoolean(OPTIMIZED)) { - configurations.add(TeaVMTestConfiguration.C_OPTIMIZED); - } - } - return configurations; - } - private void applyProperties(Class cls, Properties result) { if (cls.getSuperclass() != null) { applyProperties(cls.getSuperclass(), result); @@ -1295,70 +835,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { return new MethodDescriptor(method.getName(), signature); } - private String buildErrorMessage(TeaVM vm) { - CallGraph cg = vm.getDependencyInfo().getCallGraph(); - DefaultProblemTextConsumer consumer = new DefaultProblemTextConsumer(); - StringBuilder sb = new StringBuilder(); - for (Problem problem : vm.getProblemProvider().getProblems()) { - consumer.clear(); - problem.render(consumer); - sb.append(consumer.getText()); - TeaVMProblemRenderer.renderCallStack(cg, problem.getLocation(), sb); - sb.append("\n"); - } - return sb.toString(); - } - - private void resourceToFile(String resource, File file, Map properties) throws IOException { - if (properties.isEmpty()) { - try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource); - OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { - IOUtils.copy(input, output); - } - } else { - String content; - try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource)) { - content = IOUtils.toString(input, UTF_8); - } - content = replaceProperties(content, properties); - try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file)); - Writer writer = new OutputStreamWriter(output)) { - writer.write(content); - } - } - } - - private static String replaceProperties(String s, Map properties) { - int i = 0; - StringBuilder sb = new StringBuilder(); - while (i < s.length()) { - int next = s.indexOf("${", i); - if (next < 0) { - break; - } - int end = s.indexOf('}', next + 2); - if (end < 0) { - break; - } - - sb.append(s, i, next); - String property = s.substring(next + 2, end); - String value = properties.get(property); - if (value == null) { - sb.append(s, next, end + 1); - } else { - sb.append(value); - } - i = end + 1; - } - - if (i == 0) { - return s; - } - - return sb.append(s.substring(i)).toString(); - } - private static ClassHolderSource getClassSource(ClassLoader classLoader) { return new PreOptimizingClassHolderSource(new ClasspathClassHolderSource(classLoader, referenceCache)); } @@ -1380,7 +856,19 @@ public class TeaVMTestRunner extends Runner implements Filterable { return; } - File outputDir = getOutputPathForClass(); + for (var platform : participatingPlatforms) { + writeRunsDescriptor(platform); + } + } + + private void writeRunsDescriptor(TestPlatformSupport platform) { + var runs = runsInCurrentClass.stream().filter(run -> run.getKind() == platform.getPlatform()) + .collect(Collectors.toList()); + if (runs.isEmpty()) { + return; + } + + File outputDir = getOutputPathForClass(platform); outputDir.mkdirs(); File descriptorFile = new File(outputDir, "tests.json"); try (OutputStream output = new FileOutputStream(descriptorFile); @@ -1388,7 +876,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { Writer writer = new OutputStreamWriter(bufferedOutput)) { writer.write("[\n"); boolean first = true; - for (TestRun run : runsInCurrentClass.toArray(new TestRun[0])) { + for (TestRun run : runs) { if (!first) { writer.write(",\n"); } @@ -1460,11 +948,4 @@ public class TeaVMTestRunner extends Runner implements Filterable { private static char hex(int digit) { return (char) (digit < 10 ? '0' + digit : 'A' + digit - 10); } - - static class CompileResult { - boolean success = true; - String errorMessage; - File file; - Throwable throwable; - } } diff --git a/tools/junit/src/main/java/org/teavm/junit/RunKind.java b/tools/junit/src/main/java/org/teavm/junit/TestPlatform.java similarity index 86% rename from tools/junit/src/main/java/org/teavm/junit/RunKind.java rename to tools/junit/src/main/java/org/teavm/junit/TestPlatform.java index d5a26fa04..1f171aefc 100644 --- a/tools/junit/src/main/java/org/teavm/junit/RunKind.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Alexey Andreev. + * Copyright 2023 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ package org.teavm.junit; -enum RunKind { +public enum TestPlatform { JAVASCRIPT, - C, - WASM, - WASI + WEBASSEMBLY, + WASI, + C } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java new file mode 100644 index 000000000..4963af63f --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java @@ -0,0 +1,186 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static org.teavm.junit.TestUtil.resourceToFile; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.teavm.callgraph.CallGraph; +import org.teavm.dependency.DependencyAnalyzerFactory; +import org.teavm.dependency.PreciseDependencyAnalyzer; +import org.teavm.diagnostics.DefaultProblemTextConsumer; +import org.teavm.diagnostics.Problem; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ReferenceCache; +import org.teavm.tooling.TeaVMProblemRenderer; +import org.teavm.vm.DirectoryBuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; +import org.teavm.vm.TeaVMTarget; + +abstract class TestPlatformSupport { + private ClassHolderSource classSource; + private ReferenceCache referenceCache; + + TestPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + this.classSource = classSource; + this.referenceCache = referenceCache; + } + + abstract TestRunStrategy createRunStrategy(File outputDir); + + abstract TestPlatform getPlatform(); + + abstract String getPath(); + + abstract String getExtension(); + + abstract List> getConfigurations(); + + abstract CompileResult compile(Consumer additionalProcessing, String baseName, + TeaVMTestConfiguration configuration, File path); + + CompileResult compile(TeaVMTestConfiguration configuration, + Supplier targetSupplier, String entryPoint, File path, String extension, + CompilePostProcessor postBuild, boolean separateDir, + Consumer additionalProcessing, String baseName) { + CompileResult result = new CompileResult(); + + File outputFile = getOutputFile(path, baseName, configuration.getSuffix(), separateDir, extension); + result.file = outputFile; + + ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader(); + + var target = targetSupplier.get(); + configuration.apply(target); + + DependencyAnalyzerFactory dependencyAnalyzerFactory = PreciseDependencyAnalyzer::new; + + try { + TeaVM vm = new TeaVMBuilder(target) + .setClassLoader(classLoader) + .setClassSource(classSource) + .setReferenceCache(referenceCache) + .setDependencyAnalyzerFactory(dependencyAnalyzerFactory) + .build(); + + configuration.apply(vm); + additionalProcessing.accept(vm); + vm.installPlugins(); + + new TestExceptionPlugin().install(vm); + + vm.entryPoint(entryPoint); + + if (!outputFile.getParentFile().exists()) { + outputFile.getParentFile().mkdirs(); + } + vm.build(new DirectoryBuildTarget(outputFile.getParentFile()), outputFile.getName()); + if (!vm.getProblemProvider().getProblems().isEmpty()) { + result.success = false; + result.errorMessage = buildErrorMessage(vm); + } else { + if (postBuild != null) { + postBuild.process(vm, outputFile); + } + } + + return result; + } catch (Exception e) { + result = new CompileResult(); + result.success = false; + result.throwable = e; + return result; + } + } + + private File getOutputFile(File path, String baseName, String suffix, boolean separateDir, String extension) { + StringBuilder simpleName = new StringBuilder(); + simpleName.append(baseName); + if (!suffix.isEmpty()) { + if (!separateDir) { + simpleName.append('-').append(suffix); + } + } + File outputFile; + if (separateDir) { + outputFile = new File(new File(path, simpleName.toString()), "test" + extension); + } else { + simpleName.append(extension); + outputFile = new File(path, simpleName.toString()); + } + + return outputFile; + } + + private String buildErrorMessage(TeaVM vm) { + CallGraph cg = vm.getDependencyInfo().getCallGraph(); + DefaultProblemTextConsumer consumer = new DefaultProblemTextConsumer(); + StringBuilder sb = new StringBuilder(); + for (Problem problem : vm.getProblemProvider().getProblems()) { + consumer.clear(); + problem.render(consumer); + sb.append(consumer.getText()); + TeaVMProblemRenderer.renderCallStack(cg, problem.getLocation(), sb); + sb.append("\n"); + } + return sb.toString(); + } + + void additionalOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + } + + void additionalSingleTestOutput(File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + } + + protected final void htmlOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference, String template) { + var testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, getExtension()); + var htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html"); + var properties = Map.of( + "SCRIPT", "../" + testPath.getName(), + "IDENTIFIER", reference.toString() + ); + try { + resourceToFile(template, htmlPath, properties); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected final void htmlSingleTestOutput(File outputPathForMethod, TeaVMTestConfiguration configuration, + String template) { + File testPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".wasm"); + File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html"); + var properties = Map.of( + "SCRIPT", testPath.getName(), + "IDENTIFIER", "" + ); + + try { + resourceToFile(template, htmlPath, properties); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRun.java b/tools/junit/src/main/java/org/teavm/junit/TestRun.java index 8a02cdde2..88e0fa9ad 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRun.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRun.java @@ -23,10 +23,10 @@ class TestRun { private File baseDirectory; private Method method; private String fileName; - private RunKind kind; + private TestPlatform kind; private String argument; - TestRun(String name, File baseDirectory, Method method, String fileName, RunKind kind, + TestRun(String name, File baseDirectory, Method method, String fileName, TestPlatform kind, String argument) { this.name = name; this.baseDirectory = baseDirectory; @@ -52,7 +52,7 @@ class TestRun { return fileName; } - public RunKind getKind() { + public TestPlatform getKind() { return kind; } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestUtil.java b/tools/junit/src/main/java/org/teavm/junit/TestUtil.java new file mode 100644 index 000000000..bba757612 --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/TestUtil.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Map; +import org.apache.commons.io.IOUtils; + +final class TestUtil { + private TestUtil() { + } + + static File getOutputFile(File path, String baseName, String suffix, boolean separateDir, String extension) { + StringBuilder simpleName = new StringBuilder(); + simpleName.append(baseName); + if (!suffix.isEmpty()) { + if (!separateDir) { + simpleName.append('-').append(suffix); + } + } + File outputFile; + if (separateDir) { + outputFile = new File(new File(path, simpleName.toString()), "test" + extension); + } else { + simpleName.append(extension); + outputFile = new File(path, simpleName.toString()); + } + + return outputFile; + } + + static void resourceToFile(String resource, File file, Map properties) throws IOException { + file.getParentFile().mkdirs(); + if (properties.isEmpty()) { + try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource); + OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) { + IOUtils.copy(input, output); + } + } else { + String content; + try (InputStream input = TeaVMTestRunner.class.getClassLoader().getResourceAsStream(resource)) { + content = IOUtils.toString(input, UTF_8); + } + content = replaceProperties(content, properties); + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file)); + Writer writer = new OutputStreamWriter(output)) { + writer.write(content); + } + } + } + + static String replaceProperties(String s, Map properties) { + int i = 0; + StringBuilder sb = new StringBuilder(); + while (i < s.length()) { + int next = s.indexOf("${", i); + if (next < 0) { + break; + } + int end = s.indexOf('}', next + 2); + if (end < 0) { + break; + } + + sb.append(s, i, next); + String property = s.substring(next + 2, end); + String value = properties.get(property); + if (value == null) { + sb.append(s, next, end + 1); + } else { + sb.append(value); + } + i = end + 1; + } + + if (i == 0) { + return s; + } + + return sb.append(s.substring(i)).toString(); + } + +} diff --git a/tools/junit/src/main/java/org/teavm/junit/WasiPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WasiPlatformSupport.java new file mode 100644 index 000000000..f52b3a1fd --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/WasiPlatformSupport.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static org.teavm.junit.PropertyNames.OPTIMIZED; +import static org.teavm.junit.PropertyNames.WASI_ENABLED; +import static org.teavm.junit.PropertyNames.WASI_RUNNER; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.WasmRuntimeType; +import org.teavm.backend.wasm.WasmTarget; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.ReferenceCache; + +class WasiPlatformSupport extends BaseWebAssemblyPlatformSupport { + WasiPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + super(classSource, referenceCache); + } + + @Override + TestRunStrategy createRunStrategy(File outputDir) { + String wasiCommand = System.getProperty(WASI_RUNNER); + if (wasiCommand != null) { + return new WasiRunStrategy(wasiCommand); + } + return null; + } + + @Override + protected WasmRuntimeType getRuntimeType() { + return WasmRuntimeType.WASI; + } + + @Override + TestPlatform getPlatform() { + return TestPlatform.WASI; + } + + @Override + String getPath() { + return "wasi"; + } + + @Override + List> getConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.getBoolean(WASI_ENABLED)) { + configurations.add(TeaVMTestConfiguration.WASM_DEFAULT); + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.WASM_OPTIMIZED); + } + } + return configurations; + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java new file mode 100644 index 000000000..c042234fa --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyPlatformSupport.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.junit; + +import static org.teavm.junit.PropertyNames.OPTIMIZED; +import static org.teavm.junit.PropertyNames.WASM_ENABLED; +import static org.teavm.junit.PropertyNames.WASM_RUNNER; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.WasmRuntimeType; +import org.teavm.backend.wasm.WasmTarget; +import org.teavm.model.ClassHolderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ReferenceCache; + +class WebAssemblyPlatformSupport extends BaseWebAssemblyPlatformSupport { + WebAssemblyPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) { + super(classSource, referenceCache); + } + + @Override + TestRunStrategy createRunStrategy(File outputDir) { + var runStrategyName = System.getProperty(WASM_RUNNER); + if (runStrategyName != null) { + switch (runStrategyName) { + case "browser": + return new BrowserRunStrategy(outputDir, "WASM", BrowserRunStrategy::customBrowser); + case "chrome": + case "browser-chrome": + return new BrowserRunStrategy(outputDir, "WASM", BrowserRunStrategy::chromeBrowser); + case "browser-firefox": + return new BrowserRunStrategy(outputDir, "WASM", BrowserRunStrategy::firefoxBrowser); + default: + throw new RuntimeException("Unknown run strategy: " + runStrategyName); + } + } + return null; + } + + @Override + TestPlatform getPlatform() { + return TestPlatform.WEBASSEMBLY; + } + + @Override + String getPath() { + return "wasm"; + } + + @Override + List> getConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.getBoolean(WASM_ENABLED)) { + configurations.add(TeaVMTestConfiguration.WASM_DEFAULT); + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.WASM_OPTIMIZED); + } + } + return configurations; + } + + @Override + protected WasmRuntimeType getRuntimeType() { + return WasmRuntimeType.TEAVM; + } + + @Override + void additionalOutput(File outputPath, File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + htmlOutput(outputPath, outputPathForMethod, configuration, reference, "teavm-run-test-wasm.html"); + } + + @Override + void additionalSingleTestOutput(File outputPathForMethod, TeaVMTestConfiguration configuration, + MethodReference reference) { + htmlSingleTestOutput(outputPathForMethod, configuration, "teavm-run-test-wasm.html"); + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/WholeClassCompilation.java b/tools/junit/src/main/java/org/teavm/junit/WholeClassCompilation.java index 7de876c91..99d9798c8 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WholeClassCompilation.java +++ b/tools/junit/src/main/java/org/teavm/junit/WholeClassCompilation.java @@ -22,5 +22,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@Deprecated public @interface WholeClassCompilation { }