You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@continuum.apache.org by ba...@apache.org on 2015/04/10 02:00:59 UTC

svn commit: r1672519 - in /continuum/trunk/continuum-buildagent/continuum-buildagent-core/src: main/java/org/apache/continuum/buildagent/manager/ test/java/org/apache/continuum/buildagent/manager/

Author: batkinson
Date: Fri Apr 10 00:00:59 2015
New Revision: 1672519

URL: http://svn.apache.org/r1672519
Log:
Refactored agent directory purging out of purge manager, clarified tests.

Added:
    continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DistributedDirectoryPurgeExecutorFactory.java
Modified:
    continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DefaultBuildAgentPurgeManager.java
    continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/test/java/org/apache/continuum/buildagent/manager/BuildAgentPurgeManagerTest.java

Modified: continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DefaultBuildAgentPurgeManager.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DefaultBuildAgentPurgeManager.java?rev=1672519&r1=1672518&r2=1672519&view=diff
==============================================================================
--- continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DefaultBuildAgentPurgeManager.java (original)
+++ continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DefaultBuildAgentPurgeManager.java Fri Apr 10 00:00:59 2015
@@ -19,18 +19,12 @@ package org.apache.continuum.buildagent.
  * under the License.
  */
 
-import org.apache.commons.io.comparator.LastModifiedFileComparator;
-import org.apache.commons.io.filefilter.AgeFileFilter;
-import org.apache.commons.io.filefilter.AndFileFilter;
-import org.apache.commons.io.filefilter.DirectoryFileFilter;
-import org.apache.commons.io.filefilter.IOFileFilter;
-import org.apache.commons.io.filefilter.NotFileFilter;
-import org.apache.commons.io.filefilter.WildcardFileFilter;
 import org.apache.continuum.buildagent.configuration.BuildAgentConfigurationException;
 import org.apache.continuum.buildagent.configuration.BuildAgentConfigurationService;
 import org.apache.continuum.buildagent.model.LocalRepository;
 import org.apache.continuum.purge.executor.ContinuumPurgeExecutor;
 import org.apache.continuum.purge.executor.ContinuumPurgeExecutorException;
+import org.apache.continuum.purge.executor.DirectoryPurgeExecutorFactory;
 import org.apache.continuum.purge.executor.RepositoryPurgeExecutorFactory;
 import org.apache.continuum.purge.repository.content.RepositoryManagedContent;
 import org.apache.continuum.purge.repository.content.RepositoryManagedContentFactory;
@@ -38,14 +32,10 @@ import org.apache.continuum.utils.m2.Loc
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.component.annotations.Requirement;
 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
-import org.codehaus.plexus.util.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.util.Arrays;
 
 @Component( role = org.apache.continuum.buildagent.manager.BuildAgentPurgeManager.class, hint = "default" )
 public class DefaultBuildAgentPurgeManager
@@ -56,6 +46,9 @@ public class DefaultBuildAgentPurgeManag
     @Requirement
     private BuildAgentConfigurationService buildAgentConfigurationService;
 
+    @Requirement( hint = "distributed" )
+    private DirectoryPurgeExecutorFactory dirExecutorFactory;
+
     @Requirement
     private RepositoryPurgeExecutorFactory repoExecutorFactory;
 
@@ -66,123 +59,37 @@ public class DefaultBuildAgentPurgeManag
     private LocalRepositoryHelper localRepositoryHelper;
 
     public void executeDirectoryPurge( String directoryType, int daysOlder, int retentionCount, boolean deleteAll )
