001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.util; 015 016import static org.junit.Assert.assertEquals; 017import static org.junit.Assert.assertNotNull; 018import static org.junit.Assert.assertNull; 019import static org.junit.Assert.assertSame; 020import static org.junit.Assert.assertTrue; 021 022import java.util.HashMap; 023import java.util.Map; 024import java.util.concurrent.CountDownLatch; 025 026import org.junit.Test; 027import ch.qos.logback.core.testUtil.RandomUtil; 028 029public class LogbackMDCAdapterTest { 030 031 final static String A_SUFFIX = "A_SUFFIX"; 032 final static String B_SUFFIX = "B_SUFFIX"; 033 034 int diff = RandomUtil.getPositiveInt(); 035 036 private final LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter(); 037 038 /** 039 * Test that CopyOnInheritThreadLocal does not barf when the 040 * MDC hashmap is null 041 * 042 * @throws InterruptedException 043 */ 044 @Test 045 public void LOGBACK_442() throws InterruptedException { 046 Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter); 047 assertNull(parentHM); 048 049 ChildThreadForMDCAdapter childThread = new ChildThreadForMDCAdapter(mdcAdapter); 050 childThread.start(); 051 childThread.join(); 052 assertTrue(childThread.successul); 053 assertNull(childThread.childHM); 054 } 055 056 @Test 057 public void removeForNullKeyTest() { 058 mdcAdapter.remove(null); 059 } 060 061 @Test 062 public void removeInexistentKey() { 063 mdcAdapter.remove("abcdlw0"); 064 } 065 066 @Test 067 public void sequenceWithGet() { 068 mdcAdapter.put("k0", "v0"); 069 Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get(); 070 mdcAdapter.get("k0"); 071 mdcAdapter.put("k1", "v1"); // no map copy required 072 073 // verify that map0 is the same instance and that value was updated 074 assertSame(map0, mdcAdapter.copyOnThreadLocal.get()); 075 } 076 077 @Test 078 public void sequenceWithGetPropertyMap() { 079 mdcAdapter.put("k0", "v0"); 080 Map<String, String> map0 = mdcAdapter.getPropertyMap(); // point 0 081 mdcAdapter.put("k0", "v1"); // new map should be created 082 // verify that map0 is that in point 0 083 assertEquals("v0", map0.get("k0")); 084 } 085 086 @Test 087 public void sequenceWithCopyContextMap() { 088 mdcAdapter.put("k0", "v0"); 089 Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get(); 090 mdcAdapter.getCopyOfContextMap(); 091 mdcAdapter.put("k1", "v1"); // no map copy required 092 093 // verify that map0 is the same instance and that value was updated 094 assertSame(map0, mdcAdapter.copyOnThreadLocal.get()); 095 } 096 097 // ================================================= 098 099 /** 100 * Test that LogbackMDCAdapter does not copy its hashmap when a child 101 * thread inherits it. 102 * 103 * @throws InterruptedException 104 */ 105 @Test 106 public void noCopyOnInheritenceTest() throws InterruptedException { 107 CountDownLatch countDownLatch = new CountDownLatch(1); 108 String firstKey = "x" + diff; 109 String secondKey = "o" + diff; 110 mdcAdapter.put(firstKey, firstKey + A_SUFFIX); 111 112 ChildThread childThread = new ChildThread(mdcAdapter, firstKey, secondKey, countDownLatch); 113 childThread.start(); 114 countDownLatch.await(); 115 mdcAdapter.put(firstKey, firstKey + B_SUFFIX); 116 childThread.join(); 117 118 assertNull(mdcAdapter.get(secondKey)); 119 assertTrue(childThread.successful); 120 121 Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter); 122 assertTrue(parentHM != childThread.childHM); 123 124 HashMap<String, String> parentHMWitness = new HashMap<String, String>(); 125 parentHMWitness.put(firstKey, firstKey + B_SUFFIX); 126 assertEquals(parentHMWitness, parentHM); 127 128 HashMap<String, String> childHMWitness = new HashMap<String, String>(); 129 childHMWitness.put(secondKey, secondKey + A_SUFFIX); 130 assertEquals(childHMWitness, childThread.childHM); 131 132 } 133 134 // see also http://jira.qos.ch/browse/LBCLASSIC-253 135 @Test 136 public void clearOnChildThreadShouldNotAffectParent() throws InterruptedException { 137 String firstKey = "x" + diff; 138 String secondKey = "o" + diff; 139 140 mdcAdapter.put(firstKey, firstKey + A_SUFFIX); 141 assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey)); 142 143 Thread clearer = new ChildThread(mdcAdapter, firstKey, secondKey) { 144 @Override 145 public void run() { 146 mdcAdapter.clear(); 147 assertNull(mdcAdapter.get(firstKey)); 148 } 149 }; 150 151 clearer.start(); 152 clearer.join(); 153 154 assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey)); 155 } 156 157 // see http://jira.qos.ch/browse/LBCLASSIC-289 158 // this test used to fail without synchronization code in LogbackMDCAdapter 159 @Test 160 public void nearSimultaneousPutsShouldNotCauseConcurrentModificationException() throws InterruptedException { 161 // For the weirdest reason, modifications to mdcAdapter must be done 162 // before the definition anonymous ChildThread class below. Otherwise, the 163 // map in the child thread, the one contained in mdcAdapter.copyOnInheritThreadLocal, 164 // is null. How strange is that? 165 166 // let the map have lots of elements so that copying it takes time 167 for (int i = 0; i < 2048; i++) { 168 mdcAdapter.put("k" + i, "v" + i); 169 } 170 171 ChildThread childThread = new ChildThread(mdcAdapter, null, null) { 172 @Override 173 public void run() { 174 for (int i = 0; i < 16; i++) { 175 mdcAdapter.put("ck" + i, "cv" + i); 176 Thread.yield(); 177 } 178 successful = true; 179 } 180 }; 181 182 childThread.start(); 183 Thread.sleep(1); 184 for (int i = 0; i < 16; i++) { 185 mdcAdapter.put("K" + i, "V" + i); 186 } 187 childThread.join(); 188 assertTrue(childThread.successful); 189 } 190 191 Map<String, String> getMapFromMDCAdapter(LogbackMDCAdapter lma) { 192 ThreadLocal<Map<String, String>> copyOnThreadLocal = lma.copyOnThreadLocal; 193 return copyOnThreadLocal.get(); 194 } 195 196 // ========================== various thread classes 197 class ChildThreadForMDCAdapter extends Thread { 198 199 LogbackMDCAdapter logbackMDCAdapter; 200 boolean successul; 201 Map<String, String> childHM; 202 203 ChildThreadForMDCAdapter(LogbackMDCAdapter logbackMDCAdapter) { 204 this.logbackMDCAdapter = logbackMDCAdapter; 205 } 206 207 @Override 208 public void run() { 209 childHM = getMapFromMDCAdapter(logbackMDCAdapter); 210 logbackMDCAdapter.get(""); 211 successul = true; 212 } 213 } 214 215 class ChildThread extends Thread { 216 217 LogbackMDCAdapter logbackMDCAdapter; 218 String firstKey; 219 String secondKey; 220 boolean successful; 221 Map<String, String> childHM; 222 CountDownLatch countDownLatch; 223 224 ChildThread(LogbackMDCAdapter logbackMDCAdapter) { 225 this(logbackMDCAdapter, null, null); 226 } 227 228 ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey) { 229 this(logbackMDCAdapter, firstKey, secondKey, null); 230 } 231 232 ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey, CountDownLatch countDownLatch) { 233 super("chil"); 234 this.logbackMDCAdapter = logbackMDCAdapter; 235 this.firstKey = firstKey; 236 this.secondKey = secondKey; 237 this.countDownLatch = countDownLatch; 238 } 239 240 @Override 241 public void run() { 242 logbackMDCAdapter.put(secondKey, secondKey + A_SUFFIX); 243 assertNull(logbackMDCAdapter.get(firstKey)); 244 if (countDownLatch != null) 245 countDownLatch.countDown(); 246 assertNotNull(logbackMDCAdapter.get(secondKey)); 247 assertEquals(secondKey + A_SUFFIX, logbackMDCAdapter.get(secondKey)); 248 249 successful = true; 250 childHM = getMapFromMDCAdapter(logbackMDCAdapter); 251 } 252 } 253}