All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.maven.surefire.RoboVMSurefireProvider Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 RoboVM AB
 *
 * Licensed 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.robovm.maven.surefire;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
import org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory;
import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
import org.apache.maven.surefire.providerapi.AbstractProvider;
import org.apache.maven.surefire.providerapi.ProviderParameters;
import org.apache.maven.surefire.report.ConsoleOutputCapture;
import org.apache.maven.surefire.report.ConsoleOutputReceiver;
import org.apache.maven.surefire.report.PojoStackTraceWriter;
import org.apache.maven.surefire.report.ReportEntry;
import org.apache.maven.surefire.report.ReporterException;
import org.apache.maven.surefire.report.ReporterFactory;
import org.apache.maven.surefire.report.RunListener;
import org.apache.maven.surefire.report.SimpleReportEntry;
import org.apache.maven.surefire.shade.org.apache.maven.shared.utils.io.SelectorUtils;
import org.apache.maven.surefire.suite.RunResult;
import org.apache.maven.surefire.testset.TestSetFailedException;
import org.apache.maven.surefire.util.RunOrderCalculator;
import org.apache.maven.surefire.util.ScanResult;
import org.apache.maven.surefire.util.TestsToRun;
import org.apache.maven.surefire.util.internal.StringUtils;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.robovm.compiler.AppCompiler;
import org.robovm.compiler.Version;
import org.robovm.compiler.config.Arch;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.config.Config.Home;
import org.robovm.compiler.config.OS;
import org.robovm.compiler.log.Logger;
import org.robovm.compiler.target.LaunchParameters;
import org.robovm.compiler.target.ios.ProvisioningProfile;
import org.robovm.compiler.target.ios.SigningIdentity;
import org.robovm.junit.client.TestClient;
import org.robovm.maven.resolver.RoboVMResolver;

public class RoboVMSurefireProvider extends AbstractProvider {
    private final static String PROP_LOG_DEBUG = "robovm.test.enableDebugLogging";
    private final static String PROP_SERVER_DEBUG = "robovm.test.enableServerLogging";
    private final static String PROP_OS = "robovm.test.os";
    private final static String PROP_ARCH = "robovm.test.arch";
    private final static String PROP_CONFIG_FILE = "robovm.test.configFile";
    private final static String PROP_PROPERTIES_FILE = "robovm.test.propertiesFile";
    private final static String PROP_IOS_SIGNING_IDENTITY = "robovm.test.iosSignIdentity";
    private final static String PROP_IOS_PROVISIONING_PROFILE = "robovm.test.iosProvisioningProfile";
    private final static String PROP_IOS_SKIP_SIGNING = "robovm.test.iosSkipSigning";

    private final ClassLoader testClassLoader;
    private final List customRunListeners;
    private final JUnit4TestChecker jUnit4TestChecker;
    private final String requestedTestMethod;
    private final ProviderParameters providerParameters;
    private final RunOrderCalculator runOrderCalculator;
    private final ScanResult scanResult;
    private TestsToRun testsToRun;

    public RoboVMSurefireProvider(ProviderParameters booterParameters) {
        providerParameters = booterParameters;
        testClassLoader = booterParameters.getTestClassLoader();
        scanResult = booterParameters.getScanResult();
        runOrderCalculator = booterParameters.getRunOrderCalculator();
        customRunListeners = JUnit4RunListenerFactory.
                createCustomListeners(booterParameters.getProviderProperties().getProperty("listener"));
        jUnit4TestChecker = new JUnit4TestChecker(testClassLoader);
        requestedTestMethod = booterParameters.getTestRequest().getRequestedTestMethod();
    }

    @Override
    public Iterator getSuites() {
        testsToRun = scanClassPath();
        return testsToRun.iterator();
    }

    private TestsToRun scanClassPath() {
        final TestsToRun scannedClasses = scanResult.applyFilter(jUnit4TestChecker, testClassLoader);
        return runOrderCalculator.orderTestClasses(scannedClasses);
    }

