[java-identity-provider] 01/07: JPAR-175 Initial pass at dependency analysis test.

Rod Widdowson rdw at steadingsoftware.com
Tue Jun 22 09:23:50 UTC 2021


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=24db229f641323b5fa4080d0564a7e7c6adb77f0

commit 24db229f641323b5fa4080d0564a7e7c6adb77f0
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