/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.arquillian.common; import org.apache.openejb.config.AdditionalBeanDiscoverer; import org.apache.openejb.config.AnnotationDeployer; import org.apache.openejb.config.AppModule; import org.apache.openejb.config.ConnectorModule; import org.apache.openejb.config.EjbModule; import org.apache.openejb.config.WebModule; import org.apache.openejb.jee.EjbJar; import org.apache.openejb.jee.ManagedBean; import org.apache.openejb.jee.TransactionType; import org.apache.openejb.jee.oejb3.EjbDeployment; import org.apache.openejb.jee.oejb3.OpenejbJar; import org.apache.xbean.finder.IAnnotationFinder; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static java.util.Arrays.asList; public class TestClassDiscoverer implements AdditionalBeanDiscoverer { @Override public AppModule discover(final AppModule module) { final Set<Class<?>> testClasses = new HashSet<>(); final Map<Class<?>, WebModule> webTestClasses = new HashMap<>(); final Set<ClassLoader> saw = new HashSet<>(); if (module.getClassLoader() != null) { addTests(findMarkers(module.getClassLoader()), findClassMarkers(module.getClassLoader()), module.getEarLibFinder(), testClasses); saw.add(module.getClassLoader()); } for (final WebModule web : module.getWebModules()) { if (web.getClassLoader() != null && !saw.contains(web.getClassLoader())) { final Set<Class<?>> classes = new HashSet<>(); addTests(findMarkers(web.getClassLoader()), findClassMarkers(web.getClassLoader()), web.getFinder(), classes); saw.add(web.getClassLoader()); for (final Class<?> c : classes) { webTestClasses.put(c, web); } // in case of an ear if we find the same test class in a webapp we don't want it in lib part // this case can happen in tomee-embedded mainly final Iterator<Class<?>> c = testClasses.iterator(); while (c.hasNext()) { final String cl = c.next().getName(); for (final Class<?> wc : classes) { if (cl.equals(wc.getName())) { c.remove(); break; } } } testClasses.addAll(classes); } } for (final EjbModule ejb : module.getEjbModules()) { if (ejb.getClassLoader() != null && !saw.contains(ejb.getClassLoader())) { addTests(findMarkers(ejb.getClassLoader()), findClassMarkers(ejb.getClassLoader()), ejb.getFinder(), testClasses); saw.add(ejb.getClassLoader()); } } for (final ConnectorModule connector : module.getConnectorModules()) { if (connector.getClassLoader() != null && !saw.contains(connector.getClassLoader())) { addTests(findMarkers(connector.getClassLoader()), findClassMarkers(connector.getClassLoader()), connector.getFinder(), testClasses); saw.add(connector.getClassLoader()); } } // keep it since CukeSpace doesn't rely on JUnit or TestNG @Test so it stays mandatory final File file = module.getFile(); final String line = findTestName(file, module.getClassLoader()); if (line != null) { String name; final int endIndex = line.indexOf('#'); if (endIndex > 0) { name = line.substring(0, endIndex); if (file != null && !file.getName().equals(line.substring(endIndex + 1, line.length()))) { name = null; } } else { name = line; } if (name != null) { boolean found = false; for (final WebModule web : module.getWebModules()) { try { testClasses.add(web.getClassLoader().loadClass(name)); found = true; break; } catch (final Throwable e) { // no-op } } if (!found) { try { testClasses.add(module.getClassLoader().loadClass(name)); } catch (final Throwable e) { // no-op } } } } final Iterator<Class<?>> it = testClasses.iterator(); while (it.hasNext()) { try { // call some reflection methods to make it fail if some dep are missing... Class<?> current = it.next(); if (!AnnotationDeployer.isInstantiable(current)) { it.remove(); continue; } while (current != null) { current.getDeclaredFields(); current.getDeclaredMethods(); current.getCanonicalName(); current = current.getSuperclass(); // TODO: more validations } } catch (final NoClassDefFoundError ncdfe) { it.remove(); } } for (final Class<?> test : testClasses) { final EjbJar ejbJar = new EjbJar(); final OpenejbJar openejbJar = new OpenejbJar(); final String name = test.getName(); final String ejbName = module.getModuleId() + "_" + name; final ManagedBean bean = ejbJar.addEnterpriseBean(new ManagedBean(ejbName, name, true)); bean.localBean(); bean.setTransactionType(TransactionType.BEAN); final EjbDeployment ejbDeployment = openejbJar.addEjbDeployment(bean); ejbDeployment.setDeploymentId(ejbName); final EjbModule ejbModule = new EjbModule(ejbJar, openejbJar); ejbModule.setClassLoader(test.getClassLoader()); final WebModule webModule = webTestClasses.get(test); if (webModule != null) { ejbModule.setWebapp(true); ejbModule.getProperties().put("openejb.ejbmodule.webappId", webModule.getModuleId()); } ejbModule.getProperties().put("openejb.ejbmodule.MergeWebappJndiContext", "true"); module.getEjbModules().add(ejbModule); } return module; } private Set<Class<? extends Annotation>> findClassMarkers(final ClassLoader contextClassLoader) { final Set<Class<? extends Annotation>> testMarkers = new HashSet<>(); for (final String s : asList("org.junit.runner.RunWith", Discover.class.getName())) { try { testMarkers.add((Class<? extends Annotation>) contextClassLoader.loadClass(s)); } catch (final Throwable e) { // no-op } } return testMarkers; } private Set<Class<? extends Annotation>> findMarkers(final ClassLoader contextClassLoader) { final Set<Class<? extends Annotation>> testMarkers = new HashSet<>(); for (final String s : asList("org.junit.Test", "org.testng.annotations.Test")) { try { testMarkers.add((Class<? extends Annotation>) contextClassLoader.loadClass(s)); } catch (final Throwable e) { // no-op: deployment = false } } return testMarkers; } private static void addTests(final Set<Class<? extends Annotation>> testMarkers, final Set<Class<? extends Annotation>> classMarkers, final IAnnotationFinder finder, final Set<Class<?>> testClasses) { if (finder == null) { return; } for (final Class<? extends Annotation> marker : testMarkers) { try { final List<Method> annotatedMethods = finder.findAnnotatedMethods(marker); for (final Method m : annotatedMethods) { try { testClasses.add(m.getDeclaringClass()); } catch (final NoClassDefFoundError e) { // no-op } } } catch (final NoClassDefFoundError ncdfe) { // no-op } } for (final Class<? extends Annotation> marker : classMarkers) { try { testClasses.addAll(finder.findAnnotatedClasses(marker)); } catch (final NoClassDefFoundError ncdfe) { // no-op } } } private String findTestName(final File folder, final ClassLoader classLoader) { InputStream is = null; File dir = folder; if (dir != null && (dir.getName().endsWith(".war") || dir.getName().endsWith(".ear"))) { final File unpacked = new File(dir.getParentFile(), dir.getName().substring(0, dir.getName().length() - 4)); if (unpacked.exists()) { dir = unpacked; } } if (dir != null && dir.isDirectory()) { final File info = new File(dir, "arquillian-tomee-info.txt"); if (info.exists()) { try { is = new FileInputStream(info); } catch (final FileNotFoundException e) { // no-op } } } if (is == null) { is = classLoader.getResourceAsStream("arquillian-tomee-info.txt"); } if (is != null) { try { return org.apache.openejb.loader.IO.slurp(is); } catch (final IOException e) { // no-op } finally { org.apache.openejb.loader.IO.close(is); } } return null; } }