/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.configuration.messages;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.opensolaris.opengrok.configuration.RuntimeEnvironment;
public class ExpirationNormalMessageTest {
RuntimeEnvironment env;
private Message[] makeArray(Message... messages) {
return messages;
}
protected void sleep(long milis) {
try {
Thread.sleep(milis);
} catch (InterruptedException ex) {
}
}
@Before
public void setUp() {
env = RuntimeEnvironment.getInstance();
env.removeAllMessages();
}
@After
public void tearDown() {
env.removeAllMessages();
}
@Test
public void testExpirationSingle() {
runSingle();
}
@Test
public void testExpirationSingleTimer() {
env.startExpirationTimer();
runSingle();
env.stopExpirationTimer();
}
@Test
public void testExpirationMultiple() {
runMultiple();
}
@Test
public void testExpirationMultipleTimer() {
env.startExpirationTimer();
runMultiple();
env.stopExpirationTimer();
}
/**
* This doesn't make sense since we're testing the behaviour of the timer
* thread.
*/
@Test
public void testExpirationConcurrent() throws Exception {
for (int i = 0; i < 10; i++) {
runConcurrentModification();
}
}
@Test
public void testExpirationConcurrentTimer() throws Exception {
env.startExpirationTimer();
for (int i = 0; i < 10; i++) {
runConcurrentModification();
}
env.stopExpirationTimer();
}
protected void runSingle() {
Assert.assertEquals(0, env.getMessagesInTheSystem());
NormalMessage m1 = new NormalMessage();
m1.addTag("main")
.setExpiration(new Date(System.currentTimeMillis() + 500));
m1.setText("text");
env.addMessage(m1);
Assert.assertEquals(1, env.getMessagesInTheSystem());
for (int i = 0; i < 5; i++) {
Assert.assertEquals(1, env.getMessagesInTheSystem());
Assert.assertNotNull(env.getMessages());
Assert.assertEquals(new TreeSet<Message>(Arrays.asList(makeArray(m1))), env.getMessages());
sleep(100);
}
sleep(30);
Assert.assertEquals(0, env.getMessagesInTheSystem());
}
protected void runMultiple() {
Assert.assertEquals(0, env.getMessagesInTheSystem());
NormalMessage m1 = new NormalMessage();
m1.addTag("main")
.setExpiration(new Date(System.currentTimeMillis() + 300));
m1.setText("text");
env.addMessage(m1);
NormalMessage m2 = new NormalMessage();
m2.addTag("main")
.setExpiration(new Date(System.currentTimeMillis() + 600));
m2.setText("text");
env.addMessage(m2);
Assert.assertEquals(2, env.getMessagesInTheSystem());
Assert.assertNotNull(env.getMessages());
Assert.assertEquals(new TreeSet<Message>(Arrays.asList(makeArray(m1, m2))), env.getMessages());
// expire first
for (int i = 0; i < 3; i++) {
Assert.assertEquals(2, env.getMessagesInTheSystem());
Assert.assertNotNull(env.getMessages());
Assert.assertEquals(new TreeSet<Message>(Arrays.asList(makeArray(m1, m2))), env.getMessages());
sleep(100);
}
sleep(30);
// expire second
for (int i = 0; i < 3; i++) {
Assert.assertEquals(1, env.getMessagesInTheSystem());
Assert.assertNotNull(env.getMessages());
Assert.assertEquals(new TreeSet<Message>(Arrays.asList(makeArray(m2))), env.getMessages());
sleep(100);
}
sleep(30);
Assert.assertEquals(0, env.getMessagesInTheSystem());
}
protected void runConcurrentModification() throws Exception {
long current = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
NormalMessage m = new NormalMessage();
m.addTag("main");
m.setText("text");
m.setExpiration(new Date(current + 50000));
m.setCreated(new Date(current - 2000 - i));
m.apply(env);
}
Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread th, Throwable ex) {
if (ex instanceof ConcurrentModificationException) {
Assert.fail("The messages shouldn't throw an concurrent modification exception");
} else {
Assert.fail("The messages shouldn't throw any other exception, too");
}
}
};
Thread t = new Thread(new Runnable() {
@Override
public void run() {
invokeExpireMessages();
}
});
t.setUncaughtExceptionHandler(h);
Assert.assertEquals(500, env.getMessagesInTheSystem());
Assert.assertEquals(500, env.getMessages("main").size());
for (Message m : env.getMessages("main")) {
m.setExpiration(new Date(current - 2000));
}
for (int i = 0; i < 500; i++) {
if (i == 100) {
t.start();
}
try {
for (Message m : env.getMessages("main")) {
m.setText("Hello message");
Assert.assertNotNull(m.getText());
}
} catch (ConcurrentModificationException ex) {
Assert.fail("The messages shouldn't throw an concurrent modification exception");
} catch (Throwable ex) {
Assert.fail("The messages shouldn't throw any other exception, too");
}
}
try {
t.join();
} catch (InterruptedException ex) {
}
Assert.assertEquals(0, env.getMessagesInTheSystem());
Assert.assertEquals(0, env.getMessages("main").size());
}
private void invokeExpireMessages() {
try {
Method method = RuntimeEnvironment.class.getDeclaredMethod("expireMessages");
method.setAccessible(true);
method.invoke(env);
} catch (Exception ex) {
Assert.fail("invokeRemoveAll should not throw an exception");
}
}
@SuppressWarnings("unchecked")
protected Map<String, SortedSet<Message>> getTagMessages() {
try {
Field field = RuntimeEnvironment.class.getDeclaredField("tagMessages");
field.setAccessible(true);
return (Map<String, SortedSet<Message>>) field.get(env);
} catch (Throwable ex) {
Assert.fail("invoking getTagMessages should not throw an exception");
}
return null;
}
}