1+ /*
2+ * Copyright (c) 2017-2026 Ronald Brill
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ * https://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ package org .htmlunit .cyberneko ;
16+
17+ import static org .junit .jupiter .api .Assertions .assertEquals ;
18+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
19+ import static org .junit .jupiter .api .Assertions .assertNull ;
20+ import static org .junit .jupiter .api .Assertions .assertSame ;
21+ import static org .junit .jupiter .api .Assertions .assertThrows ;
22+
23+ import org .htmlunit .cyberneko .HTMLTagBalancer .Info ;
24+ import org .htmlunit .cyberneko .HTMLTagBalancer .InfoStack ;
25+ import org .htmlunit .cyberneko .HTMLTagBalancer .InfoStackWithOpenCounters ;
26+ import org .htmlunit .cyberneko .xerces .xni .QName ;
27+ import org .junit .jupiter .api .Test ;
28+
29+ /**
30+ * Unit tests for {@link InfoStack}.
31+ */
32+ public class InfoStackWithOpenCountersTest {
33+
34+ private static Info newInfo (final String name ) {
35+ final HTMLElements elements = new HTMLElements ();
36+ final QName qname = new QName (null , name , name , null );
37+ return new Info (elements .getElement (name ), qname );
38+ }
39+
40+ // ---- basic push / peek / pop ----
41+
42+ @ Test
43+ public void pushPeekPop () {
44+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
45+ final Info info = newInfo ("div" );
46+
47+ stack .push (info );
48+
49+ assertEquals (1 , stack .length );
50+ assertSame (info , stack .peek ());
51+ assertSame (info , stack .pop ());
52+ assertEquals (0 , stack .length );
53+ }
54+
55+ @ Test
56+ public void lifoOrder () {
57+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
58+ final Info a = newInfo ("div" );
59+ final Info b = newInfo ("span" );
60+ final Info c = newInfo ("p" );
61+
62+ stack .push (a );
63+ stack .push (b );
64+ stack .push (c );
65+
66+ assertSame (c , stack .pop ());
67+ assertSame (b , stack .pop ());
68+ assertSame (a , stack .pop ());
69+ }
70+
71+ // ---- growth beyond initial capacity ----
72+
73+ @ Test
74+ public void pushBeyondInitialCapacityTriggersGrowth () {
75+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (2 );
76+
77+ for (int i = 0 ; i < 50 ; i ++) {
78+ stack .push (newInfo ("div" ));
79+ }
80+
81+ assertEquals (50 , stack .length );
82+
83+ // verify we can pop all 50
84+ for (int i = 0 ; i < 50 ; i ++) {
85+ assertNotNull (stack .pop ());
86+ }
87+ assertEquals (0 , stack .length );
88+ }
89+
90+ // ---- clear ----
91+
92+ @ Test
93+ public void clearResetsLengthAndNullsReferences () {
94+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
95+ stack .push (newInfo ("div" ));
96+ stack .push (newInfo ("span" ));
97+
98+ stack .clear ();
99+
100+ assertEquals (0 , stack .length );
101+ // verify references are nulled out to allow GC
102+ assertNull (stack .data [0 ]);
103+ assertNull (stack .data [1 ]);
104+ }
105+
106+ // ---- pop on empty (documents current behavior) ----
107+
108+ @ Test
109+ public void popOnEmptyThrowsArrayIndexOutOfBounds () {
110+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
111+ assertThrows (ArrayIndexOutOfBoundsException .class , stack ::pop );
112+ }
113+
114+ @ Test
115+ public void popNullsVacatedSlot () {
116+ // This test documents bug 1.1: after pop(), data[length] still
117+ // holds a reference. If the bug is fixed, change assertNotNull
118+ // to assertNull.
119+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
120+ final Info info = newInfo ("div" );
121+ stack .push (info );
122+
123+ stack .pop ();
124+
125+ assertNull (stack .data [0 ]);
126+ }
127+
128+ // ---- toString ----
129+
130+ @ Test
131+ public void toStringEmpty () {
132+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
133+ assertEquals ("InfoStackWithOpenCounters()" , stack .toString ());
134+ }
135+
136+ @ Test
137+ public void toStringNonEmpty () {
138+ final InfoStackWithOpenCounters stack = new InfoStackWithOpenCounters (4 );
139+ stack .push (newInfo ("div" ));
140+ final String s = stack .toString ();
141+ assertNotNull (s );
142+ // just verify it doesn't throw and contains the wrapper
143+ assertEquals ("InfoStackWithOpenCounters(" , s .substring (0 , 26 ));
144+ }
145+ }
0 commit comments