db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From krist...@apache.org
Subject svn commit: r1041374 - in /db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit: IndexStatsUtil.java Utilities.java
Date Thu, 02 Dec 2010 13:38:08 GMT
Author: kristwaa
Date: Thu Dec  2 13:38:08 2010
New Revision: 1041374

URL: http://svn.apache.org/viewvc?rev=1041374&view=rev
Log:
DERBY-4834: Add helper class for working with index statistics in JUnit tests 

Introduced a timeout value and exepcted count when fetching statistics from
the system table. If a specific number of statistics entries is expected, the
system table will be queried repeatedly until the correct number is obtained or
the query times out. The motivation behind this is that the arrival of
entries in SYS.SYSSTATISTICS isn't that easy to predict since the work is
carried out in a background task.

Also some cleanups in Utilities:
 o fixed some JavaDoc
 o fixed formatting
 o added method sleep
 o simplified repeatChar

Patch file: derby-4834-3a-timeout.diff


Modified:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/IndexStatsUtil.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/Utilities.java

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/IndexStatsUtil.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/IndexStatsUtil.java?rev=1041374&r1=1041373&r2=1041374&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/IndexStatsUtil.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/IndexStatsUtil.java Thu
Dec  2 13:38:08 2010
@@ -38,10 +38,24 @@ import junit.framework.Assert;
  * Helper class for obtaining index statistics and doing asserts on them.
  * <p>
  * This implementation assumes all tables/indexes belong to the current schema.
