jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r1102270 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/data/db/ main/resources/org/apache/jackrabbit/core/data/db/ test/java/org/apache/jackrabbit/core/data/
Date Thu, 12 May 2011 12:47:18 GMT
Author: thomasm
Date: Thu May 12 12:47:18 2011
New Revision: 1102270

URL: http://svn.apache.org/viewvc?rev=1102270&view=rev
Log:
JCR-2026 DbDataStore: garbage collection deadlock (fix and test case)

Added:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConcurrentGcTest.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
    jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java?rev=1102270&r1=1102269&r2=1102270&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
Thu May 12 12:47:18 2011
@@ -41,6 +41,7 @@ import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -229,8 +230,8 @@ public class DbDataStore implements Data
      * in the [databaseType].properties file, initialized with the default value.
      */
     protected String updateSQL =
-        "UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=?"
-        + " WHERE ID=? AND NOT EXISTS(SELECT ID FROM ${tablePrefix}${table} WHERE ID=?)";
+        "UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=? " +
+        "WHERE ID=? AND LAST_MODIFIED=?";
 
     /**
      * This is the property 'delete'
@@ -299,48 +300,42 @@ public class DbDataStore implements Data
      */
     private ConnectionFactory connectionFactory;
 
-    /**
-     * {@inheritDoc}
-     */
     public void setConnectionFactory(ConnectionFactory connnectionFactory) {
         this.connectionFactory = connnectionFactory;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public DataRecord addRecord(InputStream stream) throws DataStoreException {
-        ResultSet rs = null;
         InputStream fileInput = null;
-        String id = null, tempId = null;
+        String tempId = null;
+        ResultSet rs = null;
         try {
-            long now;
+            long tempModified;
             while (true) {
                 try {
-                    now = System.currentTimeMillis();
-                    id = UUID.randomUUID().toString();
+                    tempModified = System.currentTimeMillis();
+                    String id = UUID.randomUUID().toString();
                     tempId = TEMP_PREFIX + id;
+                    temporaryInUse.add(tempId);
                     // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
-                    rs = conHelper.exec(selectMetaSQL, new Object[]{tempId}, false, 0);
-                    if (rs.next()) {
+                    rs = conHelper.query(selectMetaSQL, tempId);
+                    boolean hasNext = rs.next();
+                    rs.close();
+                    rs = null;
+                    if (hasNext) {
                         // re-try in the very, very unlikely event that the row already exists
                         continue;
                     }
                     // INSERT INTO DATASTORE VALUES(?, 0, ?, NULL)
-                    conHelper.exec(insertTempSQL, new Object[]{tempId, new Long(now)});
+                    conHelper.exec(insertTempSQL, tempId, tempModified);
                     break;
                 } catch (Exception e) {
                     throw convert("Can not insert new record", e);
                 } finally {
                     DbUtility.close(rs);
+                    // prevent that rs.close() is called again
+                    rs = null;
                 }
             }
-            if (id == null) {
-                String msg = "Can not create new record";
-                log.error(msg);
-                throw new DataStoreException(msg);
-            }
-            temporaryInUse.add(tempId);
             MessageDigest digest = getDigest();
             DigestInputStream dIn = new DigestInputStream(stream, digest);
             CountingInputStream in = new CountingInputStream(dIn);
@@ -358,41 +353,59 @@ public class DbDataStore implements Data
                 throw new DataStoreException("Unsupported stream store algorithm: " + storeStream);
             }
             // UPDATE DATASTORE SET DATA=? WHERE ID=?
-            conHelper.exec(updateDataSQL, new Object[]{wrapper, tempId});
-            now = System.currentTimeMillis();
+            conHelper.exec(updateDataSQL, wrapper, tempId);
             long length = in.getByteCount();
             DataIdentifier identifier = new DataIdentifier(digest.digest());
             usesIdentifier(identifier);
-            id = identifier.toString();
-            // UPDATE DATASTORE SET ID=?, LENGTH=?, LAST_MODIFIED=?
-            // WHERE ID=?
-            // AND NOT EXISTS(SELECT ID FROM DATASTORE WHERE ID=?)
-            int count = conHelper.update(updateSQL, new Object[]{
-                    id, new Long(length), new Long(now),
-                    tempId, id});
-            rs = null; // prevent that rs.close() is called in finally block if count !=
0 (rs is closed above)
-            if (count == 0) {
-                // update count is 0, meaning such a row already exists
-                // DELETE FROM DATASTORE WHERE ID=?
-                conHelper.exec(deleteSQL, new Object[]{tempId});
-                // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
-                rs = conHelper.exec(selectMetaSQL, new Object[]{id}, false, 0);
-                if (rs.next()) {
-                    long oldLength = rs.getLong(1);
-                    long lastModified = rs.getLong(2);
-                    if (oldLength != length) {
-                        String msg =
-                            DIGEST + " collision: temp=" + tempId
-                            + " id=" + id + " length=" + length
-                            + " oldLength=" + oldLength;
-                        log.error(msg);
-                        throw new DataStoreException(msg);
+            String id = identifier.toString();
+            long newModified;
+            while (true) {
+                newModified = System.currentTimeMillis();
+                if (checkExisting(tempId, length, identifier)) {
+                    touch(identifier, newModified);
+                    conHelper.exec(deleteSQL, tempId);
+                    break;
+                }
+                try {
+                    // UPDATE DATASTORE SET ID=?, LENGTH=?, LAST_MODIFIED=?
+                    // WHERE ID=? AND LAST_MODIFIED=?
+                    int count = conHelper.update(updateSQL,
+                            id, length, newModified, tempId, tempModified);
+                    if (count == 0) {
+                        // update count is 0, meaning the last modified time of the
+                        // temporary row was changed - which means we need to
+                        // re-try using a new last modified date (a later one)
+                        // because we need to ensure the new last modified date
+                        // is _newer_ than the old (otherwise the garbage collection
+                        // could delete rows)
+                    } else {
+                        // update was successful
+                        break;
                     }
-                    touch(identifier, lastModified);
+                } catch (SQLException e) {
+                    // duplicate key (the row already exists) - repeat
+                    // we use exception handling for flow control here, which is bad,
+                    // but the alternative is to use UPDATE ... WHERE ... (SELECT ...)
+                    // which could cause a deadlock in some databases - also,
+                    // duplicate key will only occur if somebody else concurrently
+                    // added the same record (which is very unlikely)
+                }
+                // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
+                rs = conHelper.query(selectMetaSQL, tempId);
+                if (!rs.next()) {
+                    // the row was deleted, which is unexpected / not allowed
+                    String msg =
+                        DIGEST + " temporary entry deleted: " +
+                            " id=" + tempId + " length=" + length;
+                    log.error(msg);
+                    throw new DataStoreException(msg);
                 }
+                tempModified = rs.getLong(2);
+                DbUtility.close(rs);
+                rs = null;
             }
             usesIdentifier(identifier);
-            DbDataRecord record = new DbDataRecord(this, identifier, length, now);
+            DbDataRecord record = new DbDataRecord(this, identifier, length, newModified);
             return record;
         } catch (Exception e) {
             throw convert("Can not insert new record", e);
@@ -412,6 +425,40 @@ public class DbDataStore implements Data
     }
 
     /**
+     * Check if a row with this ID already exists.
+     *
+     * @return true if the row exists and the length matches
+     * @throw DataStoreException if a row exists, but the length is different
+     */
+    private boolean checkExisting(String tempId, long length, DataIdentifier identifier)
throws DataStoreException, SQLException {
+        String id = identifier.toString();
+        // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
+        ResultSet rs = null;
+        try {
+            rs = conHelper.query(selectMetaSQL, id);
+            if (rs.next()) {
+                long oldLength = rs.getLong(1);
+                long lastModified = rs.getLong(2);
+                if (oldLength != length) {
+                    String msg =
+                        DIGEST + " collision: temp=" + tempId
+                        + " id=" + id + " length=" + length
+                        + " oldLength=" + oldLength;
+                    log.error(msg);
+                    throw new DataStoreException(msg);
+                }
+                touch(identifier, lastModified);
+                // row already exists
+                conHelper.exec(deleteSQL, tempId);
+                return true;
+            }
+        } finally {
+            DbUtility.close(rs);
+        }
+        return false;
+    }
+
+    /**
      * Creates a temp file and copies the data there.
      * The input stream is closed afterwards.
      *
@@ -425,9 +472,6 @@ public class DbDataStore implements Data
         return temp;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public synchronized int deleteAllOlderThan(long min) throws DataStoreException {
         try {
             ArrayList<String> touch = new ArrayList<String>();
@@ -442,21 +486,18 @@ public class DbDataStore implements Data
                 updateLastModifiedDate(key, 0);
             }
             // DELETE FROM DATASTORE WHERE LAST_MODIFIED<?
-            return conHelper.update(deleteOlderSQL, new Long[]{new Long(min)});
+            return conHelper.update(deleteOlderSQL, min);
         } catch (Exception e) {
             throw convert("Can not delete records", e);
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
         ArrayList<DataIdentifier> list = new ArrayList<DataIdentifier>();
         ResultSet rs = null;
         try {
             // SELECT ID FROM DATASTORE
-            rs = conHelper.exec(selectAllSQL, new Object[0], false, 0);
+            rs = conHelper.query(selectAllSQL);
             while (rs.next()) {
                 String id = rs.getString(1);
                 if (!id.startsWith(TEMP_PREFIX)) {
@@ -472,9 +513,6 @@ public class DbDataStore implements Data
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public int getMinRecordLength() {
         return minRecordLength;
     }
@@ -489,16 +527,13 @@ public class DbDataStore implements Data
         this.minRecordLength = minRecordLength;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException
{
         usesIdentifier(identifier);
         ResultSet rs = null;
         try {
             String id = identifier.toString();
             // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID = ?
-            rs = conHelper.exec(selectMetaSQL, new Object[]{id}, false, 0);
+            rs = conHelper.query(selectMetaSQL, id);
             if (!rs.next()) {
                 throw new DataStoreException("Record not found: " + identifier);
             }
@@ -513,9 +548,6 @@ public class DbDataStore implements Data
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
         DataRecord record = getRecordIfStored(identifier);
         if (record == null) {
@@ -537,7 +569,7 @@ public class DbDataStore implements Data
         ResultSet rs = null;
         try {
             // SELECT ID, DATA FROM DATASTORE WHERE ID = ?
-            rs = conHelper.exec(selectDataSQL, new Object[]{identifier.toString()}, false,
0);
+            rs = conHelper.query(selectDataSQL, identifier.toString());
             if (!rs.next()) {
                 throw new DataStoreException("Record not found: " + identifier);
             }
@@ -561,9 +593,6 @@ public class DbDataStore implements Data
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public synchronized void init(String homeDir) throws DataStoreException {
         try {
             initDatabaseType();
@@ -674,8 +703,11 @@ public class DbDataStore implements Data
         selectDataSQL = getProperty(prop, "selectData", selectDataSQL);
         storeStream = getProperty(prop, "storeStream", storeStream);
         if (STORE_SIZE_MINUS_ONE.equals(storeStream)) {
+            // ok
         } else if (STORE_TEMP_FILE.equals(storeStream)) {
+            // ok
         } else if (STORE_SIZE_MAX.equals(storeStream)) {
+            // ok
         } else {
             String msg = "Unsupported Stream store mechanism: " + storeStream
                     + " supported are: " + STORE_SIZE_MINUS_ONE + ", "
@@ -718,9 +750,6 @@ public class DbDataStore implements Data
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public void updateModifiedDateOnAccess(long before) {
         log.debug("Update modifiedDate on access before " + before);
         minModifiedDate = before;
@@ -741,12 +770,9 @@ public class DbDataStore implements Data
     private long updateLastModifiedDate(String key, long lastModified) throws DataStoreException
{
         if (lastModified < minModifiedDate) {
             long now = System.currentTimeMillis();
-            Long n = new Long(now);
             try {
                 // UPDATE DATASTORE SET LAST_MODIFIED = ? WHERE ID = ? AND LAST_MODIFIED
< ?
-                conHelper.exec(updateLastModifiedSQL, new Object[]{
-                        n, key, n
-                });
+                conHelper.update(updateLastModifiedSQL, now, key, now);
                 return now;
             } catch (Exception e) {
                 throw convert("Can not update lastModified", e);
@@ -862,19 +888,14 @@ public class DbDataStore implements Data
         schemaCheckEnabled = enabled;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public synchronized void close() throws DataStoreException {
+        // nothing to do
     }
 
     protected void usesIdentifier(DataIdentifier identifier) {
         inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public void clearInUse() {
         inUse.clear();
     }
@@ -905,6 +926,7 @@ public class DbDataStore implements Data
      * @param maxConnections the new value
      */
     public void setMaxConnections(int maxConnections) {
+        // no effect
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties?rev=1102270&r1=1102269&r2=1102270&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
Thu May 12 12:47:18 2011
@@ -17,4 +17,3 @@
 # currently, the objects must fit in memory
 driver=com.mysql.jdbc.Driver
 createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT,
LAST_MODIFIED BIGINT, DATA BLOB(2147483647))
-update=UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=? WHERE ID=? AND NOT
EXISTS(SELECT * FROM(SELECT ID FROM ${tablePrefix}${table} WHERE ID=?) X)

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConcurrentGcTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConcurrentGcTest.java?rev=1102270&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConcurrentGcTest.java
(added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConcurrentGcTest.java
Thu May 12 12:47:18 2011
@@ -0,0 +1,169 @@
+package org.apache.jackrabbit.core.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+import junit.framework.TestCase;
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.data.db.DbDataStore;
+import org.apache.jackrabbit.core.util.db.ConnectionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests concurrent garbage collection, see JCR-2026
+ */
+public class ConcurrentGcTest extends TestCase {
+
+    static final Logger LOG = LoggerFactory.getLogger(ConcurrentGcTest.class);
+
+    private static final String TEST_DIR = "target/ConcurrentGcTest";
+
+    protected DataStore store;
+    private Thread gcLoopThread;
+
+    protected Set<DataIdentifier> ids = Collections.synchronizedSet(new HashSet<DataIdentifier>());
+
+    protected volatile boolean gcLoopStop;
+    protected volatile Exception gcException;
+
+    public void setUp() throws IOException {
+        deleteAll();
+    }
+
+    public void tearDown() throws IOException {
+        deleteAll();
+    }
+
+    private void deleteAll() throws IOException {
+        FileUtils.deleteDirectory(new File(TEST_DIR));
+    }
+
+    public void testDatabases() throws Exception {
+        doTestDatabase(
+                "org.h2.Driver",
+                "jdbc:h2:" + TEST_DIR + "/db",
+                "sa", "sa");
+
+        // not enabled by default
+        // doTestDatabase(
+        //        "org.postgresql.Driver",
+        //        "jdbc:postgresql:test",
+        //        "sa", "sa");
+
+        // not enabled by default
+        // doTestDatabase(
+        //         "com.mysql.jdbc.Driver",
+        //         "jdbc:postgresql:test",
+        //         "sa", "sa");
+
+        // fails with a deadlock
+        // doTestDatabase(
+        //        "org.apache.derby.jdbc.EmbeddedDriver",
+        //        "jdbc:derby:" + TEST_DIR + "/db;create=true",
+        //        "sa", "sa");
+    }
+
+    private void doTestDatabase(String driver, String url, String user, String password)
throws Exception {
+        DbDataStore store = new DbDataStore();
+        store.setConnectionFactory(new ConnectionFactory());
+
+        ids.clear();
+
+        store.setDriver(driver);
+        store.setUrl(url);
+        store.setUser(user);
+        store.setPassword(password);
+
+        store.init("target/test-db-datastore");
+        store.setMinRecordLength(0);
+        doTest(store);
+    }
+
+    public void testFile() throws Exception {
+        FileDataStore store = new FileDataStore();
+        store.setPath(TEST_DIR + "/fs");
+        store.init(TEST_DIR + "/fs");
+        store.setMinRecordLength(0);
+        doTest(store);
+    }
+
+    void doTest(DataStore store) throws Exception {
+        this.store = store;
+
+        Random r = new Random();
+
+        concurrentGcLoopStart();
+
+        int len = 100;
+        if (getTestScale() > 1) {
+            len = 1000;
+        }
+
+        for (int i = 0; i < len && gcException == null; i++) {
+            LOG.info("test " + i);
+            byte[] data = new byte[3];
+            r.nextBytes(data);
+            DataRecord rec = store.addRecord(new ByteArrayInputStream(data));
+            LOG.debug("  added " + rec.getIdentifier());
+            if (r.nextBoolean()) {
+                LOG.debug("  added " + rec.getIdentifier() + " -> keep reference");
+                ids.add(rec.getIdentifier());
+                store.getRecord(rec.getIdentifier());
+            }
+            if (r.nextInt(100) == 0) {
+                LOG.debug("clear i: " + i);
+                ids.clear();
+            }
+        }
+        concurrentGcLoopStop();
+        store.close();
+    }
+
+    private void concurrentGcLoopStart() {
+        gcLoopStop = false;
+        gcException = null;
+
+        gcLoopThread = new Thread() {
+            public void run() {
+                try {
+                    while (!gcLoopStop) {
+                        if (ids.size() > 0) {
+                            // store.clearInUse();
+                            long now = System.currentTimeMillis();
+                            LOG.debug("gc now: " + now);
+                            store.updateModifiedDateOnAccess(now);
+                            for (DataIdentifier id : new ArrayList<DataIdentifier>(ids))
{
+                                LOG.debug("   gc touch " + id);
+                                store.getRecord(id);
+                            }
+                            int count = store.deleteAllOlderThan(now);
+                            LOG.debug("gc now: " + now + " done, deleted: " + count);
+                        }
+                    }
+                } catch (DataStoreException e) {
+                    gcException = e;
+                }
+            }
+        };
+        gcLoopThread.start();
+    }
+
+    private void concurrentGcLoopStop() throws Exception {
+        gcLoopStop = true;
+        gcLoopThread.join();
+        if (gcException != null) {
+            throw gcException;
+        }
+    }
+
+    static int getTestScale() {
+        return Integer.parseInt(System.getProperty("jackrabbit.test.scale", "1"));
+    }
+
+}

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java?rev=1102270&r1=1102269&r2=1102270&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
Thu May 12 12:47:18 2011
@@ -35,19 +35,21 @@ public class TestAll extends TestCase {
     public static Test suite() {
         TestSuite suite = new ConcurrentTestSuite("Data tests");
 
+        suite.addTestSuite(ConcurrentGcTest.class);
+        suite.addTestSuite(CopyValueTest.class);
         suite.addTestSuite(DataStoreAPITest.class);
-        suite.addTestSuite(LazyFileInputStreamTest.class);
-        suite.addTestSuite(OpenFilesTest.class);
         suite.addTestSuite(DataStoreTest.class);
-        suite.addTestSuite(NodeTypeTest.class);
+        suite.addTestSuite(DBDataStoreTest.class);
         suite.addTestSuite(ExportImportTest.class);
         suite.addTestSuite(GarbageCollectorTest.class);
         suite.addTestSuite(GCConcurrentTest.class);
         suite.addTestSuite(GCEventListenerTest.class);
+        suite.addTestSuite(LazyFileInputStreamTest.class);
+        suite.addTestSuite(NodeTypeTest.class);
+        suite.addTestSuite(OpenFilesTest.class);
         suite.addTestSuite(PersistenceManagerIteratorTest.class);
-        suite.addTestSuite(CopyValueTest.class);
-        suite.addTestSuite(TestTwoGetStreams.class);
         suite.addTestSuite(TempFileInputStreamTest.class);
+        suite.addTestSuite(TestTwoGetStreams.class);
         suite.addTestSuite(WriteWhileReadingTest.class);
 
         return suite;



Mime
View raw message