You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ja...@apache.org on 2013/10/23 10:32:24 UTC

svn commit: r1534956 - in /ace/trunk/org.apache.ace.repository: ./ src/org/apache/ace/repository/ src/org/apache/ace/repository/ext/impl/ src/org/apache/ace/repository/impl/ test/org/apache/ace/repository/impl/

Author: jawi
Date: Wed Oct 23 08:32:23 2013
New Revision: 1534956

URL: http://svn.apache.org/r1534956
Log:
ACE-421 - do not always bump repository version:

- in case a commit is performed without actually changing data, the 
  commit should not proceed, and the version of the repository should
  not be bumped. This makes it a lot easier to detect whether a real
  change has occurred to a repository.


Modified:
    ace/trunk/org.apache.ace.repository/   (props changed)
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/Repository.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java
    ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java

Propchange: ace/trunk/org.apache.ace.repository/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Wed Oct 23 08:32:23 2013
@@ -4,3 +4,4 @@ generated
 store
 bundle-cache
 felix-cache
+test-output

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/Repository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/Repository.java?rev=1534956&r1=1534955&r2=1534956&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/Repository.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/Repository.java Wed Oct 23 08:32:23 2013
@@ -29,33 +29,44 @@ import org.apache.ace.range.SortedRangeS
 public interface Repository
 {
     /**
-     * Determines the versions inside the repository.
+     * Determines the versions present inside the repository.
      * 
-     * @returns A <code>SortedRangeSet</code> representing all the versions currently inside the repository.
-     * @throws java.io.IOException If there is an error determining the current versions.
+     * @returns A <code>SortedRangeSet</code> representing all the versions currently inside the repository, never
+     *          <code>null</code>.
+     * @throws java.io.IOException
+     *             If there is an error determining the current versions.
      */
     public SortedRangeSet getRange() throws IOException;
 
     /**
      * Commits data into the repository.
      * 
-     * @param data The data to be committed.
-     * @param fromVersion The version the data is based upon.
-     * @return True if the commit succeeded, false otherwise if the <code>fromVersion</code> is not the latest version.
-     * @throws java.io.IOException If there was a problem reading or writing the data.
-     * @throws IllegalArgumentException If the version is not greater than 0.
-     * @throws IllegalStateException If an attempt to commit was made on a non-master repository.
+     * @param data
+     *            The input stream containing the data to be committed, cannot be <code>null</code>;
+     * @param fromVersion
+     *            The version the to-be-committed data is based upon, is used to verify no other commits have taken
+     *            place between the last checkout and this commit.
+     * @return <code>true</code> if the commit succeeded (and a new version is created in this repository),
+     *         <code>false</code> if the commit was <em>ignored</em> because no actual changes in data were found.
+     * @throws java.io.IOException
+     *             If there was a problem reading or writing the data, or when trying to commit a version that is not
+     *             the "current" version;
+     * @throws IllegalArgumentException
+     *             If the version is less than <tt>0</tt>;
+     * @throws IllegalStateException
+     *             If an attempt to commit was made on a non-master repository.
      */
     public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException;
 
     /**
-     * Checks out the version of the repository that have been passed to this
-     * method as parameter.
+     * Checks out the version of the repository that have been passed to this method as parameter.
      * 
-     * @return a stream containing a checkout of the passed in version of
-     *         the repository, or <code>null</code> if the version does not exist
-     * @throws java.io.IOException if there is an error reading the version
-     * @throws IllegalArgumentException if the version is invalid.
+     * @return a stream containing a checkout of the given repository version, or <code>null</code> if the version does
+     *         not exist.
+     * @throws java.io.IOException
+     *             if there is an error reading the requested version of the repository;
+     * @throws IllegalArgumentException
+     *             if the given version is less than or equal to <tt>0</tt>.
      */
     public InputStream checkout(long version) throws IOException, IllegalArgumentException;
-}
\ No newline at end of file
+}

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java?rev=1534956&r1=1534955&r2=1534956&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/CachedRepositoryImpl.java Wed Oct 23 08:32:23 2013
@@ -86,7 +86,12 @@ public class CachedRepositoryImpl implem
         if (m_mostRecentVersion < 0) {
             throw new IllegalStateException("A commit should be preceded by a checkout.");
         }
