Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

ParallelRunner.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /**
  2. * Copyright (C) 2012 Vaadin Ltd
  3. *
  4. * This program is available under Commercial Vaadin Add-On License 3.0
  5. * (CVALv3).
  6. *
  7. * See the file licensing.txt distributed with this software for more
  8. * information about licensing.
  9. *
  10. * You should have received a copy of the license along with this program.
  11. * If not, see <http://vaadin.com/license/cval-3>.
  12. */
  13. package com.vaadin.testbench.parallel;
  14. import java.lang.annotation.Annotation;
  15. import java.lang.reflect.InvocationTargetException;
  16. import java.lang.reflect.Method;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.LinkedList;
  20. import java.util.List;
  21. import java.util.concurrent.ExecutorService;
  22. import java.util.concurrent.Executors;
  23. import java.util.logging.Level;
  24. import java.util.logging.Logger;
  25. import org.junit.Ignore;
  26. import org.junit.Test;
  27. import org.junit.runners.BlockJUnit4ClassRunner;
  28. import org.junit.runners.Parameterized;
  29. import org.junit.runners.model.FrameworkMethod;
  30. import org.junit.runners.model.InitializationError;
  31. import org.junit.runners.model.Statement;
  32. import org.openqa.selenium.Capabilities;
  33. import org.openqa.selenium.remote.DesiredCapabilities;
  34. import com.vaadin.testbench.Parameters;
  35. import com.vaadin.testbench.annotations.BrowserConfiguration;
  36. import com.vaadin.testbench.annotations.BrowserFactory;
  37. import com.vaadin.testbench.annotations.RunLocally;
  38. /**
  39. * NOTE: The compatibility of this overridden class must be checked if TestBench
  40. * version is ever upgraded from 5.2.0.
  41. * <p>
  42. * Initial commit contains changes to {@link TBMethod#invokeExplosively}
  43. * <p>
  44. * <br>
  45. * This runner is loosely based on FactoryTestRunner by Ted Young
  46. * (http://tedyoung.me/2011/01/23/junit-runtime-tests-custom-runners/). The
  47. * generated test names give information about the parameters used (unlike
  48. * {@link Parameterized}).
  49. */
  50. public class ParallelRunner extends BlockJUnit4ClassRunner {
  51. private static Logger logger = Logger
  52. .getLogger(ParallelRunner.class.getName());
  53. /**
  54. * This is the total limit of actual JUnit test instances run in parallel
  55. */
  56. private static final int MAX_CONCURRENT_TESTS;
  57. /**
  58. * This is static so it is shared by all tests running concurrently on the
  59. * same machine and thus can limit the number of threads in use.
  60. */
  61. private static final ExecutorService service;
  62. static {
  63. MAX_CONCURRENT_TESTS = Parameters.getMaxThreads();
  64. service = Executors.newFixedThreadPool(MAX_CONCURRENT_TESTS);
  65. }
  66. public ParallelRunner(Class<?> klass) throws InitializationError {
  67. super(klass);
  68. setScheduler(new ParallelScheduler(service));
  69. }
  70. @Override
  71. protected List<FrameworkMethod> computeTestMethods() {
  72. List<FrameworkMethod> tests = new LinkedList<>();
  73. if (!ParallelTest.class
  74. .isAssignableFrom(getTestClass().getJavaClass())) {
  75. throw new RuntimeException(getClass().getName() + " only supports "
  76. + ParallelTest.class.getName());
  77. }
  78. BrowserUtil.setBrowserFactory(getBrowserFactory());
  79. try {
  80. Collection<DesiredCapabilities> desiredCapabilities = getDesiredCapabilities();
  81. TestNameSuffix testNameSuffixProperty = findAnnotation(
  82. getTestClass().getJavaClass(), TestNameSuffix.class);
  83. for (FrameworkMethod m : getTestMethods()) {
  84. // No browsers available for this test, so we need to
  85. // wrap the test method inside IgnoredTestMethod.
  86. // This will add @Ignore annotation to it.
  87. if (desiredCapabilities.size() <= 0
  88. || categoryIsExcludedOrNotExcplicitlyIncluded()) {
  89. tests.add(new IgnoredTestMethod(m.getMethod()));
  90. } else {
  91. for (DesiredCapabilities capabilities : desiredCapabilities) {
  92. TBMethod method = new TBMethod(m.getMethod(),
  93. capabilities);
  94. if (testNameSuffixProperty != null) {
  95. method.setTestNameSuffix("-" + System.getProperty(
  96. testNameSuffixProperty.property()));
  97. }
  98. tests.add(method);
  99. }
  100. }
  101. }
  102. } catch (Exception e) {
  103. throw new RuntimeException("Error retrieving browsers to run on",
  104. e);
  105. }
  106. return tests;
  107. }
  108. private boolean categoryIsExcludedOrNotExcplicitlyIncluded() {
  109. Class<?> c = getTestClass().getJavaClass();
  110. if (categoryIsExcluded(c)) {
  111. return true;
  112. }
  113. if (explicitInclusionIsUsed()) {
  114. return !categoryIsIncluded(c);
  115. }
  116. return false;
  117. }
  118. private boolean categoryIsIncluded(Class<?> c) {
  119. String include = System.getProperty("categories.include");
  120. if (include != null && include.trim().length() > 0) {
  121. return hasCategoryFor(c, include.toLowerCase().trim());
  122. }
  123. return false;
  124. }
  125. private static boolean explicitInclusionIsUsed() {
  126. String include = System.getProperty("categories.include");
  127. return include != null && include.trim().length() > 0;
  128. }
  129. private static boolean categoryIsExcluded(Class<?> c) {
  130. String exclude = System.getProperty("categories.exclude");
  131. if (exclude != null && exclude.trim().length() > 0) {
  132. return hasCategoryFor(c, exclude.toLowerCase().trim());
  133. }
  134. return false;
  135. }
  136. private static boolean hasCategoryFor(Class<?> c, String searchString) {
  137. if (hasCategory(c)) {
  138. return searchString.contains(getCategory(c).toLowerCase());
  139. }
  140. return false;
  141. }
  142. private static boolean hasCategory(Class<?> c) {
  143. return c.getAnnotation(TestCategory.class) != null;
  144. }
  145. private static String getCategory(Class<?> c) {
  146. return c.getAnnotation(TestCategory.class).value();
  147. }
  148. private List<FrameworkMethod> getTestMethods() {
  149. return getTestClass().getAnnotatedMethods(Test.class);
  150. }
  151. /*
  152. * Returns a list of desired browser capabilities according to browsers
  153. * defined in the test class, filtered by possible filter parameters. Use
  154. * {@code @RunLocally} annotation or com.vaadin.testbench.runLocally
  155. * property to override all capabilities.
  156. */
  157. private Collection<DesiredCapabilities> getDesiredCapabilities() {
  158. if (testRunsLocally()) {
  159. Collection<DesiredCapabilities> desiredCapabilities = new ArrayList<>();
  160. Class<?> javaTestClass = getTestClass().getJavaClass();
  161. desiredCapabilities.add(BrowserUtil.getBrowserFactory().create(
  162. getRunLocallyBrowserName(javaTestClass),
  163. getRunLocallyBrowserVersion(javaTestClass)));
  164. return desiredCapabilities;
  165. } else {
  166. return getFilteredCapabilities();
  167. }
  168. }
  169. private boolean testRunsLocally() {
  170. if (Parameters.getRunLocallyBrowserName() != null) {
  171. return true;
  172. }
  173. RunLocally runLocally = getTestClass().getJavaClass()
  174. .getAnnotation(RunLocally.class);
  175. if (runLocally == null) {
  176. return false;
  177. }
  178. return true;
  179. }
  180. static Browser getRunLocallyBrowserName(Class<?> testClass) {
  181. String runLocallyBrowserName = Parameters.getRunLocallyBrowserName();
  182. if (runLocallyBrowserName != null) {
  183. return Browser.valueOf(runLocallyBrowserName.toUpperCase());
  184. }
  185. RunLocally runLocally = testClass.getAnnotation(RunLocally.class);
  186. if (runLocally == null) {
  187. return null;
  188. }
  189. return runLocally.value();
  190. }
  191. static String getRunLocallyBrowserVersion(Class<?> testClass) {
  192. String runLocallyBrowserVersion = Parameters
  193. .getRunLocallyBrowserVersion();
  194. if (runLocallyBrowserVersion != null) {
  195. return runLocallyBrowserVersion;
  196. }
  197. RunLocally runLocally = testClass.getAnnotation(RunLocally.class);
  198. if (runLocally == null) {
  199. return "";
  200. }
  201. return runLocally.version();
  202. }
  203. private TestBenchBrowserFactory getBrowserFactory() {
  204. BrowserFactory browserFactoryAnnotation = getTestClass().getJavaClass()
  205. .getAnnotation(BrowserFactory.class);
  206. try {
  207. if (browserFactoryAnnotation != null
  208. && TestBenchBrowserFactory.class.isAssignableFrom(
  209. browserFactoryAnnotation.value())) {
  210. return (TestBenchBrowserFactory) browserFactoryAnnotation
  211. .value().newInstance();
  212. }
  213. } catch (Exception e) {
  214. }
  215. return new DefaultBrowserFactory();
  216. }
  217. /*
  218. * Takes the desired browser capabilities defined in the test class and
  219. * returns a list of browser capabilities filtered browsers.include and
  220. * browsers.exclude system properties. (if present)
  221. */
  222. private Collection<DesiredCapabilities> getFilteredCapabilities() {
  223. Collection<DesiredCapabilities> desiredCapabilites = getBrowsersConfiguration();
  224. ArrayList<DesiredCapabilities> filteredCapabilities = new ArrayList<>();
  225. String include = System.getProperty("browsers.include");
  226. String exclude = System.getProperty("browsers.exclude");
  227. for (DesiredCapabilities d : desiredCapabilites) {
  228. String browserName = (d.getBrowserName() + d.getVersion())
  229. .toLowerCase();
  230. if (include != null && include.trim().length() > 0) {
  231. if (include.trim().toLowerCase().contains(browserName)) {
  232. filteredCapabilities.add(d);
  233. }
  234. } else {
  235. filteredCapabilities.add(d);
  236. }
  237. if (exclude != null && exclude.trim().length() > 0) {
  238. if (exclude.trim().toLowerCase().contains(browserName)) {
  239. filteredCapabilities.remove(d);
  240. }
  241. }
  242. }
  243. return filteredCapabilities;
  244. }
  245. private Collection<DesiredCapabilities> getBrowsersConfiguration() {
  246. Class<?> klass = getTestClass().getJavaClass();
  247. while (klass != null) {
  248. Method[] declaredMethods = klass.getDeclaredMethods();
  249. for (Method method : declaredMethods) {
  250. // TODO if already found one annotated method in class, warn
  251. // user?
  252. if (method.isAnnotationPresent(BrowserConfiguration.class)) {
  253. boolean methodSignatureIsValid = validateBrowserConfigurationAnnotatedSignature(
  254. method);
  255. if (!methodSignatureIsValid) {
  256. /*
  257. * ignore this method and searches for another
  258. * BrowserConfiguration annotated method in this class'
  259. * superclasses
  260. */
  261. break;
  262. }
  263. try {
  264. return (Collection<DesiredCapabilities>) method
  265. .invoke(getTestClassInstance());
  266. } catch (Exception e) {
  267. // Handle possible exceptions.
  268. String errMsg = String.format(
  269. "Error occurred while invoking BrowserConfiguration method %s.%s(). Method was ignored, searching BrowserConfiguration method in superclasses",
  270. method.getDeclaringClass().getName(),
  271. method.getName());
  272. logger.log(Level.INFO, errMsg, e);
  273. /*
  274. * ignore this method and searches for another
  275. * BrowserConfiguration annotated method in this class'
  276. * superclasses
  277. */
  278. break;
  279. }
  280. }
  281. }
  282. klass = klass.getSuperclass();
  283. }
  284. // No valid BrowserConfiguration annotated method was found
  285. return ParallelTest.getDefaultCapabilities();
  286. }
  287. /**
  288. * Validates the signature of a BrowserConfiguration annotated method.
  289. *
  290. * @param method
  291. * BrowserConfiguration annotated method
  292. * @return true if method signature is valid. false otherwise.
  293. */
  294. private boolean validateBrowserConfigurationAnnotatedSignature(
  295. Method method) {
  296. String genericErrorMessage = "Error occurred while invoking BrowserConfigurationMethod %s.%s()."
  297. + " %s. Method was ignored, searching BrowserConfiguration method in superclasses";
  298. if (method.getParameterTypes().length != 0) {
  299. String errMsg = String.format(genericErrorMessage,
  300. method.getDeclaringClass().getName(), method.getName(),
  301. "BrowserConfiguration annotated method must not require any arguments");
  302. logger.info(errMsg);
  303. return false;
  304. }
  305. if (!Collection.class.isAssignableFrom(method.getReturnType())) {
  306. /*
  307. * Validates if method's return type is Collection.
  308. * ClassCastException may still occur if method's return type is not
  309. * Collection<DesiredCapabilities>
  310. */
  311. String errMsg = String.format(genericErrorMessage,
  312. method.getDeclaringClass().getName(), method.getName(),
  313. "BrowserConfiguration annotated method must return a Collection<DesiredCapabilities>");
  314. logger.info(errMsg);
  315. return false;
  316. }
  317. return true;
  318. }
  319. private ParallelTest getTestClassInstance() throws InstantiationException,
  320. IllegalAccessException, InvocationTargetException {
  321. ParallelTest testClassInstance = (ParallelTest) getTestClass()
  322. .getOnlyConstructor().newInstance();
  323. return testClassInstance;
  324. }
  325. // This is a FrameworkMethod class that will always
  326. // return @Ignore and @Test annotations for the wrapped method.
  327. private class IgnoredTestMethod extends FrameworkMethod {
  328. private class IgnoreTestAnnotations {
  329. // We use this method to easily get our hands on
  330. // the Annotation instances for @Ignore and @Test
  331. @Ignore
  332. @Test
  333. public void ignoredTest() {
  334. }
  335. }
  336. public IgnoredTestMethod(Method method) {
  337. super(method);
  338. }
  339. @Override
  340. public Annotation[] getAnnotations() {
  341. return getIgnoredTestMethod().getAnnotations();
  342. }
  343. private Method getIgnoredTestMethod() {
  344. try {
  345. return IgnoreTestAnnotations.class.getMethod("ignoredTest",
  346. null);
  347. } catch (Exception e) {
  348. return null;
  349. }
  350. }
  351. @Override
  352. public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
  353. return getIgnoredTestMethod().getAnnotation(annotationType);
  354. }
  355. }
  356. /**
  357. * Finds the given annotation in the given class or one of its super
  358. * classes. Return the first found annotation
  359. *
  360. * @param searchClass
  361. * @param annotationClass
  362. * @return
  363. */
  364. private <T extends Annotation> T findAnnotation(Class<?> searchClass,
  365. Class<T> annotationClass) {
  366. if (searchClass == Object.class) {
  367. return null;
  368. }
  369. if (searchClass.getAnnotation(annotationClass) != null) {
  370. return searchClass.getAnnotation(annotationClass);
  371. }
  372. return findAnnotation(searchClass.getSuperclass(), annotationClass);
  373. }
  374. @Override
  375. protected Statement withBefores(final FrameworkMethod method,
  376. final Object target, Statement statement) {
  377. if (!(method instanceof TBMethod)) {
  378. throw new RuntimeException("Unexpected method type "
  379. + method.getClass().getName() + ", expected TBMethod");
  380. }
  381. final TBMethod tbmethod = (TBMethod) method;
  382. // setDesiredCapabilities before running the real @Befores (which use
  383. // capabilities)
  384. final Statement realBefores = super.withBefores(method, target,
  385. statement);
  386. return new Statement() {
  387. @Override
  388. public void evaluate() throws Throwable {
  389. ((ParallelTest) target)
  390. .setDesiredCapabilities(tbmethod.capabilities);
  391. realBefores.evaluate();
  392. }
  393. };
  394. }
  395. public static class TBMethod extends FrameworkMethod {
  396. private final DesiredCapabilities capabilities;
  397. private String testNameSuffix = "";
  398. public TBMethod(Method method, DesiredCapabilities capabilities) {
  399. super(method);
  400. this.capabilities = capabilities;
  401. }
  402. public DesiredCapabilities getCapabilities() {
  403. return capabilities;
  404. }
  405. public void setTestNameSuffix(String testNameSuffix) {
  406. this.testNameSuffix = testNameSuffix;
  407. }
  408. @Override
  409. public Object invokeExplosively(final Object target, Object... params)
  410. throws Throwable {
  411. // Executes the test method with the supplied parameters
  412. try {
  413. return super.invokeExplosively(target);
  414. } catch (Exception e) {
  415. e.printStackTrace();
  416. throw e;
  417. }
  418. }
  419. @Override
  420. public String getName() {
  421. return String.format("%s[%s]",
  422. getMethod().getName() + testNameSuffix,
  423. getUniqueIdentifier(capabilities));
  424. }
  425. @Override
  426. public boolean equals(Object obj) {
  427. if (!TBMethod.class.isInstance(obj)) {
  428. return false;
  429. }
  430. return ((TBMethod) obj).capabilities.equals(capabilities)
  431. && super.equals(obj);
  432. }
  433. /**
  434. * Returns a string which uniquely (enough) identifies this browser.
  435. * Used mainly in screenshot names.
  436. */
  437. private static String getUniqueIdentifier(Capabilities capabilities) {
  438. String platform = BrowserUtil.getPlatform(capabilities);
  439. String browser = BrowserUtil.getBrowserIdentifier(capabilities);
  440. String version;
  441. if (capabilities == null) {
  442. version = "Unknown";
  443. } else {
  444. version = capabilities.getVersion();
  445. }
  446. return platform + "_" + browser + "_" + version;
  447. }
  448. }
  449. }