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 java.lang.annotation.Annotation;
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.Comparator;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Set;
25  
26  import org.htmlunit.BrowserVersion;
27  import org.htmlunit.WebTestCase;
28  import org.htmlunit.junit.BrowserParameterizedRunner.Default;
29  import org.junit.runner.Description;
30  import org.junit.runner.notification.RunNotifier;
31  import org.junit.runners.Parameterized.Parameter;
32  import org.junit.runners.model.FrameworkField;
33  import org.junit.runners.model.FrameworkMethod;
34  import org.junit.runners.model.InitializationError;
35  import org.junit.runners.model.Statement;
36  import org.junit.runners.parameterized.TestWithParameters;
37  
38  /**
39   * A {@link BrowserVersionClassRunner} which is also parameterized.
40   *
41   * @author Ahmed Ashour
42   */
43  public class BrowserVersionClassRunnerWithParameters extends BrowserVersionClassRunner {
44  
45      private final List<TestWithParameters> tests_;
46  
47      private List<FrameworkMethod> testMethods_;
48  
49      /**
50       * The constructor.
51       * @param klass the class
52       * @param browserVersion the browser version
53       * @param realBrowser whether to use real browser or not
54       * @param tests the tests
55       * @throws InitializationError if an error occurs
56       */
57      public BrowserVersionClassRunnerWithParameters(final Class<WebTestCase> klass,
58              final BrowserVersion browserVersion, final boolean realBrowser,
59              final List<TestWithParameters> tests)
60          throws InitializationError {
61          super(klass, browserVersion, realBrowser);
62          tests_ = tests;
63      }
64  
65      /**
66       * {@inheritDoc}
67       */
68      @Override
69      protected List<FrameworkMethod> computeTestMethods() {
70          if (testMethods_ != null) {
71              return testMethods_;
72          }
73          final List<FrameworkMethod> originalMethod = super.computeTestMethods();
74          if (tests_ == null) {
75              return originalMethod;
76          }
77          final Set<String> nativeMethodNames = new HashSet<>();
78          final List<FrameworkMethod> methods = new ArrayList<>();
79          FrameworkMethod defualtMethod = null;
80          for (final FrameworkMethod m : originalMethod) {
81              final List<Object> parameters;
82              if (m.getAnnotation(Default.class) != null) {
83                  defualtMethod = m;
84                  parameters = tests_.get(0).getParameters();
85              }
86              else {
87                  parameters = Collections.emptyList();
88                  nativeMethodNames.add(m.getName());
89              }
90              final FrameworkMethodWithParameters newMethod = new FrameworkMethodWithParameters(
91                      getTestClass(), m.getMethod(), parameters);
92              methods.add(newMethod);
93          }
94  
95          if (defualtMethod != null) {
96              for (int i = 0; i < tests_.size() - 1; i++) {
97                  final FrameworkMethodWithParameters method = new FrameworkMethodWithParameters(
98                          getTestClass(), defualtMethod.getMethod(), tests_.get(i + 1).getParameters());
99                  methods.add(method);
100             }
101         }
102 
103         for (final Iterator<FrameworkMethod> it = methods.iterator(); it.hasNext();) {
104             final FrameworkMethod method = it.next();
105             if (method.getAnnotation(Default.class) != null && nativeMethodNames.contains(method.getName())) {
106                 it.remove();
107             }
108         }
109 
110         final Comparator<FrameworkMethod> comparator = new Comparator<FrameworkMethod>() {
111             @Override
112             public int compare(final FrameworkMethod fm1, final FrameworkMethod fm2) {
113                 return fm1.getName().compareTo(fm2.getName());
114             }
115         };
116         Collections.sort(methods, comparator);
117         testMethods_ = methods;
118         return testMethods_;
119     }
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     protected String testName(final FrameworkMethod method) {
126         String prefix = "";
127         if (isNotYetImplemented(method) && !isRealBrowser()) {
128             prefix = "(NYI) ";
129         }
130 
131         String browserString = getBrowserVersion().getNickname();
132         if (isRealBrowser()) {
133             browserString = "Real " + browserString;
134         }
135 
136         final String methodName = method.getName();
137 
138         if (!MAVEN) {
139             return String.format("%s [%s]", methodName, browserString);
140         }
141         String className = method.getMethod().getDeclaringClass().getName();
142         className = className.substring(className.lastIndexOf('.') + 1);
143         return String.format("%s%s [%s]", prefix, className + '.' + methodName, browserString);
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     @Override
150     protected void validateConstructor(final List<Throwable> errors) {
151         validateOnlyOneConstructor(errors);
152         if (fieldsAreAnnotated()) {
153             validateZeroArgConstructor(errors);
154         }
155     }
156 
157     /**
158      * {@inheritDoc}
159      */
160     @Override
161     protected void validateFields(final List<Throwable> errors) {
162         super.validateFields(errors);
163         if (fieldsAreAnnotated()) {
164             final List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
165             final int[] usedIndices = new int[annotatedFieldsByParameter.size()];
166             for (final FrameworkField each : annotatedFieldsByParameter) {
167                 final int index = each.getField().getAnnotation(Parameter.class)
168                         .value();
169                 if (index < 0 || index > annotatedFieldsByParameter.size() - 1) {
170                     errors.add(new Exception("Invalid @Parameter value: "
171                             + index + ". @Parameter fields counted: "
172                             + annotatedFieldsByParameter.size()
173                             + ". Please use an index between 0 and "
174                             + (annotatedFieldsByParameter.size() - 1) + "."));
175                 }
176                 else {
177                     usedIndices[index]++;
178                 }
179             }
180             for (int index = 0; index < usedIndices.length; index++) {
181                 final int numberOfUse = usedIndices[index];
182                 if (numberOfUse == 0) {
183                     errors.add(new Exception("@Parameter(" + index
184                             + ") is never used."));
185                 }
186                 else if (numberOfUse > 1) {
187                     errors.add(new Exception("@Parameter(" + index
188                             + ") is used more than once (" + numberOfUse + ")."));
189                 }
190             }
191         }
192     }
193 
194     /**
195      * {@inheritDoc}
196      */
197     @Override
198     protected Statement classBlock(final RunNotifier notifier) {
199         return childrenInvoker(notifier);
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     protected Annotation[] getRunnerAnnotations() {
207         return new Annotation[0];
208     }
209 
210     private List<FrameworkField> getAnnotatedFieldsByParameter() {
211         return getTestClass().getAnnotatedFields(Parameter.class);
212     }
213 
214     private boolean fieldsAreAnnotated() {
215         return !getAnnotatedFieldsByParameter().isEmpty();
216     }
217 
218     /**
219      * {@inheritDoc}
220      */
221     @Override
222     protected Description describeChild(final FrameworkMethod method) {
223         if (method.getAnnotation(Default.class) != null) {
224             return Description.createTestDescription(getTestClass().getJavaClass(),
225                     testName(method), method.getAnnotations());
226         }
227         return super.describeChild(method);
228     }
229 }