-        throws Exception
-    {
-        if ( "working".equals( directoryType ) || "releases".equals( directoryType ) )
-        {
-            File directory = buildAgentConfigurationService.getWorkingDirectory();
-            String path = directory.getPath();
-            log.info( "purging directory '{}' [type={},full={},age={},retain={}]",
-                      new Object[] { path, directoryType, deleteAll, daysOlder, retentionCount } );
-            if ( deleteAll )
-            {
-                purgeAll( directory, directoryType );
-            }
-            else
-            {
-                purgeFiles( directory, directoryType, daysOlder, retentionCount );
-            }
-            log.info( "purge completed '{}'", path );
-        }
-        else
-        {
-            log.warn( "ignoring directory purge, directory type {} is invalid.", directoryType );
-        }
-
-    }
-
-    private void purgeAll( File directory, String directoryType )
-        throws Exception
-    {
-        AndFileFilter filter = new AndFileFilter();
-        filter.addFileFilter( DirectoryFileFilter.DIRECTORY );
-        filter.addFileFilter( createFileFilterForDirectoryType( directoryType ) );
-
-        File[] files = directory.listFiles( (FileFilter) filter );
-        if ( files == null )
-        {
-            return;
-        }
-        for ( File file : files )
-        {
-            try
-            {
-                FileUtils.deleteDirectory( file );
-            }
-            catch ( IOException e )
-            {
-                log.warn( "failed to purge {} directory {}: {}",
-                          new Object[] { directoryType, file.getName(), e.getMessage() } );
-            }
-        }
-    }
-
-    private void purgeFiles( File directory, String directoryType, int daysOlder, int retentionCount )
+        throws ContinuumPurgeExecutorException
     {
-        AndFileFilter filter = new AndFileFilter();
-        filter.addFileFilter( DirectoryFileFilter.DIRECTORY );
-        filter.addFileFilter( createFileFilterForDirectoryType( directoryType ) );
-
-        File[] files = directory.listFiles( (FileFilter) filter );
-
-        if ( files == null )
-        {
-            return;
-        }
-
-        //calculate to include files not in the dayold category
-        int countToPurge = files.length - retentionCount;
-
-        if ( daysOlder > 0 )
-        {
-            long cutoff = System.currentTimeMillis() - ( 24 * 60 * 26 * 1000 * daysOlder );
-            filter.addFileFilter( new AgeFileFilter( cutoff ) );
-        }
-
-        files = directory.listFiles( (FileFilter) filter );
-
-        if ( files == null )
-        {
-            return;
-        }
-
-        Arrays.sort( files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR );
-
-        for ( File file : files )
-        {
-            if ( countToPurge - 1 < 0 )
-            {
-                break;
-            }
-            try
-            {
-                FileUtils.deleteDirectory( file );
-                countToPurge--;
-            }
-            catch ( IOException e )
-            {
-                log.warn( "failed to purge {} directory {}: {}",
-                          new Object[] { directoryType, file.getName(), e.getMessage() } );
-            }
-        }
-
+        File directory = buildAgentConfigurationService.getWorkingDirectory();
+        String path = directory.getAbsolutePath();
+        ContinuumPurgeExecutor executor =
+            dirExecutorFactory.create( deleteAll, daysOlder, retentionCount, directoryType );
+        log.info( "purging directory '{}' [type={},full={},age={},retain={}]",
+                  new Object[] { path, directoryType, deleteAll, daysOlder, retentionCount } );
+        executor.purge( path );
+        log.info( "purge completed '{}'", path );
     }
 
-    private IOFileFilter createFileFilterForDirectoryType( String directoryType )
+    public void executeRepositoryPurge( String repoName, int daysOlder, int retentionCount, boolean deleteAll,
+                                        boolean deleteReleasedSnapshots )
+        throws ContinuumPurgeExecutorException
     {
-        WildcardFileFilter releasesFilter = new WildcardFileFilter( "releases-*" );
-
-        if ( "working".equals( directoryType ) )
-        {
-            return new NotFileFilter( releasesFilter );
-        }
-        else if ( "releases".equals( directoryType ) )
+        try
         {
-            return releasesFilter;
+            LocalRepository localRepo = buildAgentConfigurationService.getLocalRepositoryByName( repoName );
+            String path = localRepo.getLocation(), layout = localRepo.getLayout();
+            RepositoryManagedContent managedContent = getManagedContent( localRepo );
+            ContinuumPurgeExecutor executor = repoExecutorFactory.create( deleteAll, daysOlder, retentionCount,
+                                                                          deleteReleasedSnapshots, managedContent );
+            log.info( "purging repo '{}' [full={},age={},retain={},snapshots={},layout={}]",
+                      new Object[] { path, deleteAll, daysOlder, retentionCount, deleteReleasedSnapshots, layout } );
+            executor.purge( path );
+            log.info( "purge completed '{}'", path );
         }
-        else
+        catch ( BuildAgentConfigurationException e )
         {
-            return null;
+            log.warn( "ignoring repository purge, check agent repo configuration: {}", e.getMessage() );
         }
     }
 