-        return commit(m_mostRecentVersion++);
+        boolean result = commit(m_mostRecentVersion);
+        if (result) {
+            // ACE-421: only bump in case of successful commit! 
+            m_mostRecentVersion++;
+        }
+        return result;
     }
 
     public boolean commit(long fromVersion) throws IOException, IllegalArgumentException {
@@ -105,7 +110,7 @@ public class CachedRepositoryImpl implem
     public InputStream getLocal(boolean fail) throws IllegalArgumentException, IOException {
         // ACE-240: only fail in case there is no local version available; when mostRecentVersion 
         // equals to 0, it means that nothing has been committed locally...
-        if (m_mostRecentVersion <= 0 && (fail)) {
+        if (m_mostRecentVersion <= 0 && fail) {
     		throw new IOException("No local version available of " + m_local + ", remote " + m_remote);
         }
         return m_local.read();

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java?rev=1534956&r1=1534955&r2=1534956&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/ext/impl/RemoteRepository.java Wed Oct 23 08:32:23 2013
@@ -19,6 +19,7 @@
 package org.apache.ace.repository.ext.impl;
 
 import java.io.BufferedReader;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -82,16 +83,15 @@ public class RemoteRepository implements
 
         int rc = connection.getResponseCode();
         if (rc == HttpServletResponse.SC_NOT_FOUND) {
-            connection.disconnect();
+            closeQuietly(connection);
             throw new IllegalArgumentException("Requested version not found in remote repository. (" + connection.getResponseMessage() + ") for " + url.toExternalForm());
         }
         else if (rc != HttpServletResponse.SC_OK) {
-            connection.disconnect();
+            closeQuietly(connection);
             throw new IOException("Connection error: " + connection.getResponseMessage() + " for " + url.toExternalForm());
         }
 
         return connection.getInputStream();
-
     }
 
     public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
@@ -113,9 +113,8 @@ public class RemoteRepository implements
             return connection.getResponseCode() == HttpServletResponse.SC_OK;
         }
         finally {
-            out.flush();
-            out.close();
-            connection.disconnect();
+            closeQuietly(out);
+            closeQuietly(connection);
         }
     }
 
@@ -144,7 +143,7 @@ public class RemoteRepository implements
             throw new IOException("Connection error: " + connection.getResponseMessage() + " for " + url.toExternalForm());
         }
         finally {
-            connection.disconnect();
+            closeQuietly(connection);
         }
     }
 
@@ -218,4 +217,33 @@ public class RemoteRepository implements
     public String toString() {
         return "RemoteRepository[" + m_url + "," + m_customer + "," + m_name + "]";
     }
+
+    /**
+     * Safely closes a given HTTP URL connection.
+     * 
+     * @param resource
+     *            the resource to close, can be <code>null</code>.
+     */
+    private void closeQuietly(HttpURLConnection resource) {
+        if (resource != null) {
+            resource.disconnect();
+        }
+    }
+
+    /**
+     * Safely closes a given resource, ignoring any I/O exceptions that might occur by this.
+     * 
+     * @param resource
+     *            the resource to close, can be <code>null</code>.
+     */
+    private void closeQuietly(Closeable resource) {
+        try {
+            if (resource != null) {
+                resource.close();
+            }
+        }
+        catch (IOException e) {
+            // Ignored...
+        }
+    }
 }

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java?rev=1534956&r1=1534955&r2=1534956&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java Wed Oct 23 08:32:23 2013
@@ -18,6 +18,7 @@
  */
 package org.apache.ace.repository.impl;
 
