Home | History | Annotate | Download | only in internal
      1 package org.testng.internal;
      2 
      3 import java.lang.reflect.Constructor;
      4 import java.lang.reflect.Method;
      5 import java.lang.reflect.Modifier;
      6 import java.util.List;
      7 import java.util.Map;
      8 import java.util.Set;
      9 
     10 import org.testng.IClass;
     11 import org.testng.IInstanceInfo;
     12 import org.testng.ITestContext;
     13 import org.testng.ITestObjectFactory;
     14 import org.testng.TestNGException;
     15 import org.testng.annotations.IAnnotation;
     16 import org.testng.collections.Lists;
     17 import org.testng.collections.Maps;
     18 import org.testng.internal.annotations.AnnotationHelper;
     19 import org.testng.internal.annotations.IAnnotationFinder;
     20 import org.testng.xml.XmlClass;
     21 import org.testng.xml.XmlTest;
     22 
     23 import static org.testng.internal.ClassHelper.getAvailableMethods;
     24 
     25 /**
     26  * This class creates an ITestClass from a test class.
     27  *
     28  * @author <a href="mailto:cedric (at) beust.com">Cedric Beust</a>
     29  */
     30 public class TestNGClassFinder extends BaseClassFinder {
     31   private ITestContext m_testContext = null;
     32   private Map<Class, List<Object>> m_instanceMap = Maps.newHashMap();
     33 
     34   public TestNGClassFinder(ClassInfoMap cim,
     35                            Map<Class, List<Object>> instanceMap,
     36                            XmlTest xmlTest,
     37                            IConfiguration configuration,
     38                            ITestContext testContext)
     39   {
     40     m_testContext = testContext;
     41 
     42     if(null == instanceMap) {
     43       instanceMap= Maps.newHashMap();
     44     }
     45 
     46     IAnnotationFinder annotationFinder = configuration.getAnnotationFinder();
     47     ITestObjectFactory objectFactory = configuration.getObjectFactory();
     48 
     49     //
     50     // Find all the new classes and their corresponding instances
     51     //
     52     Set<Class<?>> allClasses= cim.getClasses();
     53 
     54     //very first pass is to find ObjectFactory, can't create anything else until then
     55     if(objectFactory == null) {
     56       objectFactory = new ObjectFactoryImpl();
     57       outer:
     58       for (Class cls : allClasses) {
     59         try {
     60           if (null != cls) {
     61             Method[] ms;
     62             try {
     63               ms = cls.getMethods();
     64             } catch (NoClassDefFoundError e) {
     65               // https://github.com/cbeust/testng/issues/602
     66               ppp("Warning: Can't link and determine methods of " + cls);
     67               ms = new Method[0];
     68             }
     69             for (Method m : ms) {
     70               IAnnotation a = annotationFinder.findAnnotation(m,
     71                   org.testng.annotations.IObjectFactoryAnnotation.class);
     72               if (null != a) {
     73                 if (!ITestObjectFactory.class.isAssignableFrom(m.getReturnType())) {
     74                   throw new TestNGException("Return type of " + m + " is not IObjectFactory");
     75                 }
     76                 try {
     77                   Object instance = cls.newInstance();
     78                   if (m.getParameterTypes().length > 0 && m.getParameterTypes()[0].equals(ITestContext.class)) {
     79                     objectFactory = (ITestObjectFactory) m.invoke(instance, testContext);
     80                   } else {
     81                     objectFactory = (ITestObjectFactory) m.invoke(instance);
     82                   }
     83                   break outer;
     84                 }
     85                 catch (Exception ex) {
     86                   throw new TestNGException("Error creating object factory: " + cls,
     87                       ex);
     88                 }
     89               }
     90             }
     91           }
     92         } catch (NoClassDefFoundError e) {
     93           Utils.log("[TestNGClassFinder]", 1, "Unable to read methods on class " + cls.getName()
     94               + " - unable to resolve class reference " + e.getMessage());
     95 
     96           for (XmlClass xmlClass : xmlTest.getXmlClasses()) {
     97             if (xmlClass.loadClasses() && xmlClass.getName().equals(cls.getName())) {
     98               throw e;
     99             }
    100           }
    101 
    102         }
    103       }
    104     }
    105 
    106     for(Class cls : allClasses) {
    107       if((null == cls)) {
    108         ppp("FOUND NULL CLASS IN FOLLOWING ARRAY:");
    109         int i= 0;
    110         for(Class c : allClasses) {
    111           ppp("  " + i + ": " + c);
    112         }
    113 
    114         continue;
    115       }
    116 
    117       if(isTestNGClass(cls, annotationFinder)) {
    118         List allInstances= instanceMap.get(cls);
    119         Object thisInstance= (null != allInstances) ? allInstances.get(0) : null;
    120 
    121         // If annotation class and instances are abstract, skip them
    122         if ((null == thisInstance) && Modifier.isAbstract(cls.getModifiers())) {
    123           Utils.log("", 5, "[WARN] Found an abstract class with no valid instance attached: " + cls);
    124           continue;
    125         }
    126 
    127         IClass ic= findOrCreateIClass(m_testContext, cls, cim.getXmlClass(cls), thisInstance,
    128             xmlTest, annotationFinder, objectFactory);
    129         if(null != ic) {
    130           Object[] theseInstances = ic.getInstances(false);
    131           if (theseInstances.length == 0) {
    132             theseInstances = ic.getInstances(true);
    133           }
    134           Object instance= theseInstances[0];
    135           putIClass(cls, ic);
    136 
    137           ConstructorOrMethod factoryMethod =
    138             ClassHelper.findDeclaredFactoryMethod(cls, annotationFinder);
    139           if (factoryMethod != null && factoryMethod.getEnabled()) {
    140             FactoryMethod fm = new FactoryMethod( /* cls, */
    141               factoryMethod,
    142               instance,
    143               xmlTest,
    144               annotationFinder,
    145               m_testContext);
    146             ClassInfoMap moreClasses = new ClassInfoMap();
    147 
    148             {
    149 //            ppp("INVOKING FACTORY " + fm + " " + this.hashCode());
    150               Object[] instances= fm.invoke();
    151 
    152               //
    153               // If the factory returned IInstanceInfo, get the class from it,
    154               // otherwise, just call getClass() on the returned instances
    155               //
    156               if (instances.length > 0) {
    157                 if (instances[0] != null) {
    158                   Class elementClass = instances[0].getClass();
    159                   if(IInstanceInfo.class.isAssignableFrom(elementClass)) {
    160                     for(Object o : instances) {
    161                       IInstanceInfo ii = (IInstanceInfo) o;
    162                       addInstance(ii.getInstanceClass(), ii.getInstance());
    163                       moreClasses.addClass(ii.getInstanceClass());
    164                     }
    165                   }
    166                   else {
    167                     for (int i = 0; i < instances.length; i++) {
    168                       Object o = instances[i];
    169                       if (o == null) {
    170                         throw new TestNGException("The factory " + fm + " returned a null instance" +
    171                             "at index " + i);
    172                       } else {
    173                         addInstance(o.getClass(), o);
    174                         if(!classExists(o.getClass())) {
    175                           moreClasses.addClass(o.getClass());
    176                         }
    177                       }
    178                     }
    179                   }
    180                 }
    181               }
    182             }
    183 
    184             if(moreClasses.getSize() > 0) {
    185               TestNGClassFinder finder=
    186                 new TestNGClassFinder(moreClasses,
    187                     m_instanceMap,
    188                     xmlTest,
    189                     configuration,
    190                     m_testContext);
    191 
    192               IClass[] moreIClasses= finder.findTestClasses();
    193               for(IClass ic2 : moreIClasses) {
    194                 putIClass(ic2.getRealClass(), ic2);
    195               }
    196             } // if moreClasses.size() > 0
    197           }
    198         } // null != ic
    199       } // if not TestNG class
    200       else {
    201         Utils.log("TestNGClassFinder", 3, "SKIPPING CLASS " + cls + " no TestNG annotations found");
    202       }
    203     } // for
    204 
    205     //
    206     // Add all the instances we found to their respective IClasses
    207     //
    208     for(Map.Entry<Class, List<Object>> entry : m_instanceMap.entrySet()) {
    209       Class clazz = entry.getKey();
    210       for(Object instance : entry.getValue()) {
    211         IClass ic= getIClass(clazz);
    212         if(null != ic) {
    213           ic.addInstance(instance);
    214         }
    215       }
    216     }
    217   }
    218 
    219   /**
    220    * @return true if this class contains TestNG annotations (either on itself
    221    * or on a superclass).
    222    */
    223   public static boolean isTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
    224     Class[] allAnnotations= AnnotationHelper.getAllAnnotations();
    225     Class<?> cls = c;
    226 
    227     try {
    228       for(Class annotation : allAnnotations) {
    229 
    230         for (cls = c; cls != null; cls = cls.getSuperclass()) {
    231           // Try on the methods
    232           for (Method m : getAvailableMethods(cls)) {
    233             IAnnotation ma= annotationFinder.findAnnotation(m, annotation);
    234             if(null != ma) {
    235               return true;
    236             }
    237           }
    238 
    239           // Try on the class
    240           IAnnotation a= annotationFinder.findAnnotation(cls, annotation);
    241           if(null != a) {
    242             return true;
    243           }
    244 
    245           // Try on the constructors
    246           for (Constructor ctor : cls.getConstructors()) {
    247             IAnnotation ca= annotationFinder.findAnnotation(ctor, annotation);
    248             if(null != ca) {
    249               return true;
    250             }
    251           }
    252         }
    253       }
    254 
    255       return false;
    256 
    257     } catch (NoClassDefFoundError e) {
    258       Utils.log("[TestNGClassFinder]", 1,
    259           "Unable to read methods on class " + cls.getName()
    260           + " - unable to resolve class reference " + e.getMessage());
    261       return false;
    262     }
    263   }
    264 
    265   private void addInstance(Class clazz, Object o) {
    266     List<Object> list= m_instanceMap.get(clazz);
    267 
    268     if(null == list) {
    269       list= Lists.newArrayList();
    270       m_instanceMap.put(clazz, list);
    271     }
    272 
    273     list.add(o);
    274   }
    275 
    276   public static void ppp(String s) {
    277     System.out.println("[TestNGClassFinder] " + s);
    278   }
    279 
    280 }
    281