@@ -203,28 +110,6 @@ public class DefaultBuildAgentPurgeManag
         }
     }
 
-    public void executeRepositoryPurge( String repoName, int daysOlder, int retentionCount, boolean deleteAll,
-                                        boolean deleteReleasedSnapshots )
-        throws ContinuumPurgeExecutorException
-    {
-        try
-        {
-            LocalRepository localRepo = buildAgentConfigurationService.getLocalRepositoryByName( repoName );
-            String path = localRepo.getLocation(), layout = localRepo.getLayout();
-            RepositoryManagedContent managedContent = getManagedContent( localRepo );
-            ContinuumPurgeExecutor executor = repoExecutorFactory.create( deleteAll, daysOlder, retentionCount,
-                                                                          deleteReleasedSnapshots, managedContent );
-            log.info( "purging repo '{}' [full={},age={},retain={},snapshots={},layout={}]",
-                      new Object[] { path, deleteAll, daysOlder, retentionCount, deleteReleasedSnapshots, layout } );
-            executor.purge( path );
-            log.info( "purge completed '{}'", path );
-        }
-        catch ( BuildAgentConfigurationException e )
-        {
-            log.warn( "ignoring repository purge, check agent repo configuration: {}", e.getMessage() );
-        }
-    }
-
     public void setBuildAgentConfigurationService( BuildAgentConfigurationService buildAgentConfigurationService )
     {
         this.buildAgentConfigurationService = buildAgentConfigurationService;

Added: continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DistributedDirectoryPurgeExecutorFactory.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DistributedDirectoryPurgeExecutorFactory.java?rev=1672519&view=auto
==============================================================================
--- continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DistributedDirectoryPurgeExecutorFactory.java (added)
+++ continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/manager/DistributedDirectoryPurgeExecutorFactory.java Fri Apr 10 00:00:59 2015
@@ -0,0 +1,281 @@
+package org.apache.continuum.buildagent.manager;
+
+/*
+ * 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 org.apache.commons.io.comparator.LastModifiedFileComparator;
+import org.apache.commons.io.filefilter.AgeFileFilter;
+import org.apache.commons.io.filefilter.AndFileFilter;
+import org.apache.commons.io.filefilter.DirectoryFileFilter;
+import org.apache.commons.io.filefilter.NotFileFilter;
+import org.apache.commons.io.filefilter.WildcardFileFilter;
+import org.apache.continuum.purge.executor.ContinuumPurgeExecutor;
+import org.apache.continuum.purge.executor.ContinuumPurgeExecutorException;
+import org.apache.continuum.purge.executor.DirectoryPurgeExecutorFactory;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Enables creation of purge executors for distributed agent working directories.
+ */
+@Component( role = DirectoryPurgeExecutorFactory.class, hint = "distributed" )
+public class DistributedDirectoryPurgeExecutorFactory
+    implements DirectoryPurgeExecutorFactory
+{
+    private Set<String> supportedTypes = new HashSet<String>();
+
+    public DistributedDirectoryPurgeExecutorFactory()
+    {
+        supportedTypes.add( AbstractPurgeExecutor.RELEASE_TYPE );
+        supportedTypes.add( AbstractPurgeExecutor.WORKING_TYPE );
+    }
+
+    /**
+     * Creates an {@link ContinuumPurgeExecutor} appropriate for the specified parameters.
+     *
+     * @param deleteAll      whether to delete all files in the directory, when true other params are ignored
+     * @param daysOld        file age in days, only files older than this will be considered for deletion
+     * @param retentionCount number of of deletion candidates to keep
+     * @param dirType        directory type considered, one of "working" or "releases", others are safely ignored.
+     * @return an executor, safe for a single use
+     */
+    public ContinuumPurgeExecutor create( boolean deleteAll, int daysOld, int retentionCount, String dirType )
+    {
+        if ( supportedTypes.contains( dirType ) )
+        {
+            if ( deleteAll )
+            {
+                return new DeleteAllPurgeExecutor( dirType );
+            }
+            return new DefaultPurgeExecutor( daysOld, retentionCount, dirType );
+        }
+        return new UnsupportedPurgeExecutor( dirType );
+    }
+}
+
+/**
+ * Encapsulates how to purge both working and release directories. This can and probably should be simplified more and
+ * combined with the code in master into a single file system purge executor.
+ */
+abstract class AbstractPurgeExecutor
+    implements ContinuumPurgeExecutor
+{
+    private static final Logger log = LoggerFactory.getLogger( AbstractPurgeExecutor.class );
+
+    public static final String WORKING_TYPE = "working";
+
+    public static final String RELEASE_TYPE = "releases";
+
+    protected String type;
+
+    AbstractPurgeExecutor( String dirType )
+    {
+        this.type = dirType;
+    }
+
+    public void purge( String path )
+    {
+        File directory = new File( path );
+        if ( !directory.exists() )
+        {
+            log.warn( "skipping purge, directory '{}' does not exist", directory );
+            return;
+        }
+        if ( !directory.isDirectory() )
+        {
+            log.warn( "skipping purge, specified path '{}' is not a directory" );
+            return;
+        }
+        purgeDir( directory );
+    }
+
+    abstract void purgeDir( File directory );
+
+    /**
+     * Lists files in the given directory, using the specified filter.
+     *
+     * @param directory the directory to list
+     * @param filter    the filter to use on the list
+     * @return an non-null array of resulting files
+     */
+    protected File[] listFiles( File directory, FileFilter filter )
+    {
+        File[] files = directory.listFiles( filter );
+        if ( files == null )
+        {
+            return new File[] {};
+        }
+        return files;
+    }
+
+    /**
+     * Creates an extensible filter based on the rules for supported distributed directory types:
+     * <ul>
+     * <li>releases - directories named releases-*</li>
+     * <li>working - directories not named releases-*</li>
+     * </ul>
+     *
+     * @param directoryType one of "working" or "releases"
+     * @return a configured AndFileFilter, which can be augmented by adding additional filters
+     */
+    protected AndFileFilter createFilter( String directoryType )
+    {
+        AndFileFilter resultFilter = new AndFileFilter();
+        resultFilter.addFileFilter( DirectoryFileFilter.DIRECTORY );
+        WildcardFileFilter releasesFilter = new WildcardFileFilter( "releases-*" );
+        if ( WORKING_TYPE.equals( directoryType ) )
+        {
+            resultFilter.addFileFilter( new NotFileFilter( releasesFilter ) );
+        }
+        else
+        {
+            resultFilter.addFileFilter( releasesFilter );
+        }
+        return resultFilter;
+    }
+}
+
+class DeleteAllPurgeExecutor
+    extends AbstractPurgeExecutor
+{
+    private static final Logger log = LoggerFactory.getLogger( DeleteAllPurgeExecutor.class );
+
+    DeleteAllPurgeExecutor( String dirType )
+    {
+        super( dirType );
+    }
+
+    public void purgeDir( File directory )
+    {
+        for ( File file : listFiles( directory, type ) )
+        {
+            try
+            {
+                FileUtils.deleteDirectory( file );
+            }
+            catch ( IOException e )
+            {
+                log.warn( "failed to purge {} directory {}: {}",
+                          new Object[] { type, file.getName(), e.getMessage() } );
+            }
+        }
+    }
+
+    private File[] listFiles( File directory, String directoryType )
+    {
+        return listFiles( directory, createFilter( directoryType ) );
+    }
+}
+
+class DefaultPurgeExecutor
+    extends AbstractPurgeExecutor
+{
+    private static Logger log = LoggerFactory.getLogger( DefaultPurgeExecutor.class );
+
+    private int daysOld;
+
+    private int retentionCount;
+
+    DefaultPurgeExecutor( int daysOld, int retentionCount, String dirType )
+    {
+        super( dirType );
+        this.daysOld = daysOld;
+        this.retentionCount = retentionCount;
+    }
+
+    public void purgeDir( File directory )
+    {
+        purgeFiles( directory );
+    }
+
+    private void purgeFiles( File directory )
+    {
+        File[] files = listFiles( directory, type, daysOld );
+        int remaining = files.length - retentionCount;
+        for ( File file : files )
+        {
+            if ( remaining <= 0 )
+            {
+                break;
+            }
+            try
+            {
+                FileUtils.deleteDirectory( file );
+                remaining--;
+            }
+            catch ( IOException e )
+            {
+                log.warn( "failed to purge {} directory {}: {}",
+                          new Object[] { type, file.getName(), e.getMessage() } );
+            }
+        }
+    }
+
+    /**
+     * Returns a sorted list of files for the specified directory type and age.
+     *
+     * @param directory     the directory to list
+     * @param directoryType one of "working" or "releases"
+     * @param daysOld       min age of file in days to accept, 0 to disable age filtering
+     * @return non-null array of files, sorted from oldest-to-newest
+     */
+    private File[] listFiles( File directory, String directoryType, int daysOld )
+    {
+        AndFileFilter filter = createFilter( directoryType );
+        if ( daysOld > 0 )
+        {
+            long cutoff = System.currentTimeMillis() - ( 24 * 60 * 26 * 1000 * daysOld );
+            filter.addFileFilter( new AgeFileFilter( cutoff ) );
+        }
+        File[] files = listFiles( directory, filter );
+        Arrays.sort( files, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR );
+        return files;
+    }
+}
+
+/**
+ * A stub executor for handling unknown directory types.
+ */
+class UnsupportedPurgeExecutor
+    implements ContinuumPurgeExecutor
+{
+    private static Logger log = LoggerFactory.getLogger( UnsupportedPurgeExecutor.class );
+
+    private String type;
+
+    UnsupportedPurgeExecutor( String dirType )
+    {
+        this.type = dirType;
+    }
+
+    public void purge( String path )
+        throws ContinuumPurgeExecutorException
+    {
+        log.warn( "ignoring directory purge, directory type {} is not supported.", type );
+    }
+}
\ No newline at end of file

Modified: continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/test/java/org/apache/continuum/buildagent/manager/BuildAgentPurgeManagerTest.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/test/java/org/apache/continuum/buildagent/manager/BuildAgentPurgeManagerTest.java?rev=1672519&r1=1672518&r2=1672519&view=diff
==============================================================================
--- continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/test/java/org/apache/continuum/buildagent/manager/BuildAgentPurgeManagerTest.java (original)
+++ continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/test/java/org/apache/continuum/buildagent/manager/BuildAgentPurgeManagerTest.java Fri Apr 10 00:00:59 2015
@@ -26,11 +26,10 @@ import org.codehaus.plexus.spring.Plexus
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
 import java.util.Date;
-import java.util.List;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * For CONTINUUM-2658 tests, Support purging of working and release directories of build agents on a schedule
@@ -38,19 +37,23 @@ import static org.mockito.Mockito.*;
 public class BuildAgentPurgeManagerTest
     extends PlexusInSpringTestCase
 {
-    private static final int DAYS_OLD = 2;
+    private static final int AGE = 2;
 
-    private static final int RELEASES_COUNT = 5;
+    private static final int NONE = 0;
 
-    private static final int RELEASES_DAYS_OLD_COUNT = 3;
+    private static final int RELEASE_DIRS = 5;
 
-    private static final int WORKING_COUNT = 10;
+    private static final int OLD_RELEASE_DIRS = 3;
 
-    private static final int WORKING_DAYS_OLD_COUNT = 9;
+    private static final int WORKING_DIRS = 10;
 
-    private static final String DIRECTORY_TYPE_RELEASES = "releases";
+    private static final int OLD_WORKING_DIRS = 9;
 
-    private static final String DIRECTORY_TYPE_WORKING = "working";
+    private static final int REGULAR_FILES = 2;
+
+    public static final int TOTAL_FILES_AND_DIRS = RELEASE_DIRS + WORKING_DIRS + REGULAR_FILES;
+
+    private static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
 
     private BuildAgentConfigurationService buildAgentConfigurationService;
 
@@ -69,7 +72,9 @@ public class BuildAgentPurgeManagerTest
 
         purgeManager.setBuildAgentConfigurationService( buildAgentConfigurationService );
 
-        createTestDirectoriesAndFiles();
+        createTestFiles();
+
+        assertEquals( TOTAL_FILES_AND_DIRS, tempDir.list().length );
 
         when( buildAgentConfigurationService.getWorkingDirectory() ).thenReturn( tempDir );
     }
@@ -78,106 +83,132 @@ public class BuildAgentPurgeManagerTest
         throws Exception
     {
         purgeManager = null;
-        cleanUpTestDirectoriesAndFiles();
+        cleanUpTestFiles();
         super.tearDown();
     }
 
     // CONTINUUM-2658
-    public void testCleanAllPurge()
+    public void testAllWorking()
         throws Exception
     {
-        //confirm current content of directory
-        //2 random files
-        assertEquals( RELEASES_COUNT + WORKING_COUNT + 2, tempDir.list().length );
-
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_WORKING, 1, 1, true );
-
-        //confirm current content of directory
-        //working directories deleted
-        assertEquals( RELEASES_COUNT + 2, tempDir.list().length );
-
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_RELEASES, 1, 1, true );
-
-        //confirm current content of directory
-        //releases directories deleted
-        assertEquals( 2, tempDir.list().length );
+        int ignored = 1;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, ignored, ignored, true );
+        assertEquals( RELEASE_DIRS + REGULAR_FILES, fileCount() );
     }
 