    @Override
    public RunResult invoke(Object forkTestSet) throws TestSetFailedException, ReporterException {
        if (testsToRun == null) {
            if (forkTestSet instanceof TestsToRun) {
                testsToRun = (TestsToRun) forkTestSet;
            } else if (forkTestSet instanceof Class) {
                testsToRun = TestsToRun.fromClass((Class) forkTestSet);
            } else {
                testsToRun = scanClassPath();
            }
        }

        final ReporterFactory reporterFactory = providerParameters.getReporterFactory();
        final RunListener reporter = reporterFactory.createReporter();
        ConsoleOutputCapture.startCapture((ConsoleOutputReceiver) reporter);
        final JUnit4RunListener jUnit4TestSetReporter = new JUnit4RunListener(reporter);
        Result result = new Result();
        final RunNotifier runNotifier = getRunNotifier(jUnit4TestSetReporter, result, customRunListeners);

        TestClient testClient = new TestClient();
        testClient.setRunListener(new org.junit.runner.notification.RunListener() {
            public void testRunStarted(Description description) throws Exception {
                runNotifier.fireTestRunStarted(description);
            }
            public void testRunFinished(Result result) throws Exception {
                runNotifier.fireTestRunFinished(result);
            }
            public void testStarted(Description description) throws Exception {
                runNotifier.fireTestStarted(description);
            }
            public void testFinished(Description description) throws Exception {
                runNotifier.fireTestFinished(description);
            }
            public void testFailure(Failure failure) throws Exception {
                runNotifier.fireTestFailure(failure);
            }
            public void testAssumptionFailure(Failure failure) {
                runNotifier.fireTestAssumptionFailed(failure);
            }
            public void testIgnored(Description description) throws Exception {
                runNotifier.fireTestIgnored(description);
            }
        });

        Process process = null;
        try {
            Config config = testClient.configure(createConfig()).build();
            config.getLogger().info("Building RoboVM tests for: %s (%s)", config.getOs(), config.getArch());
            config.getLogger().info("This could take a while, especially the first time round");
            AppCompiler appCompiler = new AppCompiler(config);
            appCompiler.build();
            
            LaunchParameters launchParameters = config.getTarget().createLaunchParameters();
            if (Boolean.getBoolean(PROP_SERVER_DEBUG)) {
                launchParameters.getArguments().add("-rvm:Drobovm.debug=true");
            }
            process = appCompiler.launchAsync(launchParameters);
            
            runNotifier.fireTestRunStarted(null);
            for (Class clazz : testsToRun) {
                executeTestSet(testClient, clazz, reporter, runNotifier);
            }
            testClient.terminate();
            process.waitFor();
            runNotifier.fireTestRunFinished(result);
            JUnit4RunListener.rethrowAnyTestMechanismFailures(result);
        } catch (Throwable t) {
            throw new RuntimeException("RoboVM test run failed", t);
        } finally {
            if (process != null) {
                process.destroy();
            }
        }

        return reporterFactory.close();
    }

