/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package abs.backend.erlang;
import static org.junit.Assert.assertEquals;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.EnumSet;
import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.Assume;
import abs.ABSTest;
import abs.backend.BackendTestDriver;
import abs.backend.common.InternalBackendException;
import abs.backend.common.SemanticTests;
import abs.frontend.ast.*;
import com.google.common.io.Files;
public class ErlangTestDriver extends ABSTest implements BackendTestDriver {
@Override
public String toString() {
return "Erlang";
}
public static boolean checkErlang() {
/* TODO: Should be checked earlier instead, before configuring the JUnit suites. */
String doAbs = System.getProperty("abs.junit.erlang");
Assume.assumeFalse("Erlang tests disabled via -Dabs.junit.erlang", "0".equals(doAbs));
if (SemanticTests.checkProg("erl")) {
// http://stackoverflow.com/a/9561398/60462
ProcessBuilder pb = new ProcessBuilder("erl", "-eval", "erlang:display(erlang:system_info(otp_release)), halt().", "-noshell");
try {
Process p = pb.start();
InputStreamReader r = new InputStreamReader(p.getInputStream());
BufferedReader b = new BufferedReader(r);
Assert.assertEquals(0, p.waitFor());
String version = b.readLine();
java.util.regex.Pattern pat = java.util.regex.Pattern.compile("\"(\\d*).*");
Matcher m = pat.matcher(version);
Assert.assertTrue("Could not identify Erlang version: "+version, m.matches());
String v = m.group(1);
Assume.assumeTrue("Need Erlang R17 or better.",Integer.parseInt(v) >= 17);
} catch (IOException e) {
return false;
} catch (InterruptedException e) {
return false;
}
}
return true;
}
@Override
public void assertEvalEquals(String absCode, boolean value) throws Exception {
if (value)
assertEvalTrue(absCode);
else
assertEvalFails(absCode);
}
@Override
public void assertEvalFails(String absCode) throws Exception {
Assert.assertEquals(null, runAndCheck(absCode));
}
@Override
public void assertEvalTrue(String absCode) throws Exception {
Assert.assertEquals("true", runAndCheck(absCode));
}
/**
* Parses, complies, runs given code and returns value of testresult.
*
* @param absCode
* @return either the Result, or if an execution error occurred null
*/
private String runAndCheck(String absCode) throws Exception {
File f = null;
try {
f = Files.createTempDir();
f.deleteOnExit();
Model model = assertParseOk(absCode, Config.WITH_STD_LIB, Config.TYPE_CHECK, /* XXX:CI Config.WITH_LOC_INF, */ Config.WITHOUT_MODULE_NAME);
String mainModule = genCode(model, f, true);
make(f);
return run(f, mainModule);
} finally {
try {
FileUtils.deleteDirectory(f);
} catch (IOException e) {
// Ignore Ex, File should be deleted anyway
}
}
}
public void generateAndCompile(Model model) throws Exception {
File f = null;
try {
f = Files.createTempDir();
f.deleteOnExit();
genCode(model, f, false);
make(f);
} finally {
try {
FileUtils.deleteDirectory(f);
} catch (IOException e) {
// Ignore Ex, File should be deleted anyway
}
}
}
/**
* Generates Erlang code in target directory
*
* To retrieve the testresult value, we inject in the mainblock a return
* statement, which will then be the result of the execution
*
* @return the Module Name, which contains the Main Block
* @throws InternalBackendException
*
*/
private String genCode(Model model, File targetDir, boolean appendResultprinter) throws IOException, InterruptedException, InternalBackendException {
if (model.hasErrors()) {
Assert.fail(model.getErrors().getFirstError().getHelpMessage());
}
if (model.hasTypeErrors()) {
Assert.fail(model.getTypeErrors().getFirstError().getHelpMessage());
}
MainBlock mb = model.getMainBlock();
if (mb != null && appendResultprinter) {
mb.addStmt(new ReturnStmt(new List<Annotation>(),
new VarUse("testresult")));
}
ErlangBackend.compile(model, targetDir, EnumSet.of(ErlangBackend.CompileOptions.VERBOSE));
if (mb == null)
return null;
else
return mb.getModuleDecl().getName();
}
/**
* Complies code in workDir
*/
private void make(File workDir) throws Exception {
ProcessBuilder pb = new ProcessBuilder("erl", "-pa", "ebin", "-noshell", "-noinput", "-eval",
"case make:all() of up_to_date -> halt(0); _ -> halt(1) end.");
pb.directory(workDir);
pb.inheritIO();
Process p = pb.start();
Assert.assertEquals("Compile failed", 0, p.waitFor());
}
private static final String RUN_SCRIPT=
"#!/usr/bin/env escript\n"+
"%%! -pa absmodel/ebin -pa absmodel/deps/cowboy/ebin -pa absmodel/deps/cowlib/ebin -pa absmodel/deps/ranch/ebin -pa absmodel/deps/jsx/ebin -pa ebin\n"+
"main(Arg)->"+
"V=runtime:start(Arg),"+
"timer:sleep(10),"+
"io:format(\"RES=~p~n\",[V]).";
/**
* Executes mainModule
*
* We replace the run script by a new version, which will write the Result
* to STDOUT Furthermore to detect faults, we have a Timeout process, which
* will kill the runtime system after 2 seconds
*/
private String run(File workDir, String mainModule) throws Exception {
String val = null;
File runFile = new File(workDir, "run");
Files.write(RUN_SCRIPT, runFile, Charset.forName("UTF-8"));
runFile.setExecutable(true);
ProcessBuilder pb = new ProcessBuilder(runFile.getAbsolutePath(), mainModule);
pb.directory(workDir);
pb.redirectErrorStream(true);
Process p = pb.start();
Thread t = new Thread(new TimeoutThread(p));
t.start();
// Search for result
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
while (true) {
String line = br.readLine();
if (line == null)
break;
if (line.startsWith("RES="))
val = line.split("=")[1];
}
int res = p.waitFor();
t.interrupt();
if (res != 0)
return null;
return val;
}
@Override
public void assertEvalTrue(Model model) throws Exception {
File f = null;
try {
f = Files.createTempDir();
f.deleteOnExit();
String mainModule = genCode(model, f, true);
make(f);
assertEquals("true",run(f, mainModule));
} finally {
try {
FileUtils.deleteDirectory(f);
} catch (IOException e) {
// Ignore Ex, File should be deleted anyway
}
}
}
}
class TimeoutThread implements Runnable {
private final Process p;
public TimeoutThread(Process p) {
super();
this.p = p;
}
@Override
public void run() {
try {
Thread.sleep(2000);
p.destroy();
} catch (InterruptedException e) {
}
}
}