StackTraceElementProxyTest.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.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.spi;

import ch.qos.logback.classic.ClassicConstants;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class StackTraceElementProxyTest {

    @Test
    public void nullStackTraceElementIsReplacedByNASubstitute() {
        StackTraceElementProxy step = new StackTraceElementProxy(null);

        assertNotNull(step.getStackTraceElement());
        assertSame(StackTraceElementProxy.NA_SUBSTITUTE, step.getStackTraceElement());

        StackTraceElement ste = step.getStackTraceElement();
        assertEquals(ClassicConstants.DECLARING_CLASS_NA, ste.getClassName());
        assertEquals(ClassicConstants.METHOD_NAME_NA, ste.getMethodName());
        assertEquals(ClassicConstants.FILENAME_NA, ste.getFileName());
        assertEquals(ClassicConstants.LINE_NUMBER_NA, ste.getLineNumber());
    }

    @Test
    public void getSTEAsStringWorksWithNullInput() {
        StackTraceElementProxy step = new StackTraceElementProxy(null);
        String steAsString = step.getSTEAsString();
        assertNotNull(steAsString);
        assertTrue(steAsString.startsWith("at "));
        assertTrue(steAsString.contains(ClassicConstants.DECLARING_CLASS_NA));
        assertTrue(steAsString.contains(ClassicConstants.METHOD_NAME_NA));
    }

    @Test
    public void toStringWorksWithNullInput() {
        StackTraceElementProxy step = new StackTraceElementProxy(null);
        assertEquals(step.getSTEAsString(), step.toString());
    }

    @Test
    public void hashCodeAndEqualsWorkWithNullInput() {
        StackTraceElementProxy step1 = new StackTraceElementProxy(null);
        StackTraceElementProxy step2 = new StackTraceElementProxy(null);

        assertEquals(step1, step2);
        assertEquals(step1.hashCode(), step2.hashCode());

        StackTraceElement realSTE = new StackTraceElement("com.example.Foo", "bar", "Foo.java", 42);
        StackTraceElementProxy step3 = new StackTraceElementProxy(realSTE);

        assertNotEquals(step1, step3);
        assertNotEquals(step1.hashCode(), step3.hashCode());
    }

    @Test
    public void equalsHandlesNullAndDifferentClass() {
        StackTraceElementProxy step = new StackTraceElementProxy(null);
        assertNotEquals(null, step);
        assertEquals(step, step);
    }

    @Test
    public void normalNonNullStackTraceElement() {
        StackTraceElement realSTE = new StackTraceElement("com.example.MyClass", "doSomething", "MyClass.java", 123);
        StackTraceElementProxy step = new StackTraceElementProxy(realSTE);

        assertSame(realSTE, step.getStackTraceElement());
        assertTrue(step.getSTEAsString().startsWith("at com.example.MyClass.doSomething(MyClass.java:123)"));
        assertEquals(step.getSTEAsString(), step.toString());
    }

    @Test
    public void classPackagingData() {
        StackTraceElementProxy step = new StackTraceElementProxy(null);

        assertEquals(null, step.getClassPackagingData());

        ClassPackagingData cpd = new ClassPackagingData("some.jar", "1.0", true);
        step.setClassPackagingData(cpd);
        assertSame(cpd, step.getClassPackagingData());

        // second set must throw
        assertThrows(IllegalStateException.class, () -> step.setClassPackagingData(new ClassPackagingData("x", "y")));

        // packaging data affects equals
        StackTraceElementProxy step2 = new StackTraceElementProxy(null);
        step2.setClassPackagingData(new ClassPackagingData("some.jar", "1.0", true));
        assertEquals(step, step2);

        StackTraceElementProxy step3 = new StackTraceElementProxy(null);
        step3.setClassPackagingData(new ClassPackagingData("other.jar", "2.0", false));
        assertNotEquals(step, step3);
    }

    @Test
    public void serializationRoundTripPreservesBehavior() throws IOException, ClassNotFoundException {
        // normal STE case
        StackTraceElement realSTE = new StackTraceElement("p.q.R", "m", "R.java", 7);
        StackTraceElementProxy original = new StackTraceElementProxy(realSTE);
        original.setClassPackagingData(new ClassPackagingData("loc", "ver"));
        String originalAsString = original.getSTEAsString(); // force cache of transient

        StackTraceElementProxy deserialized = roundTripSerialize(original);
        assertEquals(original, deserialized);
        assertEquals(originalAsString, deserialized.getSTEAsString());
        assertEquals(original.getClassPackagingData(), deserialized.getClassPackagingData()); // deserialized instance, use equals not same

        // null STE (NA_SUBSTITUTE) case
        StackTraceElementProxy originalNull = new StackTraceElementProxy(null);
        String originalNullAsString = originalNull.getSTEAsString();

        StackTraceElementProxy deserializedNull = roundTripSerialize(originalNull);
        assertEquals(originalNull, deserializedNull);
        assertEquals(StackTraceElementProxy.NA_SUBSTITUTE, deserializedNull.getStackTraceElement());
        assertEquals(originalNullAsString, deserializedNull.getSTEAsString());
    }

    private StackTraceElementProxy roundTripSerialize(StackTraceElementProxy input) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(input);
        }
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
            return (StackTraceElementProxy) ois.readObject();
        }
    }
}