View Javadoc
1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.spi;
15  
16  import ch.qos.logback.classic.ClassicConstants;
17  import org.junit.jupiter.api.Test;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.ObjectInputStream;
23  import java.io.ObjectOutputStream;
24  
25  import static org.junit.jupiter.api.Assertions.assertEquals;
26  import static org.junit.jupiter.api.Assertions.assertNotEquals;
27  import static org.junit.jupiter.api.Assertions.assertNotNull;
28  import static org.junit.jupiter.api.Assertions.assertSame;
29  import static org.junit.jupiter.api.Assertions.assertThrows;
30  import static org.junit.jupiter.api.Assertions.assertTrue;
31  
32  public class StackTraceElementProxyTest {
33  
34      @Test
35      public void nullStackTraceElementIsReplacedByNASubstitute() {
36          StackTraceElementProxy step = new StackTraceElementProxy(null);
37  
38          assertNotNull(step.getStackTraceElement());
39          assertSame(StackTraceElementProxy.NA_SUBSTITUTE, step.getStackTraceElement());
40  
41          StackTraceElement ste = step.getStackTraceElement();
42          assertEquals(ClassicConstants.DECLARING_CLASS_NA, ste.getClassName());
43          assertEquals(ClassicConstants.METHOD_NAME_NA, ste.getMethodName());
44          assertEquals(ClassicConstants.FILENAME_NA, ste.getFileName());
45          assertEquals(ClassicConstants.LINE_NUMBER_NA, ste.getLineNumber());
46      }
47  
48      @Test
49      public void getSTEAsStringWorksWithNullInput() {
50          StackTraceElementProxy step = new StackTraceElementProxy(null);
51          String steAsString = step.getSTEAsString();
52          assertNotNull(steAsString);
53          assertTrue(steAsString.startsWith("at "));
54          assertTrue(steAsString.contains(ClassicConstants.DECLARING_CLASS_NA));
55          assertTrue(steAsString.contains(ClassicConstants.METHOD_NAME_NA));
56      }
57  
58      @Test
59      public void toStringWorksWithNullInput() {
60          StackTraceElementProxy step = new StackTraceElementProxy(null);
61          assertEquals(step.getSTEAsString(), step.toString());
62      }
63  
64      @Test
65      public void hashCodeAndEqualsWorkWithNullInput() {
66          StackTraceElementProxy step1 = new StackTraceElementProxy(null);
67          StackTraceElementProxy step2 = new StackTraceElementProxy(null);
68  
69          assertEquals(step1, step2);
70          assertEquals(step1.hashCode(), step2.hashCode());
71  
72          StackTraceElement realSTE = new StackTraceElement("com.example.Foo", "bar", "Foo.java", 42);
73          StackTraceElementProxy step3 = new StackTraceElementProxy(realSTE);
74  
75          assertNotEquals(step1, step3);
76          assertNotEquals(step1.hashCode(), step3.hashCode());
77      }
78  
79      @Test
80      public void equalsHandlesNullAndDifferentClass() {
81          StackTraceElementProxy step = new StackTraceElementProxy(null);
82          assertNotEquals(null, step);
83          assertEquals(step, step);
84      }
85  
86      @Test
87      public void normalNonNullStackTraceElement() {
88          StackTraceElement realSTE = new StackTraceElement("com.example.MyClass", "doSomething", "MyClass.java", 123);
89          StackTraceElementProxy step = new StackTraceElementProxy(realSTE);
90  
91          assertSame(realSTE, step.getStackTraceElement());
92          assertTrue(step.getSTEAsString().startsWith("at com.example.MyClass.doSomething(MyClass.java:123)"));
93          assertEquals(step.getSTEAsString(), step.toString());
94      }
95  
96      @Test
97      public void classPackagingData() {
98          StackTraceElementProxy step = new StackTraceElementProxy(null);
99  
100         assertEquals(null, step.getClassPackagingData());
101 
102         ClassPackagingData cpd = new ClassPackagingData("some.jar", "1.0", true);
103         step.setClassPackagingData(cpd);
104         assertSame(cpd, step.getClassPackagingData());
105 
106         // second set must throw
107         assertThrows(IllegalStateException.class, () -> step.setClassPackagingData(new ClassPackagingData("x", "y")));
108 
109         // packaging data affects equals
110         StackTraceElementProxy step2 = new StackTraceElementProxy(null);
111         step2.setClassPackagingData(new ClassPackagingData("some.jar", "1.0", true));
112         assertEquals(step, step2);
113 
114         StackTraceElementProxy step3 = new StackTraceElementProxy(null);
115         step3.setClassPackagingData(new ClassPackagingData("other.jar", "2.0", false));
116         assertNotEquals(step, step3);
117     }
118 
119     @Test
120     public void serializationRoundTripPreservesBehavior() throws IOException, ClassNotFoundException {
121         // normal STE case
122         StackTraceElement realSTE = new StackTraceElement("p.q.R", "m", "R.java", 7);
123         StackTraceElementProxy original = new StackTraceElementProxy(realSTE);
124         original.setClassPackagingData(new ClassPackagingData("loc", "ver"));
125         String originalAsString = original.getSTEAsString(); // force cache of transient
126 
127         StackTraceElementProxy deserialized = roundTripSerialize(original);
128         assertEquals(original, deserialized);
129         assertEquals(originalAsString, deserialized.getSTEAsString());
130         assertEquals(original.getClassPackagingData(), deserialized.getClassPackagingData()); // deserialized instance, use equals not same
131 
132         // null STE (NA_SUBSTITUTE) case
133         StackTraceElementProxy originalNull = new StackTraceElementProxy(null);
134         String originalNullAsString = originalNull.getSTEAsString();
135 
136         StackTraceElementProxy deserializedNull = roundTripSerialize(originalNull);
137         assertEquals(originalNull, deserializedNull);
138         assertEquals(StackTraceElementProxy.NA_SUBSTITUTE, deserializedNull.getStackTraceElement());
139         assertEquals(originalNullAsString, deserializedNull.getSTEAsString());
140     }
141 
142     private StackTraceElementProxy roundTripSerialize(StackTraceElementProxy input) throws IOException, ClassNotFoundException {
143         ByteArrayOutputStream bos = new ByteArrayOutputStream();
144         try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
145             oos.writeObject(input);
146         }
147         try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
148             return (StackTraceElementProxy) ois.readObject();
149         }
150     }
151 }