Package org.apache.sling.junit.impl

Source Code of org.apache.sling.junit.impl.BundleTestsProvider

/*
* 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.sling.junit.impl;

import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.junit.TestsProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** A TestProvider that gets test classes from bundles
*  that have a Sling-Test-Regexp header and corresponding
*  exported classes.
*/
@Component
@Service
public class BundleTestsProvider implements TestsProvider, BundleListener {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private long lastModified;
    private BundleContext bundleContext;
    private String pid;
   
    public static final String SLING_TEST_REGEXP = "Sling-Test-Regexp";
   
    /** Symbolic names of bundles that changed state - if not empty, need
     *  to adjust the list of tests
     */
    private final List<String> changedBundles = new ArrayList<String>();
   
    /** List of (candidate) test classes, keyed by bundle so that we can
     *  update them easily when bundles come and go
     */
    private final Map<String, List<String>> testClassesMap = new HashMap<String, List<String>>();

    protected void activate(ComponentContext ctx) {
        bundleContext = ctx.getBundleContext();
        bundleContext.addBundleListener(this);
       
        // Initially consider all bundles as "changed"
        for(Bundle b : bundleContext.getBundles()) {
            if(getSlingTestRegexp(b) != null) {
                changedBundles.add(b.getSymbolicName());
                log.debug("Will look for test classes inside bundle {}", b.getSymbolicName());
            }
        }
       
        lastModified = System.currentTimeMillis();
        pid = (String)ctx.getProperties().get(Constants.SERVICE_PID);
    }
   
    protected void deactivate(ComponentContext ctx) {
        bundleContext.removeBundleListener(this);
        bundleContext = null;
        changedBundles.clear();
    }
   
    @Override
    public String toString() {
        return getClass().getSimpleName() + ", pid=" + pid;
    }

    /** Update testClasses if bundle changes require it */
    private void maybeUpdateTestClasses() {
        if(changedBundles.isEmpty()) {
            return;
        }

        // Get the list of bundles that have changed
        final List<String> bundlesToUpdate = new ArrayList<String>();
        synchronized (changedBundles) {
            bundlesToUpdate.addAll(changedBundles);
            changedBundles.clear();
        }
       
        // Remove test classes that belong to changed bundles
        for(String symbolicName : bundlesToUpdate) {
            testClassesMap.remove(symbolicName);
        }
       
        // Get test classes from bundles that are in our list
        for(Bundle b : bundleContext.getBundles()) {
            if(bundlesToUpdate.contains(b.getSymbolicName())) {
                final List<String> testClasses = getTestClasses(b);
                if(testClasses != null) {
                    testClassesMap.put(b.getSymbolicName(), testClasses);
                    log.debug("{} test classes found in bundle {}, added to our list",
                            testClasses.size(), b.getSymbolicName());
                } else {
                    log.debug("No test classes found in bundle {}", b.getSymbolicName());
                }
            }
        }
    }

    /** Called when a bundle changes state */
    public void bundleChanged(BundleEvent event) {
        // Only consider bundles which contain tests
        final Bundle b = event.getBundle();
        if(getSlingTestRegexp(b) == null) {
            log.debug("Bundle {} does not have {} header, ignored",
                    b.getSymbolicName(), SLING_TEST_REGEXP);
            return;
        }
        synchronized (changedBundles) {
            log.debug("Got BundleEvent for Bundle {}, will rebuild its lists of tests");
            changedBundles.add(b.getSymbolicName());
        }
        lastModified = System.currentTimeMillis();
    }
   
    private String getSlingTestRegexp(Bundle b) {
        return (String)b.getHeaders().get(SLING_TEST_REGEXP);
    }
   
    /** Get test classes that bundle b provides (as done in Felix/Sigil) */
    private List<String> getTestClasses(Bundle b) {
        final List<String> result = new ArrayList<String>();
        Pattern testClassRegexp = null;
        final String headerValue = getSlingTestRegexp(b);
        try {
            testClassRegexp = Pattern.compile(headerValue);
        } catch(PatternSyntaxException pse) {
            log.warn(
                    "Invalid pattern '" + headerValue
                    + "' for bundle " + b.getSymbolicName() + ", ignored",
                    pse);
        }
       
        if(testClassRegexp == null) {
            log.info("Bundle {} does not have {} header, not looking for test classes", SLING_TEST_REGEXP);
        } else if(Bundle.ACTIVE != b.getState()) {
            log.info("Bundle {} is not active, no test classes considered", b.getSymbolicName());
        } else {
            @SuppressWarnings("unchecked")
            Enumeration<URL> classUrls = b.findEntries("", "*.class", true);
            while (classUrls.hasMoreElements()) {
                URL url = classUrls.nextElement();
                final String name = toClassName(url);
                if(testClassRegexp.matcher(name).matches()) {
                    result.add(name);
                } else {
                    log.debug("Class {} does not match {} pattern {} of bundle {}, ignored",
                            new Object[] { name, SLING_TEST_REGEXP, testClassRegexp, b.getSymbolicName() });
                }
            }
            log.info("{} test classes found in bundle {}", result.size(), b.getSymbolicName());
        }
       
        return result;
    }
   
    /** Convert class URL to class name */
    private String toClassName(URL url) {
        final String f = url.getFile();
        final String cn = f.substring(1, f.length() - ".class".length());
        return cn.replace('/', '.');
    }

    /** Find bundle by symbolic name */
    private Bundle findBundle(String symbolicName) {
        for(Bundle b : bundleContext.getBundles()) {
            if(b.getSymbolicName().equals(symbolicName)) {
                return b;
            }
        }
        return null;
    }
   
    /** @inheritDoc */
    public Class<?> createTestClass(String testName) throws ClassNotFoundException {
        // Find the bundle to which the class belongs
        Bundle b = null;
        for(Map.Entry<String, List<String>> e : testClassesMap.entrySet()) {
            if(e.getValue().contains(testName)) {
                b = findBundle(e.getKey());
                break;
            }
        }
       
        if(b == null) {
            throw new IllegalArgumentException("No Bundle found that supplies test class " + testName);
        }
        return b.loadClass(testName);
    }

    /** @inheritDoc */
    public long lastModified() {
        return lastModified;
    }

    /** @inheritDoc */
    public String getServicePid() {
        return pid;
    }

    /** @inheritDoc */
    public List<String> getTestNames() {
        maybeUpdateTestClasses();
        final List<String> result = new ArrayList<String>();
        for(List<String> list : testClassesMap.values()) {
            result.addAll(list);
        }
        return result;
    }
}
TOP

Related Classes of org.apache.sling.junit.impl.BundleTestsProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.