+import java.io.BufferedInputStream;
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
@@ -167,27 +168,22 @@ public class RepositoryImpl implements R
 
         long[] versions = getVersions();
         if (versions.length == 0) {
-            if (fromVersion == 0) {
-                put(data, 1);
-
-                return true;
-            }
-            else {
-                return false;
+            if (fromVersion != 0) {
+                throw new IOException("Repository already changed, cannot commit initial version!");
             }
+            return put(data, 1);
         }
 
         long lastVersion = versions[versions.length - 1];
-        if (lastVersion == fromVersion) {
-            put(data, fromVersion + 1);
-            // Make sure we do not exceed our max limit...
-            purgeOldFiles(getVersions(), m_limit);
-
-            return true;
-        }
-        else {
-            return false;
+        if (lastVersion != fromVersion) {
+            throw new IOException("Repository already changed, cannot commit version" + fromVersion + "!");
         }
+
+        boolean result = put(data, fromVersion + 1);
+        // Make sure we do not exceed our max limit...
+        purgeOldFiles(getVersions(), m_limit);
+
+        return result;
     }
 
     public InputStream get(long version) throws IOException, IllegalArgumentException {
@@ -249,13 +245,14 @@ public class RepositoryImpl implements R
             throw e;
         }
         finally {
-            if (fileStream != null) {
-                try {
-                    fileStream.close();
-                }
-                catch (IOException ioe) {
-                    // Not much we can do
-                }
+            closeQuietly(fileStream);
+        }
+
+        // ACE-421: check whether there's a change in data...
+        if (version > 1) {
+            File current = getFilename(version - 1);
+            if (current.exists() && contentsEqual(current, tempFile)) {
+                return false;
             }
         }
 
@@ -307,6 +304,55 @@ public class RepositoryImpl implements R
         }
     }
 