    private String[] testToRunToClassPatterns(Class clazz) {
        List result = new ArrayList<>();
        if (!StringUtils.isBlank(this.requestedTestMethod)) {
            // Copied from JUnit4Provider
            String actualTestMethod = getMethod(clazz, this.requestedTestMethod);
            String[] testMethods = StringUtils.split(actualTestMethod, "+");
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                for (String testMethod : testMethods) {
                    if (SelectorUtils.match(testMethod, method.getName())) {
                        result.add(clazz.getName() + "#" + method.getName());
                    }
                }
            }
        } else {
            result.add(clazz.getName());
        }
        return result.toArray(new String[result.size()]);
    }

    private void executeTestSet(TestClient testClient, Class clazz, RunListener reporter, RunNotifier listeners)
            throws ReporterException, TestSetFailedException {

        final ReportEntry report = new SimpleReportEntry(this.getClass().getName(), clazz.getName());
        reporter.testSetStarting(report);

        try {
            testClient.runTests(testToRunToClassPatterns(clazz)).flush();
        } catch (Throwable e) {
            reporter.testError(SimpleReportEntry.withException(report.getSourceName(), report.getName(),
                    new PojoStackTraceWriter(report.getSourceName(),
                            report.getName(), e)));
        } finally {
            reporter.testSetCompleted(report);
        }
    }

    private Config.Builder createConfig() throws IOException {
        Config.Builder configBuilder = new Config.Builder();

        final Logger logger = new Logger() {
            public void debug(String format, Object... args) {
                if (Boolean.getBoolean(PROP_LOG_DEBUG)) {
                    providerParameters.getConsoleLogger().info("[DEBUG] " + String.format(format, args) + "\n");
                }
            }
            public void info(String format, Object... args) {
                providerParameters.getConsoleLogger().info("[INFO] " + String.format(format, args) + "\n");
            }
            public void warn(String format, Object... args) {
                providerParameters.getConsoleLogger().info("[WARNING] " + String.format(format, args) + "\n");
            }
            public void error(String format, Object... args) {
                providerParameters.getConsoleLogger().info("[ERROR] " + String.format(format, args) + "\n");
            }
        };
        configBuilder.logger(logger);
        
        RoboVMResolver roboVMResolver = new RoboVMResolver();
        roboVMResolver.setLogger(new org.robovm.maven.resolver.Logger() {
            public void info(String logLine) {
                logger.info(logLine);
            }
            public void debug(String logLine) {
                logger.debug(logLine);
            }
        });

        Home home = null;
        try {
            home = Home.find();
        } catch (Throwable t) {}
        if (home == null || !home.isDev()) {
            home = new Home(roboVMResolver.resolveAndUnpackRoboVMDistArtifact(Version.getVersion()));
        }
        configBuilder.home(home);
        if (home.isDev()) {
            configBuilder.useDebugLibs(Boolean.getBoolean("robovm.useDebugLibs"));
            configBuilder.dumpIntermediates(true);
        }

        File basedir = new File(System.getProperty("basedir"));
        if (System.getProperties().containsKey(PROP_PROPERTIES_FILE)) {
            File propertiesFile = new File(System.getProperty(PROP_PROPERTIES_FILE));
            if (!propertiesFile.exists()) {
                throw new FileNotFoundException("Failed to find specified "
                        + PROP_PROPERTIES_FILE + ": " + propertiesFile.getAbsolutePath());
            }
            logger.debug("Loading RoboVM config properties from " 
                    + propertiesFile.getAbsolutePath());
            configBuilder.addProperties(propertiesFile);
        } else {
            configBuilder.readProjectProperties(basedir, true);
        }

        if (System.getProperties().containsKey(PROP_CONFIG_FILE)) {
            File configFile = new File(System.getProperty(PROP_CONFIG_FILE));
            if (!configFile.exists()) {
                throw new FileNotFoundException("Failed to find specified "
                        + PROP_CONFIG_FILE + ": " + configFile.getAbsolutePath());
            }
            logger.debug("Loading RoboVM config from " + configFile.getAbsolutePath());
            configBuilder.read(configFile);
        } else {
            configBuilder.readProjectConfig(basedir, true);
        }
        
        if (System.getProperty(PROP_OS) != null) {
            configBuilder.os(OS.valueOf(System.getProperty(PROP_OS)));
        }
        if (System.getProperty(PROP_ARCH) != null) {
            configBuilder.arch(Arch.valueOf(System.getProperty(PROP_ARCH)));
        }
        if (Boolean.getBoolean(PROP_IOS_SKIP_SIGNING)) {
            configBuilder.iosSkipSigning(true);
        } else {
            if (System.getProperty(PROP_IOS_SIGNING_IDENTITY) != null) {
                String iosSignIdentity = System.getProperty(PROP_IOS_SIGNING_IDENTITY);
                logger.debug("Using explicit iOS Signing identity: " + iosSignIdentity);
                configBuilder.iosSignIdentity(SigningIdentity.find(
                        SigningIdentity.list(), iosSignIdentity));
            }
            if (System.getProperty(PROP_IOS_PROVISIONING_PROFILE) != null) {
                String iosProvisioningProfile = System.getProperty(PROP_IOS_PROVISIONING_PROFILE);
                logger.debug("Using explicit iOS provisioning profile: " + iosProvisioningProfile);
                configBuilder.iosProvisioningProfile(ProvisioningProfile.find(
                        ProvisioningProfile.list(), iosProvisioningProfile));
            }
        }
        
        // Ignore any classpath entries in the loaded robovm.xml file.
        configBuilder.clearClasspathEntries();
        
        configBuilder.addClasspathEntry(roboVMResolver.resolveArtifact("org.robovm:robovm-junit-server:" + Version.getVersion()).asFile());
        for (String p : System.getProperty("java.class.path").split(File.pathSeparator)) {
            configBuilder.addClasspathEntry(new File(p));
        }

        if (testClassLoader.getClass().getName().equals("org.apache.maven.surefire.booter.IsolatedClassLoader")) {
            // Not fork mode. We need to get to the URLs of the IsolatedClassLoader.
            // Only way is to use reflection.
            try {
                Field f = testClassLoader.getClass().getDeclaredField("urls");
                f.setAccessible(true);
                @SuppressWarnings("unchecked")
                Collection urls = (Collection) f.get(testClassLoader);
                for (URL url :urls) {
                    File file = new File(url.getFile());
                    if (file.isDirectory() || file.getName().toLowerCase().matches(".*\\.(jar|zip)$")) {
                        configBuilder.addClasspathEntry(file);
                    }
                }
            } catch (Throwable t) {
                throw new RuntimeException("Failed to get classpath URLs from IsolatedClassLoader using reflection", t);
            }
        } else {
            Properties props = providerParameters.getProviderProperties();
            for (int i = 0; true; i++) {
                String path = props.getProperty("classPathUrl." + i);
                if (path == null) {
                    break;
                }
                configBuilder.addClasspathEntry(new File(path));
            }
        }
        
        for (Class c : testsToRun.getLocatedClasses()) {
            configBuilder.addForceLinkClass(c.getName());
        }
        configBuilder.skipInstall(true);

        return configBuilder;
    }
    
    private RunNotifier getRunNotifier(org.junit.runner.notification.RunListener main, Result result,
            List others) {

        RunNotifier notifier = new RunNotifier();
        notifier.addListener(main);
        notifier.addListener(result.createListener());
        for (org.junit.runner.notification.RunListener listener : others) {
            notifier.addListener(listener);
        }
        return notifier;
    }
    
    /**
     * RoboVM note: Copied from JUnit4Provider and cleaned up.
     * 
     * This method retrieves test methods from String like 
     * "com.xx.ImmutablePairTest#testBasic,com.xx.StopWatchTest#testLang315+testStopWatchSimpleGet"
     * 
* and we need to think about cases that 2 or more method in 1 class. we should choose the correct method * * @param testClass the testclass * @param testMethodStr the test method string * @return a string ;) */ private static String getMethod(Class testClass, String testMethodStr) { String className = testClass.getName(); if (!testMethodStr.contains("#") && !testMethodStr.contains(",")) {// the original way return testMethodStr; } testMethodStr += ",";// for the bellow split code int beginIndex = testMethodStr.indexOf(className); int endIndex = testMethodStr.indexOf(",", beginIndex); String classMethodStr = testMethodStr.substring(beginIndex, endIndex);// String like // "StopWatchTest#testLang315" int index = classMethodStr.indexOf('#'); if (index >= 0) { return classMethodStr.substring(index + 1, classMethodStr.length()); } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy