[java-identity-provider] 01/03: JPAR-175 Initial pass at dependency analysis test.
Rod Widdowson
rdw at steadingsoftware.com
Thu Jun 17 13:35:14 UTC 2021
This is an automated email from the git hooks/post-receive script.
rdw pushed a commit to branch dev/JPAR-175
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=9bb2c64b6ea47f4e2589e0c5d31c81553353dcb2
commit 9bb2c64b6ea47f4e2589e0c5d31c81553353dcb2
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sat Jun 12 13:24:53 2021 +0100
JPAR-175 Initial pass at dependency analysis test.
https://issues.shibboleth.net/jira/browse/JPAR-175
---
idp-installer/pom.xml | 6 +
.../idp/dependencies/DependencyTest.java | 240 ++++++++++++++++
.../net/shibboleth/idp/dependencies/ParsedPom.java | 303 +++++++++++++++++++++
3 files changed, 549 insertions(+)
diff --git a/idp-installer/pom.xml b/idp-installer/pom.xml
index 3dfb7ef79..27ba58b5d 100644
--- a/idp-installer/pom.xml
+++ b/idp-installer/pom.xml
@@ -180,6 +180,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-invoker</artifactId>
+ <version>3.1.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<scm>
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
new file mode 100644
index 000000000..6c9f6fc5b
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/DependencyTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.dependencies;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.junit.AfterClass;
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.dependencies.ParsedPom.PomArtifact;
+import net.shibboleth.idp.installer.plugin.impl.PluginInstallerSupport;
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import net.shibboleth.utilities.java.support.xml.XMLParserException;
+
+/**
+ * Test that what we see is what we wanted in abuild - we do this by reading the pom
+ */
+public class DependencyTest extends OpenSAMLInitBaseTestCase {
+
+ /** Set this up if you want to run the tests from eclipse. */
+ private static String LOCAL_MAVEN_HOME = null;
+
+ /** Parse for us to use. */
+ private ParserPool parserPool;
+
+ /** If this is set to false we do not do any work which requires maven (which is everything). */
+ private boolean mavenAvailable;
+
+ /** Work space. Deleted on exit. */
+ private Path workingDir;
+
+ /** The project parent dependencies */
+ private List<PomArtifact> dependencies = new ArrayList<>();
+
+ /** The IDP Version/artifact info */
+ private PomArtifact idpArtefact;
+
+ private PrintWriter report;
+
+
+ /** We have as an assumption that the CWD is idp-installer. Test this.
+ * @throws IOException
+ */
+ @BeforeClass public void testWorkingDir() throws IOException {
+ final Path path = Path.of(".");
+ final String myPath = path.toFile().getCanonicalPath();
+ final String indirectPath = path.resolve("..").resolve("idp-installer").toFile().getCanonicalPath();
+
+ assertTrue(path.resolve("..").resolve("idp-war").toFile().exists());
+ assertEquals(myPath, indirectPath);
+ }
+
+ /** Set up maven.
+ * This relies on a couple of dodgy tests when running from maven from the command line and
+ * on the user setting up {@link #LOCAL_MAVEN_HOME} when running from eclipse.
+ */
+ @BeforeClass public void setupMavenEnvironment() {
+ if (System.getProperty("maven.home") != null) {
+ mavenAvailable = true;
+ return;
+ }
+ String home = System.getenv("MAVEN_HOME");
+ if (home == null) {
+ home = System.getenv("_");
+ }
+ if (home == null) {
+ home = LOCAL_MAVEN_HOME;
+ }
+
+ if (home != null) {
+ System.setProperty("maven.home", home);
+ mavenAvailable = true;
+ }
+ }
+
+ /** Set up the environment for running the test.
+ * Parse the IdP parent pom.
+ * From that find the description of parent pom & download it.
+ * Parse that.
+ * @throws IOException
+ * @throws XMLParserException
+ * @throws MavenInvocationException
+ */
+
+ @BeforeClass(dependsOnMethods = {"setupMavenEnvironment", "testWorkingDir"}) public void setup() throws IOException, XMLParserException, MavenInvocationException {
+ if (!mavenAvailable) {
+ return;
+ }
+ workingDir = Files.createTempDirectory("dependencyTest");
+ parserPool = XMLObjectProviderRegistrySupport.getParserPool();
+ final ParsedPom idpParent = new ParsedPom(parserPool, Path.of("../idp-parent/pom.xml"), true);
+ idpArtefact = idpParent.getOurInfo();
+ assertNotNull(idpParent.getParent());
+ final Path parentPath = downloadPom(idpParent.getParent());
+ final ParsedPom projectParent = new ParsedPom(parserPool, parentPath, false);
+ dependencies.addAll(projectParent.getCompileDependencies());
+ for (final PomArtifact bom : projectParent.getBomDependencies()) {
+ final Path bomPath = downloadPom(bom);
+ final ParsedPom bomContents = new ParsedPom(parserPool, bomPath, false);
+ dependencies.addAll(bomContents.getCompileDependencies());
+ }
+ final File out = new File("target/dependencyReport.txt");
+ final FileOutputStream outStream = new FileOutputStream(out);
+ report = new PrintWriter(new BufferedOutputStream(outStream));
+ }
+
+ /** Clean up after ourselves. */
+ @AfterClass public void teardown() {
+ PluginInstallerSupport.deleteTree(workingDir);
+ }
+
+ /** The guts of the first test. Are all the files what we expected?
+ * @throws IOException
+ */
+ @Test public void testDependencies() throws IOException {
+ if (!mavenAvailable) {
+ return;
+ }
+ final Path lib = Path.of("../idp-war/target/idp-war-"+ idpArtefact.getVersion()).resolve("WEB-INF").resolve("lib");
+ assertTrue(Files.exists(lib), "idp-war must have been built");
+ final Map<String, String> names = new HashMap<>();
+ int wrongVersion = 0;
+ int found = 0;
+ int nonUsed = 0;
+ int dupNames = Files.list(lib).mapToInt(e -> addName(names, lib.relativize(e).toString())).sum();
+ report.format("WAR CONTENTS\n=== ========\n");
+ Collections.sort(dependencies);
+ for (PomArtifact artifact : dependencies) {
+ final String id = artifact.getArtifactId();
+ final String ver = artifact.getVersion();
+ final String version = names.get(id);
+ if (version == null) {
+ report.format("%-22s\t: %-12s\tNOT found in war\n", id, ver);
+ nonUsed++;
+ } else if (version.equals(ver)) {
+ report.format("%-22s\t: %-12s\tFound in war\n", id, ver);
+ found++;
+ } else {
+ report.format("%-22s\t: %-12s\tVERSION MISMATCH - found %s\n", id, ver, version);
+ wrongVersion++;
+ }
+ }
+ if (dupNames != 0) {
+ report.format("\n%d Duplicate names\n", dupNames);
+ }
+
+ report.format("\n%d dependencies, %d found, %d not found %d mismatched", dependencies.size(), found, nonUsed, wrongVersion);
+ report.flush();
+ report.close();
+ assertEquals(wrongVersion, 0, "Mismatched version");
+ assertEquals(dupNames, 0, "Multiple similarly named jars");
+ }
+
+ /** Trivial accumulator to pull a name in the lib directory apart and insert it into the map.
+ * @param names The map to accumulate into
+ * @param jarPath the file we are looking at.
+ * @return 1 if here was a previous entry.
+ */
+ private int addName(Map<String, String> names, String jarPath) {
+ final int last = jarPath.lastIndexOf("-");
+ final String base = jarPath.substring(0, last);
+ String versionExtension = jarPath.substring(last+1);
+ final String oldVersion;
+ if (versionExtension.endsWith(".jar")) {
+ versionExtension = versionExtension.substring(0, versionExtension.length()-4);
+ }
+ oldVersion = names.put(base, versionExtension);
+ if (oldVersion == null) {
+ return 0;
+ }
+ report.format("%-22s\t: Duplicate version %-12s\t& %s\n", base, versionExtension, oldVersion);
+ return 1;
+ }
+
+ /** tell maven to download the artifact and returns it's path.
+ * @param artifact what to look for
+ * @return the pom as a {@link Path}
+ * @throws MavenInvocationException
+ */
+ private Path downloadPom(final PomArtifact artifact) throws MavenInvocationException {
+ final String fullArtifactName = new StringBuilder(artifact.getGroupId())
+ .append(':')
+ .append(artifact.getArtifactId())
+ .append(':')
+ .append(artifact.getVersion())
+ .append(":pom")
+ .toString();
+
+ final Properties props = new Properties(2);
+ props.setProperty("artifact",fullArtifactName);
+ props.setProperty("outputDirectory", workingDir.toString());
+
+ InvocationRequest request = new DefaultInvocationRequest().setProperties(props).setGoals( Arrays.asList( "dependency:copy" ) );
+
+ Invoker invoker = new DefaultInvoker();
+ invoker.execute( request );
+
+ return workingDir.resolve(artifact.getArtifactId() + "-" + artifact.getVersion() + ".pom");
+ }
+}
diff --git a/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java
new file mode 100644
index 000000000..b5a3cc420
--- /dev/null
+++ b/idp-installer/src/test/java/net/shibboleth/idp/dependencies/ParsedPom.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.dependencies;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.beust.jcommander.internal.Nullable;
+
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import net.shibboleth.utilities.java.support.xml.XMLParserException;
+
+/**
+ *
+ */
+public class ParsedPom extends OpenSAMLInitBaseTestCase{
+
+ /** Compile dependencies - what we care about. */
+ private final List<PomArtifact> compileDependencies = new ArrayList<>();
+
+ /** BOM dependencies. */
+ private final List<PomArtifact> bomDependencies = new ArrayList<>();
+
+ /** Properties. */
+ private final Properties properties = new Properties();
+
+ /** Parent Pom .*/
+ private PomArtifact parent;
+
+ /** Us. */
+ private final PomArtifact us;
+
+ /**
+ * Constructor.
+ *
+ * @param parsers a short vut to let us parse XML
+ * @param pom the {@link Path} to the pom.
+ * @param parentOnly Do we just want the parent (and our) info
+ * @throws IOException from parsing
+ * @throws FileNotFoundException if the file doesn't exist
+ * @throws XMLParserException from parsing
+ */
+ public ParsedPom(final ParserPool parsers, final Path pom, final boolean parentOnly)
+ throws FileNotFoundException, IOException, XMLParserException {
+ Document document;
+ try (final InputStream stream = new BufferedInputStream(new FileInputStream(pom.toFile()))) {
+ document = parsers.parse(stream);
+ }
+
+ final Element el = document.getDocumentElement();
+ if (!"project".equals(el.getLocalName())) {
+ throw new XMLParserException("Top level element was not <project>");
+ }
+ final List<Element> par = ElementSupport.getChildElementsByTagName(el, "parent");
+ if (!par.isEmpty()) {
+ parseParent(par.get(0));
+ }
+
+ us = new PomArtifact(el, parent);
+
+ if (parentOnly) {
+ return;
+ }
+ final List<Element> props = ElementSupport.getChildElementsByTagName(el, "properties");
+ if (!props.isEmpty()) {
+ parseProperties(props.get(0));
+ }
+ if (!properties.contains("project.groupId") && parent != null) {
+ properties.setProperty("project.groupId", parent.groupId);
+ }
+ if (!properties.contains("project.version") && parent != null) {
+ properties.setProperty("project.version", parent.version);
+ }
+ final List<Element> dependencyMgt = ElementSupport.getChildElementsByTagName(el, "dependencyManagement");
+ if (!dependencyMgt.isEmpty()) {
+ final List<Element> dependencies = ElementSupport.getChildElementsByTagName(dependencyMgt.get(0), "dependencies");
+ parseDependencies(dependencies.get(0));
+ }
+ }
+
+ @Nonnull protected String getElementContent(final Element el) {
+ String contents = StringSupport.trimOrNull(el.getTextContent());
+ contents = Constraint.isNotNull(contents, "<" + el.getLocalName() + "> must have content");
+ // Not perfect matching but fits our needs
+ if (contents.length() > 3 && contents.startsWith("${") && contents.endsWith("}")) {
+ final String propName = contents.substring(2, contents.length()-1);
+ contents = Constraint.isNotNull(properties.getProperty(propName), propName + " is not defined");
+ }
+ return contents;
+ }
+
+ /**
+ * @param item
+ */
+ private void parseDependencies(Element item) {
+ final List<Element> dependencies = ElementSupport.getChildElementsByTagName(item, "dependency");
+
+ for (Element dependency : dependencies) {
+ final List<Element> types = ElementSupport.getChildElementsByTagName(dependency, "type");
+ if (!types.isEmpty()) {
+ final String type = StringSupport.trimOrNull(types.get(0).getTextContent());
+ if ("pom".equals(type)) {
+ bomDependencies.add(new PomArtifact(dependency));
+ continue;
+ } else if (!"jar".equals(type)) {
+ // not for us
+ continue;
+ }
+ }
+ final List<Element> scopes = ElementSupport.getChildElementsByTagName(dependency, "scope");
+ if (!scopes.isEmpty()) {
+ final String scope = StringSupport.trimOrNull(scopes.get(0).getTextContent());
+ if (!"compile".equals(scope)) {
+ // not for us
+ continue;
+ }
+ }
+ final PomArtifact dep = new PomArtifact(dependency);
+ compileDependencies.add(dep);
+ }
+ }
+
+
+ /**
+ * @param item
+ */
+ private void parseProperties(Element item) {
+
+ for (final Element child : ElementSupport.getChildElements(item)) {
+ final String name = child.getLocalName();
+ final String value = child.getTextContent();
+ properties.setProperty(name, value);
+ }
+ }
+
+
+ /**
+ * @param item
+ */
+ private void parseParent(Element item) {
+ parent = new PomArtifact(item);
+ }
+
+ /**
+ * @return Returns the compileDependencies.
+ */
+ public List<PomArtifact> getCompileDependencies() {
+ return compileDependencies;
+ }
+
+ /**
+ * @return Returns the bomDependencies.
+ */
+ public List<PomArtifact> getBomDependencies() {
+ return bomDependencies;
+ }
+
+ /** Return our artifactInformation.
+ * @return us.
+ */
+ public PomArtifact getOurInfo() {
+ return us;
+ }
+
+ /**
+ * @return Returns the parent.
+ */
+ public PomArtifact getParent() {
+ return parent;
+ }
+
+ /** Encapsulation of a <dependency> element. */
+ public class PomArtifact implements Comparable<PomArtifact>{
+
+ /** <groupId>.*/
+ @Nonnull private final String groupId;
+
+ /** <artifactId>.*/
+ @Nonnull private final String artifactId;
+
+ /** <version>.*/
+ @Nonnull private final String version;
+
+ /** <exclusions>. */
+ @Nonnull private final Set<Pair<String, String>> exclusion = new HashSet<>();
+
+ /**
+ * Constructor.
+ *
+ * @param item element to interrogate.
+ */
+ public PomArtifact(Element item) {
+ this(item, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param item element to interrogate.
+ * @param parentArtifact to inherit from
+ */
+ public PomArtifact(final Element item, final @Nullable PomArtifact parentArtifact) {
+
+ final List<Element> grps = ElementSupport.getChildElementsByTagName(item, "groupId");
+ if (grps.size() > 0) {
+ groupId = getElementContent(grps.get(0));
+ } else if (parentArtifact != null) {
+ groupId = parentArtifact.getGroupId();
+ } else {
+ Constraint.isGreaterThan(0, grps.size(), "<groupId> should exist in dependency");
+ groupId = null;
+ }
+
+ final List<Element> arts = ElementSupport.getChildElementsByTagName(item, "artifactId");
+ Constraint.isGreaterThan(0, arts.size(), "<artifactId> should exist in dependency");
+ artifactId = getElementContent(arts.get(0));
+
+ final List<Element> vers = ElementSupport.getChildElementsByTagName(item, "version");
+ if (vers.size() > 0) {
+ version = getElementContent(vers.get(0));
+ } else if (parentArtifact != null) {
+ version = parentArtifact.getVersion();
+ } else {
+ Constraint.isGreaterThan(0, vers.size(), "<version> should exist in dependency");
+ version = null;
+ }
+
+ List<Element> excls = ElementSupport.getChildElementsByTagName(item, "exclusions");
+ if (excls.size() > 0) {
+ excls = ElementSupport.getChildElementsByTagName(excls.get(0), "exclusion");
+ for (Element e : excls) {
+ List<Element> els = ElementSupport.getChildElementsByTagName(e, "groupId");
+ Constraint.isGreaterThan(0, els.size(), "<groupId> should exist in exclusion");
+ final String grp = getElementContent(els.get(0));
+ els = ElementSupport.getChildElementsByTagName(e, "artifactId");
+ Constraint.isGreaterThan(0, els.size(), "<artifactId> should exist in exclusion");
+ final String art = getElementContent(els.get(0));
+ exclusion.add(new Pair<>(grp, art));
+ }
+ }
+ }
+
+ /**
+ * @return Returns the groupId.
+ */
+ public String getGroupId() {
+ return groupId;
+ }
+
+ /**
+ * @return Returns the artifactId.
+ */
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ /**
+ * @return Returns the version.
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo(final PomArtifact o) {
+ return getArtifactId().compareTo(o.getArtifactId());
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list