查看原文
其他

Unity编译器测试框架

Unity Unity官方平台 2018-11-15

如我们所知,Unity能够针对主机、电脑、移动端或XR等不同平台进行构建。为了做到这一点,Unity会使用到编译器、特定编译器标识、各种SDK、第三方库以及平台所要的特定操作系统等。我们将这些软件组合在一起称为:工具链 (Toolchain)

 

因此随着主机、VR设备、移动电话或智能电视系统的推出,淘汰,更新,我们需要处理随之产生的工具链变化,相应地修改Unity代码库中的一些代码,这样才能让一切功能正常运行。

 

需要解决什么问题

与所有的软件一样,总会有难以预料的问题存在。从Bug到未公开的功能,一切皆有可能。编译器和SDK并不能完全处理这些问题,它们也会受到软件更新的影响。在更新后,所使用的未公开的功能可能会突然被修改,一些Bug会被修复,但是可能产生新的Bug。针对不同的平台可能需要支持截然不同的功能。有序跟踪这些变化是一种不错的处理方式。

 

多年来我们为工具链处理过很多问题。例如:特定编译器上出现的Bug,或是某些工具链缺少的一些编译器功能。即使是如今较为过时的C++11,我们仍不能将它完全应用于我们的整个代码库,因为有些平台并不支持它。

 

基于这些不同的工具链在不同的时间点所遇到的问题,我们需要相应采取不同策略。这意味着有可能会发生这样的情况,针对某个问题的修复,会比问题本身更加长远地存在于代码中,向后人警示着一个早已不是问题的问题。所以我们需要时刻掌握在工具链中增加、更新或弃用的功能,以及由于更新所带来的一些修复、问题或是支持状态。

 

另一个问题是处理底层代码库的开发者所遇到的往返时延问题。例如:如果开发人员想了解他们制作的新宏是否能在所有的工具链中正常工作,他们不得不在构建系统中针对每个平台进行一次编辑器构建,以确认是否能够成功编译。这十分浪费时间,而且还会浪费build farm上的资源,所以这是一个我们想要改进的现状。

  

什么是编译器测试框架


编译器测试框架是是一系列C#NUnit测试它们被编写为小型CC ++代码片段可以针对我们所有的工具链进行编译。这些代码段兼容于我们所有的工具链。每次测试都包含一个代码段,它会基于我们期望的行为做出断言。即使这个行为只是个Bug,无法进行编译,它也是一个我们所期望的特定行为,仍会为其编写一个相应的测试。因此如果某个Bug在某个编译器或是SDK更新后被修复了,我们会收到通知,这意味着我们最后能将该编译器功能应用于所有支持平台上。

 

现在这个框架只编译测试代码,因为它不会运行所生成的二进制码,也不会以任何方式分析它。所以测试中不会有任何运行时断言,只会有静态断言。

 

该编译器测试使用C#编写的构建系统来完成主要的工作,让它为工具链进行构建,并在测试中将该代码段作为源文件使用。这样才能让我们对编译器输出进行断言,输出内容包括构建错误和警告。这样我们就能显示类似这样的信息:“该代码能在我们的所有工具链上使用,除了在这二个工具链上会显示这些错误信息”。然后只要断言结果一直为真,就将该测试标记为成功。


或许你会认为构建失败却仍能通过测试,这让人难以理解。但编译器测试的重点在于写出我们对于工具链的一些假设是否成立


如果我们知道某个平台不支持某个特定功能或是例外情况,就会增加一个相应的测试说明这个情况。这样就能轻易知道我们代码中的哪些功能会得到工具链的支持。它也会让我们知道,哪些功能是否会在我们更新工具链后受到影响。这也将有助于底层开发人员在所有平台上对更改进行编译的往返时延进行测试,并且任何编译时断言都是有效的。


构建系统了解我们的代码库,所以开发人员可以添加已实现内容的头文件,并在测试中做必要的额外断言。这样还可以使开发人员对不应编译到普通构建版本中的东西进行显式的测试。

  

实践


下面的示例代码,它属于我们的测试之一:

namespace Unity.CompilerTests.Tests

{

    [CompilerFeatureName("Macro behavior consistency")]

    [CompilerFeatureDescription("Does various macro checks to ensure they behave the same on all toolchains")]

    public class MacroTests : CompilerTestBase

    {

        [CompilerTest]

        public void MacroExpansions()

