【问题标题】:Why are the call stacks different when a test crashes at the exact same statement?当测试在完全相同的语句处崩溃时,为什么调用堆栈不同?
【发布时间】:2012-08-21 07:10:19
【问题描述】:

对于一个研究项目,我试图弄清楚单元测试(用 JUnit 编写)在不同上下文中执行时是否表现不同。为此,我做了两件事:首先,我使用自定义 JUnit Runner 运行程序的整个测试套件,然后使用相同的自定义 JUnit runner 运行单个测试(为什么这有意义并不重要,只需接受它现在)。当测试失败时,我会记录 JUnit 报告的整个异常堆栈跟踪,然后比较两次运行之间的堆栈跟踪。

这样做时,我偶然发现了一些我无法解释的奇怪事情。以下是以这种方式记录的两个堆栈跟踪的两个摘录。

运行整个测试套件时记录的堆栈跟踪:

org.fest.swing.edt.GuiActionRunner.resultOf(GuiActionRunner.java:126)
org.fest.swing.edt.GuiActionRunner.execute(GuiActionRunner.java:73)
net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.AbstractNumericDataTypeUITest.constructTestFrameInEDT(AbstractNumericDataTypeUITest.java:98)
net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.AbstractNumericDataTypeUITest.setUp(AbstractNumericDataTypeUITest.java:81)
sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) - sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:616)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

运行单个测试时记录的堆栈跟踪:

org.fest.swing.edt.GuiActionRunner.resultOf(GuiActionRunner.java:126)
org.fest.swing.edt.GuiActionRunner.execute(GuiActionRunner.java:73)
net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.AbstractNumericDataTypeUITest.constructTestFrameInEDT(AbstractNumericDataTypeUITest.java:98)
net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.AbstractNumericDataTypeUITest.setUp(AbstractNumericDataTypeUITest.java:81)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:616)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

两个堆栈跟踪都显示崩溃前的最后几个条目,从最后一个通用语句开始。显然,在 JDK/VM 的反射实现中的某处,这两种情况的行为有所不同。

我的问题是为什么?

我推测这与早期反射调用中的 VM 缓存内容有关,但我真的不知道。了解这一点很重要,因为我必须弄清楚这是否会发生每次我运行一个测试(因此我可以忽略它),或者这是否与我正在运行的特定测试。

我知道这很模糊,但我们将不胜感激。

【问题讨论】:

  • “我推测这与早期反射调用中的 VM 缓存内容有关”:不太可能 - 可能是由于在测试框架。推论:这应该是从一次运行到另一次运行的一致行为。
  • 运行之间肯定是一致的。如果我有一种简单的方法来检查在没有抛出异常的情况下是否发生同样的事情,我会很高兴。但是使用仪器来解决这个问题可能有点矫枉过正。
  • 你有没有检查堆栈跟踪中每个项目对应的源代码以尝试遵循执行路径?
  • 不,因为差异在于 JDK,而我没有运行它的 JDK 的源代码。
  • 谷歌:sun.reflect.DelegatingMethodAccessorImpl .java => grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…

标签: java reflection junit


【解决方案1】:

这是因为 Sun/Oracle JVM(其他我不知道)优化了反射调用。从 1.4 开始,JVM 生成字节码以将反射调用桥接到被调用的方法。因为不是所有的事情在编译时都是已知的,所以必须在运行时完成。

MethodAccessorGenerator 类生成 GeneratedMethodAccessor1

我从 NativeMethodAccessorImpl 的 cmets 中找到的一个提示:

Used only for the first few invocations of a Method; afterward, switches to bytecode-based implementation

您是否在一个 Java VM 的生命周期中运行套件和单个测试?

【讨论】:

  • @otakun85 不,我在不同的虚拟机中运行它们,所以这可以解释为什么在一种情况下我得到了对 NativeAccessor 的调用而在另一种情况下我没有。
  • @Jochen 我仍然想知道它什么时候决定创建一个字节码桥?但至少对我来说,这可能是太多的调查^^
  • 我有一个硬编码常量。它只是计算反射调用方法的频率,如果达到阈值(在我使用的版本中,阈值似乎是 15),它会生成字节码包装器。
  • @Jochen 你可能会觉得this post 很有趣。
  • @assylias 谢谢,有些答案很有启发性。
猜你喜欢
  • 2020-04-19
  • 2023-01-01
  • 2021-04-28
  • 2018-10-27
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多