/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.logging.log4j;

import org.apache.logging.log4j.spi.DefaultThreadContextMap;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Map;

import static org.junit.Assert.*;

/**
 * Tests {@link ThreadContext}.
 */
public class ThreadContextInheritanceTest {
    
    @BeforeClass
    public static void setupClass() {
        System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
    }
    
    @AfterClass
    public static void tearDownClass() {
        System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
    }

    @Test
    public void testPush() {
        ThreadContext.push("Hello");
        ThreadContext.push("{} is {}", ThreadContextInheritanceTest.class.getSimpleName(),
                "running");
        assertEquals("Incorrect parameterized stack value",
                ThreadContext.pop(), "ThreadContextInheritanceTest is running");
        assertEquals("Incorrect simple stack value", ThreadContext.pop(),
                "Hello");
    }

    @Test
    public void testInheritanceSwitchedOn() throws Exception {
        System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
        try {
            ThreadContext.clear();
            ThreadContext.put("Greeting", "Hello");
            StringBuilder sb = new StringBuilder();
            TestThread thread = new TestThread(sb);
            thread.start();
            thread.join();
            String str = sb.toString();
            assertTrue("Unexpected ThreadContext value. Expected Hello. Actual "
                    + str, "Hello".equals(str));
            sb = new StringBuilder();
            thread = new TestThread(sb);
            thread.start();
            thread.join();
            str = sb.toString();
            assertTrue("Unexpected ThreadContext value. Expected Hello. Actual "
                    + str, "Hello".equals(str));
        } finally {
            System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
        }
    }

    @Test
    public void perfTest() throws Exception {
        ThreadContext.clear();
        final Timer complete = new Timer("ThreadContextTest");
        complete.start();
        ThreadContext.put("Var1", "value 1");
        ThreadContext.put("Var2", "value 2");
        ThreadContext.put("Var3", "value 3");
        ThreadContext.put("Var4", "value 4");
        ThreadContext.put("Var5", "value 5");
        ThreadContext.put("Var6", "value 6");
        ThreadContext.put("Var7", "value 7");
        ThreadContext.put("Var8", "value 8");
        ThreadContext.put("Var9", "value 9");
        ThreadContext.put("Var10", "value 10");
        final int loopCount = 1000000;
        final Timer timer = new Timer("ThreadContextCopy", loopCount);
        timer.start();
        for (int i = 0; i < loopCount; ++i) {
            final Map<String, String> map = ThreadContext.getImmutableContext();
            assertNotNull(map);
        }
        timer.stop();
        complete.stop();
        System.out.println(timer.toString());
        System.out.println(complete.toString());
    }

    @Test
    public void testGetContextReturnsEmptyMapIfEmpty() {
        ThreadContext.clear();
        assertTrue(ThreadContext.getContext().isEmpty());
    }

    @Test
    public void testGetContextReturnsMutableCopy() {
        ThreadContext.clear();
        final Map<String, String> map1 = ThreadContext.getContext();
        assertTrue(map1.isEmpty());
        map1.put("K", "val"); // no error
        assertEquals("val", map1.get("K"));

        // adding to copy does not affect thread context map
        assertTrue(ThreadContext.getContext().isEmpty());

        ThreadContext.put("key", "val2");
        final Map<String, String> map2 = ThreadContext.getContext();
        assertEquals(1, map2.size());
        assertEquals("val2", map2.get("key"));
        map2.put("K", "val"); // no error
        assertEquals("val", map2.get("K"));

        // first copy is not affected
        assertNotSame(map1, map2);
        assertEquals(1, map1.size());
    }

    @Test
    public void testGetImmutableContextReturnsEmptyMapIfEmpty() {
        ThreadContext.clear();
        assertTrue(ThreadContext.getImmutableContext().isEmpty());
    }

    @Test(expected = UnsupportedOperationException.class)
    public void testGetImmutableContextReturnsImmutableMapIfNonEmpty() {
        ThreadContext.clear();
        ThreadContext.put("key", "val");
        final Map<String, String> immutable = ThreadContext.getImmutableContext();
        immutable.put("otherkey", "otherval");
    }

    @Test(expected = UnsupportedOperationException.class)
    public void testGetImmutableContextReturnsImmutableMapIfEmpty() {
        ThreadContext.clear();
        final Map<String, String> immutable = ThreadContext.getImmutableContext();
        immutable.put("otherkey", "otherval");
    }

    @Test
    public void testGetImmutableStackReturnsEmptyStackIfEmpty() {
        ThreadContext.clearStack();
        assertTrue(ThreadContext.getImmutableStack().asList().isEmpty());
    }

    @Test
    public void testPut() {
        ThreadContext.clear();
        assertNull(ThreadContext.get("testKey"));
        ThreadContext.put("testKey", "testValue");
        assertEquals("testValue", ThreadContext.get("testKey"));
    }

    @Test
    public void testRemove() {
        ThreadContext.clear();
        assertNull(ThreadContext.get("testKey"));
        ThreadContext.put("testKey", "testValue");
        assertEquals("testValue", ThreadContext.get("testKey"));

        ThreadContext.remove("testKey");
        assertNull(ThreadContext.get("testKey"));
        assertTrue(ThreadContext.isEmpty());
    }

    @Test
    public void testContainsKey() {
        ThreadContext.clear();
        assertFalse(ThreadContext.containsKey("testKey"));
        ThreadContext.put("testKey", "testValue");
        assertTrue(ThreadContext.containsKey("testKey"));

        ThreadContext.remove("testKey");
        assertFalse(ThreadContext.containsKey("testKey"));
    }

    private class TestThread extends Thread {

        private final StringBuilder sb;

        public TestThread(final StringBuilder sb) {
            this.sb = sb;
        }

        @Override
        public void run() {
            final String greeting = ThreadContext.get("Greeting");
            if (greeting == null) {
                sb.append("null");
            } else {
                sb.append(greeting);
            }
            ThreadContext.clear();
        }
    }
}

@@ -1248,24 +1248,54 @@
 P, %22true%22);%0A
+        ThreadContext.init();%0A
     %7D%0A    %0A 
@@ -1417,24 +1417,54 @@
 TABLE_MAP);%0A
+        ThreadContext.init();%0A
     %7D%0A%0A    @

Test order should not matter. When this test class is invoked from Eclipse for example, while running all tests in the project, it will fail. Use recently refactored static block into the new ThreadContext.init().