-    public void testRetentionOnlyPurge()
+    // CONTINUUM-2658
+    public void testAllReleases()
         throws Exception
     {
-        //confirm current content of directory
-        //2 random files
-        assertEquals( RELEASES_COUNT + WORKING_COUNT + 2, tempDir.list().length );
-
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_WORKING, 0, 2, false );
-
-        List<String> fileNames = Arrays.asList( tempDir.list() );
-
-        //confirm current content of directory
-        //2 working directories left
-        assertEquals( RELEASES_COUNT + 2 + 2, fileNames.size() );
-
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_RELEASES, 0, 4, false );
-
-        fileNames = Arrays.asList( tempDir.list() );
-
-        //confirm current content of directory
-        //4 releases directories left
-        assertEquals( 4 + 2 + 2, fileNames.size() );
+        int ignored = 1;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, ignored, ignored, true );
+        assertEquals( WORKING_DIRS + REGULAR_FILES, fileCount() );
     }
 
-    public void testDaysOldOnlyPurge()
+    // CONTINUUM-2658
+    public void testAllWorkingAndReleases()
         throws Exception
     {
-        //confirm current content of directory
-        //2 random files
-        assertEquals( RELEASES_COUNT + WORKING_COUNT + 2, tempDir.list().length );
-
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_WORKING, 1, 0, false );
-
-        List<String> fileNames = Arrays.asList( tempDir.list() );
-
-        //confirm current content of directory
-        //days old directories are deleted
-        assertEquals( RELEASES_COUNT + ( WORKING_COUNT - WORKING_DAYS_OLD_COUNT ) + 2, fileNames.size() );
+        int ignored = 1;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, ignored, ignored, true );
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, ignored, ignored, true );
+        assertEquals( REGULAR_FILES, fileCount() );
+    }
 
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_RELEASES, 1, 0, false );
+    public void testRetentionOnlyWorking()
+        throws Exception
+    {
+        int retainedWorking = 2;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, NONE, retainedWorking, false );
+        assertEquals( RELEASE_DIRS + REGULAR_FILES + retainedWorking, fileCount() );
+    }
 
