Как использовать собственный ClassLoader для теста Spring Junit? - PullRequest
0 голосов
/ 18 июня 2020

Наше приложение использует Spring. Моя цель состоит в том, чтобы при запуске системного теста я мог инструментировать класс при загрузке класса. Я использую собственный загрузчик классов для инструментария классов в проекте.

instrumenting class 'application.framework.gaeb.util.GaebDataExchangePhase'

Когда я запускаю тест, я получаю NPE.

26 Jun 2020 15:19:46,168  WARN main support.GenericWebApplicationContext:488 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'lockSubprojectAspect': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private application.persistence.dao.IAttachmentDao application.core.aspect.LockSubprojectAspect.attachmentDao; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'attachmentDaoImpl' defined in file [C:\Users\jizhang\Documents\Projekt\kukagets\target\classes\application\persistence\dao\impl\AttachmentDaoImpl.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.hibernate.SessionFactory]: : Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException
26 Jun 2020 15:19:46,179 ERROR main context.TestContextManager:215 - Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@3c407114] to prepare test instance [application.systemtest.tlo.calculation.transferTemplate.TloTransferTemplateCalculationTest@24facb47]
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:94)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:170)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:110)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:200)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:259)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:261)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:219)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at application.jacoco.ClasspathTestRunner.lambda$0(ClasspathTestRunner.java:59)
at application.jacoco.ClasspathTestRunner.run(ClasspathTestRunner.java:69)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'lockSubprojectAspect': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private application.persistence.dao.IAttachmentDao application.core.aspect.LockSubprojectAspect.attachmentDao; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'attachmentDaoImpl' defined in file [C:\Users\jizhang\Documents\Projekt\kukagets\target\classes\application\persistence\dao\impl\AttachmentDaoImpl.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.hibernate.SessionFactory]: : Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1208)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:133)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:109)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:261)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86)
... 29 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private application.persistence.dao.IAttachmentDao application.core.aspect.LockSubprojectAspect.attachmentDao; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'attachmentDaoImpl' defined in file [C:\Users\jizhang\Documents\Projekt\kukagets\target\classes\application\persistence\dao\impl\AttachmentDaoImpl.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.hibernate.SessionFactory]: : Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
... 45 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'attachmentDaoImpl' defined in file [C:\Users\jizhang\Documents\Projekt\kukagets\target\classes\application\persistence\dao\impl\AttachmentDaoImpl.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.hibernate.SessionFactory]: : Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:185)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1137)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1040)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 47 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [application/core/config/beans/DatabaseDefaultConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1572)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 60 more
Caused by: java.lang.NullPointerException
at org.hibernate.annotations.common.reflection.java.JavaAnnotationReader.isAnnotationPresent(JavaAnnotationReader.java:50)
at org.hibernate.annotations.common.reflection.java.JavaXAnnotatedElement.isAnnotationPresent(JavaXAnnotatedElement.java:60)
at org.hibernate.cfg.AnnotationBinder.bindPackage(AnnotationBinder.java:308)
at org.hibernate.cfg.Configuration.addPackage(Configuration.java:817)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.scanPackages(LocalSessionFactoryBuilder.java:314)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:434)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1631)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1568)
... 71 more

Если я использую свой собственный ClassLoader, но не сам определяю класс, но используйте только super.loadClass, тогда проблем не будет. Но использование моего собственного loadClass сообщит об ошибке.

My InstrumentClassLoader:

