1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8140450 8173898
27 * @summary Basic test for the StackWalker::walk method
28 * @run testng Basic
29 */
30
31import java.lang.StackWalker.StackFrame;
32import java.util.List;
33import java.util.Objects;
34import java.util.stream.Collectors;
35import java.util.stream.Stream;
36import static java.lang.StackWalker.Option.*;
37
38import org.testng.annotations.DataProvider;
39import org.testng.annotations.Test;
40import static org.testng.Assert.*;
41
42public class Basic {
43    private static boolean verbose = false;
44
45    @DataProvider(name = "stackDepths")
46    public static Object[][] stackDepths() {
47        return new Object[][] {
48                { new int[] { 12 },  new int[] { 4, 8, 12}      },
49                { new int[] { 18 },  new int[] { 8, 16, 20}     },
50                { new int[] { 32 },  new int[] { 16, 32, 64}    },
51        };
52    }
53
54    /**
55     * For a stack of a given depth, it creates a StackWalker with an estimate.
56     * Test walking different number of frames
57     */
58    @Test(dataProvider = "stackDepths")
59    public static void test(int[] depth, int[] estimates) {
60        Basic test = new Basic(depth[0]);
61        for (int estimate : estimates) {
62            test.walk(estimate);
63        }
64    }
65
66    @Test
67    public static void testWalkFromConstructor() throws Exception {
68        System.out.println("testWalkFromConstructor:");
69        List<String> found = ((ConstructorNewInstance)ConstructorNewInstance.class.getMethod("create")
70                             .invoke(null)).collectedFrames();
71        assertEquals(List.of(ConstructorNewInstance.class.getName()+"::<init>",
72                             ConstructorNewInstance.class.getName()+"::create",
73                             Basic.class.getName()+"::testWalkFromConstructor"),
74                     found);
75    }
76
77    private final int depth;
78    Basic(int depth) {
79        this.depth = depth;
80    }
81
82    /*
83     * Setup a stack builder with the expected stack depth
84     * Walk the stack and count the frames.
85     */
86    void walk(int estimate) {
87        int limit = Math.min(depth, 16);
88        List<StackFrame> frames = new StackBuilder(depth, limit).build();
89        System.out.format("depth=%d estimate=%d expected=%d walked=%d%n",
90                          depth, estimate, limit, frames.size());
91        assertEquals(limit, frames.size());
92    }
93
94    static class ConstructorNewInstance {
95        static final StackWalker walker =
96            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
97        List<String> testFramesOrReflectionFrames;
98        public ConstructorNewInstance() {
99            testFramesOrReflectionFrames = walker.walk(this::parse);
100        }
101        public List<String> collectedFrames() {
102            return testFramesOrReflectionFrames;
103        }
104        public boolean accept(StackFrame f) {
105            // Frames whose class names don't contain "."
106            // are our own test frames. These are the ones
107            // we expect.
108            // Frames whose class names contain ".reflect."
109            // are reflection frames. None should be present,
110            // since they are supposed to be filtered by
111            // by StackWalker. If we find any, we want to fail.
112            if (!f.getClassName().contains(".")
113                || f.getClassName().contains(".reflect.")) {
114                System.out.println("    " + f);
115                return true;
116            }
117            // Filter out all other frames (in particular
118            // those from the test framework) in order to
119            // have predictable results.
120            return false;
121        }
122        public String frame(StackFrame f) {
123            return f.getClassName() + "::" + f.getMethodName();
124        }
125        List<String> parse(Stream<StackFrame> s) {
126            return s.filter(this::accept)
127                    .map(this::frame)
128                    .collect(Collectors.toList());
129        }
130        public static ConstructorNewInstance create() throws Exception {
131            return ConstructorNewInstance.class.getConstructor().newInstance();
132        }
133    }
134
135    class StackBuilder {
136        private final int stackDepth;
137        private final int limit;
138        private int depth = 0;
139        private List<StackFrame> result;
140        StackBuilder(int stackDepth, int limit) {
141            this.stackDepth = stackDepth; // build method;
142            this.limit = limit;
143        }
144        List<StackFrame> build() {
145            trace("build");
146            m1();
147            return result;
148        }
149        void m1() {
150            trace("m1");
151            m2();
152        }
153        void m2() {
154            trace("m2");
155            m3();
156        }
157        void m3() {
158            trace("m3");
159            m4();
160        }
161        void m4() {
162            trace("m4");
163            int remaining = stackDepth-depth-1;
164            if (remaining >= 4) {
165                m1();
166            } else {
167                filler(remaining);
168            }
169        }
170        void filler(int i) {
171            trace("filler");
172            if (i == 0)
173                walk();
174            else
175                filler(--i);
176        }
177
178        void walk() {
179            StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
180            result = walker.walk(s -> s.limit(limit).collect(Collectors.toList()));
181        }
182        void trace(String methodname) {
183            ++depth;
184            if (verbose)
185                System.out.format("%2d: %s%n", depth, methodname);
186        }
187    }
188
189}
190