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 static java.nio.charset.StandardCharsets.UTF_8;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Set;
29  
30  import org.apache.commons.io.FileUtils;
31  import org.apache.commons.text.StringEscapeUtils;
32  import org.htmlunit.BrowserVersion;
33  import org.htmlunit.CodeStyleTest;
34  import org.htmlunit.general.HostExtractor;
35  import org.junit.ComparisonFailure;
36  import org.junit.runners.model.FrameworkMethod;
37  
38  /**
39   * This is meant to automatically correct the test case to put either the real browser expectations,
40   * or the {@link org.htmlunit.junit.annotation.NotYetImplemented} annotation for HtmlUnit.
41   *
42   * @author Ahmed Ashour
43   * @author Ronald Brill
44   */
45  final class TestCaseCorrector {
46  
47      private TestCaseCorrector() {
48      }
49  
50      static void correct(final FrameworkMethod method, final boolean realBrowser, final BrowserVersion browserVersion,
51              final boolean notYetImplemented, final Throwable t) throws IOException {
52          final String testRoot = "src/test/java/";
53          String browserString = browserVersion.getNickname().toUpperCase(Locale.ROOT);
54          browserString = browserString.replace('-', '_'); // FF-ESR-> FF_ESR
55  
56          final File file = new File(testRoot + method.getDeclaringClass().getName().replace('.', '/') + ".java");
57          final List<String> lines = FileUtils.readLines(file, UTF_8);
58          final String methodLine = "    public void " + method.getName() + "()";
59          if (realBrowser) {
60              String defaultExpectation = null;
61              for (int i = 0; i < lines.size(); i++) {
62                  if ("    @Default".equals(lines.get(i))) {
63                      defaultExpectation = getDefaultExpectation(lines, i);
64                  }
65                  if (lines.get(i).startsWith(methodLine)) {
66                      i = addExpectation(lines, i, browserString, (ComparisonFailure) t);
67                      break;
68                  }
69                  if (i == lines.size() - 2) {
70                      addMethodWithExpectation(lines, i, browserString, method.getName(), (ComparisonFailure) t,
71                              defaultExpectation);
72                      break;
73                  }
74              }
75          }
76          else if (!notYetImplemented) {
77              String defaultExpectation = null;
78              for (int i = 0; i < lines.size(); i++) {
79                  if ("    @Default".equals(lines.get(i))) {
80                      defaultExpectation = getDefaultExpectation(lines, i);
81                  }
82                  if (lines.get(i).startsWith(methodLine)) {
83                      addNotYetImplemented(lines, i, browserString);
84                      break;
85                  }
86                  if (i == lines.size() - 2) {
87                      addNotYetImplementedMethod(lines, i, browserString, method.getName(), defaultExpectation);
88                      break;
89                  }
90              }
91          }
92          else {
93              for (int i = 0; i < lines.size(); i++) {
94                  if (lines.get(i).startsWith(methodLine)) {
95                      removeNotYetImplemented(lines, i, browserString);
96                      break;
97                  }
98              }
99          }
100         FileUtils.writeLines(file, UTF_8.name(), lines);
101     }
102 
103     private static String getDefaultExpectation(final List<String> lines, final int defaultIndex) {
104         int index = defaultIndex;
105         while (index >= 0 && !lines.get(index).contains("Alerts")) {
106             index--;
107         }
108         if (index >= 0) {
109             final String line = lines.get(index);
110             return line.substring(line.indexOf('"') + 1, line.lastIndexOf('"'));
111         }
112         return null;
113     }
114 
115     private static int addExpectation(final List<String> lines, int i,
116             final String browserString, final ComparisonFailure comparisonFailure) {
117         while (!lines.get(i).startsWith("    @Alerts")) {
118             i--;
119         }
120         final List<String> alerts = CodeStyleTest.alertsToList(lines, i);
121         for (final Iterator<String> it = alerts.iterator(); it.hasNext();) {
122             if (it.next().startsWith(browserString + " = ")) {
123                 it.remove();
124             }
125         }
126         alerts.add(browserString + " = " + getActualString(comparisonFailure));
127         lines.remove(i);
128         while (lines.get(i).startsWith("        ")) {
129             lines.remove(i);
130         }
131 
132         Collections.sort(alerts);
133         String defaultAlert = null;
134         for (final String alert : alerts) {
135             if (alert.startsWith("DEFAULT = ")) {
136                 defaultAlert = alert;
137                 break;
138             }
139         }
140 
141         if (defaultAlert != null) {
142             alerts.remove(defaultAlert);
143             alerts.add(0, defaultAlert);
144         }
145 
146         for (int x = 0; x < alerts.size(); x++) {
147             String line = alerts.get(x);
148             if (x == 0) {
149                 if (!line.contains(" = ")) {
150                     line = "DEFAULT = " + line;
151                 }
152                 line = "    @Alerts(" + line;
153             }
154             else {
155                 line = "            " + line;
156             }
157             if (x < alerts.size() - 1) {
158                 line += ",";
159             }
160             else {
161                 line += ")";
162             }
163             lines.add(i++, line);
164         }
165         return i;
166     }
167 
168     private static String getActualString(final ComparisonFailure failure) {
169         final int lineLength = 96;
170 
171         String actual = failure.getActual();
172         actual = actual.substring(0, actual.length() - 1);
173         actual = StringEscapeUtils.escapeJava(actual);
174         if (actual.length() > lineLength) {
175             final StringBuilder builder = new StringBuilder();
176             while (!actual.isEmpty()) {
177                 int length = actual.lastIndexOf(',', lineLength) + 1;
178                 if (length == 0 && !actual.isEmpty()) {
179                     length = Math.min(lineLength, actual.length());
180                 }
181                 if (builder.length() != 0) {
182                     builder.append(System.lineSeparator()).append("                + ");
183                 }
184                 builder.append('"').append(actual.substring(0, length)).append('"');
185                 actual = actual.substring(length);
186             }
187             return builder.toString();
188         }
189         return "\"" + actual + "\"";
190     }
191 
192     private static void removeNotYetImplemented(final List<String> lines,
193             final int i, final String browserString) {
194         final String previous = lines.get(i - 1);
195         if (previous.contains("@NotYetImplemented")) {
196             if (previous.indexOf('(') != -1) {
197                 final int p0 = previous.indexOf('(') + 1;
198                 final int p1 = previous.lastIndexOf(')');
199                 String browsers = previous.substring(p0, p1);
200                 if (browsers.indexOf('{') != -1) {
201                     browsers = browsers.substring(1, browsers.length() - 1).trim();
202                 }
203                 final Set<String> browserSet = new HashSet<>();
204                 for (final String browser : browsers.split(",")) {
205                     browserSet.add(browser.trim());
206                 }
207                 browserSet.remove(browserString);
208                 if (browserSet.size() == 1) {
209                     lines.set(i - 1, "    @NotYetImplemented(" + browserSet.iterator().next() + ")");
210                 }
211                 else if (browserSet.size() > 1) {
212                     lines.set(i - 1, "    @NotYetImplemented({" + String.join(", ", browserSet) + "})");
213                 }
214                 else {
215                     lines.remove(i - 1);
216                 }
217             }
218             else {
219                 final List<String> allBrowsers = new ArrayList<>(Arrays.asList("CHROME", "EDGE", "FF", "FF_ESR"));
220                 for (final Iterator<String> it = allBrowsers.iterator(); it.hasNext();) {
221                     if (it.next().equals(browserString)) {
222                         it.remove();
223                     }
224                 }
225                 lines.set(i - 1, "    @NotYetImplemented({" + String.join(", ", allBrowsers) + "})");
226             }
227         }
228     }
229 
230     private static void addNotYetImplementedMethod(final List<String> lines,
231             int i, final String browserString, final String methodName, final String defaultExpectations) {
232         String parent = methodName;
233         final String child = parent.substring(parent.lastIndexOf('_') + 1);
234         parent = parent.substring(1, parent.indexOf('_', 1));
235 
236         if (!lines.get(i).isEmpty()) {
237             i++;
238         }
239         lines.add(i++, "");
240         lines.add(i++, "    /**");
241         lines.add(i++, "     * @throws Exception if the test fails");
242         lines.add(i++, "     */");
243         lines.add(i++, "    @Test");
244         lines.add(i++, "    @Alerts(\"" + defaultExpectations + "\")");
245         lines.add(i++, "    @NotYetImplemented(" + browserString + ")");
246         lines.add(i++, "    public void _" + parent + "_" + child + "() throws Exception {");
247         lines.add(i++, "        test(\"" + parent + "\", \"" + child + "\");");
248         lines.add(i++, "    }");
249         lines.add(i++, "}");
250         while (lines.size() > i) {
251             lines.remove(i);
252         }
253     }
254 
255     private static void addNotYetImplemented(final List<String> lines, final int i, final String browserString) {
256         final String previous = lines.get(i - 1);
257         if (previous.contains("@NotYetImplemented")) {
258             if (previous.indexOf('(') != -1 && !previous.contains(browserString)) {
259                 final int p0 = previous.indexOf('(') + 1;
260                 final int p1 = previous.lastIndexOf(')');
261                 String browsers = previous.substring(p0, p1);
262                 if (browsers.indexOf('{') != -1) {
263                     browsers = browsers.substring(1, browsers.length() - 1).trim();
264                 }
265                 browsers += ", " + browserString;
266                 lines.set(i - 1, "    @NotYetImplemented({" + browsers + "})");
267             }
268         }
269         else {
270             lines.add(i, "    @NotYetImplemented(" + browserString + ")");
271         }
272     }
273 
274     private static void addMethodWithExpectation(final List<String> lines,
275             int i, final String browserString, final String methodName, final ComparisonFailure comparisonFailure,
276             final String defaultExpectations) {
277         String parent = methodName;
278         final String child = parent.substring(parent.lastIndexOf('_') + 1);
279         final int index = parent.indexOf('_', 1);
280         if (index != -1) {
281             parent = parent.substring(1, index);
282         }
283         else {
284             parent = parent.substring(1);
285         }
286 
287         if (!lines.get(i).isEmpty()) {
288             i++;
289         }
290         lines.add(i++, "");
291         lines.add(i++, "    /**");
292         lines.add(i++, "     * @throws Exception if the test fails");
293         lines.add(i++, "     */");
294         lines.add(i++, "    @Test");
295         lines.add(i++, "    @Alerts(DEFAULT = \"" + defaultExpectations + "\",");
296         lines.add(i++, "            " + browserString + " = " + getActualString(comparisonFailure) + ")");
297         if (index != -1) {
298             lines.add(i++, "    public void _" + parent + "_" + child + "() throws Exception {");
299             lines.add(i++, "        test(\"" + parent + "\", \"" + child + "\");");
300         }
301         else {
302             String method = parent;
303             for (final String prefix : HostExtractor.PREFIXES_) {
304                 if (method.startsWith(prefix)) {
305                     method = prefix.toLowerCase(Locale.ROOT) + method.substring(prefix.length());
306                     break;
307                 }
308             }
309             if (Character.isUpperCase(method.charAt(0))) {
310                 method = Character.toLowerCase(method.charAt(0)) + method.substring(1);
311             }
312             lines.add(i++, "    public void " + method + "() throws Exception {");
313             lines.add(i++, "        test(\"" + parent + "\");");
314         }
315         lines.add(i++, "    }");
316         lines.add(i++, "}");
317         while (lines.size() > i) {
318             lines.remove(i);
319         }
320     }
321 }