-        fileNames = Arrays.asList( tempDir.list() );
+    public void testRetentionOnlyReleases()
+        throws Exception
+    {
+        int retainedReleases = 4;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, NONE, retainedReleases, false );
+        assertEquals( WORKING_DIRS + retainedReleases + REGULAR_FILES, fileCount() );
+    }
 
-        //confirm current content of directory
-        //days old directories are deleted
-        assertEquals( ( RELEASES_COUNT - RELEASES_DAYS_OLD_COUNT ) + ( WORKING_COUNT - WORKING_DAYS_OLD_COUNT ) + 2,
-                      fileNames.size() );
+    public void testRetentionOnlyWorkingAndReleases()
+        throws Exception
+    {
+        int retainedReleases = 4, retainedWorking = 2;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, NONE, retainedWorking, false );
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, NONE, retainedReleases, false );
+        assertEquals( retainedWorking + retainedReleases + REGULAR_FILES, fileCount() );
     }
 
-    public void testRetentionAndDaysOldOnlyPurge()
+    public void testDaysOldOnlyWorking()
         throws Exception
     {
-        //confirm current content of directory
-        //2 random files
-        assertEquals( RELEASES_COUNT + WORKING_COUNT + 2, tempDir.list().length );
+        int maxAge = 1, ineligibleWorkDirs = WORKING_DIRS - OLD_WORKING_DIRS;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, maxAge, NONE, false );
+        assertEquals( RELEASE_DIRS + ineligibleWorkDirs + REGULAR_FILES, fileCount() );
+    }
 
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_WORKING, 1, 5, false );
+    public void testDaysOldOnlyReleases()
+        throws Exception
+    {
+        int maxAge = 1, ineligibleReleaseDirs = RELEASE_DIRS - OLD_RELEASE_DIRS;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, maxAge, NONE, false );
+        assertEquals( WORKING_DIRS + ineligibleReleaseDirs + REGULAR_FILES, fileCount() );
+    }
 
