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;
16  
17  import java.io.ByteArrayOutputStream;
18  import java.io.OutputStream;
19  import java.io.PrintStream;
20  import java.util.regex.Pattern;
21  
22  import org.apache.commons.lang3.StringUtils;
23  import org.junit.rules.MethodRule;
24  import org.junit.runners.model.FrameworkMethod;
25  import org.junit.runners.model.Statement;
26  
27  /**
28   * JUnit 4 {@link org.junit.Rule} verifying that nothing is printed to {@link System#err}
29   * during test execution. If this is the case, the rule generates a failure for
30   * the unit test.
31   *
32   * @author Marc Guillemot
33   * @author Ronald Brill
34   * @author Ahmed Ashour
35   * @author Frank Danek
36   */
37  public class ErrorOutputChecker implements MethodRule {
38      private PrintStream originalErr_;
39      private final ByteArrayOutputStream baos_ = new ByteArrayOutputStream();
40      private static final Pattern[] PATTERNS = {
41              // jetty
42              Pattern.compile(".*Logging initialized .* to org.eclipse.jetty.util.log.StdErrLog.*\r?\n"),
43  
44              // slf4j
45              Pattern.compile("SLF4J\\(I\\): .*\r?\n"),
46  
47              // Quercus
48              Pattern.compile(".*com.caucho.quercus.servlet.QuercusServlet initImpl\r?\n"),
49              Pattern.compile(".*QuercusServlet starting as QuercusServletImpl\r?\n"),
50              Pattern.compile(".*Quercus finished initialization in \\d*ms\r?\n"),
51      };
52  
53      /**
54       * {@inheritDoc}
55       */
56      @Override
57      public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
58          if (target instanceof WebDriverTestCase) {
59              final WebDriverTestCase testCase = (WebDriverTestCase) target;
60              if (testCase.useRealBrowser()) {
61                  return base;
62              }
63          }
64  
65          return new Statement() {
66              @Override
67              public void evaluate() throws Throwable {
68                  wrapSystemErr();
69                  try {
70                      base.evaluate();
71                      verifyNoOutput();
72                  }
73                  finally {
74                      restoreSystemErr();
75                  }
76              }
77          };
78      }
79  
80      void verifyNoOutput() {
81          if (baos_.size() != 0) {
82              String output = baos_.toString();
83  
84              // remove webdriver messages
85              for (final Pattern pattern : PATTERNS) {
86                  output = pattern.matcher(output).replaceAll("");
87              }
88  
89              if (!output.isEmpty()) {
90                  if (output.contains("ChromeDriver")) {
91                      throw new RuntimeException("Outdated Chrome driver version: " + output);
92                  }
93                  if (output.contains("geckodriver")) {
94                      throw new RuntimeException("Outdated Gecko driver version: " + output);
95                  }
96                  output = StringUtils.replaceEach(output, new String[] {"\n", "\r"}, new String[]{"\\n", "\\r"});
97                  throw new RuntimeException("Test has produced output to System.err: " + output);
98              }
99          }
100     }
101 
102     private void wrapSystemErr() {
103         originalErr_ = System.err;
104         System.setErr(new NSAPrintStreamWrapper(originalErr_, baos_));
105     }
106 
107     void restoreSystemErr() {
108         System.setErr(originalErr_);
109     }
110 }
111 
112 /**
113  * A {@link PrintStream} spying what is written on the wrapped stream.
114  * It prints the content to the wrapped {@link PrintStream} and captures it simultaneously.
115  * @author Marc Guillemot
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 }