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-2006 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.apache.tools.ant.module.run;
043:
044: import java.io.File;
045: import java.net.MalformedURLException;
046: import java.net.URL;
047: import java.util.ArrayList;
048: import java.util.Arrays;
049: import java.util.Collections;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.Set;
053: import java.util.WeakHashMap;
054: import java.util.logging.Level;
055: import java.util.regex.Matcher;
056: import java.util.regex.Pattern;
057: import org.apache.tools.ant.module.spi.AntEvent;
058: import org.apache.tools.ant.module.spi.AntLogger;
059: import org.apache.tools.ant.module.spi.AntSession;
060: import org.apache.tools.ant.module.spi.TaskStructure;
061: import org.netbeans.junit.NbTestCase;
062: import org.openide.filesystems.FileUtil;
063: import org.openide.util.NbBundle;
064: import org.openide.util.Utilities;
065: import org.openide.util.WeakSet;
066: import org.openide.windows.OutputEvent;
067: import org.openide.windows.OutputListener;
068:
069: /**
070: * Tests functionality of the standard logger using a mock execution environment.
071: * @author Jesse Glick
072: */
073: public class StandardLoggerTest extends NbTestCase {
074:
075: // XXX set up mock StatusDisplayer impl?
076:
077: public StandardLoggerTest(String name) {
078: super (name);
079: }
080:
081: public void testBasicUsage() throws Exception {
082: // Just simulates something like
083: // <target name="some-target">
084: // <echo>some message</echo>
085: // <target>
086: AntLogger[] loggers = new AntLogger[] { new StandardLogger(
087: 15345L) };
088: MockAntSession session = new MockAntSession(loggers,
089: AntEvent.LOG_INFO);
090: AntSession realSession = LoggerTrampoline.ANT_SESSION_CREATOR
091: .makeAntSession(session);
092: session.sendBuildStarted(makeAntEvent(realSession, null, -1,
093: null, null, null));
094: session.sendTargetStarted(makeAntEvent(realSession, null, -1,
095: null, "some-target", null));
096: session.sendMessageLogged(makeAntEvent(realSession,
097: "some message", AntEvent.LOG_WARN, null, null, null));
098: session.sendTargetFinished(makeAntEvent(realSession, null, -1,
099: null, "some-target", null));
100: session.sendBuildFinished(makeAntEvent(realSession, null, -1,
101: null, null, null));
102: List<Message> expectedMessages = Arrays.asList(new Message[] {
103: new Message(NbBundle.getMessage(StandardLogger.class,
104: "MSG_target_started_printed", "some-target"),
105: false, null),
106: new Message("some message", true, null),
107: new Message(NbBundle.getMessage(StandardLogger.class,
108: "FMT_finished_target_printed", new Integer(0),
109: new Integer(15)), false, null), });
110: assertEquals("correct text printed", expectedMessages,
111: session.messages);
112: }
113:
114: public void testMultilineMessagesSplit() throws Exception {
115: // Tests what happens when messages with embedded newlines are sent.
116: // Does not cover build-terminating errors; for that use testThrownErrors.
117: AntLogger[] loggers = new AntLogger[] {
118: new MockStackTraceLogger(), new StandardLogger(1000L) };
119: MockAntSession session = new MockAntSession(loggers,
120: AntEvent.LOG_INFO);
121: AntSession realSession = LoggerTrampoline.ANT_SESSION_CREATOR
122: .makeAntSession(session);
123: session.sendBuildStarted(makeAntEvent(realSession, null, -1,
124: null, null, null));
125: session.sendMessageLogged(makeAntEvent(realSession,
126: "Stack trace in separate lines:", AntEvent.LOG_INFO,
127: null, null, null));
128: session
129: .sendMessageLogged(makeAntEvent(realSession,
130: "\tat Foo.java:3", AntEvent.LOG_INFO, null,
131: null, null));
132: session
133: .sendMessageLogged(makeAntEvent(realSession,
134: "\tat Bar.java:5", AntEvent.LOG_INFO, null,
135: null, null));
136: session.sendMessageLogged(makeAntEvent(realSession,
137: "Stack trace in one big line:\n"
138: + "\tat Quux.java:11\n" + "\tat Baz.java:6\n"
139: + "...and an unrelated message",
140: AntEvent.LOG_WARN, null, null, null));
141: session.sendBuildFinished(makeAntEvent(realSession, null, -1,
142: null, null, null));
143: List<Message> expectedMessages = Arrays
144: .asList(new Message[] {
145: new Message("Stack trace in separate lines:",
146: false, null),
147: new Message("\tat Foo.java:3", false,
148: new MockHyperlink("file:/src/Foo.java",
149: "stack trace", 3, -1, -1, -1)),
150: new Message("\tat Bar.java:5", false,
151: new MockHyperlink("file:/src/Bar.java",
152: "stack trace", 5, -1, -1, -1)),
153: new Message("Stack trace in one big line:",
154: true, null),
155: new Message("\tat Quux.java:11", true,
156: new MockHyperlink(
157: "file:/src/Quux.java",
158: "stack trace", 11, -1, -1, -1)),
159: new Message("\tat Baz.java:6", true,
160: new MockHyperlink("file:/src/Baz.java",
161: "stack trace", 6, -1, -1, -1)),
162: new Message("...and an unrelated message",
163: true, null),
164: new Message(NbBundle.getMessage(
165: StandardLogger.class,
166: "FMT_finished_target_printed",
167: new Integer(0), new Integer(1)), false,
168: null), });
169: assertEquals("correct text printed", expectedMessages,
170: session.messages);
171: }
172:
173: public void testFileHyperlinks() throws Exception {
174: clearWorkDir();
175: File top = new File(getWorkDir(), "top");
176: FileUtil.createData(top);
177: File dir1 = new File(getWorkDir(), "dir1");
178: File middle = new File(dir1, "middle");
179: FileUtil.createData(middle);
180: File dir2 = new File(dir1, "dir2");
181: File bottom = new File(dir2, "bottom");
182: FileUtil.createData(bottom);
183: MockAntSession session = new MockAntSession(
184: new AntLogger[] { new StandardLogger(1000L) },
185: AntEvent.LOG_INFO);
186: AntSession realSession = LoggerTrampoline.ANT_SESSION_CREATOR
187: .makeAntSession(session);
188: session.sendBuildStarted(makeAntEvent(realSession, null, -1,
189: null, null, null));
190: session.sendTargetStarted(makeAntEvent(realSession, null, -1,
191: null, "some-target", null));
192: /* Can't test, since the file does not actually exist:
193: session.sendMessageLogged(makeAntEvent(realSession, "c:\\temp\\foo: malformed", AntEvent.LOG_WARN, null, null, null));
194: session.sendMessageLogged(makeAntEvent(realSession, "c:\\temp\\bar:27: really malformed", AntEvent.LOG_WARN, null, null, null));
195: */
196: session.sendMessageLogged(makeAntEvent(realSession, top
197: + ":1: some problem", AntEvent.LOG_WARN, null, null,
198: null));
199: session.sendMessageLogged(makeAntEvent(realSession, top.toURI()
200: + ":1:10:2:5: same problem", AntEvent.LOG_WARN, null,
201: null, null));
202: session.sendMessageLogged(makeAntEvent(realSession,
203: "make: Entering directory `" + dir1 + "'",
204: AntEvent.LOG_INFO, null, null, null));
205: session.sendMessageLogged(makeAntEvent(realSession,
206: "middle:2:3: some other problem", AntEvent.LOG_WARN,
207: null, null, null));
208: session.sendMessageLogged(makeAntEvent(realSession,
209: "../top: yet another problem", AntEvent.LOG_WARN, null,
210: null, null));
211: session.sendMessageLogged(makeAntEvent(realSession,
212: "make: Entering directory `" + dir2 + "'",
213: AntEvent.LOG_INFO, null, null, null));
214: session.sendMessageLogged(makeAntEvent(realSession,
215: "bottom: something new", AntEvent.LOG_WARN, null, null,
216: null));
217: session.sendMessageLogged(makeAntEvent(realSession,
218: "\"../middle\", line 12: warning: statement is stupid",
219: AntEvent.LOG_WARN, null, null, null));
220: session.sendMessageLogged(makeAntEvent(realSession,
221: "make: Leaving directory `" + dir2 + "'",
222: AntEvent.LOG_INFO, null, null, null));
223: session.sendMessageLogged(makeAntEvent(realSession,
224: "middle: back here", AntEvent.LOG_WARN, null, null,
225: null));
226: session.sendMessageLogged(makeAntEvent(realSession,
227: "make: Leaving directory `" + dir1 + "'",
228: AntEvent.LOG_INFO, null, null, null));
229: session.sendTargetFinished(makeAntEvent(realSession, null, -1,
230: null, "some-target", null));
231: session.sendBuildFinished(makeAntEvent(realSession, null, -1,
232: null, null, null));
233: List<Message> expectedMessages = Arrays
234: .asList(
235: new Message(NbBundle.getMessage(
236: StandardLogger.class,
237: "MSG_target_started_printed",
238: "some-target"), false, null),
239: /*
240: new Message("c:\\temp\\foo: malformed", true, new MockHyperlink("c:\\temp\\foo", "malformed", -1, -1, -1, -1)),
241: new Message("c:\\temp\\bar:27: really malformed", true, new MockHyperlink("c:\\temp\\bar", "really malformed", 27, -1, -1, -1)),
242: */
243: new Message(top + ":1: some problem", true,
244: new MockHyperlink(top.toURI()
245: .toString(), "some problem", 1,
246: -1, -1, -1)),
247: new Message(top.toURI()
248: + ":1:10:2:5: same problem", true,
249: new MockHyperlink(top.toURI()
250: .toString(), "same problem", 1,
251: 10, 2, 5)),
252: new Message("make: Entering directory `" + dir1
253: + "'", false, null),
254: new Message("middle:2:3: some other problem",
255: true, new MockHyperlink(middle.toURI()
256: .toString(),
257: "some other problem", 2, 3, -1,
258: -1)),
259: new Message("../top: yet another problem",
260: true, new MockHyperlink(top.toURI()
261: .toString(),
262: "yet another problem", -1, -1,
263: -1, -1)),
264: new Message("make: Entering directory `" + dir2
265: + "'", false, null),
266: new Message("bottom: something new", true,
267: new MockHyperlink(bottom.toURI()
268: .toString(), "something new",
269: -1, -1, -1, -1)),
270: new Message(
271: "\"../middle\", line 12: warning: statement is stupid",
272: true, new MockHyperlink(middle.toURI()
273: .toString(),
274: "warning: statement is stupid",
275: 12, -1, -1, -1)), new Message(
276: "make: Leaving directory `" + dir2
277: + "'", false, null),
278: new Message("middle: back here", true,
279: new MockHyperlink(middle.toURI()
280: .toString(), "back here", -1,
281: -1, -1, -1)), new Message(
282: "make: Leaving directory `" + dir1
283: + "'", false, null),
284: new Message(NbBundle.getMessage(
285: StandardLogger.class,
286: "FMT_finished_target_printed", 0, 1),
287: false, null));
288: assertEquals("correct text printed", expectedMessages
289: .toString(), session.messages.toString());
290:
291: }
292:
293: // XXX testVerbosityLevels
294: // XXX testThrownErrors
295: // XXX testCaretShowingColumn
296:
297: /**
298: * Create an event to be delivered.
299: */
300: private static AntEvent makeAntEvent(AntSession realSession,
301: String message, int level, Throwable exc, String target,
302: String task) {
303: return LoggerTrampoline.ANT_EVENT_CREATOR
304: .makeAntEvent(new MockAntEvent(realSession, message,
305: level, exc, target, task));
306: }
307:
308: /**
309: * Minimal session implementation.
310: * Supports delivery of various events, though it does no filtering beyond
311: * filtering of messageLogged according to log level (has a custom verbosity).
312: * Handles custom data and consumption of exceptions.
313: * See {@link MockHyperlink} for hyperlink impl.
314: * Display name always "Mock Session"; orig target is "mock-target"; orig script is "/tmp/mock-script".
315: */
316: private static final class MockAntSession implements
317: LoggerTrampoline.AntSessionImpl {
318:
319: private final AntLogger[] loggers;
320: private final int verbosity;
321: private final Map<AntLogger, Object> customData = new WeakHashMap<AntLogger, Object>();
322: private final Set<Throwable> consumedExceptions = new WeakSet<Throwable>();
323: public final List<Message> messages = new ArrayList<Message>();
324:
325: public MockAntSession(AntLogger[] loggers, int verbosity) {
326: this .loggers = loggers;
327: this .verbosity = verbosity;
328: }
329:
330: public void sendMessageLogged(AntEvent event) {
331: for (AntLogger logger : loggers) {
332: int[] levels = logger.interestedInLogLevels(event
333: .getSession());
334: Arrays.sort(levels);
335: if (Arrays.binarySearch(levels, event.getLogLevel()) >= 0) {
336: logger.messageLogged(event);
337: }
338: }
339: }
340:
341: public void sendBuildStarted(AntEvent event) {
342: for (AntLogger logger : loggers) {
343: logger.buildStarted(event);
344: }
345: }
346:
347: public void sendBuildFinished(AntEvent event) {
348: for (AntLogger logger : loggers) {
349: logger.buildFinished(event);
350: }
351: }
352:
353: public void sendBuildInitializationFailed(AntEvent event) {
354: for (AntLogger logger : loggers) {
355: logger.buildInitializationFailed(event);
356: }
357: }
358:
359: public void sendTargetStarted(AntEvent event) {
360: for (AntLogger logger : loggers) {
361: logger.targetStarted(event);
362: }
363: }
364:
365: public void sendTargetFinished(AntEvent event) {
366: for (AntLogger logger : loggers) {
367: logger.targetFinished(event);
368: }
369: }
370:
371: public void sendTaskStarted(AntEvent event) {
372: for (AntLogger logger : loggers) {
373: logger.taskStarted(event);
374: }
375: }
376:
377: public void sendTaskFinished(AntEvent event) {
378: for (AntLogger logger : loggers) {
379: logger.taskFinished(event);
380: }
381: }
382:
383: public void deliverMessageLogged(AntEvent originalEvent,
384: String message, int level) {
385: sendMessageLogged(makeAntEvent(originalEvent.getSession(),
386: message, level, null,
387: originalEvent.getTargetName(), originalEvent
388: .getTaskName()));
389: }
390:
391: public OutputListener createStandardHyperlink(URL file,
392: String message, int line1, int column1, int line2,
393: int column2) {
394: return new MockHyperlink(file.toExternalForm(), message,
395: line1, column1, line2, column2);
396: }
397:
398: public void println(String message, boolean err,
399: OutputListener listener) {
400: messages.add(new Message(message, err, listener));
401: }
402:
403: public void putCustomData(AntLogger logger, Object data) {
404: customData.put(logger, data);
405: }
406:
407: public boolean isExceptionConsumed(Throwable t) { // copied from NbBuildLogger
408: if (consumedExceptions.contains(t)) {
409: return true;
410: }
411: Throwable nested = t.getCause();
412: if (nested != null && isExceptionConsumed(nested)) {
413: consumedExceptions.add(t);
414: return true;
415: }
416: return false;
417: }
418:
419: public void consumeException(Throwable t)
420: throws IllegalStateException { // copied from NbBuildLogger
421: if (isExceptionConsumed(t)) {
422: throw new IllegalStateException();
423: }
424: consumedExceptions.add(t);
425: }
426:
427: public Object getCustomData(AntLogger logger) {
428: return customData.get(logger);
429: }
430:
431: public int getVerbosity() {
432: return verbosity;
433: }
434:
435: public String[] getOriginatingTargets() {
436: return new String[] { "mock-target" };
437: }
438:
439: public File getOriginatingScript() {
440: return new File(System.getProperty("java.io.tmpdir"),
441: "mock-script");
442: }
443:
444: public String getDisplayName() {
445: return "Mock Session";
446: }
447:
448: }
449:
450: /**
451: * Minimal event class.
452: * Supports a message, level, exception, target and task.
453: * Supports isConsumed/consume.
454: */
455: private static final class MockAntEvent implements
456: LoggerTrampoline.AntEventImpl {
457:
458: private final AntSession session;
459: private final String message;
460: private final int level;
461: private final Throwable exc;
462: private final String target;
463: private final String task;
464: private boolean consumed = false;
465:
466: public MockAntEvent(AntSession session, String message,
467: int level, Throwable exc, String target, String task) {
468: this .session = session;
469: this .message = message;
470: this .level = level;
471: this .exc = exc;
472: this .target = target;
473: this .task = task;
474: }
475:
476: public String evaluate(String text) {
477: return null;
478: }
479:
480: public String getProperty(String name) {
481: return null;
482: }
483:
484: public boolean isConsumed() {
485: return consumed;
486: }
487:
488: public void consume() throws IllegalStateException {
489: if (consumed) {
490: throw new IllegalStateException();
491: } else {
492: consumed = true;
493: }
494: }
495:
496: public Throwable getException() {
497: return exc;
498: }
499:
500: public int getLine() {
501: return -1;
502: }
503:
504: public int getLogLevel() {
505: return level;
506: }
507:
508: public String getMessage() {
509: return message;
510: }
511:
512: public Set<String> getPropertyNames() {
513: return Collections.emptySet();
514: }
515:
516: public File getScriptLocation() {
517: return null;
518: }
519:
520: public AntSession getSession() {
521: return session;
522: }
523:
524: public String getTargetName() {
525: return target;
526: }
527:
528: public String getTaskName() {
529: return task;
530: }
531:
532: public TaskStructure getTaskStructure() {
533: return null;
534: }
535:
536: }
537:
538: /**
539: * Struct representing a message printed to an output stream.
540: */
541: private static final class Message {
542:
543: public final String message;
544: public final boolean err;
545: public final MockHyperlink hyperlink;
546:
547: public Message(String message, boolean err,
548: OutputListener listener) {
549: this .message = message;
550: this .err = err;
551: if (listener instanceof MockHyperlink) {
552: hyperlink = (MockHyperlink) listener;
553: } else {
554: hyperlink = null;
555: }
556: }
557:
558: @Override
559: public boolean equals(Object o) {
560: if (o instanceof Message) {
561: Message m = (Message) o;
562: return m.message.equals(message)
563: && m.err == err
564: && Utilities.compareObjects(m.hyperlink,
565: hyperlink);
566: } else {
567: return false;
568: }
569: }
570:
571: @Override
572: public String toString() {
573: return "Message[" + message + "]"
574: + (err ? "(err)" : "(out)")
575: + (hyperlink != null ? "(" + hyperlink + ")" : "");
576: }
577:
578: }
579:
580: /**
581: * Struct representing a hyperlink in a message.
582: */
583: private static final class MockHyperlink implements OutputListener {
584:
585: public final String url;
586: public final String message;
587: public final int line1;
588: public final int column1;
589: public final int line2;
590: public final int column2;
591:
592: public MockHyperlink(String url, String message, int line1,
593: int column1, int line2, int column2) {
594: this .url = url;
595: this .message = message;
596: this .line1 = line1;
597: this .column1 = column1;
598: this .line2 = line2;
599: this .column2 = column2;
600: }
601:
602: @Override
603: public boolean equals(Object o) {
604: if (o instanceof MockHyperlink) {
605: MockHyperlink h = (MockHyperlink) o;
606: return h.url.equals(url) && h.message.equals(message)
607: && h.line1 == line1 && h.column1 == column1
608: && h.line2 == line2 && h.column2 == column2;
609: } else {
610: return false;
611: }
612: }
613:
614: @Override
615: public String toString() {
616: return "MockHyperlink[" + url + ":" + line1 + ":" + column1
617: + ":" + line2 + ":" + column2 + ":" + message + "]";
618: }
619:
620: public void outputLineSelected(OutputEvent ev) {
621: }
622:
623: public void outputLineCleared(OutputEvent ev) {
624: }
625:
626: public void outputLineAction(OutputEvent ev) {
627: }
628:
629: }
630:
631: /**
632: * Sample logger which hyperlinks stuff looking a bit like Java stack traces,
633: * to test redelivery of logging events.
634: */
635: private static final class MockStackTraceLogger extends AntLogger {
636:
637: private static final Pattern STACK_TRACE_LINE = Pattern
638: .compile("\tat ([a-zA-Z]+\\.java):([0-9]+)");
639:
640: public MockStackTraceLogger() {
641: }
642:
643: @Override
644: public void messageLogged(AntEvent event) {
645: if (event.isConsumed()) {
646: return;
647: }
648: Matcher m = STACK_TRACE_LINE.matcher(event.getMessage());
649: if (m.matches()) {
650: event.consume();
651: String filename = m.group(1);
652: int line = Integer.parseInt(m.group(2));
653: AntSession session = event.getSession();
654: URL u;
655: try {
656: u = new URL("file:/src/" + filename);
657: } catch (MalformedURLException e) {
658: throw new AssertionError(e);
659: }
660: session.println(event.getMessage(),
661: event.getLogLevel() <= AntEvent.LOG_WARN,
662: session.createStandardHyperlink(u,
663: "stack trace", line, -1, -1, -1));
664: }
665: }
666:
667: @Override
668: public String[] interestedInTasks(AntSession session) {
669: return AntLogger.ALL_TASKS;
670: }
671:
672: @Override
673: public String[] interestedInTargets(AntSession session) {
674: return AntLogger.ALL_TARGETS;
675: }
676:
677: @Override
678: public boolean interestedInSession(AntSession session) {
679: return true;
680: }
681:
682: @Override
683: public int[] interestedInLogLevels(AntSession session) {
684: return new int[] { AntEvent.LOG_INFO, AntEvent.LOG_WARN,
685: AntEvent.LOG_ERR, };
686: }
687:
688: @Override
689: public boolean interestedInAllScripts(AntSession session) {
690: return true;
691: }
692:
693: @Override
694: public boolean interestedInScript(File script,
695: AntSession session) {
696: return true;
697: }
698:
699: }
700:
701: @Override
702: protected Level logLevel() {
703: return Level.FINE;
704: }
705:
706: }
|