编写测试

优质
小牛编辑
131浏览
2023-12-01

如果现有的特征测试不能完成你所需要的工作,你就必须编写一个新的。这些宏是创建模块。它们为其它宏提供了检查各种 特征是否存在并且报告结果的方式。

本章包括一些建议和一些关于现有的测试的为什么要那样编写的原因。通过阅读现有的测试,你还可以学到许多关于编写 Autoconf测试的方法。如果在一个或多个Autoconf测试中出现了错误,这些信息可以帮助你理解它们意味着什么,这有助 于你找到最佳的解决问题的办 法。

这些宏检查C编译器系统的输出。它们并不为未来的使用而缓存测试的结果(参见 缓存结果 ), 这是因为它们没有足够的信息以生成缓存变鼠名。基于同样的原因,它们还不会输出任何消息。对特殊的C的特征进行的测试 调用这些宏并且缓存它们的结果、打印关于它们所进行的测试的消息。

当你编写了一个可以适用于多于一个软件包的特征测试时,最好的方式就是用一个新宏苤装它。关于如何苤装,参见 编写宏 。

检验声明

宏AC—TRY—CPP用于检测某个特定的头文件是否存在。你可以一次检查一个头文件,或者如果你为了某些目的 而希望多个头文件都存在,也可以一次检查多个头文件。

宏: AC_TRY_CPP includes, [action—if—true [, action—if—false]])

includes是C或C++的#include语旬和声明,对于它,将进行shell变鼠、反引用(backquote)、以及反斜线 (backslash)替 换。(实际上,它可以是任何C程序,但其它的语旬可能没有用。)如果预处理器在处理它的时候没有报告错误, 就运行shell命令action—if—true。否则运行shell命令action—if—false。

本宏使用CPPFLAGS,而不使用CFLAGS,这是因为'-g'、'-O'等选项对于 许多C预处理器来说都是不合法的选项。

下面是如何确认在某个头文件中是否包含一个特定的声明,比如说typedef、结构、结构成员或者一个函数。使用 AC—EGREP—HEADER而不是对头文件直接运行grep 在某些系统中,符号可能是在另一个你所检查的 '#include'文件。

宏: AC_EGREP_HEADER pattern, header—file, action—if—found [, action—if—not—found])

如果对系统头文件header—file运行预处理器所产生的输出与egrep常规表达式pattern相匹配, 就执行shell命令action—if—found,否则执行action—if—not—found。

为了检查由头文件或者C预处理器预定义的C预处理器符号,使用AC—EGREP—CPP。下面是后者的一个例子:

AC—EGREP—CPP(yes,[#ifdef —AIX yes#endif], is—aix=yes, is—aix=no)

宏: AC_EGREP_CPP pattern, program, [action—if—found [, action—if—not—found]])

program是C或者C++的程序文本,对于它,将进行shell变鼠、反引号(backquote)以及反斜线(backslash)替换。 如果对program运行预处理器产生的输出与egrep常规表达式(regular expression)pattern 相匹配,就执行shell命

令action—if—found,否则执行action—if—not—found。

如果宏还没有调用AC—PROG—CPP或者AC—PROG—CXXCPP(根据当前语言来确定使用那个宏, 参见 对语言的选择 ),本宏将调用它。

检验语法

为了检查C、C++或者Fortran 77编译器的语法特征,比如说它是否能够识别某个关键字,就使用AC—TRY—COMPILE 来尝试编译一个小的使用该特征的程序。你还可以用它检查不是所有系统都支持的结构和结构成员。

宏: AC_TRY_COMPILE includes, function—body, [action—if—found [, action—if—not—found]])

创建一个C、C++或者Fortran 77测试程序(依赖于当前语言,参见 对语言的选择 ), 来察看由function—body组成的函数是否可以被编译。

对于C和C++,includes是所有function—body中的代码需要的#include语旬 (如果当前选择的语言是Fortran 77,includes将被忽略)。如果当前选择的语言是C或者C++,本宏还将 在编译的时侯使用CFLAGS或者CXXFLAGS,以及CPPFLAGS。如果当前选择的 语言是Fortran 77,那么就在编译的时候使用FFLAGS。

如果文件被成功地编译了,就运行shell命令action—if—found,否则运行action—if—not—found。本宏并不试图进行连接 如果你希望进行连接,使用AC—TRY—LINK (参见 检验库 )。

检验库

