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