Skip to content

Commit

Permalink
Extend test for OSGi manifest attributes (#2738)
Browse files Browse the repository at this point in the history
* Extend test for OSGi manifest attributes

* Fix test failure for JDK 21 CI build

* Use try-with-resources
  • Loading branch information
Marcono1234 committed Sep 8, 2024
1 parent 6af6705 commit 48889db
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 76 deletions.
21 changes: 20 additions & 1 deletion gson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@
<version>33.3.0-jre</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.1-jre</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -173,6 +179,19 @@
<argLine>--illegal-access=deny</argLine>
</configuration>
</plugin>
<!-- Failsafe plugin for running integration tests against final JAR, see `*IT.java` test classes -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.coderplus.maven.plugins</groupId>
<artifactId>copy-rename-maven-plugin</artifactId>
Expand Down Expand Up @@ -271,7 +290,7 @@
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<!-- Use existing manifest generated by BND plugin -->
<!-- Use existing manifest generated by bnd-maven-plugin -->
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
Expand Down
238 changes: 238 additions & 0 deletions gson/src/test/java/com/google/gson/integration/OSGiManifestIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2024 Google Inc.
*
* Licensed 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 com.google.gson.integration;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;

import com.google.common.base.Splitter;
import com.google.gson.internal.GsonBuildConfig;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;

/**
* Performs assertions on the generated OSGi manifest attributes. This is an integration test
* ({@code *IT.java}) intended to be run against the final JAR.
*
* <p>Note: These tests must be run on the command line with {@code mvn clean verify}, running them
* in the IDE will not work because it won't use the generated JAR, and additionally the
* bnd-maven-plugin seems to behave differently in the IDE (at least Eclipse), adding unexpected
* {@code Import-Package} entries for JDK classes (<a
* href="https://github.com/bndtools/bnd/issues/6258>bnd-maven-plugin issue</a>).<br>
* Running Maven's {@code clean} phase is necessary due to a <a
* href="https://github.com/bndtools/bnd/issues/6221">bnd-maven-plugin bug</a>.
*/
public class OSGiManifestIT {
private static class ManifestData {
public final URL url;
public final Manifest manifest;

public ManifestData(URL url, Manifest manifest) {
this.url = url;
this.manifest = manifest;
}
}

private static final String GSON_VERSION = GsonBuildConfig.VERSION;
private Attributes manifestAttributes;

@Before
public void getGsonManifestAttributes() throws Exception {
ManifestData manifestData = findManifest("com.google.gson");
// Make sure manifest was loaded from final Gson JAR (and not intermediate manifest is used)
assertWithMessage(
"Should load manifest from Gson JAR file; run this test with `mvn clean verify` on"
+ " command line and not from IDE")
.that(manifestData.url.toString())
.endsWith(".jar!/META-INF/MANIFEST.MF");

manifestAttributes = manifestData.manifest.getMainAttributes();
}

private String getAttribute(String name) {
return manifestAttributes.getValue(name);
}

@Test
public void testBundleInformation() {
assertThat(getAttribute("Bundle-SymbolicName")).isEqualTo("com.google.gson");
assertThat(getAttribute("Bundle-Name")).isEqualTo("Gson");
assertThat(getAttribute("Bundle-License"))
.isEqualTo("\"Apache-2.0\";link=\"https://www.apache.org/licenses/LICENSE-2.0.txt\"");
assertThat(getAttribute("Bundle-Version"))
.isEqualTo(GSON_VERSION.replace("-SNAPSHOT", ".SNAPSHOT"));
}

@Test
public void testImports() throws Exception {
// Keep only 'major.minor', drop the 'patch' version
String errorProneVersion =
shortenVersionNumber(
findManifest("com.google.errorprone.annotations")
.manifest
.getMainAttributes()
.getValue("Bundle-Version"),
1);
String nextMajorErrorProneVersion = increaseVersionNumber(errorProneVersion, 0);
String errorProneVersionRange =
"[" + errorProneVersion + "," + nextMajorErrorProneVersion + ")";

List<String> imports = splitPackages(getAttribute("Import-Package"));
// If imports contains `java.*`, then either user started from IDE, or IDE rebuilt project while
// Maven build was running, see https://github.com/bndtools/bnd/issues/6258
if (imports.stream().anyMatch(i -> i.startsWith("java."))) {
fail(
"Test must be run from command line with `mvn clean verify`; additionally make sure your"
+ " IDE did not rebuild the project in the meantime");
}

assertThat(imports)
.containsExactly(
// Dependency on JDK's sun.misc.Unsafe should be optional
"sun.misc;resolution:=optional",
"com.google.errorprone.annotations;version=\"" + errorProneVersionRange + "\"");

// Should not contain any import for Gson's own packages, see
// https://github.com/google/gson/pull/2735#issuecomment-2330047410
for (String importedPackage : imports) {
assertThat(importedPackage).doesNotContain("com.google.gson");
}
}

@Test
public void testExports() {
String gsonVersion = GSON_VERSION.replace("-SNAPSHOT", "");

List<String> exports = splitPackages(getAttribute("Export-Package"));
// When not running `mvn clean` the exports might differ slightly, see
// https://github.com/bndtools/bnd/issues/6221
assertWithMessage("Unexpected exports; make sure you are running `mvn clean ...`")
.that(exports)
// Note: This just represents the currently generated exports; especially the `uses` can be
// adjusted if necessary when Gson's implementation changes
.containsExactly(
"com.google.gson;uses:=\"com.google.gson.reflect,com.google.gson.stream\";version=\""
+ gsonVersion
+ "\"",
"com.google.gson.annotations;version=\"" + gsonVersion + "\"",
"com.google.gson.reflect;version=\"" + gsonVersion + "\"",
"com.google.gson.stream;uses:=\"com.google.gson\";version=\"" + gsonVersion + "\"");
}

@Test
public void testRequireCapability() {
// When building with JDK >= 21, the minimum target version is Java 8
String expectedJavaVersion = Runtime.version().feature() < 21 ? "1.7" : "1.8";

// Defines the minimum required Java version
assertThat(getAttribute("Require-Capability"))
.isEqualTo("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=" + expectedJavaVersion + "))\"");

// Should not define deprecated "Bundle-RequiredExecutionEnvironment"
assertThat(getAttribute("Bundle-RequiredExecutionEnvironment")).isNull();
}

private ManifestData findManifest(String bundleName) throws IOException {
List<URL> manifestResources =
Collections.list(getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"));

for (URL manifestResource : manifestResources) {
Manifest manifest;
try (InputStream is = manifestResource.openStream()) {
manifest = new Manifest(is);
}
if (bundleName.equals(manifest.getMainAttributes().getValue("Bundle-SymbolicName"))) {
return new ManifestData(manifestResource, manifest);
}
}

fail(
"Cannot find "
+ bundleName
+ " OSGi bundle manifest among: "
+ manifestResources
+ "\nRun this test with `mvn clean verify` on command line and not from the IDE.");
return null;
}

/** Splits a list of packages separated by {@code ','}. */
private List<String> splitPackages(String packagesString) {
List<String> splitPackages = new ArrayList<>();
int nextSplitStart = 0;
boolean isInQuotes = false;

for (int i = 0; i < packagesString.length(); i++) {
char c = packagesString.charAt(i);
// Ignore ',' inside quotes
if (c == '"') {
isInQuotes = !isInQuotes;
} else if (c == ',' && !isInQuotes) {
splitPackages.add(packagesString.substring(nextSplitStart, i));
nextSplitStart = i + 1; // skip past the ','
}
}

// Add package behind last ','
splitPackages.add(packagesString.substring(nextSplitStart));
return splitPackages;
}

/**
* Shortens a version number by dropping lower parts. For example {@code 1.2.3 -> 1.2} (when
* {@code keepPosition = 1}).
*
* @param versionString e.g. "1.2.3"
* @param keepPosition position of the version to keep: 0 = major, 1 = minor, ...
* @return shortened version number
*/
private String shortenVersionNumber(String versionString, int keepPosition) {
return Splitter.on('.')
.splitToStream(versionString)
.limit(keepPosition + 1)
.collect(Collectors.joining("."));
}

/**
* Increases part of a version number (and drops lower parts). For example {@code 1.2.3 -> 1.3}
* (when {@code position = 1}).
*
* @param versionString e.g. "1.2.3"
* @param position position of the version to increase: 0 = major, 1 = minor, ...
* @return increased version number
*/
private String increaseVersionNumber(String versionString, int position) {
List<Integer> splitVersion = new ArrayList<>();
for (String versionPiece : Splitter.on('.').split(versionString)) {
splitVersion.add(Integer.valueOf(versionPiece));
}
// Drop lower version parts
splitVersion = splitVersion.subList(0, position + 1);
// Increase version number
splitVersion.set(position, splitVersion.get(position) + 1);

return splitVersion.stream().map(i -> i.toString()).collect(Collectors.joining("."));
}
}
71 changes: 0 additions & 71 deletions gson/src/test/java/com/google/gson/regression/OSGiTest.java

This file was deleted.

13 changes: 9 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,12 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
Expand Down Expand Up @@ -542,9 +547,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs combine.self="override">
<compilerArg>-Xlint:all,-options</compilerArg>
</compilerArgs>
<compilerArgs combine.self="override">
<compilerArg>-Xlint:all,-options</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
Expand Down

0 comments on commit 48889db

Please sign in to comment.