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