/* * Copyright 2008-2009 LinkedIn, Inc * Copyright (c) 2013 Big Switch Networks, 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.sdnplatform.sync.internal; import java.io.File; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import java.util.Random; import org.sdnplatform.sync.internal.util.ByteArray; import org.sdnplatform.sync.internal.version.VectorClock; /** * Helper utilities for tests * * */ public class TUtils { public static final String DIGITS = "0123456789"; public static final String LETTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; public static final String CHARACTERS = LETTERS + DIGITS + "~!@#$%^&*()____+-=[];',,,./>?:{}"; public static final Random SEEDED_RANDOM = new Random(19873482374L); public static final Random UNSEEDED_RANDOM = new Random(); /** * Get a vector clock with events on the sequence of nodes given So * getClock(1,1,2,2,2) means a clock that has two writes on node 1 and 3 * writes on node 2. The timestamp will be the current time * * @param nodes The sequence of nodes * @return A VectorClock initialized with the given sequence of events */ public static VectorClock getClock(int... nodes) { VectorClock clock = new VectorClock(); return increment(clock, nodes); } /** * Get a vector clock with a the given timestamp and events on the * sequence of nodes given So getClock(1,1,2,2,2) means a clock that has * two writes on node 1 and 3 writes on node 2. * * @param nodes The sequence of nodes * @return A VectorClock initialized with the given sequence of events */ public static VectorClock getClockT(long timestamp, int... nodes) { VectorClock clock = new VectorClock(timestamp); return incrementT(timestamp, clock, nodes); } /** * Record events for the given sequence of nodes * * @param clock The VectorClock to record the events on * @param nodes The sequences of node events */ public static VectorClock incrementT(long timestamp, VectorClock clock, int... nodes) { for(int n: nodes) clock = clock.incremented((short) n, timestamp); return clock; } /** * Record events for the given sequence of nodes * * @param clock The VectorClock to record the events on * @param nodes The sequences of node events */ public static VectorClock increment(VectorClock clock, int... nodes) { for(int n: nodes) clock = clock.incremented((short) n, System.currentTimeMillis()); return clock; } /** * Test two byte arrays for (deep) equality. I think this exists in java 6 * but not java 5 * * @param a1 Array 1 * @param a2 Array 2 * @return True iff a1.length == a2.length and a1[i] == a2[i] for 0 <= i < * a1.length */ public static boolean bytesEqual(byte[] a1, byte[] a2) { if(a1 == a2) { return true; } else if(a1 == null || a2 == null) { return false; } else if(a1.length != a2.length) { return false; } else { for(int i = 0; i < a1.length; i++) if(a1[i] != a2[i]) return false; } return true; } /** * Create a string with some random letters * * @param length The length of the string to create * @return The string */ public static String randomLetters(int length) { return randomString(LETTERS, length); } /** * Create a string that is a random sample (with replacement) from the given * string * * @param sampler The string to sample from * @param length The length of the string to create * @return The created string */ public static String randomString(String sampler, int length) { StringBuilder builder = new StringBuilder(length); for(int i = 0; i < length; i++) builder.append(sampler.charAt(SEEDED_RANDOM.nextInt(sampler.length()))); return builder.toString(); } /** * Generate an array of random bytes * * @param length * @return */ public static byte[] randomBytes(int length) { byte[] bytes = new byte[length]; SEEDED_RANDOM.nextBytes(bytes); return bytes; } /** * Return an array of length count containing random integers in the range * (0, max) generated off the test rng. * * @param max The bound on the random number size * @param count The number of integers to generate * @return The array of integers */ public static int[] randomInts(int max, int count) { int[] vals = new int[count]; for(int i = 0; i < count; i++) vals[i] = SEEDED_RANDOM.nextInt(max); return vals; } /** * Weirdly java doesn't seem to have Arrays.shuffle(), this terrible hack * does that. * * @return A shuffled copy of the input */ public static int[] shuffle(int[] input) { List<Integer> vals = new ArrayList<Integer>(input.length); for(int i = 0; i < input.length; i++) vals.add(input[i]); Collections.shuffle(vals, SEEDED_RANDOM); int[] copy = new int[input.length]; for(int i = 0; i < input.length; i++) copy[i] = vals.get(i); return copy; } /** * Compute the requested quantile of the given array * * @param values The array of values * @param quantile The quantile requested (must be between 0.0 and 1.0 * inclusive) * @return The quantile */ public static long quantile(long[] values, double quantile) { if(values == null) throw new IllegalArgumentException("Values cannot be null."); if(quantile < 0.0 || quantile > 1.0) throw new IllegalArgumentException("Quantile must be between 0.0 and 1.0"); long[] copy = new long[values.length]; System.arraycopy(values, 0, copy, 0, copy.length); Arrays.sort(copy); int index = (int) (copy.length * quantile); return copy[index]; } /** * Compute the mean of the given values * * @param values The values * @return The mean */ public static double mean(long[] values) { double total = 0.0; for(int i = 0; i < values.length; i++) total += values[i]; return total / values.length; } /** * Create a temporary directory in the directory given by java.io.tmpdir * * @return The directory created. */ public static File createTempDir() { return createTempDir(new File(System.getProperty("java.io.tmpdir"))); } /** * Create a temporary directory that is a child of the given directory * * @param parent The parent directory * @return The temporary directory */ public static File createTempDir(File parent) { File temp = new File(parent, Integer.toString(Math.abs(UNSEEDED_RANDOM.nextInt()) % 1000000)); temp.delete(); temp.mkdir(); temp.deleteOnExit(); return temp; } /** * Wrap the given string in quotation marks. This is slightly more readable * then the java inline quotes that require escaping. * * @param s The string to wrap in quotes * @return The string */ public static String quote(String s) { return "\"" + s + "\""; } /** * Always uses UTF-8. */ public static ByteArray toByteArray(String s) { try { return new ByteArray(s.getBytes("UTF-8")); } catch(UnsupportedEncodingException e) { /* Should not happen */ throw new IllegalStateException(e); } } /* public static void assertWithBackoff(long timeout, Attempt attempt) throws Exception { assertWithBackoff(30, timeout, attempt); } public static void assertWithBackoff(long initialDelay, long timeout, Attempt attempt) throws Exception { long delay = initialDelay; long finishBy = System.currentTimeMillis() + timeout; while(true) { try { attempt.checkCondition(); return; } catch(AssertionError e) { if(System.currentTimeMillis() < finishBy) { Thread.sleep(delay); delay *= 2; } else { throw e; } } } } */ /** * Because java.beans.ReflectionUtils isn't public... */ @SuppressWarnings("unchecked") public static <T> T getPrivateValue(Object instance, String fieldName) throws Exception { Field eventDataQueueField = instance.getClass().getDeclaredField(fieldName); eventDataQueueField.setAccessible(true); return (T) eventDataQueueField.get(instance); } /** * Constructs a calendar object representing the given time */ public static GregorianCalendar getCalendar(int year, int month, int day, int hour, int mins, int secs) { GregorianCalendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month); cal.set(Calendar.DATE, day); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, mins); cal.set(Calendar.SECOND, secs); cal.set(Calendar.MILLISECOND, 0); return cal; } }