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.archunit;
16  
17  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
18  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
19  
20  import java.util.HashSet;
21  import java.util.Set;
22  import java.util.stream.Collectors;
23  
24  import org.htmlunit.BrowserVersion;
25  import org.htmlunit.junit.annotation.AnnotationUtils;
26  import org.junit.Assert;
27  import org.junit.Test;
28  import org.junit.runner.RunWith;
29  
30  import com.tngtech.archunit.core.domain.JavaClasses;
31  import com.tngtech.archunit.core.domain.JavaMethod;
32  import com.tngtech.archunit.junit.AnalyzeClasses;
33  import com.tngtech.archunit.junit.ArchTest;
34  import com.tngtech.archunit.junit.ArchUnitRunner;
35  import com.tngtech.archunit.lang.ArchCondition;
36  import com.tngtech.archunit.lang.ArchRule;
37  import com.tngtech.archunit.lang.ConditionEvents;
38  import com.tngtech.archunit.lang.SimpleConditionEvent;
39  
40  /**
41   * Architecture tests for our test cases.
42   *
43   * @author Ronald Brill
44   */
45  @RunWith(ArchUnitRunner.class)
46  @AnalyzeClasses(packages = "org.htmlunit")
47  public class Architecture2Test {
48  
49      /**
50       * All property test should test the same objects.
51       * @param classes all classes
52       */
53      @ArchTest
54      public static void allPropertyTestShouldTestTheSameObjects(final JavaClasses classes) {
55          compare(classes, "ElementPropertiesTest", "ElementOwnPropertiesTest");
56          compare(classes, "ElementPropertiesTest", "ElementOwnPropertySymbolsTest");
57      }
58  
59      /**
60       * All element test should test the same objects.
61       * @param classes all classes
62       */
63      @ArchTest
64      public static void allElementTestShouldTestTheSameObjects(final JavaClasses classes) {
65          compare(classes, "ElementChildNodesTest", "ElementClosesItselfTest");
66          compare(classes, "ElementChildNodesTest", "ElementCreationTest");
67          compare(classes, "ElementChildNodesTest", "ElementDefaultStyleDisplayTest");
68          compare(classes, "ElementChildNodesTest", "ElementOuterHtmlTest");
69      }
70  
71      /**
72       * All host test should test the same objects.
73       * @param classes all classes
74       */
75      @ArchTest
76      public static void allHostTestShouldTestTheSameObjects(final JavaClasses classes) {
77          compare(classes, "HostClassNameTest", "HostTypeOfTest");
78          compare(classes, "HostClassNameTest", "DedicatedWorkerGlobalScopeClassNameTest");
79          compare(classes, "HostClassNameTest", "DedicatedWorkerGlobalScopeTypeOfTest");
80      }
81  
82      private static void compare(final JavaClasses classes, final String oneName, final String anotherName) {
83          final Set<String> oneTests =
84                  classes.get("org.htmlunit.general." + oneName).getAllMethods().stream()
85                      .filter(m -> m.tryGetAnnotationOfType("org.junit.Test").isPresent())
86                      .map(m -> m.getName())
87                      .collect(Collectors.toSet());
88  
89          final Set<String> anotherTests =
90                  classes.get("org.htmlunit.general." + anotherName).getAllMethods().stream()
91                      .filter(m -> m.tryGetAnnotationOfType("org.junit.Test").isPresent())
92                      .map(m -> m.getName())
93                      .collect(Collectors.toSet());
94  
95          final Set<String> tmp = new HashSet<>(anotherTests);
96          tmp.removeAll(oneTests);
97          oneTests.removeAll(anotherTests);
98  
99          if (tmp.size() + oneTests.size() > 0) {
100             if (tmp.size() == 0) {
101                 Assert.fail("The " + oneTests.size() + " method(s) "
102                     + oneTests.stream().sorted().collect(Collectors.toList())
103                     + " are available in " + oneName + " but missing in " + anotherName + ".");
104             }
105             else if (oneTests.size() == 0) {
106                 anotherTests.removeAll(oneTests);
107                 Assert.fail("The " + tmp.size() + " method(s) "
108                     + tmp.stream().sorted().collect(Collectors.toList())
109                     + " are available in " + anotherName + " but missing in " + oneName + ".");
110             }
111 
112             Assert.fail("The " + tmp.size() + " method(s) "
113                     + tmp.stream().sorted().collect(Collectors.toList())
114                     + " are available in " + anotherName + " but missing in " + oneName
115                     + " and the " + oneTests.size() + " method(s) "
116                     + oneTests.stream().sorted().collect(Collectors.toList())
117                     + " are available in " + oneName + " but missing in " + anotherName + ".");
118         }
119     }
120 
121     /**
122      * Do not use BrowserVersion.isChrome().
123      */
124     @ArchTest
125     public static final ArchRule isChrome = noClasses()
126         .that()
127             .doNotHaveFullyQualifiedName("org.htmlunit.BrowserVersion")
128             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration")
129             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$Chrome")
130             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdge")
131             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdgeAndFirefox")
132             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdgeNotIterable")
133             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.dom.Document")
134             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.DateTimeFormat")
135             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.NumberFormat")
136 
137         .should().callMethod(BrowserVersion.class, "isChrome", new Class[] {});
138 
139     /**
140      * Do not use BrowserVersion.isEdge().
141      */
142     @ArchTest
143     public static final ArchRule isEdge = noClasses()
144         .that()
145             .doNotHaveFullyQualifiedName("org.htmlunit.BrowserVersion")
146             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration")
147             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$Edge")
148             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$Chrome")
149             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdge")
150             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdgeAndFirefox")
151             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdgeNotIterable")
152             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.dom.Document")
153             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.DateTimeFormat")
154             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.NumberFormat")
155 
156         .should().callMethod(BrowserVersion.class, "isEdge", new Class[] {});
157 
158     /**
159      * Do not use BrowserVersion.isFirefox().
160      */
161     @ArchTest
162     public static final ArchRule isFirefox = noClasses()
163         .that()
164             .doNotHaveFullyQualifiedName("org.htmlunit.BrowserVersion")
165             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration")
166             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.worker.DedicatedWorkerGlobalScope")
167 
168             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$FF")
169             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$FFNotIterable")
170             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$FFESR")
171             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$FFLatest")
172             .and().doNotHaveFullyQualifiedName("org.htmlunit.css.BrowserConfiguration$ChromeAndEdgeAndFirefox")
173 
174             .and().doNotHaveFullyQualifiedName("org.htmlunit.general.huge.ElementClosesElementTest")
175         .should().callMethod(BrowserVersion.class, "isFirefox", new Class[] {});
176 
177     /**
178      * Do not use BrowserVersion.isFirefoxESR().
179      */
180     @ArchTest
181     public static final ArchRule isFirefoxESR = noClasses()
182         .that()
183             .doNotHaveFullyQualifiedName("org.htmlunit.BrowserVersion")
184             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration")
185             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.worker.DedicatedWorkerGlobalScope")
186 
187             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.DateTimeFormat")
188             .and().doNotHaveFullyQualifiedName("org.htmlunit.javascript.host.intl.NumberFormat")
189         .should().callMethod(BrowserVersion.class, "isFirefoxESR", new Class[] {});
190 
191 
192     /**
193      * Do not use hamcrest.
194      */
195     @ArchTest
196     public static final ArchRule hamcrest = noClasses()
197         .should().dependOnClassesThat().resideInAPackage("org.hamcrest..");
198 
199     private static final ArchCondition<JavaMethod> haveConsistentTestAnnotations =
200             new ArchCondition<JavaMethod>("have consistent HtmlUnit test annotations") {
201                 @Override
202                 public void check(final JavaMethod method, final ConditionEvents events) {
203                     try {
204                         AnnotationUtils.assertAlerts(method.reflect());
205                     }
206                     catch (final AssertionError e) {
207                         events.add(SimpleConditionEvent.violated(method, e.getMessage()));
208                     }
209                 }
210             };
211 
212     /**
213      * Validate test annotations.
214      */
215     @ArchTest
216     public static final ArchRule consistentTestAnnotations = methods()
217             .that().areAnnotatedWith(Test.class)
218             .and().areNotDeclaredIn("org.htmlunit.junit.annotation.AnnotationUtilsTest")
219             .should(haveConsistentTestAnnotations);
220 }