-        List<String> fileNames = Arrays.asList( tempDir.list() );
+    public void testDaysOldOnlyWorkingAndReleases()
+        throws Exception
+    {
+        int maxAge = 1;
+        int ineligibleWorkDirs = WORKING_DIRS - OLD_WORKING_DIRS;
+        int ineligibleReleaseDirs = RELEASE_DIRS - OLD_RELEASE_DIRS;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, maxAge, NONE, false );
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, maxAge, NONE, false );
+        assertEquals( ineligibleWorkDirs + ineligibleReleaseDirs + REGULAR_FILES, fileCount() );
+    }
 
-        //confirm current content of directory
-        //days old directories are deleted
-        assertEquals( RELEASES_COUNT + Math.max( 5, WORKING_COUNT - WORKING_DAYS_OLD_COUNT ) + 2, fileNames.size() );
+    public void testRetentionAndDaysOldWorking()
+        throws Exception
+    {
+        int maxAge = 1;
+        int retainWorking = 5;
+        int ineligibleWorkDirs = WORKING_DIRS - OLD_WORKING_DIRS;
+        int expectedWorkDirs = retainWorking + ineligibleWorkDirs;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, maxAge, retainWorking, false );
+        assertEquals( RELEASE_DIRS + expectedWorkDirs + REGULAR_FILES, fileCount() );
+    }
 
