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  
55      @Override
56      public void beforeEach(final ExtensionContext context) throws Exception {
57          wrapSystemErr();
58      }
59  
60      @Override
61      public void afterEach(final ExtensionContext context) throws Exception {
62          try {
63              final Optional<Object> testInstance = context.getTestInstance();
64  
65              if (testInstance.isPresent()
66                      && testInstance.get() instanceof WebDriverTestCase) {
67                  final WebDriverTestCase webDriverTestCase = (WebDriverTestCase) testInstance.get();
68                  if (!webDriverTestCase.useRealBrowser()) {
69                      verifyNoOutput();
70                  }
71              }
72              else {
73                  verifyNoOutput();
74              }
75          }
76          finally {
77              restoreSystemErr();
78          }
79      }
80  
81      void verifyNoOutput() {
82          if (baos_.size() != 0) {
83              String output = baos_.toString();
84  
85              // remove webdriver messages
86              for (final Pattern pattern : PATTERNS) {
87                  output = pattern.matcher(output).replaceAll("");
88              }
89  
90              if (!output.isEmpty()) {
91                  if (output.contains("ChromeDriver")) {
92                      throw new RuntimeException("Outdated Chrome driver version: " + output);
93                  }
94                  if (output.contains("geckodriver")) {
95                      throw new RuntimeException("Outdated Gecko driver version: " + output);
96                  }
97                  output = StringUtils.replaceEach(output, new String[] {"\n", "\r"}, new String[]{"\\n", "\\r"});
98                  throw new RuntimeException("Test has produced output to System.err: " + output);
99              }
100         }
101     }
102 
103     private void wrapSystemErr() {
104         originalErr_ = System.err;
105         System.setErr(new NSAPrintStreamWrapper(originalErr_, baos_));
106     }
107 
108     void restoreSystemErr() {
109         System.setErr(originalErr_);
110     }
111 }
112 
113 /**
114  * A {@link PrintStream} spying what is written on the wrapped stream.
115  * It prints the content to the wrapped {@link PrintStream} and captures it simultaneously.
116  */
117 class NSAPrintStreamWrapper extends PrintStream {
118     private PrintStream wrapped_;
119 
120     NSAPrintStreamWrapper(final PrintStream original, final OutputStream spyOut) {
121         super(spyOut, true);
122         wrapped_ = original;
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128     @Override
129     public int hashCode() {
130         return wrapped_.hashCode();
131     }
132 
133     /**
134      * {@inheritDoc}
135      */
136     @Override
137     public boolean equals(final Object obj) {
138         return wrapped_.equals(obj);
139     }
140 
141     /**
142      * {@inheritDoc}
143      */
144     @Override
145     public String toString() {
146         return wrapped_.toString();
147     }
148 
149     /**
150      * {@inheritDoc}
151      */
152     @Override
153     public void flush() {
154         super.flush();
155         wrapped_.flush();
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
162     public void close() {
163         super.close();
164         wrapped_.close();
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     @Override
171     public boolean checkError() {
172         super.checkError();
173         return wrapped_.checkError();
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     @Override
180     public void write(final int b) {
181         super.write(b);
182         wrapped_.write(b);
183     }
184 
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     public void write(final byte[] buf, final int off, final int len) {
190         super.write(buf, off, len);
191         wrapped_.write(buf, off, len);
192     }
193 }