Copilot commented on code in PR #23:
URL: 
https://github.com/apache/sling-org-apache-sling-feature-extension-apiregions/pull/23#discussion_r2807504943


##########
src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApi.java:
##########
@@ -199,41 +195,45 @@ Set<String> getAllowedRegions(final ApiRegion region) {
         return allowedNames;
     }
 
-    Set<ApiExport> calculateDeprecatedPackages(
+    Map<String, DeprecatedPackage> calculateDeprecatedPackages(
             final ApiRegion region, final Map<BundleDescriptor, Set<String>> 
bundleRegions) {
-        final Set<ApiExport> result = new LinkedHashSet<>();
-        for (final ApiExport export : region.listAllExports()) {
-            if (export.getDeprecation().getPackageInfo() != null) {
-                final String version = getVersion(bundleRegions, 
region.getName(), export.getName());
-                // create new ApiExport to add version
-                final ApiExport clone = new ApiExport(export.getName());
-                
clone.getDeprecation().setPackageInfo(export.getDeprecation().getPackageInfo());
-                if (version != null) {
-                    clone.getProperties().put(PROP_VERSION, version);
+        final Map<String, DeprecatedPackage> result = new HashMap<>();
+        ApiRegion current = region;
+        while (current != null) {
+            for (final ApiExport export : current.listExports()) {
+                if (export.getDeprecation().getPackageInfo() != null && 
!result.containsKey(export.getName())) {
+                    final DeprecatedPackage pck = 
getDeprecatedPackage(bundleRegions, current, export);
+                    result.put(export.getName(), pck);
                 }
-                result.add(clone);
             }
+            current = current.getParent();
         }
         return result;
     }
 
-    String getVersion(
-            final Map<BundleDescriptor, Set<String>> bundleRegions, final 
String regionName, final String packageName) {
-        String version = null;
+    DeprecatedPackage getDeprecatedPackage(
+            final Map<BundleDescriptor, Set<String>> bundleRegions, final 
ApiRegion region, final ApiExport export) {
+        final List<PackageInfo> deprecatedList = new ArrayList<>();
+        final List<PackageInfo> nonDeprecatedList = new ArrayList<>();
+
+        final ArtifactId[] regionOrigins = region.getFeatureOrigins();
+
         for (final Map.Entry<BundleDescriptor, Set<String>> entry : 
bundleRegions.entrySet()) {
-            if (entry.getValue().contains(regionName)) {
+            final ArtifactId[] bundleOrigins = 
entry.getKey().getArtifact().getFeatureOrigins();
+            if (entry.getValue().contains(region.getName())) {
                 for (final PackageInfo info : 
entry.getKey().getExportedPackages()) {
-                    if (info.getName().equals(packageName)) {
-                        version = info.getVersion();
-                        break;
+                    if (info.getName().equals(export.getName())) {
+                        if (regionOrigins.length == 0
+                                || (bundleOrigins.length > 0 && 
bundleOrigins[0].isSame(regionOrigins[0]))) {

Review Comment:
   The feature origins comparison only checks the first origin 
(bundleOrigins[0] and regionOrigins[0]). If a bundle or region has multiple 
feature origins, only the first one is considered when deciding whether to 
treat the package as deprecated or non-deprecated. Consider whether all origins 
should be compared, or document why only the first origin matters.



##########
src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckDeprecatedApiTest.java:
##########
@@ -138,6 +141,145 @@ public void testOptionalImportReportedWhenEnabled() 
throws Exception {
                         Mockito.eq(ArtifactId.fromMvnId("g:b:1.0.0")), 
Mockito.contains("org.foo.deprecated"));
     }
 
+    @Test
+    public void testVersionRangeChecking() throws Exception {
+        // Test that version ranges are properly checked
+        final CheckDeprecatedApi analyser = new CheckDeprecatedApi();
+
+        // Create a feature with deprecated package exported at version 2.0.0
+        final Feature feature = new 
Feature(ArtifactId.fromMvnId("g:feature:1"));
+        final Extension extension =
+                new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, 
ExtensionState.OPTIONAL);
+        
extension.setJSON("[{\"name\":\"global\",\"feature-origins\":[\"g:feature:1\"],"
+                + 
"\"exports\":[{\"name\":\"org.foo.deprecated\",\"deprecated\":\"This package is 
deprecated\"}]}]");
+        feature.getExtensions().add(extension);
+
+        final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
+
+        // Bundle that exports the deprecated package at version 2.0.0
+        final Artifact exportBundle = new 
Artifact(ArtifactId.fromMvnId("g:exporter:1.0.0"));
+        exportBundle.setFeatureOrigins(feature.getId());
+        final BundleDescriptor exportBd = new 
TestBundleDescriptor(exportBundle);
+        exportBd.getExportedPackages()
+                .add(new PackageInfo("org.foo.deprecated", "2.0.0", false, 
Collections.emptySet()));
+        fd.getBundleDescriptors().add(exportBd);
+
+        // Bundle that imports with version range [1.0.0,2.0.0) - should NOT 
be flagged
+        final Artifact importBundle1 = new 
Artifact(ArtifactId.fromMvnId("g:importer1:1.0.0"));
+        importBundle1.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd1 = new 
TestBundleDescriptor(importBundle1);
+        final PackageInfo import1 =
+                new PackageInfo("org.foo.deprecated", "[1.0.0,2.0.0)", false, 
Collections.emptySet());
+        importBd1.getImportedPackages().add(import1);
+        fd.getBundleDescriptors().add(importBd1);
+
+        // Bundle that imports with version range [2.0.0,3.0.0) - SHOULD be 
flagged
+        final Artifact importBundle2 = new 
Artifact(ArtifactId.fromMvnId("g:importer2:1.0.0"));
+        importBundle2.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd2 = new 
TestBundleDescriptor(importBundle2);
+        final PackageInfo import2 =
+                new PackageInfo("org.foo.deprecated", "[2.0.0,3.0.0)", false, 
Collections.emptySet());
+        importBd2.getImportedPackages().add(import2);
+        fd.getBundleDescriptors().add(importBd2);
+
+        // Bundle that imports without version - SHOULD be flagged (matches 
any version)
+        final Artifact importBundle3 = new 
Artifact(ArtifactId.fromMvnId("g:importer3:1.0.0"));
+        importBundle3.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd3 = new 
TestBundleDescriptor(importBundle3);
+        final PackageInfo import3 = new PackageInfo("org.foo.deprecated", 
null, false, Collections.emptySet());
+        importBd3.getImportedPackages().add(import3);
+        fd.getBundleDescriptors().add(importBd3);
+
+        final AnalyserTaskContext ctx = 
Mockito.mock(AnalyserTaskContext.class);
+        Mockito.when(ctx.getFeature()).thenReturn(feature);
+        Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+        
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
+
+        analyser.execute(ctx);
+
+        // importer1 should NOT be flagged (version range excludes 2.0.0)
+        Mockito.verify(ctx, never())
+                
.reportArtifactWarning(Mockito.eq(ArtifactId.fromMvnId("g:importer1:1.0.0")), 
Mockito.anyString());
+
+        // importer2 SHOULD be flagged (version range includes 2.0.0)
+        Mockito.verify(ctx)
+                .reportArtifactWarning(
+                        Mockito.eq(ArtifactId.fromMvnId("g:importer2:1.0.0")), 
Mockito.contains("org.foo.deprecated"));
+
+        // importer3 SHOULD be flagged (no version constraint)
+        Mockito.verify(ctx)
+                .reportArtifactWarning(
+                        Mockito.eq(ArtifactId.fromMvnId("g:importer3:1.0.0")), 
Mockito.contains("org.foo.deprecated"));
+    }
+
+    @Test
+    public void testMultipleExportVersions() throws Exception {
+        // Test that when multiple versions of a deprecated package exist,
+        // only imports matching the actual exported versions are flagged
+        final CheckDeprecatedApi analyser = new CheckDeprecatedApi();
+
+        final Feature feature = new 
Feature(ArtifactId.fromMvnId("g:feature:1"));
+        final Extension extension =
+                new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, 
ExtensionState.OPTIONAL);
+        
extension.setJSON("[{\"name\":\"global\",\"feature-origins\":[\"g:feature:1\"],"
+                + 
"\"exports\":[{\"name\":\"org.foo.deprecated\",\"deprecated\":\"This package is 
deprecated\"}]}]");
+        feature.getExtensions().add(extension);
+
+        final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
+
+        // Bundle that exports the deprecated package at version 1.0.0
+        final Artifact exportBundle1 = new 
Artifact(ArtifactId.fromMvnId("g:exporter1:1.0.0"));
+        exportBundle1.setFeatureOrigins(feature.getId());
+        final BundleDescriptor exportBd1 = new 
TestBundleDescriptor(exportBundle1);
+        exportBd1
+                .getExportedPackages()
+                .add(new PackageInfo("org.foo.deprecated", "1.0.0", false, 
Collections.emptySet()));
+        fd.getBundleDescriptors().add(exportBd1);
+
+        // DIFFERENT bundle that exports the SAME package at version 3.0.0
+        final Artifact exportBundle2 = new 
Artifact(ArtifactId.fromMvnId("g:exporter2:1.0.0"));
+        exportBundle2.setFeatureOrigins(feature.getId());
+        final BundleDescriptor exportBd2 = new 
TestBundleDescriptor(exportBundle2);
+        exportBd2
+                .getExportedPackages()
+                .add(new PackageInfo("org.foo.deprecated", "3.0.0", false, 
Collections.emptySet()));
+        fd.getBundleDescriptors().add(exportBd2);
+
+        // Bundle that imports version [1.0.0,2.0.0) - matches only the 1.0.0 
export
+        final Artifact importBundle1 = new 
Artifact(ArtifactId.fromMvnId("g:importer1:1.0.0"));
+        importBundle1.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd1 = new 
TestBundleDescriptor(importBundle1);
+        importBd1
+                .getImportedPackages()
+                .add(new PackageInfo("org.foo.deprecated", "[1.0.0,2.0.0)", 
false, Collections.emptySet()));
+        fd.getBundleDescriptors().add(importBd1);
+
+        // Bundle that imports version [2.5.0,4.0.0) - matches only the 3.0.0 
export
+        final Artifact importBundle2 = new 
Artifact(ArtifactId.fromMvnId("g:importer2:1.0.0"));
+        importBundle2.setFeatureOrigins(feature.getId());
+        final BundleDescriptor importBd2 = new 
TestBundleDescriptor(importBundle2);
+        importBd2
+                .getImportedPackages()
+                .add(new PackageInfo("org.foo.deprecated", "[2.5.0,4.0.0)", 
false, Collections.emptySet()));
+        fd.getBundleDescriptors().add(importBd2);
+
+        final AnalyserTaskContext ctx = 
Mockito.mock(AnalyserTaskContext.class);
+        Mockito.when(ctx.getFeature()).thenReturn(feature);
+        Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
+        
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
+
+        analyser.execute(ctx);
+
+        // BOTH importers should be flagged since both versions (1.0.0 and 
3.0.0) are available
+        // The implementation checks all matching exported versions for this 
package.
+        Mockito.verify(ctx)
+                .reportArtifactWarning(
+                        Mockito.eq(ArtifactId.fromMvnId("g:importer1:1.0.0")), 
Mockito.contains("org.foo.deprecated"));
+        Mockito.verify(ctx)
+                .reportArtifactWarning(
+                        Mockito.eq(ArtifactId.fromMvnId("g:importer2:1.0.0")), 
Mockito.contains("org.foo.deprecated"));
+    }
+

Review Comment:
   The new tests don't actually test the main scenario described in the PR 
title "Check for deprecated API does not consider other bundles exporting the 
API". Both new tests (testVersionRangeChecking and testMultipleExportVersions) 
have all bundles with the same feature origin. There should be a test where a 
deprecated package from one feature is also exported by a bundle from a 
DIFFERENT feature, and verify that imports matching only the non-deprecated 
version are not flagged.
   ```suggestion
   
       @Test
       public void testDeprecatedExportFromOtherFeatureDoesNotTriggerWarning() 
throws Exception {
           final Feature feature = new 
Feature(ArtifactId.fromMvnId("g:feature:1"));
           final Extension extension =
                   new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, 
ExtensionState.OPTIONAL);
           extension.setJSON(API_REGIONS_JSON);
           feature.getExtensions().add(extension);
   
           final FeatureDescriptor fd = new FeatureDescriptorImpl(feature);
   
           // Importer bundle belonging to the current feature, importing only 
the newer (non-deprecated) version
           final Artifact importBundle = new 
Artifact(ArtifactId.fromMvnId("g:importer:1.0.0"));
           importBundle.setFeatureOrigins(feature.getId());
           final BundleDescriptor importBd = new 
TestBundleDescriptor(importBundle);
           importBd
                   .getImportedPackages()
                   .add(new PackageInfo("org.foo.deprecated", "[3.0.0,4.0.0)", 
false, Collections.emptySet()));
           fd.getBundleDescriptors().add(importBd);
   
           // Deprecated export coming from the current feature
           final Artifact deprecatedExportBundle =
                   new 
Artifact(ArtifactId.fromMvnId("g:deprecated-exporter:1.0.0"));
           deprecatedExportBundle.setFeatureOrigins(feature.getId());
           final BundleDescriptor deprecatedExportBd = new 
TestBundleDescriptor(deprecatedExportBundle);
           deprecatedExportBd
                   .getExportedPackages()
                   .add(new PackageInfo("org.foo.deprecated", "1.0.0", false, 
Collections.emptySet()));
           fd.getBundleDescriptors().add(deprecatedExportBd);
   
           // Non-deprecated export coming from a different feature
           final Artifact otherFeatureExportBundle =
                   new Artifact(ArtifactId.fromMvnId("g:other-exporter:1.0.0"));
           
otherFeatureExportBundle.setFeatureOrigins(ArtifactId.fromMvnId("g:otherfeature:1"));
           final BundleDescriptor otherFeatureExportBd = new 
TestBundleDescriptor(otherFeatureExportBundle);
           otherFeatureExportBd
                   .getExportedPackages()
                   .add(new PackageInfo("org.foo.deprecated", "3.0.0", false, 
Collections.emptySet()));
           fd.getBundleDescriptors().add(otherFeatureExportBd);
   
           final AnalyserTaskContext ctx = 
Mockito.mock(AnalyserTaskContext.class);
           Mockito.when(ctx.getFeature()).thenReturn(feature);
           Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
           
Mockito.when(ctx.getConfiguration()).thenReturn(Collections.emptyMap());
   
           analyser.execute(ctx);
   
           // The importer only matches the non-deprecated 3.0.0 export from 
the other feature, so it must not be flagged
           Mockito.verify(ctx, never())
                   .reportArtifactWarning(
                           
Mockito.eq(ArtifactId.fromMvnId("g:importer:1.0.0")), Mockito.anyString());
       }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to