-        purgeManager.executeDirectoryPurge( DIRECTORY_TYPE_RELEASES, 1, 1, false );
+    public void testRetentionAndDaysOldReleases()
+        throws Exception
+    {
+        int maxAge = 1;
+        int retainRelease = 1;
+        int ineligibleReleaseDirs = RELEASE_DIRS - OLD_RELEASE_DIRS;
+        int expectedReleaseDirs = retainRelease + ineligibleReleaseDirs;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, maxAge, retainRelease, false );
+        assertEquals( WORKING_DIRS + expectedReleaseDirs + REGULAR_FILES, fileCount() );
+    }
 
-        fileNames = Arrays.asList( tempDir.list() );
+    public void testRetentionAndDaysOldWorkingAndReleases()
+        throws Exception
+    {
+        int maxAge = 1;
+        int retainWorking = 5, retainRelease = 1;
+        int ineligibleWorkDirs = WORKING_DIRS - OLD_WORKING_DIRS;
+        int ineligibleReleaseDirs = RELEASE_DIRS - OLD_RELEASE_DIRS;
+        int expectedWorkDirs = retainWorking + ineligibleWorkDirs;
+        int expectedReleaseDirs = retainRelease + ineligibleReleaseDirs;
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.WORKING_TYPE, maxAge, retainWorking, false );
+        purgeManager.executeDirectoryPurge( AbstractPurgeExecutor.RELEASE_TYPE, maxAge, retainRelease, false );
+        assertEquals( expectedReleaseDirs + expectedWorkDirs + REGULAR_FILES, fileCount() );
+    }
 
-        //confirm current content of directory
-        //days old directories are deleted
-        assertEquals( Math.max( 1, RELEASES_COUNT - RELEASES_DAYS_OLD_COUNT ) + Math.max( 5, WORKING_COUNT -
-            WORKING_DAYS_OLD_COUNT ) + 2, fileNames.size() );
+    private int fileCount()
+    {
+        return tempDir.list().length;
     }
 
