/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.itest;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.OSUtil;
public abstract class AntBasedTests {
protected static final String EXEC_CEYLON = "script.ceylon";
protected static final String ARG_VERBOSE = "arg.verbose";
protected static final String ARG_SRC = "arg.src";
protected static final String ARG_OUT = "arg.out";
protected Method mainMethod;
protected final File originalBuildfile;
protected File actualBuildFile;
private Class<?> securityManagerClass;
private ByteArrayOutputStream redirectedStdout;
private ByteArrayOutputStream redirectedStderr;
private PrintStream savedStderr;
private PrintStream savedStdout;
private SecurityManager savedSecurityManager;
private Properties savedProperties;
private File out;
private final URL[] antJarUrls;
private final static String errorMessage = "ant.home not set, nor ANT_HOME, cannot find it in PATH,\n"
+"or cannot find lib/ant.jar in it: cannot run ant integration tests\n"
+"Find the path to you ant binary: `which ant` (mine is /usr/bin/ant)\n"
+"Check that it's not a symlink: `ls -lsa /usr/bin/ant` (mine points to /usr/share/ant/bin/ant)\n"
+"Remove 'bin/ant' from the end (mine is /usr/share/ant)\n"
+"Set it in Eclipse's Ant run configuration for these tests: \n"
+" Right-click on AntBasedTest.java > Run as > Run configurations...\n"
+" Arguments > VM Arguments: -Dant.home=/usr/share/ant";
public AntBasedTests(String buildfileResource) throws Exception {
originalBuildfile = new File(buildfileResource);
String antHome = System.getProperty("ant.home");
if(antHome == null)
antHome = detectAnt();
if (antHome == null) {
throw new Exception(errorMessage);
}
File[] antJars = new File(antHome, "lib").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().matches("ant(-launcher)?\\.jar");
}
});
if(antJars.length == 0) {
throw new Exception(errorMessage);
}
/*
* Create a class loader with just the jars we need to run ant
* make the parent the bootstrap class loader, because ant is on
* the CLASSPATH (in eclipse at least) and we want to run the tests
* using the ant version given by ant.home system property
* Note this uses the ant.jar from build/lib, so doesn't pick up
* changes done in eclipse without ant recompiling them
*/
antJarUrls = new URL[antJars.length+1];
int ii = 0;
for (File antJar : antJars) {
antJarUrls[ii] = antJar.toURI().toURL();
ii++;
}
antJarUrls[antJarUrls.length-1] = getCeylonAntJar().toURI().toURL();
}
private String detectAnt() {
String home = System.getenv("ANT_HOME");
if(home != null && !home.isEmpty() && new File(home).isDirectory())
return home;
String path = System.getenv("PATH");
if(path == null)
return null;
for(String pathPart : path.split(Pattern.quote(File.pathSeparator))){
File pathDir = new File(pathPart);
if(!pathDir.isDirectory())
continue;
for(File exec : pathDir.listFiles()){
// first filter on name: cheaper than fstat
if(!exec.getName().equalsIgnoreCase("ant"))
continue;
if(!exec.isFile() || !exec.canExecute())
continue;
// got it
return detectAntHome(exec);
}
}
return null;
}
private String detectAntHome(File antExec) {
try {
File expandedFile = antExec.getCanonicalFile();
System.err.println("Found ant at "+antExec.getAbsolutePath()+" => "+expandedFile);
File binFile = expandedFile.getParentFile();
if(!binFile.getName().equalsIgnoreCase("bin"))
return null;
File antHome = binFile.getParentFile();
if(antHome == null)
return null;
return antHome.getAbsolutePath();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private void initMain() throws Exception {
ClassLoader antClassloader = new URLClassLoader(antJarUrls, ClassLoader.getSystemClassLoader().getParent());
Class<?> antMain = antClassloader.loadClass("org.apache.tools.ant.Main");
mainMethod = antMain.getMethod("main", String[].class);
securityManagerClass = antClassloader.loadClass("org.apache.tools.ant.util.optional.NoExitSecurityManager");
}
private void search(File file, List<File> matches) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
search(child, matches);
}
} else {
if (file.getName().contains("-ant")
&& file.getName().endsWith(".jar")) {
matches.add(file);
}
}
}
private File getCeylonAntJar() throws Exception {
String property = System.getProperty("ceylon.ant.lib");
if (property != null) {
File possibleResult = new File(property);
if (possibleResult.exists()) {
return possibleResult;
} else {
throw new Exception("File specified by ceylon.ant.lib system property " + possibleResult.getAbsolutePath() + " does not exist");
}
}
File dist = new File(System.getProperty("build.dist", "build/lib"));
ArrayList<File> matches = new ArrayList<File>(1);
search(dist, matches);
if (matches.size() == 0) {
throw new Exception("Could not *ant*.jar file below " + dist.getAbsolutePath());
} else if (matches.size() > 1) {
throw new Exception("Found several *ant*.jar files below " + dist.getAbsolutePath());
}
return matches.get(0);
}
@Before
public void rewriteBuildFile() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
Document document = parser.parse(this.originalBuildfile);
if (OSUtil.isWindows()) {
/*
* On windows you can't just use the <exec> task on a .bat file
* you have to exec "cmd -c foo.bat". To avoid a maintainance
* nightmare in the test build files, we just rewrite the build
* files on the fly.
*/
XPathFactory xpfactory = XPathFactory.newInstance();
NodeList execTasks = (NodeList)xpfactory.newXPath().evaluate("//exec", document, XPathConstants.NODESET);
for (int ii = 0; ii < execTasks.getLength(); ii++) {
Element exec = (Element)execTasks.item(ii);
String executable = exec.getAttribute("executable");
exec.setAttribute("executable", "cmd");
Element argExecutable = document.createElement("arg");
argExecutable.setAttribute("value", executable);
exec.insertBefore(argExecutable, exec.getFirstChild());
Element argOptiopnC = document.createElement("arg");
argOptiopnC.setAttribute("value", "/c");
exec.insertBefore(argOptiopnC, exec.getFirstChild());
}
}
actualBuildFile = File.createTempFile("ceylon-ant-test-", "-build.xml");
TransformerFactory.newInstance().newTransformer().transform(
new DOMSource(document),
new StreamResult(actualBuildFile));
}
@Before
public void saveProperties() throws Exception {
savedProperties = new Properties(System.getProperties());
String scriptExt = OSUtil.isWindows() ? ".bat" : "";
System.setProperty(EXEC_CEYLON, "../ceylon-dist/dist/bin/ceylon" + scriptExt);
System.setProperty(Constants.PROP_CEYLON_HOME_DIR, "../ceylon-dist/dist");
System.setProperty(ARG_VERBOSE, "false");
System.setProperty(ARG_SRC, "test/src/com/redhat/ceylon/itest");
out = Files.createTempDirectory("ceylon-anttest-out-").toFile();
System.setProperty(ARG_OUT, out.getPath());
System.setProperty("basedir", new File("x").getAbsoluteFile().getParent());
}
@After
public void restoreProperties() {
System.setProperties(savedProperties);
}
@After
public void deleteOut() {
FileUtil.deleteQuietly(actualBuildFile);
FileUtil.deleteQuietly(out);
}
protected void saveGlobalState() throws Exception {
// Redirect stdout and stderr temporarily so we can capture the ant output
if (savedStdout != null || savedStderr != null
|| redirectedStderr != null || redirectedStdout != null) {
throw new RuntimeException();
}
savedStdout = System.out;
redirectedStdout = new ByteArrayOutputStream();
System.setOut(new PrintStream(redirectedStdout));
savedStderr = System.err;
redirectedStderr = new ByteArrayOutputStream();
System.setErr(new PrintStream(redirectedStdout));
savedSecurityManager = System.getSecurityManager();
System.setSecurityManager((SecurityManager)securityManagerClass.newInstance());
}
protected AntResult restoreGlobalState(int sc) {
System.setSecurityManager(savedSecurityManager);
System.err.flush();
System.setErr(savedStderr);
savedStderr = null;
String stderr = new String(redirectedStderr.toByteArray());
System.err.print(stderr);
redirectedStderr = null;
System.out.flush();
System.setOut(savedStdout);
savedStdout = null;
String stdout = new String(redirectedStdout.toByteArray());
System.out.print(stdout);
redirectedStdout = null;
return new AntResult(sc, stdout, stderr, this.out);
}
protected AntResult ant(String goal) throws Exception {
initMain();
String[] antArgs = new String[]{
"-buildfile", actualBuildFile.getPath(),
"-verbose",
goal};
/*
* Don't let ant call System.exit(): Use a security manager to prevent
* this, and also capture the argument that was passed, to make
* assertions against
*/
int sc = 0;
AntResult result;
try {
saveGlobalState();
mainMethod.invoke(null, new Object[]{antArgs});
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
try {
sc = (Integer)cause.getClass().getMethod("getStatus").invoke(cause);
} catch (Exception e2) {
throw e;
}
}finally {
result = restoreGlobalState(sc);
}
return result;
}
public static class AntResult {
private int statusCode;
private String stdout;
private String stderr;
private File out;
private AntResult(int statusCode,
String stdout,
String stderr,
File out) {
this.statusCode = statusCode;
this.stdout = stdout;
this.stderr = stderr;
this.out = out;
}
public int getStatusCode() {
return statusCode;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
public File getOut() {
return out;
}
}
protected final void assertContains(String string, String sought) {
Assert.assertTrue(string.contains(sought));
}
protected final void assertContainsMatch(String string, String pattern) {
Assert.assertTrue(Pattern.compile(pattern).matcher(string).find());
}
protected final void assertContainsMatch(String string, Pattern pattern) {
Assert.assertTrue(pattern.matcher(string).find());
}
protected final void assertNotContains(String string, String sought) {
Assert.assertFalse(string.contains(sought));
}
protected final void assertNotContainsMatch(String string, String pattern) {
Assert.assertFalse(Pattern.compile(pattern).matcher(string).find());
}
protected final void assertNotContainsMatch(String string, Pattern pattern) {
Assert.assertFalse(pattern.matcher(string).find());
}
protected final void assertZipEntryNewer(File zipFile,
String olderEntry, String newerEntry) throws IOException {
ZipFile zip = new ZipFile(zipFile);
Enumeration<? extends ZipEntry> entries = zip.entries();
Long newer = null;
Long older = null;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().equals(newerEntry)) {
newer = entry.getTime();
}
if (entry.getName().equals(olderEntry)) {
older = entry.getTime();
}
}
if (newer != null
&& older != null) {
if (newer <= older) {
SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
Assert.fail("Entry " + newerEntry +
" (mtime: " + fmt.format(new Date(newer)) + ") " +
"was not newer than " + olderEntry +
" (mtime: " +fmt.format(new Date(older)) + ") " +
"in archive " + zipFile);
}
} else if (newer != null) {
Assert.fail("Couldn't find entry " + olderEntry + " in archive " + zipFile);
} else if (older != null) {
Assert.fail("Couldn't find entry " + newerEntry + " in archive " + zipFile);
} else {
Assert.fail("Couldn't find entries " + newerEntry + " and " + olderEntry + " in archive " + zipFile);
}
}
}