2017-04-02 22:26:53 +02:00
|
|
|
/*
|
2022-03-19 20:37:20 +01:00
|
|
|
* Copyright 2017 - 2022 Volker Berlin (i-net software)
|
2017-04-02 22:26:53 +02:00
|
|
|
*
|
|
|
|
* 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 de.inetsoftware.jwebassembly;
|
|
|
|
|
|
|
|
import static org.junit.Assert.assertEquals;
|
2020-06-18 22:53:22 +02:00
|
|
|
import static org.junit.Assert.assertTrue;
|
2018-08-14 22:36:43 +02:00
|
|
|
import static org.junit.Assert.fail;
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2020-04-13 16:00:12 +02:00
|
|
|
import java.io.ByteArrayOutputStream;
|
2017-04-02 22:26:53 +02:00
|
|
|
import java.io.File;
|
2019-06-17 19:00:16 +02:00
|
|
|
import java.io.FileInputStream;
|
2017-04-02 22:26:53 +02:00
|
|
|
import java.io.FileOutputStream;
|
2017-04-04 20:51:50 +02:00
|
|
|
import java.io.IOException;
|
2017-04-02 22:26:53 +02:00
|
|
|
import java.io.InputStream;
|
2019-06-17 19:00:16 +02:00
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.OutputStreamWriter;
|
2018-09-28 18:39:58 +02:00
|
|
|
import java.lang.ProcessBuilder.Redirect;
|
2020-01-01 23:04:51 +01:00
|
|
|
import java.lang.reflect.InvocationTargetException;
|
2017-04-02 22:26:53 +02:00
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
2020-01-05 18:29:38 +01:00
|
|
|
import java.nio.file.Files;
|
2018-08-03 23:07:25 +02:00
|
|
|
import java.util.Arrays;
|
2019-06-17 19:00:16 +02:00
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Map.Entry;
|
2020-04-13 16:00:12 +02:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2020-08-08 23:17:27 +02:00
|
|
|
import java.util.logging.Level;
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2018-11-11 12:27:40 +01:00
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
import org.junit.rules.TemporaryFolder;
|
|
|
|
|
2019-06-17 19:00:16 +02:00
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
/**
|
|
|
|
* A Junit Rule that compile the given classes and compare the results from node and Java.
|
|
|
|
*
|
|
|
|
* @author Volker Berlin
|
|
|
|
*/
|
2017-04-04 20:51:50 +02:00
|
|
|
public class WasmRule extends TemporaryFolder {
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2018-08-14 22:36:43 +02:00
|
|
|
private static final boolean IS_WINDOWS = System.getProperty( "os.name" ).toLowerCase().indexOf( "win" ) >= 0;
|
|
|
|
|
2017-04-04 20:51:50 +02:00
|
|
|
private static final SpiderMonkey spiderMonkey = new SpiderMonkey();
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2020-06-07 12:00:40 +02:00
|
|
|
private static final Node node = new Node();
|
|
|
|
|
2019-02-17 10:11:16 +01:00
|
|
|
private static final Wat2Wasm wat2Wasm = new Wat2Wasm();
|
|
|
|
|
2020-05-02 16:09:50 +02:00
|
|
|
private static boolean npmWabtNightly;
|
|
|
|
|
2020-05-02 21:42:32 +02:00
|
|
|
private static String nodeModulePath;
|
|
|
|
|
2017-04-04 20:51:50 +02:00
|
|
|
private final Class<?>[] classes;
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2020-01-12 12:46:00 +01:00
|
|
|
private final JWebAssembly compiler;
|
2019-04-19 15:57:56 +02:00
|
|
|
|
2021-08-29 15:11:17 +02:00
|
|
|
private Map<ScriptEngine, Object> compiledFiles = new HashMap<>(); // File or Throwable
|
2017-04-04 20:51:50 +02:00
|
|
|
|
2020-06-13 17:18:21 +02:00
|
|
|
private Map<ScriptEngine, File> scriptFiles = new HashMap<>();
|
2019-02-17 10:11:16 +01:00
|
|
|
|
2018-03-25 20:55:29 +02:00
|
|
|
private boolean failed;
|
|
|
|
|
2019-06-17 19:00:16 +02:00
|
|
|
private Map<String, Object[]> testData;
|
|
|
|
|
|
|
|
private Map<ScriptEngine, Map<String, String>> testResults;
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
/**
|
|
|
|
* Compile the given classes to a Wasm and save it to a file.
|
|
|
|
*
|
|
|
|
* @param classes
|
|
|
|
* list of classes to compile
|
|
|
|
*/
|
2017-04-04 20:51:50 +02:00
|
|
|
public WasmRule( Class<?>... classes ) {
|
2017-04-02 22:26:53 +02:00
|
|
|
if( classes == null || classes.length == 0 ) {
|
|
|
|
throw new IllegalArgumentException( "You need to set minimum one test class" );
|
|
|
|
}
|
|
|
|
this.classes = classes;
|
2020-01-12 12:46:00 +01:00
|
|
|
compiler = new JWebAssembly();
|
2020-06-13 14:27:56 +02:00
|
|
|
for( Class<?> clazz : classes ) {
|
|
|
|
URL url = clazz.getResource( '/' + clazz.getName().replace( '.', '/' ) + ".class" );
|
|
|
|
compiler.addFile( url );
|
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
|
|
|
|
// add the libraries that it can be scanned for annotations
|
|
|
|
final String[] libraries = System.getProperty("java.class.path").split(File.pathSeparator);
|
|
|
|
for( String lib : libraries ) {
|
|
|
|
if( lib.endsWith( ".jar" ) || lib.toLowerCase().contains( "jwebassembly-api" ) ) {
|
2021-03-27 13:31:29 +01:00
|
|
|
File library = new File(lib);
|
|
|
|
if( library.exists() ) {
|
|
|
|
compiler.addLibrary( library );
|
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
}
|
|
|
|
}
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
|
|
|
|
2019-06-17 19:00:16 +02:00
|
|
|
/**
|
|
|
|
* Set the parameter of a test.
|
|
|
|
*
|
|
|
|
* @param params
|
|
|
|
* the parameters with [ScriptEngine,method name,method parameters]
|
|
|
|
*/
|
|
|
|
public void setTestParameters( Collection<Object[]> params ) {
|
|
|
|
testData = new HashMap<>();
|
|
|
|
for( Object[] param : params ) {
|
|
|
|
testData.put( (String)param[1], (Object[])param[2] );
|
|
|
|
}
|
|
|
|
testResults = new HashMap<>();
|
|
|
|
}
|
|
|
|
|
2020-01-12 12:46:00 +01:00
|
|
|
/**
|
|
|
|
* Set property to control the behavior of the compiler
|
|
|
|
*
|
|
|
|
* @param key
|
|
|
|
* the key
|
|
|
|
* @param value
|
|
|
|
* the new value
|
|
|
|
*/
|
|
|
|
public void setProperty( String key, String value ) {
|
|
|
|
compiler.setProperty( key, value );
|
|
|
|
}
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
protected void before() throws Throwable {
|
2020-06-13 17:18:21 +02:00
|
|
|
super.before();
|
2019-06-17 19:00:16 +02:00
|
|
|
if( testData != null ) {
|
|
|
|
writeJsonTestData( testData );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 19:10:31 +02:00
|
|
|
/**
|
|
|
|
* Prepare the rule for the script engine
|
|
|
|
*
|
|
|
|
* @param script
|
|
|
|
* the script engine
|
|
|
|
* @throws Exception
|
|
|
|
* if any error occur
|
|
|
|
*/
|
|
|
|
public void before( ScriptEngine script ) throws Exception {
|
|
|
|
switch( script ) {
|
|
|
|
case Wat2Wasm:
|
2021-01-12 22:40:43 +01:00
|
|
|
case Wat2WasmGC:
|
2019-09-08 19:10:31 +02:00
|
|
|
// this is already part of execute and not only a compile
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
createCommand( script );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 19:00:16 +02:00
|
|
|
/**
|
|
|
|
* Write the test data as JSON file.
|
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* the data
|
|
|
|
* @throws IOException
|
|
|
|
* if any IO error occur
|
|
|
|
*/
|
|
|
|
private void writeJsonTestData( Map<String, Object[]> data ) throws IOException {
|
|
|
|
// a character we need to convert an integer
|
2019-08-27 20:44:27 +02:00
|
|
|
HashMap<String, Object[]> copy = new HashMap<>( data );
|
2019-06-17 19:00:16 +02:00
|
|
|
for( Entry<String, Object[]> entry : copy.entrySet() ) {
|
|
|
|
Object[] params = entry.getValue();
|
|
|
|
for( int i = 0; i < params.length; i++ ) {
|
|
|
|
if( params[i] instanceof Character ) {
|
|
|
|
params = Arrays.copyOf( params, params.length );
|
|
|
|
params[i] = new Integer( ((Character)params[i]).charValue() );
|
|
|
|
entry.setValue( params );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try (OutputStreamWriter jsonData = new OutputStreamWriter( new FileOutputStream( new File( getRoot(), "testdata.json" ) ), StandardCharsets.UTF_8 )) {
|
|
|
|
new Gson().toJson( copy, jsonData );
|
|
|
|
}
|
2017-04-14 16:31:35 +02:00
|
|
|
}
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2018-03-25 20:55:29 +02:00
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
protected void after() {
|
2020-08-08 23:17:27 +02:00
|
|
|
if( failed || JWebAssembly.LOGGER.isLoggable( Level.FINE )) {
|
2021-08-29 15:11:17 +02:00
|
|
|
File watFile = null;
|
|
|
|
boolean wasJsFile = false;
|
|
|
|
boolean wasFiles = false;
|
|
|
|
for( Object fileOrException : compiledFiles.values() ) {
|
|
|
|
if( !(fileOrException instanceof File) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
wasFiles = true;
|
|
|
|
File wasmFile = (File)fileOrException;
|
2020-06-21 13:30:07 +02:00
|
|
|
File jsFile;
|
2020-06-13 17:18:21 +02:00
|
|
|
if( wasmFile.getName().endsWith( ".wasm" ) ) {
|
2020-06-21 13:30:07 +02:00
|
|
|
jsFile = new File( wasmFile.toString() + ".js" );
|
|
|
|
} else if( wasmFile.getName().endsWith( ".wat" ) ) {
|
2021-08-29 15:11:17 +02:00
|
|
|
if( wasmFile.isFile() ) {
|
|
|
|
watFile = wasmFile;
|
|
|
|
}
|
2020-06-21 13:30:07 +02:00
|
|
|
String name = wasmFile.toString();
|
|
|
|
jsFile = new File( name.substring( 0, name.length() - 4 ) + ".wasm.js" );
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
2021-08-29 15:11:17 +02:00
|
|
|
if( !wasJsFile && jsFile.isFile() ) {
|
2020-06-21 13:30:07 +02:00
|
|
|
try {
|
2021-08-29 15:11:17 +02:00
|
|
|
wasJsFile = true;
|
2020-06-21 13:30:07 +02:00
|
|
|
System.out.println( new String( Files.readAllBytes( jsFile.toPath() ), StandardCharsets.UTF_8 ) );
|
|
|
|
System.out.println();
|
|
|
|
} catch( IOException e ) {
|
|
|
|
e.printStackTrace();
|
2020-01-05 18:29:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-29 15:11:17 +02:00
|
|
|
try {
|
|
|
|
String textCompiled;
|
|
|
|
if( watFile != null ) {
|
|
|
|
textCompiled = new String( Files.readAllBytes( watFile.toPath() ), StandardCharsets.UTF_8 );
|
|
|
|
} else {
|
|
|
|
textCompiled = compiler.compileToText();
|
|
|
|
}
|
|
|
|
System.out.println( textCompiled );
|
|
|
|
System.out.println();
|
|
|
|
} catch( IOException e ) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2018-03-25 20:55:29 +02:00
|
|
|
}
|
2020-01-05 18:29:38 +01:00
|
|
|
super.after();
|
2018-03-25 20:55:29 +02:00
|
|
|
}
|
|
|
|
|
2018-12-06 19:59:20 +01:00
|
|
|
/**
|
|
|
|
* Compile the classes of the test.
|
2019-02-17 10:11:16 +01:00
|
|
|
*
|
|
|
|
* @throws WasmException
|
|
|
|
* if the compiling is failing
|
2018-12-06 19:59:20 +01:00
|
|
|
*/
|
2019-02-17 10:11:16 +01:00
|
|
|
public void compile() throws WasmException {
|
2020-06-13 17:18:21 +02:00
|
|
|
try {
|
|
|
|
create();
|
|
|
|
} catch( Throwable ex ) {
|
|
|
|
throwException( ex );
|
|
|
|
}
|
|
|
|
compile( ScriptEngine.NodeJS );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compile the classes of the script engine if not already compiled.
|
|
|
|
*
|
|
|
|
* @param script
|
|
|
|
* the script engine
|
|
|
|
* @return the compiled main file
|
|
|
|
* @throws WasmException
|
|
|
|
* if the compiling is failing
|
|
|
|
*/
|
|
|
|
public File compile( ScriptEngine script ) throws WasmException {
|
2021-08-29 15:11:17 +02:00
|
|
|
Object fileOrException = compiledFiles.get( script );
|
|
|
|
if( fileOrException instanceof File ) {
|
2020-06-13 17:18:21 +02:00
|
|
|
// compile only once
|
2021-08-29 15:11:17 +02:00
|
|
|
return (File)fileOrException;
|
|
|
|
}
|
|
|
|
if( fileOrException instanceof Throwable ) {
|
|
|
|
throwException( (Throwable)fileOrException );
|
2020-06-13 17:18:21 +02:00
|
|
|
}
|
|
|
|
|
2019-04-19 15:57:56 +02:00
|
|
|
compiler.setProperty( JWebAssembly.DEBUG_NAMES, "true" );
|
|
|
|
assertEquals( "true", compiler.getProperty( JWebAssembly.DEBUG_NAMES ) );
|
2020-06-13 17:18:21 +02:00
|
|
|
compiler.setProperty( JWebAssembly.WASM_USE_GC, script.useGC );
|
2019-02-24 20:02:36 +01:00
|
|
|
|
2021-08-29 15:11:17 +02:00
|
|
|
File file = null;
|
2018-03-25 20:55:29 +02:00
|
|
|
try {
|
2020-06-13 17:18:21 +02:00
|
|
|
String name = script.name();
|
|
|
|
if( name.contains( "Wat" ) ) {
|
|
|
|
file = newFile( name + ".wat" );
|
|
|
|
compiler.compileToText( file );
|
|
|
|
} else {
|
|
|
|
file = newFile( name + ".wasm" );
|
|
|
|
compiler.compileToBinary( file );
|
2018-08-14 22:36:43 +02:00
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
compiledFiles.put( script, file );
|
2019-02-19 21:00:05 +01:00
|
|
|
} catch( Throwable ex ) {
|
2021-08-29 15:11:17 +02:00
|
|
|
compiledFiles.put( script, ex );
|
2019-02-19 21:00:05 +01:00
|
|
|
throwException( ex );
|
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare the node node script.
|
|
|
|
*
|
|
|
|
* @param script
|
|
|
|
* the script engine
|
|
|
|
* @return the script file
|
|
|
|
* @throws IOException
|
|
|
|
* if any error occur.
|
|
|
|
*/
|
|
|
|
private File prepareNodeJs( ScriptEngine script ) throws IOException {
|
|
|
|
File scriptFile = scriptFiles.get( script );
|
|
|
|
if( scriptFile == null ) {
|
|
|
|
compile( script );
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "nodetest.js", "{test}", script.name() );
|
|
|
|
scriptFiles.put( script, scriptFile );
|
2020-06-13 17:18:21 +02:00
|
|
|
}
|
|
|
|
return scriptFile;
|
2019-02-19 21:00:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare the node wabt module.
|
|
|
|
*
|
2020-06-13 17:18:21 +02:00
|
|
|
* @param script
|
|
|
|
* the script engine
|
|
|
|
* @return the script file
|
2019-02-19 21:00:05 +01:00
|
|
|
* @throws Exception
|
|
|
|
* if any error occur.
|
|
|
|
*/
|
2020-06-13 17:18:21 +02:00
|
|
|
private File prepareNodeWat( ScriptEngine script ) throws Exception {
|
|
|
|
File scriptFile = scriptFiles.get( script );
|
|
|
|
if( scriptFile == null ) {
|
|
|
|
compile( script );
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "WatTest.js", "{test}", script.name() );
|
|
|
|
scriptFiles.put( script, scriptFile );
|
2018-08-14 22:36:43 +02:00
|
|
|
|
2020-05-02 16:09:50 +02:00
|
|
|
if( !npmWabtNightly ) {
|
|
|
|
npmWabtNightly = true;
|
|
|
|
ProcessBuilder processBuilder = new ProcessBuilder( "npm", "install", "-g", "wabt@nightly" );
|
|
|
|
if( IS_WINDOWS ) {
|
|
|
|
processBuilder.command().add( 0, "cmd" );
|
|
|
|
processBuilder.command().add( 1, "/C" );
|
2021-07-10 22:57:10 +02:00
|
|
|
} else {
|
|
|
|
processBuilder.command().add( 0, "sudo" ); // with Github actions there is no write right
|
2020-05-02 16:09:50 +02:00
|
|
|
}
|
|
|
|
execute( processBuilder );
|
|
|
|
}
|
2020-05-02 21:42:32 +02:00
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
return scriptFile;
|
2020-05-02 21:42:32 +02:00
|
|
|
}
|
2018-11-11 13:31:37 +01:00
|
|
|
|
2020-05-02 21:42:32 +02:00
|
|
|
/**
|
|
|
|
* Get the path of the global installed module pathes.
|
|
|
|
*
|
|
|
|
* @return the path
|
|
|
|
* @throws Exception
|
|
|
|
* if any error occur.
|
|
|
|
*/
|
|
|
|
private static String getNodeModulePath() throws Exception {
|
|
|
|
if( nodeModulePath == null ) {
|
|
|
|
ProcessBuilder processBuilder = new ProcessBuilder( "npm", "root", "-g" );
|
2018-11-11 12:27:40 +01:00
|
|
|
if( IS_WINDOWS ) {
|
|
|
|
processBuilder.command().add( 0, "cmd" );
|
|
|
|
processBuilder.command().add( 1, "/C" );
|
|
|
|
}
|
2020-05-02 21:42:32 +02:00
|
|
|
Process process = processBuilder.start();
|
2020-06-13 17:18:21 +02:00
|
|
|
process.waitFor();
|
2020-06-21 13:02:58 +02:00
|
|
|
nodeModulePath = readStream( process.getInputStream(), false ).trim(); // module install path
|
2020-05-02 21:42:32 +02:00
|
|
|
System.out.println( "node global module path: " + nodeModulePath );
|
|
|
|
|
2018-03-25 20:55:29 +02:00
|
|
|
}
|
2020-05-02 21:42:32 +02:00
|
|
|
return nodeModulePath;
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
|
|
|
|
2019-02-17 10:11:16 +01:00
|
|
|
/**
|
|
|
|
* Prepare the Wat2Wasm tool if not already do. Fire an JUnit fail if the process produce an error.
|
|
|
|
*
|
2020-06-13 17:18:21 +02:00
|
|
|
* @param script
|
|
|
|
* the script engine
|
|
|
|
* @return the script file
|
2019-02-17 10:11:16 +01:00
|
|
|
* @throws Exception
|
|
|
|
* if any error occur.
|
|
|
|
*/
|
2020-06-13 17:18:21 +02:00
|
|
|
private File prepareWat2Wasm( ScriptEngine script ) throws Exception {
|
|
|
|
File scriptFile = scriptFiles.get( script );
|
|
|
|
if( scriptFile == null ) {
|
2020-06-18 21:28:44 +02:00
|
|
|
File watFile = compile( script );
|
2019-02-17 10:11:16 +01:00
|
|
|
String cmd = wat2Wasm.getCommand();
|
2020-06-18 23:05:58 +02:00
|
|
|
File wat2WasmFile = new File( getRoot(), script.name() + ".wasm" );
|
2019-02-17 10:11:16 +01:00
|
|
|
// the wat2wasm tool
|
|
|
|
ProcessBuilder processBuilder =
|
2020-04-26 14:03:06 +02:00
|
|
|
new ProcessBuilder( cmd, watFile.toString(), "-o", wat2WasmFile.toString(), "--debug-names", "--enable-all" );
|
2019-02-17 10:11:16 +01:00
|
|
|
execute( processBuilder );
|
2020-06-18 22:53:22 +02:00
|
|
|
assertTrue( wat2WasmFile.isFile() );
|
2019-02-17 10:11:16 +01:00
|
|
|
|
|
|
|
// create the node script
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "nodetest.js", "{test}", script.name() );
|
2019-02-17 10:11:16 +01:00
|
|
|
}
|
2020-06-13 17:18:21 +02:00
|
|
|
return scriptFile;
|
2019-02-17 10:11:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a external process and redirect the output to the console. Fire an JUnit fail if the process produce an error.
|
|
|
|
*
|
|
|
|
* @param processBuilder
|
|
|
|
* the process definition
|
|
|
|
* @throws Exception
|
|
|
|
* if any error occur
|
|
|
|
*/
|
|
|
|
private void execute( ProcessBuilder processBuilder ) throws Exception {
|
|
|
|
processBuilder.directory( getRoot() );
|
|
|
|
processBuilder.redirectOutput( Redirect.INHERIT );
|
|
|
|
processBuilder.redirectError( Redirect.INHERIT );
|
|
|
|
System.out.println( String.join( " ", processBuilder.command() ) );
|
|
|
|
Process process = processBuilder.start();
|
|
|
|
int exitCode = process.waitFor();
|
|
|
|
if( exitCode != 0 ) {
|
2020-06-21 13:02:58 +02:00
|
|
|
fail( readStream( process.getErrorStream(), false ) + "\nExit code: " + exitCode + " from: " + processBuilder.command().get( 0 ) );
|
2019-02-17 10:11:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-04 20:51:50 +02:00
|
|
|
/**
|
|
|
|
* Load a script resource, patch it and save it
|
|
|
|
*
|
2020-06-14 15:37:11 +02:00
|
|
|
* @param script
|
|
|
|
* the script engine
|
2017-04-04 20:51:50 +02:00
|
|
|
* @param name
|
2020-06-14 15:26:13 +02:00
|
|
|
* the template resource name
|
2019-02-17 10:11:16 +01:00
|
|
|
* @param placeholder
|
|
|
|
* A placeholder that should be replaced.
|
|
|
|
* @param value
|
|
|
|
* the replacing value.
|
2017-04-04 20:51:50 +02:00
|
|
|
* @return The saved file name
|
|
|
|
* @throws IOException
|
|
|
|
* if any IO error occur
|
|
|
|
*/
|
2020-06-14 15:37:11 +02:00
|
|
|
private File createScript( ScriptEngine script, String name, String placeholder, String value ) throws IOException {
|
|
|
|
File file = newFile( script.name() + "Test.js" );
|
2017-04-04 20:51:50 +02:00
|
|
|
URL scriptUrl = getClass().getResource( name );
|
2020-06-21 13:02:58 +02:00
|
|
|
String template = readStream( scriptUrl.openStream(), true );
|
2020-06-14 15:37:11 +02:00
|
|
|
template = template.replace( placeholder, value );
|
2017-04-04 20:51:50 +02:00
|
|
|
try (FileOutputStream scriptStream = new FileOutputStream( file )) {
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptStream.write( template.getBytes( StandardCharsets.UTF_8 ) );
|
2017-04-04 20:51:50 +02:00
|
|
|
}
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
/**
|
|
|
|
* Run a test single test. It run the method in Java and call it via node in the WenAssembly. If the result are
|
|
|
|
* different it fire an error.
|
|
|
|
*
|
2017-04-04 20:51:50 +02:00
|
|
|
* @param script
|
|
|
|
* The script engine
|
2017-04-08 20:43:41 +02:00
|
|
|
* @param methodName
|
|
|
|
* the method name of the test.
|
2017-04-02 22:26:53 +02:00
|
|
|
* @param params
|
|
|
|
* the parameters for the method
|
|
|
|
*/
|
2017-04-04 20:51:50 +02:00
|
|
|
public void test( ScriptEngine script, String methodName, Object... params ) {
|
|
|
|
Object expected;
|
2017-04-02 22:26:53 +02:00
|
|
|
try {
|
|
|
|
Class<?>[] types = new Class[params.length];
|
|
|
|
for( int i = 0; i < types.length; i++ ) {
|
|
|
|
Class<?> type = params[i].getClass();
|
|
|
|
switch( type.getName() ) {
|
2018-03-31 19:34:27 +02:00
|
|
|
case "java.lang.Byte":
|
|
|
|
type = byte.class;
|
|
|
|
break;
|
|
|
|
case "java.lang.Short":
|
|
|
|
type = short.class;
|
|
|
|
break;
|
|
|
|
case "java.lang.Character":
|
|
|
|
type = char.class;
|
|
|
|
break;
|
2017-04-02 22:26:53 +02:00
|
|
|
case "java.lang.Integer":
|
|
|
|
type = int.class;
|
|
|
|
break;
|
2017-04-09 18:43:29 +02:00
|
|
|
case "java.lang.Long":
|
|
|
|
type = long.class;
|
|
|
|
break;
|
|
|
|
case "java.lang.Float":
|
|
|
|
type = float.class;
|
|
|
|
break;
|
|
|
|
case "java.lang.Double":
|
|
|
|
type = double.class;
|
|
|
|
break;
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
|
|
|
types[i] = type;
|
|
|
|
}
|
|
|
|
Method method = null;
|
|
|
|
for( int i = 0; i < classes.length; i++ ) {
|
|
|
|
try {
|
|
|
|
Class<?> clazz = classes[i];
|
|
|
|
method = clazz.getDeclaredMethod( methodName, types );
|
2018-12-12 21:28:16 +01:00
|
|
|
break;
|
2017-04-02 22:26:53 +02:00
|
|
|
} catch( NoSuchMethodException ex ) {
|
|
|
|
if( i == classes.length - 1 ) {
|
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
method.setAccessible( true );
|
2017-04-04 20:51:50 +02:00
|
|
|
expected = method.invoke( null, params );
|
2018-03-31 19:34:27 +02:00
|
|
|
if( expected instanceof Character ) { // WASM does not support char that it is number
|
|
|
|
expected = new Integer( ((Character)expected).charValue() );
|
|
|
|
}
|
2018-12-14 21:19:47 +01:00
|
|
|
if( expected instanceof Boolean ) { // WASM does not support boolean that it is number
|
|
|
|
expected = new Integer( ((Boolean)expected) ? 1 : 0 );
|
|
|
|
}
|
2017-04-02 22:26:53 +02:00
|
|
|
|
2019-10-20 12:43:26 +02:00
|
|
|
Object actual;
|
|
|
|
String actualStr = evalWasm( script, methodName, params );
|
2019-06-27 18:43:44 +02:00
|
|
|
if( expected instanceof Double ) { // handle different string formating of double values
|
2019-10-20 12:43:26 +02:00
|
|
|
try {
|
|
|
|
actual = Double.valueOf( actualStr );
|
|
|
|
} catch( NumberFormatException ex ) {
|
|
|
|
actual = actualStr;
|
|
|
|
}
|
2019-07-13 20:17:23 +02:00
|
|
|
} else if( expected instanceof Float ) { // handle different string formating of float values
|
2019-10-20 12:43:26 +02:00
|
|
|
try {
|
|
|
|
actual = Float.valueOf( actualStr );
|
|
|
|
} catch( NumberFormatException ex ) {
|
|
|
|
actual = actualStr;
|
|
|
|
}
|
2019-06-27 18:43:44 +02:00
|
|
|
} else {
|
2019-10-20 12:43:26 +02:00
|
|
|
expected = String.valueOf( expected );
|
|
|
|
actual = actualStr;
|
2019-06-27 18:43:44 +02:00
|
|
|
}
|
2019-10-20 12:43:26 +02:00
|
|
|
assertEquals( expected, actual );
|
2020-01-01 23:04:51 +01:00
|
|
|
} catch( InvocationTargetException ex ) {
|
|
|
|
failed = true;
|
|
|
|
throwException( ex.getTargetException() );
|
2018-03-25 20:55:29 +02:00
|
|
|
} catch( Throwable ex ) {
|
|
|
|
failed = true;
|
2017-04-08 20:43:41 +02:00
|
|
|
throwException( ex );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-08 19:10:31 +02:00
|
|
|
/**
|
|
|
|
* Compile the sources and create the ProcessBuilder
|
|
|
|
*
|
|
|
|
* @param script
|
|
|
|
* The script engine
|
|
|
|
* @return ProcessBuilder to execute the test
|
|
|
|
* @throws Exception
|
|
|
|
* if any error occur
|
|
|
|
*/
|
|
|
|
private ProcessBuilder createCommand( ScriptEngine script ) throws Exception {
|
2020-05-30 23:06:29 +02:00
|
|
|
compiler.setProperty( JWebAssembly.WASM_USE_GC, script.useGC );
|
2019-09-08 19:10:31 +02:00
|
|
|
switch( script ) {
|
|
|
|
case SpiderMonkey:
|
2020-06-13 17:18:21 +02:00
|
|
|
return spiderMonkeyCommand( true, script );
|
2019-09-08 19:10:31 +02:00
|
|
|
case SpiderMonkeyWat:
|
2020-06-13 17:18:21 +02:00
|
|
|
return spiderMonkeyCommand( false, script );
|
2019-09-08 19:10:31 +02:00
|
|
|
case SpiderMonkeyGC:
|
2020-06-13 17:18:21 +02:00
|
|
|
return spiderMonkeyCommand( true, script );
|
2019-09-12 21:54:35 +02:00
|
|
|
case SpiderMonkeyWatGC:
|
2020-06-13 17:18:21 +02:00
|
|
|
return spiderMonkeyCommand( false, script );
|
2019-09-08 19:10:31 +02:00
|
|
|
case NodeJS:
|
2020-06-12 22:09:42 +02:00
|
|
|
case NodeJsGC:
|
2020-06-13 17:18:21 +02:00
|
|
|
return nodeJsCommand( prepareNodeJs( script ) );
|
2019-09-08 19:10:31 +02:00
|
|
|
case NodeWat:
|
2020-06-12 22:09:42 +02:00
|
|
|
case NodeWatGC:
|
2020-06-13 17:18:21 +02:00
|
|
|
ProcessBuilder processBuilder = nodeJsCommand( prepareNodeWat( script ) );
|
2020-05-02 21:42:32 +02:00
|
|
|
processBuilder.environment().put( "NODE_PATH", getNodeModulePath() );
|
|
|
|
return processBuilder;
|
2019-09-08 19:10:31 +02:00
|
|
|
case Wat2Wasm:
|
2021-01-12 22:40:43 +01:00
|
|
|
case Wat2WasmGC:
|
2020-06-13 17:18:21 +02:00
|
|
|
return nodeJsCommand( prepareWat2Wasm( script ) );
|
2019-09-08 19:10:31 +02:00
|
|
|
default:
|
|
|
|
throw new IllegalStateException( script.toString() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-08 20:43:41 +02:00
|
|
|
/**
|
|
|
|
* Evaluate the wasm exported function.
|
|
|
|
*
|
|
|
|
* @param script
|
|
|
|
* The script engine
|
|
|
|
* @param methodName
|
|
|
|
* the method name of the test.
|
|
|
|
* @param params
|
|
|
|
* the parameters for the method
|
|
|
|
* @return the output of the script
|
|
|
|
*/
|
|
|
|
public String evalWasm( ScriptEngine script, String methodName, Object... params ) {
|
2018-08-03 23:07:25 +02:00
|
|
|
ProcessBuilder processBuilder = null;
|
2017-04-08 20:43:41 +02:00
|
|
|
try {
|
2019-06-17 19:00:16 +02:00
|
|
|
if( testData != null ) {
|
|
|
|
// data are available as block data
|
|
|
|
Map<String, String> resultMap = testResults.get( script );
|
|
|
|
if( resultMap != null ) {
|
|
|
|
return resultMap.get( methodName );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// block data then write single test data
|
|
|
|
writeJsonTestData( Collections.singletonMap( methodName, params ) );
|
|
|
|
}
|
|
|
|
|
2019-09-08 19:10:31 +02:00
|
|
|
processBuilder = createCommand( script );
|
2017-04-04 20:51:50 +02:00
|
|
|
processBuilder.directory( getRoot() );
|
2017-04-02 22:26:53 +02:00
|
|
|
Process process = processBuilder.start();
|
2020-04-13 16:00:12 +02:00
|
|
|
|
|
|
|
String stdoutMessage = "";
|
|
|
|
String errorMessage = "";
|
|
|
|
do {
|
2020-04-18 11:25:11 +02:00
|
|
|
if( process.getInputStream().available() > 0 ) {
|
2020-06-21 13:02:58 +02:00
|
|
|
stdoutMessage += readStream( process.getInputStream(), false );
|
2020-04-18 11:25:11 +02:00
|
|
|
}
|
|
|
|
if( process.getErrorStream().available() > 0 ) {
|
2020-06-21 13:02:58 +02:00
|
|
|
errorMessage += readStream( process.getErrorStream(), false );
|
2020-04-18 11:25:11 +02:00
|
|
|
}
|
2020-04-13 16:00:12 +02:00
|
|
|
}
|
|
|
|
while( !process.waitFor( 10, TimeUnit.MILLISECONDS ) );
|
2020-06-21 13:02:58 +02:00
|
|
|
stdoutMessage += readStream( process.getInputStream(), false );
|
|
|
|
errorMessage += readStream( process.getErrorStream(), false );
|
2020-04-13 16:00:12 +02:00
|
|
|
int exitCode = process.exitValue();
|
2022-03-19 20:37:20 +01:00
|
|
|
|
|
|
|
// read the result from file
|
|
|
|
File resultFile = new File( getRoot(), "testresult.json" );
|
|
|
|
String result = null;
|
|
|
|
if( resultFile.exists() ) {
|
|
|
|
try( InputStreamReader jsonData = new InputStreamReader( new FileInputStream( new File( getRoot(), "testresult.json" ) ), StandardCharsets.UTF_8 ) ) {
|
|
|
|
@SuppressWarnings( "unchecked" )
|
|
|
|
Map<String, String> map = new Gson().fromJson( jsonData, Map.class );
|
|
|
|
if( testData != null ) {
|
|
|
|
testResults.put( script, map );
|
|
|
|
}
|
|
|
|
result = map.get( methodName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-13 16:00:12 +02:00
|
|
|
if( exitCode != 0 || !stdoutMessage.isEmpty() || !errorMessage.isEmpty() ) {
|
|
|
|
System.err.println( stdoutMessage );
|
|
|
|
System.err.println( errorMessage );
|
2022-03-19 20:37:20 +01:00
|
|
|
fail( stdoutMessage + '\n' + errorMessage + '\n' + result + "\nExit code: " + exitCode );
|
2019-06-17 19:00:16 +02:00
|
|
|
}
|
|
|
|
|
2022-03-19 20:37:20 +01:00
|
|
|
return result;
|
2018-03-25 20:55:29 +02:00
|
|
|
} catch( Throwable ex ) {
|
|
|
|
failed = true;
|
2018-08-03 23:07:25 +02:00
|
|
|
ex.printStackTrace();
|
|
|
|
|
|
|
|
if( processBuilder != null ) {
|
|
|
|
String executable = processBuilder.command().get( 0 );
|
|
|
|
System.err.println( executable );
|
|
|
|
File exec = new File(executable);
|
|
|
|
System.err.println( exec.exists() );
|
|
|
|
exec = exec.getParentFile();
|
|
|
|
if( exec != null ) {
|
|
|
|
System.err.println( Arrays.toString( exec.list() ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
throwException( ex );
|
2017-04-08 20:43:41 +02:00
|
|
|
return null;
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-04 20:51:50 +02:00
|
|
|
/**
|
|
|
|
* Create a ProcessBuilder for spider monkey script shell.
|
|
|
|
*
|
2020-06-13 17:18:21 +02:00
|
|
|
* @param binary
|
|
|
|
* true, if the WASM format should be test; false, if the WAT format should be tested.
|
|
|
|
* @param script
|
|
|
|
* the script engine
|
2017-04-04 20:51:50 +02:00
|
|
|
* @return the value from the script
|
|
|
|
* @throws IOException
|
|
|
|
* if the download failed
|
|
|
|
*/
|
2020-06-13 17:18:21 +02:00
|
|
|
private ProcessBuilder spiderMonkeyCommand( boolean binary, ScriptEngine script ) throws IOException {
|
|
|
|
boolean gc = Boolean.valueOf( script.useGC );
|
|
|
|
File scriptFile = scriptFiles.get( script );
|
|
|
|
if( scriptFile == null ) {
|
|
|
|
File file = compile( script );
|
|
|
|
if( gc ) {
|
|
|
|
if( binary ) {
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "SpiderMonkeyTest.js", "{test.wasm}", file.getName() );
|
2020-06-13 17:18:21 +02:00
|
|
|
} else {
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "SpiderMonkeyWatTest.js", "{test}", script.name() );
|
2019-04-19 15:57:56 +02:00
|
|
|
}
|
2020-04-04 11:40:35 +02:00
|
|
|
} else {
|
2020-06-13 17:18:21 +02:00
|
|
|
if( binary ) {
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "SpiderMonkeyTest.js", "{test.wasm}", file.getName() );
|
2020-06-13 17:18:21 +02:00
|
|
|
} else {
|
2020-06-14 15:37:11 +02:00
|
|
|
scriptFile = createScript( script, "SpiderMonkeyWatTest.js", "{test}", script.name() );
|
2020-04-04 11:40:35 +02:00
|
|
|
}
|
2019-04-19 15:57:56 +02:00
|
|
|
}
|
2020-06-14 15:26:13 +02:00
|
|
|
scriptFiles.put( script, scriptFile );
|
2019-04-19 15:57:56 +02:00
|
|
|
}
|
2020-04-04 11:40:35 +02:00
|
|
|
|
2020-06-13 17:18:21 +02:00
|
|
|
ProcessBuilder process = new ProcessBuilder( spiderMonkey.getCommand(), scriptFile.getAbsolutePath() );
|
2019-09-22 17:09:12 +02:00
|
|
|
if( gc ) {
|
|
|
|
process.command().add( 1, "--wasm-gc" );
|
|
|
|
}
|
|
|
|
return process;
|
2017-04-04 20:51:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-11 12:27:40 +01:00
|
|
|
* The executable of the node command.
|
2017-04-04 20:51:50 +02:00
|
|
|
*
|
2018-11-11 12:27:40 +01:00
|
|
|
* @return the node executable
|
2020-06-07 12:00:40 +02:00
|
|
|
* @throws IOException
|
|
|
|
* if any I/O error occur
|
2017-04-04 20:51:50 +02:00
|
|
|
*/
|
2018-11-11 12:27:40 +01:00
|
|
|
@Nonnull
|
2020-06-07 12:00:40 +02:00
|
|
|
private static String nodeExecuable() throws IOException {
|
|
|
|
String command = node.getNodeDir();
|
2018-08-03 23:23:36 +02:00
|
|
|
if( command == null ) {
|
|
|
|
command = "node";
|
|
|
|
} else {
|
2018-08-14 22:36:43 +02:00
|
|
|
if( IS_WINDOWS ) {
|
2018-08-03 23:23:36 +02:00
|
|
|
command += "/node";
|
|
|
|
} else {
|
|
|
|
command += "/bin/node";
|
|
|
|
}
|
|
|
|
}
|
2018-11-11 12:27:40 +01:00
|
|
|
return command;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a ProcessBuilder for node.js
|
|
|
|
*
|
2020-06-13 17:18:21 +02:00
|
|
|
* @param nodeScript
|
2018-11-11 12:27:40 +01:00
|
|
|
* the path to the script that should be executed
|
|
|
|
* @return the value from the script
|
2020-06-07 12:00:40 +02:00
|
|
|
* @throws IOException
|
|
|
|
* if any I/O error occur
|
2018-11-11 12:27:40 +01:00
|
|
|
*/
|
2020-06-13 17:18:21 +02:00
|
|
|
private static ProcessBuilder nodeJsCommand( File nodeScript ) throws IOException {
|
2018-11-11 12:27:40 +01:00
|
|
|
String command = nodeExecuable();
|
2018-11-03 18:01:42 +01:00
|
|
|
// details see with command: node --v8-options
|
2020-01-18 13:21:43 +01:00
|
|
|
ProcessBuilder processBuilder = new ProcessBuilder( command, //
|
|
|
|
"--experimental-wasm-eh", // exception handling
|
2020-06-21 13:43:39 +02:00
|
|
|
"--experimental-wasm-typed-funcref", //
|
2020-06-07 12:00:40 +02:00
|
|
|
"--experimental-wasm-gc", //
|
2020-06-13 17:18:21 +02:00
|
|
|
nodeScript.getName() );
|
2018-12-06 19:59:20 +01:00
|
|
|
if( IS_WINDOWS ) {
|
|
|
|
processBuilder.command().add( 0, "cmd" );
|
|
|
|
processBuilder.command().add( 1, "/C" );
|
|
|
|
}
|
|
|
|
return processBuilder;
|
2017-04-04 20:51:50 +02:00
|
|
|
}
|
|
|
|
|
2017-04-02 22:26:53 +02:00
|
|
|
/**
|
|
|
|
* Reads a stream into a String.
|
|
|
|
*
|
|
|
|
* @param input
|
|
|
|
* the InputStream
|
|
|
|
* @return the string
|
2020-04-13 16:00:12 +02:00
|
|
|
* @throws IOException
|
|
|
|
* if an I/O error occurs.
|
2017-04-02 22:26:53 +02:00
|
|
|
*/
|
2019-02-17 10:11:16 +01:00
|
|
|
@SuppressWarnings( "resource" )
|
2020-06-21 13:02:58 +02:00
|
|
|
public static String readStream( InputStream input, boolean all ) throws IOException {
|
2020-04-13 21:58:00 +02:00
|
|
|
byte[] bytes = new byte[8192];
|
|
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
|
int count;
|
2020-06-21 13:02:58 +02:00
|
|
|
while( (all || input.available() > 0) && (count = input.read( bytes )) > 0 ) {
|
2020-04-13 21:58:00 +02:00
|
|
|
stream.write( bytes, 0, count );
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
2020-04-13 21:58:00 +02:00
|
|
|
return new String( stream.toByteArray() );
|
2017-04-02 22:26:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Throw any exception independent of signatures
|
|
|
|
*
|
|
|
|
* @param exception
|
|
|
|
* the exception
|
|
|
|
* @throws T
|
|
|
|
* a generic helper
|
|
|
|
*/
|
2020-06-13 17:18:21 +02:00
|
|
|
@SuppressWarnings( "unchecked" )
|
2017-04-02 22:26:53 +02:00
|
|
|
public static <T extends Throwable> void throwException( Throwable exception ) throws T {
|
|
|
|
throw (T)exception;
|
|
|
|
}
|
|
|
|
}
|