You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rf...@apache.org on 2019/08/03 15:20:40 UTC

[maven-studies] branch maven-ci-extension created (now f45542d)

This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a change to branch maven-ci-extension
in repository https://gitbox.apache.org/repos/asf/maven-studies.git.


      at f45542d  Initial commit

This branch includes the following new commits:

     new f45542d  Initial commit

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[maven-studies] 01/01: Initial commit

Posted by rf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rfscholte pushed a commit to branch maven-ci-extension
in repository https://gitbox.apache.org/repos/asf/maven-studies.git

commit f45542d83492a1b0a3b3cf7cd554f7df9439698f
Author: rfscholte <rf...@apache.org>
AuthorDate: Sat Aug 3 17:20:24 2019 +0200

    Initial commit
---
 .gitignore                                         |   4 +
 pom.xml                                            |  88 +++++++++
 .../maven/extensions/ci/CleanInstallExtension.java | 207 +++++++++++++++++++++
 .../extensions/ci/CleanInstallExtensionTest.java   | 179 ++++++++++++++++++
 4 files changed, 478 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..06a84e9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target/
+/.classpath
+/.project
+/.settings
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5fa3218
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.maven.plugins</groupId>
+    <artifactId>maven-plugins</artifactId>
+    <version>33</version>
+    <relativePath />
+  </parent>
+  <groupId>org.apache.maven.extensions</groupId>
+  <artifactId>maven-ci-extension</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+
+  <prerequisites>
+    <maven>3.2.1</maven>
+  </prerequisites>
+
+  <properties>
+    <javaVersion>8</javaVersion>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.plexus</groupId>
+        <artifactId>plexus-component-metadata</artifactId>
+        <version>1.7.1</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>generate-metadata</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>3.2.1</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <version>5.5.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>3.0.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+      <version>3.0.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/extensions/ci/CleanInstallExtension.java b/src/main/java/org/apache/maven/extensions/ci/CleanInstallExtension.java
new file mode 100644
index 0000000..bed0308
--- /dev/null
+++ b/src/main/java/org/apache/maven/extensions/ci/CleanInstallExtension.java
@@ -0,0 +1,207 @@
+package org.apache.maven.extensions.ci;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.AbstractMavenLifecycleParticipant;
+import org.apache.maven.MavenExecutionException;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.logging.LogEnabled;
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * Extension that verifies if "clean install" was useful
+ * 
+ * @author Robert Scholte
+ * @since 1.0
+ */
+@Named
+@Singleton
+public class CleanInstallExtension extends AbstractMavenLifecycleParticipant implements LogEnabled
+{
+    private Path ciLog = Paths.get( System.getProperty( "user.home" ), ".m2/extensions/ci.ser" );
+    
+    private Logger logger;
+    
+    @Override
+    public void enableLogging( Logger logger )
+    {
+        this.logger = logger;
+    }
+    
+    @SuppressWarnings( "unchecked" )
+    @Override
+    public void afterSessionEnd( MavenSession session )
+        throws MavenExecutionException
+    {
+        if ( session.getResult().hasExceptions() )
+        {
+            return;
+        }
+        
+        final List<MavenProject> projects = session.getProjects(); 
+        final List<String> projectKeys = projects
+                        .stream()
+                        .map( p -> p.getGroupId() + ':' + p.getArtifactId() + ':' + p.getVersion() )
+                        .collect( Collectors.toList() );
+        
+        try
+        {
+            List<Data> originalLines;
+            if ( Files.exists( ciLog ) )
+            {
+                try ( FileInputStream fis = new FileInputStream( ciLog.toFile() );
+                      ObjectInputStream ois = new ObjectInputStream( fis ) ) 
+                {
+                    originalLines = (List<Data>) ois.readObject();
+                }
+                catch ( ClassNotFoundException e )
+                {
+                    throw new MavenExecutionException( e.getMessage(), e );
+                }
+            }
+            else
+            {
+                originalLines = Collections.emptyList();
+            }
+
+            Set<String> dependencyKeys = projects
+                            .parallelStream()
+                            .map( p -> p.getDependencies() )
+                            .flatMap( d -> d.stream() )
+                            .map( d -> d.getGroupId() + ':' + d.getArtifactId() + ':' + d.getVersion() )
+                            .collect( Collectors.toSet() );
+            
+            Function<Data, State> state = l -> 
+            {
+                List<String> keys = l.projectKeys;
+                if ( !Collections.disjoint( keys, projectKeys ) )
+                {
+                    return State.PROJECTKEY;
+                }
+                else if ( !Collections.disjoint( keys, dependencyKeys ) )
+                {
+                    return State.DEPENDENCYKEY;
+                }
+                else
+                {
+                    return State.NONE;
+                }
+            };
+            
+            Map<State, List<Data>> groupedLines = originalLines.stream()
+                .collect( Collectors.groupingBy( state ) );
+            
+            List<Data> disjoint = groupedLines.get( State.PROJECTKEY );
+            if ( !Optional.ofNullable( disjoint ).map( List::isEmpty ).orElse( true ) )
+            {
+                
+                if ( session.getGoals().contains( "install" ) )
+                {
+                    disjoint.stream().forEach( d -> 
+                    {
+                        logger.warn( "Previous 'install' on this project was unnecessary "
+                            + "and polluted your local repository" );
+                        logger.warn( "A good Maven citizen uses 'verify' instead" );
+                    } );
+                }
+                else
+                {
+                    // learning...
+                    disjoint.stream().forEach( d -> logger.warn( "Previous 'install' on this project was unnecessary, "
+                        + "but you're learning. Well done!" ) );
+                }
+            }
+
+            List<Data> newLines = groupedLines.getOrDefault( State.NONE, new ArrayList<>( 1 ) );
+            if ( session.getGoals().contains( "install" ) )
+            {
+                Data data = new Data();
+                data.startTime = session.getStartTime().getTime();
+                data.executionRootDirectory = session.getExecutionRootDirectory();
+                data.goals = session.getGoals();
+                data.projectKeys = projectKeys;
+                newLines.add( data );
+            }
+            
+            if ( !( newLines.isEmpty() && originalLines.isEmpty() ) )
+            {
+                if ( !Files.exists( ciLog ) )
+                {
+                    Files.createDirectories( ciLog.getParent() );
+                }
+                
+                try ( FileOutputStream fos = new FileOutputStream( ciLog.toFile() );
+                      ObjectOutputStream oos = new ObjectOutputStream( fos ) ) 
+                {
+                    oos.writeObject( newLines );
+                }
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new MavenExecutionException( e.getMessage(), e );
+        }
+    }
+    
+    // for testing purpose
+    void setCiLog( Path ciLog )
+    {
+        this.ciLog = ciLog;
+    }
+    
+    private enum State 
+    {
+       PROJECTKEY, DEPENDENCYKEY, NONE    
+    }
+    
+    private static class Data implements Serializable 
+    {
+        private String executionRootDirectory;
+        
+        private List<String> goals;
+
+        private long startTime;
+        
+        private List<String> projectKeys;
+    }
+}
diff --git a/src/test/java/org/apache/maven/extensions/ci/CleanInstallExtensionTest.java b/src/test/java/org/apache/maven/extensions/ci/CleanInstallExtensionTest.java
new file mode 100644
index 0000000..35f4ebf
--- /dev/null
+++ b/src/test/java/org/apache/maven/extensions/ci/CleanInstallExtensionTest.java
@@ -0,0 +1,179 @@
+package org.apache.maven.extensions.ci;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.logging.Logger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith( MockitoExtension.class )
+class CleanInstallExtensionTest
+{
+    private CleanInstallExtension extension;
+
+    @Mock
+    private MavenSession session;
+
+    @Mock
+    private MavenExecutionRequest request;
+
+    @Mock
+    private MavenExecutionResult result;
+    
+    @Mock
+    private Logger logger;
+
+    @BeforeEach
+    public void setup()
+    {
+        extension = new CleanInstallExtension();
+        extension.enableLogging( logger );
+        when( session.getResult() ).thenReturn( result );
+    }
+
+    @Test
+    void firstTimeNoInstall()
+        throws Exception
+    {
+        Path ciLog = Paths.get( "target/tests/unit/start1.ser" );
+        extension.setCiLog( ciLog );
+        List<MavenProject> projects = Arrays.asList( newMavenProject( "G", "A", "V" ) );
+        when( session.getProjects() ).thenReturn( projects );
+
+        extension.afterSessionEnd( session );
+
+        assertTrue( Files.notExists( ciLog ) );
+        verifyZeroInteractions( logger );
+    }
+
+    @Test
+    void firstTimeInstall()
+        throws Exception
+    {
+        Path ciLog = Paths.get( "target/tests/unit/start2.ser" );
+        Files.deleteIfExists( ciLog );
+        
+        extension.setCiLog( ciLog );
+        List<MavenProject> projects = Arrays.asList( newMavenProject( "G", "A", "V" ) );
+        when( session.getProjects() ).thenReturn( projects );
+        when( session.getGoals() ).thenReturn( Arrays.asList( "clean", "install" ) );
+        when( session.getStartTime() ).thenReturn( new Date() );
+
+        extension.afterSessionEnd( session );
+
+        assertTrue( Files.exists( ciLog ) );
+        
+        verifyZeroInteractions( logger );
+    }
+
+    @Test
+    void classic()
+        throws Exception
+    {
+        Path ciLog = Paths.get( "target/tests/unit/start2.ser" );
+        Files.deleteIfExists( ciLog );
+        
+        extension.setCiLog( ciLog );
+        List<MavenProject> projects = Arrays.asList( newMavenProject( "G", "A", "V" ) );
+        when( session.getProjects() ).thenReturn( projects );
+        when( session.getGoals() ).thenReturn( Arrays.asList( "clean", "install" ) );
+        when( session.getStartTime() ).thenReturn( new Date() );
+
+        extension.afterSessionEnd( session );
+        extension.afterSessionEnd( session );
+
+        assertTrue( Files.exists( ciLog ) );
+        
+        verify( logger, atLeastOnce() ).warn( anyString() );
+    }
+
+    @Test
+    void localTest()
+        throws Exception
+    {
+        Path ciLog = Paths.get( "target/tests/unit/localtest.ser" );
+        Files.deleteIfExists( ciLog );
+        
+        extension.setCiLog( ciLog );
+        MavenProject lib = newMavenProject( "G", "lib", "V" );
+        List<MavenProject> libList = Arrays.asList( lib );
+        
+        MavenProject app = newMavenProject( "G", "app", "V" );
+        List<Dependency> appDeps = Arrays.asList( newDependency( "G", "lib", "V" ) );
+        when( app.getDependencies() ).thenReturn( appDeps );
+        List<MavenProject> appList = Arrays.asList( app );
+        
+        when( session.getProjects() ).thenReturn( libList ).thenReturn( appList ).thenReturn( libList );
+        when( session.getGoals() ).thenReturn( Arrays.asList( "clean", "install" ) );
+        when( session.getStartTime() ).thenReturn( new Date() );
+
+        extension.afterSessionEnd( session );
+        extension.afterSessionEnd( session );
+        extension.afterSessionEnd( session );
+
+        assertTrue( Files.exists( ciLog ) );
+        
+        verifyZeroInteractions( logger );
+    }
+
+    private MavenProject newMavenProject( String groupId, String artifactId, String version )
+    {
+        MavenProject project = mock( MavenProject.class );
+        when( project.getGroupId() ).thenReturn( groupId );
+        when( project.getArtifactId() ).thenReturn( artifactId );
+        when( project.getVersion() ).thenReturn( version );
+        return project;
+    }
+
+    private Dependency newDependency( String groupId, String artifactId, String version )
+    {
+        Dependency dependency = new Dependency();
+        dependency.setGroupId( groupId );
+        dependency.setArtifactId( artifactId );
+        dependency.setVersion( version );
+        return dependency;
+    }
+
+    
+    
+}