This is an automated email from the ASF dual-hosted git repository.

sagarmiglani pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-core.git


The following commit(s) were added to refs/heads/master by this push:
     new aa36fd6  SLING-12843: Sling Installer core ignores startlevel change 
of bundles (#15)
aa36fd6 is described below

commit aa36fd648262fb76b72d30f6f65e595d1c02618a
Author: ankursinglaadobe <[email protected]>
AuthorDate: Tue Jul 1 10:45:10 2025 +0530

    SLING-12843: Sling Installer core ignores startlevel change of bundles (#15)
    
    * SLING-12843: Sling Installer core ignores startlevel change of bundles
    
    This PR enhances the bundle installation logic to also consider the start 
level, ensuring that bundles with different start levels are not ignored during 
installation, even if their version match.
    
    * SLING-12843: Updated the UTs.
    
    * SLING-12843: Added some more test cases.
    
    ---------
    
    Co-authored-by: Ankur Singla <[email protected]>
---
 .../core/impl/tasks/BundleTaskCreator.java         |  53 ++++-
 .../core/impl/tasks/BundleUpdateTask.java          |   8 +-
 .../installer/core/impl/MockBundleContext.java     |  23 ++-
 .../installer/core/impl/MockBundleResource.java    |  15 +-
 .../core/impl/tasks/BundleTaskCreatorTest.java     | 215 +++++++++++++++++++++
 .../core/impl/tasks/BundleUpdateTaskTest.java      | 143 ++++++++++++++
 .../core/impl/tasks/MockBundleTaskCreator.java     |   9 +-
 7 files changed, 454 insertions(+), 12 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreator.java
 
b/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreator.java
index b52296d..5850759 100644
--- 
a/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreator.java
+++ 
b/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreator.java
@@ -37,6 +37,7 @@ import org.apache.sling.installer.core.impl.OsgiInstallerImpl;
 import org.apache.sling.installer.core.impl.PersistentResourceList;
 import org.apache.sling.installer.core.impl.RegisteredResourceImpl;
 import org.apache.sling.installer.core.impl.Util;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
 import org.osgi.framework.BundleListener;
@@ -44,6 +45,7 @@ import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.FrameworkListener;
 import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -259,6 +261,8 @@ public class BundleTaskCreator implements InternalService, 
InstallTaskFactory, F
                     }
 
                     final BundleInfo info = this.getBundleInfo(symbolicName, 
bundleVersion);
+                    final int currentStartLevel = 
getCurrentBundleStartLevel(info);
+                    final int newStartLevel = 
getNewBundleStartLevel(toActivate);
 
                     // check if we should start the bundle as we installed it 
in the previous run
                     if (info == null) {
@@ -281,7 +285,8 @@ public class BundleTaskCreator implements InternalService, 
InstallTaskFactory, F
                                 logger.debug("Bundle " + info.symbolicName + " 
" + newVersion
                                         + " is not installed, bundle with 
higher version is already installed.");
                             }
-                        } else if (compare == 0 && 
BundleInfo.isSnapshot(newVersion)) {
+                        } else if (compare == 0
+                                && (BundleInfo.isSnapshot(newVersion) || 
currentStartLevel != newStartLevel)) {
 
                             // installed, same version but SNAPSHOT
                             doUpdate = true;
@@ -324,4 +329,50 @@ public class BundleTaskCreator implements InternalService, 
InstallTaskFactory, F
     protected BundleInfo getBundleInfo(final String symbolicName, final String 
version) {
         return BundleInfo.getBundleInfo(this.bundleContext, symbolicName, 
version);
     }
+
+    /**
+     * Gets the current start level of a bundle.
+     *
+     * @param info the bundle info, may be null if bundle is not installed
+     * @return the current start level, or 0 if not available
+     */
+    private int getCurrentBundleStartLevel(final BundleInfo info) {
+        final int FALLBACK_START_LEVEL = 0;
+        if (info == null) {
+            return FALLBACK_START_LEVEL;
+        }
+
+        final Bundle currentBundle = this.bundleContext.getBundle(info.id);
+        if (currentBundle == null) {
+            return FALLBACK_START_LEVEL;
+        }
+
+        final BundleStartLevel bundleStartLevel = 
currentBundle.adapt(BundleStartLevel.class);
+        if (bundleStartLevel == null) {
+            return FALLBACK_START_LEVEL;
+        }
+
+        return bundleStartLevel.getStartLevel();
+    }
+
+    /**
+     * Gets the new start level for a bundle from the installation hint.
+     *
+     * @param toActivate the task resource containing installation hints
+     * @return the new start level
+     */
+    private int getNewBundleStartLevel(final TaskResource toActivate) {
+        final int FALLBACK_START_LEVEL = 0;
+        final Object installationHint = 
toActivate.getDictionary().get(InstallableResource.INSTALLATION_HINT);
+        if (installationHint == null) {
+            return FALLBACK_START_LEVEL;
+        }
+
+        try {
+            return Integer.parseInt(installationHint.toString());
+        } catch (NumberFormatException e) {
+            logger.warn("Invalid installation hint value '{}' for bundle {}", 
installationHint, toActivate.getURL());
+            return FALLBACK_START_LEVEL;
+        }
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java
 
b/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java
index 20543b6..ad732b8 100644
--- 
a/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java
+++ 
b/src/main/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTask.java
@@ -79,7 +79,10 @@ public class BundleUpdateTask extends AbstractBundleTask {
         boolean snapshot = false;
         final Version currentVersion = b.getVersion();
         snapshot = BundleInfo.isSnapshot(newVersion);
-        if (currentVersion.equals(newVersion) && !snapshot) {
+        final BundleStartLevel startLevelService = 
b.adapt(BundleStartLevel.class);
+        final int newStartLevel = this.getBundleStartLevel();
+        final int oldStartLevel = startLevelService.getStartLevel();
+        if (currentVersion.equals(newVersion) && !snapshot && newStartLevel == 
oldStartLevel) {
             // TODO : Isn't this already checked in the task creator?
             String message = MessageFormat.format(
                     "Same version is already installed, and not a snapshot, 
ignoring update: {0}", getResource());
@@ -105,9 +108,6 @@ public class BundleUpdateTask extends AbstractBundleTask {
             setBundleLocation(getResource(), b.getLocation());
             // start level handling - after update to avoid starting the bundle
             // just before the update
-            final BundleStartLevel startLevelService = 
b.adapt(BundleStartLevel.class);
-            final int newStartLevel = this.getBundleStartLevel();
-            final int oldStartLevel = startLevelService.getStartLevel();
             if (newStartLevel != oldStartLevel && newStartLevel != 0) {
                 startLevelService.setStartLevel(newStartLevel);
                 ctx.log("Set start level for bundle {} to {}", b, 
newStartLevel);
diff --git 
a/src/test/java/org/apache/sling/installer/core/impl/MockBundleContext.java 
b/src/test/java/org/apache/sling/installer/core/impl/MockBundleContext.java
index 4f3d145..5b9bf27 100644
--- a/src/test/java/org/apache/sling/installer/core/impl/MockBundleContext.java
+++ b/src/test/java/org/apache/sling/installer/core/impl/MockBundleContext.java
@@ -22,9 +22,11 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Dictionary;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.Map;
 
 import org.osgi.framework.Bundle;
@@ -43,6 +45,18 @@ import org.osgi.framework.Version;
 
 public class MockBundleContext implements BundleContext {
 
+    private final List<Bundle> bundles = new ArrayList<>();
+
+    public MockBundleContext() {
+        // Default constructor
+    }
+
+    public MockBundleContext(final List<Bundle> bundleList) {
+        if (bundleList != null) {
+            this.bundles.addAll(bundleList);
+        }
+    }
+
     @Override
     public boolean ungetService(ServiceReference reference) {
         // TODO Auto-generated method stub
@@ -134,13 +148,16 @@ public class MockBundleContext implements BundleContext {
 
     @Override
     public Bundle[] getBundles() {
-        // TODO Auto-generated method stub
-        return null;
+        return this.bundles.toArray(new Bundle[0]);
     }
 
     @Override
     public Bundle getBundle(long id) {
-        // TODO Auto-generated method stub
+        for (Bundle bundle : this.bundles) {
+            if (bundle.getBundleId() == id) {
+                return bundle;
+            }
+        }
         return null;
     }
 
diff --git 
a/src/test/java/org/apache/sling/installer/core/impl/MockBundleResource.java 
b/src/test/java/org/apache/sling/installer/core/impl/MockBundleResource.java
index 602e005..fd61462 100644
--- a/src/test/java/org/apache/sling/installer/core/impl/MockBundleResource.java
+++ b/src/test/java/org/apache/sling/installer/core/impl/MockBundleResource.java
@@ -21,6 +21,7 @@ package org.apache.sling.installer.core.impl;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Dictionary;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
@@ -44,6 +45,7 @@ public class MockBundleResource implements TaskResource, 
Comparable<MockBundleRe
     private ResourceState state = ResourceState.INSTALL;
     private final String digest;
     private final int priority;
+    private final Dictionary<String, Object> dictionary = new Hashtable<>();
 
     public MockBundleResource(String symbolicName, String version) {
         this(symbolicName, version, InstallableResource.DEFAULT_PRIORITY);
@@ -63,6 +65,15 @@ public class MockBundleResource implements TaskResource, 
Comparable<MockBundleRe
         this.priority = priority;
     }
 
+    public void setDictionary(Dictionary<String, Object> dictionary) {
+        Enumeration<String> keys = dictionary.keys();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+            Object value = dictionary.get(key);
+            this.dictionary.put(key, value);
+        }
+    }
+
     @Override
     public String toString() {
         return getClass().getSimpleName()
@@ -83,7 +94,7 @@ public class MockBundleResource implements TaskResource, 
Comparable<MockBundleRe
      * @see 
org.apache.sling.installer.api.tasks.RegisteredResource#getDictionary()
      */
     public Dictionary<String, Object> getDictionary() {
-        return null;
+        return dictionary;
     }
 
     /**
@@ -211,7 +222,7 @@ public class MockBundleResource implements TaskResource, 
Comparable<MockBundleRe
         final InstallableResource is = new InstallableResource(
                 (String) this.attributes.get(Constants.BUNDLE_SYMBOLICNAME),
                 null,
-                new Hashtable<String, Object>(),
+                getDictionary(),
                 this.getDigest(),
                 this.getType(),
                 this.getPriority());
diff --git 
a/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreatorTest.java
 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreatorTest.java
index f345802..fb3cea7 100644
--- 
a/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreatorTest.java
+++ 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleTaskCreatorTest.java
@@ -19,10 +19,15 @@
 package org.apache.sling.installer.core.impl.tasks;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
+import org.apache.sling.installer.api.InstallableResource;
 import org.apache.sling.installer.api.tasks.ChangeStateTask;
 import org.apache.sling.installer.api.tasks.InstallTask;
 import org.apache.sling.installer.api.tasks.ResourceState;
@@ -30,11 +35,14 @@ import org.apache.sling.installer.api.tasks.TaskResource;
 import org.apache.sling.installer.core.impl.EntityResourceList;
 import org.apache.sling.installer.core.impl.MockBundleResource;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.startlevel.BundleStartLevel;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
 
 public class BundleTaskCreatorTest {
     public static final String SN = "TestSymbolicName";
@@ -205,4 +213,211 @@ public class BundleTaskCreatorTest {
                     t.getResource().getEntityId());
         }
     }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged() throws IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 19);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_BundleInfoNull() throws 
IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 19);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            // Don't add bundle info, so getBundleInfo will return null
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleInstallTask", s.first() instanceof 
BundleInstallTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_BundleNotFoundInContext() 
throws IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 19);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(); // 
Empty bundle list
+            c.addBundleInfo(SN, "1.0", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            // When bundle info exists with same version, it creates a 
ChangeStateTask
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void 
testBundleUpgradeStartLevelChanged_BundleStartLevelServiceNull() throws 
IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 19);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            // Simulate bundle found but no BundleStartLevel service available
+            bundles.add(getMockBundle(bundleId, SN, null));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_NoInstallationHint() throws 
IOException {
+        final TaskResource[] r = {new MockBundleResource(SN, "1.1")};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_InstallationHintNull() 
throws IOException {
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(new Hashtable<>());
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_InvalidInstallationHint() 
throws IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, "NOT_A_NUMBER");
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpgradeStartLevelChanged_InstallationHintAsString() 
throws IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, "19");
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpdateWithStartLevelChange_CurrentLowerThanNew() 
throws IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 20);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 19));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a BundleUpdateTask", s.first() instanceof 
BundleUpdateTask);
+        }
+    }
+
+    @Test
+    public void testBundleUpdateWithStartLevelChange_SameStartLevel() throws 
IOException {
+        Dictionary<String, Object> dictionary = new Hashtable<>();
+        dictionary.put(InstallableResource.INSTALLATION_HINT, 20);
+        final MockBundleResource resource = new MockBundleResource(SN, "1.1");
+        resource.setDictionary(dictionary);
+        final TaskResource[] r = {resource};
+
+        {
+            final long bundleId = 1L;
+            List<Bundle> bundles = new ArrayList<>();
+            bundles.add(getMockBundle(bundleId, SN, 20));
+            final MockBundleTaskCreator c = new MockBundleTaskCreator(bundles);
+            c.addBundleInfo(SN, "1.1", Bundle.ACTIVE);
+            final SortedSet<InstallTask> s = getTasks(r, c);
+            assertEquals("Expected one task", 1, s.size());
+            assertTrue("Expected a ChangeStateTask", s.first() instanceof 
ChangeStateTask);
+        }
+    }
+
+    private Bundle getMockBundle(long bundleId, String symbolicName, Integer 
startLevel) {
+        // Create a mock bundle with the specified symbolic name and start 
level
+        Bundle bundle = Mockito.mock(Bundle.class);
+        when(bundle.getSymbolicName()).thenReturn(symbolicName);
+        when(bundle.getBundleId()).thenReturn(bundleId);
+        when(bundle.getState()).thenReturn(Bundle.ACTIVE);
+        if (startLevel != null) {
+            BundleStartLevel bundleStartLevel = 
Mockito.mock(BundleStartLevel.class);
+            when(bundleStartLevel.getStartLevel()).thenReturn(startLevel);
+            
when(bundle.adapt(BundleStartLevel.class)).thenReturn(bundleStartLevel);
+        }
+        return bundle;
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTaskTest.java
 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTaskTest.java
new file mode 100644
index 0000000..28a3908
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/BundleUpdateTaskTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.installer.core.impl.tasks;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.installer.api.tasks.InstallationContext;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.core.impl.EntityResourceList;
+import org.apache.sling.installer.core.impl.MockBundleResource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BundleUpdateTaskTest {
+
+    private static final String BUNDLE_SYMBOLIC_NAME = "test.bundle";
+    private static final String BUNDLE_VERSION = "1.1.0";
+
+    @Mock
+    private BundleContext bundleContext;
+
+    @Mock
+    private Bundle bundle;
+
+    @Mock
+    private BundleStartLevel bundleStartLevel;
+
+    @Mock
+    private InstallationContext installationContext;
+
+    @Mock
+    private TaskSupport taskSupport;
+
+    @Test
+    public void testBundleUpdateExecute_BundleNotFound() throws Exception {
+        List<Bundle> bundles = new ArrayList<>();
+        bundles.add(bundle);
+        // Setup bundle context
+        when(bundleContext.getBundles()).thenReturn(bundles.toArray(new 
Bundle[0]));
+        // Setup task support
+        when(taskSupport.getBundleContext()).thenReturn(bundleContext);
+        // Setup resource with proper InputStream
+        MockBundleResource resource = new 
MockBundleResource(BUNDLE_SYMBOLIC_NAME, BUNDLE_VERSION) {
+            @Override
+            public InputStream getInputStream() throws IOException {
+                return new ByteArrayInputStream("test bundle 
content".getBytes());
+            }
+        };
+        EntityResourceList resourceList =
+                new EntityResourceList(resource.getEntityId(), new 
MockInstallationListener());
+        try {
+            resourceList.addOrUpdate(resource.getRegisteredResourceImpl());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Create task
+        BundleUpdateTask task = new BundleUpdateTask(resourceList, 
taskSupport);
+        // Given: Bundle not found in context
+        when(bundleContext.getBundles()).thenReturn(new Bundle[0]);
+
+        // When: Execute task
+        task.execute(installationContext);
+
+        // Then: Task should be ignored
+        assertEquals(ResourceState.IGNORED, 
resourceList.getFirstResource().getState());
+        assertTrue(resourceList
+                .getFirstResource()
+                .getError()
+                .contains("Bundle to update (" + BUNDLE_SYMBOLIC_NAME + ") not 
found"));
+    }
+
+    @Test
+    public void testBundleUpdateExecute_SameVersionNonSnapshotSameStartLevel() 
throws Exception {
+        List<Bundle> bundles = new ArrayList<>();
+        // Setup basic bundle mock
+        when(bundle.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC_NAME);
+        when(bundle.getVersion()).thenReturn(new Version(BUNDLE_VERSION));
+        
when(bundle.adapt(BundleStartLevel.class)).thenReturn(bundleStartLevel);
+        bundles.add(bundle);
+        // Setup bundle context
+        when(bundleContext.getBundles()).thenReturn(bundles.toArray(new 
Bundle[0]));
+        // Setup task support
+        when(taskSupport.getBundleContext()).thenReturn(bundleContext);
+        // Setup resource with proper InputStream
+        MockBundleResource resource = new 
MockBundleResource(BUNDLE_SYMBOLIC_NAME, BUNDLE_VERSION) {
+            @Override
+            public InputStream getInputStream() throws IOException {
+                return new ByteArrayInputStream("test bundle 
content".getBytes());
+            }
+        };
+        EntityResourceList resourceList =
+                new EntityResourceList(resource.getEntityId(), new 
MockInstallationListener());
+        try {
+            resourceList.addOrUpdate(resource.getRegisteredResourceImpl());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Create task
+        BundleUpdateTask task = new BundleUpdateTask(resourceList, 
taskSupport);
+
+        // When: Execute task
+        task.execute(installationContext);
+
+        assertEquals(ResourceState.INSTALLED, 
resourceList.getFirstResource().getState());
+        assertTrue(resourceList
+                .getFirstResource()
+                .getError()
+                .contains("Same version is already installed, and not a 
snapshot, ignoring update"));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/installer/core/impl/tasks/MockBundleTaskCreator.java
 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/MockBundleTaskCreator.java
index a53d341..7acd843 100644
--- 
a/src/test/java/org/apache/sling/installer/core/impl/tasks/MockBundleTaskCreator.java
+++ 
b/src/test/java/org/apache/sling/installer/core/impl/tasks/MockBundleTaskCreator.java
@@ -18,11 +18,12 @@
  */
 package org.apache.sling.installer.core.impl.tasks;
 
-import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.sling.installer.core.impl.MockBundleContext;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.Version;
 
 /** BundleTaskCreator that simulates the presence and state of bundles */
@@ -30,10 +31,14 @@ class MockBundleTaskCreator extends BundleTaskCreator {
 
     private final Map<String, BundleInfo> fakeBundleInfo = new HashMap<String, 
BundleInfo>();
 
-    public MockBundleTaskCreator() throws IOException {
+    public MockBundleTaskCreator() {
         this.init(new MockBundleContext(), null, null);
     }
 
+    public MockBundleTaskCreator(final List<Bundle> bundles) {
+        this.init(new MockBundleContext(bundles), null, null);
+    }
+
     void addBundleInfo(String symbolicName, String version, int state) {
         fakeBundleInfo.put(symbolicName, new BundleInfo(symbolicName, new 
Version(version), state, 1));
     }

Reply via email to