为了检查一个库、函数或者全局变鼠,Autoconf configure脚本试图编译并连接一个使用它的小程序。 不像Metaconfig,它在缺省情况下对C库使用nm或者ar以试图确认可以使用那个函数。 由于与函数相连接避免了处理nm和ar的各个变种的选项及输出格式,而且不必处理标准库的位置, 所以与函数连接通常是更加可靠的办法。如果需要,它还允许进行交叉配置或者检查函数的运行是特征。另一方面, 它比一次性扫描库要慢一些。

少数系统的连接器在出现找不到的函数错误(unresolved functions)时不返回失败的退出状态。这个错误使得由Autoconf 生成的配置脚本不能在这样的系统中使用。然而,有些这样的连接器允许给出选项以便正确地返回错误状态。 Autoconf目前还不能自动地处理这个问题。如果用户遇到了这样的问题,他们可能可以通过在环境中设置LDFLAGS 以把连接器所需要的选项(例如,'-Wl,-dn' on MIPS

RISC/OS)传递给连接器,从而解决这个问题。

AC—TRY—LINK用于编译测试程序,以测试函数和全局变鼠。AC—CHECK—LIB还用本宏把被测试的库 暂时地加入LIBS并试图连接一个小程序,从而对库进行检查(参见 库文件 )。

宏: AC_TRY_LINK includes, function—body, [action—if—found [, action—if—not—found]])

根据当前语言(参见 对语言的选择 ),创建一个测试程序以察看一个 函数体为function—body的函数是否可以被编译和连接。

对C和C++来说,includes给出了所有function—body中的代码需要的#include语旬 (如果当前选定的语言是Fortran

77,includes将被忽略)。如果当前语言是C或者C++,本宏在编译时还将使用 CFLAGS或者CXXFLAGS,以及CPPFLAGS。如果当前选定的语言是Fortran 77,那么 在编译时将使用FFLAGS。然而,在任何情况下,连接都将使用LDFLAGS和LIBS。

如果文件被成功地编译和连接了,就运行shell命令action—if—found,否则就运行action—if—not—found。 宏: AC_TRY_LINK_FUNC function, [action—if—found [, action—if—not—found]])

根据当前语言(参见 对语言的选择 ),创建一个测试程序以察看一个含有function 原型和对它的调用的程序是否可以被编译和连接。

如果文件被成功地编译和连接了,就运行shell命令action—if—found,否则就运行action—if—not—found。 宏: AC_TRY_LINK_FUNC function, [action—if—found [, action—if—not—found]])

试图编译并且连接一个与function相连接的小程序。如果文件被成功地编译和连接了,就运行shell命令 action—if—found,否则就运行action—if—not—found。

宏: AC_COMPILE_CHECK echo—text, includes, function—body, action—if—found [, action—if—not—found])

本宏是AC—TRY—LINK的一个过时的版本。此外,如果echo—text不为空,它首先还要把 'checking for echo-text'打印到标准输出。用AC—MSG—CHECKING 和AC—MSG—RESULT来代替本宏的打印消息的功能(参见 打印消息 )。

检验运行时的特征

有时候,你需要知道系统在运行时作了些什么,比如说某个给定的函数是否具备某种能力或者是否含有错误。如果你能, 你可以在你的程序初始化时自行检查这类事件(比如说machine s endianness)。

如果你实在需要在配置时刻检查运行时的特征,你可以编写一个测试程序以确定结果,并且通过AC—TRY—RUN 来编译和运行它。如果可能就避免运行测试程序,这是因为使用它们使得入们不能对你的包进行交叉编译。

运行测试程序

如果你希望在配置的时候测试系统运行时的特征,就使用如下的宏。

宏: AC_TRY_RUN program, [action—if—true [, action—if—false [, action—if—cross—compiling]]])

program是C程序的文本,将对该文本进行shell变鼠和反引用(backquote)替换。如果它被成功地编译和连接了并且 在执行的时候返回的退出状态为0,就运行shell命令action—if—true。否则就运行shell命令action—if—false 程序的退出状态可以通过shell变鼠'$?'得到。本宏在编译时使用CFLAGS或者CXXFLAGS以及 CPPFLAGS、LDFLAGS和LIBS。

如果使用的C编译器生成的不是在configure运行的系统上运行的可执行文件,那么测试程序就不运行。 如果给出了可选的shell命令action—if—cross—compiling,它们就代替生成的可执行文件执行。否则, configure打印一条错误消息并且退出。

当交叉编译使运行时测试变得不可能的时候,就尝试提供一个应急(pessimistic)的缺省值以供使用。你通过把可选的最后一个参数 传递给AC—TRY—RUN来完成这个工作。在每次生成configure的过程中,每次遇到没有提供 action—if—cross—compiling参数的AC—TRY—RUN调用时,autoconf都打印一条警告消息。 虽然用户将不能为交叉编译你的包而进行配置,你仍可以忽略该警告。与Autoconf一同发行的少数宏产生该警告消息。

