001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.xtest.plugin.ide;
043:
044: import java.awt.AWTEvent;
045: import java.awt.Component;
046: import java.awt.Container;
047: import java.awt.EventQueue;
048: import java.awt.Toolkit;
049: import java.awt.event.AWTEventListener;
050: import java.awt.event.HierarchyEvent;
051: import java.awt.event.HierarchyListener;
052: import java.io.File;
053: import java.io.FileOutputStream;
054: import java.io.IOException;
055: import java.io.PrintWriter;
056: import java.lang.reflect.Method;
057: import java.net.URL;
058: import java.net.URLClassLoader;
059: import java.util.Date;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import org.netbeans.TopSecurityManager;
063: import org.netbeans.xtest.util.JNIKill;
064: import org.netbeans.xtest.util.PNGEncoder;
065:
066: /**
067: * Main part of XTest starter. Must not use anything outside lib/*.jar and lib/ext/*.jar.
068: * @author Jan Chalupa, Jesse Glick
069: */
070: public class Main extends Object {
071:
072: private static final Logger LOGGER = Logger.getLogger(Main.class
073: .getName());
074:
075: // ide running flag file
076: private static File ideRunning = null;
077:
078: // netbeans pid system property
079: private static final String IDE_PID_SYSTEM_PROPERTY = "netbeans.pid";
080:
081: // flag whether IDE was interrupted
082: private static boolean ideInterrupted = false;
083:
084: private static void captureIDEScreen(final String fileName) {
085: Thread captureThread = new Thread(new Runnable() {
086: public void run() {
087: try {
088: System.out.println("capturing screenshot:"
089: + fileName);
090: String userdirName = System
091: .getProperty("netbeans.user");
092: // capture the screenshot
093: PNGEncoder.captureScreenToIdeUserdir(userdirName,
094: fileName);
095: } catch (Exception e) {
096: LOGGER
097: .log(
098: Level.WARNING,
099: "Exception thrown when capturing IDE screenshot",
100: e);
101: }
102: }
103: });
104: captureThread.start();
105: try {
106: captureThread.join(60000L); // wait 1 minute at the most
107: } catch (InterruptedException iex) {
108: LOGGER.log(Level.WARNING,
109: "Exception thrown when capturing IDE screenshot",
110: iex);
111: } finally {
112: }
113: if (captureThread.isAlive()) {
114: captureThread.interrupt();
115: }
116: }
117:
118: private static void prepareModuleLoaderClassPath() {
119: String moduleLoaderName = System.getProperty(
120: "xtest.ide.use.classloader", "");
121: if (!moduleLoaderName.equals("")) {
122: if (!moduleLoaderName.equals("openide")) { // openide is handler in the executor build script
123: System.out.println("Using module " + moduleLoaderName
124: + " classloader to load tests");
125: String testbagClassPath = System
126: .getProperty("tbag.classpath");
127: if (testbagClassPath != null) {
128: // set appropriate property
129: String patchesProperty = "netbeans.patches."
130: + moduleLoaderName;
131: System.out.println("Setting system property "
132: + patchesProperty + " to "
133: + testbagClassPath);
134: System.setProperty(patchesProperty,
135: testbagClassPath);
136: } else {
137: System.out
138: .println("TestBag classpath (tbag.classpath) property not defined - there is nothing to load");
139: }
140: } else {
141: System.out
142: .println("Using openide classlaoder to load the tests");
143: }
144: } else {
145: System.out
146: .println("Using system classloader to load the tests");
147: }
148: }
149:
150: /** Starts the IDE.
151: * @param args the command line arguments
152: */
153: public static void main(String args[]) {
154:
155: System.out.println("!!!!! testlist is "
156: + System.getProperty("testlist"));
157:
158: // use the console logging
159: System.setProperty("netbeans.logger.console", "true");
160:
161: final String blacklist = System
162: .getProperty("xtest.ide.blacklist");
163: if (blacklist != null) {
164: try {
165: Logger classLogger = Logger
166: .getLogger("org.netbeans.ProxyClassLoader");
167: classLogger.addHandler(BlacklistedClassesHandler
168: .getBlacklistedClassesHandler(blacklist));
169: classLogger.setLevel(Level.ALL);
170: classLogger.setUseParentHandlers(false);
171: System.out
172: .println("BlacklistedClassesHandler handler added");
173: } catch (Exception ex) {
174: System.out
175: .println("Can't initialize BlacklistedClassesHandler due to the following exception:");
176: ex.printStackTrace();
177: }
178: }
179:
180: // create the IDE flag file
181: String workdir = System.getProperty("xtest.workdir");
182: if (workdir != null) {
183: // create flag file indicating running tests
184: ideRunning = new File(workdir, "ide.flag");
185: File idePID = new File(workdir, "ide.pid");
186: PrintWriter writer = null;
187: try {
188: ideRunning.createNewFile();
189: idePID.createNewFile();
190: writer = new PrintWriter(new FileOutputStream(idePID));
191: // get my pid
192: JNIKill kill = new JNIKill();
193: if (kill.startDumpThread())
194: System.out
195: .println("IDE dump thread succesfully started.");
196: long myPID = kill.getMyPID();
197: // store it as a system property
198: System.setProperty(IDE_PID_SYSTEM_PROPERTY, Long
199: .toString(myPID));
200: // write it out to a file
201: System.out.println("IDE is running under PID:" + myPID);
202: writer.println(myPID);
203: } catch (IOException ioe) {
204: System.out.println("cannot create flag file:"
205: + ideRunning);
206: ideRunning = null;
207: } catch (Throwable e) {
208: System.out.println("There was a problem: " + e);
209: } finally {
210: if (writer != null) {
211: writer.close();
212: }
213: }
214: } else {
215: System.out
216: .println("cannot get xtest.workdir property - it has to be set to a valid working directory");
217: }
218:
219: // prepare module's classpath - for tests which are loaded by modules' classlaoders
220: prepareModuleLoaderClassPath();
221:
222: // do the expected stuff
223: try {
224: org.netbeans.core.startup.Main.main(args);
225: } catch (Exception e) {
226: e.printStackTrace();
227: exit();
228: }
229:
230: // some threads may be still active, wait for the event queue to become quiet
231: Thread initThread = new Thread(new Runnable() {
232: public void run() {
233: try {
234: new QueueEmpty().waitEventQueueEmpty(2000);
235: } catch (Exception ex) {
236: LOGGER
237: .log(
238: Level.WARNING,
239: "Exception while waiting for empty event queue.",
240: ex);
241: }
242: }
243: });
244: // workaround for JDK bug 4924516 (see below)
245: Toolkit.getDefaultToolkit().addAWTEventListener(
246: distributingHierarchyListener,
247: HierarchyEvent.HIERARCHY_EVENT_MASK);
248: // start the init thread
249: initThread.start();
250: try {
251: initThread.join(60000L); // wait 1 minute at the most
252: } catch (InterruptedException iex) {
253: // ignore it
254: } finally {
255: // workaround for JDK bug 4924516 (see below)
256: Toolkit.getDefaultToolkit().removeAWTEventListener(
257: distributingHierarchyListener);
258: }
259: if (initThread.isAlive()) {
260: // time-out expired, event queue still busy -> interrupt the init thread
261: LOGGER.info(new Date().toString()
262: + ": EventQueue still busy, starting anyway.");
263: initThread.interrupt();
264: }
265: // ok. The IDE should be up and fully initialized
266: // let's run the test now
267: try {
268: // just aTest
269: doTestPart();
270: } catch (RuntimeException ex) {
271: ex.printStackTrace();
272: } catch (Error err) {
273: err.printStackTrace();
274: }
275: }
276:
277: private static final String TEST_EXIT = "test.exit";
278: private static final String TEST_TIMEOUT = "xtest.timeout";
279: private static final String TEST_REUSE_IDE = "test.reuse.ide";
280: private static final long DEFAULT_TIMEOUT = 2400000;
281:
282: // properties used for new project infrastructure
283: private static final String IDE_CREATE_PROJECT = "xtest.ide.create.project";
284: private static final String IDE_OPEN_PROJECT = "xtest.ide.open.project";
285: private static final String IDE_OPEN_PROJECTS = "xtest.ide.open.projects";
286: private static final String XTEST_USERDIR = "xtest.userdir";
287:
288: private static MainWithProjectsInterface projectsHandle;
289:
290: /** Gets projects handle only when needed. It doesn't fail when used for
291: * platform (http://www.netbeans.org/issues/show_bug.cgi?id=47928)
292: */
293: private static MainWithProjectsInterface getProjectsHandle() {
294: if (projectsHandle == null) {
295: try {
296: projectsHandle = (MainWithProjectsInterface) new WithProjectsClassLoader()
297: .loadClass(
298: "org.netbeans.xtest.plugin.ide.MainWithProjects")
299: .newInstance();
300: } catch (Exception e) {
301: LOGGER.log(Level.WARNING, e.getMessage(), e);
302: return null;
303: }
304: }
305: return projectsHandle;
306: }
307:
308: private static void doTestPart() {
309: final MainWithExecInterface handle;
310: try {
311: handle = (MainWithExecInterface) new WithExecClassLoader()
312: .loadClass(
313: "org.netbeans.xtest.plugin.ide.MainWithExec")
314: .newInstance();
315: } catch (Exception e) {
316: LOGGER.log(Level.WARNING, e.getMessage(), e);
317: return;
318: }
319:
320: long testTimeout;
321:
322: try {
323: // default timeout is 30 minutes
324: testTimeout = Long.parseLong(System.getProperty(
325: TEST_TIMEOUT, "2400000"));
326: } catch (NumberFormatException ex) {
327: testTimeout = DEFAULT_TIMEOUT;
328: }
329: if (testTimeout <= 0) {
330: testTimeout = DEFAULT_TIMEOUT;
331: }
332:
333: Thread testThread = new Thread(new Runnable() {
334: public void run() {
335: try {
336:
337: // setup the repository - should not be needed for tests loaded by system classloader
338:
339: if (System.getProperty(TEST_REUSE_IDE, "false")
340: .equals("false")) {
341:
342: // create an empty Java project
343: if (System.getProperty(IDE_CREATE_PROJECT,
344: "false").equals("true")) {
345: getProjectsHandle().createProject(
346: System.getProperty(XTEST_USERDIR));
347: }
348: // open project at specified location
349: if (!System.getProperty(IDE_OPEN_PROJECT, "")
350: .equals("")) {
351: getProjectsHandle()
352: .openProject(
353: System
354: .getProperty(IDE_OPEN_PROJECT));
355: }
356: // open multiple projects at specified location
357: if (!System.getProperty(IDE_OPEN_PROJECTS, "")
358: .equals("")) {
359: String pathToProjects = System
360: .getProperty(IDE_OPEN_PROJECTS);
361: File dir = new File(pathToProjects);
362: File[] projects = dir.listFiles();
363: for (int k = 0; k < projects.length; k++) {
364: if (projects[k].isDirectory()) {
365: getProjectsHandle().openProject(
366: projects[k]
367: .getAbsolutePath());
368: }
369: }
370: }
371: }
372: handle.run();
373: } catch (Exception e) {
374: e.printStackTrace();
375: LOGGER.log(Level.WARNING, e.getMessage(), e);
376: }
377: }
378: });
379:
380: // start the test thread
381: testThread.start();
382: try {
383: testThread.join(testTimeout);
384: } catch (InterruptedException iex) {
385: LOGGER.log(Level.WARNING, iex.getMessage(), iex);
386: }
387: if (testThread.isAlive()) {
388: // time-out expired, test not finished -> interrupt
389: LOGGER.info(new Date().toString()
390: + ": time-out expired - interrupting! ***");
391: // thread dump
392: new JNIKill().dumpMe();
393: // save the screen capture
394: captureIDEScreen("screenshot-timeout.png");
395: ideInterrupted = true;
396: testThread.interrupt();
397: }
398:
399: // we're leaving IDE
400: // delete the flag file (if ide was not interrupted)
401: if (ideRunning != null) {
402: if (!ideInterrupted) {
403: if (ideRunning.delete() == false) {
404: System.out.println("Cannot delete the flag file "
405: + ideRunning);
406: }
407: }
408: }
409:
410: // close IDE
411: if (System.getProperty(TEST_EXIT, "false").equals("true")) {
412: Thread exitThread = new Thread(new Runnable() {
413: public void run() {
414: try {
415: // kill all running tasks
416: handle.killPendingTasks();
417: // discard changes in all modified files
418: handle.discardChanges();
419: } catch (Exception e) {
420: System.out
421: .println("Exception when killing tasks or discarding changes.");
422: e.printStackTrace();
423: }
424: // exit IDE
425: handle.exit();
426: }
427: });
428: // try to exit nicely first
429: LOGGER.info(new Date().toString() + ": soft exit attempt.");
430: exitThread.start();
431: try {
432: // wait 60 seconds for the IDE to exit
433: exitThread.join(60000);
434: } catch (InterruptedException iex) {
435: LOGGER.log(Level.WARNING, iex.getMessage(), iex);
436: }
437: if (exitThread.isAlive()) {
438: // IDE refuses to shutdown, terminate unconditionally
439: LOGGER.info(new Date().toString()
440: + ": hard exit attempt!!!.");
441: // screen shot first
442: captureIDEScreen("screenshot-hardexit.png");
443: exitThread.interrupt();
444: exit();
445: }
446: }
447: }
448:
449: private static void exit() {
450: try {
451: Class param[] = new Class[1];
452: param[0] = int.class;
453: Class c = TopSecurityManager.class;
454: Method m = c.getMethod("exit", param);
455: Integer intparam[] = { new Integer(1) };
456: LOGGER
457: .info(new Date().toString()
458: + ": using TopSecurityManager.exit(1) to exit IDE.");
459: // exit
460: m.invoke(null, (Object[]) intparam);
461: } catch (Exception e) {
462: LOGGER.info(new Date().toString()
463: + ": using System.exit(1) to exit IDE.");
464: // exit
465: System.exit(1);
466: }
467: }
468:
469: private static class QueueEmpty implements AWTEventListener {
470:
471: private long eventDelayTime = 100; // 100 millis
472: private long lastEventTime;
473:
474: /** Creates a new QueueEmpty instance */
475: public QueueEmpty() {
476: }
477:
478: /** method called every time when AWT Event is dispatched
479: * @param event event dispatched from AWT Event Queue
480: */
481: public void eventDispatched(AWTEvent awtEvent) {
482: lastEventTime = System.currentTimeMillis();
483: }
484:
485: /** constructor with user defined value
486: * @param eventdelaytime maximum delay between two events of one redraw action
487: */
488: public synchronized void waitEventQueueEmpty(long eventDelayTime)
489: throws InterruptedException {
490: this .eventDelayTime = eventDelayTime;
491: waitEventQueueEmpty();
492: }
493:
494: /** Waits until the AWTEventQueue is empty for a specified interval
495: */
496: public synchronized void waitEventQueueEmpty()
497: throws InterruptedException {
498: // store current time as the start time
499: long startTime = System.currentTimeMillis();
500: // register itself as an AWTEventListener
501: Toolkit.getDefaultToolkit().addAWTEventListener(
502: this ,
503: AWTEvent.ACTION_EVENT_MASK
504: | AWTEvent.ADJUSTMENT_EVENT_MASK
505: | AWTEvent.COMPONENT_EVENT_MASK
506: | AWTEvent.CONTAINER_EVENT_MASK
507: | AWTEvent.FOCUS_EVENT_MASK
508: | AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK
509: | AWTEvent.HIERARCHY_EVENT_MASK
510: | AWTEvent.INPUT_METHOD_EVENT_MASK
511: | AWTEvent.INVOCATION_EVENT_MASK
512: | AWTEvent.ITEM_EVENT_MASK
513: | AWTEvent.KEY_EVENT_MASK
514: | AWTEvent.MOUSE_EVENT_MASK
515: | AWTEvent.MOUSE_MOTION_EVENT_MASK
516: | AWTEvent.PAINT_EVENT_MASK
517: | AWTEvent.TEXT_EVENT_MASK
518: | AWTEvent.WINDOW_EVENT_MASK);
519:
520: // set last event time to the current time
521: lastEventTime = System.currentTimeMillis();
522: // get the thread to be put asleep
523: Thread t = Thread.currentThread();
524: // get current AWT Event Queue
525: EventQueue queue = Toolkit.getDefaultToolkit()
526: .getSystemEventQueue();
527:
528: try {
529:
530: // while AWT Event Queue is not empty and timedelay from last event is shorter then eventdelaytime
531:
532: //wait till the queue is empty
533: while (queue.peekEvent() != null)
534: Thread.sleep(100);
535: //test it - post my own task and wait for it
536: synchronized (this ) {
537: Runnable r = new Runnable() {
538: public void run() {
539: synchronized (QueueEmpty.this ) {
540: QueueEmpty.this .notifyAll();
541: }
542: }
543: };
544: EventQueue.invokeLater(r);
545: wait();
546: }
547: //now 2 sec continuously should be silence
548: while (System.currentTimeMillis() - lastEventTime < eventDelayTime) {
549: //sleep for the rest of eventDelay time
550: long sleepTime = eventDelayTime + lastEventTime
551: - System.currentTimeMillis();
552: // it may happen that sleepTime < 0 (TODO investigate why)
553: if (sleepTime > 0) {
554: Thread.sleep(sleepTime);
555: }
556: }
557:
558: //if (queue.peekEvent()==null) System.out.println("The AWT event queue seems to be empty.");
559: //else System.out.println("Ops, in the AWT event queue still seems to be some tasks!");
560:
561: } catch (InterruptedException ex) {
562: throw ex;
563: } finally {
564: //removing from listeners
565: Toolkit.getDefaultToolkit()
566: .removeAWTEventListener(this );
567: }
568: }
569:
570: }
571:
572: private static class WithExecClassLoader extends URLClassLoader {
573: public WithExecClassLoader() {
574: super (new URL[] { Main.class.getProtectionDomain()
575: .getCodeSource().getLocation() }, Thread
576: .currentThread().getContextClassLoader());
577: }
578:
579: protected Class loadClass(String n, boolean r)
580: throws ClassNotFoundException {
581: if (n
582: .startsWith("org.netbeans.xtest.plugin.ide.MainWithExec")) { // NOI18N
583: // Do not proxy to parent!
584: Class c = findLoadedClass(n);
585: if (c != null)
586: return c;
587: c = findClass(n);
588: if (r)
589: resolveClass(c);
590: return c;
591: } else {
592: return super .loadClass(n, r);
593: }
594: }
595: }
596:
597: public static interface MainWithExecInterface {
598: void run() throws Exception;
599:
600: void killPendingTasks();
601:
602: void discardChanges();
603:
604: void exit();
605: }
606:
607: /** ClassLoader to load projects API calls. */
608: private static class WithProjectsClassLoader extends URLClassLoader {
609: public WithProjectsClassLoader() {
610: super (new URL[] { Main.class.getProtectionDomain()
611: .getCodeSource().getLocation() }, Thread
612: .currentThread().getContextClassLoader());
613: }
614:
615: protected Class loadClass(String n, boolean r)
616: throws ClassNotFoundException {
617: if (n
618: .startsWith("org.netbeans.xtest.plugin.ide.MainWithProjects")) { // NOI18N
619: // Do not proxy to parent!
620: Class c = findLoadedClass(n);
621: if (c != null)
622: return c;
623: c = findClass(n);
624: if (r)
625: resolveClass(c);
626: return c;
627: } else {
628: return super .loadClass(n, r);
629: }
630: }
631: }
632:
633: public static interface MainWithProjectsInterface {
634: void openProject(String projectPath);
635:
636: void createProject(String projectDir);
637: }
638:
639: /* Workaround for JDK bug http://developer.java.sun.com/developer/bugParade/bugs/4924516.html.
640: * Also see issue http://www.netbeans.org/issues/show_bug.cgi?id=42414.
641: * ------------------------------------------------------------------------------------------
642: * It is fixed in JDK1.5.0, so tt can be removed when JDK1.4.2 become unsupported. The following
643: * listener is added to Toolkit at runBare() method and removed when it finishes.
644: * It distributes HierarchyEvent to all listening components and its subcomponents.
645: */
646: private static final DistributingHierarchyListener distributingHierarchyListener = new DistributingHierarchyListener();
647:
648: private static class DistributingHierarchyListener implements
649: AWTEventListener {
650:
651: public DistributingHierarchyListener() {
652: }
653:
654: public void eventDispatched(AWTEvent aWTEvent) {
655: HierarchyEvent hevt = null;
656: if (aWTEvent instanceof HierarchyEvent) {
657: hevt = (HierarchyEvent) aWTEvent;
658: }
659: if (hevt != null
660: && ((HierarchyEvent.SHOWING_CHANGED & hevt
661: .getChangeFlags()) != 0)) {
662: distributeShowingEvent(hevt.getComponent(), hevt);
663: }
664: }
665:
666: private static void distributeShowingEvent(Component c,
667: HierarchyEvent hevt) {
668: //HierarchyListener[] hierarchyListeners = c.getHierarchyListeners();
669: // Need to use component.getListeners because it is not synchronized
670: // and it not cause deadlock
671: HierarchyListener[] hierarchyListeners = (HierarchyListener[]) (c
672: .getListeners(HierarchyListener.class));
673: if (hierarchyListeners != null) {
674: for (int i = 0; i < hierarchyListeners.length; i++) {
675: hierarchyListeners[i].hierarchyChanged(hevt);
676: }
677: }
678: if (c instanceof Container) {
679: Container cont = (Container) c;
680: int n = cont.getComponentCount();
681: for (int i = 0; i < n; i++) {
682: distributeShowingEvent(cont.getComponent(i), hevt);
683: }
684: }
685: }
686: }
687:
688: }
|