Skip to content

Commit

Permalink
Add Mojos to replace "The Eclipse Test Framework"
Browse files Browse the repository at this point in the history
The Eclipse Test Framework is a way to specifically run a test inside an
eclipse installation itself against a prebuild SDK.

This implements the framework on top of maven to make it much easier to
run and setup than with the several (ant) prerequsites of the original
description provided here:

- https://wiki.eclipse.org/Platform-releng/Eclipse_Test_Framework
- https://wiki.eclipse.org/Platform-releng/Automated_Testing

as well as offer much more flexibility e.g. to run tests in a local mode
where not the SDK but a local build is used as the test base.
  • Loading branch information
laeubi committed Nov 20, 2023
1 parent f7d8923 commit 196c841
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Predicate;
import java.util.jar.JarFile;

import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.logging.Logger;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IRequirement;
Expand Down Expand Up @@ -130,12 +132,25 @@ private List<Path> resolveBundles(P2Resolver resolver) {
*/
public synchronized void addBundle(String bundleSymbolicName) {
try {
resolver.addDependency(ArtifactType.TYPE_INSTALLABLE_UNIT, bundleSymbolicName, "0.0.0");
resolver.addDependency(ArtifactType.TYPE_ECLIPSE_PLUGIN, bundleSymbolicName, "0.0.0");
needResolve = true;
} catch (IllegalArtifactReferenceException e) {
throw new TargetPlatformConfigurationException("Can't add API tools requirement", e);
}
}

/**
* Add a feature to the application
*
* @param featureId
*/
public synchronized void addFeature(String featureId) {
try {
resolver.addDependency(ArtifactType.TYPE_ECLIPSE_FEATURE, featureId, "0.0.0");
needResolve = true;
} catch (IllegalArtifactReferenceException e) {
throw new TargetPlatformConfigurationException("Can't add API tools requirement", e);
}
}

/**
Expand Down Expand Up @@ -205,6 +220,17 @@ public <T> EclipseFramework startFramework(EclipseWorkspace<T> workspace, List<S
//not installed yet...
if (Files.isDirectory(bundleFile)) {
bundle = systemBundleContext.installBundle(location);
} else if (isDirectoryBundly(bundleFile)) {
Path explodePath = workspace.getWorkDir().resolve("exploded").resolve(bundleFile.getFileName());
try {
Files.createDirectories(explodePath);
ZipUnArchiver unArchiver = new ZipUnArchiver(bundleFile.toFile());
unArchiver.setDestDirectory(explodePath.toFile());
unArchiver.extract();
} catch (IOException e) {
throw new BundleException("can't explode bundle " + bundleFile, e);
}
bundle = systemBundleContext.installBundle(explodePath.toUri().toASCIIString());
} else {
try (InputStream stream = Files.newInputStream(bundleFile)) {
bundle = systemBundleContext.installBundle(location, stream);
Expand All @@ -222,6 +248,14 @@ public <T> EclipseFramework startFramework(EclipseWorkspace<T> workspace, List<S
return new EclipseFramework(framework, configuration, this, connector);
}

private boolean isDirectoryBundly(Path bundleFile) {
try (JarFile jarFile = new JarFile(bundleFile.toFile())) {
return "dir".equals(jarFile.getManifest().getMainAttributes().getValue("Eclipse-BundleShape"));
} catch (IOException e1) {
}
return false;
}

private void setupLogging(BundleContext bundleContext) {
LogListener logListener = new LogListener() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private void addBundlesAndFeatures(Bundles bundles, Features features, EclipseAp
application.addBundle(bsn);
}
for (String feature : features.features()) {
//TODO application.addFeature(feature);
application.addFeature(feature);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
Expand Down Expand Up @@ -191,4 +193,10 @@ public boolean hasBundle(String bsn) {
return false;
}

public Bundle install(File file) throws IOException, BundleException {
try (FileInputStream stream = new FileInputStream(file)) {
return framework.getBundleContext().installBundle(file.getAbsolutePath(), stream);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public class EclipseBuildMojo extends AbstractMojo {
@Parameter
private List<String> bundles;

@Parameter
private List<String> features;

@Parameter(property = "project", readonly = true)
private MavenProject project;

Expand All @@ -101,7 +104,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
EclipseApplication application;
//TODO configureable by parameters!
Bundles bundles = new Bundles(getBundles());
Features features = new Features(Set.of());
Features features = new Features(getFeatures());
if (local) {
TargetPlatform targetPlatform = projectManager.getTargetPlatform(project).orElseThrow(
() -> new MojoFailureException("Can't get target platform for project " + project.getId()));
Expand Down Expand Up @@ -145,8 +148,16 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
}

private Set<String> getFeatures() {
Set<String> set = new HashSet<>();
if (features != null) {
set.addAll(features);
}
return set;
}

private Set<String> getBundles() {
HashSet<String> set = new HashSet<>();
Set<String> set = new HashSet<>();
set.add("org.eclipse.core.resources");
set.add("org.eclipse.core.runtime");
set.add("org.eclipse.core.jobs");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.eclipsetest;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.model.Repository;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.tycho.TargetPlatform;
import org.eclipse.tycho.core.TychoProjectManager;
import org.eclipse.tycho.osgi.framework.Bundles;
import org.eclipse.tycho.osgi.framework.EclipseApplication;
import org.eclipse.tycho.osgi.framework.EclipseApplicationManager;
import org.eclipse.tycho.osgi.framework.EclipseFramework;
import org.eclipse.tycho.osgi.framework.EclipseWorkspaceManager;
import org.eclipse.tycho.osgi.framework.Features;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;

public abstract class AbstractEclipseTestMojo extends AbstractMojo {

private static final String PARAMETER_JUNIT_REPORT_OUTPUT = "junitReportOutput";

private static final String PARAMETER_CLASSNAME = "classname";

private static final String PARAMETER_TESTPLUGINNAME = "testpluginname";

static final String PARAMETER_LOCAL = "local";

private static final String NAME = "Eclipse Test";

@Parameter()
private Repository eclipseRepository;

@Parameter(defaultValue = "false", property = "tycho.eclipsetest.skip")
private boolean skip;

@Parameter(defaultValue = "false", property = "tycho.eclipsetest.debug")
private boolean debug;

@Parameter(name = PARAMETER_CLASSNAME, alias = "test-classname", required = true)
private String classname;

@Parameter(name = PARAMETER_JUNIT_REPORT_OUTPUT)
private String junitReportOutput;

@Parameter(defaultValue = "${project.build.directory}/eclipse-test-reports/${project.artifactId}.xml")
private File resultFile;

@Parameter
private List<String> bundles;

@Parameter
private List<String> features;

/**
* Controls if the local target platform of the project should be used to
* resolve the eclipse application
*/
@Parameter(defaultValue = "false", property = "tycho.eclipsebuild.local", name = PARAMETER_LOCAL)
private boolean local;

@Parameter(property = "project", readonly = true)
private MavenProject project;

@Component
private EclipseWorkspaceManager workspaceManager;

@Component
private EclipseApplicationManager eclipseApplicationManager;

@Component
private TychoProjectManager projectManager;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
EclipseApplication application;
Bundles bundles = new Bundles(getBundles());
Features features = new Features(getFeatures());
if (local) {
TargetPlatform targetPlatform = projectManager.getTargetPlatform(project).orElseThrow(
() -> new MojoFailureException("Can't get target platform for project " + project.getId()));
application = eclipseApplicationManager.getApplication(targetPlatform, bundles, features, NAME);
} else {
application = eclipseApplicationManager.getApplication(eclipseRepository, bundles, features, NAME);
}
List<String> arguments = new ArrayList<>();
arguments.add(EclipseApplication.ARG_APPLICATION);
arguments.add(getApplication());
arguments.add(toParam(PARAMETER_TESTPLUGINNAME));
arguments.add(projectManager.getArtifactKey(project).get().getId());
arguments.add(toParam(PARAMETER_CLASSNAME));
arguments.add(classname);
if (junitReportOutput != null) {
arguments.add(toParam(PARAMETER_JUNIT_REPORT_OUTPUT));
arguments.add(junitReportOutput);
}
arguments.add("formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,"
+ resultFile.getAbsolutePath());
try (EclipseFramework framework = application.startFramework(workspaceManager
.getWorkspace(EclipseApplicationManager.getRepository(eclipseRepository).getURL(), this), arguments)) {
if (debug) {
framework.printState();
}
Bundle install = framework.install(project.getArtifact().getFile());
try {
install.start();
framework.start();
} finally {
install.uninstall();
}
} catch (BundleException e) {
throw new MojoFailureException("Can't start framework!", e);
} catch (Exception e) {
throw new MojoExecutionException(e);
}
}

