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