1 /*
   2  * Copyright (c) 2013, 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.test.lib.process;
  25 
  26 import jdk.test.lib.Asserts;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintStream;
  30 import java.util.Arrays;
  31 import java.util.List;
  32 import java.util.stream.Collectors;
  33 import java.util.regex.Matcher;
  34 import java.util.regex.Pattern;
  35 
  36 public final class OutputAnalyzer {
  37 
  38     private final OutputBuffer buffer;
  39     /**
  40      * Create an OutputAnalyzer, a utility class for verifying output and exit
  41      * value from a Process
  42      *
  43      * @param process Process to analyze
  44      * @throws IOException If an I/O error occurs.
  45      */
  46     public OutputAnalyzer(Process process) throws IOException {
  47         buffer = OutputBuffer.of(process);
  48     }
  49 
  50     /**
  51      * Create an OutputAnalyzer, a utility class for verifying output
  52      *
  53      * @param buf String buffer to analyze
  54      */
  55     public OutputAnalyzer(String buf) {
  56         buffer = OutputBuffer.of(buf, buf);
  57     }
  58 
  59     /**
  60      * Create an OutputAnalyzer, a utility class for verifying output
  61      *
  62      * @param stdout stdout buffer to analyze
  63      * @param stderr stderr buffer to analyze
  64      */
  65     public OutputAnalyzer(String stdout, String stderr) {
  66         buffer = OutputBuffer.of(stdout, stderr);
  67     }
  68 
  69     /**
  70      * Verify that the stdout contents of output buffer is empty
  71      *
  72      * @throws RuntimeException
  73      *             If stdout was not empty
  74      */
  75     public OutputAnalyzer stdoutShouldBeEmpty() {
  76         if (!getStdout().isEmpty()) {
  77             reportDiagnosticSummary();
  78             throw new RuntimeException("stdout was not empty");
  79         }
  80         return this;
  81     }
  82 
  83     /**
  84      * Verify that the stderr contents of output buffer is empty
  85      *
  86      * @throws RuntimeException
  87      *             If stderr was not empty
  88      */
  89     public OutputAnalyzer stderrShouldBeEmpty() {
  90         if (!getStderr().isEmpty()) {
  91             reportDiagnosticSummary();
  92             throw new RuntimeException("stderr was not empty");
  93         }
  94         return this;
  95     }
  96 
  97     /**
  98      * Verify that the stderr contents of output buffer is empty,
  99      * after filtering out the Hotspot warning messages
 100      *
 101      * @throws RuntimeException
 102      *             If stderr was not empty
 103      */
 104     public OutputAnalyzer stderrShouldBeEmptyIgnoreVMWarnings() {
 105         if (!getStderr().replaceAll(jvmwarningmsg + "\\R", "").isEmpty()) {
 106             reportDiagnosticSummary();
 107             throw new RuntimeException("stderr was not empty");
 108         }
 109         return this;
 110     }
 111 
 112     /**
 113      * Verify that the stdout contents of output buffer is not empty
 114      *
 115      * @throws RuntimeException
 116      *             If stdout was empty
 117      */
 118     public OutputAnalyzer stdoutShouldNotBeEmpty() {
 119         if (getStdout().isEmpty()) {
 120             reportDiagnosticSummary();
 121             throw new RuntimeException("stdout was empty");
 122         }
 123         return this;
 124     }
 125 
 126     /**
 127      * Verify that the stderr contents of output buffer is not empty
 128      *
 129      * @throws RuntimeException
 130      *             If stderr was empty
 131      */
 132     public OutputAnalyzer stderrShouldNotBeEmpty() {
 133         if (getStderr().isEmpty()) {
 134             reportDiagnosticSummary();
 135             throw new RuntimeException("stderr was empty");
 136         }
 137         return this;
 138     }
 139 
 140     /**
 141      * Verify that the stdout and stderr contents of output buffer contains the string
 142      *
 143      * @param expectedString String that buffer should contain
 144      * @throws RuntimeException If the string was not found
 145      */
 146     public OutputAnalyzer shouldContain(String expectedString) {
 147         String stdout = getStdout();
 148         String stderr = getStderr();
 149         if (!stdout.contains(expectedString) && !stderr.contains(expectedString)) {
 150             reportDiagnosticSummary();
 151             throw new RuntimeException("'" + expectedString + "' missing from stdout/stderr \n");
 152         }
 153         return this;
 154     }
 155 
 156     /**
 157      * Verify that the stdout contents of output buffer contains the string
 158      *
 159      * @param expectedString String that buffer should contain
 160      * @throws RuntimeException If the string was not found
 161      */
 162     public OutputAnalyzer stdoutShouldContain(String expectedString) {
 163         String stdout = getStdout();
 164         if (!stdout.contains(expectedString)) {
 165             reportDiagnosticSummary();
 166             throw new RuntimeException("'" + expectedString + "' missing from stdout \n");
 167         }
 168         return this;
 169     }
 170 
 171     /**
 172      * Verify that the stderr contents of output buffer contains the string
 173      *
 174      * @param expectedString String that buffer should contain
 175      * @throws RuntimeException If the string was not found
 176      */
 177     public OutputAnalyzer stderrShouldContain(String expectedString) {
 178         String stderr = getStderr();
 179         if (!stderr.contains(expectedString)) {
 180             reportDiagnosticSummary();
 181             throw new RuntimeException("'" + expectedString + "' missing from stderr \n");
 182         }
 183         return this;
 184     }
 185 
 186     /**
 187      * Verify that the stdout and stderr contents of output buffer does not contain the string
 188      *
 189      * @param notExpectedString String that the buffer should not contain
 190      * @throws RuntimeException If the string was found
 191      */
 192     public OutputAnalyzer shouldNotContain(String notExpectedString) {
 193         String stdout = getStdout();
 194         String stderr = getStderr();
 195         if (stdout.contains(notExpectedString)) {
 196             reportDiagnosticSummary();
 197             throw new RuntimeException("'" + notExpectedString + "' found in stdout \n");
 198         }
 199         if (stderr.contains(notExpectedString)) {
 200             reportDiagnosticSummary();
 201             throw new RuntimeException("'" + notExpectedString + "' found in stderr \n");
 202         }
 203         return this;
 204     }
 205 
 206     /**
 207      * Verify that the stdout and stderr contents of output buffer are empty
 208      *
 209      * @throws RuntimeException If the stdout and stderr are not empty
 210      */
 211     public OutputAnalyzer shouldBeEmpty() {
 212         String stdout = getStdout();
 213         String stderr = getStderr();
 214         if (!stdout.isEmpty()) {
 215             reportDiagnosticSummary();
 216             throw new RuntimeException("stdout was not empty");
 217         }
 218         if (!stderr.isEmpty()) {
 219             reportDiagnosticSummary();
 220             throw new RuntimeException("stderr was not empty");
 221         }
 222         return this;
 223     }
 224 
 225     /**
 226      * Verify that the stdout contents of output buffer does not contain the string
 227      *
 228      * @param notExpectedString String that the buffer should not contain
 229      * @throws RuntimeException If the string was found
 230      */
 231     public OutputAnalyzer stdoutShouldNotContain(String notExpectedString) {
 232         String stdout = getStdout();
 233         if (stdout.contains(notExpectedString)) {
 234             reportDiagnosticSummary();
 235             throw new RuntimeException("'" + notExpectedString + "' found in stdout \n");
 236         }
 237         return this;
 238     }
 239 
 240     /**
 241      * Verify that the stderr contents of output buffer does not contain the string
 242      *
 243      * @param notExpectedString String that the buffer should not contain
 244      * @throws RuntimeException If the string was found
 245      */
 246     public OutputAnalyzer stderrShouldNotContain(String notExpectedString) {
 247         String stderr = getStderr();
 248         if (stderr.contains(notExpectedString)) {
 249             reportDiagnosticSummary();
 250             throw new RuntimeException("'" + notExpectedString + "' found in stderr \n");
 251         }
 252         return this;
 253     }
 254 
 255     /**
 256      * Verify that the stdout and stderr contents of output buffer matches
 257      * the pattern
 258      *
 259      * @param regexp
 260      * @throws RuntimeException If the pattern was not found
 261      */
 262     public OutputAnalyzer shouldMatch(String regexp) {
 263         String stdout = getStdout();
 264         String stderr = getStderr();
 265         Pattern pattern = Pattern.compile(regexp, Pattern.MULTILINE);
 266         Matcher stdoutMatcher = pattern.matcher(stdout);
 267         Matcher stderrMatcher = pattern.matcher(stderr);
 268         if (!stdoutMatcher.find() && !stderrMatcher.find()) {
 269             reportDiagnosticSummary();
 270             throw new RuntimeException("'" + regexp
 271                   + "' missing from stdout/stderr \n");
 272         }
 273         return this;
 274     }
 275 
 276     /**
 277      * Verify that the stdout contents of output buffer matches the
 278      * pattern
 279      *
 280      * @param regexp
 281      * @throws RuntimeException If the pattern was not found
 282      */
 283     public OutputAnalyzer stdoutShouldMatch(String regexp) {
 284         String stdout = getStdout();
 285         Matcher matcher = Pattern.compile(regexp, Pattern.MULTILINE).matcher(stdout);
 286         if (!matcher.find()) {
 287             reportDiagnosticSummary();
 288             throw new RuntimeException("'" + regexp
 289                   + "' missing from stdout \n");
 290         }
 291         return this;
 292     }
 293 
 294     /**
 295      * Verify that the stderr contents of output buffer matches the
 296      * pattern
 297      *
 298      * @param pattern
 299      * @throws RuntimeException If the pattern was not found
 300      */
 301     public OutputAnalyzer stderrShouldMatch(String pattern) {
 302         String stderr = getStderr();
 303         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 304         if (!matcher.find()) {
 305             reportDiagnosticSummary();
 306             throw new RuntimeException("'" + pattern
 307                   + "' missing from stderr \n");
 308         }
 309         return this;
 310     }
 311 
 312     /**
 313      * Verify that the stdout and stderr contents of output buffer does not
 314      * match the pattern
 315      *
 316      * @param regexp
 317      * @throws RuntimeException If the pattern was found
 318      */
 319     public OutputAnalyzer shouldNotMatch(String regexp) {
 320         String stdout = getStdout();
 321         Pattern pattern = Pattern.compile(regexp, Pattern.MULTILINE);
 322         Matcher matcher = pattern.matcher(stdout);
 323         if (matcher.find()) {
 324             reportDiagnosticSummary();
 325             throw new RuntimeException("'" + regexp
 326                     + "' found in stdout: '" + matcher.group() + "' \n");
 327         }
 328 
 329         String stderr = getStderr();
 330         matcher = pattern.matcher(stderr);
 331         if (matcher.find()) {
 332             reportDiagnosticSummary();
 333             throw new RuntimeException("'" + regexp
 334                     + "' found in stderr: '" + matcher.group() + "' \n");
 335         }
 336 
 337         return this;
 338     }
 339 
 340     /**
 341      * Verify that the stdout contents of output buffer does not match the
 342      * pattern
 343      *
 344      * @param regexp
 345      * @throws RuntimeException If the pattern was found
 346      */
 347     public OutputAnalyzer stdoutShouldNotMatch(String regexp) {
 348         String stdout = getStdout();
 349         Matcher matcher = Pattern.compile(regexp, Pattern.MULTILINE).matcher(stdout);
 350         if (matcher.find()) {
 351             reportDiagnosticSummary();
 352             throw new RuntimeException("'" + regexp
 353                     + "' found in stdout \n");
 354         }
 355         return this;
 356     }
 357 
 358     /**
 359      * Verify that the stderr contents of output buffer does not match the
 360      * pattern
 361      *
 362      * @param regexp
 363      * @throws RuntimeException If the pattern was found
 364      */
 365     public OutputAnalyzer stderrShouldNotMatch(String regexp) {
 366         String stderr = getStderr();
 367         Matcher matcher = Pattern.compile(regexp, Pattern.MULTILINE).matcher(stderr);
 368         if (matcher.find()) {
 369             reportDiagnosticSummary();
 370             throw new RuntimeException("'" + regexp
 371                     + "' found in stderr \n");
 372         }
 373         return this;
 374     }
 375 
 376     /**
 377      * Get the captured group of the first string matching the pattern.
 378      * stderr is searched before stdout.
 379      *
 380      * @param regexp The multi-line pattern to match
 381      * @param group The group to capture
 382      * @return The matched string or null if no match was found
 383      */
 384     public String firstMatch(String regexp, int group) {
 385         Pattern pattern = Pattern.compile(regexp, Pattern.MULTILINE);
 386         String stderr = getStderr();
 387         Matcher stderrMatcher = pattern.matcher(stderr);
 388         if (stderrMatcher.find()) {
 389             return stderrMatcher.group(group);
 390         }
 391         String stdout = getStdout();
 392         Matcher stdoutMatcher = pattern.matcher(stdout);
 393         if (stdoutMatcher.find()) {
 394             return stdoutMatcher.group(group);
 395         }
 396         return null;
 397     }
 398 
 399     /**
 400      * Get the first string matching the pattern.
 401      * stderr is searched before stdout.
 402      *
 403      * @param pattern The multi-line pattern to match
 404      * @return The matched string or null if no match was found
 405      */
 406     public String firstMatch(String pattern) {
 407         return firstMatch(pattern, 0);
 408     }
 409 
 410     /**
 411      * Verify the exit value of the process
 412      *
 413      * @param expectedExitValue Expected exit value from process
 414      * @throws RuntimeException If the exit value from the process did not match the expected value
 415      */
 416     public OutputAnalyzer shouldHaveExitValue(int expectedExitValue) {
 417         if (getExitValue() != expectedExitValue) {
 418             reportDiagnosticSummary();
 419             throw new RuntimeException("Expected to get exit value of ["
 420                     + expectedExitValue + "]\n");
 421         }
 422         return this;
 423     }
 424 
 425     /**
 426      * Verify the exit value of the process
 427      *
 428      * @param notExpectedExitValue Unexpected exit value from process
 429      * @throws RuntimeException If the exit value from the process did match the expected value
 430      */
 431     public OutputAnalyzer shouldNotHaveExitValue(int notExpectedExitValue) {
 432         if (getExitValue() == notExpectedExitValue) {
 433             reportDiagnosticSummary();
 434             throw new RuntimeException("Unexpected to get exit value of ["
 435                     + notExpectedExitValue + "]\n");
 436         }
 437         return this;
 438     }
 439 
 440 
 441     /**
 442      * Report summary that will help to diagnose the problem
 443      * Currently includes:
 444      *  - standard input produced by the process under test
 445      *  - standard output
 446      *  - exit code
 447      *  Note: the command line is printed by the ProcessTools
 448      */
 449     public void reportDiagnosticSummary() {
 450         String msg =
 451             " stdout: [" + getStdout() + "];\n" +
 452             " stderr: [" + getStderr() + "]\n" +
 453             " exitValue = " + getExitValue() + "\n";
 454 
 455         System.err.println(msg);
 456     }
 457 
 458     /**
 459      * Print the stdout buffer to the given {@code PrintStream}.
 460      *
 461      * @return this OutputAnalyzer
 462      */
 463     public OutputAnalyzer outputTo(PrintStream out) {
 464         out.println(getStdout());
 465         return this;
 466     }
 467 
 468     /**
 469      * Print the stderr buffer to the given {@code PrintStream}.
 470      *
 471      * @return this OutputAnalyzer
 472      */
 473     public OutputAnalyzer errorTo(PrintStream out) {
 474         out.println(getStderr());
 475         return this;
 476     }
 477 
 478     /**
 479      * Get the contents of the output buffer (stdout and stderr)
 480      *
 481      * @return Content of the output buffer
 482      */
 483     public String getOutput() {
 484         return getStdout() + getStderr();
 485     }
 486 
 487     /**
 488      * Get the contents of the stdout buffer
 489      *
 490      * @return Content of the stdout buffer
 491      */
 492     public String getStdout() {
 493         return buffer.getStdout();
 494     }
 495 
 496     /**
 497      * Get the contents of the stderr buffer
 498      *
 499      * @return Content of the stderr buffer
 500      */
 501     public String getStderr() {
 502         return buffer.getStderr();
 503     }
 504 
 505     /**
 506      * Get the process exit value
 507      *
 508      * @return Process exit value
 509      */
 510     public int getExitValue() {
 511         return buffer.getExitValue();
 512     }
 513 
 514     /**
 515      * Get the contents of the output buffer (stdout and stderr) as list of strings.
 516      * Output will be split by newlines.
 517      *
 518      * @return Contents of the output buffer as list of strings
 519      */
 520     public List<String> asLines() {
 521         return asLines(getOutput());
 522     }
 523 
 524     private List<String> asLines(String buffer) {
 525         return Arrays.asList(buffer.split("\\R"));
 526     }
 527 
 528 
 529     private static final String jvmwarningmsg = ".* VM warning:.*";
 530 
 531     /**
 532      * Verifies that the stdout and stderr contents of output buffer are empty, after
 533      * filtering out the HotSpot warning messages.
 534      *
 535      * @throws RuntimeException If the stdout and stderr are not empty
 536      */
 537     public OutputAnalyzer shouldBeEmptyIgnoreVMWarnings() {
 538         String stdout = getStdout();
 539         String stderr = getStderr();
 540         if (!stdout.isEmpty()) {
 541             reportDiagnosticSummary();
 542             throw new RuntimeException("stdout was not empty");
 543         }
 544         if (!stderr.replaceAll(jvmwarningmsg + "\\R", "").isEmpty()) {
 545             reportDiagnosticSummary();
 546             throw new RuntimeException("stderr was not empty");
 547         }
 548         return this;
 549     }
 550 
 551     /**
 552      * Verify that the stderr contents of output buffer matches the pattern,
 553      * after filtering out the Hotespot warning messages
 554      *
 555      * @param pattern
 556      * @throws RuntimeException If the pattern was not found
 557      */
 558     public OutputAnalyzer stderrShouldMatchIgnoreVMWarnings(String pattern) {
 559         String stderr = getStderr().replaceAll(jvmwarningmsg + "\\R", "");
 560         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 561         if (!matcher.find()) {
 562             reportDiagnosticSummary();
 563             throw new RuntimeException("'" + pattern
 564                   + "' missing from stderr \n");
 565         }
 566         return this;
 567     }
 568 
 569     /**
 570      * Returns the contents of the output buffer (stdout and stderr), without those
 571      * JVM warning msgs, as list of strings. Output is split by newlines.
 572      *
 573      * @return Contents of the output buffer as list of strings
 574      */
 575     public List<String> asLinesWithoutVMWarnings() {
 576         return Arrays.stream(getOutput().split("\\R"))
 577                      .filter(Pattern.compile(jvmwarningmsg).asPredicate().negate())
 578                      .collect(Collectors.toList());
 579     }
 580 
 581     /**
 582      * @see #shouldMatchByLine(String, String, String)
 583      */
 584     public OutputAnalyzer shouldMatchByLine(String pattern) {
 585         return shouldMatchByLine(null, null, pattern);
 586     }
 587 
 588     /**
 589      * @see #stdoutShouldMatchByLine(String, String, String)
 590      */
 591     public OutputAnalyzer stdoutShouldMatchByLine(String pattern) {
 592         return stdoutShouldMatchByLine(null, null, pattern);
 593     }
 594 
 595     /**
 596      * @see #shouldMatchByLine(String, String, String)
 597      */
 598     public OutputAnalyzer shouldMatchByLineFrom(String from, String pattern) {
 599         return shouldMatchByLine(from, null, pattern);
 600     }
 601 
 602     /**
 603      * @see #shouldMatchByLine(String, String, String)
 604      */
 605     public OutputAnalyzer shouldMatchByLineTo(String to, String pattern) {
 606         return shouldMatchByLine(null, to, pattern);
 607     }
 608 
 609     /**
 610      * Verify that the stdout and stderr contents of output buffer match the
 611      * {@code pattern} line by line. The whole output could be matched or
 612      * just a subset of it.
 613      *
 614      * @param from
 615      *            The line from where output will be matched.
 616      *            Set {@code from} to null for matching from the first line.
 617      * @param to
 618      *            The line until where output will be matched.
 619      *            Set {@code to} to null for matching until the last line.
 620      * @param pattern
 621      *            Matching pattern
 622      */
 623     public OutputAnalyzer shouldMatchByLine(String from, String to, String pattern) {
 624         return shouldMatchByLine(getOutput(), from, to, pattern);
 625     }
 626 
 627     /**
 628      * Verify that the stdout contents of output buffer matches the
 629      * {@code pattern} line by line. The whole stdout could be matched or
 630      * just a subset of it.
 631      *
 632      * @param from
 633      *            The line from where stdout will be matched.
 634      *            Set {@code from} to null for matching from the first line.
 635      * @param to
 636      *            The line until where stdout will be matched.
 637      *            Set {@code to} to null for matching until the last line.
 638      * @param pattern
 639      *            Matching pattern
 640      */
 641     public OutputAnalyzer stdoutShouldMatchByLine(String from, String to, String pattern) {
 642         return shouldMatchByLine(getStdout(), from, to, pattern);
 643     }
 644 
 645     private OutputAnalyzer shouldMatchByLine(String buffer, String from, String to, String pattern) {
 646         List<String> lines = asLines(buffer);
 647 
 648         int fromIndex = 0;
 649         if (from != null) {
 650             fromIndex = indexOf(lines, from);
 651             Asserts.assertGreaterThan(fromIndex, -1,
 652                     "The line/pattern '" + from + "' from where the output should match can not be found");
 653         }
 654 
 655         int toIndex = lines.size();
 656         if (to != null) {
 657             toIndex = indexOf(lines, to);
 658             Asserts.assertGreaterThan(toIndex, -1,
 659                     "The line/pattern '" + to + "' until where the output should match can not be found");
 660         }
 661 
 662         List<String> subList = lines.subList(fromIndex, toIndex);
 663         Asserts.assertFalse(subList.isEmpty(), "There are no lines to check");
 664 
 665         subList.stream()
 666                .filter(Pattern.compile(pattern).asPredicate().negate())
 667                .findAny()
 668                .ifPresent(line -> Asserts.assertTrue(false,
 669                        "The line '" + line + "' does not match pattern '" + pattern + "'"));
 670 
 671         return this;
 672     }
 673 
 674     /**
 675      * Check if there is a line matching {@code regexp} and return its index
 676      *
 677      * @param regexp Matching pattern
 678      * @return Index of first matching line
 679      */
 680     private int indexOf(List<String> lines, String regexp) {
 681         Pattern pattern = Pattern.compile(regexp);
 682         for (int i = 0; i < lines.size(); i++) {
 683             if (pattern.matcher(lines.get(i)).matches()) {
 684                 return i;
 685             }
 686         }
 687         return -1;
 688     }
 689 
 690 }