You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2017/06/15 05:59:36 UTC

karaf git commit: [KARAF-5104] Add feature support in karaf:run mojo

Repository: karaf
Updated Branches:
  refs/heads/karaf-4.1.x 525c99ad9 -> de932d334


[KARAF-5104] Add feature support in karaf:run mojo


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/de932d33
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/de932d33
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/de932d33

Branch: refs/heads/karaf-4.1.x
Commit: de932d3348c20f4cd143ff560612658e93f55aa7
Parents: 525c99a
Author: Steinar Bang <sb...@dod.no>
Authored: Sat Jun 10 20:44:10 2017 +0200
Committer: Jean-Baptiste Onofré <jb...@apache.org>
Committed: Thu Jun 15 07:40:35 2017 +0200

----------------------------------------------------------------------
 .../java/org/apache/karaf/tooling/RunMojo.java  | 106 ++++-
 .../org/apache/karaf/tooling/RunMojoTest.java   | 418 +++++++++++++++++++
 2 files changed, 517 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/de932d33/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
index 16023bf..be62836 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
@@ -40,12 +40,16 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.util.tracker.ServiceTracker;
 
 import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -74,6 +78,19 @@ public class RunMojo extends MojoSupport {
     private boolean deployProjectArtifact = true;
 
     /**
+     * A list of URLs referencing feature repositories that will be added
+     * to the karaf instance started by this goal.
+     */
+    @Parameter
+    private String[] featureRepositories = null;
+
+    /**
+     * Comma-separated list of features to install.
+     */
+    @Parameter(defaultValue = "")
+    private String featuresToInstall = null;
+
+    /**
      * Define if the Karaf container keep running or stop just after the goal execution
      */
     @Parameter(defaultValue = "true")
@@ -124,7 +141,11 @@ public class RunMojo extends MojoSupport {
                     bootFinished = bundleContext.getService(ref);
                 }
             }
-            deploy(bundleContext);
+
+            Object featureService = findFeatureService(bundleContext);
+            addFeatureRepositories(featureService);
+            deploy(bundleContext, featureService);
+            addFeatures(featureService);
             if (keepRunning)
                 main.awaitShutdown();
             main.destroy();
@@ -135,10 +156,28 @@ public class RunMojo extends MojoSupport {
         }
     }
 
