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 import java.lang.StackWalker.Option;
  25 import java.lang.StackWalker.StackFrame;
  26 import java.util.*;
  27 
  28 /**
  29  * Utility class for recording a stack trace for later comparison to
  30  * StackWalker results.
  31  *
  32  * StackTraceElement comparison does not include line number, isNativeMethod
  33  */
  34 public class StackRecorderUtil implements Iterable<StackRecorderUtil.TestFrame> {
  35     private List<TestFrame> testFrames = new LinkedList();
  36 
  37     private boolean compareClasses;
  38     private boolean compareClassNames = true;
  39     private boolean compareMethodNames = true;
  40     private boolean compareSTEs;
  41 
  42     public StackRecorderUtil(Set<StackWalker.Option> swOptions) {
  43         compareClasses = swOptions.contains(Option.RETAIN_CLASS_REFERENCE);
  44         compareSTEs = true;
  45     }
  46 
  47     /**
  48      * Add a method call to this recorded stack.
  49      */
  50     public void add(Class declaringClass, String methodName, String fileName) {
  51         testFrames.add(0, new TestFrame(declaringClass, methodName, fileName));
  52     }
  53 
  54     public int frameCount() { return testFrames.size(); }
  55 
  56     /**
  57      * Compare the given StackFrame returned from the StackWalker to the
  58      * recorded frame at the given index.
  59      *
  60      * Tests for equality, as well as functional correctness with respect to
  61      * the StackWalker's options (e.g. throws or doesn't throw exceptions)
  62      */
  63     public void compareFrame(int index, StackFrame sf) {
  64         TestFrame tf = testFrames.get(index);
  65         if (compareClasses) {
  66             if (!tf.declaringClass.equals(sf.getDeclaringClass())) {
  67                 throw new RuntimeException("Expected class: " +
  68                   tf.declaringClass.toString() + ", but got: " +
  69                   sf.getDeclaringClass().toString());
  70             }
  71         } else {
  72             boolean caught = false;
  73             try {
  74                 sf.getDeclaringClass();
  75             } catch (UnsupportedOperationException e) {
  76                 caught = true;
  77             }
  78             if (!caught) {
  79                 throw new RuntimeException("StackWalker did not have " +
  80                   "RETAIN_CLASS_REFERENCE Option, but did not throw " +
  81                   "UnsupportedOperationException");
  82             }
  83         }
  84 
  85         if (compareClassNames && !tf.className().equals(sf.getClassName())) {
  86             throw new RuntimeException("Expected class name: " + tf.className() +
  87                     ", but got: " + sf.getClassName());
  88         }
  89         if (compareMethodNames && !tf.methodName.equals(sf.getMethodName())) {
  90             throw new RuntimeException("Expected method name: " + tf.methodName +
  91                     ", but got: " + sf.getMethodName());
  92         }
  93         if (compareSTEs) {
  94             StackTraceElement ste = sf.toStackTraceElement();
  95             if (!(ste.getClassName().equals(tf.className()) &&
  96                   ste.getMethodName().equals(tf.methodName)) &&
  97                   ste.getFileName().equals(tf.fileName)) {
  98                 throw new RuntimeException("Expected StackTraceElement info: " +
  99                         tf + ", but got: " + ste);
 100             }
 101             if (!Objects.equals(ste.getClassName(), sf.getClassName())
 102                 || !Objects.equals(ste.getMethodName(), sf.getMethodName())
 103                 || !Objects.equals(ste.getFileName(), sf.getFileName().orElse(null))
 104                 || !Objects.equals(ste.getLineNumber(), sf.getLineNumber().orElse(-1))
 105                 || !Objects.equals(ste.isNativeMethod(), sf.isNativeMethod())) {
 106                 throw new RuntimeException("StackFrame and StackTraceElement differ: " +
 107                         "sf=" + sf + ", ste=" + ste);
 108             }
 109         }
 110     }
 111 
 112     public Iterator<TestFrame> iterator() {
 113         return testFrames.iterator();
 114     }
 115 
 116     /**
 117      * Class used to record stack frame information.
 118      */
 119     public static class TestFrame {
 120         public Class declaringClass;
 121         public String methodName;
 122         public String fileName = null;
 123 
 124         public TestFrame (Class declaringClass, String methodName, String fileName) {
 125             this.declaringClass = declaringClass;
 126             this.methodName = methodName;
 127             this.fileName = fileName;
 128         }
 129         public String className() {
 130             return declaringClass.getName();
 131         }
 132         public String toString() {
 133             return "TestFrame: " + className() + "." + methodName +
 134                     (fileName == null ? "" : "(" + fileName + ")");
 135         }
 136     }
 137 }