public class InstrumentClassLoader extends ClassLoader {

private IRuntime runtime = null;
private Instrumenter instr = null;
private static List<String> classesForJaCoCo;

private static Map<String, Class<?>> loadedClasses = new HashMap<>();

public InstrumentClassLoader(ClassLoader classloader) {
    super(classloader);
}

public void setRuntime(IRuntime runtime) {
    this.runtime = runtime;
    instr = new Instrumenter(runtime);
}

public IRuntime getRuntime() {
    return runtime;
}

public void setList() {
    classesForJaCoCo = new ArrayList<>();
}

public List<String> getList() {
    return classesForJaCoCo;
}

private Class<?> getClass(String name, InputStream stream) throws ClassNotFoundException {
    // String file = name.replace('.', File.separatorChar) + ".class";
    byte[] b = null;
    try {
        b = loadClassData(stream);
        Class<?> c = defineClass(name, b, 0, b.length);
        resolveClass(c);
        return c;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

public Class<?> instrumentClass(String name, InputStream stream) {

    byte[] bytes = null;
    try {
        bytes = instr.instrument(stream, name);
    } catch (IOException e) {
        e.printStackTrace();
    }
    Class<?> c = defineClass(name, bytes, 0, bytes.length);
    resolveClass(c);
    return c;
}

private InputStream getStream(String name) {
    String file = name.replace('.', File.separatorChar) + ".class";
    return getClass().getClassLoader().getResourceAsStream(file);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> c = loadedClasses.get(name);
    if (c == null) {

        if (name.startsWith("application.") && !name.contains("jacoco")) {
            InputStream stream = getStream(name);
            if (stream != null) {
                if (name.contains("Test") || name.contains("systemtest")) {
                    System.out.println("loading class '" + name + "'");
                    c = getClass(name, stream);
                    loadedClasses.put(name, c);
                    return c;
                }
                System.out.println("instrumenting class '" + name + "'");
                c = instrumentClass(name, stream);
                classesForJaCoCo.add(name);
                loadedClasses.put(name, c);
                return c;
            } else {
                System.out.println("No InputStream: '" + name + "'");
            }
        }
        c = super.loadClass(name);
        loadedClasses.put(name, c);
    }
    return c;
}

private byte[] loadClassData(InputStream stream) throws IOException {

    int size = stream.available();
    byte buff[] = new byte[size];
    DataInputStream in = new DataInputStream(stream);
    // Reading the binary data
    in.readFully(buff);
    in.close();
    return buff;
}

public Map<String, Class<?>> getLoadedClasses() {
    return loadedClasses;
}

}

My ClasspathTestRunner:

public class ClasspathTestRunner extends SpringJUnit4ClassRunner {
static ClassLoader instrumentClassLoader;
static IRuntime runtime = new LoggerRuntime();
static Instrumenter instr = null;
private static InputStream original = null;
private static RuntimeData data = new RuntimeData();

public ClasspathTestRunner(Class<?> clazz) throws Exception {
    super(loadFromCustomClassloader(clazz));
}

// Loads a class in the custom classloader
private static Class<?> loadFromCustomClassloader(Class<?> clazz) throws InitializationError, Exception {
    // Only load once to support parallel tests
    if (instrumentClassLoader == null) {
        instrumentClassLoader = new InstrumentClassLoader(ClassLoader.getSystemClassLoader());
        ((InstrumentClassLoader) instrumentClassLoader).setRuntime(runtime);
        ((InstrumentClassLoader) instrumentClassLoader).setList();
    }
    return Class.forName(clazz.getName(), true, instrumentClassLoader);
}

@Override
public void run(final RunNotifier notifier) {
    Runnable runnable = () -> {
        super.run(notifier);
    };
    //Thread thread = new Thread(runnable);
    //thread.setContextClassLoader(instrumentClassLoader);

    try {
        runtime.startup(data);
        System.out.println("++++++++++++++++++++ runtime start ++++++++++++++++++++");
        //thread.start();
        //System.out.println("++++++++++++++++++++ thread start ++++++++++++++++++++");
        runnable.run();
        //thread.join();
        //System.out.println("++++++++++++++++++++ thread join ++++++++++++++++++++");

        runtime.shutdown();
        System.out.println("++++++++++++++++++++ runtime shutdown ++++++++++++++++++++");

    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

public static void resetData() {
    data.reset();
}

public static void analyse() throws Exception {
    final ExecutionDataStore executionData = new ExecutionDataStore();
    final SessionInfoStore sessionInfos = new SessionInfoStore();
    data.collect(executionData, sessionInfos, false);
    final CoverageBuilder coverageBuilder = new CoverageBuilder();
    final Analyzer analyzer = new Analyzer(executionData, coverageBuilder);

    List<String> classes = ((InstrumentClassLoader) instrumentClassLoader).getList();
    for (String s : classes) {
        String file = s.replace('.', File.separatorChar) + ".class";
        InputStream stream = ClasspathTestRunner.class.getClassLoader().getResourceAsStream(file);
        analyzer.analyzeClass(stream, s);
    }
    for (final IClassCoverage cc : coverageBuilder.getClasses()) {
        boolean nameCount = false;

        for (IMethodCoverage c : cc.getMethods()) {
            if (c.getMethodCounter().getCoveredCount() != 0) { 
                if (!nameCount) {
                    System.out.println("Coverage of class " + cc.getName());
                    nameCount = true;
                }
                System.out.println(c.getName());
            }
        }

    }
}

System Тест:

@ActiveProfiles({
    "defaultDatabase"
})
@ContextConfiguration(classes = Application.class)
@WebAppConfiguration
public class SystemTestBasics { 

    ...
    @Before
    public void setUpJacoco() {
        System.out.println("-------------------------before-------------------------");
        ClasspathTestRunner.resetData();
    }

    @After
    public void tearDownJacoco() {
        System.out.println("-------------------------after-------------------------");
        try {
            ClasspathTestRunner.analyse();
        } catch (Exception e) {
        e.printStackTrace();
        }
    }
}

@RunWith(ClasspathTestRunner.class)
public class MyTest extends SystemTestBasics {
    ...
}

Почему такая проблема? Спасибо!

...