1 /*
   2  * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation. Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  */
  24 
  25 package client.test.runner;
  26 
  27 import client.test.ExcludeRunModeMethod;
  28 import client.test.ExcludeRunModeType;
  29 import client.test.OnlyRunModeMethod;
  30 import client.test.OnlyRunModeType;
  31 import client.test.RunModes;
  32 import client.test.RunModes.RunModeException;
  33 import client.test.runner.CommonTestRunnerWorker.Command;
  34 import client.test.runner.CommonTestRunnerWorker.Command.CommandType;
  35 import client.test.runner.interview.LookAndFeelQuestion;
  36 import client.test.runner.interview.PipelineQuestion;
  37 import client.util.CtrUtils;
  38 import static client.util.CtrUtils.*;
  39 import client.util.CtrUtils.OutputReader;
  40 import client.util.JettyServer;
  41 import com.sun.interview.YesNoQuestion;
  42 import com.sun.javatest.Status;
  43 import com.sun.javatest.TestDescription;
  44 import com.sun.javatest.TestEnvironment;
  45 import com.sun.javatest.TestEnvironment.Fault;
  46 import java.io.*;
  47 import java.lang.reflect.Method;
  48 import java.net.ServerSocket;
  49 import java.net.Socket;
  50 import java.net.URL;
  51 import java.net.UnknownHostException;
  52 import java.util.ArrayList;
  53 import java.util.HashSet;
  54 import java.util.List;
  55 import java.util.Set;
  56 import java.util.concurrent.Semaphore;
  57 import java.util.concurrent.TimeUnit;
  58 import java.util.logging.Level;
  59 import java.util.logging.Logger;
  60 import org.junit.runner.RunWith;
  61 import test.javaclient.shared.CanvasRunner;
  62 import test.javaclient.shared.Utils;
  63 
  64 /**
  65  *
  66  * @author shura, mrkam, Sergey Grinev, Victor Shubov
  67  */
  68 public class TestScript extends htmltestrunner.TestScript {
  69 
  70     private static final int FORCED_TERMINATION_TIMEOUT = 5000;
  71     private static final boolean verbose = true; //TODO: use real logger
  72     private volatile Process process = null;
  73     private volatile ServerSocket cmdServer = null;
  74     private volatile Socket cmdSocket;
  75     private volatile ObjectOutputStream commandStream;
  76     private volatile TestRunner.Status status = null;
  77     private volatile TestEnvironment savedEnv;
  78     private Thread resultThread;
  79 
  80     @Override
  81     protected synchronized void interrupt(Status status) {
  82         System.out.println("interrupting with status " + status);
  83         try {
  84             if (process.isAlive()) {
  85                 process.destroyForcibly().waitFor(FORCED_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS);
  86             }
  87         } catch (InterruptedException ex) {
  88             System.err.println("Exception while interrupting test process: " + ex.getMessage());
  89         }
  90         if (resultThread.isAlive()) {
  91             resultThread.interrupt();
  92         }
  93         super.interrupt(status);
  94     }
  95 
  96     @Override
  97     protected synchronized Status getStatus() {
  98         return super.getStatus();
  99     }
 100 
 101     @Override
 102     protected void before(TestDescription description, String resultDir) throws Throwable {
 103         String testClassName = td.getParameter(RunUITestFinder.UNIT_TEST_CLASS_NAME);
 104         String testName = td.getParameter(RunUITestFinder.TEST_NAME);
 105         final String runMode = savedEnv.lookup(BasicFXInterview.RUN_MODE_PARAM)[0];
 106 
 107         System.out.println("\nTEST: " + testClassName + "/" + testName);
 108         System.out.println("Mode:" + runMode);
 109         System.out.println("Result Dir:" + resultDir);
 110         if (needToRun(testClassName, testName, runMode)) {
 111 
 112             try {
 113                 int masterPort = startServer();
 114 
 115                 if (runMode.equals(BasicFXInterview.RUN_MODE_DESKTOP)
 116                         || runMode.equals(BasicFXInterview.RUN_MODE_DESKTOP_SWING_INTEROPERABILITY)
 117                         || runMode.equals(BasicFXInterview.RUN_MODE_DESKTOP_SWT_INTEROPERABILITY)) {
 118                     runTd(description, resultDir, masterPort);
 119                 } else {
 120                     runPlugin(td, resultDir, BasicFXInterview.RUN_MODE_JNLP.equals(runMode));
 121                 }
 122             } catch (Throwable e) {
 123                 e.printStackTrace(System.err);
 124                 if (cmdServer != null) {
 125                     try {
 126                         cmdServer.close();
 127                     } catch (IOException ex) {
 128                         ex.printStackTrace(System.err);
 129                     }
 130                 }
 131                 if (cmdSocket != null) {
 132                     try {
 133                         cmdSocket.close();
 134                     } catch (IOException ex) {
 135                         ex.printStackTrace(System.err);
 136                     }
 137                 }
 138                 if (commandStream != null) {
 139                     try {
 140                         commandStream.close();
 141                     } catch (IOException ex) {
 142                         ex.printStackTrace(System.err);
 143                     }
 144                 }
 145                 if (process != null) {
 146                     process.destroy();
 147                 }
 148                 commandStream.close();
 149                 interrupt(Status.error(e.toString()));
 150             }
 151 
 152             resultThread = new Thread(new Runnable() {
 153                 @Override
 154                 public void run() {
 155                     try {
 156                         System.err.println("Waiting for exit");
 157                         ObjectInputStream ois = new ObjectInputStream(cmdSocket.getInputStream());
 158                         status = (TestRunner.Status) ois.readObject();
 159 
 160                         Status jtStatus;
 161                         System.err.println("Process returned status " + status);
 162                         if (status == null) {
 163                             jtStatus = Status.error("Unexpected status: " + status);
 164                         } else if (status.isPassed()) {
 165                             jtStatus = Status.passed(status.getText());
 166                         } else if (status.isFailed()) {
 167                             jtStatus = Status.failed(status.getText());
 168                         } else {
 169                             jtStatus = Status.error(status.getText());
 170                         }
 171                         interrupt(jtStatus);
 172 
 173                     } catch (Throwable ex) {
 174                         ex.printStackTrace(System.err);
 175                         interrupt(Status.error(CtrUtils.stackTraceToString(ex)));
 176                     } finally {
 177 
 178                         //finalize
 179                         try {
 180                             commandStream.close();
 181                             cmdSocket.close();
 182                             cmdServer.close();
 183                         } catch (IOException ex) {
 184                             ex.printStackTrace();
 185                         }
 186 
 187                         if (runMode.equals(BasicFXInterview.RUN_MODE_PLUGIN)) {
 188                             System.err.println("closing browser");
 189                             if (process != null) {
 190                                 process.destroy();
 191                             }
 192                         }
 193                         try {
 194                             if (process != null) {
 195                                 process.waitFor();
 196                             }
 197                         } catch (InterruptedException ex) {
 198                             ex.printStackTrace(System.err);
 199                         }
 200 //                    if (jemmyProcess != null) {
 201 //                        System.out.println("killed jemmy process");
 202 //                        try {
 203 //                            jemmyProcess.getInputStream().close();
 204 //                        } catch (IOException ex) {
 205 //                            ex.printStackTrace();
 206 //                        }
 207 //
 208 //                        jemmyProcess.destroy();
 209 //                    }
 210                         System.out.println("DONE");
 211                     }
 212                 }
 213             }, "I'm waiting for test's result");
 214             resultThread.start();
 215         }
 216     }
 217 
 218     @Override
 219     protected void showTestDialog(TestDescription td, TestEnvironment env) {
 220         String nodescription = td.getParameter(RunUITestFinder.NO_DESCRIPTION);
 221         boolean dryRun = YesNoQuestion.YES.equals(lookup(BasicFXInterview.DRY_RUN_TAG, YesNoQuestion.NO));
 222         if (!Boolean.parseBoolean(nodescription)) {
 223             if (dryRun) {
 224                 System.err.println("DRY RUN MODE");
 225                 int delay = Integer.parseInt(lookup(BasicFXInterview.DRY_RUN_DURATION_TAG, BasicFXInterview.DRY_RUN_DURATION_TAG.toString()));
 226                 try {
 227                     Thread.sleep(delay * 1000);
 228                 } catch (InterruptedException ex) {
 229                 }
 230                 setStatus(Status.error("Dry Run"));
 231             } else {
 232                 super.showTestDialog(td, env);
 233             }
 234         }
 235     }
 236 
 237     /**
 238      * Never put any cleanup here as this method would be called only in case of
 239      * success
 240      * @throws java.lang.Throwable
 241      */
 242     @Override
 243     protected void after(TestDescription description, String resultDir) throws Throwable {
 244     }
 245 
 246     @Override
 247     public Status run(String[] args, TestDescription td, TestEnvironment env) {
 248         savedEnv = env;
 249         try {
 250             String testClassName = td.getParameter(RunUITestFinder.UNIT_TEST_CLASS_NAME);
 251             String testName = td.getParameter(RunUITestFinder.TEST_NAME);
 252             final String runMode = savedEnv.lookup(BasicFXInterview.RUN_MODE_PARAM)[0];
 253             if (!needToRun(testClassName, testName, runMode)) {
 254                 return Status.passed("Not run in " + runMode + " mode");
 255             }
 256         } catch (RunModes.RunModeException ex) {
 257             return Status.error(ex.toString());
 258         } catch (NoSuchMethodException ex) {
 259             return Status.error(ex.toString());
 260         } catch (ClassNotFoundException ex) {
 261             return Status.error(ex.toString());
 262         } catch (Fault ex) {
 263             return Status.error(ex.toString());
 264         }
 265         super.run(args, td, env);
 266         String hasCheckUI = td.getParameter(RunUITestFinder.HAS_CHECK_UI);
 267         try {
 268             if (Boolean.parseBoolean(hasCheckUI)) {
 269                 sendCommand(CommandType.CHECK_UI_AND_EXIT);
 270             } else {
 271                 try {
 272                     sendCommand(CommandType.EXIT);
 273                 } catch (IOException ex) {
 274                     //this is valid for UI to exit before we asked for
 275                 }
 276             }
 277             resultThread.join(600000);
 278             if (status == null) {
 279                 sendCommand(CommandType.ABORT);
 280                 setStatus(Status.error("process didn't return valid status"));
 281             }
 282         } catch (Throwable ex) {
 283             return Status.error(ex.toString());
 284         }
 285 
 286         System.out.println("We are done with " + td.getName() + ". Status: " + getStatus());
 287         return getStatus();
 288     }
 289 
 290     private String getJvmArgPrismOrder() {
 291         final String pipelineOptions = lookup(PipelineQuestion.PIPELINE_PARAM_NAME, "");
 292         String jvmArgPrismOrder = "";
 293         if (!pipelineOptions.trim().equals("")) {
 294             jvmArgPrismOrder = "-Dprism.order=" + pipelineOptions;
 295         }
 296         return jvmArgPrismOrder;
 297     }
 298 
 299     /**
 300      *
 301      * @param td
 302      * @param resultDir
 303      * @return
 304      * @throws IOException
 305      * @throws Fault
 306      */
 307     protected String[] tdCmdArgs(TestDescription td, String resultDir, int port) throws IOException, Fault {
 308         String testClassName = td.getParameter(RunUITestFinder.UNIT_TEST_CLASS_NAME);
 309 
 310         boolean isJunit = Boolean.parseBoolean(td.getParameter(RunUITestFinder.TYPE_JUNIT));
 311 
 312         //String fxSdkHome = td.getParameter(BasicFXInterview.FX_SDK_HOME_PARAM_NAME);
 313         String fxSdkHome = savedEnv.lookup(BasicFXInterview.FX_SDK_HOME_PARAM_NAME)[0];
 314 
 315         String[] pathArr = savedEnv.lookup(BasicFXInterview.JAVA_PARAM_NAME);
 316         StringBuilder pathStr = new StringBuilder();
 317         for (String p : pathArr) {
 318             if (pathStr.length() > 0) {
 319                 pathStr.append(" ");
 320             }
 321             pathStr.append(p);
 322         }
 323         String javaExec = pathStr.toString();
 324 
 325 
 326         String proxy = lookup(BasicFXInterview.PROXY_PARAM_NAME, "");
 327         String jvmProxyHost = "";
 328         String jvmProxyPort = "";
 329         if (proxy != null && proxy.trim().length() > 0) {
 330             URL proxyUrl = getProxyUrl(proxy);
 331             if (proxyUrl != null) {
 332                 jvmProxyHost = "-DproxyHost=" + proxyUrl.getHost();
 333                 jvmProxyPort = "-DproxyPort=" + proxyUrl.getPort();
 334             }
 335         }
 336 
 337         String additionalOptions = getAdditionalOptions();
 338 
 339         String[] jvmVmOptions = savedEnv.lookup(BasicFXInterview.VM_OPTIONS_PARAM_NAME);
 340 
 341         String[] xPatch = savedEnv.lookup(BasicFXInterview.XPATCH_PARAM_NAME);
 342         String xPatchArg = "";
 343         if (xPatch != null && xPatch.length > 0) {
 344             File xPatchFile = new File(xPatch[0]);
 345             xPatchArg = xPatchFile.getAbsolutePath();
 346         }
 347 
 348         boolean enableAddExports = YesNoQuestion.YES.equals(lookup(
 349                 BasicFXInterview.ENABLE_ADD_EXPORTS_PARAM_NAME, YesNoQuestion.NO));
 350 
 351         String noAddExportsArg = enableAddExports ? null : "-DnoAddExports=true";
 352 
 353         String addExportsArg[] = enableAddExports
 354                 ? combineAddExports(
 355                     savedEnv.lookup(BasicFXInterview.ADD_EXPORTS_PARAM_NAME),
 356                     td.getParameter(RunUITestFinder.ADD_EXPORTS))
 357                 : null;
 358 
 359         String jvmArgPrismOrder = getJvmArgPrismOrder();
 360 
 361 
 362 //        String externaloutput = lookup(BasicFXInterview.EXTERNALOUTPUT, "");
 363 //        if (externaloutput != null) {
 364 //            jvmArgImageUtils = "-Dimageutils.outputpath=" + externaloutput + File.separator;
 365 //        }
 366         String jvmArgImageUtils = "-Dimageutils.outputpath=" + resultDir + File.separator;
 367 
 368         String jvmArgNoDesc = "";
 369         if (Boolean.parseBoolean(td.getParameter(RunUITestFinder.NO_DESCRIPTION))) {
 370             jvmArgNoDesc = "-Djavatest.mode.nodesc=true";
 371         }
 372 
 373         String runMode = lookup(BasicFXInterview.RUN_MODE_PARAM, BasicFXInterview.RUN_MODE_DESKTOP);
 374 
 375         String jvmInterop = "";
 376         if (runMode.equals(BasicFXInterview.RUN_MODE_DESKTOP_SWING_INTEROPERABILITY)) {
 377             jvmInterop = "-Djavafx.swinginteroperability=true";
 378         } else if (runMode.equals(BasicFXInterview.RUN_MODE_DESKTOP_SWT_INTEROPERABILITY)) {
 379             jvmInterop = "-Djavafx.swtinteroperability=true";
 380         }
 381 
 382         String lookAndFeelOptions = "";
 383         String lfOptions = lookup(LookAndFeelQuestion.LOOKANDFEEL_PARAM_NAME, "");
 384         if (!lfOptions.trim().equals("")) {
 385             lookAndFeelOptions = "-Djavafx.userAgentStylesheetUrl=" + lfOptions;
 386         }
 387 
 388         //Needed to get images via proxy in Evergreen via VPN see RT-21325 || RT-19661
 389         final boolean isFXCompatibility = System.getProperty("java.class.path").contains("JavaFXCompatibility");
 390         String ipV4 = isFXCompatibility ? "-Djava.net.preferIPv4Stack=true" : "";
 391 
 392         String swtTestOpt = "";
 393         try {
 394             if (System.getProperty("os.name").toLowerCase().contains("mac")) {
 395                 RunWith wunWithAnnotation = Class.forName(testClassName).getAnnotation(RunWith.class);
 396                 if ((wunWithAnnotation != null) && (wunWithAnnotation.value().equals(CanvasRunner.class))) {
 397                     swtTestOpt = "-XstartOnFirstThread";
 398                     System.out.println("Use -XstartOnFirstThread option, as we on MacOS, and SWT test is run.");
 399                 }
 400             }
 401         } catch (ClassNotFoundException ex) {
 402             System.err.println("Error : " + ex);
 403         }
 404 
 405         String jvmArgClientTestRoot = "-DtestRoot=" + RunUITestFinder.testRoot;
 406 
 407         String[] command = new String[]{};
 408         command = addToArray(command, javaExec.trim());
 409         command = addToArray(command, jvmVmOptions);
 410         if (addExportsArg != null) {
 411             command = addToArray(command, addExportsArg);
 412         } else if (noAddExportsArg != null) {
 413             command = addToArray(command, noAddExportsArg);
 414         }
 415         if (xPatchArg != null && !xPatchArg.isEmpty()) {
 416             command = addToArray(command, "--patch-module " + xPatchArg);
 417         }
 418         command = addToArray(command, lookAndFeelOptions);
 419         command = addToArray(command, ipV4);
 420         command = addToArray(command, jvmArgPrismOrder, jvmArgImageUtils);
 421         command = addToArray(command, additionalOptions);
 422         command = addToArray(command, jvmArgNoDesc, jvmProxyHost, jvmProxyPort, jvmInterop, swtTestOpt);
 423         command = addToArray(command, jvmArgClientTestRoot);
 424         command = addToArray(command, "-DmasterPort=" + port);
 425         command = addToArray(command, "-classpath", System.getProperty("java.class.path"));
 426 //        command = addToArray(command, "-Xdebug", "-Xnoagent", "-Djava.compiler=NONE", "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5858");
 427         command = addToArray(command, isJunit ? JUnit2TestRunner.class.getName() : TestRunner.class.getName(), testClassName);
 428         return command;
 429     }
 430 
 431     static String[] combineAddExports(String[] addExportsArray, String addExportsValue) {
 432         if (addExportsValue == null) {
 433             return combineAddExports(addExportsArray);
 434         } else {
 435             String[] newArray = new String[addExportsArray.length + 1];
 436             System.arraycopy(addExportsArray, 0, newArray, 1, addExportsArray.length);
 437             newArray[0] = addExportsValue;
 438             return combineAddExports(newArray);
 439         }
 440     }
 441 
 442     static String[] combineAddExports(String[] addExportsArray) {
 443         List<String> addExportsList = new ArrayList<>();
 444         for (String addExports : addExportsArray) {
 445             if (addExports != null) {
 446                 for (String value : addExports.split("\\s*,\\s*")) {
 447                     if (!value.isEmpty() && !addExportsList.contains(value.trim())) {
 448                         addExportsList.add("--add-exports");
 449                         addExportsList.add(value.trim());
 450                         addExportsList.add("--add-opens");
 451                         addExportsList.add(value.trim());
 452                     }
 453                 }
 454             }
 455         }
 456         return addExportsList.toArray(new String[addExportsList.size()]);
 457     }
 458 
 459 
 460     /**
 461      *
 462      * @param td
 463      * @param resultDir
 464      * @return
 465      */
 466     protected String[] pluginCmdArgs(TestDescription td, String resultDir) {
 467         return null;
 468     }
 469 
 470     /**
 471      *
 472      * @param command
 473      * @throws IOException
 474      */
 475     protected void doRunTd(String[] command) throws IOException {
 476         process = Runtime.getRuntime().exec(deleteEmptyElements(command));
 477     }
 478 
 479     private int startServer() throws IOException {
 480         System.out.println("Starting server." );
 481         int iRetryCount = 3;
 482         boolean bindDone = false;
 483         while ( (iRetryCount > 0) && (false==bindDone) ) {
 484             iRetryCount = iRetryCount - 1;
 485 
 486             try {
 487                 cmdServer = new ServerSocket(0);
 488                 System.out.println("Started server at port " + cmdServer.getLocalPort());
 489                 bindDone = true;
 490             } catch (java.net.BindException be) {
 491                 bindDone = false;
 492                 System.out.println("  === bind exception ===");
 493                 Socket socket = new Socket("127.0.0.1", cmdServer.getLocalPort());
 494                 commandStream = new ObjectOutputStream(socket.getOutputStream());
 495                 sendCommand(CommandType.ABORT);
 496                 try {Thread.sleep(100);} catch(Exception e){}
 497                 }
 498 
 499         }// retry loop end
 500         cmdServer.setSoTimeout(60000); // we need to be generous for plugin mode, it gets to "download" runnable
 501         return cmdServer.getLocalPort();
 502     }
 503 
 504     private void waitForConnection() throws IOException {
 505         System.out.println("Waiting for connection...");
 506         cmdSocket = cmdServer.accept();
 507         commandStream = new ObjectOutputStream(cmdSocket.getOutputStream());
 508         System.out.println("Connected");
 509     }
 510 
 511     private void sendCommand(CommandType command) throws UnknownHostException, IOException {
 512         sendCommand(command, "-");
 513     }
 514 
 515     private synchronized void sendCommand(CommandType command, String param) throws IOException {
 516         Command trc = new Command(command, param);
 517         if (verbose) {
 518             System.out.println("command " + trc);
 519         }
 520         commandStream.writeObject(trc);
 521     }
 522 
 523     /**
 524      *
 525      * @param id
 526      * @param def
 527      * @return
 528      */
 529     protected String lookup(String id, String def) {
 530         String[] result = null;
 531         try {
 532             result = savedEnv.lookup(id);
 533         } catch (Fault ex) {
 534             ex.printStackTrace(System.err);
 535         }
 536         return result == null || result.length == 0 ? def : result[0];
 537     }
 538 
 539     /**
 540      *
 541      * @param id
 542      * @return
 543      */
 544     protected String[] lookup(String id) {
 545         String[] result = null;
 546         try {
 547             result = savedEnv.lookup(id);
 548         } catch (Fault ex) {
 549             ex.printStackTrace(System.err);
 550         }
 551         return result;
 552     }
 553 
 554     private void runTd(TestDescription td, String resultDir, int port) throws IOException, InterruptedException, Fault {
 555         String[] command = tdCmdArgs(td, resultDir, port);
 556         dumpProcessExecution(resultDir, command, null);
 557         doRunTd(command);
 558         System.out.println("Logs: " + resultDir + File.separator);
 559 
 560         if (process != null) {
 561             //TODO: who close that streams?
 562             new Thread(new OutputReader(new FileOutputStream(resultDir + File.separator + "process.out"), process.getInputStream())).start();
 563             new Thread(new OutputReader(new FileOutputStream(resultDir + File.separator + "process.err"), process.getErrorStream())).start();
 564         } else {
 565             System.out.println("ERROR: Failed to create process"); // TODO: fail test?
 566         }
 567         waitForConnection();
 568         String testName = td.getParameter(RunUITestFinder.TEST_NAME);
 569         String testClassName = td.getParameter(RunUITestFinder.UNIT_TEST_CLASS_NAME);
 570         sendCommand(CommandType.SET_TEST_CLASS, testClassName);
 571         if (testName != null) {
 572             sendCommand(CommandType.SET_TEST_NAME, testName);
 573         }
 574         sendCommand(CommandType.RUN_TEST);
 575     }
 576 
 577     private static void dumpProcessExecution(String resultDir, String[] cmd, String workdir) throws IOException {
 578         final PrintWriter writer = new PrintWriter(
 579                 new FileWriter(resultDir + File.separator + "process_execution.log"));
 580         writer.println("starting process...");
 581         writer.println("Command: ");
 582         for (String s : cmd) {
 583             writer.print(s);
 584             writer.print("\n");
 585         }
 586         writer.println("");
 587         if (workdir != null) {
 588             writer.println("Workdir: " + workdir);
 589         }
 590         writer.flush();
 591     }
 592     private static final String pluginPath = "./dist-plugin/";
 593     private static final String pluginFile = "JavaClientPluginTest";
 594 
 595     private void runPlugin(TestDescription td, String resultDir, boolean isJnlp)
 596             throws IOException, Fault {
 597         String testClassName = td.getParameter(RunUITestFinder.UNIT_TEST_CLASS_NAME);
 598         String testName = td.getParameter(RunUITestFinder.TEST_NAME);
 599 
 600         //TODO: add non-junit tests support
 601         boolean isJunit = Boolean.parseBoolean(td.getParameter(RunUITestFinder.TYPE_JUNIT));
 602         if (!isJunit) {
 603             throw new UnsupportedOperationException("runui is not supported yet");
 604         }
 605         String[] command;
 606         String param;
 607         File workdir = new File(System.getProperty("user.dir"));
 608         final int port = 8485;
 609         if (isJnlp) {
 610             String[] pathArr = savedEnv.lookup(BasicFXInterview.JAVAWS_PARAM_NAME);
 611             StringBuilder pathStr = new StringBuilder();
 612             for (String p : pathArr) {
 613                 if (pathStr.length() > 0) {
 614                     pathStr.append(" ");
 615                 }
 616                 pathStr.append(p);
 617             }
 618             File path = new File(pathStr.toString());
 619             param = new File(pluginPath + pluginFile + ".jnlp").getAbsolutePath();
 620             if (!path.exists()) {
 621                 throw new IllegalArgumentException("javaws path is invalid: " + pathStr);
 622             }
 623             workdir = path.getParentFile();
 624 
 625             String jvmArgPrismOrder = getJvmArgPrismOrder();
 626             if (jvmArgPrismOrder.length() > 0) {
 627                 command = new String[]{pathStr.toString(), "-J" + jvmArgPrismOrder};  // "-Dprism.order=sw"
 628             } else {
 629                 command = new String[]{pathStr.toString()};
 630             }
 631 
 632         } else { // plugin
 633             command = addBrowserTricks(savedEnv.lookup(BasicFXInterview.BROWSER_PARAM_NAME));
 634             JettyServer.getInstance(port).setBaseDir(new File(pluginPath).getAbsolutePath());
 635             param = "http://localhost:" + port + "/" + pluginFile + ".html";
 636         }
 637         if (verbose) {
 638             for (String string : command) {
 639                 System.err.println("cmd: " + string);
 640             }
 641         }
 642         String[] merge = addToArray(command, param);
 643         dumpProcessExecution(resultDir, merge, workdir.getAbsolutePath());
 644         if (Utils.isMacOS()) {
 645             ensureJemmyServerRun(workdir.getAbsolutePath());
 646         }
 647         process = new ProcessBuilder(merge).directory(workdir).start();
 648         if (process == null) {
 649             throw new RuntimeException("Failed to create process for test ");
 650         }
 651         waitForConnection();
 652         sendCommand(CommandType.SET_OUT, resultDir + File.separator + "process.out");
 653         sendCommand(CommandType.SET_ERR, resultDir + File.separator + "process.err");
 654         sendCommand(CommandType.SET_TEST_CLASS, testClassName);
 655         sendCommand(CommandType.SET_BASEDIR, System.getProperty("user.dir"));
 656         sendCommand(CommandType.SET_ABSOLUTE_DIR, resultDir + File.separator);
 657         if (testName != null) {
 658             sendCommand(CommandType.SET_TEST_NAME, testName);
 659         }
 660         sendCommand(CommandType.RUN_TEST);
 661     }
 662 
 663     /**
 664      *
 665      * @param arr
 666      * @param st
 667      * @return
 668      */
 669     public static String[] addToArray(String[] arr, String... st) {
 670         String[] newArr = new String[arr.length + st.length];
 671         System.arraycopy(arr, 0, newArr, 0, arr.length);
 672         System.arraycopy(st, 0, newArr, arr.length, st.length);
 673         return newArr;
 674     }
 675 
 676     private static enum BROWSER {
 677 
 678         FIREFOX("firefox"), CHROME("chrome"), IE("iexplore"), SAFARI("safari"), UNKNOWN("\n");
 679         private final String key;
 680 
 681         private BROWSER(String key) {
 682             this.key = key;
 683         }
 684 
 685         public static BROWSER detect(String[] command) {
 686             for (int i = command.length - 1; i >= 0; i--) {
 687                 for (BROWSER browser : values()) {
 688                     if (command[i] != null && command[i].toLowerCase().contains(browser.key)) {
 689                         System.out.println("Detected Browser: " + browser.name());
 690                         return browser;
 691                     }
 692                 }
 693             }
 694             return UNKNOWN;
 695         }
 696     };
 697 
 698     private static String[] addBrowserTricks(String[] command) {
 699         switch (BROWSER.detect(command)) {
 700             case CHROME:
 701                 return addToArray(command, "--kiosk", "--incognito", "--disable-hang-monitor", "--always-authorize-plugins", "--allow-outdated-plugins");
 702             case FIREFOX:
 703                 return addToArray(command, "-new-window");
 704             case IE:
 705                 return addToArray(command, "-k");
 706             case SAFARI:
 707                 return new String[]{"/usr/bin/open", "-a", "Safari"};
 708         }
 709         return command;
 710     }
 711 
 712     private boolean needToRun(String testClassName, String testName, String runMode) throws NoSuchMethodException, ClassNotFoundException, RunModeException {
 713         Class testClass = Class.forName(testClassName);
 714         RunModes mode = RunModes.parseString(runMode);
 715         if (mode == null) {
 716             throw new RunModes.RunModeException(runMode);
 717         }
 718         ExcludeRunModeType ermt = (ExcludeRunModeType) testClass.getAnnotation(ExcludeRunModeType.class);
 719         if (ermt != null) {
 720             if (ermt.value() == mode) {
 721                 return false;
 722             }
 723         }
 724         OnlyRunModeType ormt = (OnlyRunModeType) testClass.getAnnotation(OnlyRunModeType.class);
 725         if (ormt != null) {
 726             if (ormt.value() != mode) {
 727                 return false;
 728             }
 729         }
 730         if (testName == null) {
 731             // fox JavaFXCompatibility suite weird run mode
 732             return true;
 733         }
 734         //not testClass.getMethod(testName) because we can't get static method.
 735         Method[] methods = testClass.getMethods();
 736         for (Method testMethod : methods) {
 737             if (testName.equals(testMethod.getName())) {
 738                 ExcludeRunModeMethod ermm = (ExcludeRunModeMethod) testMethod.getAnnotation(ExcludeRunModeMethod.class);
 739                 if (ermm != null) {
 740                     if (ermm.value() == mode) {
 741                         return false;
 742                     }
 743                 }
 744                 OnlyRunModeMethod ormm = (OnlyRunModeMethod) testMethod.getAnnotation(OnlyRunModeMethod.class);
 745                 if (ormm != null) {
 746                     if (ormm.value() != mode) {
 747                         return false;
 748                     }
 749                 }
 750             }
 751         }
 752         return true;
 753     }
 754 
 755     /**
 756      *
 757      * @return
 758      */
 759     public Thread getProcessKiller() {
 760         return new ProcessKiller(5000L, process, cmdServer, cmdSocket);
 761     }
 762 
 763     private class ProcessKiller extends Thread {
 764 
 765         long sleepTime;
 766         Process targetProc;
 767         ServerSocket targetSerSocket;
 768         Socket targetSocket;
 769 
 770         public ProcessKiller(long time, Process process, ServerSocket targetSerSock, Socket targetSock) {
 771             this.sleepTime = time;
 772             this.targetProc = process;
 773             this.targetSerSocket = targetSerSock;
 774             this.targetSocket = targetSock;
 775         }
 776 
 777         @Override
 778         public void run() {
 779             try {
 780                 sleep(sleepTime);
 781             } catch (InterruptedException ex) {
 782                 Logger.getLogger(TestScript.class.getName()).log(Level.SEVERE, null, ex);
 783             }
 784             if (targetProc.isAlive()) {
 785                 System.out.println("Process has been interrupted forcibly!");
 786                 targetProc.destroyForcibly();
 787                 if (targetSerSocket != null) {
 788                     if (!targetSerSocket.isClosed()) {
 789                         try {
 790                             targetSerSocket.close();
 791                         } catch (IOException ex) {
 792                             System.out.println("CMDSERVER: exception while cloing");
 793                             ex.printStackTrace();
 794                         }
 795                     }
 796                 } else {
 797                     System.out.println("CMDSERVER: NULL");
 798                 }
 799                 if (targetSocket != null) {
 800                     if (!targetSocket.isClosed()) {
 801                         try {
 802                             targetSocket.close();
 803                         } catch (IOException ex) {
 804                             System.out.println("CMDSOCKET: exception while cloing");
 805                             ex.printStackTrace();
 806                         }
 807                     }
 808                 } else {
 809                     System.out.println("CMDSOCKET: NULL");
 810                 }
 811             }
 812         }
 813     }
 814 
 815     /**
 816      *
 817      * @return
 818      */
 819     protected TestEnvironment getSavedEnv() {
 820         return savedEnv;
 821     }
 822 
 823     /**
 824      *
 825      * @return
 826      */
 827     protected String getAdditionalOptions(){
 828         return "";
 829     }
 830 
 831 }