package org.codehaus.groovy.testng; import groovy.lang.GroovyClassLoader; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.security.CodeSource; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.SourceUnit; import org.testng.Assert; import org.testng.TestNG; import org.testng.TestNGCommandLineArgs; /** * The Test'N'Groove command line support class. * * @author Alex Popescu */ public class GroovyTestNG { private GroovyClassLoader gcl; private List inputOptions; private List files = new ArrayList(); private List outputOptions = new ArrayList(); public GroovyTestNG(String[] args) { inputOptions = expand(args); gcl = new GroovyNGClassLoader(); } public int run() { initializeOptions(); if(files.size() == 0) return TestNG.HAS_NO_TEST; Class[] testclasses = loadTests(files); // dump(testclasses); TestNG testng = new TestNG(); testng.configure(TestNGCommandLineArgs.parseCommandLine( this.outputOptions.toArray(new String[this.outputOptions.size()]))); testng.setTestClasses(testclasses); setSuiteAndTestNames(testng, files); testng.run(); return testng.getStatus(); } private void setSuiteAndTestNames(TestNG testng, List files) { String testname = null; StringBuffer suitename = new StringBuffer(); if(files.size() == 1) { File f = files.get(0); testname= f.getName(); suitename.append("Single script suite (") .append(f.getName()) .append(")"); } else { testname= "Multi-script test"; suitename.append("Suite ("); for(int i = 0; i < 2; i++) { suitename.append(files.get(i).getName()) .append(","); } suitename.append("...)"); } testng.setDefaultTestName(testname); testng.setDefaultSuiteName(suitename.toString()); } private void initializeOptions() { int optiondsIdx = -1; for(int i = this.inputOptions.size() - 1; i >= 0; i--) { File f = getScript(this.inputOptions.get(i)); if(f != null) { files.add(f); } else { optiondsIdx = i; break; } } this.outputOptions = this.inputOptions.subList(0, optiondsIdx + 1); } private List expand(String[] args) { List result= new ArrayList(); String argsFile= null; for(String opt: args) { if(opt.startsWith("@")) { argsFile= opt.substring(1); } else { result.add(opt); } } if(null != argsFile) { result.addAll(readFile(argsFile)); } return result; } private List readFile(String filePath) { List lines = new ArrayList(); try { BufferedReader bufRead = new BufferedReader(new FileReader(filePath)); String line; while ((line = bufRead.readLine()) != null) { lines.add(line); } bufRead.close(); } catch (IOException ioex) { throw new RuntimeException("Cannot read argument file:" + filePath, ioex); } return lines; } private Class[] loadTests(List files) { for(Iterator it = files.iterator(); it.hasNext(); ) { File f = it.next(); try { gcl.parseClass(f); } catch(IOException ioex) { System.err.print("Cannot load script " + f.getAbsolutePath() + " Cause:" + ioex.getMessage()); } } Class[] loadedClasses = gcl.getLoadedClasses(); List classes = new ArrayList(); for(Class cls: loadedClasses) { if(accept(cls.getName())) { classes.add(cls); } } return classes.toArray(new Class[classes.size()]); } private boolean accept(String className) { // no need to pass Closure classes if(className.indexOf("$_closure_closure") != -1) return false; // no need to pass GString classes int len = className.length() - 1; boolean isGString = false; for(int i = len; i >= 0; i--) { if(className.charAt(i) < '0' || className.charAt(i) > '9') { if(className.charAt(i) == '$') { isGString = true; } else { break; } } } return !isGString; } private File getScript(String string) { if (!string.endsWith(".groovy")) return null; File f = new File(string); return (f.exists() && f.isFile() ? f : null); } private void dump(Class[] testclasses) { int i= 0; for(String s: this.outputOptions) { System.out.println("Opt" + (i++) + " :" + s); } for(Iterator it = this.files.iterator(); it.hasNext(); ) { System.out.println("Script :" + it.next()); } for(Class clazz: testclasses) { System.out.println("Class :" + clazz.getName()); } } public static void main(String[] args) { int exitCode = new GroovyTestNG(args).run(); System.exit(exitCode); } /** * A simple GCL extension that pre-imports TestNG annotations. */ private static class GroovyNGClassLoader extends GroovyClassLoader { protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) { CompilationUnit cu = super.createCompilationUnit(config, source); cu.addPhaseOperation(new CompilationUnit.SourceUnitOperation() { public void call(SourceUnit source) throws CompilationFailedException { ModuleNode module = source.getAST(); module.addImportPackage("org.testng.annotations."); module.addImport("Assert", new ClassNode(Assert.class)); } }, Phases.CONVERSION); return cu; } } }