blob: 495a92470d4e93c55b1f4671ad8ed4c6552eca93 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.libs.jstestdriver;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.login.Configuration;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.netbeans.api.extexecution.ExecutionDescriptor;
import org.netbeans.api.extexecution.ExecutionService;
import org.netbeans.api.extexecution.ExternalProcessBuilder;
import org.netbeans.api.extexecution.input.InputProcessor;
import org.netbeans.api.extexecution.print.LineConvertor;
import org.netbeans.libs.jstestdriver.api.BrowserInfo;
import org.netbeans.libs.jstestdriver.api.ServerListener;
import org.netbeans.libs.jstestdriver.api.TestListener;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Exceptions;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.windows.IOProvider;
// TODO: initially this implementation was in a different module;
// perhaps refactor back and drop the JsTestDriverImplementation
public class JsTestDriverImpl implements JsTestDriverImplementation {
private boolean running = false;
private Future task;
private boolean externallyStarted;
private static final Logger LOGGER = Logger.getLogger(JsTestDriverImpl.class.getName());
private static RequestProcessor RP = new RequestProcessor("re-fire js-test-driver events");
public JsTestDriverImpl() {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
if (task != null) {
task.cancel(true);
}
}
}));
}
private boolean wasStartedExternally(int port) {
try {
URL u = new URL("http://localhost:"+port);
URLConnection conn = u.openConnection();
conn.connect();
return true;
} catch (IOException ex) {
return false;
}
}
private static String getJavaBinary() {
String javaHome = System.getProperty("java.home");
String javaBinary = Utilities.isWindows() ? "java.exe" : "java"; // NOI18N
return javaHome + File.separator + "bin" + File.separator + javaBinary;
}
@Override
public void startServer(File jsTestDriverJar, int port, boolean strictMode, ServerListener listener) {
if (wasStartedExternally(port)) {
externallyStarted = true;
IOProvider.getDefault().getIO("js-test-driver Server", false).getOut().
println("Port "+port+" is busy. Server was started outside of the IDE.");
return;
}
ExecutionDescriptor descriptor = new ExecutionDescriptor().
controllable(false).
// outLineBased(true).
// errLineBased(true).
outProcessorFactory(new ServerInputProcessorFactory(listener)).
frontWindowOnError(true);
File extjar = InstalledFileLocator.getDefault().locate("modules/ext/libs.jstestdriver-ext.jar", "org.netbeans.libs.jstestdriver", false); // NOI18N
ExternalProcessBuilder processBuilder = new ExternalProcessBuilder(getJavaBinary()).
addArgument("-cp").
addArgument(jsTestDriverJar.getAbsolutePath()+File.pathSeparatorChar+extjar.getAbsolutePath()).
addArgument("org.netbeans.libs.jstestdriver.ext.StartServer").
addArgument(""+port).
addArgument(""+strictMode);
ExecutionService service = ExecutionService.newService(processBuilder, descriptor, "js-test-driver Server");
task = service.run();
running = true;
}
@Override
public void stopServer() {
assert running : "server is not running";
task.cancel(true);
task = null;
running = false;
}
@Override
public boolean isRunning() {
return running || externallyStarted;
}
@Override
public boolean wasStartedExternally() {
return externallyStarted;
}
@Override
public void runTests(File jsTestDriverJar, String serverURL, boolean strictMode, File baseFolder, File configFile,
String testsToRun, final TestListener listener, final LineConvertor lineConvertor) {
ExecutionDescriptor descriptor = new ExecutionDescriptor().
controllable(false).
// outLineBased(true).
// errLineBased(true).
outProcessorFactory(new TestRunInputProcessorFactory(listener)).
outConvertorFactory(new ExecutionDescriptor.LineConvertorFactory() {
@Override
public LineConvertor newLineConvertor() {
return lineConvertor;
}
}).
frontWindowOnError(true);
File extjar = InstalledFileLocator.getDefault().locate("modules/ext/libs.jstestdriver-ext.jar", "org.netbeans.libs.jstestdriver", false); // NOI18N
ExternalProcessBuilder processBuilder = new ExternalProcessBuilder(getJavaBinary()).
addArgument("-cp").
addArgument(jsTestDriverJar.getAbsolutePath()+File.pathSeparatorChar+extjar.getAbsolutePath()).
addArgument("org.netbeans.libs.jstestdriver.ext.RunTests").
addArgument(serverURL).
addArgument(baseFolder.getAbsolutePath()).
addArgument(configFile.getAbsolutePath()).
addArgument(testsToRun).
addArgument(""+strictMode);
ExecutionService service = ExecutionService.newService(processBuilder, descriptor, "Running JS unit tests");
try {
service.run().get();
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
RP.post(new Runnable() {
@Override
public void run() {
listener.onTestingFinished();
}
});
}
private static class ServerInputProcessorFactory implements ExecutionDescriptor.InputProcessorFactory {
private ServerListener listener;
public ServerInputProcessorFactory(ServerListener listener) {
this.listener = listener;
}
@Override
public InputProcessor newInputProcessor(InputProcessor defaultProcessor) {
return new ServerInputProcessor(defaultProcessor, listener);
}
}
private static class ServerInputProcessor implements InputProcessor {
private InputProcessor delegate;
private ServerListener listener;
public ServerInputProcessor(InputProcessor delegate, ServerListener listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public void processInput(char[] chars) throws IOException {
String s = new String(chars);
if (s.indexOf("msg.server.started") != -1) {
s = s.replace("msg.server.started", "Server has started");
delegate.processInput(s.toCharArray());
listener.serverStarted();
} else {
delegate.processInput(chars);
}
}
@Override
public void reset() throws IOException {
delegate.reset();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
private static class TestRunInputProcessorFactory implements ExecutionDescriptor.InputProcessorFactory {
private TestListener listener;
public TestRunInputProcessorFactory(TestListener listener) {
this.listener = listener;
}
@Override
public InputProcessor newInputProcessor(InputProcessor defaultProcessor) {
return new TestRunInputProcessor(defaultProcessor, listener);
}
}
private static class TestRunInputProcessor implements InputProcessor {
private InputProcessor delegate;
private TestListener listener;
public TestRunInputProcessor(InputProcessor delegate, TestListener listener) {
this.delegate = delegate;
this.listener = listener;
}
@Override
public void processInput(char[] chars) throws IOException {
String s = new String(chars);
processPossibleBlockOfLines(s);
}
private void processPossibleBlockOfLines(String s) throws IOException {
try {
StringTokenizer st = new StringTokenizer(s, "\n");
while (st.hasMoreTokens()) {
String ss = processSingleLine(st.nextToken())+"\n";
delegate.processInput(ss.toCharArray());
}
} catch (Throwable t) {
LOGGER.log(Level.SEVERE, "something went wrong: "+s, t);
}
}
private String processSingleLine(String s) throws IOException {
int start = s.indexOf("nb-easel-json:{");
if (start != -1) {
int end = s.lastIndexOf("}");
String command = s.substring(start+14, end+1);
Object o = JSONValue.parse(command);
if (o == null) {
LOGGER.log(Level.SEVERE, "cannot parse following JSON: "+command + " original message: "+s, new IOException("cannot parse"));
} else {
assert o instanceof JSONObject : "must be JSONObject: "+o+" "+o.getClass();
JSONObject json = (JSONObject)o;
final TestListener.TestResult tr = new TestListener.TestResult(
new BrowserInfo((String)json.get("browserName"), (String)json.get("browserVersion"), (String)json.get("browserOS")),
(String)json.get("result"), (String)json.get("message"), (String)json.get("log"),
(String)json.get("testCase"), (String)json.get("testName"), ((Number)json.get("duration")).longValue(),
(String)json.get("stack"));
StringBuilder test = new StringBuilder();
test.append(tr.getTestCaseName() + " - " + tr.getTestName() + " " +
tr.getResult().toString().toUpperCase()+" in "+tr.getDuration()+"ms (" +tr.getBrowserInfo().getName()+
","+tr.getBrowserInfo().getOs() + ","+tr.getBrowserInfo().getVersion()+")");
if (tr.getStack().length() > 0) {
test.append("\n"+tr.getStack());
}
if (tr.getLog().length() > 0) {
test.append("\n"+tr.getLog());
}
if (tr.getMessage().length() > 0) {
test.append("\n"+tr.getMessage());
}
s = s.substring(0, start)+test.toString()+s.substring(end+1);
RP.post(new Runnable() {
@Override
public void run() {
listener.onTestComplete(tr);
}
});
return s;
}
}
return s;
}
@Override
public void reset() throws IOException {
delegate.reset();
}
@Override
public void close() throws IOException {
delegate.close();
}
}
}