org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.java

Source

/*******************************************************************************
 * Copyright (c) 2015, 2016 Pivotal, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Pivotal, Inc. - initial API and implementation
 *******************************************************************************/
package org.springframework.ide.eclipse.boot.test;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.springsource.ide.eclipse.commons.livexp.ui.ProjectLocationSection.getDefaultProjectLocation;

import java.io.File;
import java.util.Arrays;
import java.util.Comparator;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.m2e.core.ui.internal.UpdateMavenProjectJob;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
import org.springframework.ide.eclipse.boot.core.ISpringBootProject;
import org.springframework.ide.eclipse.boot.core.SpringBootCore;
import org.springframework.ide.eclipse.boot.test.util.CopyFromFolder;
import org.springframework.ide.eclipse.boot.util.RetryUtil;
import org.springframework.ide.eclipse.boot.wizard.NewSpringBootWizardModel;
import org.springframework.ide.eclipse.boot.wizard.RadioGroup;
import org.springframework.ide.eclipse.boot.wizard.RadioInfo;
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
import org.springframework.ide.eclipse.boot.wizard.importing.ImportConfiguration;
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategies;
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil;

/**
 * @author Kris De Volder
 */
public class BootProjectTestHarness {

    private static final boolean DEBUG = true;

    private static void debug(String string) {
        if (DEBUG) {
            System.out.println(string);
        }
    }

    public static final long BOOT_PROJECT_CREATION_TIMEOUT = 5 * 60 * 1000; // long, may download maven dependencies

    private IWorkspace workspace;

    public BootProjectTestHarness(IWorkspace workspace) {
        this.workspace = workspace;
    }

    @FunctionalInterface
    public interface WizardConfigurer {

        void apply(NewSpringBootWizardModel wizard);

        WizardConfigurer NULL = new WizardConfigurer() {
            public void apply(NewSpringBootWizardModel wizard) {
                /*do nothing*/}
        };
    }

    public static WizardConfigurer withImportStrategy(final String id) {
        final ImportStrategy is = ImportStrategies.withId(id);
        Assert.isNotNull(is);
        return new WizardConfigurer() {
            public void apply(NewSpringBootWizardModel wizard) {
                wizard.setImportStrategy(is);
            }
        };
    }

    public static WizardConfigurer withPackaging(final String packagingTypeName) {
        return (wizard) -> {
            RadioGroup packagingRadio = wizard.getRadioGroups().getGroup("packaging");
            assertNotNull("Couldn't find 'packaging' radiogroup in the wizard model", packagingRadio);
            for (RadioInfo r : packagingRadio.getRadios()) {
                if (r.getValue().equals(packagingTypeName)) {
                    packagingRadio.getSelection().selection.setValue(r);
                    return;
                }
            }
            fail("Couldn't find packaging type '" + packagingTypeName + "' in the wizard model");
        };
    }

    public static WizardConfigurer withStarters(final String... ids) {
        if (ids.length > 0) {
            return new WizardConfigurer() {
                public void apply(NewSpringBootWizardModel wizard) {
                    for (String id : ids) {
                        wizard.addDependency(id);
                    }
                }
            };
        }
        return WizardConfigurer.NULL;
    }

    public static WizardConfigurer setPackage(final String pkgName) {
        return new WizardConfigurer() {
            public void apply(NewSpringBootWizardModel wizard) {
                wizard.getStringInput("packageName").setValue(pkgName);
            }
        };
    }

    /**
     * @return A wizard configurer that ensures the selected 'boot version' is exactly
     * a given version of boot.
     */
    public static WizardConfigurer bootVersion(final String wantedVersion) throws Exception {
        return new WizardConfigurer() {
            public void apply(NewSpringBootWizardModel wizard) {
                RadioGroup bootVersionRadio = wizard.getBootVersion();
                for (RadioInfo option : bootVersionRadio.getRadios()) {
                    if (option.getValue().equals(wantedVersion)) {
                        bootVersionRadio.setValue(option);
                        return;
                    }
                }
                fail("The wanted bootVersion '" + wantedVersion + "'is not found in the wizard");
            }
        };
    }

