View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.junit;
16  
17  import java.io.ByteArrayOutputStream;
18  import java.io.OutputStream;
19  import java.io.PrintStream;
20  import java.util.Optional;
21  import java.util.regex.Pattern;
22  
23  import org.apache.commons.lang3.StringUtils;
24  import org.htmlunit.WebDriverTestCase;
25  import org.junit.jupiter.api.extension.AfterEachCallback;
26  import org.junit.jupiter.api.extension.BeforeEachCallback;
27  import org.junit.jupiter.api.extension.ExtensionContext;
28  
29  /**
30   * JUnit callback verifying that nothing is printed to {@link System#err}
31   * during test execution. If this is the case, the rule generates a failure for
32   * the unit test.
33   *
34   * @author Marc Guillemot
35   * @author Ronald Brill
36   * @author Ahmed Ashour
37   * @author Frank Danek
38   */
39  public class ErrorOutputChecker implements BeforeEachCallback, AfterEachCallback {
40      private PrintStream originalErr_;
41      private final ByteArrayOutputStream baos_ = new ByteArrayOutputStream();
42      private static final Pattern[] PATTERNS = {
43              // jetty
44              Pattern.compile(".*Logging initialized .* to org.eclipse.jetty.util.log.StdErrLog.*\r?\n"),
45  
46              // slf4j
47              Pattern.compile("SLF4J\\(I\\): .*\r?\n"),
48  
49              // Quercus
50              Pattern.compile(".*com.caucho.quercus.servlet.QuercusServlet initImpl\r?\n"),
51              Pattern.compile(".*QuercusServlet starting as QuercusServletImpl\r?\n"),
52              Pattern.compile(".*Quercus finished initialization in \\d*ms\r?\n"),
53  
54              // XSLT
55              Pattern.compile("ERROR:  'Use of the extension function .*\r?\n"),
56      };
57  
58      @Override
59      public void beforeEach(final ExtensionContext context) throws Exception {
60          wrapSystemErr();
61      }
62  
63      @Override
64      public void afterEach(final ExtensionContext context) throws Exception {
65          try {
66              final Optional<Object> testInstance = context.getTestInstance();
67  
68              if (testInstance.isPresent()
69                      && testInstance.get() instanceof WebDriverTestCase) {
70                  final WebDriverTestCase webDriverTestCase = (WebDriverTestCase) testInstance.get();
71                  if (!webDriverTestCase.useRealBrowser()) {
72                      verifyNoOutput();
73                  }
74              }
75              else {
76                  verifyNoOutput();
77              }
78          }
79          finally {
80              restoreSystemErr();
81          }
82      }
83  
84      void verifyNoOutput() {
85          if (baos_.size() != 0) {
86              String output = baos_.toString();
87  
88              // remove webdriver messages
89              for (final Pattern pattern : PATTERNS) {
90                  output = pattern.matcher(output).replaceAll("");
91              }
92  
93              if (!output.isEmpty()) {
94                  if (output.contains("ChromeDriver")) {
95                      throw new RuntimeException("Outdated Chrome driver version: " + output);
96                  }
97                  if (output.contains("geckodriver")) {
98                      throw new RuntimeException("Outdated Gecko driver version: " + output);
99                  }
100                 output = StringUtils.replaceEach(output, new String[] {"\n", "\r"}, new String[]{"\\n", "\\r"});
101                 throw new RuntimeException("Test has produced output to System.err: " + output);
102             }
103         }
104     }
105 
106     private void wrapSystemErr() {
107         originalErr_ = System.err;
108         System.setErr(new NSAPrintStreamWrapper(originalErr_, baos_));
109     }
110 
111     void restoreSystemErr() {
112         System.setErr(originalErr_);
113     }
114 }
115 
116 /**
117  * A {@link PrintStream} spying what is written on the wrapped stream.
118  * It prints the content to the wrapped {@link PrintStream} and captures it simultaneously.
119  */
120 class NSAPrintStreamWrapper extends PrintStream {
121     private PrintStream wrapped_;
122 
123     NSAPrintStreamWrapper(final PrintStream original, final OutputStream spyOut) {
124         super(spyOut, true);
125         wrapped_ = original;
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public int hashCode() {
133         return wrapped_.hashCode();
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public boolean equals(final Object obj) {
141         return wrapped_.equals(obj);
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     @Override
148     public String toString() {
149         return wrapped_.toString();
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     @Override
156     public void flush() {
157         super.flush();
158         wrapped_.flush();
159     }
160 
161     /**
162      * {@inheritDoc}
163      */
164     @Override
165     public void close() {
166         super.close();
167         wrapped_.close();
168     }
169 
170     /**
171      * {@inheritDoc}
172      */
173     @Override
174     public boolean checkError() {
175         super.checkError();
176         return wrapped_.checkError();
177     }
178 
179     /**
180      * {@inheritDoc}
181      */
182     @Override
183     public void write(final int b) {
184         super.write(b);
185         wrapped_.write(b);
186     }
187 
188     /**
189      * {@inheritDoc}
190      */
191     @Override
192     public void write(final byte[] buf, final int off, final int len) {
193         super.write(buf, off, len);
194         wrapped_.write(buf, off, len);
195     }
196 }