-    private void createTestDirectoriesAndFiles()
+    private void createTestFiles()
         throws Exception
     {
         SimpleDateFormat format = new SimpleDateFormat( "yyyyMMddHHmmss" );
@@ -187,70 +218,56 @@ public class BuildAgentPurgeManagerTest
         {
             throw new IOException( "Unable to create test directory: " + tempDir.getName() );
         }
+        createReleasesDirectories( RELEASE_DIRS, AGE, OLD_RELEASE_DIRS );
+        createWorkingDirectories( WORKING_DIRS, AGE, OLD_WORKING_DIRS );
+        createRegularFile( "random.txt" );
+        createRegularFile( "releases-random.txt" );
+    }
 
-        createReleasesDirectories( tempDir, RELEASES_COUNT, DAYS_OLD, RELEASES_DAYS_OLD_COUNT );
-        createWorkingDirectories( tempDir, WORKING_COUNT, DAYS_OLD, WORKING_DAYS_OLD_COUNT );
-        createRandomFile( tempDir, "random.txt" );
-        createRandomFile( tempDir, "releases-random.txt" );
+    private void cleanUpTestFiles()
+        throws IOException
+    {
+        FileUtils.deleteDirectory( tempDir );
     }
 
-    private void createReleasesDirectories( File parentDir, int count, int daysOld, int daysOldCount )
+    private void createReleasesDirectories( int count, int daysOld, int daysOldCount )
         throws Exception
     {
-        int daysOldIndex = 0;
-        for ( int x = 1; x <= count; x++ )
-        {
-            File file = new File( tempDir.getAbsolutePath() + System.getProperty( "file.separator" ) + "releases-" +
-                                      x );
-            if ( !file.mkdirs() )
-            {
-                throw new IOException( "Unable to create test directory: " + file.getName() );
-            }
-            if ( daysOldIndex < daysOldCount )
-            {
-                long daysOldTime = System.currentTimeMillis() - 24 * 60 * 60 * 1000 * daysOld;
-                file.setLastModified( daysOldTime );
-                daysOldIndex++;
-            }
+        createDirectories( "releases-", count, daysOld, daysOldCount );
+    }
 
-        }
+    private void createWorkingDirectories( int count, int daysOld, int daysOldCount )
+        throws IOException
+    {
+        createDirectories( "", count, daysOld, daysOldCount );
     }
 
-    private void createWorkingDirectories( File parentDir, int count, int daysOld, int daysOldCount )
-        throws Exception
+    private void createDirectories( String namePrefix, int count, int age, int toAge )
+        throws IOException
     {
-        int daysOldIndex = 0;
+        long generationStart = System.currentTimeMillis();
         for ( int x = 1; x <= count; x++ )
         {
-            File file = new File( tempDir.getAbsolutePath() + System.getProperty( "file.separator" ) + x );
+            File file = new File( tempDir.getAbsolutePath() + System.getProperty( "file.separator" ) + namePrefix + x );
             if ( !file.mkdirs() )
             {
                 throw new IOException( "Unable to create test directory: " + file.getName() );
             }
-            if ( daysOldIndex < daysOldCount )
+            if ( x <= toAge )
             {
-                long daysOldTime = System.currentTimeMillis() - 24 * 60 * 60 * 1000 * daysOld;
-                file.setLastModified( daysOldTime );
-                daysOldIndex++;
+                file.setLastModified( generationStart - ( age * MILLIS_IN_DAY ) );
             }
-
         }
     }
 
-    private File createRandomFile( File parentDir, String fileName )
+    private File createRegularFile( String fileName )
         throws IOException
     {
-        File randomFile = new File( parentDir.getAbsolutePath() + System.getProperty( "file.separator" ) + fileName );
+        File randomFile = new File( tempDir.getAbsolutePath() + System.getProperty( "file.separator" ) + fileName );
         if ( !randomFile.exists() )
         {
             randomFile.createNewFile();
         }
         return randomFile;
     }
-
-    private void cleanUpTestDirectoriesAndFiles()
-        throws IOException
-    {
-        FileUtils.deleteDirectory( tempDir );
-    }
 }