    /**
     * @return A wizard configurer that ensures the selected 'boot version' is at least
     * a given version of boot.
     */
    public static WizardConfigurer bootVersionAtLeast(final String wantedVersion) throws Exception {
        final VersionRange WANTED_RANGE = new VersionRange(wantedVersion);
        return new WizardConfigurer() {
            public void apply(NewSpringBootWizardModel wizard) {
                RadioGroup bootVersionRadio = wizard.getBootVersion();
                RadioInfo selected = bootVersionRadio.getValue();
                Version selectedVersion = getVersion(selected);
                if (WANTED_RANGE.includes(selectedVersion)) {
                    //existing selection is fine
                } else {
                    //try to select the latest available version and verify it meets the requirement
                    bootVersionRadio.setValue(selected = getLatestVersion(bootVersionRadio));
                    selectedVersion = getVersion(selected);
                    Assert.isTrue(WANTED_RANGE.includes(selectedVersion));
                }
            }

            private RadioInfo getLatestVersion(RadioGroup bootVersionRadio) {
                RadioInfo[] infos = bootVersionRadio.getRadios();
                Arrays.sort(infos, new Comparator<RadioInfo>() {
                    public int compare(RadioInfo o1, RadioInfo o2) {
                        Version v1 = getVersion(o1);
                        Version v2 = getVersion(o2);
                        return v2.compareTo(v1);
                    }
                });
                return infos[0];
            }

            private Version getVersion(RadioInfo info) {
                String versionString = info.getValue();
                Version v = new Version(versionString);
                if ("BUILD-SNAPSHOT".equals(v.getQualifier())) {
                    // Caveat "M1" will be treated as 'later' than "BUILD-SNAPSHOT" so that is wrong.
                    return new Version(v.getMajor(), v.getMinor(), v.getMicro(), "SNAPSHOT"); //Comes after "MX" but before "RELEASE"
                }
                return v;
            }
        };
    }

    public IProject createBootWebProject(final String projectName, final WizardConfigurer... extraConfs)
            throws Exception {
        return createBootProject(projectName, merge(extraConfs, withStarters("web")));
    }

    private WizardConfigurer[] merge(WizardConfigurer[] confs, WizardConfigurer... moreConfs) {
        WizardConfigurer[] merged = new WizardConfigurer[confs.length + moreConfs.length];
        System.arraycopy(confs, 0, merged, 0, confs.length);
        System.arraycopy(moreConfs, 0, merged, confs.length, moreConfs.length);
        return merged;
    }

    public IProject createBootProject(final String projectName, final WizardConfigurer... extraConfs)
            throws Exception {
        RetryUtil.retryWhen("createBootProject(" + projectName + ")", 3, RetryUtil.errorWithMsg("Read timed out"),
                () -> {
                    final Job job = new Job("Create boot project '" + projectName + "'") {
                        protected IStatus run(IProgressMonitor monitor) {
                            try {
                                //No point doing a retry if we will just fail because project already exists!
                                IProject p = getProject(projectName);
                                if (p.exists()) {
                                    p.delete(true, true, new NullProgressMonitor());
                                }
                                NewSpringBootWizardModel wizard = new NewSpringBootWizardModel(
                                        new MockPrefsStore());
                                wizard.allowUIThread(true);
                                wizard.getProjectName().setValue(projectName);
                                wizard.getArtifactId().setValue(projectName);
                                //Note: unlike most of the rest of the wizard's behavior, the 'use default location'
                                //  checkbox and its effect is not part of the model but part of the GUI code (this is
                                //  wrong, really, but that's how it is, so we have to explictly set the project
                                //  location in the model.
                                wizard.getLocation().setValue(getDefaultProjectLocation(projectName));
                                for (WizardConfigurer extraConf : extraConfs) {
                                    extraConf.apply(wizard);
                                }
                                wizard.performFinish(new NullProgressMonitor()/*new SysOutProgressMonitor()*/);
                                return Status.OK_STATUS;
                            } catch (Throwable e) {
                                return ExceptionUtil.status(e);
                            }
                        }
                    };
                    //job.setRule(workspace.getRuleFactory().buildRule());
                    job.schedule();

                    waitForImportJob(getProject(projectName), job);

                });
        return getProject(projectName);
    }

