From fd644a302ce56080eaf489c32e1dcf66d8c6fa31 Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Thu, 9 Jun 2022 12:06:44 -0400 Subject: [PATCH] ci: linakge checker test (#4494) * ci: linkage checker test * ci: xmx12g * ci: running linkage checker only for release PRs --- .github/workflows/full-convergence-check.yaml | 19 +++ tests/pom.xml | 14 +++ .../cloud/MaximumLinkageErrorsTest.java | 119 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 tests/src/test/java/com/google/cloud/MaximumLinkageErrorsTest.java diff --git a/.github/workflows/full-convergence-check.yaml b/.github/workflows/full-convergence-check.yaml index 33dd8ccff3c6..3ca0ce2c922a 100644 --- a/.github/workflows/full-convergence-check.yaml +++ b/.github/workflows/full-convergence-check.yaml @@ -23,6 +23,25 @@ jobs: # BomContentTest should run part of the verify mvn -B -V -ntp verify -Dtest="BomContentTest#testLibrariesBomReachable" working-directory: tests + + linkage-checker: + name: Linkage Checker to find new linkage errors + runs-on: ubuntu-latest + if: github.repository_owner == 'googleapis' && github.head_ref == 'release-please--branches--main' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: zulu + java-version: 8 + - name: Install BOMs + run: | + mvn -B -V -ntp install + - name: Ensure the Libraries BOM does not contain new linkage errors + run: | + mvn -B -V -ntp test -Dtest="MaximumLinkageErrorsTest" + working-directory: tests + full-dependencies-convergence: runs-on: ubuntu-latest if: github.repository_owner == 'googleapis' && github.head_ref == 'release-please--branches--main' diff --git a/tests/pom.xml b/tests/pom.xml index 9e3965802ad1..f3a42cede73c 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -43,4 +43,18 @@ test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + + -Xmx8g + + + + diff --git a/tests/src/test/java/com/google/cloud/MaximumLinkageErrorsTest.java b/tests/src/test/java/com/google/cloud/MaximumLinkageErrorsTest.java new file mode 100644 index 000000000000..4e531003c422 --- /dev/null +++ b/tests/src/test/java/com/google/cloud/MaximumLinkageErrorsTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2019 Google LLC. + * + * 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.cloud; + +import com.google.cloud.tools.opensource.classpath.ClassPathBuilder; +import com.google.cloud.tools.opensource.classpath.ClassPathEntry; +import com.google.cloud.tools.opensource.classpath.ClassPathResult; +import com.google.cloud.tools.opensource.classpath.DependencyMediation; +import com.google.cloud.tools.opensource.classpath.LinkageChecker; +import com.google.cloud.tools.opensource.classpath.LinkageProblem; +import com.google.cloud.tools.opensource.dependencies.Bom; +import com.google.cloud.tools.opensource.dependencies.MavenRepositoryException; +import com.google.cloud.tools.opensource.dependencies.RepositoryUtility; +import com.google.cloud.tools.opensource.dependencies.UnresolvableArtifactProblem; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import org.eclipse.aether.RepositoryException; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.version.InvalidVersionSpecificationException; +import org.junit.Assert; +import org.junit.Test; + +public class MaximumLinkageErrorsTest { + + @Test + public void testForNewLinkageErrors() + throws IOException, MavenRepositoryException, RepositoryException { + // Not using RepositoryUtility.findLatestCoordinates, which may return a snapshot version + String version = findLatestNonSnapshotVersion(); + String baselineCoordinates = "com.google.cloud:libraries-bom:" + version; + Bom baseline = Bom.readBom(baselineCoordinates); + + Path bomFile = Paths.get("../libraries-bom/pom.xml"); + Bom bom = Bom.readBom(bomFile); + + ImmutableSet oldProblems = createLinkageChecker(baseline).findLinkageProblems(); + LinkageChecker checker = createLinkageChecker(bom); + ImmutableSet currentProblems = checker.findLinkageProblems(); + + // This only tests for newly missing methods, not new invocations of + // previously missing methods. + Set newProblems = Sets.difference(currentProblems, oldProblems); + + // Appengine-api-1.0-sdk is known to contain linkage errors because it shades dependencies + // https://github.com/GoogleCloudPlatform/cloud-opensource-java/issues/441 + newProblems = + newProblems.stream() + .filter(problem -> !hasLinkageProblemFromArtifactId(problem, "appengine-api-1.0-sdk")) + .collect(Collectors.toSet()); + + // Check that no new linkage errors have been introduced since the baseline + StringBuilder message = new StringBuilder("Baseline BOM: " + baselineCoordinates + "\n"); + if (!newProblems.isEmpty()) { + message.append("Newly introduced problems:\n"); + message.append(LinkageProblem.formatLinkageProblems(newProblems, null)); + Assert.fail(message.toString()); + } + } + + private LinkageChecker createLinkageChecker(Bom bom) + throws InvalidVersionSpecificationException, IOException { + ImmutableList managedDependencies = bom.getManagedDependencies(); + ClassPathBuilder classPathBuilder = new ClassPathBuilder(); + + // full: false to avoid fetching optional dependencies. + ClassPathResult classPathResult = + classPathBuilder.resolve(managedDependencies, false, DependencyMediation.MAVEN); + ImmutableList classpath = classPathResult.getClassPath(); + ImmutableList artifactProblems = + classPathResult.getArtifactProblems(); + if (!artifactProblems.isEmpty()) { + throw new IOException("Could not resolve artifacts: " + artifactProblems); + } + List artifactsInBom = classpath.subList(0, managedDependencies.size()); + ImmutableSet entryPoints = ImmutableSet.copyOf(artifactsInBom); + return LinkageChecker.create(classpath, entryPoints, null); + } + + private boolean hasLinkageProblemFromArtifactId(LinkageProblem problem, String artifactId) { + ClassPathEntry sourceClassPathEntry = problem.getSourceClass().getClassPathEntry(); + Artifact sourceArtifact = sourceClassPathEntry.getArtifact(); + return artifactId.equals(sourceArtifact.getArtifactId()); + } + + private String findLatestNonSnapshotVersion() throws MavenRepositoryException { + ImmutableList versions = + RepositoryUtility.findVersions( + RepositoryUtility.newRepositorySystem(), "com.google.cloud", "libraries-bom"); + ImmutableList versionsLatestFirst = versions.reverse(); + Optional highestNonsnapshotVersion = + versionsLatestFirst.stream().filter(version -> !version.contains("SNAPSHOT")).findFirst(); + if (!highestNonsnapshotVersion.isPresent()) { + Assert.fail("Could not find non-snapshot version of the BOM"); + } + return highestNonsnapshotVersion.get(); + } +}