        {

            var code = PrepareCode(

                    "const int _Q = 1;",

 

                    "#define PP_CAT(a, b) a##b",

                    "#define PP_CAT2(a, b) PP_CAT(a,b)",

                    "#define PP_CAT3(a, b) PP_CAT2(a,b)",

 

                    "#define E(a) QQ a",

                    "#define QQ() Q",

 

                    "#define T2() PP_CAT2(_,E(()))",

                    "#define T3() PP_CAT3(_,E(()))",

 

                    "int function()",

                    "{",

                    "    if (T2() == T3())",

                    "        return 18;",

                    "    return 42;",

                    "}");

 

            var result = Compile(code);

 

            result.ShouldSucceed(outcomeException: () =>

                {

                    // VisualStudioToolChain is the only toolchain that does not support this scenario,

                    // but we do expect it so as long as we get it we fail it.

                    // If this suddenly starts working in the future (by this test failing)

                    // we need to check over our macros and see if any specific workarounds can be removed.

                    if (toolChain is VisualStudioToolChain)

                        return "error C3861: \'_QQ\': identifier not found";

                    return null;

                });

        }

    }

 

以上这段代码是我们使用MSVC编译器发现的一个未曾预料到的行为。如果我们编译代码段中的代码,它会将宏在我们所有的工具链上扩展开,除了Visual Studio Toolchain。

 

你可以发现,在这段代码的注释中,我们基于这个行为做出了一些假设。所以如果在以后的Visual Studio版本中,由于成功编译而导致测试失败的话,我们就可以去掉宏里的这个特殊设定,不必继续维护这个行为。

 

这对其它工具链也适用。就我们可以使用的C++语言特性而言,不同的工具链之间仍有很大的差别。有些功能我们从不会使用,因为某个工具链不支持这个功能,而这其中有些功能我们已经写了对应的模拟代码。这些测试让我们时刻掌握其状态信息,或许某一天我们能完全支持C++11,弃用我们被迫为一些平台所写的模拟代码。

 

所以对于这个框架,我们创建了一些特性来提供帮助。例如:CompilerTestAttribute用于指出当前操作系统支持哪些工具链,确保每次使用不同的工具链来进行多次测试。这和我们继承的CompilerTestBase类有关联,该类为当前需要的工具链进行设置,确保其Compile()函数只为正确的工具链编译,然后报告结果。


我们还加入了一些自定义文档特性,用于生成测试报告。你可以在后面了解这方面的更多信息。

 

下图我们可以看见在Windows系统机器上运行测试的情况:

 

 

它在Visual Studio 2010和 Visual Studio2015 (包括x86和x64架构)以及Emscripten工具链上运行。由于这是Windows系统的机器,它只会运行当前系统所支持的工具链。如果我们在Mac上运行它,它则会显示Mac Editor Toolchain的测试结果。

 

我们在Katana上的构建运行器被设定为在Mac、Windows和Linux上运行测试,因此它支持所有工具链。如果在测试中涉及到的工具链中的某些内容发生变化,则构建将会失败并通知我们。


这也会让开发人员能够轻松确保他们所编写的测试能在我们支持的工具链中运行。目前测试套件需要大概8分钟来在所有支持的工具链上完成运行(1分钟检查代码,6分钟准备和构建构建系统,最后1分钟运行所有构建系统测试)。

 

我们还开发了一个小工具,它将生成有关当前编译器测试的状态信息,我们将使用该工具在内部的网站上生成报告。

 

编译器测试报告网站

 


在上图中可以看到所有测试信息。这个报告中只有一个失败情况,那是我们刻意处理的,它就是前面提到的Visual Studio工具链上的宏扩展(Macro Expansions)测试。

 

如果开发人员不确定当前情况,只要访问报告页面即可了解所需信息。我们前面提到的自定义文档特性便是出于这个目的。如下图所示,我们可以将鼠标悬停在各个部分上,并获取有关各个条目的信息。

 

报告中的每一行都是一个测试类的结果,其中可能包含多个测试。每个类都代表一个高级特性。

 

如果想要仔细看看实际测试的内容,也可以通过右边的快捷链接来浏览代码库中的测试类。

 

结语

我们目前还没加入太多测试和工具链。这是因为框架高度依赖于新的基于C#的构建系统,而Unity的整个构建系统还没有完全转移过去。


所以现在编译器测试框架仍处于早期阶段。我们希望它在工具链被移植到C#的过程中,能够慢慢成熟,直到我们覆盖所有已检测到的异常,以及所有正在使用或是想要使用的编译器功能。


未来我们会发布介绍这个框架更新的文章在Unity中文官方论坛(Unitychina.cn),敬请关注!


推荐阅读

官方活动

Unity GDC 2018 活动日程

时间:美国时间3月19-23日

网址:https://unity3d.com/cn/gdc-2018

 

Unite Beijing 2018 及 Training Day

活动信息:5月11-13日 北京国家会议中心

售票官网: http://unite2018.csdn.net/  或者直接扫描下图二维码进行购票!


点击“阅读原文”访问Unity官方技术社区!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存