1 package org.testng.remote; 2 3 import static org.testng.internal.Utils.defaultIfStringEmpty; 4 5 import com.beust.jcommander.JCommander; 6 import com.beust.jcommander.ParameterException; 7 8 import org.testng.CommandLineArgs; 9 import org.testng.IClassListener; 10 import org.testng.IInvokedMethodListener; 11 import org.testng.ISuite; 12 import org.testng.ISuiteListener; 13 import org.testng.ITestRunnerFactory; 14 import org.testng.TestNG; 15 import org.testng.TestNGException; 16 import org.testng.TestRunner; 17 import org.testng.collections.Lists; 18 import org.testng.remote.strprotocol.GenericMessage; 19 import org.testng.remote.strprotocol.IMessageSender; 20 import org.testng.remote.strprotocol.MessageHelper; 21 import org.testng.remote.strprotocol.MessageHub; 22 import org.testng.remote.strprotocol.RemoteTestListener; 23 import org.testng.remote.strprotocol.SerializedMessageSender; 24 import org.testng.remote.strprotocol.StringMessageSender; 25 import org.testng.remote.strprotocol.SuiteMessage; 26 import org.testng.reporters.JUnitXMLReporter; 27 import org.testng.reporters.TestHTMLReporter; 28 import org.testng.xml.XmlSuite; 29 import org.testng.xml.XmlTest; 30 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.List; 34 35 /** 36 * Extension of TestNG registering a remote TestListener. 37 * 38 * @author Cedric Beust <cedric@beust.com> 39 */ 40 public class RemoteTestNG extends TestNG { 41 private static final String LOCALHOST = "localhost"; 42 43 // The following constants are referenced by the Eclipse plug-in, make sure you 44 // modify the plug-in as well if you change any of them. 45 public static final String DEBUG_PORT = "12345"; 46 public static final String DEBUG_SUITE_FILE = "testng-customsuite.xml"; 47 public static final String DEBUG_SUITE_DIRECTORY = System.getProperty("java.io.tmpdir"); 48 public static final String PROPERTY_DEBUG = "testng.eclipse.debug"; 49 public static final String PROPERTY_VERBOSE = "testng.eclipse.verbose"; 50 // End of Eclipse constants. 51 52 private ITestRunnerFactory m_customTestRunnerFactory; 53 private String m_host; 54 55 /** Port used for the string protocol */ 56 private Integer m_port = null; 57 58 /** Port used for the serialized protocol */ 59 private static Integer m_serPort = null; 60 61 private static boolean m_debug; 62 63 private static boolean m_dontExit; 64 65 private static boolean m_ack; 66 setHost(String host)67 public void setHost(String host) { 68 m_host = defaultIfStringEmpty(host, LOCALHOST); 69 } 70 calculateAllSuites(List<XmlSuite> suites, List<XmlSuite> outSuites)71 private void calculateAllSuites(List<XmlSuite> suites, List<XmlSuite> outSuites) { 72 for (XmlSuite s : suites) { 73 outSuites.add(s); 74 // calculateAllSuites(s.getChildSuites(), outSuites); 75 } 76 } 77 78 @Override run()79 public void run() { 80 IMessageSender sender = m_serPort != null 81 ? new SerializedMessageSender(m_host, m_serPort, m_ack) 82 : new StringMessageSender(m_host, m_port); 83 final MessageHub msh = new MessageHub(sender); 84 msh.setDebug(isDebug()); 85 try { 86 msh.connect(); 87 // We couldn't do this until now in debug mode since the .xml file didn't exist yet. 88 // Now that we have connected with the Eclipse client, we know that it created the .xml 89 // file so we can proceed with the initialization 90 initializeSuitesAndJarFile(); 91 92 List<XmlSuite> suites = Lists.newArrayList(); 93 calculateAllSuites(m_suites, suites); 94 // System.out.println("Suites: " + m_suites.get(0).getChildSuites().size() 95 // + " and:" + suites.get(0).getChildSuites().size()); 96 if(suites.size() > 0) { 97 98 int testCount= 0; 99 100 for (XmlSuite suite : suites) { 101 testCount += suite.getTests().size(); 102 } 103 104 GenericMessage gm= new GenericMessage(MessageHelper.GENERIC_SUITE_COUNT); 105 gm.setSuiteCount(suites.size()); 106 gm.setTestCount(testCount); 107 msh.sendMessage(gm); 108 109 addListener(new RemoteSuiteListener(msh)); 110 setTestRunnerFactory(new DelegatingTestRunnerFactory(buildTestRunnerFactory(), msh)); 111 112 // System.out.println("RemoteTestNG starting"); 113 super.run(); 114 } 115 else { 116 System.err.println("No test suite found. Nothing to run"); 117 } 118 } 119 catch(Throwable cause) { 120 cause.printStackTrace(System.err); 121 } 122 finally { 123 // System.out.println("RemoteTestNG finishing: " + (getEnd() - getStart()) + " ms"); 124 msh.shutDown(); 125 if (! m_debug && ! m_dontExit) { 126 System.exit(0); 127 } 128 } 129 } 130 131 /** 132 * Override by the plugin if you need to configure differently the <code>TestRunner</code> 133 * (usually this is needed if different listeners/reporters are needed). 134 * <b>Note</b>: you don't need to worry about the wiring listener, because it is added 135 * automatically. 136 */ buildTestRunnerFactory()137 protected ITestRunnerFactory buildTestRunnerFactory() { 138 if(null == m_customTestRunnerFactory) { 139 m_customTestRunnerFactory= new ITestRunnerFactory() { 140 @Override 141 public TestRunner newTestRunner(ISuite suite, XmlTest xmlTest, 142 Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) { 143 TestRunner runner = 144 new TestRunner(getConfiguration(), suite, xmlTest, 145 false /*skipFailedInvocationCounts */, 146 listeners, classListeners); 147 if (m_useDefaultListeners) { 148 runner.addListener(new TestHTMLReporter()); 149 runner.addListener(new JUnitXMLReporter()); 150 } 151 152 return runner; 153 } 154 }; 155 } 156 157 return m_customTestRunnerFactory; 158 } 159 main(String[] args)160 public static void main(String[] args) throws ParameterException { 161 CommandLineArgs cla = new CommandLineArgs(); 162 RemoteArgs ra = new RemoteArgs(); 163 new JCommander(Arrays.asList(cla, ra), args); 164 m_dontExit = ra.dontExit; 165 if (cla.port != null && ra.serPort != null) { 166 throw new TestNGException("Can only specify one of " + CommandLineArgs.PORT 167 + " and " + RemoteArgs.PORT); 168 } 169 m_debug = cla.debug; 170 m_ack = ra.ack; 171 if (m_debug) { 172 // while (true) { 173 initAndRun(args, cla, ra); 174 // } 175 } 176 else { 177 initAndRun(args, cla, ra); 178 } 179 } 180 initAndRun(String[] args, CommandLineArgs cla, RemoteArgs ra)181 private static void initAndRun(String[] args, CommandLineArgs cla, RemoteArgs ra) { 182 RemoteTestNG remoteTestNg = new RemoteTestNG(); 183 if (m_debug) { 184 // In debug mode, override the port and the XML file to a fixed location 185 cla.port = Integer.parseInt(DEBUG_PORT); 186 ra.serPort = cla.port; 187 cla.suiteFiles = Arrays.asList(new String[] { 188 DEBUG_SUITE_DIRECTORY + DEBUG_SUITE_FILE 189 }); 190 } 191 remoteTestNg.configure(cla); 192 remoteTestNg.setHost(cla.host); 193 m_serPort = ra.serPort; 194 remoteTestNg.m_port = cla.port; 195 if (isVerbose()) { 196 StringBuilder sb = new StringBuilder("Invoked with "); 197 for (String s : args) { 198 sb.append(s).append(" "); 199 } 200 p(sb.toString()); 201 // remoteTestNg.setVerbose(1); 202 // } else { 203 // remoteTestNg.setVerbose(0); 204 } 205 validateCommandLineParameters(cla); 206 remoteTestNg.run(); 207 // if (m_debug) { 208 // // Run in a loop if in debug mode so it is possible to run several launches 209 // // without having to relauch RemoteTestNG. 210 // while (true) { 211 // remoteTestNg.run(); 212 // remoteTestNg.configure(cla); 213 // } 214 // } else { 215 // remoteTestNg.run(); 216 // } 217 } 218 p(String s)219 private static void p(String s) { 220 if (isVerbose()) { 221 System.out.println("[RemoteTestNG] " + s); 222 } 223 } 224 isVerbose()225 public static boolean isVerbose() { 226 boolean result = System.getProperty(PROPERTY_VERBOSE) != null || isDebug(); 227 return result; 228 } 229 isDebug()230 public static boolean isDebug() { 231 return m_debug || System.getProperty(PROPERTY_DEBUG) != null; 232 } 233 getHost()234 private String getHost() { 235 return m_host; 236 } 237 getPort()238 private int getPort() { 239 return m_port; 240 } 241 242 /** A ISuiteListener wiring the results using the internal string-based protocol. */ 243 private static class RemoteSuiteListener implements ISuiteListener { 244 private final MessageHub m_messageSender; 245 RemoteSuiteListener(MessageHub smsh)246 RemoteSuiteListener(MessageHub smsh) { 247 m_messageSender= smsh; 248 } 249 250 @Override onFinish(ISuite suite)251 public void onFinish(ISuite suite) { 252 m_messageSender.sendMessage(new SuiteMessage(suite, false /*start*/)); 253 } 254 255 @Override onStart(ISuite suite)256 public void onStart(ISuite suite) { 257 m_messageSender.sendMessage(new SuiteMessage(suite, true /*start*/)); 258 } 259 } 260 261 private static class DelegatingTestRunnerFactory implements ITestRunnerFactory { 262 private final ITestRunnerFactory m_delegateFactory; 263 private final MessageHub m_messageSender; 264 DelegatingTestRunnerFactory(ITestRunnerFactory trf, MessageHub smsh)265 DelegatingTestRunnerFactory(ITestRunnerFactory trf, MessageHub smsh) { 266 m_delegateFactory= trf; 267 m_messageSender= smsh; 268 } 269 270 @Override newTestRunner(ISuite suite, XmlTest test, Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners)271 public TestRunner newTestRunner(ISuite suite, XmlTest test, 272 Collection<IInvokedMethodListener> listeners, List<IClassListener> classListeners) { 273 TestRunner tr = m_delegateFactory.newTestRunner(suite, test, listeners, classListeners); 274 tr.addListener(new RemoteTestListener(suite, test, m_messageSender)); 275 return tr; 276 } 277 } 278 } 279