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 java.io.IOException;
  27 import java.io.PrintStream;
  28 import java.util.Arrays;
  29 import java.util.List;
  30 import java.util.stream.Collectors;
  31 import java.util.regex.Matcher;
  32 import java.util.regex.Pattern;
  33 
  34 public final class OutputAnalyzer {
  35 
  36     private final String stdout;
  37     private final String stderr;
  38     private final int exitValue;
  39 
  40     /**
  41      * Create an OutputAnalyzer, a utility class for verifying output and exit
  42      * value from a Process
  43      *
  44      * @param process Process to analyze
  45      * @throws IOException If an I/O error occurs.
  46      */
  47     public OutputAnalyzer(Process process) throws IOException {
  48         OutputBuffer output = ProcessTools.getOutput(process);
  49         exitValue = process.exitValue();
  50         this.stdout = output.getStdout();
  51         this.stderr = output.getStderr();
  52     }
  53 
  54     /**
  55      * Create an OutputAnalyzer, a utility class for verifying output
  56      *
  57      * @param buf String buffer to analyze
  58      */
  59     public OutputAnalyzer(String buf) {
  60         this(buf, buf);
  61     }
  62 
  63     /**
  64      * Create an OutputAnalyzer, a utility class for verifying output
  65      *
  66      * @param stdout stdout buffer to analyze
  67      * @param stderr stderr buffer to analyze
  68      */
  69     public OutputAnalyzer(String stdout, String stderr) {
  70         this.stdout = stdout;
  71         this.stderr = stderr;
  72         exitValue = -1;
  73     }
  74 
  75     /**
  76      * Verify that the stdout contents of output buffer is empty
  77      *
  78      * @throws RuntimeException
  79      *             If stdout was not empty
  80      */
  81     public void stdoutShouldBeEmpty() {
  82         if (!getStdout().isEmpty()) {
  83             reportDiagnosticSummary();
  84             throw new RuntimeException("stdout was not empty");
  85         }
  86     }
  87 
  88     /**
  89      * Verify that the stderr contents of output buffer is empty
  90      *
  91      * @throws RuntimeException
  92      *             If stderr was not empty
  93      */
  94     public void stderrShouldBeEmpty() {
  95         if (!getStderr().isEmpty()) {
  96             reportDiagnosticSummary();
  97             throw new RuntimeException("stderr was not empty");
  98         }
  99     }
 100 
 101     /**
 102      * Verify that the stderr contents of output buffer is empty,
 103      * after filtering out the Hotspot warning messages
 104      *
 105      * @throws RuntimeException
 106      *             If stderr was not empty
 107      */
 108     public void stderrShouldBeEmptyIgnoreVMWarnings() {
 109         if (!getStderr().replaceAll(jvmwarningmsg + "\\R", "").isEmpty()) {
 110             reportDiagnosticSummary();
 111             throw new RuntimeException("stderr was not empty");
 112         }
 113     }
 114 
 115     /**
 116      * Verify that the stdout contents of output buffer is not empty
 117      *
 118      * @throws RuntimeException
 119      *             If stdout was empty
 120      */
 121     public void stdoutShouldNotBeEmpty() {
 122         if (getStdout().isEmpty()) {
 123             reportDiagnosticSummary();
 124             throw new RuntimeException("stdout was empty");
 125         }
 126     }
 127 
 128     /**
 129      * Verify that the stderr contents of output buffer is not empty
 130      *
 131      * @throws RuntimeException
 132      *             If stderr was empty
 133      */
 134     public void stderrShouldNotBeEmpty() {
 135         if (getStderr().isEmpty()) {
 136             reportDiagnosticSummary();
 137             throw new RuntimeException("stderr was empty");
 138         }
 139     }
 140 
 141     /**
 142      * Verify that the stdout and stderr contents of output buffer contains the string
 143      *
 144      * @param expectedString String that buffer should contain
 145      * @throws RuntimeException If the string was not found
 146      */
 147     public OutputAnalyzer shouldContain(String expectedString) {
 148         if (!stdout.contains(expectedString) && !stderr.contains(expectedString)) {
 149             reportDiagnosticSummary();
 150             throw new RuntimeException("'" + expectedString + "' missing from stdout/stderr \n");
 151         }
 152         return this;
 153     }
 154 
 155     /**
 156      * Verify that the stdout contents of output buffer contains the string
 157      *
 158      * @param expectedString String that buffer should contain
 159      * @throws RuntimeException If the string was not found
 160      */
 161     public OutputAnalyzer stdoutShouldContain(String expectedString) {
 162         if (!stdout.contains(expectedString)) {
 163             reportDiagnosticSummary();
 164             throw new RuntimeException("'" + expectedString + "' missing from stdout \n");
 165         }
 166         return this;
 167     }
 168 
 169     /**
 170      * Verify that the stderr contents of output buffer contains the string
 171      *
 172      * @param expectedString String that buffer should contain
 173      * @throws RuntimeException If the string was not found
 174      */
 175     public OutputAnalyzer stderrShouldContain(String expectedString) {
 176         if (!stderr.contains(expectedString)) {
 177             reportDiagnosticSummary();
 178             throw new RuntimeException("'" + expectedString + "' missing from stderr \n");
 179         }
 180         return this;
 181     }
 182 
 183     /**
 184      * Verify that the stdout and stderr contents of output buffer does not contain the string
 185      *
 186      * @param expectedString String that the buffer should not contain
 187      * @throws RuntimeException If the string was found
 188      */
 189     public OutputAnalyzer shouldNotContain(String notExpectedString) {
 190         if (stdout.contains(notExpectedString)) {
 191             reportDiagnosticSummary();
 192             throw new RuntimeException("'" + notExpectedString + "' found in stdout \n");
 193         }
 194         if (stderr.contains(notExpectedString)) {
 195             reportDiagnosticSummary();
 196             throw new RuntimeException("'" + notExpectedString + "' found in stderr \n");
 197         }
 198         return this;
 199     }
 200 
 201     /**
 202      * Verify that the stdout and stderr contents of output buffer are empty
 203      *
 204      * @throws RuntimeException If the stdout and stderr are not empty
 205      */
 206     public OutputAnalyzer shouldBeEmpty() {
 207         if (!stdout.isEmpty()) {
 208             reportDiagnosticSummary();
 209             throw new RuntimeException("stdout was not empty");
 210         }
 211         if (!stderr.isEmpty()) {
 212             reportDiagnosticSummary();
 213             throw new RuntimeException("stderr was not empty");
 214         }
 215         return this;
 216     }
 217 
 218     /**
 219      * Verify that the stdout contents of output buffer does not contain the string
 220      *
 221      * @param expectedString String that the buffer should not contain
 222      * @throws RuntimeException If the string was found
 223      */
 224     public OutputAnalyzer stdoutShouldNotContain(String notExpectedString) {
 225         if (stdout.contains(notExpectedString)) {
 226             reportDiagnosticSummary();
 227             throw new RuntimeException("'" + notExpectedString + "' found in stdout \n");
 228         }
 229         return this;
 230     }
 231 
 232     /**
 233      * Verify that the stderr contents of output buffer does not contain the string
 234      *
 235      * @param expectedString String that the buffer should not contain
 236      * @throws RuntimeException If the string was found
 237      */
 238     public OutputAnalyzer stderrShouldNotContain(String notExpectedString) {
 239         if (stderr.contains(notExpectedString)) {
 240             reportDiagnosticSummary();
 241             throw new RuntimeException("'" + notExpectedString + "' found in stderr \n");
 242         }
 243         return this;
 244     }
 245 
 246     /**
 247      * Verify that the stdout and stderr contents of output buffer matches
 248      * the pattern
 249      *
 250      * @param pattern
 251      * @throws RuntimeException If the pattern was not found
 252      */
 253     public OutputAnalyzer shouldMatch(String pattern) {
 254         Matcher stdoutMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
 255         Matcher stderrMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 256         if (!stdoutMatcher.find() && !stderrMatcher.find()) {
 257             reportDiagnosticSummary();
 258             throw new RuntimeException("'" + pattern
 259                   + "' missing from stdout/stderr \n");
 260         }
 261         return this;
 262     }
 263 
 264     /**
 265      * Verify that the stdout contents of output buffer matches the
 266      * pattern
 267      *
 268      * @param pattern
 269      * @throws RuntimeException If the pattern was not found
 270      */
 271     public OutputAnalyzer stdoutShouldMatch(String pattern) {
 272         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
 273         if (!matcher.find()) {
 274             reportDiagnosticSummary();
 275             throw new RuntimeException("'" + pattern
 276                   + "' missing from stdout \n");
 277         }
 278         return this;
 279     }
 280 
 281     /**
 282      * Verify that the stderr contents of output buffer matches the
 283      * pattern
 284      *
 285      * @param pattern
 286      * @throws RuntimeException If the pattern was not found
 287      */
 288     public OutputAnalyzer stderrShouldMatch(String pattern) {
 289 
 290         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 291         if (!matcher.find()) {
 292             reportDiagnosticSummary();
 293             throw new RuntimeException("'" + pattern
 294                   + "' missing from stderr \n");
 295         }
 296         return this;
 297     }
 298 
 299     /**
 300      * Verify that the stdout and stderr contents of output buffer does not
 301      * match the pattern
 302      *
 303      * @param pattern
 304      * @throws RuntimeException If the pattern was found
 305      */
 306     public OutputAnalyzer shouldNotMatch(String pattern) {
 307         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
 308         if (matcher.find()) {
 309             reportDiagnosticSummary();
 310             throw new RuntimeException("'" + pattern
 311                     + "' found in stdout: '" + matcher.group() + "' \n");
 312         }
 313         matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 314         if (matcher.find()) {
 315             reportDiagnosticSummary();
 316             throw new RuntimeException("'" + pattern
 317                     + "' found in stderr: '" + matcher.group() + "' \n");
 318         }
 319         return this;
 320     }
 321 
 322     /**
 323      * Verify that the stdout contents of output buffer does not match the
 324      * pattern
 325      *
 326      * @param pattern
 327      * @throws RuntimeException If the pattern was found
 328      */
 329     public OutputAnalyzer stdoutShouldNotMatch(String pattern) {
 330         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
 331         if (matcher.find()) {
 332             reportDiagnosticSummary();
 333             throw new RuntimeException("'" + pattern
 334                     + "' found in stdout \n");
 335         }
 336         return this;
 337     }
 338 
 339     /**
 340      * Verify that the stderr contents of output buffer does not match the
 341      * pattern
 342      *
 343      * @param pattern
 344      * @throws RuntimeException If the pattern was found
 345      */
 346     public OutputAnalyzer stderrShouldNotMatch(String pattern) {
 347         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 348         if (matcher.find()) {
 349             reportDiagnosticSummary();
 350             throw new RuntimeException("'" + pattern
 351                     + "' found in stderr \n");
 352         }
 353         return this;
 354     }
 355 
 356     /**
 357      * Get the captured group of the first string matching the pattern.
 358      * stderr is searched before stdout.
 359      *
 360      * @param pattern The multi-line pattern to match
 361      * @param group The group to capture
 362      * @return The matched string or null if no match was found
 363      */
 364     public String firstMatch(String pattern, int group) {
 365         Matcher stderrMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 366         Matcher stdoutMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
 367         if (stderrMatcher.find()) {
 368             return stderrMatcher.group(group);
 369         }
 370         if (stdoutMatcher.find()) {
 371             return stdoutMatcher.group(group);
 372         }
 373         return null;
 374     }
 375 
 376     /**
 377      * Get the first string matching the pattern.
 378      * stderr is searched before stdout.
 379      *
 380      * @param pattern The multi-line pattern to match
 381      * @return The matched string or null if no match was found
 382      */
 383     public String firstMatch(String pattern) {
 384         return firstMatch(pattern, 0);
 385     }
 386 
 387     /**
 388      * Verify the exit value of the process
 389      *
 390      * @param expectedExitValue Expected exit value from process
 391      * @throws RuntimeException If the exit value from the process did not match the expected value
 392      */
 393     public OutputAnalyzer shouldHaveExitValue(int expectedExitValue) {
 394         if (getExitValue() != expectedExitValue) {
 395             reportDiagnosticSummary();
 396             throw new RuntimeException("Expected to get exit value of ["
 397                     + expectedExitValue + "]\n");
 398         }
 399         return this;
 400     }
 401 
 402     /**
 403      * Verify the exit value of the process
 404      *
 405      * @param notExpectedExitValue Unexpected exit value from process
 406      * @throws RuntimeException If the exit value from the process did match the expected value
 407      */
 408     public OutputAnalyzer shouldNotHaveExitValue(int notExpectedExitValue) {
 409         if (getExitValue() == notExpectedExitValue) {
 410             reportDiagnosticSummary();
 411             throw new RuntimeException("Unexpected to get exit value of ["
 412                     + notExpectedExitValue + "]\n");
 413         }
 414         return this;
 415     }
 416 
 417 
 418     /**
 419      * Report summary that will help to diagnose the problem
 420      * Currently includes:
 421      *  - standard input produced by the process under test
 422      *  - standard output
 423      *  - exit code
 424      *  Note: the command line is printed by the ProcessTools
 425      */
 426     public void reportDiagnosticSummary() {
 427         String msg =
 428             " stdout: [" + stdout + "];\n" +
 429             " stderr: [" + stderr + "]\n" +
 430             " exitValue = " + getExitValue() + "\n";
 431 
 432         System.err.println(msg);
 433     }
 434 
 435     /**
 436      * Print the stdout buffer to the given {@code PrintStream}.
 437      *
 438      * @return this OutputAnalyzer
 439      */
 440     public OutputAnalyzer outputTo(PrintStream out) {
 441         out.println(getStdout());
 442         return this;
 443     }
 444 
 445     /**
 446      * Print the stderr buffer to the given {@code PrintStream}.
 447      *
 448      * @return this OutputAnalyzer
 449      */
 450     public OutputAnalyzer errorTo(PrintStream out) {
 451         out.println(getStderr());
 452         return this;
 453     }
 454 
 455     /**
 456      * Get the contents of the output buffer (stdout and stderr)
 457      *
 458      * @return Content of the output buffer
 459      */
 460     public String getOutput() {
 461         return stdout + stderr;
 462     }
 463 
 464     /**
 465      * Get the contents of the stdout buffer
 466      *
 467      * @return Content of the stdout buffer
 468      */
 469     public String getStdout() {
 470         return stdout;
 471     }
 472 
 473     /**
 474      * Get the contents of the stderr buffer
 475      *
 476      * @return Content of the stderr buffer
 477      */
 478     public String getStderr() {
 479         return stderr;
 480     }
 481 
 482     /**
 483      * Get the process exit value
 484      *
 485      * @return Process exit value
 486      */
 487     public int getExitValue() {
 488         return exitValue;
 489     }
 490 
 491     /**
 492      * Get the contents of the output buffer (stdout and stderr) as list of strings.
 493      * Output will be split by newlines.
 494      *
 495      * @return Contents of the output buffer as list of strings
 496      */
 497     public List<String> asLines() {
 498         return asLines(getOutput());
 499     }
 500 
 501     private List<String> asLines(String buffer) {
 502         return Arrays.asList(buffer.split("(\\r\\n|\\n|\\r)"));
 503     }
 504 
 505 
 506     private static final String jvmwarningmsg = ".* VM warning:.*";
 507 
 508     /**
 509      * Verifies that the stdout and stderr contents of output buffer are empty, after
 510      * filtering out the HotSpot warning messages.
 511      *
 512      * @throws RuntimeException If the stdout and stderr are not empty
 513      */
 514     public OutputAnalyzer shouldBeEmptyIgnoreVMWarnings() {
 515         if (!stdout.isEmpty()) {
 516             reportDiagnosticSummary();
 517             throw new RuntimeException("stdout was not empty");
 518         }
 519         if (!stderr.replaceAll(jvmwarningmsg + "\\R", "").isEmpty()) {
 520             reportDiagnosticSummary();
 521             throw new RuntimeException("stderr was not empty");
 522         }
 523         return this;
 524     }
 525 
 526     /**
 527      * Verify that the stderr contents of output buffer matches the pattern,
 528      * after filtering out the Hotespot warning messages
 529      *
 530      * @param pattern
 531      * @throws RuntimeException If the pattern was not found
 532      */
 533     public OutputAnalyzer stderrShouldMatchIgnoreVMWarnings(String pattern) {
 534         String stderr = this.stderr.replaceAll(jvmwarningmsg + "\\R", "");
 535         Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
 536         if (!matcher.find()) {
 537             reportDiagnosticSummary();
 538             throw new RuntimeException("'" + pattern
 539                   + "' missing from stderr \n");
 540         }
 541         return this;
 542     }
 543 
 544     /**
 545      * Returns the contents of the output buffer (stdout and stderr), without those
 546      * JVM warning msgs, as list of strings. Output is split by newlines.
 547      *
 548      * @return Contents of the output buffer as list of strings
 549      */
 550     public List<String> asLinesWithoutVMWarnings() {
 551         return Arrays.asList(getOutput().split("\\R"))
 552                 .stream()
 553                 .filter(Pattern.compile(jvmwarningmsg).asPredicate().negate())
 554                 .collect(Collectors.toList());
 555     }
 556 
 557 }