    public static void waitForImportJob(final IProject project, final Job job) throws Exception {
        new ACondition("Wait for import of " + project.getName(), BOOT_PROJECT_CREATION_TIMEOUT) {
            @Override
            public boolean test() throws Exception {
                assertOk(job.getResult());
                if (project.hasNature("org.eclipse.m2e.core.maven2Nature")) {
                    updateMavenProjectDependencies(project);
                }
                StsTestUtil.assertNoErrors(project);
                return true;
            }
        };
    }

    public IProject getProject(String projectName) {
        return workspace.getRoot().getProject(projectName);
    }

    public static void updateMavenProjectDependencies(IProject project) throws InterruptedException {
        debug("updateMavenProjectDependencies(" + project.getName() + ") ...");
        boolean refreshFromLocal = true;
        boolean cleanProjects = true;
        boolean updateConfig = true;
        IProject[] projects = { project };
        boolean offline = false;
        boolean forceUpdateDeps = true;
        UpdateMavenProjectJob job = new UpdateMavenProjectJob(projects, offline, forceUpdateDeps, updateConfig,
                cleanProjects, refreshFromLocal);
        job.schedule();
        job.join();
        debug("updateMavenProjectDependencies(" + project.getName() + ") DONE");
    }

    public static IProject createPredefinedMavenProject(final String projectName, final String bundleName)
            throws CoreException, Exception {
        IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
        if (project.exists()) {
            return project;
        }
        StsTestUtil.setAutoBuilding(false);
        ImportConfiguration importConf = new ImportConfiguration() {

            @Override
            public String getProjectName() {
                return projectName;
            }

            @Override
            public String getLocation() {
                return ResourcesPlugin.getWorkspace().getRoot().getLocation().append(projectName).toString();
            }

            @Override
            public CodeSet getCodeSet() {
                File sourceWorkspace = new File(StsTestUtil.getSourceWorkspacePath(bundleName));
                File sourceProject = new File(sourceWorkspace, projectName);
                return new CopyFromFolder(projectName, sourceProject);
            }
        };
        final IRunnableWithProgress importOp = BuildType.MAVEN.getDefaultStrategy().createOperation(importConf);
        Job runner = new Job("Import " + projectName) {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    importOp.run(monitor);
                } catch (Throwable e) {
                    return ExceptionUtil.status(e);
                }
                return Status.OK_STATUS;
            }
        };
        runner.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
        runner.schedule();

        waitForImportJob(project, runner);
        //      BootProjectTestHarness.assertNoErrors(project);
        return project;
    }

    public static void buildMavenProject(IProject p) throws Exception {
        ISpringBootProject bp = SpringBootCore.create(p);
        updateMavenProjectDependencies(bp.getProject());
        bp.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
    }

    public static void assertOk(IStatus result) throws Exception {
        if (result == null || !result.isOK()) {
            throw ExceptionUtil.coreException(result);
        }
    }

    /**
     * Create the most basic project possible. It has no natures, no builders, not nothing.
     * This project is suitable as a test fixture for a test that only needs a project to
     * exist and nothing more.
     */
    public IProject createProject(String projectName) throws Exception {
        IProject project = workspace.getRoot().getProject(projectName);
        project.create(new NullProgressMonitor());
        project.open(new NullProgressMonitor());
        return project;
    }

    public IProject rename(IProject project, String newName) throws Exception {
        IProjectDescription description = project.getDescription();
        description.setName(newName);
        project.move(description, true, new NullProgressMonitor());
        return workspace.getRoot().getProject(newName);
    }
}