+    /**
+     * Tests whether two given files are identical with respect to their length and content.
+     * 
+     * @param file1
+     *            the file to check against;
+     * @param file2
+     *            the file to check.
+     * @return <code>true</code> if both files are equal in length and content, <code>false</code> otherwise.
+     */
+    private boolean contentsEqual(File file1, File file2) {
+        // if lengths are not equal, we're certain that the contents won't be equal...
+        if (file1.length() != file2.length()) {
+            return false;
+        }
+        if (file1.getAbsolutePath().equals(file2.getAbsolutePath())) {
+            // they are one and the same file...
+            return true;
+        }
+
+        InputStream is1 = null;
+        InputStream is2 = null;
+
+        boolean result = true;
+
+        try {
+            is1 = new BufferedInputStream(new FileInputStream(file1));
+            is2 = new BufferedInputStream(new FileInputStream(file2));
+
+            int read;
+            while ((read = is1.read()) >= 0) {
+                int readData = is2.read();
+                if (readData != read) {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        catch (IOException e) {
+            // Log and ignore...
+            m_log.log(LogService.LOG_DEBUG, "Failed to diff files?!", e);
+        }
+        finally {
+            closeQuietly(is1);
+            closeQuietly(is2);
+        }
+
+        return result;
+    }
+
     private boolean delete(long version) throws IOException, IllegalArgumentException {
         if (version <= 0) {
             throw new IllegalArgumentException("Version must be greater than 0.");

Modified: ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java?rev=1534956&r1=1534955&r2=1534956&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java (original)
+++ ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java Wed Oct 23 08:32:23 2013
@@ -19,17 +19,21 @@
 package org.apache.ace.repository.impl;
 
 import static org.apache.ace.test.utils.TestUtils.UNIT;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileReader;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
 import org.apache.ace.range.SortedRangeSet;
-import static org.testng.Assert.*;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -37,144 +41,245 @@ public class RepositoryImplTest {
 
     private File m_baseDir;
 
-    @BeforeMethod(alwaysRun = true)
-    protected void setUp() throws IOException {
-        m_baseDir = File.createTempFile("repo", null);
-        m_baseDir.delete();
-        m_baseDir.mkdirs();
-    }
-
-    @Test(groups = { UNIT })
-    public void testGetAndPut() throws Exception {
+    /**
+     * Tests that if we do change something in an {@link InputStream} while committing data, that the version is bumped
+     * for a repository.
+     */
+    @Test
+    public void testCheckoutAndCommitWithChangeDoesChangeVersion() throws Exception {
+        SortedRangeSet range;
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
         InputStream data = new ByteArrayInputStream("abc".getBytes());
-        boolean result = repo.put(data, 1);
-        assert result : "Put should have succeeded.";
 
-        File file = new File(m_baseDir, "data" + File.separator + "1");
-        BufferedReader reader = new BufferedReader(new FileReader(file));
-        assert "abc".equals(reader.readLine()) : "File " + file.getAbsolutePath() + " should have contained 'abc'.";
+        assertTrue(repo.put(data, 1), "Put should have succeeded");
 
-        assert !repo.put(data, 1) : "Putting an existing version should return false.";
+        range = repo.getRange();
+        assertEquals(1, range.getHigh(), "Version 1 should be the most recent one");
 
-        InputStream in = repo.get(1);
-        reader = new BufferedReader(new InputStreamReader(in));
-        assert "abc".equals(reader.readLine()) : "'get'ting version 1 should have returned an inputstream containing 'abc'";
-        assert null == repo.get(2) : "'get'ting a non-existing version should return null";
-    }
+        InputStream is = repo.checkout(1);
+        assertNotNull(is, "Nothing checked out?!");
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testPutNegative() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.put(new ByteArrayInputStream("abc".getBytes()), -1);
+        data = new ByteArrayInputStream("def".getBytes());
+
+        assertTrue(repo.commit(data, 1), "Commit should be ignored");
+
+        range = repo.getRange();
+        assertEquals(2, range.getHigh());
     }
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testPutZero() throws Exception {
+    /**
+     * Tests that if we do not change anything in an {@link InputStream} while committing data, that the version is not
+     * bumped for a repository.
+     */
+    @Test
+    public void testCheckoutAndCommitWithoutChangeDoesNotChangeVersion() throws Exception {
+        SortedRangeSet range;
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.put(new ByteArrayInputStream("abc".getBytes()), 0);
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+
+        assertTrue(repo.put(data, 1), "Put should have succeeded");
+
+        range = repo.getRange();
+        assertEquals(1, range.getHigh(), "Version 1 should be the most recent one");
+
+        InputStream is = repo.checkout(1);
+        assertFalse(repo.commit(is, 1), "Commit should be ignored");
+
+        range = repo.getRange();
+        assertEquals(1, range.getHigh(), "Version 1 should still be the most recent one");
     }
 
     @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testGetNegative() throws Exception {
+    public void testCheckoutNegativeVersionFail() throws Exception {
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.get(-1);
+        repo.checkout(-1);
     }
 
     @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testGetZero() throws Exception {
+    public void testCheckoutVersionZeroOnEmptyRepositoryFail() throws Exception {
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.get(0);
+        repo.checkout(0);
     }
 
     @Test(groups = { UNIT })
     public void testCommitAndCheckout() throws Exception {
+        String readLine;
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        InputStream data = new ByteArrayInputStream("abc".getBytes());
-        boolean result = repo.commit(data, 1);
-        assert !result : "Commit with incorrect 'base' number should have failed.";
-
-        result = repo.commit(data, 0);
-        assert result : "Commit should have succeeded";
-
         File file = new File(m_baseDir, "data" + File.separator + "1");
-        BufferedReader reader = new BufferedReader(new FileReader(file));
-        assert "abc".equals(reader.readLine()) : "File " + file.getAbsolutePath() + " should have contained 'abc'.";
 
-        assert !repo.commit(data, 0) : "Committing an existing version should return false.";
-        assert !repo.commit(data, 999) : "Committing should only succeed if the base number equals the highest version inside the repository";
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+        assertTrue(repo.commit(data, 0), "Commit should have succeeded");
 
-        InputStream in = repo.checkout(1);
-        reader = new BufferedReader(new InputStreamReader(in));
-        assert "abc".equals(reader.readLine()) : "Checking out version 1 should have returned an inputstream containing 'abc'";
-        assert null == repo.get(2) : "Checking out a non-existing version should return null";
-    }
+        readLine = getContentAsString(file);
+        assertEquals(readLine, "abc", "File " + file.getAbsolutePath() + " should have contained 'abc'.");
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testCommitNegative() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.commit(new ByteArrayInputStream("abc".getBytes()), -1);
+        readLine = getContentAsString(repo.checkout(1));
+        assertEquals(readLine, "abc", "Checking out version 1 should have returned an inputstream containing 'abc'");
+        assertNull(repo.get(2), "Checking out a non-existing version should return null");
     }
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testCheckoutNegative() throws Exception {
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class })
+    public void testCommitExistingVersionFail() throws Exception {
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.checkout(-1);
-    }
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
-    public void testCheckoutZero() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.checkout(0);
-    }
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+        repo.commit(data, 0);
 
-    @Test(groups = { UNIT }, expectedExceptions = { IllegalStateException.class })
-    public void testUpdated() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.updated(false, Long.MAX_VALUE);
-        assert !repo.commit(new ByteArrayInputStream("abc".getBytes()), 0) : "Committing should not be allowed on slave repositories.";
-        assert repo.put(new ByteArrayInputStream("abc".getBytes()), 1) : "'put'ting a replica should be allowed on slave repositories.";
-        File file = new File(m_baseDir, "newLocation" + File.separator + "1");
-        BufferedReader reader = new BufferedReader(new FileReader(file));
-        assert "abc".equals(reader.readLine()) : "File " + file.getAbsolutePath() + " should have contained 'abc'.";
+        data = new ByteArrayInputStream("def".getBytes());
+        repo.commit(data, 0); // should fail, as we're at version 1!
     }
 
-    @Test(groups = { UNIT })
-    public void testFileExtension() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), ".gz", true);
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class })
+    public void testCommitIncorrectVersionFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
         InputStream data = new ByteArrayInputStream("abc".getBytes());
-        boolean result = repo.put(data, 1);
-        assert result : "Put should have succeeded.";
-        File file = new File(m_baseDir, "data" + File.separator + "1.gz");
-        BufferedReader reader = new BufferedReader(new FileReader(file));
-        assert "abc".equals(reader.readLine()) : "File " + file.getAbsolutePath() + " should have contained 'abc'.";
+
+        repo.commit(data, 1); // should fail, as we're at version 0!
     }
 
     @Test(groups = { UNIT })
