/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2013, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.util;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
import ch.qos.logback.core.testUtil.RandomUtil;
public class LogbackMDCAdapterTest {
final static String A_SUFFIX = "A_SUFFIX";
final static String B_SUFFIX = "B_SUFFIX";
int diff = RandomUtil.getPositiveInt();
private final LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter();
/**
* Test that CopyOnInheritThreadLocal does not barf when the
* MDC hashmap is null
*
* @throws InterruptedException
*/
@Test
public void lbclassic77Test() throws InterruptedException {
Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter);
assertNull(parentHM);
ChildThreadForMDCAdapter childThread = new ChildThreadForMDCAdapter(mdcAdapter);
childThread.start();
childThread.join();
assertTrue(childThread.successul);
assertNull(childThread.childHM);
}
@Test
public void removeForNullKeyTest() {
mdcAdapter.remove(null);
}
@Test
public void removeInexistentKey() {
mdcAdapter.remove("abcdlw0");
}
@Test
public void sequenceWithGet() {
mdcAdapter.put("k0", "v0");
Map<String, String> map0 = mdcAdapter.copyOnInheritThreadLocal.get();
mdcAdapter.get("k0"); // point 0
mdcAdapter.put("k0", "v1");
// verify that map0 is that in point 0
assertEquals("v0", map0.get("k0"));
}
@Test
public void sequenceWithGetPropertyMap() {
mdcAdapter.put("k0", "v0");
Map<String, String> map0 = mdcAdapter.getPropertyMap(); // point 0
mdcAdapter.put("k0", "v1");
// verify that map0 is that in point 0
assertEquals("v0", map0.get("k0"));
}
// =================================================
/**
* Test that LogbackMDCAdapter copies its hashmap when a child
* thread inherits it.
*
* @throws InterruptedException
*/
@Test
public void copyOnInheritenceTest() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
String firstKey = "x" + diff;
String secondKey = "o" + diff;
mdcAdapter.put(firstKey, firstKey + A_SUFFIX);
ChildThread childThread = new ChildThread(mdcAdapter, firstKey, secondKey, countDownLatch);
childThread.start();
countDownLatch.await();
mdcAdapter.put(firstKey, firstKey + B_SUFFIX);
childThread.join();
assertNull(mdcAdapter.get(secondKey));
assertTrue(childThread.successful);
Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter);
assertTrue(parentHM != childThread.childHM);
HashMap<String, String> parentHMWitness = new HashMap<String, String>();
parentHMWitness.put(firstKey, firstKey + B_SUFFIX);
assertEquals(parentHMWitness, parentHM);
HashMap<String, String> childHMWitness = new HashMap<String, String>();
childHMWitness.put(firstKey, firstKey + A_SUFFIX);
childHMWitness.put(secondKey, secondKey + A_SUFFIX);
assertEquals(childHMWitness, childThread.childHM);
}
// see also http://jira.qos.ch/browse/LBCLASSIC-253
@Test
public void clearOnChildThreadShouldNotAffectParent() throws InterruptedException {
String firstKey = "x" + diff;
String secondKey = "o" + diff;
mdcAdapter.put(firstKey, firstKey + A_SUFFIX);
assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey));
Thread clearer = new ChildThread(mdcAdapter, firstKey, secondKey) {
@Override
public void run() {
mdcAdapter.clear();
assertNull(mdcAdapter.get(firstKey));
}
};
clearer.start();
clearer.join();
assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey));
}
// see http://jira.qos.ch/browse/LBCLASSIC-289
// this test used to fail without synchronization code in LogbackMDCAdapter
@Test
public void nearSimultaneousPutsShouldNotCauseConcurrentModificationException() throws InterruptedException {
// For the weirdest reason, modifications to mdcAdapter must be done
// before the definition anonymous ChildThread class below. Otherwise, the
// map in the child thread, the one contained in mdcAdapter.copyOnInheritThreadLocal,
// is null. How strange is that?
// let the map have lots of elements so that copying it takes time
for (int i = 0; i < 2048; i++) {
mdcAdapter.put("k" + i, "v" + i);
}
ChildThread childThread = new ChildThread(mdcAdapter, null, null) {
@Override
public void run() {
for (int i = 0; i < 16; i++) {
mdcAdapter.put("ck" + i, "cv" + i);
Thread.yield();
}
successful = true;
}
};
childThread.start();
Thread.sleep(1);
for (int i = 0; i < 16; i++) {
mdcAdapter.put("K" + i, "V" + i);
}
childThread.join();
assertTrue(childThread.successful);
}
Map<String, String> getMapFromMDCAdapter(LogbackMDCAdapter lma) {
InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = lma.copyOnInheritThreadLocal;
return copyOnInheritThreadLocal.get();
}
// ========================== various thread classes
class ChildThreadForMDCAdapter extends Thread {
LogbackMDCAdapter logbackMDCAdapter;
boolean successul;
Map<String, String> childHM;
ChildThreadForMDCAdapter(LogbackMDCAdapter logbackMDCAdapter) {
this.logbackMDCAdapter = logbackMDCAdapter;
}
@Override
public void run() {
childHM = getMapFromMDCAdapter(logbackMDCAdapter);
logbackMDCAdapter.get("");
successul = true;
}
}
class ChildThread extends Thread {
LogbackMDCAdapter logbackMDCAdapter;
String firstKey;
String secondKey;
boolean successful;
Map<String, String> childHM;
CountDownLatch countDownLatch;
ChildThread(LogbackMDCAdapter logbackMDCAdapter) {
this(logbackMDCAdapter, null, null);
}
ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey) {
this(logbackMDCAdapter, firstKey, secondKey, null);
}
ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey, CountDownLatch countDownLatch) {
super("chil");
this.logbackMDCAdapter = logbackMDCAdapter;
this.firstKey = firstKey;
this.secondKey = secondKey;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
logbackMDCAdapter.put(secondKey, secondKey + A_SUFFIX);
assertNotNull(logbackMDCAdapter.get(firstKey));
assertEquals(firstKey + A_SUFFIX, logbackMDCAdapter.get(firstKey));
if (countDownLatch != null) countDownLatch.countDown();
assertEquals(secondKey + A_SUFFIX, logbackMDCAdapter.get(secondKey));
successful = true;
childHM = getMapFromMDCAdapter(logbackMDCAdapter);
}
}
}