Fix testShardsAreSomewhatBalanced to only check for shards that are too big, and avoid nondeterministic behavior due to garbage collection. (If there are any more platform-specific failures, they will be due to differences in String.hashCode().)

Review at http://gwt-code-reviews.appspot.com/1578810

Review by: rdayal@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10732 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/util/StringInterner.java b/dev/core/src/com/google/gwt/dev/util/StringInterner.java
index 106e0e9..ee4fbf0 100644
--- a/dev/core/src/com/google/gwt/dev/util/StringInterner.java
+++ b/dev/core/src/com/google/gwt/dev/util/StringInterner.java
@@ -53,20 +53,14 @@
    * Returns a string equal to the input string, but not always the same.
    */
   public String intern(String original) {
-    int hashCode = original.hashCode();
-    int shardId = (hashCode ^ (hashCode >> SHARD_BITS)) & SHARD_MASK;
+    int shardId = getShardId(original);
     return shards[shardId].intern(original);
   }
 
-  /**
-   * Returns the size of each shard, for testing shard balance.
-   */
-  int[] getShardSizes() {
-    int[] result = new int[SHARD_COUNT];
-    for (int i = 0; i < shards.length; i++) {
-      result[i] = shards[i].map.size();
-    }
-    return result;
+  /* visible for testing */
+  int getShardId(String original) {
+    int hashCode = original.hashCode();
+    return (hashCode ^ (hashCode >> SHARD_BITS)) & SHARD_MASK;
   }
 
   private static class Shard {
diff --git a/dev/core/test/com/google/gwt/dev/util/StringInternerTest.java b/dev/core/test/com/google/gwt/dev/util/StringInternerTest.java
index c17104a..ab98ee4 100644
--- a/dev/core/test/com/google/gwt/dev/util/StringInternerTest.java
+++ b/dev/core/test/com/google/gwt/dev/util/StringInternerTest.java
@@ -51,28 +51,33 @@
   }
 
   public void testShardsAreSomewhatBalanced() throws Exception {
-    for (int i = 0; i < 1000000; i++) {
-      interner.intern("foo" + i);
+
+    // Simulate adding a million strings. We use the production algorithm to choose the shard,
+    // but increment a counter instead. This avoids the WeakHashMap's nondeterministic
+    // behavior due to garbage collection.
+    int meanShardSize = 1000;
+    int shardSizes[] = new int[StringInterner.SHARD_COUNT];
+    int stringsToAdd = StringInterner.SHARD_COUNT * meanShardSize;
+    for (int i = 0; i < stringsToAdd; i++) {
+      int shardId = interner.getShardId("foo" + i);
+      shardSizes[shardId]++;
     }
 
-    int[] shardSizes = interner.getShardSizes();
-    assertEquals(StringInterner.SHARD_COUNT, shardSizes.length);
+    // Verify that no shards are too big. (A shard that's oversized could create lock contention.)
 
-    int minShardSize = Integer.MAX_VALUE;
+    int expectedMaxShardSize = meanShardSize * 2;
     int maxShardSize = 0;
-    int total = 0;
-    for (int candidate : shardSizes) {
-      minShardSize = Math.min(minShardSize, candidate);
-      maxShardSize = Math.max(maxShardSize, candidate);
-      total += candidate;
+    int tooBigShardCount = 0;
+    for (int shardSize : shardSizes) {
+      maxShardSize = Math.max(maxShardSize, shardSize);
+      if (shardSize > expectedMaxShardSize) {
+        tooBigShardCount++;
+      }
     }
 
-    int meanShardSize = total / shardSizes.length;
-    int expectedMinShardSize = meanShardSize / 5;
-    int expectedMaxShardSize = meanShardSize * 5;
-    if (minShardSize < expectedMinShardSize || maxShardSize > expectedMaxShardSize) {
-      fail("unbalanced shards: [" + minShardSize + "," + maxShardSize + "] not within [" +
-      expectedMinShardSize + ", " + expectedMaxShardSize + "]");
+    if (tooBigShardCount > 0) {
+      fail(tooBigShardCount + " of " + shardSizes.length + " shards are too big (more than " +
+          expectedMaxShardSize + " entries); largest shard has " + maxShardSize + " entries.");
     }
   }