-    public void testCommitMultiple() throws Exception {
+    public void testCommitMultipleVersionsOk() throws Exception {
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+
         assertTrue(repo.commit(new ByteArrayInputStream("abc-1".getBytes()), 0), "Commit should have worked.");
         assertTrue(repo.commit(new ByteArrayInputStream("abc-2".getBytes()), 1), "Commit should have worked.");
         assertTrue(repo.commit(new ByteArrayInputStream("abc-3".getBytes()), 2), "Commit should have worked.");
+
         SortedRangeSet range = repo.getRange();
         assertTrue(range.getHigh() == 3, "We should have 3 versions in the repository.");
     }
 
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
+    public void testCommitNegativeVersionFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        repo.commit(new ByteArrayInputStream("abc".getBytes()), -1);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IOException.class })
+    public void testCommitNonExistingVersionFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+        repo.commit(data, 0);
+
+        data = new ByteArrayInputStream("def".getBytes());
+        repo.commit(data, 2); // should fail, as we're at version 1!
+    }
+
     @Test(groups = { UNIT })
     public void testCommitToLimitedRepository() throws Exception {
-        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true, 2);
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true, 2 /* limit */);
+
         assertTrue(repo.commit(new ByteArrayInputStream("abc-1".getBytes()), 0), "Commit should have worked.");
         assertTrue(repo.commit(new ByteArrayInputStream("abc-2".getBytes()), 1), "Commit should have worked.");
         assertTrue(repo.commit(new ByteArrayInputStream("abc-3".getBytes()), 2), "Commit should have worked.");