private Set<String> getBundles() {
Set<String> bundles = new HashSet<String>();
bundles.add("org.eclipse.test");
return bundles;
}

private Set<String> getFeatures() {
Set<String> set = new HashSet<>();
if (features != null) {
set.addAll(features);
}
return set;
}

protected abstract String getApplication();

private static String toParam(String name) {
return "-" + name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.eclipsetest;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;

/**
* This Mojo provides the <a href=
* "https://wiki.eclipse.org/Platform-releng/Eclipse_Test_Framework">Eclipse
* Test Framework</a> to maven and is a replacement for the <a href=
* "https://wiki.eclipse.org/Platform-releng/Eclipse_Test_Framework#Headless_Testing_vs._UI_testing"><code>core-test</code></a>
* ant target.
*/
@Mojo(name = "eclipse-core-test", defaultPhase = LifecyclePhase.INTEGRATION_TEST, threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class EclipseCoreTestMojo extends AbstractEclipseTestMojo {

@Override
protected String getApplication() {
return "org.eclipse.test.coretestapplication";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.eclipsetest;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;

/**
* This Mojo provides the <a href=
* "https://wiki.eclipse.org/Platform-releng/Eclipse_Test_Framework">Eclipse
* Test Framework</a> to maven and is a replacement for the <a href=
* "https://wiki.eclipse.org/Platform-releng/Eclipse_Test_Framework#Headless_Testing_vs._UI_testing"><code>ui-test</code></a>
* ant target.
*/
@Mojo(name = "eclipse-ui-test", defaultPhase = LifecyclePhase.INTEGRATION_TEST, threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class EclipseUITestMojo extends AbstractEclipseTestMojo {

@Override
protected String getApplication() {
return "org.eclipse.test.uitestapplication";
}

}

0 comments on commit 196c841

Please sign in to comment.