/*
* Copyright (C) 2012 Facebook, 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 com.facebook.testing;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.Assert;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import static org.testng.Assert.assertEquals;
public class TestUtils {
private TestUtils() {
throw new AssertionError();
}
public static <T> Function<T> noOpFunction() {
return new Function<T>() {
@Override
public void execute(T argument) {
// noting
}
};
}
public static AtomicInteger countCompletedRunnables(
int numTasks, Function<Runnable> submissionCallback
) {
final AtomicInteger completed = new AtomicInteger(0);
for (int i = 0; i < numTasks; i++) {
submissionCallback.execute(
new Runnable() {
@Override
public void run() {
completed.incrementAndGet();
}
}
);
}
return completed;
}
public static <V> AtomicInteger countCompletedCallables(
int numTasks, Function<Callable<V>> submissionCallback
) {
final AtomicInteger completed = new AtomicInteger(0);
for (int i = 0; i < numTasks; i++) {
submissionCallback.execute(
new Callable<V>() {
@Override
public V call() throws Exception {
completed.incrementAndGet();
return null;
}
}
);
}
return completed;
}
public static String generateString(int start, int length) {
try {
return new String(generateSequentialBytes(start, length), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static byte[] generateSequentialBytes(int start, int length) {
byte[] result = new byte[length];
for (int i = 0; i < length; i++) {
result[i] = (byte) ((start + i) % 127);
}
return result;
}
/**
* a bit hackish, we wait until the thread is not runnable or new to indicate
* that is is blocked on a lock or monitor
*
* @param t thread to wait for
*/
public static void waitUntilThreadBlocks(Thread t) {
Thread.State state = t.getState();
while (true) {
switch (state) {
case NEW:
case RUNNABLE:
state = t.getState();
continue;
case TERMINATED:
case BLOCKED:
case TIMED_WAITING:
case WAITING:
return;
}
}
}
public static Thread runInThread(Runnable runnable) {
return runInThread(runnable, null);
}
public static Thread runInThread(Runnable runnable, String threadName) {
Thread t = new Thread(runnable);
if (threadName != null) {
t.setName(threadName);
}
t.start();
return t;
}
/**
* generate count distinct moments in time, 1 second apart, then randomly order them
*
* @param baseDateTime start moment
* @param count how many moments to generate
* @return
*/
public static List<DateTime> generateMoments(DateTime baseDateTime, int count) {
List<DateTime> result = new ArrayList<DateTime>(count);
for (int i = 0; i < count; i++) {
result.add(baseDateTime.plusSeconds(i));
}
Collections.shuffle(result);
return result;
}
/**
* Returns a set of timezones that are distinct form each other in the kind of offsets they
* have from UTC.
* Note: This method returns timezones having the same offsets but are different in that they
* have fixed or non-fixed offsets. However, it's possible that two timezones with non-fixed
* offsets have different transitions, only one of such timezones will be returned here.
*/
public static Collection<DateTimeZone> getDistinctTimeZones() {
Multimap<Integer, DateTimeZone> timeZones = LinkedHashMultimap.create();
// figure out a set of timezones with unique offsets
for (Object obj : DateTimeZone.getAvailableIDs()) {
String id = (String) obj;
DateTimeZone dateTimeZone = DateTimeZone.forID(id);
TimeZone timeZone = dateTimeZone.toTimeZone();
timeZones.put(timeZone.getRawOffset(), dateTimeZone);
timeZones.put(timeZone.getRawOffset() + timeZone.getDSTSavings(), dateTimeZone);
}
Set<DateTimeZone> distinctZones = new HashSet<DateTimeZone>();
for (Map.Entry<Integer, Collection<DateTimeZone>> entry : timeZones.asMap().entrySet()) {
DateTimeZone fixedZone = null;
DateTimeZone unfixedZone = null;
for (DateTimeZone timeZone : entry.getValue()) {
if (timeZone.isFixed()) {
if (fixedZone == null) {
fixedZone = timeZone;
}
} else {
if (unfixedZone == null) {
unfixedZone = timeZone;
}
}
}
// Collect one instance each of fixed and non-fixed timezones
if (fixedZone != null) {
distinctZones.add(fixedZone);
}
if (unfixedZone != null) {
distinctZones.add(unfixedZone);
}
}
return distinctZones;
}
public static void assertContains(String haystack, Object needle) {
Assert.assertTrue(
haystack != null && haystack.contains(String.valueOf(needle)),
String.format("Expected to find '%s' in: %s", needle, haystack)
);
}
/**
* Same as TestNG's {@link Assert#assertEquals(Object, Object)} except the message text is
* guaranteed to use the {@link Object#toString()} of the objects. Useful for short maps and lists
* where seeing all the elements is more useful than just the one differing element or size
* difference.
*
* @param actual
* @param expected
*/
public static void assertEqualsWithNiceMessage(Object actual, Object expected) {
assertEqualsWithNiceMessage(actual, expected, null);
}
/**
* Same as TestNG's {@link Assert#assertEquals(Object, Object)} except the message text is
* guaranteed to use the {@link Object#toString()} of the objects. Useful for short maps and lists
* where seeing all the elements is more useful than just the one differing element or size
* difference.
*
* @param actual actual value
* @param expected expected value
* @param message custom message to prepend to default message
*/
public static void assertEqualsWithNiceMessage(Object actual, Object expected, String message) {
message = message == null ? "" : (message + " ");
// using the same formatting as Assert.format() makes Idea do nice UI things (e.g., "Click to
// see difference")
assertEquals(
actual,
expected,
message + "expected:<" + expected + "> but was:<" + actual + ">"
);
}
}