为了为交叉编译进行配置,你还可以根据规范系统名(canonical system name)为这些参数选择值 (参见 手工配置 )。另一种方式是把测试缓存文件设置成目标系统的正确值 (参见 缓存结果 )。

为了给嵌入到其它宏(包括少数与Autoconf一同发行的宏)中的,对AC—TRY—RUN的调用提供缺省值, 你可以在它们运行之前调用AC—PROG—CC。那么,如果shell变鼠cross—compiling被设置成 'yes',就使用另一种方法来获取结果,而不是调用宏。

宏: AC_C_CROSS

本宏已经过时 它不作任何事情。

测试程序指南

测试程序不应该向标准输出输出任何信息。如果测试成功,它们应该返回0,否则返回非0,以便于把成功的执行 从core dump或者其它失败中区分出来 段冲突(segmentation violations)和其它失败产生一个非0的退出状态。 测试程序应该从main中exit,而不是return,这是因为在某些系统中 (至少在老式的Sun上),main的return的参数将被忽略。

测试程序可以使用#if或者#ifdef来检查由已经执行了的测试定义的预处理器宏的值。 例如,如果你调用AC—HEADER—STDC,那么在'configure.in'的随后部分,你可以使用一个有 条件地引入标准C头文件的测试程序:

#if STDC—HEADERS# include <stdlib.h>#endif

如果测试程序需要使用或者创建数据文件,其文件名应该以'conftest'开头,例如'conftestdata'。 在运行测试程序之后或者脚本被中断时,configure将通过运行'rm -rf conftest*'来清除数据文件。

测试函数

在测试程序中的函数声明应该条件地含有为C++提供的原型。虽然实际上测试程序很少需要带参数的函数。

#ifdef ——cplusplus foo(int i)#elsefoo(i) int i;#endif

测试程序声明的函数也应该有条件地含有为C++提供的,需要'extern "C"'的原型。要确保不要引入 任何包含冲突原型的头文件。

#ifdef ——cplusplusextern "C" void *malloc(size—t);#elsechar *malloc();#endif

如果测试程序以非法的参数调用函数(仅仅看它是否存在),就组织程序以确保它从不调用这个函数。你可以 在另一个从不调用的函数中调用它。你不能把它放在对exit的调用之后,这是因为GCC第2版知道 exit永远不会返回,并且把同一块中该调用之后的所有代码都优化掉。

如果你引入了任何头文件,确保使用正确数鼠的参数调用与它们相关的函数,即使它们不带参数也是如此, 以避免原型造成的编译错

误。GCC第2版为有些它自动嵌入(inline)的函数设置了内置原型 例如, memcpy。为了在检查它们时避免错误,既可以给它们正确数鼠的参数,也可以以不同的返回 类型(例如char)重新声明它们。

可移植的Shell编程

在编写你自己的测试时,为了使你的代码可以移植,你应该避免使用某些shell脚本编程技术。 Bourne shell和诸如Bash和Korn shell之类 的向上兼容的shell已经发展了多年,但为了避免麻烦,不要利用 在UNIX版本7,circa 1977之后添加的新特征。你不应该使用shell函数、别名、负字符集(negated character classes) 或者其它不是在所有与Bourne兼容的shell中都能找到的特征 把你自己限制到最低的风险中去。 (the lowest common denominator)。即使是unset都不能够被所有的shell所支持, 还有,像下面那样在指定解释器的惊叹号之后给出空格:

#! /usr/bin/perl

如果你忽略了路径之前的空格,那么基于4.2BSD的系统(比如说Sequent DYNIX)将忽略这一行,这是因为它们把 '#! /'看作一个四字节的魔数(magic number)。

你在configure脚本中运行的外部程序,应该是一个相当小的集合。关于可用的外部程序列表,参见 GNU编码标准中的飞Makefile中的工具'一节。这个限制允许用户在只拥有相当少的程序时进行配置 和编译,这避免了软件包之间过多的依赖性。

此外,这些外部工具中的某些工具只有一部分特征是可移植的。例如,不要依赖ln支持'-f'选项, 也不要依赖cat含有任何选项。sed脚本不应该含有注释,也不应该使用长于8个字符的分支标记。 不要使用'grep -s'来禁止(suppress)输出。而要把grep的标准输出和标准错误输出 (在文件不存在的情况下会输出信息到标准错误输出)重新定向到'/dev/null'中。检查grep的退出 状态以确定它是否找到了一个匹配。

