You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by si...@apache.org on 2018/11/06 15:26:17 UTC

[sling-org-apache-sling-feature-analyser] branch SLING-8078 created (now a26609d)

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

simonetripodi pushed a change to branch SLING-8078
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git.


      at a26609d  SLING-8078 - New Analyser task which is able to detect Export-Package dependencies between regions

This branch includes the following new commits:

     new a26609d  SLING-8078 - New Analyser task which is able to detect Export-Package dependencies between regions

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-feature-analyser] 01/01: SLING-8078 - New Analyser task which is able to detect Export-Package dependencies between regions

Posted by si...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

simonetripodi pushed a commit to branch SLING-8078
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit a26609ddd4e6d5104b89399fad439d4559707384
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Tue Nov 6 16:25:53 2018 +0100

    SLING-8078 - New Analyser task which is able to detect Export-Package
    dependencies between regions
---
 .../task/impl/AbstractApiRegionsAnalyserTask.java  |  68 +++++++++
 .../feature/analyser/task/impl/ApiRegions.java     | 133 +++++++++++++++++
 .../analyser/task/impl/CheckApiRegions.java        | 158 +--------------------
 .../task/impl/CheckApiRegionsDependencies.java     |  71 +++++++++
 .../apache/sling/feature/scanner/PackageInfo.java  |  13 ++
 .../feature/scanner/impl/BundleDescriptorImpl.java |  17 ++-
 ...ava => AbstractApiRegionsAnalyserTaskTest.java} |  46 +++---
 .../task/impl/CheckApiRegionsDependenciesTest.java |  49 +++++++
 .../analyser/task/impl/CheckApiRegionsTest.java    | 141 +-----------------
 9 files changed, 380 insertions(+), 316 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTask.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTask.java