-    private void deploy(BundleContext bundleContext) throws MojoExecutionException {
+    void addFeatureRepositories(Object featureService) throws MojoExecutionException {
+    	if (featureRepositories != null) {
+            try {
+            	Class<? extends Object> serviceClass = featureService.getClass();
+                Method addRepositoryMethod = serviceClass.getMethod("addRepository", URI.class);
+
+                for (String featureRepo : featureRepositories) {
+                    addRepositoryMethod.invoke(featureService, URI.create(featureRepo));
+                }
+            } catch (Exception e) {
+                throw new MojoExecutionException("Failed to add feature repositories to karaf", e);
+            }
+    	}
+    }
+
+    void deploy(BundleContext bundleContext, Object featureService) throws MojoExecutionException {
         if (deployProjectArtifact) {
             File artifact = project.getArtifact().getFile();
-            if (artifact != null && artifact.exists()) {
+            File attachedFeatureFile = getAttachedFeatureFile(project);
+            boolean artifactExists = artifact != null && artifact.exists();
+            boolean attachedFeatureFileExists = attachedFeatureFile != null && attachedFeatureFile.exists();
+            if (artifactExists) {
                 if (project.getPackaging().equals("bundle")) {
                     try {
                         Bundle bundle = bundleContext.installBundle(artifact.toURI().toURL().toString());
@@ -149,12 +188,30 @@ public class RunMojo extends MojoSupport {
                 } else {
                     throw new MojoExecutionException("Packaging " + project.getPackaging() + " is not supported");
                 }
+            } else if (attachedFeatureFileExists) {
+                addFeaturesAttachmentAsFeatureRepository(featureService, attachedFeatureFile);
             } else {
                 throw new MojoExecutionException("Project artifact doesn't exist");
             }
         }
     }
 
+    void addFeatures(Object featureService) throws MojoExecutionException {
+    	if (featuresToInstall != null) {
+            try {
+            	Class<? extends Object> serviceClass = featureService.getClass();
+                Method installFeatureMethod = serviceClass.getMethod("installFeature", String.class);
+                String[] features = featuresToInstall.split(" *, *");
+                for (String feature : features) {
+                    installFeatureMethod.invoke(featureService, feature);
+                    Thread.sleep(1000L);
+                }
+            } catch (Exception e) {
+                throw new MojoExecutionException("Failed to add features to karaf", e);
+            }
+    	}
+    }
+
     public static void extract(File sourceFile, File targetFolder) throws IOException {
         if (sourceFile.getAbsolutePath().indexOf(".zip") > 0) {
             extractZipDistribution(sourceFile, targetFolder);
@@ -166,16 +223,14 @@ public class RunMojo extends MojoSupport {
         return;
     }
 
-    private static void extractTarGzDistribution(File sourceDistribution, File _targetFolder)
-            throws IOException {
+    private static void extractTarGzDistribution(File sourceDistribution, File _targetFolder) throws IOException {
         File uncompressedFile = File.createTempFile("uncompressedTarGz-", ".tar");
         extractGzArchive(new FileInputStream(sourceDistribution), uncompressedFile);
         extract(new TarArchiveInputStream(new FileInputStream(uncompressedFile)), _targetFolder);
         FileUtils.forceDelete(uncompressedFile);
     }
 
-    private static void extractZipDistribution(File sourceDistribution, File _targetFolder)
-            throws IOException {
+    private static void extractZipDistribution(File sourceDistribution, File _targetFolder) throws IOException {
         extract(new ZipArchiveInputStream(new FileInputStream(sourceDistribution)), _targetFolder);
     }
 
@@ -301,4 +356,41 @@ public class RunMojo extends MojoSupport {
         return part != null && !part.isEmpty();
     }
 
+    private File getAttachedFeatureFile(MavenProject project) {
+        List<Artifact> attachedArtifacts = project.getAttachedArtifacts();
+        for (Artifact artifact : attachedArtifacts) {
+            if ("features".equals(artifact.getClassifier()) && "xml".equals(artifact.getType())) {
+                return artifact.getFile();
+            }
+        }
+
+        return null;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" }) Object findFeatureService(BundleContext bundleContext) {
+        // Use Object as the service type and use reflection when calling the service,
+    	// because the returned services use the OSGi classloader
+    	ServiceReference ref = bundleContext.getServiceReference(FeaturesService.class);
+        if (ref != null) {
+            Object featureService = bundleContext.getService(ref);
+            return featureService;
+        }
+
+        return null;
+    }
+
+    private void addFeaturesAttachmentAsFeatureRepository(Object featureService, File attachedFeatureFile) throws MojoExecutionException {
+        if (featureService != null) {
+            try {
+            	Class<? extends Object> serviceClass = featureService.getClass();
+            	Method addRepositoryMethod = serviceClass.getMethod("addRepository", URI.class);
+                addRepositoryMethod.invoke(featureService, attachedFeatureFile.toURI());
+            } catch (Exception e) {
+                throw new MojoExecutionException("Failed to register attachment as feature repository", e);
+            }
+        } else {
+            throw new MojoExecutionException("Failed to find the FeatureService when adding a feature repository");
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/de932d33/tooling/karaf-maven-plugin/src/test/java/org/apache/karaf/tooling/RunMojoTest.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/test/java/org/apache/karaf/tooling/RunMojoTest.java b/tooling/karaf-maven-plugin/src/test/java/org/apache/karaf/tooling/RunMojoTest.java
new file mode 100644
index 0000000..f63ac72
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/test/java/org/apache/karaf/tooling/RunMojoTest.java
@@ -0,0 +1,418 @@
+package org.apache.karaf.tooling;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.URI;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import static org.easymock.EasyMock.*;
+
+import org.easymock.EasyMock;
+import org.easymock.EasyMockSupport;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+
+public class RunMojoTest extends EasyMockSupport {
+
+    @Test
+    public void testAddFeatureRepositoriesWithNullRepoList() throws MojoExecutionException {
+        FeaturesService featureService = mock(FeaturesService.class);
+        replay(featureService);
+
+        RunMojo mojo = new RunMojo();
+        mojo.addFeatureRepositories(featureService);
+        verify(featureService); // Non-nice easymock mock will fail on any call
+    }
+
+    @Test
+    public void testAddFeatureRepositoriesWithEmptyRepoListAndNullFeatureService() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, MojoExecutionException  {
+        RunMojo mojo = new RunMojo();
+        String[] empty = new String[0];
+        setPrivateField(mojo, "featureRepositories", empty);
+        try {
+            mojo.addFeatureRepositories(null);
+            fail("Expected MojoExecutionException to be thrown");
+        } catch(MojoExecutionException e) {
+            assertEquals("Failed to add feature repositories to karaf", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testAddFeatureRepositoriesWithEmptyRepoList() throws MojoExecutionException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        FeaturesService featureService = mock(FeaturesService.class);
+        replay(featureService);
+
+        RunMojo mojo = new RunMojo();
+        String[] empty = new String[0];
+        setPrivateField(mojo, "featureRepositories", empty);
+        mojo.addFeatureRepositories(featureService);
+        verify(featureService); // Non-nice easymock mock will fail on any call
+    }
+
+    @Test
+    public void testAddFeatureRepositories() throws Exception {
+        FeaturesService featureService = niceMock(FeaturesService.class);
+        featureService.addRepository(anyObject(URI.class));
+        EasyMock.expectLastCall().once();
+        replay(featureService);
+
+        RunMojo mojo = new RunMojo();
+        String[] features = { "liquibase-core", "ukelonn" };
+        setPrivateField(mojo, "featureRepositories", features);
+        mojo.addFeatureRepositories(featureService);
+        verify(featureService);
+    }
+
+    @Test
+    public void testDeployWithDeployProjectArtifactFalse() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, MojoExecutionException {
+        BundleContext context = mock(BundleContext.class);
+        RunMojo mojo = new RunMojo();
+        setPrivateField(mojo, "deployProjectArtifact", false);
+        mojo.deploy(context, null);
+    }
+
+    @Test
+    public void testDeployWithNullArtifact() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        BundleContext context = mock(BundleContext.class);
+        Artifact artifact = mock(Artifact.class);
+        RunMojo mojo = new RunMojo();
+        MavenProject project = new MavenProject();
+        project.setArtifact(artifact);
+        setInheritedPrivateField(mojo, "project", project);
+        try {
+            mojo.deploy(context, null);
+            fail("Expected MojoExecutionException");
+        } catch (MojoExecutionException e) {
+            assertEquals("Project artifact doesn't exist", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDeployWithNonExistingArtifact() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        BundleContext context = mock(BundleContext.class);
+        Artifact artifact = mock(Artifact.class);
+        File artifactFile = mock(File.class);
+        expect(artifact.getFile()).andReturn(artifactFile);
+        replay(artifact);
+        RunMojo mojo = new RunMojo();
+        MavenProject project = new MavenProject();
+        project.setArtifact(artifact);
+        setInheritedPrivateField(mojo, "project", project);
+        try {
+            mojo.deploy(context, null);
+            fail("Expected MojoExecutionException");
+        } catch (MojoExecutionException e) {
+            assertEquals("Project artifact doesn't exist", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDeployWithExistingArtifactButProjectNotBundle() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        BundleContext context = mock(BundleContext.class);
+        Artifact artifact = mock(Artifact.class);
+        File artifactFile = mock(File.class);
+        expect(artifactFile.exists()).andReturn(true);
+        replay(artifactFile);
+        expect(artifact.getFile()).andReturn(artifactFile);
+        replay(artifact);
+        RunMojo mojo = new RunMojo();
+        MavenProject project = new MavenProject();
+        project.setArtifact(artifact);
+        setInheritedPrivateField(mojo, "project", project);
+        try {
+            mojo.deploy(context, null);
+            fail("Expected MojoExecutionException");
+        } catch (MojoExecutionException e) {
+            assertEquals("Packaging jar is not supported", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDeployWithExistingArtifactFailsInInstall() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        BundleContext context = mock(BundleContext.class);
+        Artifact artifact = mock(Artifact.class);
+        File artifactFile = niceMock(File.class);
+        expect(artifactFile.exists()).andReturn(true);
+        replay(artifactFile);
+        expect(artifact.getFile()).andReturn(artifactFile);
+        replay(artifact);
+        RunMojo mojo = new RunMojo();
+        MavenProject project = new MavenProject();
+        project.setPackaging("bundle");
+        project.setArtifact(artifact);
+        setInheritedPrivateField(mojo, "project", project);
+        try {
+            mojo.deploy(context, null);
+            fail("Expected MojoExecutionException");
+        } catch (MojoExecutionException e) {
+            assertEquals("Can't deploy project artifact in container", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDeployWithExistingArtifact() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, IOException, BundleException, MojoExecutionException {
+        BundleContext context = niceMock(BundleContext.class);
+        Bundle bundle = niceMock(Bundle.class);
+        expect(context.installBundle(anyString())).andReturn(bundle);
+        replay(context);
+        Artifact artifact = mock(Artifact.class);
+        File artifactFile = File.createTempFile("fake-bundle", ".jar");
+        try {
+            expect(artifact.getFile()).andReturn(artifactFile);
+            replay(artifact);
+            RunMojo mojo = new RunMojo();
+            MavenProject project = new MavenProject();
+            project.setPackaging("bundle");
+            project.setArtifact(artifact);
+            setInheritedPrivateField(mojo, "project", project);
+            replay(bundle);
+            mojo.deploy(context, null);
+            verify(bundle);
+        } finally {
+            artifactFile.delete();
+        }
+    }
+
+    @Test
+    public void testDeployWithPomArtifactAndAttachedFeatureXmlNoFeatureService() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, IOException, BundleException, MojoExecutionException {
+        File artifactFeaturesAttachmentFile = File.createTempFile("someproject-features", ".xml");
+        try {
+            BundleContext context = niceMock(BundleContext.class);
+            Bundle bundle = niceMock(Bundle.class);
+            expect(context.installBundle(anyString())).andReturn(bundle);
+            replay(context);
+            Artifact artifact = niceMock(Artifact.class);
+            replay(artifact);
+            Artifact artifactFeaturesAttachment = mock(Artifact.class);
+            expect(artifactFeaturesAttachment.getFile()).andReturn(artifactFeaturesAttachmentFile);
+            expect(artifactFeaturesAttachment.getClassifier()).andReturn("features");
+            expect(artifactFeaturesAttachment.getType()).andReturn("xml");
+            replay(artifactFeaturesAttachment);
+
+            RunMojo mojo = new RunMojo();
+            MavenProject project = new MavenProject();
+            project.setPackaging("pom");
+            project.setArtifact(artifact);
+            project.addAttachedArtifact(artifactFeaturesAttachment);
+            setInheritedPrivateField(mojo, "project", project);
+            replay(bundle);
+            try {
+                mojo.deploy(context, null);
+                fail("Expected MojoExecutionException");
+            } catch (MojoExecutionException e) {
+                assertEquals("Failed to find the FeatureService when adding a feature repository", e.getMessage());
+            }
+        } finally {
+            artifactFeaturesAttachmentFile.delete();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testDeployWithPomArtifactAndAttachedFeatureXmlRepoRegistrationFails() throws Exception {
+        File artifactFeaturesAttachmentFile = File.createTempFile("someproject-features", ".xml");
+        try {
+            FeaturesService featureService = niceMock(FeaturesService.class);
+            featureService.addRepository(anyObject(URI.class));
+            EasyMock.expectLastCall().andThrow(new Exception("Not a feature repository"));
+            replay(featureService);
+            ServiceReference<FeaturesService> ref = niceMock(ServiceReference.class);
+            BundleContext context = niceMock(BundleContext.class);
+            expect(context.getServiceReference(eq(FeaturesService.class))).andReturn(ref);
+            expect(context.getService(eq(ref))).andReturn(featureService);
+            replay(context);
+            Artifact artifact = niceMock(Artifact.class);
+            replay(artifact);
+            Artifact artifactFeaturesAttachment = mock(Artifact.class);
+            expect(artifactFeaturesAttachment.getFile()).andReturn(artifactFeaturesAttachmentFile);
+            expect(artifactFeaturesAttachment.getClassifier()).andReturn("features");
+            expect(artifactFeaturesAttachment.getType()).andReturn("xml");
+            replay(artifactFeaturesAttachment);
+
+            RunMojo mojo = new RunMojo();
+            MavenProject project = new MavenProject();
+            project.setPackaging("pom");
+            project.setArtifact(artifact);
+            project.addAttachedArtifact(artifactFeaturesAttachment);
+            setInheritedPrivateField(mojo, "project", project);
+            try {
+                mojo.deploy(context, featureService);
+                fail("Expected MojoExecutionException");
+            } catch (MojoExecutionException e) {
+                assertEquals("Failed to register attachment as feature repository", e.getMessage());
+            }
+        } finally {
+            artifactFeaturesAttachmentFile.delete();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testDeployWithPomArtifactAndAttachedFeatureXmlAndNoFeatures() throws Exception {
+        File artifactFeaturesAttachmentFile = File.createTempFile("someproject-features", ".xml");
+        try {
+            FeaturesService featureService = niceMock(FeaturesService.class);
+            replay(featureService);
+            ServiceReference<FeaturesService> ref = niceMock(ServiceReference.class);
+            BundleContext context = niceMock(BundleContext.class);
+            expect(context.getServiceReference(eq(FeaturesService.class))).andReturn(ref);
+            expect(context.getService(eq(ref))).andReturn(featureService);
+            replay(context);
+            Artifact artifact = niceMock(Artifact.class);
+            replay(artifact);
+            Artifact artifactFeaturesAttachment = mock(Artifact.class);
+            expect(artifactFeaturesAttachment.getFile()).andReturn(artifactFeaturesAttachmentFile);
+            expect(artifactFeaturesAttachment.getClassifier()).andReturn("features");
+            expect(artifactFeaturesAttachment.getType()).andReturn("xml");
+            replay(artifactFeaturesAttachment);
+
+            RunMojo mojo = new RunMojo();
+            MavenProject project = new MavenProject();
+            project.setPackaging("pom");
+            project.setArtifact(artifact);
+            project.addAttachedArtifact(artifactFeaturesAttachment);
+            setInheritedPrivateField(mojo, "project", project);
+            mojo.deploy(context, featureService);
+            verify(featureService);
+        } finally {
+            artifactFeaturesAttachmentFile.delete();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testDeployWithPomArtifactAndAttachedFeatureXml() throws Exception {
+        File artifactFeaturesAttachmentFile = File.createTempFile("someproject-features", ".xml");
+        try {
+            FeaturesService featureService = niceMock(FeaturesService.class);
+            replay(featureService);
+            ServiceReference<FeaturesService> ref = niceMock(ServiceReference.class);
+            BundleContext context = niceMock(BundleContext.class);
+            expect(context.getServiceReference(eq(FeaturesService.class))).andReturn(ref);
+            expect(context.getService(eq(ref))).andReturn(featureService);
+            replay(context);
+            Artifact artifact = niceMock(Artifact.class);
+            replay(artifact);
+            Artifact artifactFeaturesAttachment = mock(Artifact.class);
+            expect(artifactFeaturesAttachment.getFile()).andReturn(artifactFeaturesAttachmentFile);
+            expect(artifactFeaturesAttachment.getClassifier()).andReturn("features");
+            expect(artifactFeaturesAttachment.getType()).andReturn("xml");
+            replay(artifactFeaturesAttachment);
+
+            RunMojo mojo = new RunMojo();
+            MavenProject project = new MavenProject();
+            project.setPackaging("pom");
+            project.setArtifact(artifact);
+            project.addAttachedArtifact(artifactFeaturesAttachment);
+            setInheritedPrivateField(mojo, "project", project);
+            setPrivateField(mojo, "featuresToInstall", "liquibase-core, ukelonn-db-derby-test, ukelonn");
+            String[] featureRepos = { "mvn:org.ops4j.pax.jdbc/pax-jdbc-features/LATEST/xml/features" };
+            setPrivateField(mojo, "featureRepositories", featureRepos);
+            mojo.deploy(context, featureService);
+            verify(featureService);
+        } finally {
+            artifactFeaturesAttachmentFile.delete();
+        }
+    }
+
+    /**
+     * Just check the string split behaviour on various feature strings.
+     */
+    @Test
+    public void testStringSplit() {
+    	String[] split1 = "liquibase-core, ukelonn-db-derby-test, ukelonn".split(" *, *");
+    	assertEquals(3, split1.length);
+    	String[] split2 = "liquibase-core".split(" *, *");
+    	assertEquals(1, split2.length);
+    	String[] split3 = " ".split(" *, *");
+    	assertEquals(1, split3.length);
+    	String[] split4 = " , ".split(" *, *");
+    	assertEquals(0, split4.length);
+    	String[] split5 = "liquibase-core, ".split(" *, *");
+    	assertEquals(1, split5.length);
+    	String[] split6 = "liquibase-core, , ".split(" *, *");
+    	assertEquals(1, split6.length);
+    }
+
+    @Test
+    public void testAddFeaturesNullFeaturesToInstall() throws MojoExecutionException {
+        FeaturesService featureService = mock(FeaturesService.class);
+        replay(featureService);
+
+        RunMojo mojo = new RunMojo();
+        mojo.addFeatures(featureService);
+        verify(featureService); // Non-nice easymock mock will fail on any call
+    }
+
+    @Test
+    public void testAddFeaturesNullFeatureService() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        RunMojo mojo = new RunMojo();
+        setPrivateField(mojo, "featuresToInstall", "liquibase-core, ukelonn-db-derby-test, ukelonn");
+
+        try {
+            mojo.addFeatures(null);
+        } catch (MojoExecutionException e) {
+            assertEquals("Failed to add features to karaf", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testAddFeatures() throws Exception {
+        FeaturesService featureService = mock(FeaturesService.class);
+        featureService.installFeature(anyString());
+        EasyMock.expectLastCall().times(3);
+        replay(featureService);
+
+        RunMojo mojo = new RunMojo();
+        setPrivateField(mojo, "featuresToInstall", "liquibase-core, ukelonn-db-derby-test, ukelonn");
+        mojo.addFeatures(featureService);
+        verify(featureService);
+    }
+
+    @Test
+    public void testFindFeatureServiceNullServiceRef() {
+        BundleContext context = niceMock(BundleContext.class);
+        replay(context);
+
+        RunMojo mojo = new RunMojo();
+        Object service = mojo.findFeatureService(context);
+        assertNull(service);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testFindFeatureService() {
+        FeaturesService featureService = niceMock(FeaturesService.class);
+        replay(featureService);
+        ServiceReference<FeaturesService> ref = niceMock(ServiceReference.class);
+        BundleContext context = niceMock(BundleContext.class);
+        expect(context.getServiceReference(eq(FeaturesService.class))).andReturn(ref);
+        expect(context.getService(eq(ref))).andReturn(featureService);
+        replay(context);
+
+        RunMojo mojo = new RunMojo();
+        Object service = mojo.findFeatureService(context);
+        assertNotNull(service);
+    }
+
+    private void setPrivateField(Object obj, String fieldName, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        Field field = obj.getClass().getDeclaredField(fieldName);
+        field.setAccessible(true);
+        field.set(obj, value);
+    }
+
+    private void setInheritedPrivateField(Object obj, String fieldName, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
+        Field field = obj.getClass().getSuperclass().getDeclaredField(fieldName);
+        field.setAccessible(true);
+        field.set(obj, value);
+    }
+
+}