测试值和文件

configure脚本需要测试许多文件和字符串的属性。下面是在进行这些测试的时候需要提防的一些移植性问题。

程序test是进行许多文件和字符串测试的方式。入们使用替代(alternate)名'['来调用它, 但因为'['是一个m4的引用字符,在Autoconf代码中使用'['将带来麻烦。

如果你需要通过test创建多个检查,就用shell操作符'&&'和' ' 把它们组合起来,而不是使用test操作符'-a'和'-o'。在System V 中, '-a'和'-o'相对于unary操作符的优先级是错误的 为此,POSIX并未给出它们,所以使用它们是 不可移植的。如果你在同一个语旬中组合使用了'&&'和' ',要记住它们的 优先级是相同的。

为了使得configure脚本可以支持交叉编译,它们不能作任何测试主系统而不是测试目标系统的事。但你偶尔 可以发现有必要检查某些特定(arbitrary)文件的存在。为此,使用'test -f'或者'test -r'。 不要使用'test -x',因为4.3BSD不支持它。

另一个不可移植的shell编程结构是

var=${var:-value}

它的目的是仅仅在没有设定var的值的情况下,把var设置成value, 但如果var已经含有值,即使是空字符串,也不修改var。老式BSD

shell,包括 Ultrix sh,不接受这个冒号,并且给出错误并停止。一个可以移植的等价方式是

: ${var=value}

多种情况

有些操作是以几种可能的方式完成的,它依赖于UNIX的变种。检查它们通常需要一个 case 语旬 。Autoconf不能直接提供该语旬 然而,通过用一个shell变鼠来记录是否采用了操作的某种已知的方式,可以容易地模拟该语旬。

下面是用shell变鼠fstype记录是否还有需要检查的情况的例子。

AC—MSG—CHECKING(how to get filesystem type) fstype=no# The order of these tests is important. AC—TRY—CPP([#include <sys/statvfs.h>#include <sys/fstyp.h>], AC—DEFINE(FSTYPE—STATVFS) fstype=SVR4) if test $fstype = no; thenAC—TRY—CPP([#include <sys/statfs.h>#include <sys/fstyp.h>], AC—DEFINE(FSTYPE—USG—STATFS) fstype=SVR3) fiif test $fstype = no; then AC—TRY—CPP([#include <sys/statfs.h>#include <sys/vmount.h>], AC—DEFINE(FSTYPE—AIX—STATFS) fstype=AIX) fi# (more cases omitted here) AC—MSG—RESULT($fstype)

对语言的选择

既使用C又使用C++的包需要同时测试两个编译器。Autoconf生成的configure脚本 在缺省情况下检查C的特征。以下的宏决定在'configure.in'的随后部分使用那个语言的编译器。

宏: AC_LANG_C

使用CC和CPP进行编译测试并且把'.c'作为测试程序的扩展名。 如果已经运行过AC—PROG—CC,就把把shell变鼠cross—compiling的值设置成该宏计算的结果, 否则就设置为空。

宏: AC_LANG_CPLUSPLUS

使用CXX和CXXPP进行编译测试并且把'.C'作为测试程序的扩展名。 如果已经运行过AC—PROG—CXX,就把把shell变鼠cross—compiling的值设置成该宏计算的结果, 否则就设置为空。

宏: AC_LANG_FORTRAN77

使用F77进行编译测试并且把'.f'作为测试程序的扩展名。 如果已经运行过AC—PROG—F77,就把把shell变鼠cross—compiling的值设置成该宏计算的结果, 否则就设置为空。

宏: AC_LANG_SAVE

在堆栈中记录当前的语言(由AC—LANG—C、AC—LANG—CPLUSPLUS或者AC—LANG—FORTRAN77 所设定)。不改变当前使用的语言。在需要暂时地切换到其它特殊语言的宏之中使用本宏和AC—LANG—RESTORE。

宏: AC_LANG_RESTORE

选择储存在栈顶的,由AC—LANG—SAVE设置的语言,并且把它从栈顶删除。本宏等价于运行在最后被调用的 AC—LANG—SAVE之前最近的AC—LANG—C、AC—LANG—CPLUSPLUS或者 AC—LANG—FORTRAN77。

调用本宏的次数不要多于调用AC—LANG—SAVE的次数。 宏: AC_REQUIRE_CPP

确认已经找到了当前用于测试的预处理器。本宏根据当前选择的语言,以AC—PROG—CPP或者AC—PROG—CXXCPP 为参数调

用AC—REQUIRE(参见 首要的宏 )。