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