1 19 20 package org.netbeans.modules.junit.output; 21 22 import java.io.File ; 23 import java.io.FileInputStream ; 24 import java.io.IOException ; 25 import java.io.InputStreamReader ; 26 import java.io.StringReader ; 27 import java.net.MalformedURLException ; 28 import java.net.URL ; 29 import java.nio.charset.UnsupportedCharsetException ; 30 import java.text.MessageFormat ; 31 import java.util.Arrays ; 32 import java.util.logging.Level ; 33 import java.util.logging.Logger ; 34 import java.util.Collection ; 35 import java.util.GregorianCalendar ; 36 import java.util.LinkedHashSet ; 37 import java.util.StringTokenizer ; 38 import java.util.regex.Matcher ; 39 import org.apache.tools.ant.module.spi.AntEvent; 40 import org.apache.tools.ant.module.spi.AntSession; 41 import org.apache.tools.ant.module.spi.TaskStructure; 42 import org.netbeans.api.java.classpath.ClassPath; 43 import org.netbeans.api.java.platform.JavaPlatform; 44 import org.netbeans.api.java.platform.JavaPlatformManager; 45 import org.netbeans.api.java.queries.SourceForBinaryQuery; 46 import org.netbeans.api.progress.ProgressHandle; 47 import org.netbeans.api.progress.ProgressHandleFactory; 48 import org.openide.ErrorManager; 49 import org.openide.filesystems.FileObject; 50 import org.openide.filesystems.FileUtil; 51 import org.openide.util.NbBundle; 52 import org.xml.sax.SAXException ; 53 import static java.util.Calendar.MILLISECOND ; 54 import static org.netbeans.modules.junit.output.RegexpUtils.NESTED_EXCEPTION_PREFIX; 55 import static org.netbeans.modules.junit.output.RegexpUtils.OUTPUT_DELIMITER_PREFIX; 56 import static org.netbeans.modules.junit.output.RegexpUtils.TESTCASE_PREFIX; 57 import static org.netbeans.modules.junit.output.RegexpUtils.TESTSUITE_PREFIX; 58 import static org.netbeans.modules.junit.output.RegexpUtils.TESTSUITE_STATS_PREFIX; 59 import static org.netbeans.modules.junit.output.RegexpUtils.XML_DECL_PREFIX; 60 61 70 final class JUnitOutputReader { 71 72 private static final int MAX_REPORT_FILE_SIZE = 1 << 19; 74 private static final int PROGRESS_WORKUNITS = 1000; 75 76 private static final int UPDATE_DELAY = 300; 78 79 private static final String XML_FORMATTER_CLASS_NAME 80 = "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter"; 82 87 private int expectedSuitesCount = 0; 88 93 private int executedSuitesCount = 0; 94 98 private boolean testsuiteStatsKnown = false; 100 101 private final AntSession session; 102 103 private final TaskType sessionType; 104 107 private ProgressHandle progressHandle; 108 113 private boolean isDeterminateProgress; 114 115 private MessageFormat progressStepFormatSuiteName; 116 117 private MessageFormat progressStepFormatAnonymous; 118 119 private boolean expectXmlReport; 120 121 private final File antScript; 122 123 private final long timeOfSessionStart; 124 125 126 private RegexpUtils regexp = RegexpUtils.getInstance(); 127 128 129 private Report topReport; 130 131 private Report report; 132 133 private Report.Testcase testcase; 134 135 private Report.Trouble trouble; 136 137 private TroubleParser troubleParser; 138 139 private String suiteName; 140 141 142 private StringBuffer xmlOutputBuffer; 143 144 151 private boolean readingOutputReport; 152 153 private boolean lastHeaderBrief; 154 155 private boolean waitingForIssueStatus; 156 157 private final Manager manager = Manager.getInstance(); 158 159 private String classpath; 160 161 private ClassPath platformSources; 162 163 164 165 JUnitOutputReader(final AntSession session, 166 final TaskType sessionType, 167 final long timeOfSessionStart) { 168 this.session = session; 169 this.sessionType = sessionType; 170 this.antScript = session.getOriginatingScript(); 171 this.timeOfSessionStart = timeOfSessionStart; 172 } 173 174 176 void verboseMessageLogged(final AntEvent event) { 177 final String msg = event.getMessage(); 178 if (msg == null) { 179 return; 180 } 181 182 183 184 185 186 Matcher matcher; 187 188 matcher = RegexpUtils.CLASSPATH_ARGS.matcher(msg); 189 if (matcher.find()) { 190 this.classpath = matcher.group(1); 191 } 192 matcher = RegexpUtils.JAVA_EXECUTABLE.matcher(msg); 194 if (matcher.find()) { 195 String executable = matcher.group(1); 196 ClassPath platformSrcs = findPlatformSources(executable); 197 if (platformSrcs != null) { 198 this.platformSources = platformSrcs; 199 } 200 } 201 } 202 203 205 void messageLogged(final AntEvent event) { 206 final String msg = event.getMessage(); 207 if (msg == null) { 208 return; 209 } 210 211 if (waitingForIssueStatus) { 213 assert testcase != null; 214 215 Matcher matcher = regexp.getTestcaseIssuePattern().matcher(msg); 216 if (matcher.matches()) { 217 boolean error = (matcher.group(1) == null); 218 219 trouble = (testcase.trouble = new Report.Trouble(error)); 220 waitingForIssueStatus = false; 221 return; 222 } else { 223 report.reportTest(testcase); 224 waitingForIssueStatus = false; 225 } 226 } if (xmlOutputBuffer != null) { 229 xmlOutputBuffer.append(msg).append('\n'); 230 if (msg.equals("</testsuite>")) { closePreviousReport(); 232 } 233 return; 234 } if (readingOutputReport) { 237 if (msg.startsWith(OUTPUT_DELIMITER_PREFIX)) { 238 Matcher matcher = regexp.getOutputDelimPattern().matcher(msg); 239 if (matcher.matches() && (matcher.group(1) == null)) { 240 readingOutputReport = false; 241 } 242 } 243 return; 244 } if (trouble != null) { 247 if (troubleParser == null) { 248 troubleParser = new TroubleParser(trouble, regexp); 249 } 250 if (troubleParser.processMessage(msg)) { 251 troubleParser = null; 252 253 if ((trouble.stackTrace != null) && (trouble.stackTrace.length != 0)) { 254 setClasspathSourceRoots(); 255 } 256 257 report.reportTest(testcase); 258 259 trouble = null; 260 testcase = null; 261 } 262 return; 263 } 265 if (msg.startsWith(TESTCASE_PREFIX)) { 267 268 if (report == null) { 269 return; 270 } 271 272 String header = msg.substring(TESTCASE_PREFIX.length()); 273 274 boolean success = 275 lastHeaderBrief 276 ? tryParseBriefHeader(header) 277 || !(lastHeaderBrief = !tryParsePlainHeader(header)) 278 : tryParsePlainHeader(header) 279 || (lastHeaderBrief = tryParseBriefHeader(header)); 280 if (success) { 281 waitingForIssueStatus = !lastHeaderBrief; 282 } 283 } else if (msg.startsWith(OUTPUT_DELIMITER_PREFIX) 286 && regexp.getOutputDelimPattern().matcher(msg).matches()) { 287 if (report == null) { 288 return; 289 } 290 readingOutputReport = true; 291 } else if (expectXmlReport && msg.startsWith(XML_DECL_PREFIX)) { 294 Matcher matcher = regexp.getXmlDeclPattern().matcher(msg.trim()); 295 if (matcher.matches()) { 296 suiteStarted(null); 297 298 xmlOutputBuffer = new StringBuffer (4096); 299 xmlOutputBuffer.append(msg); 300 } 301 } else if (msg.startsWith(TESTSUITE_PREFIX)) { 304 suiteName = msg.substring(TESTSUITE_PREFIX.length()); 305 if (regexp.getFullJavaIdPattern().matcher(suiteName).matches()){ 306 suiteStarted(suiteName); 307 report.resultsDir = determineResultsDir(event); 308 } 309 } else if (msg.startsWith(TESTSUITE_STATS_PREFIX)) { 312 313 if (report == null) { 314 return; 315 } 316 if (testsuiteStatsKnown) { 317 return; } 319 320 Matcher matcher = regexp.getSuiteStatsPattern().matcher(msg); 321 if (matcher.matches()) { 322 assert report != null; 323 324 try { 325 report.totalTests = Integer.parseInt(matcher.group(1)); 326 report.failures = Integer.parseInt(matcher.group(2)); 327 report.errors = Integer.parseInt(matcher.group(3)); 328 report.elapsedTimeMillis 329 = regexp.parseTimeMillis(matcher.group(4)); 330 } catch (NumberFormatException ex) { 331 assert false; 333 } 334 } 335 testsuiteStatsKnown = true; 336 } else if ((suiteName != null) 339 && msg.startsWith("Test ") && msg.endsWith(" FAILED") && msg.equals("Test " + suiteName + " FAILED")) { suiteName = null; 343 } 346 else { 349 displayOutput(msg, 350 event.getLogLevel() == AntEvent.LOG_WARN); 351 } 352 } 354 355 363 private static File determineResultsDir(final AntEvent event) { 364 File resultsDir = null; 365 String dirName = null; 366 367 final String taskName = event.getTaskName(); 368 if (taskName != null) { 369 if (taskName.equals("junit")) { dirName = determineJunitTaskResultsDir(event); 371 } else if (taskName.equals("java")) { dirName = determineJavaTaskResultsDir(event); 373 } 374 } 375 376 if (dirName != null) { 377 resultsDir = new File (dirName); 378 if (!resultsDir.isAbsolute()) { 379 resultsDir = new File (event.getProperty("basedir"), dirName); 381 } 382 if (!resultsDir.exists() || !resultsDir.isDirectory()) { 383 resultsDir = null; 384 } 385 } else { 386 resultsDir = null; 387 } 388 389 return resultsDir; 390 } 391 392 394 private static String determineJunitTaskResultsDir(final AntEvent event) { 395 final TaskStructure taskStruct = event.getTaskStructure(); 396 if (taskStruct == null) { 397 return null; 398 } 399 400 String dirName = null; 401 boolean hasXmlFileOutput = false; 402 403 for (TaskStructure taskChild : taskStruct.getChildren()) { 404 String taskChildName = taskChild.getName(); 405 if (taskChildName.equals("batchtest") || taskChildName.equals("test")) { String dirAttr = taskChild.getAttribute("todir"); dirName = (dirAttr != null) 409 ? event.evaluate(dirAttr) 410 : "."; 412 413 } else if (taskChildName.equals("formatter")) { if (hasXmlFileOutput) { 415 continue; 416 } 417 String typeAttr = taskChild.getAttribute("type"); if ((typeAttr != null) 419 && "xml".equals(event.evaluate(typeAttr))) { String useFileAttr 421 = taskChild.getAttribute("usefile"); if ((useFileAttr == null) 423 || "true".equals(event.evaluate(useFileAttr))) { hasXmlFileOutput = true; 425 } 426 } 427 } 428 } 429 430 return hasXmlFileOutput ? dirName : null; 431 } 432 433 435 private static String determineJavaTaskResultsDir(final AntEvent event) { 436 final TaskStructure taskStruct = event.getTaskStructure(); 437 if (taskStruct == null) { 438 return null; 439 } 440 441 for (TaskStructure taskChild : taskStruct.getChildren()) { 442 String taskChildName = taskChild.getName(); 443 if (taskChildName.equals("arg")) { String valueAttr = taskChild.getAttribute("value"); if (valueAttr != null) { 446 valueAttr = event.evaluate(valueAttr); 447 if (valueAttr.startsWith("formatter=")) { String formatter = valueAttr.substring("formatter=".length()); int commaIndex = formatter.indexOf(','); 450 if ((commaIndex != -1) 451 && formatter.substring(0, commaIndex).equals(XML_FORMATTER_CLASS_NAME)) { 452 String fullReportFileName = formatter.substring(commaIndex + 1); 453 int lastSlashIndex = fullReportFileName.lastIndexOf('/'); 454 String dirName = (lastSlashIndex != -1) 455 ? fullReportFileName.substring(0, lastSlashIndex) 456 : "."; if (dirName.length() != 0) { 458 return dirName; 459 } 460 } 461 } 462 } 463 } 464 } 465 466 return null; 467 } 468 469 471 private Report createReport(final String suiteName) { 472 Report report = new Report(suiteName); 473 report.antScript = antScript; 474 475 report.classpath = classpath; 476 report.platformSources = platformSources; 477 478 this.classpath = null; 479 this.platformSources = null; 480 481 return report; 482 } 483 484 486 private ClassPath findPlatformSources(final String javaExecutable) { 487 488 489 490 final JavaPlatform[] platforms = JavaPlatformManager.getDefault() 491 .getInstalledPlatforms(); 492 for (int i = 0; i < platforms.length; i++) { 493 FileObject fo = platforms[i].findTool("java"); if (fo != null) { 495 File f = FileUtil.toFile(fo); 496 if (f.getAbsolutePath().startsWith(javaExecutable)) { 497 return platforms[i].getSourceFolders(); 498 } 499 } 500 } 501 return null; 502 } 503 504 511 private void setClasspathSourceRoots() { 512 513 514 515 if (report == null) { 516 return; 517 } 518 519 if (report.classpathSourceRoots != null) { return; 521 } 522 523 if (report.classpath == null) { 524 return; 525 } 526 527 Collection <FileObject> sourceRoots = new LinkedHashSet <FileObject>(); 528 final StringTokenizer tok = new StringTokenizer (report.classpath, 529 File.pathSeparator); 530 while (tok.hasMoreTokens()) { 531 String binrootS = tok.nextToken(); 532 File f = FileUtil.normalizeFile(new File (binrootS)); 533 URL binroot; 534 try { 535 binroot = f.toURI().toURL(); 536 } catch (MalformedURLException e) { 537 throw new AssertionError (e); 538 } 539 if (FileUtil.isArchiveFile(binroot)) { 540 URL root = FileUtil.getArchiveRoot(binroot); 541 if (root != null) { 542 binroot = root; 543 } 544 } 545 FileObject[] someRoots = SourceForBinaryQuery 546 .findSourceRoots(binroot).getRoots(); 547 sourceRoots.addAll(Arrays.asList(someRoots)); 548 } 549 550 if (report.platformSources != null) { 551 sourceRoots.addAll(Arrays.asList(report.platformSources.getRoots())); 552 } else { 553 JavaPlatform platform = JavaPlatform.getDefault(); 555 if (platform != null) { 557 sourceRoots.addAll( 558 Arrays.asList(platform.getSourceFolders().getRoots())); 559 } 560 } 561 report.classpathSourceRoots = sourceRoots; 562 563 567 report.classpath = null; 568 report.platformSources = null; 569 } 570 571 577 void testTaskStarted(int expectedSuitesCount, boolean expectXmlOutput) { 578 this.expectXmlReport = expectXmlOutput; 579 580 final boolean willBeDeterminateProgress = (expectedSuitesCount > 0); 581 if (progressHandle == null) { 582 progressHandle = ProgressHandleFactory.createHandle( 583 NbBundle.getMessage(getClass(), "MSG_ProgressMessage")); 585 if (willBeDeterminateProgress) { 586 this.expectedSuitesCount = expectedSuitesCount; 587 progressHandle.start(PROGRESS_WORKUNITS); 588 progressHandle.progress(PROGRESS_WORKUNITS / 100); 589 } else { 590 progressHandle.start(); 591 } 592 } else if (willBeDeterminateProgress) { 593 if (!isDeterminateProgress) { 594 progressHandle.switchToDeterminate(PROGRESS_WORKUNITS); 595 } 596 this.expectedSuitesCount += expectedSuitesCount; 597 updateProgress(); 598 } else if (isDeterminateProgress ) { 599 progressHandle.switchToIndeterminate(); 600 } isDeterminateProgress = willBeDeterminateProgress; 604 605 Manager.getInstance().testStarted(session, 606 sessionType); 607 } 608 609 611 void testTaskFinished() { 612 expectedSuitesCount = executedSuitesCount; 613 if (isDeterminateProgress) { 614 620 progressHandle.progress(PROGRESS_WORKUNITS); 621 } 622 } 623 624 628 private void updateProgress() { 629 assert progressHandle != null; 630 631 progressHandle.progress(getProcessedWorkunits()); 632 } 633 634 639 private String getProgressStepMessage(String suiteName) { 640 String msg; 641 642 if (isDeterminateProgress) { 643 MessageFormat messageFormat; 644 Object [] messageParams; 645 if (suiteName != null) { 646 if (progressStepFormatSuiteName == null) { 647 progressStepFormatSuiteName = new MessageFormat ( 648 NbBundle.getMessage( 649 getClass(), 650 "MSG_ProgressStepMessage")); } 652 messageFormat = progressStepFormatSuiteName; 653 messageParams = new Object [] {suiteName, 654 executedSuitesCount + 1, 655 expectedSuitesCount}; 656 } else { 657 if (progressStepFormatAnonymous == null) { 658 progressStepFormatAnonymous = new MessageFormat ( 659 NbBundle.getMessage( 660 getClass(), 661 "MSG_ProgressStepMessageAnonymous")); } 663 messageFormat = progressStepFormatAnonymous; 664 messageParams = new Object [] {executedSuitesCount + 1, 665 expectedSuitesCount}; 666 } 667 msg = messageFormat.format(messageParams, new StringBuffer (), null) 668 .toString(); 669 } else { 670 msg = (suiteName != null) ? suiteName : ""; } 672 return msg; 673 } 674 675 678 private int getProcessedWorkunits() { 679 try { 680 return executedSuitesCount * PROGRESS_WORKUNITS / expectedSuitesCount; 681 } catch (Exception ex) { 682 return 0; 683 } 684 } 685 686 688 void buildFinished(final AntEvent event) { 689 try { 690 finishReport(event.getException()); 691 Manager.getInstance().sessionFinished(session, 692 sessionType); 693 } finally { 694 progressHandle.finish(); 695 } 696 } 697 698 704 private Report suiteStarted(final String suiteName) { 705 closePreviousReport(); 706 report = createReport(suiteName); 707 708 String stepMessage = getProgressStepMessage(suiteName); 709 if (expectedSuitesCount <= executedSuitesCount) { 710 expectedSuitesCount = executedSuitesCount + 1; 711 } 712 if (executedSuitesCount != 0) { 713 progressHandle.progress(stepMessage, getProcessedWorkunits()); 714 } else { 715 progressHandle.progress(stepMessage); 716 } 717 718 Manager.getInstance().displaySuiteRunning(session, 719 sessionType, 720 suiteName); 721 return report; 722 } 723 724 726 private void suiteFinished(final Report report) { 727 executedSuitesCount++; 728 729 Manager.getInstance().displayReport(session, sessionType, report); 730 } 731 732 734 void finishReport(final Throwable exception) { 735 if (waitingForIssueStatus) { 736 assert testcase != null; 737 738 report.reportTest(testcase); 739 } 740 closePreviousReport(); 741 742 754 755 769 } 771 772 774 776 private void displayOutput(final String text, final boolean error) { 777 Manager.getInstance().displayOutput(session, sessionType, text, error); 778 } 779 780 782 784 private boolean tryParsePlainHeader(String testcaseHeader) { 785 final Matcher matcher = regexp.getTestcaseHeaderPlainPattern() 786 .matcher(testcaseHeader); 787 if (matcher.matches()) { 788 String methodName = matcher.group(1); 789 int timeMillis = regexp.parseTimeMillisNoNFE(matcher.group(2)); 790 791 testcase = new Report.Testcase(); 792 testcase.className = null; 793 testcase.name = methodName; 794 testcase.timeMillis = timeMillis; 795 796 trouble = null; 797 troubleParser = null; 798 799 return true; 800 } else { 801 return false; 802 } 803 } 804 805 807 private boolean tryParseBriefHeader(String testcaseHeader) { 808 final Matcher matcher = regexp.getTestcaseHeaderBriefPattern() 809 .matcher(testcaseHeader); 810 if (matcher.matches()) { 811 String methodName = matcher.group(1); 812 String clsName = matcher.group(2); 813 boolean error = (matcher.group(3) == null); 814 815 testcase = new Report.Testcase(); 816 testcase.className = clsName; 817 testcase.name = methodName; 818 testcase.timeMillis = -1; 819 820 trouble = (testcase.trouble = new Report.Trouble(error)); 821 822 return true; 823 } else { 824 return false; 825 } 826 } 827 828 830 private void closePreviousReport() { 831 if (xmlOutputBuffer != null) { 832 try { 833 String xmlOutput = xmlOutputBuffer.toString(); 834 xmlOutputBuffer = null; Report xmlReport; 836 xmlReport = XmlOutputParser.parseXmlOutput( 837 new StringReader (xmlOutput)); 838 report.update(xmlReport); 839 } catch (SAXException ex) { 840 841 } catch (IOException ex) { 842 assert false; } 844 } else if ((report != null) && (report.resultsDir != null)) { 845 849 850 File reportFile = new File ( 851 report.resultsDir, 852 "TEST-" + report.suiteClassName + ".xml"); if (reportFile.exists() && isValidReportFile(reportFile)) { 854 final long fileSize = reportFile.length(); 855 if ((fileSize > 0l) && (fileSize <= MAX_REPORT_FILE_SIZE)) { 856 try { 857 Report fileReport; 858 fileReport = XmlOutputParser.parseXmlOutput( 859 new InputStreamReader ( 860 new FileInputStream (reportFile), 861 "UTF-8")); report.update(fileReport); 863 } catch (UnsupportedCharsetException ex) { 864 assert false; 865 } catch (SAXException ex) { 866 867 } catch (IOException ex) { 868 872 int severity = ErrorManager.INFORMATIONAL; 873 ErrorManager errMgr = ErrorManager.getDefault(); 874 if (errMgr.isLoggable(severity)) { 875 errMgr.notify( 876 severity, 877 errMgr.annotate( 878 ex, 879 "I/O exception while reading JUnit XML report file from JUnit: ")); } 881 } 882 } 883 } 884 } 885 if (report != null) { 886 suiteFinished(report); 887 } 888 889 xmlOutputBuffer = null; 890 readingOutputReport = false; 891 testcase = null; 892 trouble = null; 893 troubleParser = null; 894 report = null; 895 testsuiteStatsKnown = false; 896 } 897 898 900 private boolean isValidReportFile(File reportFile) { 901 if (!reportFile.isFile() || !reportFile.canRead()) { 902 return false; 903 } 904 905 long lastModified = reportFile.lastModified(); 906 long timeDelta = lastModified - timeOfSessionStart; 907 908 final Logger logger = Logger.getLogger("org.netbeans.modules.junit.outputreader.timestamps"); final Level logLevel = Level.FINER; 910 if (logger.isLoggable(logLevel)) { 911 logger.log(logLevel, "Report file: " + reportFile.getPath()); 913 final GregorianCalendar timeStamp = new GregorianCalendar (); 914 915 timeStamp.setTimeInMillis(timeOfSessionStart); 916 logger.log(logLevel, "Session start: " + String.format("%1$tT.%2$03d", timeStamp, timeStamp.get(MILLISECOND))); 918 timeStamp.setTimeInMillis(lastModified); 919 logger.log(logLevel, "Report timestamp: " + String.format("%1$tT.%2$03d", timeStamp, timeStamp.get(MILLISECOND))); } 921 922 if (timeDelta >= 0) { 923 return true; 924 } 925 926 936 return -timeDelta <= timeOfSessionStart % 1000; 937 938 } 963 964 } 965 | Popular Tags |