什么是 Jacks?
Jacks 测试套件检查 Java 编译器是否符合 JLS(Java 语言规范)。它由大量小测试案例组成,每个测试都侧重于 JLS 中特定的部分。Eric Blake 为 Jacks 项目作出了很大贡献,他从面向细节领域描述了这种类型测试的好处。“通过生成带有指定编译行为的小测试案例,然后将每个案例的执行自动化,编译器作者或调试者可以快速找出 Java 源码到字节码转换中存在的问题。”
开发 Jacks 背后的概念是要简化对多编译器或多编译器配置所运行的测试(例如,对上两个发行版的 Jikes 和 Javac 的 JDK 1.3 发行版所进行的一组测试)。如果手工进行,您必须重复地设置环境变量,然后根据所期望的结果来检查测试结果。而通过使用 Jacks,只需要更改到存放测试的目录, 调用 Jacks 框架,然后表明应该使用哪个编译器配置。
Sun 没有履行对 Java 开发者所做的承诺激发了 Jikes 小组对 Jacks 项目的设置和运行。Sun 再三声明它会把 Java、JCK(Java Compatibility Kit)和相关 Java 技术交到一个标准主体的手中。但因为这还没有实现,从事 Java 项目的开发者就不能使用 JCK 来对日常的开发进行回归测试。当面对由于不合理的许可证限制而导致的代码人为不足时,他们倾向于用新的更完善的系统来替换旧系统。这就是发生在 Jacks 上的故事。(尽管 Jacks 由 developerWorks 主持,它受 GPL 而非 IBM Public License 约束。)
使用 Jacks
Jacks 是以 Tcl 编写的,因此需要确保拥有 Tcl 8.3。(需要版本 8.3 来确保具有 tcltest 扩展和 Unicode 支持,这两者都是 Jacks 所必需的)。可以下载用于 Windows 的安装程序和用于 Red Hat x86 的 RPM,也可以更方便地从源代码中构建。如果您不知道到什么地方下载,请参阅本文稍后的 参考资料 部分;如果使用的是 Red Hat 7,很可能已安装了 Tcl 8.3。
安装了 Tcl 后,需要从 CVS 取出 Jacks。然后通过将编译器路径名包括在要测试的编译器的 Jacks _setup 配置文件中来配置 Jacks。对于每个希望支持的配置都需要一个 _setup 文件。例如,Jacks 带有 javac_setup 文件。需要编辑该文件来为 javac 设置路径。Eric Blake 说,“最困难的部分是断定如何测试 Jikes,因为我在环境中已设置了 JIKESPATH。但我想出了要在 jikes_setup 配置文件中更改什么内容,一切都很顺利。”
从 CVS 模块中取出 Jacks 源代码
可以对数量不限的编译器或编译器配置使用 Jacks。要除去某一编译器的配置,只需要删除其 _setup 文件。setenv CVSROOT :pserver:anoncvs@oss.software.ibm.com:/usr/cvs/jikes cvs login paswsd anoncvs cvs checkout jacks
从 CVS 中取出源代码后,就需要在路径中包括顶层 Jacks 目录,这样才能运行 Jacks shell 脚本。为谨慎起见,最初运行 shell 脚本时应该不带任何自变量,以确保每项都经过正确配置。
如果一切正常,将看到 Jacks 脚本所接受的命令行选项的清单。如果收到错误,请检查在路径中是否能找到可执行文件 tclsh8.3。Windows 用户需要直接运行 tclsh83,并将 jacks.tcl 自变量在一般标志之前传递给它。还应该考虑安装 Cygwin UNIX 兼容性层,这样,象 Unix 用户一样,您就可以使用提供的 shell 脚本来运行 Jacks 了。下面的指令假设您使用的是 shell 脚本。% jacks
对于测试示例,需要使用 Jikes 编译器来运行给定子目录中的所有测试,命令如下:
开发新的回归测试% cd tests/jls/packages/package-declarations/unnamed-packages % jacks jikes
开发新的 Jacks 测试案例非常简便。照 Eric Blakes 的话说,“基本上,您设计一个简单的源文件来测试问题,将它放在特定的 Jacks 格式中,然后运行 Jacks。如果编译器结果与所期望的结果不一样,它打印出错误。”这里是 Jacks 主页上教程中有关添加新测试案例的一例:
使用 Jikes 编译时,生成以下错误:// File SynchronizedInterface.java public synchronized interface SynchronizedInterface {}
如果很快看一下 JLS 的第 9.1.1 节,会发现 synchronized 在该上下文中不是合法的修饰符。如果尝试使用早期发行版 JDK 中的 Javac 编译器来编译相同的类,则不会生成错误(该错误在稍后的发行版中得到修正)。% jikes SynchronizedInterface.java Found 1 semantic error compiling "SynchronizedInterface.java": 3. public synchronized interface SynchronizedInterface {} <----------> *** Error: synchronized is not a valid interface modifier.
% javac SynchronizedInterface.java
现在既然问题得以重现,可以通过以下步骤来对 Jacks 测试套件添加回归测试案例:
- 了解应该将测试案例放在哪个目录中
- 编写回归测试
- 在 Jacks 框架中运行新测试
tcltest 框架中回归测试的格式是:
这是 JLS 第 9.1.1 节中的第一个测试,所以 NAME 是 9.1.1-1。tcltest::test NAME DESCRIPTION { COMMANDS } EXPECTED_RESULT
该测试案例在目录 tests/jls/interfaces/interface-declarations/interface-modifiers (位置基于 JLS 节的名称)中。
DESCRIPTION 可以是任何想要的内容。
COMMANDS 一节包含了所有 Tcl 命令,但大多数情况只需要 Jacks 中的 saveas 和 compile 方法。
saveas 命令使用两个自变量:文件名和将保存到文件中的数据。
compile 命令使用任意数量的命令行自变量,并将它们传递给 Java 编译器。它将返回 PASS、FAIL 或 WARN 来表明编译器的退出状态。saveas SynchronizedInterface.java \ {public synchronized interface SynchronizedInterface {}}
EXPECTED_RESULT 是希望从 compile 命令获得的结果。
在该接口示例中,编译应该不成功。因此完整的回归测试应该类似于:
检验结果tcltest::test 9.1.1-1 {should generate error on synchronized interface} { saveas SynchronizedInterface.java \ {synchronized interface SynchronizedInterface {}} compile SynchronizedInterface.java } FAIL
运行测试并检查结果是完全自动的,因此可以真正地休息一下,看看出现的结果。Jacks 框架在测试目录中递归下降,运行它所找到的所有测试。
如果一切正常,就不打印任何消息。如果测试失败,将打印有关失败的描述,如 Mo Dejong 在 清单 1 中显示的那样。该例演示了 Javac 中因为第一个构造器调用第二个构造器,第二个又调用第一个所造成的错误。JLS 规定这是非法的(第 8.8.5.1 节),因此如果检测到这种情况,Java 编译器必须用信号通知该错误。
让我们看看 Jikes 对于同一测试案例是如何做的。在 清单 2 中,我们将使用 Jacks 中的一些特性,可以让您将模式作为 Jacks 脚本的第三个自变量传递。将跳过那些名称与模式不匹配的测试案例。在这个小案例中,模式就是测试案例的名称。在该例中,请注意我们所感兴趣的那个测试案例是如何通过的,其它测试案例是如何跳过的。上面的输出表明在 Javac 编译器中找到的错误在 Jikes 中并不存在。
尽管人类可读的结果非常有用,但在您有许多要处理的测试案例的情况下,它们很快就会变得非常难于管理。Jacks 最近庆祝了一个重要的里程碑:现在它包含了逾 1,000 个 JLS 独立测试案例。有了这么多的测试案例,没人能够记住在某一时刻哪些案例通过了,哪些又失败了。但不用害怕,Jacks 包括了一系列记录和测试结果分析特性,能够随时间跟踪测试结果。这是一项关键特性,因为它为 Java 编译器开发者提供了一种跟踪错误修正状态和可能回归的方法。
如何编写 Jacks,以及为什么使用 Tcl
当实现例如 Jacks 这样的测试套件时,脚本语言是个很自然的选择,而使用 Tcl 也有以下几个原因:
Tcl 是开放源码,因此在今后的一段时间内仍然会继续存在。
易于安装,不需要编译脚本。
易于读写,脚本语言远比 C/C++ 更易于掌握。
易于使用字符串处理和常规规则表达式特性。
高度可移植,在比 Java 多的平台上运行。
过去十年中成功地在几千个组织中使用过
具有讽刺意味的是,它曾是 Sun 项目 :)
Mo DeJong 说,“Jacks 最了不起的一个特性是自生成文档。” 在 Jacks 主页上,您可以找到到达测试案例索引页面的链接,这些页面列出了所有可用的测试案例。它以几种有用的方式进行索引和交叉引用。可以方便地通过名称查找测试案例,也可以通过现有测试来发现某个 JLS 章节的内容是多么完善。Tcl 高度动态的语言特性使自记录测试案例的实现更容易。
到目前为止,Jacks 支持以下几种 Java 编译器:
JDK 1.3(1.1 和 1.2 也可以使用,但已经过时了)
Jikes,IBM 的开放源码 Java 编译器
Kaffe,利用了 Kopi 编译器
GCJ,到 gcc 的 Java 前端
随处改进 Java 编译器
Jacks 最初着重只为 Jikes 项目提供编译器测试。原来的目标是要替换为 Jikes 创建的自制测试系统,但这个初衷由于太难建立和使用而被放弃了。人们很快发现,如果测试套件变得更常规一些,就可以为其它 Java 编译器项目使用。这样将会导致已提交测试案例在数量上的增加。至少,让其它 Java 专家评估一下正确性测试案例也并无大碍。
Jikes 项目自然大大利用鉴了 Jacks,但 GCJ 和 Kopi 编译器项目又如何呢?Tom Tromey,Red Hat 的常任 Java 领导者,已经意识到了 Jacks 开发对于 GCJ 项目的作用。“Jacks 对于 GCJ 项目已经有了实际意义。每当我在进行前端编译器更改时就会运行 Jacks,并定期使用 Jacks 查找 GCJ 中的错误。我发现添加测试是桩小事。框架非常易于使用,考虑也很周到。”
在第一次运行了 Jacks 后,Kopi 编译器的项目经理 Thomas Graf 也成了一个拥戴者。“第一次运行产生了 169 个失败的测试。在基于对某些失败测试的分析而应用一些修正后,失败的测试数为 147 个。这些结果非常振奋人心(对于 Jacks:)。结论是:Jacks 测试套件对于提高编译器的质量来说确实是一种非常有价值的工具!”