/*
* Copyright (C) 2009 Google Inc.
*
* Licensed 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.
*/
package org.waveprotocol.box.server.util;
import junit.framework.TestCase;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Random;
/**
* Tests {@link RandomBase64Generator}.
*
*
*/
public class RandomBase64GeneratorTest extends TestCase {
private final RandomBase64Generator[] generators = {
new RandomBase64Generator(new Random()),
new RandomBase64Generator(new SecureRandom())
};
public void testBase64IdLengths() {
for (RandomBase64Generator generator : generators)
for (int i = 0; i < 100; i++)
assertEquals(i, generator.next(i).length());
}
public void testUniqueness() {
for (RandomBase64Generator generator : generators) {
HashSet<String> set = new HashSet<String>();
// Len starts at 4 because collisions in really small strings are
// actually likely.
for (int len = 4; len < 10; len++) {
// Expect at most 1 collision among (2^i)+1 pseudo-random strings
// of length i
set.clear();
int collisions = 0;
for (int j = 0; j < (1 << len) + 1; j++) {
if (!set.add(generator.next(len))) collisions++;
}
assertTrue("improbable " + collisions + " collisions in set of " + set.size(),
collisions <= 1);
}
}
}
public void testBase64CharacterSpreadInLongId() {
final int chars = 40000;
final double variance = 0.2; // % difference
for (RandomBase64Generator generator : generators) {
int[] histogram = new int[256];
String id = generator.next(chars);
for (byte b : id.getBytes())
histogram[b + 128]++;
verifyBase64Spread(histogram, id.length(), variance);
}
}
public void testBase64IdCharacterSpreadAtFixedCharPosition() {
final int count = 40000;
final double variance = 0.3; // % difference
// Pick a fixed id length (in quads) and a fixed char position therein.
final int chars = 4 + (int) (Math.random() * 10); // between 4 and 14 characters
final int index = (int) (Math.random() * chars); // index into id strings
for (RandomBase64Generator generator : generators) {
int[] histogram = new int[256];
for (int i = 0; i < count; i++) {
String id = generator.next(chars);
byte b = id.getBytes()[index]; // char at fixed position
histogram[b + 128]++;
}
verifyBase64Spread(histogram, count, variance);
}
}
/**
* Verifies bounds of the frequency spread in a base64 histogram.
* @param histogram Maps the WEB64_ALPHABET values to frequencies.
* @param count Sum of the frequencies.
* @param variance Maximum permitted variance.
*/
private void verifyBase64Spread(int[] histogram, int count, double variance) {
double average = count / 64.0;
int accumulator = 0;
for (byte b : String.valueOf(RandomBase64Generator.WEB64_ALPHABET).getBytes()) {
int frequency = histogram[b + 128];
accumulator += frequency;
String msg = "Char " + Character.toChars(b).toString() + ", frequency " + frequency
+ ", average " + average + ", count " + count;
assertTrue(msg, average * (1 - variance) < frequency);
assertTrue(msg, average * (1 + variance) > frequency);
}
assertEquals(count, accumulator);
}
}