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 
  31 import java.lang.StackWalker.StackFrame;
  32 import java.util.List;
  33 import java.util.Objects;
  34 import java.util.stream.Collectors;
  35 import java.util.stream.Stream;
  36 import static java.lang.StackWalker.Option.*;
  37 
  38 import org.testng.annotations.DataProvider;
  39 import org.testng.annotations.Test;
  40 import static org.testng.Assert.*;
  41 
  42 public 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 }