+
         assertNotNull(repo.checkout(3));
         assertNotNull(repo.checkout(2));
         assertNull(repo.checkout(1));
+
         repo.updated(true, 3);
+
         assertTrue(repo.commit(new ByteArrayInputStream("abc-4".getBytes()), 3), "Commit should have worked.");
         assertNotNull(repo.checkout(2));
+
         repo.updated(true, 1);
+
         assertNull(repo.checkout(2));
         assertNull(repo.checkout(3));
         assertNotNull(repo.checkout(4));
     }
+
+    @Test(groups = { UNIT })
+    public void testCustomFileExtensionOk() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), ".gz", true);
+        File file = new File(m_baseDir, "data" + File.separator + "1.gz");
+
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+        assertTrue(repo.put(data, 1), "Put should have succeeded.");
+
+        String readLine = getContentAsString(file);
+        assertEquals(readLine, "abc", "File " + file.getAbsolutePath() + " should have contained 'abc'.");
+    }
+
+    @Test(groups = { UNIT })
+    public void testGetAndPut() throws Exception {
+        String readLine;
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        File file = new File(m_baseDir, "data" + File.separator + "1");
+
+        InputStream data = new ByteArrayInputStream("abc".getBytes());
+        assertTrue(repo.put(data, 1), "Put should have succeeded.");
+
+        readLine = getContentAsString(file);
+        assertEquals(readLine, "abc", "File " + file.getAbsolutePath() + " should have contained 'abc'.");
+
+        assertFalse(repo.put(data, 1), "Putting an existing version should return false.");
+
+        readLine = getContentAsString(repo.get(1));
+        assertEquals(readLine, "abc", "'get'ting version 1 should have returned an inputstream containing 'abc'");
+
+        assertNull(repo.get(2), "'get'ting a non-existing version should return null");
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
+    public void testGetNegativeVersionFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        repo.get(-1);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
+    public void testGetVersionZeroForEmptyRepositoryFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        repo.get(0);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
+    public void testPutNegativeVersionFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        repo.put(new ByteArrayInputStream("abc".getBytes()), -1);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalArgumentException.class })
+    public void testPutVersionZeroFail() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        repo.put(new ByteArrayInputStream("abc".getBytes()), 0);
+    }
+
+    @Test(groups = { UNIT }, expectedExceptions = { IllegalStateException.class })
+    public void testUpdatedConfigurationOk() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        File file = new File(m_baseDir, "newLocation" + File.separator + "1");
+
+        // update the configuration of the repository...
+        repo.updated(false /* isMaster */, Long.MAX_VALUE);
+
+        assertFalse(repo.commit(new ByteArrayInputStream("abc".getBytes()), 0), "Committing should not be allowed on slave repositories.");
+        assertTrue(repo.put(new ByteArrayInputStream("abc".getBytes()), 1), "'put'ting a replica should be allowed on slave repositories.");
+
+        String readLine = getContentAsString(file);
+        assertEquals(readLine, "abc", "File " + file.getAbsolutePath() + " should have contained 'abc'.");
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws IOException {
+        m_baseDir = File.createTempFile("repo", null);
+        m_baseDir.delete();
+        m_baseDir.mkdirs();
+    }
+
+    private String getContentAsString(File file) throws IOException {
+        return getContentAsString(new FileInputStream(file));
+    }
+
+    private String getContentAsString(InputStream is) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        try {
+            return reader.readLine();
+        }
+        finally {
+            is.close();
+            reader.close();
+        }
+    }
 }