+ * <p>
+ * The <em>timeout</em> value is used to make the utility more resilient to
+ * differences in timing due to varying scheduling decisions, processor speeds,
+ * etc. If the system table contains the wrong number of statistics objects for
+ * the query, it will be queried repeatedly until the right number of statistics
+ * objects is obtained or the query times out.
  */
 public class IndexStatsUtil {
 
+    private static final boolean INDEX = false;
+    private static final boolean TABLE = true;
+    private static final int NO_EXPECTATION = -1;
+    private static final String SEP =
+                BaseJDBCTestCase.getSystemProperty("line.separator");
+
     private final Connection con;
+    /** Timeout in milliseconds. */
+    private final long timeout;
     private PreparedStatement psGetTableId;
     private PreparedStatement psGetStatsForTable;
     private PreparedStatement psGetIndexId;
@@ -50,11 +64,39 @@ public class IndexStatsUtil {
     private PreparedStatement psGetIdToNameMapConglom;
     private PreparedStatement psGetIdToNameMapTable;
 
-    public IndexStatsUtil(Connection con)
-            throws SQLException {
+    /**
+     * Creates an instance querying the given database with no timeout set.
+     * <p>
+     * Querying with no timeout means that if there are too few or too many
+     * statistics objects matching the query, a failure will be raised
+     * immediately.
+     *
+     * @param con connection to the database to query
+     */
+    public IndexStatsUtil(Connection con) {
+        this(con, 0L);
+    }
+
+    /**
+     * Creates an instance querying the given database with the specified
+     * timeout value.
+     *
+     * @param con connection to the database to query
+     * @param timeout the longest time to wait to see if the expectations for a
+     *      query are met (milliseconds)
+     */
+    public IndexStatsUtil(Connection con, long timeout) {
         // Rely on auto-commit to release locks.
-        Assert.assertTrue(con.getAutoCommit());
+        try {
+            Assert.assertTrue(con.getAutoCommit());
+        } catch (SQLException sqle) {
+            Assert.fail("Failed to get auto commit: " + sqle.getMessage());
+        }
+        if (timeout < 0) {
+            throw new IllegalArgumentException("timeout cannot be negative");
+        }
         this.con = con;
+        this.timeout = timeout;
     }
 
     /**
@@ -85,7 +127,11 @@ public class IndexStatsUtil {
      */
     public void assertStats(int expectedCount)
             throws SQLException {
-        assertStatCount(getStats(), "<ALL>", expectedCount, false);
+        IdxStats[] ret = getStats();
+        if (ret.length != expectedCount) {
+            Assert.assertEquals(buildStatString(ret, "<ALL TABLES>"),
+                    expectedCount, ret.length);
+        }
     }
 
     /**
@@ -98,7 +144,7 @@ public class IndexStatsUtil {
      */
     public void assertTableStats(String table, int expectedCount)
             throws SQLException {
-        assertStatCount(getStatsTable(table), table, expectedCount, false);
+        getStatsTable(table, expectedCount);
     }
 
     /**
@@ -111,24 +157,7 @@ public class IndexStatsUtil {
      */
     public void assertIndexStats(String index, int expectedCount)
             throws SQLException {
-        assertStatCount(getStatsIndex(index), index, expectedCount, true);
-    }
-
-    /**
-     * Asserts that the expected number of statistics exists.
-     *
-     * @param stats statistics
-     * @param conglom conglomerate name
-     * @param expectedCount expected number of statistics
-     * @param isIndex {@code true} if the conglomerate is an index
-     */
-    private void assertStatCount(IdxStats[] stats, String conglom,
-                                 int expectedCount, boolean isIndex) {
-        if (stats.length != expectedCount) {
-            String name = (isIndex ? "index " : "table ") + "'" + conglom + "'";
-            Assert.assertEquals(buildStatString(stats, name),
-                    expectedCount, stats.length);
-        }
+        getStatsIndex(index, expectedCount);
     }
 
     /**
@@ -139,8 +168,6 @@ public class IndexStatsUtil {
      * @return A string representation of the statistics.
      */
     private String buildStatString(IdxStats[] stats, String name) {
-        String SEP =
-                BaseJDBCTestCase.getSystemProperty("line.separator");
         StringBuffer sb = new StringBuffer(
                 "Index statistics for " + name + SEP);
         for (int i=0; i < stats.length; i++) {
@@ -163,7 +190,8 @@ public class IndexStatsUtil {
             throws SQLException {
         if (psGetStats == null) {
             psGetStats = con.prepareStatement(
-                    "select * from SYS.SYSSTATISTICS");
+                    "select * from SYS.SYSSTATISTICS " +
+                    "order by TABLEID, REFERENCEID, COLCOUNT");
         }
         return buildStatisticsList(psGetStats.executeQuery(), getIdToNameMap());
     }
@@ -177,23 +205,40 @@ public class IndexStatsUtil {
      */
     public IdxStats[] getStatsTable(String table)
             throws SQLException {
+        return getStatsTable(table, NO_EXPECTATION);
+    }
+
+    /**
+     * Obtains statistics for the specified table, fails if the number of
+     * statistics objects isn't as expected within the timeout.
+     *
+     * @param table table name
+     * @param expectedCount number of expected statistics objects
+     * @return A list of statistics entries (possibly empty).
+     * @throws SQLException if obtaining the statistics fail
+     */
+    public IdxStats[] getStatsTable(String table, int expectedCount)
+            throws SQLException {
         if (psGetTableId == null) {
             psGetTableId = con.prepareStatement(
                 "select TABLEID from SYS.SYSTABLES where TABLENAME = ?");
         }
-        if (psGetStatsForTable == null) {
-            psGetStatsForTable = con.prepareStatement(
-                "select * from SYS.SYSSTATISTICS where TABLEID = ?");
-        }
         psGetTableId.setString(1, table);
         ResultSet rs = psGetTableId.executeQuery();
         Assert.assertTrue("No such table: " + table, rs.next());
         String tableId = rs.getString(1);
         Assert.assertFalse("More than one table named " + table, rs.next());
         rs.close();
-        psGetStatsForTable.setString(1, tableId);
-        return buildStatisticsList(
-                psGetStatsForTable.executeQuery(), getIdToNameMap());
+
+        IdxStats[] ret = querySystemTable(tableId, TABLE, expectedCount);
+        // Avoid building the stat string if not necessary.
+        if (expectedCount != NO_EXPECTATION && ret.length != expectedCount) {
+            Assert.assertEquals("failed to get statistics for table " + table +
+                    " (#expected=" + expectedCount + ", timeout=" + timeout +
+                    ")" + SEP + buildStatString(ret, table),
+                    expectedCount, ret.length);
+        }
+        return ret;
     }
 
     /**
@@ -205,25 +250,93 @@ public class IndexStatsUtil {
      */
     public IdxStats[] getStatsIndex(String index)
              throws SQLException {
+        return getStatsIndex(index, NO_EXPECTATION);
+    }
+
+    /**
+     * Obtains statistics for the specified index, fails if the number of
+     * statistics objects isn't as expected within the timeout.
+     *
+     * @param index index name
+     * @param expectedCount number of expected statistics objects
+     * @return A list of statistics entries (possibly empty).
+     * @throws SQLException if obtaining the statistics fail
+     */
+    public IdxStats[] getStatsIndex(String index, int expectedCount)
+             throws SQLException {
         if (psGetIndexId == null) {
             psGetIndexId = con.prepareStatement(
                     "select CONGLOMERATEID from SYS.SYSCONGLOMERATES where " +
                     "CONGLOMERATENAME = ? and " +
                     "CAST(ISINDEX as VARCHAR(5)) = 'true'");
         }
-        if (psGetStatsForIndex == null) {
-            psGetStatsForIndex = con.prepareStatement(
-                   "select * from SYS.SYSSTATISTICS where REFERENCEID = ?");
-        }
         psGetIndexId.setString(1, index);
         ResultSet rs = psGetIndexId.executeQuery();
         Assert.assertTrue("No such index: " + index, rs.next());
         String indexId = rs.getString(1);
         Assert.assertFalse("More than one index named " + index, rs.next());
         rs.close();
-        psGetStatsForIndex.setString(1, indexId);
-        return buildStatisticsList(
-                psGetStatsForIndex.executeQuery(), getIdToNameMap());
+
+        IdxStats[] ret = querySystemTable(indexId, INDEX, expectedCount);
+        // Avoid building the stat string if not necessary.
+        if (expectedCount != NO_EXPECTATION && ret.length != expectedCount) {
+            Assert.assertEquals("failed to get statistics for index " + index +
+                    " (#expected=" + expectedCount + ", timeout=" + timeout +
+                    ")" + SEP + buildStatString(ret, index),
+                    expectedCount, ret.length);
+        }
+        return ret;
+    }
+
+    /**
+     * Queries the system table {@code SYS.SYSSTATISTICS} for statistics
+     * associated with a specific table or index.
+     *
+     * @param conglomId conglomerate id (UUID)
+     * @param isTable tells if the conglomerate is a table or an index
+     * @param expectedCount the number of statistics objects expected, use
+     *      {@code NO_EXPECTATION} to return whatever matches the query
+     *      immediately
+     */
+    private IdxStats[] querySystemTable(String conglomId, boolean isTable,
+                                        int expectedCount)
+            throws SQLException {
+        // Assign the correct prepared statement.
+        PreparedStatement ps;
+        if (isTable) {
+            if (psGetStatsForTable == null) {
+                psGetStatsForTable = con.prepareStatement(
+                        "select * from SYS.SYSSTATISTICS " +
+                            "where TABLEID = ? " +
+                            "order by REFERENCEID, COLCOUNT");
+            }
+            ps = psGetStatsForTable;
+        } else {
+            if (psGetStatsForIndex == null) {
+                psGetStatsForIndex = con.prepareStatement(
+                        "select * from SYS.SYSSTATISTICS " +
+                            "where REFERENCEID = ? " +
+                            "order by COLCOUNT");
+            }
+            ps = psGetStatsForIndex;
+        }
+        ps.setString(1, conglomId);
+
+        long started = System.currentTimeMillis();
+        long waited = -1;
+        IdxStats[] ret = null;
+        while (waited < timeout) {
+            // Don't wait the first time.
+            if (ret != null) {
+                Utilities.sleep(Math.min(250L, timeout - waited));
+            }
+            ret = buildStatisticsList(ps.executeQuery(), getIdToNameMap());
+            if (expectedCount == NO_EXPECTATION || ret.length == expectedCount){
+                break;
+            }
+            waited = System.currentTimeMillis() - started;
+        }
+        return ret;
     }
 
     /**
@@ -398,5 +511,28 @@ public class IndexStatsUtil {
                     append(", created=").append(created).append('}');
             return sb.toString();
         }
+
+        /**
+         * Equality is based on the statistics entry UUID.
+         *
+         * @param obj other object
+         * @return {@code true} if the other object is considered equal to this
+         */
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final IdxStats other = (IdxStats) obj;
+            return this.id.equals(other.id);
+        }
+
+        public int hashCode() {
+            int hash = 7;
+            hash = 17 * hash + this.id.hashCode();
+            return hash;
+        }
     }
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/Utilities.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/Utilities.java?rev=1041374&r1=1041373&r2=1041374&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/Utilities.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/Utilities.java Thu Dec
 2 13:38:08 2010
@@ -1,6 +1,6 @@
 /*
  *
- * Derby - Class Utilities
+ * Derby - Class org.apache.derbyTesting.junit.Utilities
  *
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -22,142 +22,130 @@ package org.apache.derbyTesting.junit;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
+import java.util.Arrays;
 import java.util.StringTokenizer;
 
+import junit.framework.Assert;
 
 /**
- * General non-JDBC related utilities relocated from TestUtil
- *
- *
+ * General non-JDBC related utilities.
+ * Some of these were relocated from TestUtil.
  */
 public class Utilities {
 
-    public Utilities() {
-        // TODO Auto-generated constructor stub
-    }
-        /**
-         * Just converts a string to a hex literal to assist in converting test
-         * cases that used to insert strings into bit data tables
-         * Converts using UTF-16BE just like the old casts used to.
-         *
-         * @param s  String to convert  (e.g
-         * @return hex literal that can be inserted into a bit column.
-         */
-        public static String stringToHexLiteral(String s)
-        {
-                byte[] bytes;
-                String hexLiteral = null;
-                try {
-                        bytes = s.getBytes("UTF-16BE");
-                        hexLiteral = convertToHexString(bytes);
-                }
-                catch (UnsupportedEncodingException ue)
-                {
-                        System.out.println("This shouldn't happen as UTF-16BE should be supported");
-                        ue.printStackTrace();
-                }
+    private Utilities() { }
 
-                return hexLiteral;
+    /**
+     * Converts a string to a hex literal to assist in converting test
+     * cases that used to insert strings into bit data tables.
+     * <p>
+     * Converts using UTF-16BE just like the old casts used to.
+     *
+     * @param s string to convert
+     * @return hex literal that can be inserted into a bit column.
+     */
+    public static String stringToHexLiteral(String s) {
+        byte[] bytes;
+        String hexLiteral = null;
+        try {
+            bytes = s.getBytes("UTF-16BE");
+            hexLiteral = convertToHexString(bytes);
+        } catch (UnsupportedEncodingException ue) {
+            Assert.fail("Encoding UTF-16BE unavailable: " + ue.getMessage());
         }
 
-        /**
-         * Convert a byte array to a hex string suitable for insert 
-         * @param buf  byte array to convert
-         * @return     formated string representing byte array
-         */
-        private static String convertToHexString(byte [] buf)
-        {
-                StringBuffer str = new StringBuffer();
-                str.append("X'");
-                String val;
-                int byteVal;
-                for (int i = 0; i < buf.length; i++)
-                {
-                        byteVal = buf[i] & 0xff;
-                        val = Integer.toHexString(byteVal);
-                        if (val.length() < 2)
-                                str.append("0");
-                        str.append(val);
-                }
-                return str.toString() +"'";
+        return hexLiteral;
+    }
+
+    /**
+     * Convert a byte array to a hex string suitable for insert.
+     *
+     * @param buf  byte array to convert
+     * @return     formated string representing byte array
+     */
+    private static String convertToHexString(byte[] buf) {
+        StringBuffer str = new StringBuffer();
+        str.append("X'");
+        String val;
+        int byteVal;
+        for (int i = 0; i < buf.length; i++) {
+            byteVal = buf[i] & 0xff;
+            val = Integer.toHexString(byteVal);
+            if (val.length() < 2) {
+                str.append("0");
+            }
+            str.append(val);
         }
+        return str.toString() + "'";
+    }
+
+    /**
+     * Creates a string with the specified length.
+     * <p>
+     * Called from various tests to test edge cases and such.
+     *
+     * @param c             character to repeat
+     * @param repeatCount   Number of times to repeat character
+     * @return              String of repeatCount characters c
+     */
+    public static String repeatChar(String c, int repeatCount) {
+        char[] chArray = new char[repeatCount];
+        Arrays.fill(chArray, c.charAt(0));
+        return String.valueOf(chArray);
+    }
 
-    	/**
-    	 * repeatChar is used to create strings of varying lengths.
-    	 * called from various tests to test edge cases and such.
-    	 *
-    	 * @param c             character to repeat
-    	 * @param repeatCount   Number of times to repeat character
-    	 * @return              String of repeatCount characters c
-    	 */
-       public static String repeatChar(String c, int repeatCount)
-       {
-    	   char ch = c.charAt(0);
-
-    	   char[] chArray = new char[repeatCount];
-    	   for (int i = 0; i < repeatCount; i++)
-    	   {
-    		   chArray[i] = ch;
-    	   }
-
-    	   return new String(chArray);
-
-       }
-
-        /**
-         * Print out resultSet in two dimensional array format, for use by
-         * JDBC.assertFullResultSet(rs,expectedRows) expectedRows argument.
-         * Useful while converting tests to get output in correct format.
-         * 
-         * @param rs
-         * @throws SQLException
-         */
-        public static void showResultSet(ResultSet rs) throws SQLException {
+    /**
+     * Print out resultSet in two dimensional array format, for use by
+     * JDBC.assertFullResultSet(rs,expectedRows) expectedRows argument.
+     * Useful while converting tests to get output in correct format.
+     *
+     * @param rs result set to print
+     * @throws SQLException if accessing the result set fails
+     */
+    public static void showResultSet(ResultSet rs) throws SQLException {
+        System.out.print("{");
+        int row = 0;
+        boolean next = rs.next();
+        while (next) {
+            row++;
+            ResultSetMetaData rsmd = rs.getMetaData();
+            int nocols = rsmd.getColumnCount();
             System.out.print("{");
-            int row = 0;
-            boolean next = rs.next();
-            while (next) {
-                row++;
-                ResultSetMetaData rsmd = rs.getMetaData();
-                int nocols = rsmd.getColumnCount();
-                System.out.print("{");
-                
-                for (int i = 0; i < nocols; i++)
-                {
-                	String val = rs.getString(i+1);
-                	if (val == null)
-                		System.out.print("null");
-                	else
-                		System.out.print("\"" + rs.getString(i+1) + "\"");
-                    if (i == (nocols -1))
-                        System.out.print("}");
-                    else
-                        System.out.print(",");
-                           
+
+            for (int i = 0; i < nocols; i++) {
+                String val = rs.getString(i + 1);
+                if (val == null) {
+                    System.out.print("null");
+                } else {
+                    System.out.print("\"" + rs.getString(i + 1) + "\"");
+                }
+                if (i == (nocols - 1)) {
+                    System.out.print("}");
+                } else {
+                    System.out.print(",");
                 }
-                next = rs.next();
-                   
-                if (next)
-                    System.out.println(",");
-                else
-                    System.out.println("};\n");
+
+            }
+            next = rs.next();
+
+            if (next) {
+                System.out.println(",");
+            } else {
+                System.out.println("};\n");
             }
         }
-        
+    }
+
     /**
-     * Calls the public method <code>getInfo</code> of the sysinfo tool within
-     * this JVM and returns a <code>BufferedReader</code> for reading its 
+     * Calls the public method {@code getInfo} of the sysinfo tool within
+     * this JVM and returns a {@code BufferedReader} for reading its
      * output. This is useful for obtaining system information that could be 
      * used to verify, for example, values returned by Derby MBeans.
      * 
@@ -173,13 +161,13 @@ public class Utilities {
         pw.close();
         byte[] outBytes = byteStream.toByteArray();
         BufferedReader sysinfoOutput = new BufferedReader(
-                    new InputStreamReader(
-                            new ByteArrayInputStream(outBytes)));
+                new InputStreamReader(
+                new ByteArrayInputStream(outBytes)));
         return sysinfoOutput;
     }
-    
+
     /**
-     * <p>Calls the public method <code>getSysInfo()</code> of the Network

+     * <p>Calls the public method {@code getSysInfo} of the Network
      * Server instance associated with the current test configuration and 
      * returns the result as a BufferedReader, making it easy to analyse the 
      * output line by line.</p>
@@ -192,29 +180,24 @@ public class Utilities {
      * @see org.apache.derby.drda.NetworkServerControl#getSysinfo()
      */
     public static BufferedReader getSysinfoFromServer() throws Exception {
-        
+
         return new BufferedReader(new StringReader(
                 NetworkServerTestSetup.getNetworkServerControl().getSysinfo()));
     }
-    
+
     /**
      * Splits a string around matches of the given delimiter character.
      * Copied from org.apache.derby.iapi.util.StringUtil
      *
      * Where applicable, this method can be used as a substitute for
-     * <code>String.split(String regex)</code>, which is not available
+     * {@code String.split(String regex)}, which is not available
      * on a JSR169/Java ME platform.
      *
      * @param str the string to be split
      * @param delim the delimiter
      * @throws NullPointerException if str is null
      */
-    static public String[] split(String str, char delim)
-    {
-        if (str == null) {
-            throw new NullPointerException("str can't be null");
-        }
-
+    public static String[] split(String str, char delim) {
         // Note the javadoc on StringTokenizer:
         //     StringTokenizer is a legacy class that is retained for
         //     compatibility reasons although its use is discouraged in
@@ -231,4 +214,21 @@ public class Utilities {
         return s;
     }
 
+    /**
+     * Sleeps the specified number of milliseconds.
+     *
+     * @param millis sleep duration
+     */
+    public static void sleep(long millis) {
+        long started = System.currentTimeMillis();
+        long waited = 0;
+        while (waited < millis) {
+            try {
+                Thread.sleep(millis - waited);
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+            waited = System.currentTimeMillis() - started;
+        }
+    }
 }



Mime
View raw message