new file mode 100644
index 0000000..d6b1d80
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTask.java
@@ -0,0 +1,68 @@
+/*
+ * 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.feature.analyser.task.impl;
+
+import javax.json.stream.JsonParsingException;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+
+public abstract class AbstractApiRegionsAnalyserTask implements AnalyserTask {
+
+    protected static final String API_REGIONS_KEY = "api-regions";
+
+    @Override
+    public final void execute(AnalyserTaskContext ctx) throws Exception {
+        Feature feature = ctx.getFeature();
+
+        // extract and check the api-regions
+
+        Extensions extensions = feature.getExtensions();
+        Extension apiRegionsExtension = extensions.getByName(API_REGIONS_KEY);
+        if (apiRegionsExtension == null) {
+            // no need to be analyzed
+            return;
+        }
+
+        String jsonRepresentation = apiRegionsExtension.getJSON();
+        if (jsonRepresentation == null || jsonRepresentation.isEmpty()) {
+            // no need to be analyzed
+            return;
+        }
+
+        // read the api-regions and create a Sieve data structure for checks
+
+        ApiRegions apiRegions;
+        try {
+            apiRegions = ApiRegions.fromJson(jsonRepresentation);
+        } catch (JsonParsingException e) {
+            ctx.reportError("API Regions '"
+                    + jsonRepresentation
+                    + "' does not represent a valid JSON 'api-regions': "
+                    + e.getMessage());
+            return;
+        }
+
+        execute(apiRegions, ctx);
+    }
+
+    protected abstract void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/ApiRegions.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/ApiRegions.java
new file mode 100644
index 0000000..68f2570
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/ApiRegions.java
@@ -0,0 +1,133 @@
+/*
+ * 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.feature.analyser.task.impl;
+
+import java.io.StringReader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.json.Json;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParser.Event;
+
+/**
+ * A "Sieve" data structure to check exported packages
+ */
+final class ApiRegions {
+
+    private static final String NAME_KEY = "name";
+
+    private static final String EXPORTS_KEY = "exports";
+
+    public static ApiRegions fromJson(String jsonRepresentation) {
+        ApiRegions apiRegions = new ApiRegions();
+
+        // pointers
+        Event event;
+        String region = null;
+        Collection<String> apis = null;
+
+        JsonParser parser = Json.createParser(new StringReader(jsonRepresentation));
+        while (parser.hasNext()) {
+            event = parser.next();
+            if (Event.KEY_NAME == event) {
+                switch (parser.getString()) {
+                    case NAME_KEY:
+                        parser.next();
+                        region = parser.getString();
+                        break;
+
+                    case EXPORTS_KEY:
+                        apis = new LinkedList<>();
+
+                        // start array
+                        parser.next();
+
+                        while (parser.hasNext() && Event.VALUE_STRING == parser.next()) {
+                            String api = parser.getString();
+                            // skip comments
+                            if ('#' != api.charAt(0)) {
+                                apis.add(api);
+                            }
+                        }
+
+                        break;
+
+                    default:
+                        break;
+                }
+            } else if (Event.END_OBJECT == event) {
+                if (region != null && apis != null) {
+                    apiRegions.add(region, apis);
+                }
+
+                region = null;
+                apis = null;
+            }
+        }
+
+        return apiRegions;
+    }
+
+    // class members
+
+    private final Map<String, Set<String>> apis = new TreeMap<>();
+
+    // ctor
+
+    protected ApiRegions() {
+        // it should not be directly instantiated outside this package
+    }
+
+    // methods
+
+    public void add(String region, Collection<String> exportedApis) {
+        apis.computeIfAbsent(region, k -> new TreeSet<>()).addAll(exportedApis);
+    }
+
+    public Iterable<String> getRegions() {
+        return apis.keySet();
+    }
+
+    public Set<String> getApis(String region) {
+        return apis.computeIfAbsent(region, k -> Collections.emptySet());
+    }
+
+    public void remove(String packageName) {
+        apis.values().forEach(apis -> apis.remove(packageName));
+    }
+
+    public boolean isEmpty() {
+        for (Set<String> packages : apis.values()) {
+            if (!packages.isEmpty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return apis.toString().replace(',', '\n');
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
index cb0502f..6ea7f82 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
@@ -16,37 +16,16 @@
  */
 package org.apache.sling.feature.analyser.task.impl;
 
-import java.io.StringReader;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Formatter;
-import java.util.LinkedList;
-import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
 
-import javax.json.Json;
-import javax.json.stream.JsonParser;
-import javax.json.stream.JsonParser.Event;
-import javax.json.stream.JsonParsingException;
-
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Extensions;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
 
-public class CheckApiRegions implements AnalyserTask {
-
-    private static final String API_REGIONS_KEY = "api-regions";
-
-    private static final String NAME_KEY = "name";
-
-    private static final String EXPORTS_KEY = "exports";
+public class CheckApiRegions extends AbstractApiRegionsAnalyserTask {
 
     @Override
     public String getId() {
@@ -59,38 +38,8 @@ public class CheckApiRegions implements AnalyserTask {
     }
 
     @Override
-    public void execute(AnalyserTaskContext ctx) throws Exception {
-        Feature feature = ctx.getFeature();
-
-        // extract and check the api-regions
-
-        Extensions extensions = feature.getExtensions();
-        Extension apiRegionsExtension = extensions.getByName(API_REGIONS_KEY);
-        if (apiRegionsExtension == null) {
-            // no need to be analyzed
-            return;
-        }
-
-        String jsonRepresentation = apiRegionsExtension.getJSON();
-        if (jsonRepresentation == null || jsonRepresentation.isEmpty()) {
-            // no need to be analyzed
-            return;
-        }
-
-        // read the api-regions and create a Sieve data structure for checks
-
-        ApiRegions apiRegions;
-        try {
-            apiRegions = fromJson(jsonRepresentation);
-        } catch (JsonParsingException e) {
-            ctx.reportError("API Regions '"
-                    + jsonRepresentation
-                    + "' does not represent a valid JSON 'api-regions': "
-                    + e.getMessage());
-            return;
-        }
-
-        // then, for each bundle, get the Export-Package and process the packages
+    protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception {
+        // for each bundle, get the Export-Package and process the packages
 
         FeatureDescriptor featureDescriptor = ctx.getFeatureDescriptor();
         for (BundleDescriptor bundleDescriptor : featureDescriptor.getBundleDescriptors()) {
@@ -110,7 +59,7 @@ public class CheckApiRegions implements AnalyserTask {
                     Formatter formatter = new Formatter();
                     formatter.format("Region '%s' defined in feature '%s' declares %s package%s which %s not exported by any bundle:%n",
                                      region,
-                                     feature.getId(),
+                                     ctx.getFeature().getId(),
                                      apis.size(),
                                      getExtension(apis, "", "s"),
                                      getExtension(apis, "is", "are"));
@@ -130,103 +79,4 @@ public class CheckApiRegions implements AnalyserTask {
         return collection.size() > 1 ? plural : singular;
     }
 
-    private static ApiRegions fromJson(String jsonRepresentation) {
-        ApiRegions apiRegions = new ApiRegions();
-
-        // pointers
-        Event event;
-        String region = null;
-        Collection<String> apis = null;
-
-        JsonParser parser = Json.createParser(new StringReader(jsonRepresentation));
-        while (parser.hasNext()) {
-            event = parser.next();
-            if (Event.KEY_NAME == event) {
-                switch (parser.getString()) {
-                    case NAME_KEY:
-                        parser.next();
-                        region = parser.getString();
-                        break;
-
-                    case EXPORTS_KEY:
-                        apis = new LinkedList<>();
-
-                        // start array
-                        parser.next();
-
-                        while (parser.hasNext() && Event.VALUE_STRING == parser.next()) {
-                            String api = parser.getString();
-                            // skip comments
-                            if ('#' != api.charAt(0)) {
-                                apis.add(api);
-                            }
-                        }
-
-                        break;
-
-                    default:
-                        break;
-                }
-            } else if (Event.END_OBJECT == event) {
-                if (region != null && apis != null) {
-                    apiRegions.add(region, apis);
-                }
-
-                region = null;
-                apis = null;
-            }
-        }
-
-        return apiRegions;
-    }
-
-    // the Sieve data structure to check exported packages
-
-    private static final class ApiRegions {
-
-        // class members
-
-        private final Map<String, Set<String>> apis = new TreeMap<>();
-
-        // ctor
-
-        protected ApiRegions() {
-            // it should not be directly instantiated outside this package
-        }
-
-        // methods
-
-        public void add(String region, Collection<String> exportedApis) {
-            apis.computeIfAbsent(region, k -> new TreeSet<>()).addAll(exportedApis);
-        }
-
-        public Iterable<String> getRegions() {
-            return apis.keySet();
-        }
-
-        public Set<String> getApis(String region) {
-            return apis.computeIfAbsent(region, k -> Collections.emptySet());
-        }
-
-        public void remove(String packageName) {
-            apis.values().forEach(apis -> apis.remove(packageName));
-        }
-
-        public boolean isEmpty() {
-            for (Set<String> packages : apis.values()) {
-                if (!packages.isEmpty()) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return apis.toString().replace(',', '\n');
-        }
-
-    }
-
-
 }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependencies.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependencies.java
new file mode 100644
index 0000000..93dfb7c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependencies.java
@@ -0,0 +1,71 @@
+/*
+ * 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.feature.analyser.task.impl;
+
+import java.util.Set;
+
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.FeatureDescriptor;
+import org.apache.sling.feature.scanner.PackageInfo;
+import org.osgi.framework.Constants;
+
+public class CheckApiRegionsDependencies extends AbstractApiRegionsAnalyserTask {
+
+    private static final String GLOBAL_REGION_NAME = "global";
+
+    private static final String DEPRECATED_REGION_NAME = "deprecated";
+
+    @Override
+    public String getId() {
+        return API_REGIONS_KEY + "-dependencies";
+    }
+
+    @Override
+    public String getName() {
+        return "Api Regions dependecies analyser task";
+    }
+
+    @Override
+    protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception {
+        Set<String> globalApis = apiRegions.getApis(GLOBAL_REGION_NAME);
+        Set<String> deprectaedApis = apiRegions.getApis(DEPRECATED_REGION_NAME);
+
+        FeatureDescriptor featureDescriptor = ctx.getFeatureDescriptor();
+        for (BundleDescriptor bundleDescriptor : featureDescriptor.getBundleDescriptors()) {
+            for (PackageInfo packageInfo : bundleDescriptor.getExportedPackages()) {
+                String exportedPackage = packageInfo.getName();
+
+                if (globalApis.contains(exportedPackage)) {
+                    for (String uses : packageInfo.getUses()) {
+                        if (deprectaedApis.contains(uses)) {
+                            String errorMessage = String.format(
+                                    "Bundle '%s', defined in feature '%s', declares '%s' in the '%s' header which requires '%s' package that is in the 'deprecated' region",
+                                    bundleDescriptor.getArtifact().getId(),
+                                    ctx.getFeature().getId(),
+                                    exportedPackage,
+                                    Constants.EXPORT_PACKAGE,
+                                    uses);
+                            ctx.reportError(errorMessage);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java b/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
index bb7081b..9979aab 100644
--- a/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
+++ b/src/main/java/org/apache/sling/feature/scanner/PackageInfo.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sling.feature.scanner;
 
+import java.util.Collections;
+import java.util.Set;
+
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 
@@ -24,11 +27,17 @@ public class PackageInfo implements Comparable<PackageInfo> {
     private final boolean optional;
     private final String name;
     private final String version;
+    private final Set<String> uses;
 
     public PackageInfo(final String name, final String version, final boolean optional) {
+        this(name, version, optional, Collections.emptySet());
+    }
+
+    public PackageInfo(final String name, final String version, final boolean optional, Set<String> uses) {
         this.name = name;
         this.version = version;
         this.optional = optional;
+        this.uses = uses;
     }
 
     public String getName() {
@@ -57,6 +66,10 @@ public class PackageInfo implements Comparable<PackageInfo> {
         return new VersionRange(this.version);
     }
 
+    public Set<String> getUses() {
+        return uses;
+    }
+
     @Override
     public String toString() {
         return "Package " + name
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
index d933060..f8d3fe7 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
@@ -20,7 +20,10 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 import java.util.stream.Collectors;
@@ -169,9 +172,21 @@ public class BundleDescriptorImpl
                     final String resolution = entry.getDirective("resolution");
                     optional = "optional".equalsIgnoreCase(resolution);
                 }
+
+                Set<String> uses = new HashSet<>();
+                String usesAttribute = entry.getDirective("uses");
+                if (usesAttribute != null && !usesAttribute.isEmpty()) {
+                    StringTokenizer tokenizer = new StringTokenizer(usesAttribute, ",");
+                    while (tokenizer.hasMoreTokens()) {
+                        String usePackage = tokenizer.nextToken();
+                        uses.add(usePackage);
+                    }
+                }
+
                 final PackageInfo pck = new PackageInfo(entry.getName(),
                     version,
-                    optional);
+                    optional,
+                    uses);
                 pcks.add(pck);
             }
 
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTaskTest.java
similarity index 82%
copy from src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java
copy to src/test/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTaskTest.java
index bfbf446..fdb1ba8 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/AbstractApiRegionsAnalyserTaskTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.jar.Manifest;
@@ -38,11 +39,25 @@ import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
-public class CheckApiRegionsTest {
+public abstract class AbstractApiRegionsAnalyserTaskTest<T extends AbstractApiRegionsAnalyserTask> {
 
-    private AnalyserTask checkApiRegions = new CheckApiRegions();
+    protected abstract T newTask();
+
+    private AnalyserTask analyserTask;
+
+    @Before
+    public void setUp() {
+        analyserTask = newTask();
+    }
+
+    @After
+    public void tearDown() {
+        analyserTask = null;
+    }
 
     @Test
     public void testNoApiRegionsExtension() throws Exception {
@@ -70,30 +85,14 @@ public class CheckApiRegionsTest {
         assertTrue(errors.iterator().next().contains("does not represent a valid JSON 'api-regions'"));
     }
 
-    @Test
-    public void testNotValidApiRegionJson() throws Exception {
-        List<String> errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function.doesnotexist\"]}]");
-
-        assertFalse(errors.isEmpty());
-        assertTrue(errors.iterator()
-                .next()
-                .startsWith("Region 'global' defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0' declares 1 package which is not exported by any bundle"));
-    }
-
-    @Test
-    public void testValidApiRegionJson() throws Exception {
-        List<String> errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]}]");
-        assertTrue(errors.isEmpty());
-    }
-
-    private List<String> execute(String apiRegionJSON) throws Exception {
+    protected final List<String> execute(String apiRegionJSON) throws Exception {
         Extension extension = mock(Extension.class);
         when(extension.getJSON()).thenReturn(apiRegionJSON);
 
         return execute(extension);
     }
 
-    private List<String> execute(Extension extension) throws Exception {
+    protected final List<String> execute(Extension extension) throws Exception {
         Extensions extensions = mock(Extensions.class);
         when(extensions.getByName("api-regions")).thenReturn(extension);
 
@@ -108,7 +107,7 @@ public class CheckApiRegionsTest {
         AnalyserTaskContext ctx = mock(AnalyserTaskContext.class);
         when(ctx.getFeature()).thenReturn(feature);
 
-        PackageInfo packageInfo = new PackageInfo("org.osgi.util.function", "1.0", false);
+        PackageInfo packageInfo = new PackageInfo("org.osgi.util.function", "1.0", false, Collections.singleton("org.objectweb.asm"));
 
         BundleDescriptor bundleDescriptor = new TestBundleDescriptor();
         bundleDescriptor.getExportedPackages().add(packageInfo);
@@ -125,7 +124,7 @@ public class CheckApiRegionsTest {
             return null;
         }).when(ctx).reportError(anyString());
 
-        checkApiRegions.execute(ctx);
+        analyserTask.execute(ctx);
 
         return errors;
     }
@@ -149,8 +148,7 @@ public class CheckApiRegionsTest {
 
         @Override
         public Artifact getArtifact() {
-            // do nothing
-            return null;
+            return new Artifact(ArtifactId.fromMvnId("org.osgi:org.osgi.util.function:1.0.0"));
         }
 
         @Override
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependenciesTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependenciesTest.java
new file mode 100644
index 0000000..09eed5a
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsDependenciesTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.feature.analyser.task.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+public class CheckApiRegionsDependenciesTest extends AbstractApiRegionsAnalyserTaskTest<CheckApiRegionsDependencies> {
+
+    @Override
+    protected CheckApiRegionsDependencies newTask() {
+        return new CheckApiRegionsDependencies();
+    }
+
+    @Test
+    public void testNotValidApiRegionJson() throws Exception {
+        List<String> errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]},{\"name\": \"deprecated\",\"exports\": [\"org.objectweb.asm\"]}]");
+
+        assertFalse(errors.isEmpty());
+        assertTrue(errors.iterator()
+                .next()
+                .startsWith("Bundle 'org.osgi:org.osgi.util.function:1.0.0', defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0', declares 'org.osgi.util.function' in the 'Export-Package' header which requires 'org.objectweb.asm' package that is in the 'deprecated' region"));
+    }
+
+    @Test
+    public void testValidApiRegionJson() throws Exception {
+        List<String> errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]}]");
+        assertTrue(errors.isEmpty());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java
index bfbf446..ab73570 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegionsTest.java
@@ -18,56 +18,16 @@ package org.apache.sling.feature.analyser.task.impl;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
-import java.io.File;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.jar.Manifest;
 
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Extensions;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.analyser.task.AnalyserTask;
-import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
-import org.apache.sling.feature.scanner.BundleDescriptor;
-import org.apache.sling.feature.scanner.FeatureDescriptor;
-import org.apache.sling.feature.scanner.PackageInfo;
 import org.junit.Test;
 
-public class CheckApiRegionsTest {
+public class CheckApiRegionsTest extends AbstractApiRegionsAnalyserTaskTest<CheckApiRegions> {
 
-    private AnalyserTask checkApiRegions = new CheckApiRegions();
-
-    @Test
-    public void testNoApiRegionsExtension() throws Exception {
-        List<String> errors = execute((Extension) null);
-        assertTrue(errors.isEmpty());
-    }
-
-    @Test
-    public void testNullApiRegionsJSON() throws Exception {
-        List<String> errors = execute((String) null);
-        assertTrue(errors.isEmpty());
-    }
-
-    @Test
-    public void testEmptyApiRegionsJSON() throws Exception {
-        List<String> errors = execute("");
-        assertTrue(errors.isEmpty());
-    }
-
-    @Test
-    public void testApiRegionsIsNotJSON() throws Exception {
-        List<String> errors = execute("this is not a JSON string");
-
-        assertFalse(errors.isEmpty());
-        assertTrue(errors.iterator().next().contains("does not represent a valid JSON 'api-regions'"));
+    @Override
+    protected CheckApiRegions newTask() {
+        return new CheckApiRegions();
     }
 
     @Test
@@ -86,97 +46,4 @@ public class CheckApiRegionsTest {
         assertTrue(errors.isEmpty());
     }
 
-    private List<String> execute(String apiRegionJSON) throws Exception {
-        Extension extension = mock(Extension.class);
-        when(extension.getJSON()).thenReturn(apiRegionJSON);
-
-        return execute(extension);
-    }
-
-    private List<String> execute(Extension extension) throws Exception {
-        Extensions extensions = mock(Extensions.class);
-        when(extensions.getByName("api-regions")).thenReturn(extension);
-
-        Feature feature = mock(Feature.class);
-        when(feature.getId()).thenReturn(new ArtifactId("org.apache.sling.testing",
-                                                        "org.apache.sling.testing.apiregions", 
-                                                        "1.0.0",
-                                                        null,
-                                                        null));
-        when(feature.getExtensions()).thenReturn(extensions);
-
-        AnalyserTaskContext ctx = mock(AnalyserTaskContext.class);
-        when(ctx.getFeature()).thenReturn(feature);
-
-        PackageInfo packageInfo = new PackageInfo("org.osgi.util.function", "1.0", false);
-
-        BundleDescriptor bundleDescriptor = new TestBundleDescriptor();
-        bundleDescriptor.getExportedPackages().add(packageInfo);
-
-        FeatureDescriptor featureDescriptor = new TestFeatureDescriptor();
-        featureDescriptor.getBundleDescriptors().add(bundleDescriptor);
-
-        when(ctx.getFeatureDescriptor()).thenReturn(featureDescriptor);
-
-        List<String> errors = new LinkedList<>();
-        doAnswer(invocation -> {
-            String error = invocation.getArgument(0);
-            errors.add(error);
-            return null;
-        }).when(ctx).reportError(anyString());
-
-        checkApiRegions.execute(ctx);
-
-        return errors;
-    }
-
-    private static final class TestFeatureDescriptor extends FeatureDescriptor {
-
-        @Override
-        public Feature getFeature() {
-            return null;
-        }
-
-    }
-
-    private static final class TestBundleDescriptor extends BundleDescriptor {
-
-        @Override
-        public File getArtifactFile() {
-            // do nothing
-            return null;
-        }
-
-        @Override
-        public Artifact getArtifact() {
-            // do nothing
-            return null;
-        }
-
-        @Override
-        public Manifest getManifest() {
-            // do nothing
-            return null;
-        }
-
-        @Override
-        public String getBundleVersion() {
-            // do nothing
-            return null;
-        }
-
-        @Override
-        public String getBundleSymbolicName() {
-            // do nothing
-            return null;
-        }
-
-        @Override
-        public int getBundleStartLevel() {
-            // do nothing
-            return 0;
-        }
-
-    }
-
 }