diff --git a/contrib/atf/.cirrus.yml b/contrib/atf/.cirrus.yml new file mode 100644 index 000000000000..fd9b6e4a47df --- /dev/null +++ b/contrib/atf/.cirrus.yml @@ -0,0 +1,26 @@ +env: + CIRRUS_CLONE_DEPTH: 1 + ARCH: amd64 + +task: + matrix: + - name: 13.0-CURRENT + freebsd_instance: + image_family: freebsd-13-0-snap + - name: 12.2-STABLE + freebsd_instance: + image_family: freebsd-12-2-snap + - name: 12.1-RELEASE + freebsd_instance: + image_family: freebsd-12-1 + install_script: + - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf + - ASSUME_ALWAYS_YES=yes pkg bootstrap -f + - pkg install -y autoconf automake libtool kyua + script: + - env JUNIT_OUTPUT=$(pwd)/test-results.xml ./admin/travis-build.sh + always: + junit_artifacts: + path: "test-results.xml" + type: text/xml + format: junit diff --git a/contrib/atf/.gitignore b/contrib/atf/.gitignore new file mode 100644 index 000000000000..396785ce2052 --- /dev/null +++ b/contrib/atf/.gitignore @@ -0,0 +1,25 @@ +*.la +*.lo +*.o +*.pc +*_helper +*_helpers +*_test +.deps +.dirstamp +.libs + +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +installcheck.log +libtool +stamp-h1 +testsuite.log diff --git a/contrib/atf/.travis.yml b/contrib/atf/.travis.yml new file mode 100644 index 000000000000..1949aae54468 --- /dev/null +++ b/contrib/atf/.travis.yml @@ -0,0 +1,25 @@ +language: cpp + +compiler: + - gcc + - clang + +before_install: + - ./admin/travis-install-deps.sh + +env: + - ARCH=amd64 AS_ROOT=no + - ARCH=amd64 AS_ROOT=yes + - ARCH=i386 AS_ROOT=no + +matrix: + exclude: + - compiler: clang + env: ARCH=i386 AS_ROOT=no + +script: + - ./admin/travis-build.sh + +notifications: + email: + - atf-log@googlegroups.com diff --git a/contrib/atf/NEWS b/contrib/atf/NEWS index f1764e0d9dda..671ee81ff6ff 100644 --- a/contrib/atf/NEWS +++ b/contrib/atf/NEWS @@ -1,726 +1,744 @@ Major changes between releases Automated Testing Framework =========================================================================== +Changes in version 0.22 +*********************** + +STILL UNDER DEVELOPMENT; NOT RELEASED YET. +DON'T FORGET TO BUMP THE -version-info PRE-RELEASE IF NECESSARY! + +* Issue #23: Fix double-free triggered by atf_map_insert in low memory + scenarios, caused by an overlook in the atf_list code. + +* Issue #29: Fixed various typos and formatting errors in manual pages. + +* Issue #31: Added require.progs metadata properties to the tests that + need a compiler to run. + +* Added the atf_check_not_equal function to atf-sh to check for + unequal values. + + Changes in version 0.21 *********************** Released on October 23rd, 2014. * Restored the atf(7) manual page to serve as a reference to all the other manual pages shipped by ATF. * Added the -s flag to atf-sh to support specifying the shell interpreter to be used. * Removed ATF_WORKDIR. The only remaining consumers have been converted to use the standard TMPDIR environment variable. As a benefit, and because Kyua forces the TMPDIR to live within the test case's work directory, any stale files left behind by ATF will be automatically cleaned up. * Documented the environment variables recognized by each component in the relevant manual pages. This information was lost with the atf-config(1) removal. * Added a new "require.diskspace" metadata property to test cases so that they can specify the minimum amount of disk space required for the test to run. * Renamed the atf-{c,c++,sh}-api(3) manual pages to atf-{c,c++,sh}(3) for discoverability purposes. Symbolic links are provided for the time being to still make the old names visible. * Issue #5: Recommend the (expected, actual) idiom for calls to the test macros in the manual pages. * Issue #7: Stopped catching unhandled exceptions in atf-c++ tests. This propagates the crash to the caller, which in turn allows it to obtain proper debugging information. In particular, Kyua should now be able to extract a stacktrace pinpointing the problem. * Issue #8: Fixed atf-c/macros_test:use test failures spotted by the clang that ships with FreeBSD 11.0-CURRENT. * Issue #12: Improved documentation of atf-sh(3) and atf-check(1) by better explaining how they relate to each other. * Issue #14: Stopped setting 'set -e' in atf-sh. This setting was initially added as a way to enable a "strict" mode in the library and to make test cases fail fast when they run unprotected commands. However, doing so in the library is surprising as the responsibility of enabling 'set -e' should be on the user's code. Also, 'set -e' introduces inconsistent behavior on subshells and users do not expect that. * Issue #15: Fixed atf_utils_{fork,wait} to support nested calls. * Issue #16: Fixed test failures (by removing a long-standing hack) on systems that lack \e support in printf(1). * Issue #19: Removed stale references to atf-config and atf-run. Changes in version 0.20 *********************** Experimental version released on February 7th, 2014. This is the first release without the code for the deprecated tools. If you require such code, please fetch a copy of the 0.19 release and extract the 'tools' directory for your own consumption. * Removed the deprecated tools. This includes atf-config, atf-report, atf-run and atf-version. * Issue #8: Fixed atf-c/macros_test:use test failures spotted by the clang that ships with FreeBSD 11.0-CURRENT. Changes in version 0.19 *********************** Experimental version released on February 7th, 2014. This is the last release to bundle the code for the deprecated tools. The next release will drop their code and will stop worrying about backwards compatibility between the ATF libraries and what the old tools may or may not support. If you still require the old tools for some reason, grab a copy of the 'tools' directory now. The code in this directory is standalone and does not depend on any internal details of atf-c++ any longer. * Various fixes and improvements to support running as part of the FreeBSD test suite. * Project hosting moved from Google Code (as a subproject of Kyua) to GitHub (as a first-class project). The main reason for the change is the suppression of binary downloads in Google Code on Jan 15th, 2014. See https://github.com/jmmv/atf/ * Removed builtin help from atf-sh(1) and atf-check(1) for simplicity reasons. In other words, their -h option is gone. * Moved the code of the deprecated tools into a 'tools' directory and completely decoupled their code from the internals of atf-c++. The reason for this is to painlessly allow a third-party to maintain a copy of these tools after we delete them because upcoming changes to atf-c++ would break the stale tools. Changes in version 0.18 *********************** Experimental version released on November 16th, 2013. * Issue 45: Added require.memory support in atf-run for FreeBSD. * Fixed an issue with the handling of cin with libc++. * Issue 64: Fixed various mandoc formatting warnings. * NetBSD PR bin/48284: Made atf-check flush its progress message to stdout so that an interrupted test case always shows the last message being executed. * NetBSD PR bin/48285: Fixed atf_check examples in atf-sh-api(3). Changes in version 0.17 *********************** Experimental version released on February 14th, 2013. * Added the atf_utils_cat_file, atf_utils_compare_file, atf_utils_copy_file, atf_utils_create_file, atf_utils_file_exists, atf_utils_fork, atf_utils_grep_file, atf_utils_grep_string, atf_utils_readline, atf_utils_redirect and atf_utils_wait utility functions to atf-c-api. Documented the already-public atf_utils_free_charpp function. * Added the cat_file, compare_file, copy_file, create_file, file_exists, fork, grep_collection, grep_file, grep_string, redirect and wait functions to the atf::utils namespace of atf-c++-api. These are wrappers around the same functions added to the atf-c-api library. * Added the ATF_CHECK_MATCH, ATF_CHECK_MATCH_MSG, ATF_REQUIRE_MATCH and ATF_REQUIRE_MATCH_MSG macros to atf-c to simplify the validation of a string against a regular expression. * Miscellaneous fixes for manpage typos and compilation problems with clang. * Added caching of the results of those configure tests that rely on executing a test program. This should help crossbuild systems by providing a mechanism to pre-specify what the results should be. * PR bin/45690: Make atf-report convert any non-printable characters to a plain-text representation (matching their corresponding hexadecimal entities) in XML output files. This is to prevent the output of test cases from breaking xsltproc later. Changes in version 0.16 *********************** Experimental version released on July 10th, 2012. * Added a --enable-tools flag to configure to request the build of the deprecated ATF tools, whose build is now disabled by default. In order to continue running tests, you should migrate to Kyua instead of enabling the build of the deprecated tools. The kyua-atf-compat package provides transitional compatibility versions of atf-run and atf-report built on top of Kyua. * Tweaked the ATF_TEST_CASE macro of atf-c++ so that the compiler can detect defined but unused test cases. * PR bin/45859: Fixed some XSLT bugs that resulted in the tc-time and tp-time XML tags leaking into the generated HTML file. Also improved the CSS file slightly to correct alignment and color issues with the timestamps column. * Optimized atf-c++/macros.hpp so that GNU G++ consumes less memory during compilation with GNU G++. * Flipped the default to building shared libraries for atf-c and atf-c++, and started versioning them. As a side-effect, this removes the --enable-unstable-shared flag from configure that appears to not work any more (under NetBSD). Additionally, some distributions require the use of shared libraries for proper dependency tracking (e.g. Fedora), so it is better if we do the right versioning upstream. * Project hosting moved from an adhoc solution (custom web site and Monotone repository) to Google Code (standard wiki and Git). ATF now lives in a subcomponent of the Kyua project. Changes in version 0.15 *********************** Experimental version released on January 16th, 2012. * Respect stdin in atf-check. The previous release silenced stdin for any processes spawned by atf, not only test programs, which caused breakage in tests that pipe data through atf-check. * Performance improvements to atf-sh. * Enabled detection of unused parameters and variables in the code and fixed all warnings. * Changed the behavior of "developer mode". Compiler warnings are now enabled unconditionally regardless of whether we are in developer mode or not; developer mode is now only used to perform strict warning checks and to enable assertions. Additionally, developer mode is now only automatically enabled when building from the repository, not for formal releases. * Added new Autoconf M4 macros (ATF_ARG_WITH, ATF_CHECK_C and ATF_CHECK_CXX) to provide a consistent way of defining a --with-arg flag in configure scripts and detecting the presence of any of the ATF bindings. Note that ATF_CHECK_SH was already introduced in 0.14, but it has now been modified to also honor --with-atf if instantiated. * Added timing support to atf-run / atf-report. * Added support for a 'require.memory' property, to specify the minimum amount of physical memory needed by the test case to yield valid results. * PR bin/45690: Force an ISO-8859-1 encoding in the XML files generated by atf-report so that invalid data in the output of test cases does not mangle our report. Changes in version 0.14 *********************** Experimental version released on June 14th, 2011. * Added a pkg-config file for atf-sh and an aclocal file to ease the detection of atf-sh from autoconf scripts. * Made the default test case body defined by atf_sh fail. This is to ensure that test cases are properly defined in test programs and helps in catching typos in the names of the body functions. * PR bin/44882: Made atf-run connect the stdin of test cases to /dev/zero. This provides more consistent results with "normal" execution (in particular, when tests are executed detached from a terminal). * Made atf-run hardcode TZ=UTC for test cases. It used to undefine TZ, but that does not take into account that libc determines the current timezone from a configuration file. * All test programs will now print a warning when they are not run through atf-run(1) stating that this is unsupported and may deliver incorrect results. * Added support for the 'require.files' test-case property. This allows test cases to specify installed files that must be present for the test case to run. Changes in version 0.13 *********************** Experimental version released on March 31st, 2011. This is the first release after the creation of the Kyua project, a more modular and reliable replacement for ATF. From now on, ATF will change to accomodate the transition to this new codebase, but ATF will still continue to see development in the short/medium term. Check out the project page at http://code.google.com/p/kyua/ for more details. The changes in this release are: * Added support to run the tests with the Kyua runtime engine (kyua-cli), a new package that aims to replace atf-run and atf-report. The ATF tests can be run with the new system by issuing a 'make installcheck-kyua' from the top-level directory of the project (assuming the 'kyua' binary is available during the configuration stage of ATF). * atf-run and atf-report are now in maintenance mode (but *not* deprecated yet!). Kyua already implements a new, much more reliable runtime engine that provides similar features to these tools. That said, it is not complete yet so all development efforts should go towards it. * If GDB is installed, atf-run dumps the stack trace of crashing test programs in an attempt to aid debugging. Contributed by Antti Kantee. * Reverted default timeout change in previous release and reset its value to 5 minutes. This was causing several issues, specially when running the existing NetBSD test suite in qemu. * Fixed the 'match' output checker in atf-check to properly validate the last line of a file even if it does not have a newline. * Added the ATF_REQUIRE_IN and ATF_REQUIRE_NOT_IN macros to atf-c++ to check for the presence (or lack thereof) of an element in a collection. * PR bin/44176: Fixed a race condition in atf-run that would crash atf-run when the cleanup of a test case triggered asynchronous modifications to its work directory (e.g. killing a daemon process that cleans up a pid file in the work directory). * PR bin/44301: Fixed the sample XSLT file to report bogus test programs instead of just listing them as having 0 test cases. Changes in version 0.12 *********************** Experimental version released on November 7th, 2010. * Added the ATF_REQUIRE_THROW_RE to atf-c++, which is the same as ATF_REQUIRE_THROW but allows checking for the validity of the exception's error message by means of a regular expression. * Added the ATF_REQUIRE_MATCH to atf-c++, which allows checking for a regular expression match in a string. * Changed the default timeout for test cases from 5 minutes to 30 seconds. 30 seconds is long enough for virtually all tests to complete, and 5 minutes is a way too long pause in a test suite where a single test case stalls. * Deprecated the use.fs property. While this seemed like a good idea in the first place to impose more control on what test cases can do, it turns out to be bad. First, use.fs=false prevents bogus test cases from dumping core so after-the-fact debugging is harder. Second, supporting use.fs adds a lot of unnecessary complexity. atf-run will now ignore any value provided to use.fs and will allow test cases to freely access the file system if they wish to. * Added the atf_tc_get_config_var_as_{bool,long}{,_wd} functions to the atf-c library. The 'text' module became private in 0.11 but was being used externally to simplify the parsing of configuration variables. * Made atf-run recognize the 'unprivileged-user' configuration variable and automatically drop root privileges when a test case sets require.user=unprivileged. Note that this is, by no means, done for security purposes; this is just for user convenience; tests should, in general, not be blindly run as root in the first place. Changes in version 0.11 *********************** Experimental version released on October 20th, 2010. * The ATF_CHECK* macros in atf-c++ were renamed to ATF_REQUIRE* to match their counterparts in atf-c. * Clearly separated the modules in atf-c that are supposed to be public from those that are implementation details. The header files for the internal modules are not installed any more. * Made the atf-check tool private. It is only required by atf-sh and being public has the danger of causing confusion. Also, making it private simplifies the public API of atf. * Changed atf-sh to enable per-command error checking (set -e) by default. This catches many cases in which a test case is broken but it is not reported as such because execution continues. * Fixed the XSTL and CSS stylesheets to support expected failures. Changes in version 0.10 *********************** Experimental version released on July 2nd, 2010. Miscellaneous features * Added expected failures support to test cases and atf-run. These include, for example, expected clean exits, expected reception of fatal signals, expected timeouts and expected errors in condition checks. These statuses can be used to denote test cases that are known to fail due to a bug in the code they are testing. atf-report reports these tests separately but they do not count towards the failed test cases amount. * Added the ATF_CHECK_ERRNO and ATF_REQUIRE_ERRNO to the C library to allow easy checking of call failures that update errno. * Added the has.cleanup meta-data property to test caes that specifies whether the test case has a cleanup routine or not; its value is automatically set. This property is read by atf-run to know if it has to run the cleanup routine; skipping this run for every test case significantly speeds up the run time of test suites. * Reversed the order of the ATF_CHECK_THROW macro in the C++ binding to take the expected exception as the first argument and the statement to execute as the second argument. Changes in atf-check * Changed atf-check to support negating the status and output checks by prefixing them with not- and added support to specify multiple checkers for stdout and stderr, not only one. * Added the match output checker to atf-check to look for regular expressions in the stdout and stderr of commands. * Modified the exit checks in atf-check to support checking for the reception of signals. Code simplifications and cleanups * Removed usage messages from test programs to simplify the implementation of every binding by a significant amount. They just now refer the user to the appropriate manual page and do not attempt to wrap lines on terminal boundaries. Test programs are not supposed to be run by users directly so this minor interface regression is not important. * Removed the atf-format internal utility, which is unused after the change documented above. * Removed the atf-cleanup internal utility. It has been unused since the test case isolation was moved to atf-run in 0.8 * Splitted the Makefile.am into smaller files for easier maintenance and dropped the use of M4. Only affects users building from the repository sources. * Intermixed tests with the source files in the source tree to provide them more visibility and easier access. The tests directory is gone from the source tree and tests are now suffixed by _test, not prefixed by t_. * Simplifications to the atf-c library: removed the io, tcr and ui modules as they had become unnecessary after all simplifications introduced since the 0.8 release. * Removed the application/X-atf-tcr format introduced in 0.8 release. Tests now print a much simplified format that is easy to parse and nicer to read by end users. As a side effect, the default for test cases is now to print their results to stdout unless otherwise stated by providing the -r flag. * Removed XML distribution documents and replaced them with plain-text documents. They provided little value and introduced a lot of complexity to the build system. * Simplified the output of atf-version by not attempting to print a revision number when building form a distfile. Makes the build system easier to maintain. Changes in version 0.9 ********************** Experimental version released on June 3rd, 2010. * Added atf-sh, an interpreter to process test programs written using the shell API. This is not really a shell interpreter by itself though: it is just a wrapper around the system shell that eases the loading of the necessary ATF libraries. * Removed atf-compile in favour of atf-sh. * Added the use.fs metadata property to test case, which is used to specify which test cases require file system access. This is to highlight dependencies on external resources more clearly and to speed up the execution of test suites by skipping the creation of many unnecessary work directories. * Fixed test programs to get a sane default value for their source directory. This means that it should not be necessary any more to pass -s when running test programs that do not live in the current directory. * Defining test case headers became optional. This is trivial to achieve in shell-based tests but a bit ugly in C and C++. In C, use the new ATF_TC_WITHOUT_HEAD macro to define the test case, and in C++ use ATF_TEST_CASE_WITHOUT_HEAD. Changes in version 0.8 ********************** Experimental version released on May 7th, 2010. * Test programs no longer run several test cases in a row. The execution of a test program now requires a test case name, and that single test case is executed. To execute several test cases, use the atf-run utility as usual. * Test programs no longer fork a subprocess to isolate the execution of test cases. They run the test case code in-process, and a crash of the test case will result in a crash of the test program. This is to ease debugging of faulty test cases. * Test programs no longer isolate their test cases. This means that they will not create temporary directories nor sanitize the environment any more. Yes: running a test case that depends on system state by hand will most likely yield different results depending on where (machine, directory, user environment, etc.) it is run. Isolation has been moved to atf-run. * Test programs no longer print a cryptic format (application/X-atf-tcs) on a special file channel. They can now print whatever they want on the screen. Because test programs can now only run one test case every time, providing controlled output is not necessary any more. * Test programs no longer write their status into a special file descriptor. Instead, they create a file with the results, which is later parsed by atf-run. This changes the semantics of the -r flag. * atf-run has been adjusted to perform the test case isolation. As a result, there is now a single canonical place that implements the isolation of test caes. In previous releases, the three language bindings (C, C++ and shell) had to be kept in sync with each other (read: not a nice thing to do at all). As a side effect of this change, writing bindings for other languages will be much, much easier from now on. * atf-run forks test programs on a test case basis, instead of on a test program basis as it did before. This is to provide the test case isolation that was before implemented by the test programs themselves. * Removed the atf-exec tool. This was used to implement test case isolation in atf-sh, but it is now unnecessary. * It is now optional to define the descr meta-data property. It has been proven to be mostly useless, because test cases often carry a descriptive name of their own. Changes in version 0.7 ********************** Experimental version released on December 22nd, 2009. * Added build-time checks to atf-c and atf-c++. A binding for atf-sh will come later. * Migrated all build-time checks for header files to proper ATF tests. This demonstrates the use of the new feature described above. * Added an internal API for child process management. * Converted all plain-text distribution documents to a Docbook canonical version, and include pre-generated plain text and HTML copies in the distribution file. * Simplified the contents of the Makefile.am by regenerating it from a canonical Makefile.am.m4 source. As a side-effect, some dependency specifications were fixed. * Migrated all checks from the check target to installcheck, as these require ATF to be installed. * Fixed sign comparison mismatches triggered by the now-enabled -Wsign-compare. * Fixed many memory and object leaks. Changes in version 0.6 ********************** Experimental version released on January 18th, 2009. * Make atf-exec be able to kill its child process after a certain period of time; this is controlled through the new -t option. * Change atf-sh to use atf-exec's -t option to control the test case's timeouts, instead of doing it internally. Same behavior as before, but noticeably faster. * atf-exec's -g option and atf-killpg are gone due to the previous change. * Added the atf-check(1) tool, a program that executes a given command and checks its exit code against a known value and allows the management of stdout and stderr in multiple ways. This replaces the previous atf_check function in the atf-sh library and exposes this functionality to both atf-c and atf-c++. * Added the ATF_REQUIRE family of macros to the C interface. These help in checking for fatal test conditions. The old ATF_CHECK macros now perform non-fatal checks only. I.e. by using ATF_CHECK, the test case can now continue its execution and the failures will not be reported until the end of the whole run. * Extended the amount of ATF_CHECK_* C macros with new ones to provide more features to the developer. These also have their corresponding counterparts in the ATF_REQUIRE_* family. The new macros (listing the suffixes only) are: _EQ (replaces _EQUAL), _EQ_MSG, _STREQ and _STREQ_MSG. Changes in version 0.5 ********************** Experimental version released on May 1st, 2008. * Clauses 3 and 4 of the BSD license used by the project were dropped. All the code is now under a 2-clause BSD license compatible with the GNU General Public License (GPL). * Added a C-only binding so that binary test programs do not need to be tied to C++ at all. This binding is now known as the atf-c library. * Renamed the C++ binding to atf-c++ for consistency with the new atf-c. * Renamed the POSIX shell binding to atf-sh for consistency with the new atf-c and atf-c++. * Added a -w flag to test programs through which it is possible to specify the work directory to be used. This was possible in prior releases by defining the workdir configuration variable (-v workdir=...), but was a conceptually incorrect mechanism. * Test programs now preserve the execution order of test cases when they are given in the command line. Even those mentioned more than once are executed multiple times to comply with the user's requests. Changes in version 0.4 ********************** Experimental version released on February 4th, 2008. * Added two new manual pages, atf-c++-api and atf-sh-api, describing the C++ and POSIX shell interfaces used to write test programs. * Added a pkg-config file, useful to get the flags to build against the C++ library or to easily detect the presence of ATF. * Added a way for test cases to require a specific architecture and/or machine type through the new 'require.arch' and 'require.machine' meta-data properties, respectively. * Added the 'timeout' property to test cases, useful to set an upper-bound limit for the test's run time and thus prevent global test program stalls due to the test case's misbehavior. * Added the atf-exec(1) internal utility, used to execute a command after changing the process group it belongs to. * Added the atf-killpg(1) internal utility, used to kill process groups. * Multiple portability fixes. Of special interest, full support for SunOS (Solaris Express Developer Edition 2007/09) using the Sun Studio 12 C++ compiler. * Fixed a serious bug that prevented atf-run(1) from working at all under Fedora 8 x86_64. Due to the nature of the bug, other platforms were likely affected too. Changes in version 0.3 ********************** Experimental version released on November 11th, 2007. * Added XML output support to atf-report. This is accompanied by a DTD for the format's structure and sample XSLT/CSS files to post-process this output and convert it to a plain HTML report. * Changed atf-run to add system information to the report it generates. This is currently used by atf-report's XML output only, and is later printed in the HTML reports in a nice and useful summary table. The user and system administrator are allowed to tune this feature by means of hooks. * Removed the test cases' 'isolated' property. This was intended to avoid touching the file system at all when running the related test case, but this has not been true for a long while: some control files are unconditionally required for several purposes, and we cannot easily get rid of them. This way we remove several critical and delicate pieces of code. * Improved atf-report's CSV output format to include information about test programs too. * Fixed the tests that used atf-compile to not require this tool as a helper. Avoids systems without build-time utilities to skip many tests that could otherwise be run. (E.g. NetBSD without the comp.tgz set installed.) * Many general cleanups: Fixed many pieces of code marked as ugly and/or incomplete. Changes in version 0.2 ********************** Experimental version released on September 20th, 2007. * Test cases now get a known umask on entry. * atf-run now detects many unexpected failures caused by test programs and reports them as bogus tests. atf-report is able to handle these new errors and nicely reports them to the user. * All the data formats read and written by the tools have been documented and cleaned up. These include those grammars that define how the different components communicate with each other as well as the format of files written by the developers and users: the Atffiles and the configuration files. * Added the atf-version tool, a utility that displays information about the currently installed version of ATF. * Test cases can now define an optional cleanup routine to undo their actions regardless of their exit status. * atf-report now summarizes the list of failed (bogus) test programs when using the ticker output format. * Test programs now capture some termination signals and clean up any temporary files before exiting the program. * Multiple bug fixes and improvements all around. Changes in version 0.1 ********************** Experimental version released on August 20th, 2007. * First public version. This was released coinciding with the end of the Google Summer of Code 2007 program. =========================================================================== vim: filetype=text:textwidth=75:expandtab:shiftwidth=2:softtabstop=2 diff --git a/contrib/atf/README.md b/contrib/atf/README.md new file mode 100644 index 000000000000..d245552f35c9 --- /dev/null +++ b/contrib/atf/README.md @@ -0,0 +1,47 @@ +# Welcome to the ATF project! + +ATF, or Automated Testing Framework, is a **collection of libraries** to +write test programs in **C, C++ and POSIX shell**. + +The ATF libraries offer a simple API. The API is orthogonal through the +various bindings, allowing developers to quickly learn how to write test +programs in different languages. + +ATF-based test programs offer a **consistent end-user command-line +interface** to allow both humans and automation to run the tests. + +ATF-based test programs **rely on an execution engine** to be run and +this execution engine is *not* shipped with ATF. +**[Kyua](https://github.com/jmmv/kyua/) is the engine of choice.** + +## Download + +Formal releases for source files are available for download from GitHub: + +* [atf 0.20](../../releases/tag/atf-0.20), released on February 7th, 2014. + +## Installation + +You are encouraged to install binary packages for your operating system +wherever available: + +* Fedora 20 and above: install the `atf` package with `yum install atf`. + +* FreeBSD 10.0 and above: install the `atf` package with `pkg install atf`. + +* NetBSD with pkgsrc: install the `pkgsrc/devel/atf` package. + +* OpenBSD: install the `devel/atf` package with `pkg_add atf`. + +Should you want to build and install ATF from the source tree provided +here, follow the instructions in the [INSTALL file](INSTALL). + +## Support + +Please use the +[atf-discuss mailing list](https://groups.google.com/forum/#!forum/atf-discuss) +for any support inquiries related to `atf-c`, `atf-c++` or `atf-sh`. + +If you have any questions on Kyua proper, please use the +[kyua-discuss mailing list](https://groups.google.com/forum/#!forum/kyua-discuss) +instead. diff --git a/contrib/atf/atf-c++/atf-c++.3 b/contrib/atf/atf-c++/atf-c++.3 index 601efaf6fd5b..fead776755af 100644 --- a/contrib/atf/atf-c++/atf-c++.3 +++ b/contrib/atf/atf-c++/atf-c++.3 @@ -1,649 +1,649 @@ .\" Copyright (c) 2008 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND .\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, .\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE .\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .Dd March 6, 2017 .Dt ATF-C++ 3 .Os .Sh NAME .Nm atf-c++ , .Nm ATF_ADD_TEST_CASE , .Nm ATF_CHECK_ERRNO , .Nm ATF_FAIL , .Nm ATF_INIT_TEST_CASES , .Nm ATF_PASS , .Nm ATF_REQUIRE , .Nm ATF_REQUIRE_EQ , .Nm ATF_REQUIRE_ERRNO , .Nm ATF_REQUIRE_IN , .Nm ATF_REQUIRE_MATCH , .Nm ATF_REQUIRE_NOT_IN , .Nm ATF_REQUIRE_THROW , .Nm ATF_REQUIRE_THROW_RE , .Nm ATF_SKIP , .Nm ATF_TEST_CASE , .Nm ATF_TEST_CASE_BODY , .Nm ATF_TEST_CASE_CLEANUP , .Nm ATF_TEST_CASE_HEAD , .Nm ATF_TEST_CASE_NAME , .Nm ATF_TEST_CASE_USE , .Nm ATF_TEST_CASE_WITH_CLEANUP , .Nm ATF_TEST_CASE_WITHOUT_HEAD , .Nm atf::utils::cat_file , .Nm atf::utils::compare_file , .Nm atf::utils::copy_file , .Nm atf::utils::create_file , .Nm atf::utils::file_exists , .Nm atf::utils::fork , .Nm atf::utils::grep_collection , .Nm atf::utils::grep_file , .Nm atf::utils::grep_string , .Nm atf::utils::redirect , .Nm atf::utils::wait .Nd C++ API to write ATF-based test programs .Sh SYNOPSIS .In atf-c++.hpp .Fn ATF_ADD_TEST_CASE "tcs" "name" .Fn ATF_CHECK_ERRNO "expected_errno" "bool_expression" .Fn ATF_FAIL "reason" .Fn ATF_INIT_TEST_CASES "tcs" .Fn ATF_PASS .Fn ATF_REQUIRE "expression" .Fn ATF_REQUIRE_EQ "expected_expression" "actual_expression" .Fn ATF_REQUIRE_ERRNO "expected_errno" "bool_expression" .Fn ATF_REQUIRE_IN "element" "collection" .Fn ATF_REQUIRE_MATCH "regexp" "string_expression" .Fn ATF_REQUIRE_NOT_IN "element" "collection" .Fn ATF_REQUIRE_THROW "expected_exception" "statement" .Fn ATF_REQUIRE_THROW_RE "expected_exception" "regexp" "statement" .Fn ATF_SKIP "reason" .Fn ATF_TEST_CASE "name" .Fn ATF_TEST_CASE_BODY "name" .Fn ATF_TEST_CASE_CLEANUP "name" .Fn ATF_TEST_CASE_HEAD "name" .Fn ATF_TEST_CASE_NAME "name" .Fn ATF_TEST_CASE_USE "name" .Fn ATF_TEST_CASE_WITH_CLEANUP "name" .Fn ATF_TEST_CASE_WITHOUT_HEAD "name" .Ft void .Fo atf::utils::cat_file .Fa "const std::string& path" .Fa "const std::string& prefix" .Fc .Ft bool .Fo atf::utils::compare_file .Fa "const std::string& path" .Fa "const std::string& contents" .Fc .Ft void .Fo atf::utils::copy_file .Fa "const std::string& source" .Fa "const std::string& destination" .Fc .Ft void .Fo atf::utils::create_file .Fa "const std::string& path" .Fa "const std::string& contents" .Fc .Ft void .Fo atf::utils::file_exists .Fa "const std::string& path" .Fc .Ft pid_t .Fo atf::utils::fork .Fa "void" .Fc .Ft bool .Fo atf::utils::grep_collection .Fa "const std::string& regexp" .Fa "const Collection& collection" .Fc .Ft bool .Fo atf::utils::grep_file .Fa "const std::string& regexp" .Fa "const std::string& path" .Fc .Ft bool .Fo atf::utils::grep_string .Fa "const std::string& regexp" .Fa "const std::string& path" .Fc .Ft void .Fo atf::utils::redirect .Fa "const int fd" .Fa "const std::string& path" .Fc .Ft void .Fo atf::utils::wait .Fa "const pid_t pid" .Fa "const int expected_exit_status" .Fa "const std::string& expected_stdout" .Fa "const std::string& expected_stderr" .Fc .Sh DESCRIPTION ATF provides a C++ programming interface to implement test programs. C++-based test programs follow this template: .Bd -literal -offset indent extern "C" { \&... C-specific includes go here ... } \&... C++-specific includes go here ... #include ATF_TEST_CASE(tc1); ATF_TEST_CASE_HEAD(tc1) { ... first test case's header ... } ATF_TEST_CASE_BODY(tc1) { ... first test case's body ... } ATF_TEST_CASE_WITH_CLEANUP(tc2); ATF_TEST_CASE_HEAD(tc2) { ... second test case's header ... } ATF_TEST_CASE_BODY(tc2) { ... second test case's body ... } ATF_TEST_CASE_CLEANUP(tc2) { ... second test case's cleanup ... } ATF_TEST_CASE(tc3); ATF_TEST_CASE_BODY(tc3) { ... third test case's body ... } \&... additional test cases ... ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, tc1); ATF_ADD_TEST_CASE(tcs, tc2); ATF_ADD_TEST_CASE(tcs, tc3); ... add additional test cases ... } .Ed .Ss Definition of test cases Test cases have an identifier and are composed of three different parts: the header, the body and an optional cleanup routine, all of which are described in .Xr atf-test-case 4 . To define test cases, one can use the .Fn ATF_TEST_CASE , .Fn ATF_TEST_CASE_WITH_CLEANUP or the .Fn ATF_TEST_CASE_WITHOUT_HEAD macros, which take a single parameter specifying the test case's name. .Fn ATF_TEST_CASE , requires to define a head and a body for the test case, .Fn ATF_TEST_CASE_WITH_CLEANUP requires to define a head, a body and a cleanup for the test case and .Fn ATF_TEST_CASE_WITHOUT_HEAD requires only a body for the test case. It is important to note that these .Em do not set the test case up for execution when the program is run. In order to do so, a later registration is needed through the .Fn ATF_ADD_TEST_CASE macro detailed in .Sx Program initialization . .Pp Later on, one must define the three parts of the body by means of three functions. Their headers are given by the .Fn ATF_TEST_CASE_HEAD , .Fn ATF_TEST_CASE_BODY and .Fn ATF_TEST_CASE_CLEANUP macros, all of which take the test case's name. Following each of these, a block of code is expected, surrounded by the opening and closing brackets. .Pp Additionally, the .Fn ATF_TEST_CASE_NAME macro can be used to obtain the name of the class corresponding to a particular test case, as the name is internally managed by the library to prevent clashes with other user identifiers. Similarly, the .Fn ATF_TEST_CASE_USE macro can be executed on a particular test case to mark it as "used" and thus prevent compiler warnings regarding unused symbols. Note that .Em you should never have to use these macros during regular operation. .Ss Program initialization The library provides a way to easily define the test program's .Fn main function. You should never define one on your own, but rely on the library to do it for you. This is done by using the .Fn ATF_INIT_TEST_CASES macro, which is passed the name of the list that will hold the test cases. This name can be whatever you want as long as it is a valid variable value. .Pp After the macro, you are supposed to provide the body of a function, which should only use the .Fn ATF_ADD_TEST_CASE macro to register the test cases the test program will execute. The first parameter of this macro matches the name you provided in the former call. .Ss Header definitions The test case's header can define the meta-data by using the .Fn set_md_var method, which takes two parameters: the first one specifies the meta-data variable to be set and the second one specifies its value. Both of them are strings. .Ss Configuration variables The test case has read-only access to the current configuration variables by means of the .Ft bool .Fn has_config_var and the .Ft std::string .Fn get_config_var methods, which can be called in any of the three parts of a test case. .Ss Access to the source directory It is possible to get the path to the test case's source directory from any of its three components by querying the .Sq srcdir configuration variable. .Ss Requiring programs Aside from the .Va require.progs meta-data variable available in the header only, one can also check for additional programs in the test case's body by using the .Fn require_prog function, which takes the base name or full path of a single binary. Relative paths are forbidden. If it is not found, the test case will be automatically skipped. .Ss Test case finalization The test case finalizes either when the body reaches its end, at which point the test is assumed to have .Em passed , or at any explicit call to .Fn ATF_PASS , .Fn ATF_FAIL or .Fn ATF_SKIP . These three macros terminate the execution of the test case immediately. The cleanup routine will be processed afterwards in a completely automated way, regardless of the test case's termination reason. .Pp .Fn ATF_PASS does not take any parameters. .Fn ATF_FAIL and .Fn ATF_SKIP take a single string that describes why the test case failed or was skipped, respectively. It is very important to provide a clear error message in both cases so that the user can quickly know why the test did not pass. .Ss Expectations Everything explained in the previous section changes when the test case expectations are redefined by the programmer. .Pp Each test case has an internal state called .Sq expect that describes what the test case expectations are at any point in time. The value of this property can change during execution by any of: .Bl -tag -width indent .It Fn expect_death "reason" Expects the test case to exit prematurely regardless of the nature of the exit. .It Fn expect_exit "exitcode" "reason" Expects the test case to exit cleanly. If .Va exitcode is not .Sq -1 , the runtime engine will validate that the exit code of the test case matches the one provided in this call. Otherwise, the exact value will be ignored. .It Fn expect_fail "reason" Any failure (be it fatal or non-fatal) raised in this mode is recorded. However, such failures do not report the test case as failed; instead, the test case finalizes cleanly and is reported as .Sq expected failure ; this report includes the provided .Fa reason as part of it. If no error is raised while running in this mode, then the test case is reported as .Sq failed . .Pp This mode is useful to reproduce actual known bugs in tests. Whenever the developer fixes the bug later on, the test case will start reporting a failure, signaling the developer that the test case must be adjusted to the new conditions. In this situation, it is useful, for example, to set .Fa reason as the bug number for tracking purposes. .It Fn expect_pass This is the normal mode of execution. In this mode, any failure is reported as such to the user and the test case is marked as .Sq failed . .It Fn expect_race "reason" Any failure or timeout during the execution of the test case will be considered as if a race condition has been triggered and reported as such. If no problems arise, the test will continue execution as usual. .It Fn expect_signal "signo" "reason" Expects the test case to terminate due to the reception of a signal. If .Va signo is not .Sq -1 , the runtime engine will validate that the signal that terminated the test case matches the one provided in this call. Otherwise, the exact value will be ignored. .It Fn expect_timeout "reason" Expects the test case to execute for longer than its timeout. .El .Ss Helper macros for common checks The library provides several macros that are very handy in multiple situations. These basically check some condition after executing a given statement or processing a given expression and, if the condition is not met, they automatically call .Fn ATF_FAIL with an appropriate error message. .Pp .Fn ATF_REQUIRE takes an expression and raises a failure if it evaluates to false. .Pp .Fn ATF_REQUIRE_EQ takes two expressions and raises a failure if the two do not evaluate to the same exact value. The common style is to put the expected value in the first parameter and the actual value in the second parameter. .Pp .Fn ATF_REQUIRE_IN takes an element and a collection and validates that the element is present in the collection. .Pp .Fn ATF_REQUIRE_MATCH takes a regular expression and a string and raises a failure if the regular expression does not match the string. .Pp .Fn ATF_REQUIRE_NOT_IN takes an element and a collection and validates that the element is not present in the collection. .Pp .Fn ATF_REQUIRE_THROW takes the name of an exception and a statement and raises a failure if the statement does not throw the specified exception. .Fn ATF_REQUIRE_THROW_RE -takes the name of an exception, a regular expresion and a statement and raises a -failure if the statement does not throw the specified exception and if the +takes the name of an exception, a regular expression and a statement, and raises +a failure if the statement does not throw the specified exception and if the message of the exception does not match the regular expression. .Pp .Fn ATF_CHECK_ERRNO and .Fn ATF_REQUIRE_ERRNO take, first, the error code that the check is expecting to find in the .Va errno variable and, second, a boolean expression that, if evaluates to true, means that a call failed and .Va errno has to be checked against the first value. .Ss Utility functions The following functions are provided as part of the .Nm API to simplify the creation of a variety of tests. In particular, these are useful to write tests for command-line interfaces. .Pp .Ft void .Fo atf::utils::cat_file .Fa "const std::string& path" .Fa "const std::string& prefix" .Fc .Bd -ragged -offset indent Prints the contents of .Fa path to the standard output, prefixing every line with the string in .Fa prefix . .Ed .Pp .Ft bool .Fo atf::utils::compare_file .Fa "const std::string& path" .Fa "const std::string& contents" .Fc .Bd -ragged -offset indent Returns true if the given .Fa path matches exactly the expected inlined .Fa contents . .Ed .Pp .Ft void .Fo atf::utils::copy_file .Fa "const std::string& source" .Fa "const std::string& destination" .Fc .Bd -ragged -offset indent Copies the file .Fa source to .Fa destination . The permissions of the file are preserved during the code. .Ed .Pp .Ft void .Fo atf::utils::create_file .Fa "const std::string& path" .Fa "const std::string& contents" .Fc .Bd -ragged -offset indent Creates .Fa file with the text given in .Fa contents . .Ed .Pp .Ft void .Fo atf::utils::file_exists .Fa "const std::string& path" .Fc .Bd -ragged -offset indent Checks if .Fa path exists. .Ed .Pp .Ft pid_t .Fo atf::utils::fork .Fa "void" .Fc .Bd -ragged -offset indent Forks a process and redirects the standard output and standard error of the child to files for later validation with .Fn atf::utils::wait . Fails the test case if the fork fails, so this does not return an error. .Ed .Pp .Ft bool .Fo atf::utils::grep_collection .Fa "const std::string& regexp" .Fa "const Collection& collection" .Fc .Bd -ragged -offset indent Searches for the regular expression .Fa regexp in any of the strings contained in the .Fa collection . This is a template that accepts any one-dimensional container of strings. .Ed .Pp .Ft bool .Fo atf::utils::grep_file .Fa "const std::string& regexp" .Fa "const std::string& path" .Fc .Bd -ragged -offset indent Searches for the regular expression .Fa regexp in the file .Fa path . The variable arguments are used to construct the regular expression. .Ed .Pp .Ft bool .Fo atf::utils::grep_string .Fa "const std::string& regexp" .Fa "const std::string& str" .Fc .Bd -ragged -offset indent Searches for the regular expression .Fa regexp in the string .Fa str . .Ed .Ft void .Fo atf::utils::redirect .Fa "const int fd" .Fa "const std::string& path" .Fc .Bd -ragged -offset indent Redirects the given file descriptor .Fa fd to the file .Fa path . This function exits the process in case of an error and does not properly mark the test case as failed. As a result, it should only be used in subprocesses of the test case; specially those spawned by .Fn atf::utils::fork . .Ed .Pp .Ft void .Fo atf::utils::wait .Fa "const pid_t pid" .Fa "const int expected_exit_status" .Fa "const std::string& expected_stdout" .Fa "const std::string& expected_stderr" .Fc .Bd -ragged -offset indent Waits and validates the result of a subprocess spawned with .Fn atf::utils::wait . The validation involves checking that the subprocess exited cleanly and returned the code specified in .Fa expected_exit_status and that its standard output and standard error match the strings given in .Fa expected_stdout and .Fa expected_stderr . .Pp If any of the .Fa expected_stdout or .Fa expected_stderr strings are prefixed with .Sq save: , then they specify the name of the file into which to store the stdout or stderr of the subprocess, and no comparison is performed. .Ed .Sh ENVIRONMENT The following variables are recognized by .Nm but should not be overridden other than for testing purposes: .Pp .Bl -tag -width ATFXBUILDXCXXFLAGSXX -compact .It Va ATF_BUILD_CC Path to the C compiler. .It Va ATF_BUILD_CFLAGS C compiler flags. .It Va ATF_BUILD_CPP Path to the C/C++ preprocessor. .It Va ATF_BUILD_CPPFLAGS C/C++ preprocessor flags. .It Va ATF_BUILD_CXX Path to the C++ compiler. .It Va ATF_BUILD_CXXFLAGS C++ compiler flags. .El .Sh EXAMPLES The following shows a complete test program with a single test case that validates the addition operator: .Bd -literal -offset indent #include ATF_TEST_CASE(addition); ATF_TEST_CASE_HEAD(addition) { set_md_var("descr", "Sample tests for the addition operator"); } ATF_TEST_CASE_BODY(addition) { ATF_REQUIRE_EQ(0, 0 + 0); ATF_REQUIRE_EQ(1, 0 + 1); ATF_REQUIRE_EQ(1, 1 + 0); ATF_REQUIRE_EQ(2, 1 + 1); ATF_REQUIRE_EQ(300, 100 + 200); } ATF_TEST_CASE(open_failure); ATF_TEST_CASE_HEAD(open_failure) { set_md_var("descr", "Sample tests for the open function"); } ATF_TEST_CASE_BODY(open_failure) { ATF_REQUIRE_ERRNO(ENOENT, open("non-existent", O_RDONLY) == -1); } ATF_TEST_CASE(known_bug); ATF_TEST_CASE_HEAD(known_bug) { set_md_var("descr", "Reproduces a known bug"); } ATF_TEST_CASE_BODY(known_bug) { expect_fail("See bug number foo/bar"); ATF_REQUIRE_EQ(3, 1 + 1); expect_pass(); ATF_REQUIRE_EQ(3, 1 + 2); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, addition); ATF_ADD_TEST_CASE(tcs, open_failure); ATF_ADD_TEST_CASE(tcs, known_bug); } .Ed .Sh SEE ALSO .Xr atf-test-program 1 , .Xr atf-test-case 4 diff --git a/contrib/atf/atf-c++/detail/test_helpers.hpp b/contrib/atf/atf-c++/detail/test_helpers.hpp index f166ee218a13..c1171801a3a7 100644 --- a/contrib/atf/atf-c++/detail/test_helpers.hpp +++ b/contrib/atf/atf-c++/detail/test_helpers.hpp @@ -1,107 +1,112 @@ // Copyright (c) 2009 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if defined(ATF_CXX_DETAIL_TEST_HELPERS_H) # error "Cannot include test_helpers.hpp more than once." #else # define ATF_CXX_DETAIL_TEST_HELPERS_H #endif #include #include #include #include #include +#include #include #define HEADER_TC(name, hdrname) \ ATF_TEST_CASE(name); \ ATF_TEST_CASE_HEAD(name) \ { \ set_md_var("descr", "Tests that the " hdrname " file can be " \ "included on its own, without any prerequisites"); \ + const std::string cxx = atf::env::get("ATF_BUILD_CXX", ATF_BUILD_CXX); \ + set_md_var("require.progs", cxx); \ } \ ATF_TEST_CASE_BODY(name) \ { \ header_check(hdrname); \ } #define BUILD_TC(name, sfile, descr, failmsg) \ ATF_TEST_CASE(name); \ ATF_TEST_CASE_HEAD(name) \ { \ set_md_var("descr", descr); \ + const std::string cxx = atf::env::get("ATF_BUILD_CXX", ATF_BUILD_CXX); \ + set_md_var("require.progs", cxx); \ } \ ATF_TEST_CASE_BODY(name) \ { \ if (!build_check_cxx_o_srcdir(*this, sfile)) \ ATF_FAIL(failmsg); \ } namespace atf { namespace tests { class tc; } } void header_check(const char*); bool build_check_cxx_o(const char*); bool build_check_cxx_o_srcdir(const atf::tests::tc&, const char*); atf::fs::path get_process_helpers_path(const atf::tests::tc&, bool); struct run_h_tc_data { const atf::tests::vars_map& m_config; run_h_tc_data(const atf::tests::vars_map& config) : m_config(config) {} }; template< class TestCase > void run_h_tc_child(void* v) { run_h_tc_data* data = static_cast< run_h_tc_data* >(v); TestCase tc; tc.init(data->m_config); tc.run("result"); std::exit(EXIT_SUCCESS); } template< class TestCase > void run_h_tc(atf::tests::vars_map config = atf::tests::vars_map()) { run_h_tc_data data(config); atf::process::child c = atf::process::fork( run_h_tc_child< TestCase >, atf::process::stream_redirect_path(atf::fs::path("stdout")), atf::process::stream_redirect_path(atf::fs::path("stderr")), &data); const atf::process::status s = c.wait(); ATF_REQUIRE(s.exited()); } diff --git a/contrib/atf/atf-c++/tests.hpp b/contrib/atf/atf-c++/tests.hpp index ce2fb1d165c8..a03cc852dcf8 100644 --- a/contrib/atf/atf-c++/tests.hpp +++ b/contrib/atf/atf-c++/tests.hpp @@ -1,125 +1,125 @@ // Copyright (c) 2007 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if !defined(ATF_CXX_TESTS_HPP) #define ATF_CXX_TESTS_HPP #include #include #include extern "C" { #include } namespace atf { namespace tests { namespace detail { class atf_tp_writer { std::ostream& m_os; bool m_is_first; public: atf_tp_writer(std::ostream&); void start_tc(const std::string&); void end_tc(void); void tc_meta_data(const std::string&, const std::string&); }; bool match(const std::string&, const std::string&); } // namespace // ------------------------------------------------------------------------ // The "vars_map" class. // ------------------------------------------------------------------------ typedef std::map< std::string, std::string > vars_map; // ------------------------------------------------------------------------ // The "tc" class. // ------------------------------------------------------------------------ struct tc_impl; class tc { // Non-copyable. tc(const tc&); tc& operator=(const tc&); - std::auto_ptr< tc_impl > pimpl; + std::unique_ptr< tc_impl > pimpl; protected: virtual void head(void); virtual void body(void) const = 0; virtual void cleanup(void) const; void require_prog(const std::string&) const; friend struct tc_impl; public: tc(const std::string&, const bool); virtual ~tc(void); void init(const vars_map&); const std::string get_config_var(const std::string&) const; const std::string get_config_var(const std::string&, const std::string&) const; const std::string get_md_var(const std::string&) const; const vars_map get_md_vars(void) const; bool has_config_var(const std::string&) const; bool has_md_var(const std::string&) const; void set_md_var(const std::string&, const std::string&); void run(const std::string&) const; void run_cleanup(void) const; // To be called from the child process only. static void pass(void) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail(const std::string&) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail_nonfatal(const std::string&); static void skip(const std::string&) ATF_DEFS_ATTRIBUTE_NORETURN; static void check_errno(const char*, const int, const int, const char*, const bool); static void require_errno(const char*, const int, const int, const char*, const bool); static void expect_pass(void); static void expect_fail(const std::string&); static void expect_exit(const int, const std::string&); static void expect_signal(const int, const std::string&); static void expect_death(const std::string&); static void expect_timeout(const std::string&); }; } // namespace tests } // namespace atf #endif // !defined(ATF_CXX_TESTS_HPP) diff --git a/contrib/atf/atf-c++/utils.cpp b/contrib/atf/atf-c++/utils.cpp index a6ab08f0d770..995d78c6542e 100644 --- a/contrib/atf/atf-c++/utils.cpp +++ b/contrib/atf/atf-c++/utils.cpp @@ -1,100 +1,107 @@ // Copyright (c) 2007 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "atf-c++/utils.hpp" extern "C" { #include "atf-c/utils.h" } #include #include void atf::utils::cat_file(const std::string& path, const std::string& prefix) { atf_utils_cat_file(path.c_str(), prefix.c_str()); } void atf::utils::copy_file(const std::string& source, const std::string& destination) { atf_utils_copy_file(source.c_str(), destination.c_str()); } bool atf::utils::compare_file(const std::string& path, const std::string& contents) { return atf_utils_compare_file(path.c_str(), contents.c_str()); } void atf::utils::create_file(const std::string& path, const std::string& contents) { atf_utils_create_file(path.c_str(), "%s", contents.c_str()); } bool atf::utils::file_exists(const std::string& path) { return atf_utils_file_exists(path.c_str()); } pid_t atf::utils::fork(void) { std::cout.flush(); std::cerr.flush(); return atf_utils_fork(); } +void +atf::utils::reset_resultsfile(void) +{ + + atf_utils_reset_resultsfile(); +} + bool atf::utils::grep_file(const std::string& regex, const std::string& path) { return atf_utils_grep_file("%s", path.c_str(), regex.c_str()); } bool atf::utils::grep_string(const std::string& regex, const std::string& str) { return atf_utils_grep_string("%s", str.c_str(), regex.c_str()); } void atf::utils::redirect(const int fd, const std::string& path) { if (fd == STDOUT_FILENO) std::cout.flush(); else if (fd == STDERR_FILENO) std::cerr.flush(); atf_utils_redirect(fd, path.c_str()); } void atf::utils::wait(const pid_t pid, const int exitstatus, const std::string& expout, const std::string& experr) { atf_utils_wait(pid, exitstatus, expout.c_str(), experr.c_str()); } diff --git a/contrib/atf/atf-c++/utils.hpp b/contrib/atf/atf-c++/utils.hpp index 8f5c5e337455..34d77a126df7 100644 --- a/contrib/atf/atf-c++/utils.hpp +++ b/contrib/atf/atf-c++/utils.hpp @@ -1,64 +1,65 @@ // Copyright (c) 2007 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if !defined(ATF_CXX_UTILS_HPP) #define ATF_CXX_UTILS_HPP extern "C" { #include } #include namespace atf { namespace utils { void cat_file(const std::string&, const std::string&); bool compare_file(const std::string&, const std::string&); void copy_file(const std::string&, const std::string&); void create_file(const std::string&, const std::string&); bool file_exists(const std::string&); pid_t fork(void); +void reset_resultsfile(void); bool grep_file(const std::string&, const std::string&); bool grep_string(const std::string&, const std::string&); void redirect(const int, const std::string&); void wait(const pid_t, const int, const std::string&, const std::string&); template< typename Collection > bool grep_collection(const std::string& regexp, const Collection& collection) { for (typename Collection::const_iterator iter = collection.begin(); iter != collection.end(); ++iter) { if (grep_string(regexp, *iter)) return true; } return false; } } // namespace utils } // namespace atf #endif // !defined(ATF_CXX_UTILS_HPP) diff --git a/contrib/atf/atf-c++/utils_test.cpp b/contrib/atf/atf-c++/utils_test.cpp index 34e0709f580a..93e16652bac1 100644 --- a/contrib/atf/atf-c++/utils_test.cpp +++ b/contrib/atf/atf-c++/utils_test.cpp @@ -1,509 +1,510 @@ // Copyright (c) 2007 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "atf-c++/utils.hpp" extern "C" { #include #include #include #include } #include #include #include #include #include #include #include static std::string read_file(const std::string& path) { char buffer[1024]; const int fd = open(path.c_str(), O_RDONLY); if (fd == -1) ATF_FAIL("Cannot open " + path); const ssize_t length = read(fd, buffer, sizeof(buffer) - 1); close(fd); ATF_REQUIRE(length != -1); if (length == sizeof(buffer) - 1) ATF_FAIL("Internal buffer not long enough to read temporary file"); ((char *)buffer)[length] = '\0'; return buffer; } // ------------------------------------------------------------------------ // Tests cases for the free functions. // ------------------------------------------------------------------------ ATF_TEST_CASE_WITHOUT_HEAD(cat_file__empty); ATF_TEST_CASE_BODY(cat_file__empty) { atf::utils::create_file("file.txt", ""); atf::utils::redirect(STDOUT_FILENO, "captured.txt"); atf::utils::cat_file("file.txt", "PREFIX"); std::cout.flush(); close(STDOUT_FILENO); ATF_REQUIRE_EQ("", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(cat_file__one_line); ATF_TEST_CASE_BODY(cat_file__one_line) { atf::utils::create_file("file.txt", "This is a single line\n"); atf::utils::redirect(STDOUT_FILENO, "captured.txt"); atf::utils::cat_file("file.txt", "PREFIX"); std::cout.flush(); close(STDOUT_FILENO); ATF_REQUIRE_EQ("PREFIXThis is a single line\n", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(cat_file__several_lines); ATF_TEST_CASE_BODY(cat_file__several_lines) { atf::utils::create_file("file.txt", "First\nSecond line\nAnd third\n"); atf::utils::redirect(STDOUT_FILENO, "captured.txt"); atf::utils::cat_file("file.txt", ">"); std::cout.flush(); close(STDOUT_FILENO); ATF_REQUIRE_EQ(">First\n>Second line\n>And third\n", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(cat_file__no_newline_eof); ATF_TEST_CASE_BODY(cat_file__no_newline_eof) { atf::utils::create_file("file.txt", "Foo\n bar baz"); atf::utils::redirect(STDOUT_FILENO, "captured.txt"); atf::utils::cat_file("file.txt", "PREFIX"); std::cout.flush(); close(STDOUT_FILENO); ATF_REQUIRE_EQ("PREFIXFoo\nPREFIX bar baz", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__empty__match); ATF_TEST_CASE_BODY(compare_file__empty__match) { atf::utils::create_file("test.txt", ""); ATF_REQUIRE(atf::utils::compare_file("test.txt", "")); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__empty__not_match); ATF_TEST_CASE_BODY(compare_file__empty__not_match) { atf::utils::create_file("test.txt", ""); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "\n")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "foo")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", " ")); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__short__match); ATF_TEST_CASE_BODY(compare_file__short__match) { atf::utils::create_file("test.txt", "this is a short file"); ATF_REQUIRE(atf::utils::compare_file("test.txt", "this is a short file")); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__short__not_match); ATF_TEST_CASE_BODY(compare_file__short__not_match) { atf::utils::create_file("test.txt", "this is a short file"); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "\n")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "this is a Short file")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "this is a short fil")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "this is a short file ")); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__long__match); ATF_TEST_CASE_BODY(compare_file__long__match) { char long_contents[3456]; size_t i = 0; for (; i < sizeof(long_contents) - 1; i++) long_contents[i] = '0' + (i % 10); long_contents[i] = '\0'; atf::utils::create_file("test.txt", long_contents); ATF_REQUIRE(atf::utils::compare_file("test.txt", long_contents)); } ATF_TEST_CASE_WITHOUT_HEAD(compare_file__long__not_match); ATF_TEST_CASE_BODY(compare_file__long__not_match) { char long_contents[3456]; size_t i = 0; for (; i < sizeof(long_contents) - 1; i++) long_contents[i] = '0' + (i % 10); long_contents[i] = '\0'; atf::utils::create_file("test.txt", long_contents); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "\n")); ATF_REQUIRE(!atf::utils::compare_file("test.txt", "0123456789")); long_contents[i - 1] = 'Z'; ATF_REQUIRE(!atf::utils::compare_file("test.txt", long_contents)); } ATF_TEST_CASE_WITHOUT_HEAD(copy_file__empty); ATF_TEST_CASE_BODY(copy_file__empty) { atf::utils::create_file("src.txt", ""); ATF_REQUIRE(chmod("src.txt", 0520) != -1); atf::utils::copy_file("src.txt", "dest.txt"); ATF_REQUIRE(atf::utils::compare_file("dest.txt", "")); struct stat sb; ATF_REQUIRE(stat("dest.txt", &sb) != -1); ATF_REQUIRE_EQ(0520, sb.st_mode & 0xfff); } ATF_TEST_CASE_WITHOUT_HEAD(copy_file__some_contents); ATF_TEST_CASE_BODY(copy_file__some_contents) { atf::utils::create_file("src.txt", "This is a\ntest file\n"); atf::utils::copy_file("src.txt", "dest.txt"); ATF_REQUIRE(atf::utils::compare_file("dest.txt", "This is a\ntest file\n")); } ATF_TEST_CASE_WITHOUT_HEAD(create_file); ATF_TEST_CASE_BODY(create_file) { atf::utils::create_file("test.txt", "This is a %d test"); ATF_REQUIRE_EQ("This is a %d test", read_file("test.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(file_exists); ATF_TEST_CASE_BODY(file_exists) { atf::utils::create_file("test.txt", "foo"); ATF_REQUIRE( atf::utils::file_exists("test.txt")); ATF_REQUIRE( atf::utils::file_exists("./test.txt")); ATF_REQUIRE(!atf::utils::file_exists("./test.tx")); ATF_REQUIRE(!atf::utils::file_exists("test.txt2")); } ATF_TEST_CASE_WITHOUT_HEAD(fork); ATF_TEST_CASE_BODY(fork) { std::cout << "Should not get into child\n"; std::cerr << "Should not get into child\n"; pid_t pid = atf::utils::fork(); if (pid == 0) { std::cout << "Child stdout\n"; std::cerr << "Child stderr\n"; exit(EXIT_SUCCESS); } int status; ATF_REQUIRE(waitpid(pid, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); std::ostringstream out_name; out_name << "atf_utils_fork_" << pid << "_out.txt"; std::ostringstream err_name; err_name << "atf_utils_fork_" << pid << "_err.txt"; ATF_REQUIRE_EQ("Child stdout\n", read_file(out_name.str())); ATF_REQUIRE_EQ("Child stderr\n", read_file(err_name.str())); } ATF_TEST_CASE_WITHOUT_HEAD(grep_collection__set); ATF_TEST_CASE_BODY(grep_collection__set) { std::set< std::string > strings; strings.insert("First"); strings.insert("Second"); ATF_REQUIRE( atf::utils::grep_collection("irs", strings)); ATF_REQUIRE( atf::utils::grep_collection("cond", strings)); ATF_REQUIRE(!atf::utils::grep_collection("Third", strings)); } ATF_TEST_CASE_WITHOUT_HEAD(grep_collection__vector); ATF_TEST_CASE_BODY(grep_collection__vector) { std::vector< std::string > strings; strings.push_back("First"); strings.push_back("Second"); ATF_REQUIRE( atf::utils::grep_collection("irs", strings)); ATF_REQUIRE( atf::utils::grep_collection("cond", strings)); ATF_REQUIRE(!atf::utils::grep_collection("Third", strings)); } ATF_TEST_CASE_WITHOUT_HEAD(grep_file); ATF_TEST_CASE_BODY(grep_file) { atf::utils::create_file("test.txt", "line1\nthe second line\naaaabbbb\n"); ATF_REQUIRE(atf::utils::grep_file("line1", "test.txt")); ATF_REQUIRE(atf::utils::grep_file("second line", "test.txt")); ATF_REQUIRE(atf::utils::grep_file("aa.*bb", "test.txt")); ATF_REQUIRE(!atf::utils::grep_file("foo", "test.txt")); ATF_REQUIRE(!atf::utils::grep_file("bar", "test.txt")); ATF_REQUIRE(!atf::utils::grep_file("aaaaa", "test.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(grep_string); ATF_TEST_CASE_BODY(grep_string) { const char *str = "a string - aaaabbbb"; ATF_REQUIRE(atf::utils::grep_string("a string", str)); ATF_REQUIRE(atf::utils::grep_string("^a string", str)); ATF_REQUIRE(atf::utils::grep_string("aaaabbbb$", str)); ATF_REQUIRE(atf::utils::grep_string("aa.*bb", str)); ATF_REQUIRE(!atf::utils::grep_string("foo", str)); ATF_REQUIRE(!atf::utils::grep_string("bar", str)); ATF_REQUIRE(!atf::utils::grep_string("aaaaa", str)); } ATF_TEST_CASE_WITHOUT_HEAD(redirect__stdout); ATF_TEST_CASE_BODY(redirect__stdout) { std::cout << "Buffer this"; atf::utils::redirect(STDOUT_FILENO, "captured.txt"); std::cout << "The printed message"; std::cout.flush(); ATF_REQUIRE_EQ("The printed message", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(redirect__stderr); ATF_TEST_CASE_BODY(redirect__stderr) { std::cerr << "Buffer this"; atf::utils::redirect(STDERR_FILENO, "captured.txt"); std::cerr << "The printed message"; std::cerr.flush(); ATF_REQUIRE_EQ("The printed message", read_file("captured.txt")); } ATF_TEST_CASE_WITHOUT_HEAD(redirect__other); ATF_TEST_CASE_BODY(redirect__other) { const std::string message = "Foo bar\nbaz\n"; atf::utils::redirect(15, "captured.txt"); ATF_REQUIRE(write(15, message.c_str(), message.length()) != -1); close(15); ATF_REQUIRE_EQ(message, read_file("captured.txt")); } static void fork_and_wait(const int exitstatus, const char* expout, const char* experr) { const pid_t pid = atf::utils::fork(); if (pid == 0) { std::cout << "Some output\n"; std::cerr << "Some error\n"; exit(123); } + atf::utils::reset_resultsfile(); atf::utils::wait(pid, exitstatus, expout, experr); exit(EXIT_SUCCESS); } ATF_TEST_CASE_WITHOUT_HEAD(wait__ok); ATF_TEST_CASE_BODY(wait__ok) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__ok_nested); ATF_TEST_CASE_BODY(wait__ok_nested) { const pid_t parent = atf::utils::fork(); ATF_REQUIRE(parent != -1); if (parent == 0) { const pid_t child = atf::utils::fork(); ATF_REQUIRE(child != -1); if (child == 0) { std::cerr.flush(); std::cout << "Child output\n"; std::cout.flush(); std::cerr << "Child error\n"; std::exit(50); } else { std::cout << "Parent output\n"; std::cerr << "Parent error\n"; atf::utils::wait(child, 50, "Child output\n", "Child error\n"); std::exit(40); } } else { atf::utils::wait(parent, 40, "Parent output\n" "subprocess stdout: Child output\n" "subprocess stderr: Child error\n", "Parent error\n"); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__invalid_exitstatus); ATF_TEST_CASE_BODY(wait__invalid_exitstatus) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(120, "Some output\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__invalid_stdout); ATF_TEST_CASE_BODY(wait__invalid_stdout) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output foo\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__invalid_stderr); ATF_TEST_CASE_BODY(wait__invalid_stderr) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "Some error foo\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__save_stdout); ATF_TEST_CASE_BODY(wait__save_stdout) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "save:my-output.txt", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); ATF_REQUIRE(atf::utils::compare_file("my-output.txt", "Some output\n")); } } ATF_TEST_CASE_WITHOUT_HEAD(wait__save_stderr); ATF_TEST_CASE_BODY(wait__save_stderr) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "save:my-output.txt"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); ATF_REQUIRE(atf::utils::compare_file("my-output.txt", "Some error\n")); } } // ------------------------------------------------------------------------ // Main. // ------------------------------------------------------------------------ ATF_INIT_TEST_CASES(tcs) { // Add the test for the free functions. ATF_ADD_TEST_CASE(tcs, cat_file__empty); ATF_ADD_TEST_CASE(tcs, cat_file__one_line); ATF_ADD_TEST_CASE(tcs, cat_file__several_lines); ATF_ADD_TEST_CASE(tcs, cat_file__no_newline_eof); ATF_ADD_TEST_CASE(tcs, compare_file__empty__match); ATF_ADD_TEST_CASE(tcs, compare_file__empty__not_match); ATF_ADD_TEST_CASE(tcs, compare_file__short__match); ATF_ADD_TEST_CASE(tcs, compare_file__short__not_match); ATF_ADD_TEST_CASE(tcs, compare_file__long__match); ATF_ADD_TEST_CASE(tcs, compare_file__long__not_match); ATF_ADD_TEST_CASE(tcs, copy_file__empty); ATF_ADD_TEST_CASE(tcs, copy_file__some_contents); ATF_ADD_TEST_CASE(tcs, create_file); ATF_ADD_TEST_CASE(tcs, file_exists); ATF_ADD_TEST_CASE(tcs, fork); ATF_ADD_TEST_CASE(tcs, grep_collection__set); ATF_ADD_TEST_CASE(tcs, grep_collection__vector); ATF_ADD_TEST_CASE(tcs, grep_file); ATF_ADD_TEST_CASE(tcs, grep_string); ATF_ADD_TEST_CASE(tcs, redirect__stdout); ATF_ADD_TEST_CASE(tcs, redirect__stderr); ATF_ADD_TEST_CASE(tcs, redirect__other); ATF_ADD_TEST_CASE(tcs, wait__ok); ATF_ADD_TEST_CASE(tcs, wait__ok_nested); ATF_ADD_TEST_CASE(tcs, wait__invalid_exitstatus); ATF_ADD_TEST_CASE(tcs, wait__invalid_stdout); ATF_ADD_TEST_CASE(tcs, wait__invalid_stderr); ATF_ADD_TEST_CASE(tcs, wait__save_stdout); ATF_ADD_TEST_CASE(tcs, wait__save_stderr); } diff --git a/contrib/atf/atf-c/.gitignore b/contrib/atf/atf-c/.gitignore new file mode 100644 index 000000000000..e7f0fb647c32 --- /dev/null +++ b/contrib/atf/atf-c/.gitignore @@ -0,0 +1 @@ +defs.h diff --git a/contrib/atf/atf-c/check.c b/contrib/atf/atf-c/check.c index 38afdf3743a6..1aec01bcca6b 100644 --- a/contrib/atf/atf-c/check.c +++ b/contrib/atf/atf-c/check.c @@ -1,484 +1,485 @@ /* Copyright (c) 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/check.h" #include #include #include +#include #include #include #include #include #include "atf-c/build.h" #include "atf-c/defs.h" #include "atf-c/detail/dynstr.h" #include "atf-c/detail/env.h" #include "atf-c/detail/fs.h" #include "atf-c/detail/list.h" #include "atf-c/detail/process.h" #include "atf-c/detail/sanity.h" #include "atf-c/error.h" #include "atf-c/utils.h" /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ static atf_error_t create_tmpdir(atf_fs_path_t *dir) { atf_error_t err; err = atf_fs_path_init_fmt(dir, "%s/check.XXXXXX", atf_env_get_with_default("TMPDIR", "/tmp")); if (atf_is_error(err)) goto out; err = atf_fs_mkdtemp(dir); if (atf_is_error(err)) { atf_fs_path_fini(dir); goto out; } INV(!atf_is_error(err)); out: return err; } static void cleanup_tmpdir(const atf_fs_path_t *dir, const atf_fs_path_t *outfile, const atf_fs_path_t *errfile) { { atf_error_t err = atf_fs_unlink(outfile); if (atf_is_error(err)) { INV(atf_error_is(err, "libc") && atf_libc_error_code(err) == ENOENT); atf_error_free(err); } else INV(!atf_is_error(err)); } { atf_error_t err = atf_fs_unlink(errfile); if (atf_is_error(err)) { INV(atf_error_is(err, "libc") && atf_libc_error_code(err) == ENOENT); atf_error_free(err); } else INV(!atf_is_error(err)); } { atf_error_t err = atf_fs_rmdir(dir); INV(!atf_is_error(err)); } } static int const_execvp(const char *file, const char *const *argv) { -#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) return execvp(file, UNCONST(argv)); #undef UNCONST } static atf_error_t init_sb(const atf_fs_path_t *path, atf_process_stream_t *sb) { atf_error_t err; if (path == NULL) err = atf_process_stream_init_inherit(sb); else err = atf_process_stream_init_redirect_path(sb, path); return err; } static atf_error_t init_sbs(const atf_fs_path_t *outfile, atf_process_stream_t *outsb, const atf_fs_path_t *errfile, atf_process_stream_t *errsb) { atf_error_t err; err = init_sb(outfile, outsb); if (atf_is_error(err)) goto out; err = init_sb(errfile, errsb); if (atf_is_error(err)) { atf_process_stream_fini(outsb); goto out; } out: return err; } struct exec_data { const char *const *m_argv; }; static void exec_child(void *) ATF_DEFS_ATTRIBUTE_NORETURN; static void exec_child(void *v) { struct exec_data *ea = v; const_execvp(ea->m_argv[0], ea->m_argv); fprintf(stderr, "execvp(%s) failed: %s\n", ea->m_argv[0], strerror(errno)); exit(127); } static atf_error_t fork_and_wait(const char *const *argv, const atf_fs_path_t *outfile, const atf_fs_path_t *errfile, atf_process_status_t *status) { atf_error_t err; atf_process_child_t child; atf_process_stream_t outsb, errsb; struct exec_data ea = { argv }; err = init_sbs(outfile, &outsb, errfile, &errsb); if (atf_is_error(err)) goto out; err = atf_process_fork(&child, exec_child, &outsb, &errsb, &ea); if (atf_is_error(err)) goto out_sbs; err = atf_process_child_wait(&child, status); out_sbs: atf_process_stream_fini(&errsb); atf_process_stream_fini(&outsb); out: return err; } static void update_success_from_status(const char *progname, const atf_process_status_t *status, bool *success) { bool s = atf_process_status_exited(status) && atf_process_status_exitstatus(status) == EXIT_SUCCESS; if (atf_process_status_exited(status)) { if (atf_process_status_exitstatus(status) == EXIT_SUCCESS) INV(s); else { INV(!s); fprintf(stderr, "%s failed with exit code %d\n", progname, atf_process_status_exitstatus(status)); } } else if (atf_process_status_signaled(status)) { INV(!s); fprintf(stderr, "%s failed due to signal %d%s\n", progname, atf_process_status_termsig(status), atf_process_status_coredump(status) ? " (core dumped)" : ""); } else { INV(!s); fprintf(stderr, "%s failed due to unknown reason\n", progname); } *success = s; } static atf_error_t array_to_list(const char *const *a, atf_list_t *l) { atf_error_t err; err = atf_list_init(l); if (atf_is_error(err)) goto out; while (*a != NULL) { char *item = strdup(*a); if (item == NULL) { err = atf_no_memory_error(); goto out; } err = atf_list_append(l, item, true); if (atf_is_error(err)) goto out; a++; } out: return err; } static void print_array(const char *const *array, const char *pfx) { const char *const *ptr; printf("%s", pfx); for (ptr = array; *ptr != NULL; ptr++) printf(" %s", *ptr); printf("\n"); } static atf_error_t check_build_run(const char *const *argv, bool *success) { atf_error_t err; atf_process_status_t status; print_array(argv, ">"); err = fork_and_wait(argv, NULL, NULL, &status); if (atf_is_error(err)) goto out; update_success_from_status(argv[0], &status, success); atf_process_status_fini(&status); INV(!atf_is_error(err)); out: return err; } /* --------------------------------------------------------------------- * The "atf_check_result" type. * --------------------------------------------------------------------- */ struct atf_check_result_impl { atf_list_t m_argv; atf_fs_path_t m_dir; atf_fs_path_t m_stdout; atf_fs_path_t m_stderr; atf_process_status_t m_status; }; static atf_error_t atf_check_result_init(atf_check_result_t *r, const char *const *argv, const atf_fs_path_t *dir) { atf_error_t err; r->pimpl = malloc(sizeof(struct atf_check_result_impl)); if (r->pimpl == NULL) return atf_no_memory_error(); err = array_to_list(argv, &r->pimpl->m_argv); if (atf_is_error(err)) goto out; err = atf_fs_path_copy(&r->pimpl->m_dir, dir); if (atf_is_error(err)) goto err_argv; err = atf_fs_path_init_fmt(&r->pimpl->m_stdout, "%s/stdout", atf_fs_path_cstring(dir)); if (atf_is_error(err)) goto err_dir; err = atf_fs_path_init_fmt(&r->pimpl->m_stderr, "%s/stderr", atf_fs_path_cstring(dir)); if (atf_is_error(err)) goto err_stdout; INV(!atf_is_error(err)); goto out; err_stdout: atf_fs_path_fini(&r->pimpl->m_stdout); err_dir: atf_fs_path_fini(&r->pimpl->m_dir); err_argv: atf_list_fini(&r->pimpl->m_argv); out: return err; } void atf_check_result_fini(atf_check_result_t *r) { atf_process_status_fini(&r->pimpl->m_status); cleanup_tmpdir(&r->pimpl->m_dir, &r->pimpl->m_stdout, &r->pimpl->m_stderr); atf_fs_path_fini(&r->pimpl->m_stdout); atf_fs_path_fini(&r->pimpl->m_stderr); atf_fs_path_fini(&r->pimpl->m_dir); atf_list_fini(&r->pimpl->m_argv); free(r->pimpl); } const char * atf_check_result_stdout(const atf_check_result_t *r) { return atf_fs_path_cstring(&r->pimpl->m_stdout); } const char * atf_check_result_stderr(const atf_check_result_t *r) { return atf_fs_path_cstring(&r->pimpl->m_stderr); } bool atf_check_result_exited(const atf_check_result_t *r) { return atf_process_status_exited(&r->pimpl->m_status); } int atf_check_result_exitcode(const atf_check_result_t *r) { return atf_process_status_exitstatus(&r->pimpl->m_status); } bool atf_check_result_signaled(const atf_check_result_t *r) { return atf_process_status_signaled(&r->pimpl->m_status); } int atf_check_result_termsig(const atf_check_result_t *r) { return atf_process_status_termsig(&r->pimpl->m_status); } /* --------------------------------------------------------------------- * Free functions. * --------------------------------------------------------------------- */ /* XXX: This function shouldn't be in this module. It messes with stdout * and stderr, and it provides a very high-end interface. This belongs, * probably, somewhere related to test cases (such as in the tc module). */ atf_error_t atf_check_build_c_o(const char *sfile, const char *ofile, const char *const optargs[], bool *success) { atf_error_t err; char **argv; err = atf_build_c_o(sfile, ofile, optargs, &argv); if (atf_is_error(err)) goto out; err = check_build_run((const char *const *)argv, success); atf_utils_free_charpp(argv); out: return err; } atf_error_t atf_check_build_cpp(const char *sfile, const char *ofile, const char *const optargs[], bool *success) { atf_error_t err; char **argv; err = atf_build_cpp(sfile, ofile, optargs, &argv); if (atf_is_error(err)) goto out; err = check_build_run((const char *const *)argv, success); atf_utils_free_charpp(argv); out: return err; } atf_error_t atf_check_build_cxx_o(const char *sfile, const char *ofile, const char *const optargs[], bool *success) { atf_error_t err; char **argv; err = atf_build_cxx_o(sfile, ofile, optargs, &argv); if (atf_is_error(err)) goto out; err = check_build_run((const char *const *)argv, success); atf_utils_free_charpp(argv); out: return err; } atf_error_t atf_check_exec_array(const char *const *argv, atf_check_result_t *r) { atf_error_t err; atf_fs_path_t dir; err = create_tmpdir(&dir); if (atf_is_error(err)) goto out; err = atf_check_result_init(r, argv, &dir); if (atf_is_error(err)) { atf_error_t err2 = atf_fs_rmdir(&dir); INV(!atf_is_error(err2)); goto out; } err = fork_and_wait(argv, &r->pimpl->m_stdout, &r->pimpl->m_stderr, &r->pimpl->m_status); if (atf_is_error(err)) { atf_check_result_fini(r); goto out; } INV(!atf_is_error(err)); atf_fs_path_fini(&dir); out: return err; } diff --git a/contrib/atf/atf-c/detail/fs_test.c b/contrib/atf/atf-c/detail/fs_test.c index 3dbc4d3ba7ef..7812be0334b8 100644 --- a/contrib/atf/atf-c/detail/fs_test.c +++ b/contrib/atf/atf-c/detail/fs_test.c @@ -1,1079 +1,1086 @@ /* Copyright (c) 2007 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/detail/fs.h" #include #include #include #include #include #include #include #include #include #include "atf-c/detail/test_helpers.h" #include "atf-c/detail/user.h" /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ static void create_dir(const char *p, int mode) { int ret; ret = mkdir(p, mode); if (ret == -1) atf_tc_fail("Could not create helper directory %s", p); } static void create_file(const char *p, int mode) { int fd; fd = open(p, O_CREAT | O_WRONLY | O_TRUNC, mode); if (fd == -1) atf_tc_fail("Could not create helper file %s", p); close(fd); } static bool exists(const atf_fs_path_t *p) { return access(atf_fs_path_cstring(p), F_OK) == 0; } static atf_error_t mkstemp_discard_fd(atf_fs_path_t *p) { int fd; atf_error_t err = atf_fs_mkstemp(p, &fd); if (!atf_is_error(err)) close(fd); return err; } /* --------------------------------------------------------------------- * Test cases for the "atf_fs_path" type. * --------------------------------------------------------------------- */ ATF_TC(path_normalize); ATF_TC_HEAD(path_normalize, tc) { atf_tc_set_md_var(tc, "descr", "Tests the path's normalization"); } ATF_TC_BODY(path_normalize, tc) { struct test { const char *in; const char *out; } tests[] = { { ".", ".", }, { "..", "..", }, { "/", "/", }, { "//", "/", }, /* NO_CHECK_STYLE */ { "///", "/", }, /* NO_CHECK_STYLE */ { "foo", "foo", }, { "foo/", "foo", }, { "foo/bar", "foo/bar", }, { "foo/bar/", "foo/bar", }, { "/foo", "/foo", }, { "/foo/bar", "/foo/bar", }, { "/foo/bar/", "/foo/bar", }, { "///foo", "/foo", }, /* NO_CHECK_STYLE */ { "///foo///bar", "/foo/bar", }, /* NO_CHECK_STYLE */ { "///foo///bar///", "/foo/bar", }, /* NO_CHECK_STYLE */ { NULL, NULL } }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p; printf("Input : >%s<\n", t->in); printf("Expected output: >%s<\n", t->out); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); printf("Output : >%s<\n", atf_fs_path_cstring(&p)); ATF_REQUIRE(strcmp(atf_fs_path_cstring(&p), t->out) == 0); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_copy); ATF_TC_HEAD(path_copy, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_path_copy constructor"); } ATF_TC_BODY(path_copy, tc) { atf_fs_path_t str, str2; RE(atf_fs_path_init_fmt(&str, "foo")); RE(atf_fs_path_copy(&str2, &str)); ATF_REQUIRE(atf_equal_fs_path_fs_path(&str, &str2)); RE(atf_fs_path_append_fmt(&str2, "bar")); ATF_REQUIRE(!atf_equal_fs_path_fs_path(&str, &str2)); atf_fs_path_fini(&str2); atf_fs_path_fini(&str); } ATF_TC(path_is_absolute); ATF_TC_HEAD(path_is_absolute, tc) { atf_tc_set_md_var(tc, "descr", "Tests the path::is_absolute function"); } ATF_TC_BODY(path_is_absolute, tc) { struct test { const char *in; bool abs; } tests[] = { { "/", true }, { "////", true }, /* NO_CHECK_STYLE */ { "////a", true }, /* NO_CHECK_STYLE */ { "//a//", true }, /* NO_CHECK_STYLE */ { "a////", false }, /* NO_CHECK_STYLE */ { "../foo", false }, { NULL, false }, }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p; printf("Input : %s\n", t->in); printf("Expected result: %s\n", t->abs ? "true" : "false"); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); printf("Result : %s\n", atf_fs_path_is_absolute(&p) ? "true" : "false"); if (t->abs) ATF_REQUIRE(atf_fs_path_is_absolute(&p)); else ATF_REQUIRE(!atf_fs_path_is_absolute(&p)); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_is_root); ATF_TC_HEAD(path_is_root, tc) { atf_tc_set_md_var(tc, "descr", "Tests the path::is_root function"); } ATF_TC_BODY(path_is_root, tc) { struct test { const char *in; bool root; } tests[] = { { "/", true }, { "////", true }, /* NO_CHECK_STYLE */ { "////a", false }, /* NO_CHECK_STYLE */ { "//a//", false }, /* NO_CHECK_STYLE */ { "a////", false }, /* NO_CHECK_STYLE */ { "../foo", false }, { NULL, false }, }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p; printf("Input : %s\n", t->in); printf("Expected result: %s\n", t->root ? "true" : "false"); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); printf("Result : %s\n", atf_fs_path_is_root(&p) ? "true" : "false"); if (t->root) ATF_REQUIRE(atf_fs_path_is_root(&p)); else ATF_REQUIRE(!atf_fs_path_is_root(&p)); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_branch_path); ATF_TC_HEAD(path_branch_path, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_path_branch_path " "function"); } ATF_TC_BODY(path_branch_path, tc) { struct test { const char *in; const char *branch; } tests[] = { { ".", "." }, { "foo", "." }, { "foo/bar", "foo" }, { "/foo", "/" }, { "/foo/bar", "/foo" }, { NULL, NULL }, }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p, bp; printf("Input : %s\n", t->in); printf("Expected output: %s\n", t->branch); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); RE(atf_fs_path_branch_path(&p, &bp)); printf("Output : %s\n", atf_fs_path_cstring(&bp)); ATF_REQUIRE(strcmp(atf_fs_path_cstring(&bp), t->branch) == 0); atf_fs_path_fini(&bp); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_leaf_name); ATF_TC_HEAD(path_leaf_name, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_path_leaf_name " "function"); } ATF_TC_BODY(path_leaf_name, tc) { struct test { const char *in; const char *leaf; } tests[] = { { ".", "." }, { "foo", "foo" }, { "foo/bar", "bar" }, { "/foo", "foo" }, { "/foo/bar", "bar" }, { NULL, NULL }, }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p; atf_dynstr_t ln; printf("Input : %s\n", t->in); printf("Expected output: %s\n", t->leaf); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); RE(atf_fs_path_leaf_name(&p, &ln)); printf("Output : %s\n", atf_dynstr_cstring(&ln)); ATF_REQUIRE(atf_equal_dynstr_cstring(&ln, t->leaf)); atf_dynstr_fini(&ln); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_append); ATF_TC_HEAD(path_append, tc) { atf_tc_set_md_var(tc, "descr", "Tests the concatenation of multiple " "paths"); } ATF_TC_BODY(path_append, tc) { struct test { const char *in; const char *ap; const char *out; } tests[] = { { "foo", "bar", "foo/bar" }, { "foo/", "/bar", "foo/bar" }, { "foo/", "/bar/baz", "foo/bar/baz" }, { "foo/", "///bar///baz", "foo/bar/baz" }, /* NO_CHECK_STYLE */ { NULL, NULL, NULL } }; struct test *t; for (t = &tests[0]; t->in != NULL; t++) { atf_fs_path_t p; printf("Input : >%s<\n", t->in); printf("Append : >%s<\n", t->ap); printf("Expected output: >%s<\n", t->out); RE(atf_fs_path_init_fmt(&p, "%s", t->in)); RE(atf_fs_path_append_fmt(&p, "%s", t->ap)); printf("Output : >%s<\n", atf_fs_path_cstring(&p)); ATF_REQUIRE(strcmp(atf_fs_path_cstring(&p), t->out) == 0); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_to_absolute); ATF_TC_HEAD(path_to_absolute, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_path_to_absolute " "function"); } ATF_TC_BODY(path_to_absolute, tc) { const char *names[] = { ".", "dir", NULL }; const char **n; ATF_REQUIRE(mkdir("dir", 0755) != -1); for (n = names; *n != NULL; n++) { atf_fs_path_t p, p2; atf_fs_stat_t st1, st2; RE(atf_fs_path_init_fmt(&p, "%s", *n)); RE(atf_fs_stat_init(&st1, &p)); printf("Relative path: %s\n", atf_fs_path_cstring(&p)); RE(atf_fs_path_to_absolute(&p, &p2)); printf("Absolute path: %s\n", atf_fs_path_cstring(&p2)); ATF_REQUIRE(atf_fs_path_is_absolute(&p2)); RE(atf_fs_stat_init(&st2, &p2)); ATF_REQUIRE_EQ(atf_fs_stat_get_device(&st1), atf_fs_stat_get_device(&st2)); ATF_REQUIRE_EQ(atf_fs_stat_get_inode(&st1), atf_fs_stat_get_inode(&st2)); atf_fs_stat_fini(&st2); atf_fs_stat_fini(&st1); atf_fs_path_fini(&p2); atf_fs_path_fini(&p); printf("\n"); } } ATF_TC(path_equal); ATF_TC_HEAD(path_equal, tc) { atf_tc_set_md_var(tc, "descr", "Tests the equality operators for paths"); } ATF_TC_BODY(path_equal, tc) { atf_fs_path_t p1, p2; RE(atf_fs_path_init_fmt(&p1, "foo")); RE(atf_fs_path_init_fmt(&p2, "foo")); ATF_REQUIRE(atf_equal_fs_path_fs_path(&p1, &p2)); atf_fs_path_fini(&p2); RE(atf_fs_path_init_fmt(&p2, "bar")); ATF_REQUIRE(!atf_equal_fs_path_fs_path(&p1, &p2)); atf_fs_path_fini(&p2); atf_fs_path_fini(&p1); } /* --------------------------------------------------------------------- * Test cases for the "atf_fs_stat" type. * --------------------------------------------------------------------- */ ATF_TC(stat_mode); ATF_TC_HEAD(stat_mode, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_stat_get_mode function " "and, indirectly, the constructor"); } ATF_TC_BODY(stat_mode, tc) { atf_fs_path_t p; atf_fs_stat_t st; create_file("f1", 0400); create_file("f2", 0644); RE(atf_fs_path_init_fmt(&p, "f1")); RE(atf_fs_stat_init(&st, &p)); ATF_CHECK_EQ(0400, atf_fs_stat_get_mode(&st)); atf_fs_stat_fini(&st); atf_fs_path_fini(&p); RE(atf_fs_path_init_fmt(&p, "f2")); RE(atf_fs_stat_init(&st, &p)); ATF_CHECK_EQ(0644, atf_fs_stat_get_mode(&st)); atf_fs_stat_fini(&st); atf_fs_path_fini(&p); } ATF_TC(stat_type); ATF_TC_HEAD(stat_type, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_stat_get_type function " "and, indirectly, the constructor"); } ATF_TC_BODY(stat_type, tc) { atf_fs_path_t p; atf_fs_stat_t st; create_dir("dir", 0755); create_file("reg", 0644); RE(atf_fs_path_init_fmt(&p, "dir")); RE(atf_fs_stat_init(&st, &p)); ATF_REQUIRE_EQ(atf_fs_stat_get_type(&st), atf_fs_stat_dir_type); atf_fs_stat_fini(&st); atf_fs_path_fini(&p); RE(atf_fs_path_init_fmt(&p, "reg")); RE(atf_fs_stat_init(&st, &p)); ATF_REQUIRE_EQ(atf_fs_stat_get_type(&st), atf_fs_stat_reg_type); atf_fs_stat_fini(&st); atf_fs_path_fini(&p); } ATF_TC(stat_perms); ATF_TC_HEAD(stat_perms, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_stat_is_* functions"); } ATF_TC_BODY(stat_perms, tc) { atf_fs_path_t p; atf_fs_stat_t st; create_file("reg", 0); RE(atf_fs_path_init_fmt(&p, "reg")); #define perms(ur, uw, ux, gr, gw, gx, othr, othw, othx) \ { \ RE(atf_fs_stat_init(&st, &p)); \ ATF_REQUIRE(atf_fs_stat_is_owner_readable(&st) == ur); \ ATF_REQUIRE(atf_fs_stat_is_owner_writable(&st) == uw); \ ATF_REQUIRE(atf_fs_stat_is_owner_executable(&st) == ux); \ ATF_REQUIRE(atf_fs_stat_is_group_readable(&st) == gr); \ ATF_REQUIRE(atf_fs_stat_is_group_writable(&st) == gw); \ ATF_REQUIRE(atf_fs_stat_is_group_executable(&st) == gx); \ ATF_REQUIRE(atf_fs_stat_is_other_readable(&st) == othr); \ ATF_REQUIRE(atf_fs_stat_is_other_writable(&st) == othw); \ ATF_REQUIRE(atf_fs_stat_is_other_executable(&st) == othx); \ atf_fs_stat_fini(&st); \ } chmod("reg", 0000); perms(false, false, false, false, false, false, false, false, false); chmod("reg", 0001); perms(false, false, false, false, false, false, false, false, true); chmod("reg", 0010); perms(false, false, false, false, false, true, false, false, false); chmod("reg", 0100); perms(false, false, true, false, false, false, false, false, false); chmod("reg", 0002); perms(false, false, false, false, false, false, false, true, false); chmod("reg", 0020); perms(false, false, false, false, true, false, false, false, false); chmod("reg", 0200); perms(false, true, false, false, false, false, false, false, false); chmod("reg", 0004); perms(false, false, false, false, false, false, true, false, false); chmod("reg", 0040); perms(false, false, false, true, false, false, false, false, false); chmod("reg", 0400); perms(true, false, false, false, false, false, false, false, false); chmod("reg", 0644); perms(true, true, false, true, false, false, true, false, false); chmod("reg", 0755); perms(true, true, true, true, false, true, true, false, true); chmod("reg", 0777); perms(true, true, true, true, true, true, true, true, true); #undef perms atf_fs_path_fini(&p); } /* --------------------------------------------------------------------- * Test cases for the free functions. * --------------------------------------------------------------------- */ ATF_TC(exists); ATF_TC_HEAD(exists, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_exists function"); } ATF_TC_BODY(exists, tc) { atf_error_t err; atf_fs_path_t pdir, pfile; bool b; RE(atf_fs_path_init_fmt(&pdir, "dir")); RE(atf_fs_path_init_fmt(&pfile, "dir/file")); create_dir(atf_fs_path_cstring(&pdir), 0755); create_file(atf_fs_path_cstring(&pfile), 0644); printf("Checking existence of a directory\n"); RE(atf_fs_exists(&pdir, &b)); ATF_REQUIRE(b); printf("Checking existence of a file\n"); RE(atf_fs_exists(&pfile, &b)); ATF_REQUIRE(b); /* XXX: This should probably be a separate test case to let the user * be aware that some tests were skipped because privileges were not * correct. */ if (!atf_user_is_root()) { printf("Checking existence of a file inside a directory without " "permissions\n"); ATF_REQUIRE(chmod(atf_fs_path_cstring(&pdir), 0000) != -1); err = atf_fs_exists(&pfile, &b); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_REQUIRE(chmod(atf_fs_path_cstring(&pdir), 0755) != -1); atf_error_free(err); } printf("Checking existence of a non-existent file\n"); ATF_REQUIRE(unlink(atf_fs_path_cstring(&pfile)) != -1); RE(atf_fs_exists(&pfile, &b)); ATF_REQUIRE(!b); atf_fs_path_fini(&pfile); atf_fs_path_fini(&pdir); } ATF_TC(eaccess); ATF_TC_HEAD(eaccess, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_eaccess function"); } ATF_TC_BODY(eaccess, tc) { const int modes[] = { atf_fs_access_f, atf_fs_access_r, atf_fs_access_w, atf_fs_access_x, 0 }; const int *m; struct tests { mode_t fmode; int amode; int uerror; int rerror; } tests[] = { { 0000, atf_fs_access_r, EACCES, 0 }, { 0000, atf_fs_access_w, EACCES, 0 }, { 0000, atf_fs_access_x, EACCES, EACCES }, { 0001, atf_fs_access_r, EACCES, 0 }, { 0001, atf_fs_access_w, EACCES, 0 }, { 0001, atf_fs_access_x, EACCES, 0 }, { 0002, atf_fs_access_r, EACCES, 0 }, { 0002, atf_fs_access_w, EACCES, 0 }, { 0002, atf_fs_access_x, EACCES, EACCES }, { 0004, atf_fs_access_r, EACCES, 0 }, { 0004, atf_fs_access_w, EACCES, 0 }, { 0004, atf_fs_access_x, EACCES, EACCES }, { 0010, atf_fs_access_r, EACCES, 0 }, { 0010, atf_fs_access_w, EACCES, 0 }, { 0010, atf_fs_access_x, 0, 0 }, { 0020, atf_fs_access_r, EACCES, 0 }, { 0020, atf_fs_access_w, 0, 0 }, { 0020, atf_fs_access_x, EACCES, EACCES }, { 0040, atf_fs_access_r, 0, 0 }, { 0040, atf_fs_access_w, EACCES, 0 }, { 0040, atf_fs_access_x, EACCES, EACCES }, { 0100, atf_fs_access_r, EACCES, 0 }, { 0100, atf_fs_access_w, EACCES, 0 }, { 0100, atf_fs_access_x, 0, 0 }, { 0200, atf_fs_access_r, EACCES, 0 }, { 0200, atf_fs_access_w, 0, 0 }, { 0200, atf_fs_access_x, EACCES, EACCES }, { 0400, atf_fs_access_r, 0, 0 }, { 0400, atf_fs_access_w, EACCES, 0 }, { 0400, atf_fs_access_x, EACCES, EACCES }, { 0, 0, 0, 0 } }; struct tests *t; atf_fs_path_t p; atf_error_t err; RE(atf_fs_path_init_fmt(&p, "the-file")); printf("Non-existent file checks\n"); for (m = &modes[0]; *m != 0; m++) { err = atf_fs_eaccess(&p, *m); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_REQUIRE_EQ(atf_libc_error_code(err), ENOENT); atf_error_free(err); } create_file(atf_fs_path_cstring(&p), 0000); ATF_REQUIRE(chown(atf_fs_path_cstring(&p), geteuid(), getegid()) != -1); for (t = &tests[0]; t->amode != 0; t++) { const int experr = atf_user_is_root() ? t->rerror : t->uerror; printf("\n"); printf("File mode : %04o\n", (unsigned int)t->fmode); printf("Access mode : 0x%02x\n", t->amode); ATF_REQUIRE(chmod(atf_fs_path_cstring(&p), t->fmode) != -1); /* First, existence check. */ err = atf_fs_eaccess(&p, atf_fs_access_f); ATF_REQUIRE(!atf_is_error(err)); /* Now do the specific test case. */ printf("Expected error: %d\n", experr); err = atf_fs_eaccess(&p, t->amode); if (atf_is_error(err)) { if (atf_error_is(err, "libc")) printf("Error : %d\n", atf_libc_error_code(err)); else printf("Error : Non-libc error\n"); } else printf("Error : None\n"); if (experr == 0) { ATF_REQUIRE(!atf_is_error(err)); } else { ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_REQUIRE_EQ(atf_libc_error_code(err), experr); atf_error_free(err); } } atf_fs_path_fini(&p); } ATF_TC(getcwd); ATF_TC_HEAD(getcwd, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_getcwd function"); } ATF_TC_BODY(getcwd, tc) { atf_fs_path_t cwd1, cwd2; create_dir ("root", 0755); RE(atf_fs_getcwd(&cwd1)); ATF_REQUIRE(chdir("root") != -1); RE(atf_fs_getcwd(&cwd2)); RE(atf_fs_path_append_fmt(&cwd1, "root")); ATF_REQUIRE(atf_equal_fs_path_fs_path(&cwd1, &cwd2)); atf_fs_path_fini(&cwd2); atf_fs_path_fini(&cwd1); } ATF_TC(rmdir_empty); ATF_TC_HEAD(rmdir_empty, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_rmdir function"); } ATF_TC_BODY(rmdir_empty, tc) { atf_fs_path_t p; RE(atf_fs_path_init_fmt(&p, "test-dir")); ATF_REQUIRE(mkdir("test-dir", 0755) != -1); ATF_REQUIRE(exists(&p)); RE(atf_fs_rmdir(&p)); ATF_REQUIRE(!exists(&p)); atf_fs_path_fini(&p); } ATF_TC(rmdir_enotempty); ATF_TC_HEAD(rmdir_enotempty, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_rmdir function"); } ATF_TC_BODY(rmdir_enotempty, tc) { atf_fs_path_t p; atf_error_t err; RE(atf_fs_path_init_fmt(&p, "test-dir")); ATF_REQUIRE(mkdir("test-dir", 0755) != -1); ATF_REQUIRE(exists(&p)); create_file("test-dir/foo", 0644); err = atf_fs_rmdir(&p); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_REQUIRE_EQ(atf_libc_error_code(err), ENOTEMPTY); atf_error_free(err); atf_fs_path_fini(&p); } -ATF_TC(rmdir_eperm); +ATF_TC_WITH_CLEANUP(rmdir_eperm); ATF_TC_HEAD(rmdir_eperm, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_rmdir function"); } ATF_TC_BODY(rmdir_eperm, tc) { atf_fs_path_t p; atf_error_t err; RE(atf_fs_path_init_fmt(&p, "test-dir/foo")); ATF_REQUIRE(mkdir("test-dir", 0755) != -1); ATF_REQUIRE(mkdir("test-dir/foo", 0755) != -1); ATF_REQUIRE(chmod("test-dir", 0555) != -1); ATF_REQUIRE(exists(&p)); err = atf_fs_rmdir(&p); if (atf_user_is_root()) { ATF_REQUIRE(!atf_is_error(err)); } else { ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_REQUIRE_EQ(atf_libc_error_code(err), EACCES); atf_error_free(err); } atf_fs_path_fini(&p); } +ATF_TC_CLEANUP(rmdir_eperm, tc) +{ + if (chmod("test-dir", 0755) == -1) { + fprintf(stderr, "Failed to unprotect test-dir; test directory " + "cleanup will fail\n"); + } +} ATF_TC(mkdtemp_ok); ATF_TC_HEAD(mkdtemp_ok, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkdtemp function, " "successful execution"); } ATF_TC_BODY(mkdtemp_ok, tc) { atf_fs_path_t p1, p2; atf_fs_stat_t s1, s2; RE(atf_fs_path_init_fmt(&p1, "testdir.XXXXXX")); RE(atf_fs_path_init_fmt(&p2, "testdir.XXXXXX")); RE(atf_fs_mkdtemp(&p1)); RE(atf_fs_mkdtemp(&p2)); ATF_REQUIRE(!atf_equal_fs_path_fs_path(&p1, &p2)); ATF_REQUIRE(exists(&p1)); ATF_REQUIRE(exists(&p2)); RE(atf_fs_stat_init(&s1, &p1)); ATF_REQUIRE_EQ(atf_fs_stat_get_type(&s1), atf_fs_stat_dir_type); ATF_REQUIRE( atf_fs_stat_is_owner_readable(&s1)); ATF_REQUIRE( atf_fs_stat_is_owner_writable(&s1)); ATF_REQUIRE( atf_fs_stat_is_owner_executable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_group_readable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_group_writable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_group_executable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_other_readable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_other_writable(&s1)); ATF_REQUIRE(!atf_fs_stat_is_other_executable(&s1)); RE(atf_fs_stat_init(&s2, &p2)); ATF_REQUIRE_EQ(atf_fs_stat_get_type(&s2), atf_fs_stat_dir_type); ATF_REQUIRE( atf_fs_stat_is_owner_readable(&s2)); ATF_REQUIRE( atf_fs_stat_is_owner_writable(&s2)); ATF_REQUIRE( atf_fs_stat_is_owner_executable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_group_readable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_group_writable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_group_executable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_other_readable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_other_writable(&s2)); ATF_REQUIRE(!atf_fs_stat_is_other_executable(&s2)); atf_fs_stat_fini(&s2); atf_fs_stat_fini(&s1); atf_fs_path_fini(&p2); atf_fs_path_fini(&p1); } ATF_TC(mkdtemp_err); ATF_TC_HEAD(mkdtemp_err, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkdtemp function, " "error conditions"); atf_tc_set_md_var(tc, "require.user", "unprivileged"); } ATF_TC_BODY(mkdtemp_err, tc) { atf_error_t err; atf_fs_path_t p; ATF_REQUIRE(mkdir("dir", 0555) != -1); RE(atf_fs_path_init_fmt(&p, "dir/testdir.XXXXXX")); err = atf_fs_mkdtemp(&p); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_CHECK_EQ(atf_libc_error_code(err), EACCES); atf_error_free(err); ATF_CHECK(!exists(&p)); ATF_CHECK(strcmp(atf_fs_path_cstring(&p), "dir/testdir.XXXXXX") == 0); atf_fs_path_fini(&p); } static void do_umask_check(atf_error_t (*const mk_func)(atf_fs_path_t *), atf_fs_path_t *path, const mode_t test_mask, const char *str_mask, const char *exp_name) { char buf[1024]; int old_umask; atf_error_t err; printf("Creating temporary %s with umask %s\n", exp_name, str_mask); old_umask = umask(test_mask); err = mk_func(path); (void)umask(old_umask); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "invalid_umask")); atf_error_format(err, buf, sizeof(buf)); ATF_CHECK(strstr(buf, exp_name) != NULL); ATF_CHECK(strstr(buf, str_mask) != NULL); atf_error_free(err); } ATF_TC(mkdtemp_umask); ATF_TC_HEAD(mkdtemp_umask, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkdtemp function " "causing an error due to a too strict umask"); } ATF_TC_BODY(mkdtemp_umask, tc) { atf_fs_path_t p; RE(atf_fs_path_init_fmt(&p, "testdir.XXXXXX")); do_umask_check(atf_fs_mkdtemp, &p, 00100, "00100", "directory"); do_umask_check(atf_fs_mkdtemp, &p, 00200, "00200", "directory"); do_umask_check(atf_fs_mkdtemp, &p, 00400, "00400", "directory"); do_umask_check(atf_fs_mkdtemp, &p, 00500, "00500", "directory"); do_umask_check(atf_fs_mkdtemp, &p, 00600, "00600", "directory"); atf_fs_path_fini(&p); } ATF_TC(mkstemp_ok); ATF_TC_HEAD(mkstemp_ok, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkstemp function, " "successful execution"); } ATF_TC_BODY(mkstemp_ok, tc) { int fd1, fd2; atf_fs_path_t p1, p2; atf_fs_stat_t s1, s2; RE(atf_fs_path_init_fmt(&p1, "testfile.XXXXXX")); RE(atf_fs_path_init_fmt(&p2, "testfile.XXXXXX")); fd1 = fd2 = -1; RE(atf_fs_mkstemp(&p1, &fd1)); RE(atf_fs_mkstemp(&p2, &fd2)); ATF_REQUIRE(!atf_equal_fs_path_fs_path(&p1, &p2)); ATF_REQUIRE(exists(&p1)); ATF_REQUIRE(exists(&p2)); ATF_CHECK(fd1 != -1); ATF_CHECK(fd2 != -1); ATF_CHECK(write(fd1, "foo", 3) == 3); ATF_CHECK(write(fd2, "bar", 3) == 3); close(fd1); close(fd2); RE(atf_fs_stat_init(&s1, &p1)); ATF_CHECK_EQ(atf_fs_stat_get_type(&s1), atf_fs_stat_reg_type); ATF_CHECK( atf_fs_stat_is_owner_readable(&s1)); ATF_CHECK( atf_fs_stat_is_owner_writable(&s1)); ATF_CHECK(!atf_fs_stat_is_owner_executable(&s1)); ATF_CHECK(!atf_fs_stat_is_group_readable(&s1)); ATF_CHECK(!atf_fs_stat_is_group_writable(&s1)); ATF_CHECK(!atf_fs_stat_is_group_executable(&s1)); ATF_CHECK(!atf_fs_stat_is_other_readable(&s1)); ATF_CHECK(!atf_fs_stat_is_other_writable(&s1)); ATF_CHECK(!atf_fs_stat_is_other_executable(&s1)); RE(atf_fs_stat_init(&s2, &p2)); ATF_CHECK_EQ(atf_fs_stat_get_type(&s2), atf_fs_stat_reg_type); ATF_CHECK( atf_fs_stat_is_owner_readable(&s2)); ATF_CHECK( atf_fs_stat_is_owner_writable(&s2)); ATF_CHECK(!atf_fs_stat_is_owner_executable(&s2)); ATF_CHECK(!atf_fs_stat_is_group_readable(&s2)); ATF_CHECK(!atf_fs_stat_is_group_writable(&s2)); ATF_CHECK(!atf_fs_stat_is_group_executable(&s2)); ATF_CHECK(!atf_fs_stat_is_other_readable(&s2)); ATF_CHECK(!atf_fs_stat_is_other_writable(&s2)); ATF_CHECK(!atf_fs_stat_is_other_executable(&s2)); atf_fs_stat_fini(&s2); atf_fs_stat_fini(&s1); atf_fs_path_fini(&p2); atf_fs_path_fini(&p1); } ATF_TC(mkstemp_err); ATF_TC_HEAD(mkstemp_err, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkstemp function, " "error conditions"); atf_tc_set_md_var(tc, "require.user", "unprivileged"); } ATF_TC_BODY(mkstemp_err, tc) { int fd; atf_error_t err; atf_fs_path_t p; ATF_REQUIRE(mkdir("dir", 0555) != -1); RE(atf_fs_path_init_fmt(&p, "dir/testfile.XXXXXX")); fd = 1234; err = atf_fs_mkstemp(&p, &fd); ATF_REQUIRE(atf_is_error(err)); ATF_REQUIRE(atf_error_is(err, "libc")); ATF_CHECK_EQ(atf_libc_error_code(err), EACCES); atf_error_free(err); ATF_CHECK(!exists(&p)); ATF_CHECK(strcmp(atf_fs_path_cstring(&p), "dir/testfile.XXXXXX") == 0); ATF_CHECK_EQ(fd, 1234); atf_fs_path_fini(&p); } ATF_TC(mkstemp_umask); ATF_TC_HEAD(mkstemp_umask, tc) { atf_tc_set_md_var(tc, "descr", "Tests the atf_fs_mkstemp function " "causing an error due to a too strict umask"); } ATF_TC_BODY(mkstemp_umask, tc) { atf_fs_path_t p; RE(atf_fs_path_init_fmt(&p, "testfile.XXXXXX")); do_umask_check(mkstemp_discard_fd, &p, 00100, "00100", "regular file"); do_umask_check(mkstemp_discard_fd, &p, 00200, "00200", "regular file"); do_umask_check(mkstemp_discard_fd, &p, 00400, "00400", "regular file"); atf_fs_path_fini(&p); } /* --------------------------------------------------------------------- * Main. * --------------------------------------------------------------------- */ ATF_TP_ADD_TCS(tp) { /* Add the tests for the "atf_fs_path" type. */ ATF_TP_ADD_TC(tp, path_normalize); ATF_TP_ADD_TC(tp, path_copy); ATF_TP_ADD_TC(tp, path_is_absolute); ATF_TP_ADD_TC(tp, path_is_root); ATF_TP_ADD_TC(tp, path_branch_path); ATF_TP_ADD_TC(tp, path_leaf_name); ATF_TP_ADD_TC(tp, path_append); ATF_TP_ADD_TC(tp, path_to_absolute); ATF_TP_ADD_TC(tp, path_equal); /* Add the tests for the "atf_fs_stat" type. */ ATF_TP_ADD_TC(tp, stat_mode); ATF_TP_ADD_TC(tp, stat_type); ATF_TP_ADD_TC(tp, stat_perms); /* Add the tests for the free functions. */ ATF_TP_ADD_TC(tp, eaccess); ATF_TP_ADD_TC(tp, exists); ATF_TP_ADD_TC(tp, getcwd); ATF_TP_ADD_TC(tp, rmdir_empty); ATF_TP_ADD_TC(tp, rmdir_enotempty); ATF_TP_ADD_TC(tp, rmdir_eperm); ATF_TP_ADD_TC(tp, mkdtemp_ok); ATF_TP_ADD_TC(tp, mkdtemp_err); ATF_TP_ADD_TC(tp, mkdtemp_umask); ATF_TP_ADD_TC(tp, mkstemp_ok); ATF_TP_ADD_TC(tp, mkstemp_err); ATF_TP_ADD_TC(tp, mkstemp_umask); return atf_no_error(); } diff --git a/contrib/atf/atf-c/detail/list.c b/contrib/atf/atf-c/detail/list.c index d14216eb409f..7ac9f1fc948b 100644 --- a/contrib/atf/atf-c/detail/list.c +++ b/contrib/atf/atf-c/detail/list.c @@ -1,388 +1,388 @@ /* Copyright (c) 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/detail/list.h" #include #include #include "atf-c/detail/sanity.h" #include "atf-c/error.h" #include "atf-c/utils.h" /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ struct list_entry { struct list_entry *m_prev; struct list_entry *m_next; void *m_object; bool m_managed; }; static atf_list_citer_t entry_to_citer(const atf_list_t *l, const struct list_entry *le) { atf_list_citer_t iter; iter.m_list = l; iter.m_entry = le; return iter; } static atf_list_iter_t entry_to_iter(atf_list_t *l, struct list_entry *le) { atf_list_iter_t iter; iter.m_list = l; iter.m_entry = le; return iter; } static struct list_entry * new_entry(void *object, bool managed) { struct list_entry *le; le = (struct list_entry *)malloc(sizeof(*le)); if (le != NULL) { le->m_prev = le->m_next = NULL; le->m_object = object; le->m_managed = managed; - } else + } else if (managed) free(object); return le; } static void delete_entry(struct list_entry *le) { if (le->m_managed) free(le->m_object); free(le); } static struct list_entry * new_entry_and_link(void *object, bool managed, struct list_entry *prev, struct list_entry *next) { struct list_entry *le; le = new_entry(object, managed); if (le != NULL) { le->m_prev = prev; le->m_next = next; prev->m_next = le; next->m_prev = le; } return le; } /* --------------------------------------------------------------------- * The "atf_list_citer" type. * --------------------------------------------------------------------- */ /* * Getters. */ const void * atf_list_citer_data(const atf_list_citer_t citer) { const struct list_entry *le = citer.m_entry; PRE(le != NULL); return le->m_object; } atf_list_citer_t atf_list_citer_next(const atf_list_citer_t citer) { const struct list_entry *le = citer.m_entry; atf_list_citer_t newciter; PRE(le != NULL); newciter = citer; newciter.m_entry = le->m_next; return newciter; } bool atf_equal_list_citer_list_citer(const atf_list_citer_t i1, const atf_list_citer_t i2) { return i1.m_list == i2.m_list && i1.m_entry == i2.m_entry; } /* --------------------------------------------------------------------- * The "atf_list_iter" type. * --------------------------------------------------------------------- */ /* * Getters. */ void * atf_list_iter_data(const atf_list_iter_t iter) { const struct list_entry *le = iter.m_entry; PRE(le != NULL); return le->m_object; } atf_list_iter_t atf_list_iter_next(const atf_list_iter_t iter) { const struct list_entry *le = iter.m_entry; atf_list_iter_t newiter; PRE(le != NULL); newiter = iter; newiter.m_entry = le->m_next; return newiter; } bool atf_equal_list_iter_list_iter(const atf_list_iter_t i1, const atf_list_iter_t i2) { return i1.m_list == i2.m_list && i1.m_entry == i2.m_entry; } /* --------------------------------------------------------------------- * The "atf_list" type. * --------------------------------------------------------------------- */ /* * Constructors and destructors. */ atf_error_t atf_list_init(atf_list_t *l) { struct list_entry *lebeg, *leend; lebeg = new_entry(NULL, false); if (lebeg == NULL) { return atf_no_memory_error(); } leend = new_entry(NULL, false); if (leend == NULL) { free(lebeg); return atf_no_memory_error(); } lebeg->m_next = leend; lebeg->m_prev = NULL; leend->m_next = NULL; leend->m_prev = lebeg; l->m_size = 0; l->m_begin = lebeg; l->m_end = leend; return atf_no_error(); } void atf_list_fini(atf_list_t *l) { struct list_entry *le; size_t freed; le = (struct list_entry *)l->m_begin; freed = 0; while (le != NULL) { struct list_entry *lenext; lenext = le->m_next; delete_entry(le); le = lenext; freed++; } INV(freed == l->m_size + 2); } /* * Getters. */ atf_list_iter_t atf_list_begin(atf_list_t *l) { struct list_entry *le = l->m_begin; return entry_to_iter(l, le->m_next); } atf_list_citer_t atf_list_begin_c(const atf_list_t *l) { const struct list_entry *le = l->m_begin; return entry_to_citer(l, le->m_next); } atf_list_iter_t atf_list_end(atf_list_t *l) { return entry_to_iter(l, l->m_end); } atf_list_citer_t atf_list_end_c(const atf_list_t *l) { return entry_to_citer(l, l->m_end); } void * atf_list_index(atf_list_t *list, const size_t idx) { atf_list_iter_t iter; PRE(idx < atf_list_size(list)); iter = atf_list_begin(list); { size_t pos = 0; while (pos < idx && !atf_equal_list_iter_list_iter((iter), atf_list_end(list))) { iter = atf_list_iter_next(iter); pos++; } } return atf_list_iter_data(iter); } const void * atf_list_index_c(const atf_list_t *list, const size_t idx) { atf_list_citer_t iter; PRE(idx < atf_list_size(list)); iter = atf_list_begin_c(list); { size_t pos = 0; while (pos < idx && !atf_equal_list_citer_list_citer((iter), atf_list_end_c(list))) { iter = atf_list_citer_next(iter); pos++; } } return atf_list_citer_data(iter); } size_t atf_list_size(const atf_list_t *l) { return l->m_size; } char ** atf_list_to_charpp(const atf_list_t *l) { char **array; atf_list_citer_t iter; size_t i; array = malloc(sizeof(char *) * (atf_list_size(l) + 1)); if (array == NULL) goto out; i = 0; atf_list_for_each_c(iter, l) { array[i] = strdup((const char *)atf_list_citer_data(iter)); if (array[i] == NULL) { atf_utils_free_charpp(array); array = NULL; goto out; } i++; } array[i] = NULL; out: return array; } /* * Modifiers. */ atf_error_t atf_list_append(atf_list_t *l, void *data, bool managed) { struct list_entry *le, *next, *prev; atf_error_t err; next = (struct list_entry *)l->m_end; prev = next->m_prev; le = new_entry_and_link(data, managed, prev, next); if (le == NULL) err = atf_no_memory_error(); else { l->m_size++; err = atf_no_error(); } return err; } void atf_list_append_list(atf_list_t *l, atf_list_t *src) { struct list_entry *e1, *e2, *ghost1, *ghost2; ghost1 = (struct list_entry *)l->m_end; ghost2 = (struct list_entry *)src->m_begin; e1 = ghost1->m_prev; e2 = ghost2->m_next; delete_entry(ghost1); delete_entry(ghost2); e1->m_next = e2; e2->m_prev = e1; l->m_end = src->m_end; l->m_size += src->m_size; } diff --git a/contrib/atf/atf-c/detail/process.c b/contrib/atf/atf-c/detail/process.c index 8e08b6c57466..a6189bf78e20 100644 --- a/contrib/atf/atf-c/detail/process.c +++ b/contrib/atf/atf-c/detail/process.c @@ -1,670 +1,671 @@ /* Copyright (c) 2007 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/detail/process.h" #include #include #include #include +#include #include #include #include #include #include "atf-c/defs.h" #include "atf-c/detail/sanity.h" #include "atf-c/error.h" /* This prototype is not in the header file because this is a private * function; however, we need to access it during testing. */ atf_error_t atf_process_status_init(atf_process_status_t *, int); /* --------------------------------------------------------------------- * The "stream_prepare" auxiliary type. * --------------------------------------------------------------------- */ struct stream_prepare { const atf_process_stream_t *m_sb; bool m_pipefds_ok; int m_pipefds[2]; }; typedef struct stream_prepare stream_prepare_t; static atf_error_t stream_prepare_init(stream_prepare_t *sp, const atf_process_stream_t *sb) { atf_error_t err; const int type = atf_process_stream_type(sb); sp->m_sb = sb; sp->m_pipefds_ok = false; if (type == atf_process_stream_type_capture) { if (pipe(sp->m_pipefds) == -1) err = atf_libc_error(errno, "Failed to create pipe"); else { err = atf_no_error(); sp->m_pipefds_ok = true; } } else err = atf_no_error(); return err; } static void stream_prepare_fini(stream_prepare_t *sp) { if (sp->m_pipefds_ok) { close(sp->m_pipefds[0]); close(sp->m_pipefds[1]); } } /* --------------------------------------------------------------------- * The "atf_process_stream" type. * --------------------------------------------------------------------- */ const int atf_process_stream_type_capture = 1; const int atf_process_stream_type_connect = 2; const int atf_process_stream_type_inherit = 3; const int atf_process_stream_type_redirect_fd = 4; const int atf_process_stream_type_redirect_path = 5; static bool stream_is_valid(const atf_process_stream_t *sb) { return (sb->m_type == atf_process_stream_type_capture) || (sb->m_type == atf_process_stream_type_connect) || (sb->m_type == atf_process_stream_type_inherit) || (sb->m_type == atf_process_stream_type_redirect_fd) || (sb->m_type == atf_process_stream_type_redirect_path); } atf_error_t atf_process_stream_init_capture(atf_process_stream_t *sb) { sb->m_type = atf_process_stream_type_capture; POST(stream_is_valid(sb)); return atf_no_error(); } atf_error_t atf_process_stream_init_connect(atf_process_stream_t *sb, const int src_fd, const int tgt_fd) { PRE(src_fd >= 0); PRE(tgt_fd >= 0); PRE(src_fd != tgt_fd); sb->m_type = atf_process_stream_type_connect; sb->m_src_fd = src_fd; sb->m_tgt_fd = tgt_fd; POST(stream_is_valid(sb)); return atf_no_error(); } atf_error_t atf_process_stream_init_inherit(atf_process_stream_t *sb) { sb->m_type = atf_process_stream_type_inherit; POST(stream_is_valid(sb)); return atf_no_error(); } atf_error_t atf_process_stream_init_redirect_fd(atf_process_stream_t *sb, const int fd) { sb->m_type = atf_process_stream_type_redirect_fd; sb->m_fd = fd; POST(stream_is_valid(sb)); return atf_no_error(); } atf_error_t atf_process_stream_init_redirect_path(atf_process_stream_t *sb, const atf_fs_path_t *path) { sb->m_type = atf_process_stream_type_redirect_path; sb->m_path = path; POST(stream_is_valid(sb)); return atf_no_error(); } void atf_process_stream_fini(atf_process_stream_t *sb) { PRE(stream_is_valid(sb)); } int atf_process_stream_type(const atf_process_stream_t *sb) { PRE(stream_is_valid(sb)); return sb->m_type; } /* --------------------------------------------------------------------- * The "atf_process_status" type. * --------------------------------------------------------------------- */ atf_error_t atf_process_status_init(atf_process_status_t *s, int status) { s->m_status = status; return atf_no_error(); } void atf_process_status_fini(atf_process_status_t *s ATF_DEFS_ATTRIBUTE_UNUSED) { } bool atf_process_status_exited(const atf_process_status_t *s) { int mutable_status = s->m_status; return WIFEXITED(mutable_status); } int atf_process_status_exitstatus(const atf_process_status_t *s) { PRE(atf_process_status_exited(s)); int mutable_status = s->m_status; return WEXITSTATUS(mutable_status); } bool atf_process_status_signaled(const atf_process_status_t *s) { int mutable_status = s->m_status; return WIFSIGNALED(mutable_status); } int atf_process_status_termsig(const atf_process_status_t *s) { PRE(atf_process_status_signaled(s)); int mutable_status = s->m_status; return WTERMSIG(mutable_status); } bool atf_process_status_coredump(const atf_process_status_t *s) { PRE(atf_process_status_signaled(s)); #if defined(WCOREDUMP) int mutable_status = s->m_status; return WCOREDUMP(mutable_status); #else return false; #endif } /* --------------------------------------------------------------------- * The "atf_process_child" type. * --------------------------------------------------------------------- */ static atf_error_t atf_process_child_init(atf_process_child_t *c) { c->m_pid = 0; c->m_stdout = -1; c->m_stderr = -1; return atf_no_error(); } static void atf_process_child_fini(atf_process_child_t *c) { if (c->m_stdout != -1) close(c->m_stdout); if (c->m_stderr != -1) close(c->m_stderr); } atf_error_t atf_process_child_wait(atf_process_child_t *c, atf_process_status_t *s) { atf_error_t err; int status; if (waitpid(c->m_pid, &status, 0) == -1) err = atf_libc_error(errno, "Failed waiting for process %d", c->m_pid); else { atf_process_child_fini(c); err = atf_process_status_init(s, status); } return err; } pid_t atf_process_child_pid(const atf_process_child_t *c) { return c->m_pid; } int atf_process_child_stdout(atf_process_child_t *c) { PRE(c->m_stdout != -1); return c->m_stdout; } int atf_process_child_stderr(atf_process_child_t *c) { PRE(c->m_stderr != -1); return c->m_stderr; } /* --------------------------------------------------------------------- * Free functions. * --------------------------------------------------------------------- */ static atf_error_t safe_dup(const int oldfd, const int newfd) { atf_error_t err; if (oldfd != newfd) { if (dup2(oldfd, newfd) == -1) { err = atf_libc_error(errno, "Could not allocate file descriptor"); } else { close(oldfd); err = atf_no_error(); } } else err = atf_no_error(); return err; } static atf_error_t child_connect(const stream_prepare_t *sp, int procfd) { atf_error_t err; const int type = atf_process_stream_type(sp->m_sb); if (type == atf_process_stream_type_capture) { close(sp->m_pipefds[0]); err = safe_dup(sp->m_pipefds[1], procfd); } else if (type == atf_process_stream_type_connect) { if (dup2(sp->m_sb->m_tgt_fd, sp->m_sb->m_src_fd) == -1) err = atf_libc_error(errno, "Cannot connect descriptor %d to %d", sp->m_sb->m_tgt_fd, sp->m_sb->m_src_fd); else err = atf_no_error(); } else if (type == atf_process_stream_type_inherit) { err = atf_no_error(); } else if (type == atf_process_stream_type_redirect_fd) { err = safe_dup(sp->m_sb->m_fd, procfd); } else if (type == atf_process_stream_type_redirect_path) { int aux = open(atf_fs_path_cstring(sp->m_sb->m_path), O_WRONLY | O_CREAT | O_TRUNC, 0644); if (aux == -1) err = atf_libc_error(errno, "Could not create %s", atf_fs_path_cstring(sp->m_sb->m_path)); else { err = safe_dup(aux, procfd); if (atf_is_error(err)) close(aux); } } else { UNREACHABLE; err = atf_no_error(); } return err; } static void parent_connect(const stream_prepare_t *sp, int *fd) { const int type = atf_process_stream_type(sp->m_sb); if (type == atf_process_stream_type_capture) { close(sp->m_pipefds[1]); *fd = sp->m_pipefds[0]; } else if (type == atf_process_stream_type_connect) { /* Do nothing. */ } else if (type == atf_process_stream_type_inherit) { /* Do nothing. */ } else if (type == atf_process_stream_type_redirect_fd) { /* Do nothing. */ } else if (type == atf_process_stream_type_redirect_path) { /* Do nothing. */ } else { UNREACHABLE; } } static atf_error_t do_parent(atf_process_child_t *c, const pid_t pid, const stream_prepare_t *outsp, const stream_prepare_t *errsp) { atf_error_t err; err = atf_process_child_init(c); if (atf_is_error(err)) goto out; c->m_pid = pid; parent_connect(outsp, &c->m_stdout); parent_connect(errsp, &c->m_stderr); out: return err; } static void do_child(void (*)(void *), void *, const stream_prepare_t *, const stream_prepare_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void do_child(void (*start)(void *), void *v, const stream_prepare_t *outsp, const stream_prepare_t *errsp) { atf_error_t err; err = child_connect(outsp, STDOUT_FILENO); if (atf_is_error(err)) goto out; err = child_connect(errsp, STDERR_FILENO); if (atf_is_error(err)) goto out; start(v); UNREACHABLE; out: if (atf_is_error(err)) { char buf[1024]; atf_error_format(err, buf, sizeof(buf)); fprintf(stderr, "Unhandled error: %s\n", buf); atf_error_free(err); exit(EXIT_FAILURE); } else exit(EXIT_SUCCESS); } static atf_error_t fork_with_streams(atf_process_child_t *c, void (*start)(void *), const atf_process_stream_t *outsb, const atf_process_stream_t *errsb, void *v) { atf_error_t err; stream_prepare_t outsp; stream_prepare_t errsp; pid_t pid; err = stream_prepare_init(&outsp, outsb); if (atf_is_error(err)) goto out; err = stream_prepare_init(&errsp, errsb); if (atf_is_error(err)) goto err_outpipe; pid = fork(); if (pid == -1) { err = atf_libc_error(errno, "Failed to fork"); goto err_errpipe; } if (pid == 0) { do_child(start, v, &outsp, &errsp); UNREACHABLE; abort(); err = atf_no_error(); } else { err = do_parent(c, pid, &outsp, &errsp); if (atf_is_error(err)) goto err_errpipe; } goto out; err_errpipe: stream_prepare_fini(&errsp); err_outpipe: stream_prepare_fini(&outsp); out: return err; } static atf_error_t init_stream_w_default(const atf_process_stream_t *usersb, atf_process_stream_t *inheritsb, const atf_process_stream_t **realsb) { atf_error_t err; if (usersb == NULL) { err = atf_process_stream_init_inherit(inheritsb); if (!atf_is_error(err)) *realsb = inheritsb; } else { err = atf_no_error(); *realsb = usersb; } return err; } atf_error_t atf_process_fork(atf_process_child_t *c, void (*start)(void *), const atf_process_stream_t *outsb, const atf_process_stream_t *errsb, void *v) { atf_error_t err; atf_process_stream_t inherit_outsb, inherit_errsb; const atf_process_stream_t *real_outsb, *real_errsb; real_outsb = NULL; /* Shut up GCC warning. */ err = init_stream_w_default(outsb, &inherit_outsb, &real_outsb); if (atf_is_error(err)) goto out; real_errsb = NULL; /* Shut up GCC warning. */ err = init_stream_w_default(errsb, &inherit_errsb, &real_errsb); if (atf_is_error(err)) goto out_out; err = fork_with_streams(c, start, real_outsb, real_errsb, v); if (errsb == NULL) atf_process_stream_fini(&inherit_errsb); out_out: if (outsb == NULL) atf_process_stream_fini(&inherit_outsb); out: return err; } static int const_execvp(const char *file, const char *const *argv) { -#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) return execvp(file, UNCONST(argv)); #undef UNCONST } static atf_error_t list_to_array(const atf_list_t *l, const char ***ap) { atf_error_t err; const char **a; a = (const char **)malloc((atf_list_size(l) + 1) * sizeof(const char *)); if (a == NULL) err = atf_no_memory_error(); else { const char **aiter; atf_list_citer_t liter; aiter = a; atf_list_for_each_c(liter, l) { *aiter = (const char *)atf_list_citer_data(liter); aiter++; } *aiter = NULL; err = atf_no_error(); *ap = a; } return err; } struct exec_args { const atf_fs_path_t *m_prog; const char *const *m_argv; void (*m_prehook)(void); }; static void do_exec(void *v) { struct exec_args *ea = v; if (ea->m_prehook != NULL) ea->m_prehook(); const int ret = const_execvp(atf_fs_path_cstring(ea->m_prog), ea->m_argv); const int errnocopy = errno; INV(ret == -1); fprintf(stderr, "exec(%s) failed: %s\n", atf_fs_path_cstring(ea->m_prog), strerror(errnocopy)); exit(EXIT_FAILURE); } atf_error_t atf_process_exec_array(atf_process_status_t *s, const atf_fs_path_t *prog, const char *const *argv, const atf_process_stream_t *outsb, const atf_process_stream_t *errsb, void (*prehook)(void)) { atf_error_t err; atf_process_child_t c; struct exec_args ea = { prog, argv, prehook }; PRE(outsb == NULL || atf_process_stream_type(outsb) != atf_process_stream_type_capture); PRE(errsb == NULL || atf_process_stream_type(errsb) != atf_process_stream_type_capture); err = atf_process_fork(&c, do_exec, outsb, errsb, &ea); if (atf_is_error(err)) goto out; again: err = atf_process_child_wait(&c, s); if (atf_is_error(err)) { INV(atf_error_is(err, "libc") && atf_libc_error_code(err) == EINTR); atf_error_free(err); goto again; } out: return err; } atf_error_t atf_process_exec_list(atf_process_status_t *s, const atf_fs_path_t *prog, const atf_list_t *argv, const atf_process_stream_t *outsb, const atf_process_stream_t *errsb, void (*prehook)(void)) { atf_error_t err; const char **argv2; PRE(outsb == NULL || atf_process_stream_type(outsb) != atf_process_stream_type_capture); PRE(errsb == NULL || atf_process_stream_type(errsb) != atf_process_stream_type_capture); argv2 = NULL; /* Silence GCC warning. */ err = list_to_array(argv, &argv2); if (atf_is_error(err)) goto out; err = atf_process_exec_array(s, prog, argv2, outsb, errsb, prehook); free(argv2); out: return err; } diff --git a/contrib/atf/atf-c/detail/test_helpers.h b/contrib/atf/atf-c/detail/test_helpers.h index a601c293ffe4..90841f803c59 100644 --- a/contrib/atf/atf-c/detail/test_helpers.h +++ b/contrib/atf/atf-c/detail/test_helpers.h @@ -1,75 +1,82 @@ /* Copyright (c) 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(ATF_C_DETAIL_TEST_HELPERS_H) # error "Cannot include test_helpers.h more than once." #else # define ATF_C_DETAIL_TEST_HELPERS_H #endif #include #include +#include #include #include struct atf_dynstr; struct atf_fs_path; #define CE(stm) ATF_CHECK(!atf_is_error(stm)) #define RE(stm) ATF_REQUIRE(!atf_is_error(stm)) #define HEADER_TC(name, hdrname) \ ATF_TC(name); \ ATF_TC_HEAD(name, tc) \ { \ + const char *cc; \ atf_tc_set_md_var(tc, "descr", "Tests that the " hdrname " file can " \ "be included on its own, without any prerequisites"); \ + cc = atf_env_get_with_default("ATF_BUILD_CC", ATF_BUILD_CC); \ + atf_tc_set_md_var(tc, "require.progs", cc); \ } \ ATF_TC_BODY(name, tc) \ { \ header_check(hdrname); \ } #define BUILD_TC(name, sfile, descr, failmsg) \ ATF_TC(name); \ ATF_TC_HEAD(name, tc) \ { \ + const char *cc; \ atf_tc_set_md_var(tc, "descr", descr); \ + cc = atf_env_get_with_default("ATF_BUILD_CC", ATF_BUILD_CC); \ + atf_tc_set_md_var(tc, "require.progs", cc); \ } \ ATF_TC_BODY(name, tc) \ { \ if (!build_check_c_o_srcdir(tc, sfile)) \ atf_tc_fail("%s", failmsg); \ } bool build_check_c_o(const char *); bool build_check_c_o_srcdir(const atf_tc_t *, const char *); void header_check(const char *); void get_process_helpers_path(const atf_tc_t *, const bool, struct atf_fs_path *); bool read_line(int, struct atf_dynstr *); void run_h_tc(atf_tc_t *, const char *, const char *, const char *); diff --git a/contrib/atf/atf-c/tc.c b/contrib/atf/atf-c/tc.c index 92c3e12c99b1..69b31123f3a3 100644 --- a/contrib/atf/atf-c/tc.c +++ b/contrib/atf/atf-c/tc.c @@ -1,1217 +1,1279 @@ /* Copyright (c) 2008 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/tc.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include "atf-c/defs.h" #include "atf-c/detail/env.h" #include "atf-c/detail/fs.h" #include "atf-c/detail/map.h" #include "atf-c/detail/sanity.h" #include "atf-c/detail/text.h" #include "atf-c/error.h" /* --------------------------------------------------------------------- * Auxiliary functions. * --------------------------------------------------------------------- */ enum expect_type { EXPECT_PASS, EXPECT_FAIL, EXPECT_EXIT, EXPECT_SIGNAL, EXPECT_DEATH, EXPECT_TIMEOUT, }; struct context { const atf_tc_t *tc; const char *resfile; + int resfilefd; size_t fail_count; enum expect_type expect; atf_dynstr_t expect_reason; size_t expect_previous_fail_count; size_t expect_fail_count; int expect_exitcode; int expect_signo; }; static void context_init(struct context *, const atf_tc_t *, const char *); +static void context_set_resfile(struct context *, const char *); +static void context_close_resfile(struct context *); static void check_fatal_error(atf_error_t); static void report_fatal_error(const char *, ...) ATF_DEFS_ATTRIBUTE_NORETURN; static atf_error_t write_resfile(const int, const char *, const int, const atf_dynstr_t *); -static void create_resfile(const char *, const char *, const int, +static void create_resfile(struct context *, const char *, const int, atf_dynstr_t *); static void error_in_expect(struct context *, const char *, ...) ATF_DEFS_ATTRIBUTE_NORETURN; static void validate_expect(struct context *); static void expected_failure(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail_requirement(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void fail_check(struct context *, atf_dynstr_t *); static void pass(struct context *) ATF_DEFS_ATTRIBUTE_NORETURN; static void skip(struct context *, atf_dynstr_t *) ATF_DEFS_ATTRIBUTE_NORETURN; static void format_reason_ap(atf_dynstr_t *, const char *, const size_t, const char *, va_list); static void format_reason_fmt(atf_dynstr_t *, const char *, const size_t, const char *, ...); static void errno_test(struct context *, const char *, const size_t, const int, const char *, const bool, void (*)(struct context *, atf_dynstr_t *)); static atf_error_t check_prog_in_dir(const char *, void *); static atf_error_t check_prog(struct context *, const char *); +/* No prototype in header for this one, it's a little sketchy (internal). */ +void atf_tc_set_resultsfile(const char *); + static void context_init(struct context *ctx, const atf_tc_t *tc, const char *resfile) { + ctx->tc = tc; - ctx->resfile = resfile; + ctx->resfilefd = -1; + context_set_resfile(ctx, resfile); ctx->fail_count = 0; ctx->expect = EXPECT_PASS; check_fatal_error(atf_dynstr_init(&ctx->expect_reason)); ctx->expect_previous_fail_count = 0; ctx->expect_fail_count = 0; ctx->expect_exitcode = 0; ctx->expect_signo = 0; } +static void +context_set_resfile(struct context *ctx, const char *resfile) +{ + atf_error_t err; + + context_close_resfile(ctx); + ctx->resfile = resfile; + if (strcmp(resfile, "/dev/stdout") == 0) + ctx->resfilefd = STDOUT_FILENO; + else if (strcmp(resfile, "/dev/stderr") == 0) + ctx->resfilefd = STDERR_FILENO; + else + ctx->resfilefd = open(resfile, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (ctx->resfilefd == -1) { + err = atf_libc_error(errno, + "Cannot create results file '%s'", resfile); + check_fatal_error(err); + } + + ctx->resfile = resfile; +} + +static void +context_close_resfile(struct context *ctx) +{ + + if (ctx->resfilefd == -1) + return; + if (ctx->resfilefd != STDOUT_FILENO && ctx->resfilefd != STDERR_FILENO) + close(ctx->resfilefd); + ctx->resfilefd = -1; + ctx->resfile = NULL; +} + static void check_fatal_error(atf_error_t err) { if (atf_is_error(err)) { char buf[1024]; atf_error_format(err, buf, sizeof(buf)); fprintf(stderr, "FATAL ERROR: %s\n", buf); atf_error_free(err); abort(); } } static void report_fatal_error(const char *msg, ...) { va_list ap; fprintf(stderr, "FATAL ERROR: "); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); fprintf(stderr, "\n"); abort(); } /** Writes to a results file. * * The results file is supposed to be already open. * * This function returns an error code instead of exiting in case of error * because the caller needs to clean up the reason object before terminating. */ static atf_error_t write_resfile(const int fd, const char *result, const int arg, const atf_dynstr_t *reason) { static char NL[] = "\n", CS[] = ": "; char buf[64]; const char *r; struct iovec iov[5]; ssize_t ret; int count = 0; INV(arg == -1 || reason != NULL); -#define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) +#define UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) iov[count].iov_base = UNCONST(result); iov[count++].iov_len = strlen(result); if (reason != NULL) { if (arg != -1) { iov[count].iov_base = buf; iov[count++].iov_len = snprintf(buf, sizeof(buf), "(%d)", arg); } iov[count].iov_base = CS; iov[count++].iov_len = sizeof(CS) - 1; r = atf_dynstr_cstring(reason); iov[count].iov_base = UNCONST(r); iov[count++].iov_len = strlen(r); } #undef UNCONST iov[count].iov_base = NL; iov[count++].iov_len = sizeof(NL) - 1; while ((ret = writev(fd, iov, count)) == -1 && errno == EINTR) continue; /* Retry. */ if (ret != -1) return atf_no_error(); return atf_libc_error( errno, "Failed to write results file; result %s, reason %s", result, reason == NULL ? "null" : atf_dynstr_cstring(reason)); } /** Creates a results file. * * The input reason is released in all cases. * * An error in this function is considered to be fatal, hence why it does * not return any error code. */ static void -create_resfile(const char *resfile, const char *result, const int arg, +create_resfile(struct context *ctx, const char *result, const int arg, atf_dynstr_t *reason) { atf_error_t err; - if (strcmp("/dev/stdout", resfile) == 0) { - err = write_resfile(STDOUT_FILENO, result, arg, reason); - } else if (strcmp("/dev/stderr", resfile) == 0) { - err = write_resfile(STDERR_FILENO, result, arg, reason); - } else { - const int fd = open(resfile, O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (fd == -1) { - err = atf_libc_error(errno, "Cannot create results file '%s'", - resfile); - } else { - err = write_resfile(fd, result, arg, reason); - close(fd); - } - } + /* + * We'll attempt to truncate the results file, but only if it's not pointed + * at stdout/stderr. We could just blindly ftruncate() here, but it may + * be that stdout/stderr have been redirected to a file that we want to + * validate expectations on, for example. Kyua will want the truncation, + * but it will also redirect the results directly to some file and we'll + * have no issue here. + */ + if (ctx->resfilefd != STDOUT_FILENO && ctx->resfilefd != STDERR_FILENO && + ftruncate(ctx->resfilefd, 0) != -1) + lseek(ctx->resfilefd, 0, SEEK_SET); + err = write_resfile(ctx->resfilefd, result, arg, reason); if (reason != NULL) atf_dynstr_fini(reason); check_fatal_error(err); } /** Fails a test case if validate_expect fails. */ static void error_in_expect(struct context *ctx, const char *fmt, ...) { atf_dynstr_t reason; va_list ap; va_start(ap, fmt); format_reason_ap(&reason, NULL, 0, fmt, ap); va_end(ap); ctx->expect = EXPECT_PASS; /* Ensure fail_requirement really fails. */ fail_requirement(ctx, &reason); } /** Ensures that the "expect" state is correct. * * Call this function before modifying the current value of expect. */ static void validate_expect(struct context *ctx) { if (ctx->expect == EXPECT_DEATH) { error_in_expect(ctx, "Test case was expected to terminate abruptly " "but it continued execution"); } else if (ctx->expect == EXPECT_EXIT) { error_in_expect(ctx, "Test case was expected to exit cleanly but it " "continued execution"); } else if (ctx->expect == EXPECT_FAIL) { if (ctx->expect_fail_count == ctx->expect_previous_fail_count) error_in_expect(ctx, "Test case was expecting a failure but none " "were raised"); else INV(ctx->expect_fail_count > ctx->expect_previous_fail_count); } else if (ctx->expect == EXPECT_PASS) { /* Nothing to validate. */ } else if (ctx->expect == EXPECT_SIGNAL) { error_in_expect(ctx, "Test case was expected to receive a termination " "signal but it continued execution"); } else if (ctx->expect == EXPECT_TIMEOUT) { error_in_expect(ctx, "Test case was expected to hang but it continued " "execution"); } else UNREACHABLE; } static void expected_failure(struct context *ctx, atf_dynstr_t *reason) { check_fatal_error(atf_dynstr_prepend_fmt(reason, "%s: ", atf_dynstr_cstring(&ctx->expect_reason))); - create_resfile(ctx->resfile, "expected_failure", -1, reason); + create_resfile(ctx, "expected_failure", -1, reason); + context_close_resfile(ctx); exit(EXIT_SUCCESS); } static void fail_requirement(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_FAIL) { expected_failure(ctx, reason); } else if (ctx->expect == EXPECT_PASS) { - create_resfile(ctx->resfile, "failed", -1, reason); + create_resfile(ctx, "failed", -1, reason); + context_close_resfile(ctx); exit(EXIT_FAILURE); } else { error_in_expect(ctx, "Test case raised a failure but was not " "expecting one; reason was %s", atf_dynstr_cstring(reason)); } UNREACHABLE; } static void fail_check(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_FAIL) { fprintf(stderr, "*** Expected check failure: %s: %s\n", atf_dynstr_cstring(&ctx->expect_reason), atf_dynstr_cstring(reason)); ctx->expect_fail_count++; } else if (ctx->expect == EXPECT_PASS) { fprintf(stderr, "*** Check failed: %s\n", atf_dynstr_cstring(reason)); ctx->fail_count++; } else { error_in_expect(ctx, "Test case raised a failure but was not " "expecting one; reason was %s", atf_dynstr_cstring(reason)); } atf_dynstr_fini(reason); } static void pass(struct context *ctx) { if (ctx->expect == EXPECT_FAIL) { error_in_expect(ctx, "Test case was expecting a failure but got " "a pass instead"); } else if (ctx->expect == EXPECT_PASS) { - create_resfile(ctx->resfile, "passed", -1, NULL); + create_resfile(ctx, "passed", -1, NULL); + context_close_resfile(ctx); exit(EXIT_SUCCESS); } else { error_in_expect(ctx, "Test case asked to explicitly pass but was " "not expecting such condition"); } UNREACHABLE; } static void skip(struct context *ctx, atf_dynstr_t *reason) { if (ctx->expect == EXPECT_PASS) { - create_resfile(ctx->resfile, "skipped", -1, reason); + create_resfile(ctx, "skipped", -1, reason); + context_close_resfile(ctx); exit(EXIT_SUCCESS); } else { error_in_expect(ctx, "Can only skip a test case when running in " "expect pass mode"); } UNREACHABLE; } /** Formats a failure/skip reason message. * * The formatted reason is stored in out_reason. out_reason is initialized * in this function and is supposed to be released by the caller. In general, * the reason will eventually be fed to create_resfile, which will release * it. * * Errors in this function are fatal. Rationale being: reasons are used to * create results files; if we can't format the reason correctly, the result * of the test program will be bogus. So it's better to just exit with a * fatal error. */ static void format_reason_ap(atf_dynstr_t *out_reason, const char *source_file, const size_t source_line, const char *reason, va_list ap) { atf_error_t err; if (source_file != NULL) { err = atf_dynstr_init_fmt(out_reason, "%s:%zd: ", source_file, source_line); } else { PRE(source_line == 0); err = atf_dynstr_init(out_reason); } if (!atf_is_error(err)) { va_list ap2; va_copy(ap2, ap); err = atf_dynstr_append_ap(out_reason, reason, ap2); va_end(ap2); } check_fatal_error(err); } static void format_reason_fmt(atf_dynstr_t *out_reason, const char *source_file, const size_t source_line, const char *reason, ...) { va_list ap; va_start(ap, reason); format_reason_ap(out_reason, source_file, source_line, reason, ap); va_end(ap); } static void errno_test(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result, void (*fail_func)(struct context *, atf_dynstr_t *)) { const int actual_errno = errno; if (expr_result) { if (exp_errno != actual_errno) { atf_dynstr_t reason; format_reason_fmt(&reason, file, line, "Expected errno %d, got %d, " "in %s", exp_errno, actual_errno, expr_str); fail_func(ctx, &reason); } } else { atf_dynstr_t reason; format_reason_fmt(&reason, file, line, "Expected true value in %s", expr_str); fail_func(ctx, &reason); } } struct prog_found_pair { const char *prog; bool found; }; static atf_error_t check_prog_in_dir(const char *dir, void *data) { struct prog_found_pair *pf = data; atf_error_t err; if (pf->found) err = atf_no_error(); else { atf_fs_path_t p; err = atf_fs_path_init_fmt(&p, "%s/%s", dir, pf->prog); if (atf_is_error(err)) goto out_p; err = atf_fs_eaccess(&p, atf_fs_access_x); if (!atf_is_error(err)) pf->found = true; else { atf_error_free(err); INV(!pf->found); err = atf_no_error(); } out_p: atf_fs_path_fini(&p); } return err; } static atf_error_t check_prog(struct context *ctx, const char *prog) { atf_error_t err; atf_fs_path_t p; err = atf_fs_path_init_fmt(&p, "%s", prog); if (atf_is_error(err)) goto out; if (atf_fs_path_is_absolute(&p)) { err = atf_fs_eaccess(&p, atf_fs_access_x); if (atf_is_error(err)) { atf_dynstr_t reason; atf_error_free(err); atf_fs_path_fini(&p); format_reason_fmt(&reason, NULL, 0, "The required program %s could " "not be found", prog); skip(ctx, &reason); } } else { const char *path = atf_env_get("PATH"); struct prog_found_pair pf; atf_fs_path_t bp; err = atf_fs_path_branch_path(&p, &bp); if (atf_is_error(err)) goto out_p; if (strcmp(atf_fs_path_cstring(&bp), ".") != 0) { atf_fs_path_fini(&bp); atf_fs_path_fini(&p); report_fatal_error("Relative paths are not allowed when searching " "for a program (%s)", prog); UNREACHABLE; } pf.prog = prog; pf.found = false; err = atf_text_for_each_word(path, ":", check_prog_in_dir, &pf); if (atf_is_error(err)) goto out_bp; if (!pf.found) { atf_dynstr_t reason; atf_fs_path_fini(&bp); atf_fs_path_fini(&p); format_reason_fmt(&reason, NULL, 0, "The required program %s could " "not be found in the PATH", prog); fail_requirement(ctx, &reason); } out_bp: atf_fs_path_fini(&bp); } out_p: atf_fs_path_fini(&p); out: return err; } /* --------------------------------------------------------------------- * The "atf_tc" type. * --------------------------------------------------------------------- */ struct atf_tc_impl { const char *m_ident; atf_map_t m_vars; atf_map_t m_config; atf_tc_head_t m_head; atf_tc_body_t m_body; atf_tc_cleanup_t m_cleanup; }; /* * Constructors/destructors. */ atf_error_t atf_tc_init(atf_tc_t *tc, const char *ident, atf_tc_head_t head, atf_tc_body_t body, atf_tc_cleanup_t cleanup, const char *const *config) { atf_error_t err; tc->pimpl = malloc(sizeof(struct atf_tc_impl)); if (tc->pimpl == NULL) { err = atf_no_memory_error(); goto err; } tc->pimpl->m_ident = ident; tc->pimpl->m_head = head; tc->pimpl->m_body = body; tc->pimpl->m_cleanup = cleanup; err = atf_map_init_charpp(&tc->pimpl->m_config, config); if (atf_is_error(err)) goto err; err = atf_map_init(&tc->pimpl->m_vars); if (atf_is_error(err)) goto err_vars; err = atf_tc_set_md_var(tc, "ident", ident); if (atf_is_error(err)) goto err_map; if (cleanup != NULL) { err = atf_tc_set_md_var(tc, "has.cleanup", "true"); if (atf_is_error(err)) goto err_map; } /* XXX Should the head be able to return error codes? */ if (tc->pimpl->m_head != NULL) tc->pimpl->m_head(tc); if (strcmp(atf_tc_get_md_var(tc, "ident"), ident) != 0) { report_fatal_error("Test case head modified the read-only 'ident' " "property"); UNREACHABLE; } INV(!atf_is_error(err)); return err; err_map: atf_map_fini(&tc->pimpl->m_vars); err_vars: atf_map_fini(&tc->pimpl->m_config); err: return err; } atf_error_t atf_tc_init_pack(atf_tc_t *tc, const atf_tc_pack_t *pack, const char *const *config) { return atf_tc_init(tc, pack->m_ident, pack->m_head, pack->m_body, pack->m_cleanup, config); } void atf_tc_fini(atf_tc_t *tc) { atf_map_fini(&tc->pimpl->m_vars); free(tc->pimpl); } /* * Getters. */ const char * atf_tc_get_ident(const atf_tc_t *tc) { return tc->pimpl->m_ident; } const char * atf_tc_get_config_var(const atf_tc_t *tc, const char *name) { const char *val; atf_map_citer_t iter; PRE(atf_tc_has_config_var(tc, name)); iter = atf_map_find_c(&tc->pimpl->m_config, name); val = atf_map_citer_data(iter); INV(val != NULL); return val; } const char * atf_tc_get_config_var_wd(const atf_tc_t *tc, const char *name, const char *defval) { const char *val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var(tc, name); return val; } bool atf_tc_get_config_var_as_bool(const atf_tc_t *tc, const char *name) { bool val; const char *strval; atf_error_t err; strval = atf_tc_get_config_var(tc, name); err = atf_text_to_bool(strval, &val); if (atf_is_error(err)) { atf_error_free(err); atf_tc_fail("Configuration variable %s does not have a valid " "boolean value; found %s", name, strval); } return val; } bool atf_tc_get_config_var_as_bool_wd(const atf_tc_t *tc, const char *name, const bool defval) { bool val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var_as_bool(tc, name); return val; } long atf_tc_get_config_var_as_long(const atf_tc_t *tc, const char *name) { long val; const char *strval; atf_error_t err; strval = atf_tc_get_config_var(tc, name); err = atf_text_to_long(strval, &val); if (atf_is_error(err)) { atf_error_free(err); atf_tc_fail("Configuration variable %s does not have a valid " "long value; found %s", name, strval); } return val; } long atf_tc_get_config_var_as_long_wd(const atf_tc_t *tc, const char *name, const long defval) { long val; if (!atf_tc_has_config_var(tc, name)) val = defval; else val = atf_tc_get_config_var_as_long(tc, name); return val; } const char * atf_tc_get_md_var(const atf_tc_t *tc, const char *name) { const char *val; atf_map_citer_t iter; PRE(atf_tc_has_md_var(tc, name)); iter = atf_map_find_c(&tc->pimpl->m_vars, name); val = atf_map_citer_data(iter); INV(val != NULL); return val; } char ** atf_tc_get_md_vars(const atf_tc_t *tc) { return atf_map_to_charpp(&tc->pimpl->m_vars); } bool atf_tc_has_config_var(const atf_tc_t *tc, const char *name) { atf_map_citer_t end, iter; iter = atf_map_find_c(&tc->pimpl->m_config, name); end = atf_map_end_c(&tc->pimpl->m_config); return !atf_equal_map_citer_map_citer(iter, end); } bool atf_tc_has_md_var(const atf_tc_t *tc, const char *name) { atf_map_citer_t end, iter; iter = atf_map_find_c(&tc->pimpl->m_vars, name); end = atf_map_end_c(&tc->pimpl->m_vars); return !atf_equal_map_citer_map_citer(iter, end); } /* * Modifiers. */ atf_error_t atf_tc_set_md_var(atf_tc_t *tc, const char *name, const char *fmt, ...) { atf_error_t err; char *value; va_list ap; va_start(ap, fmt); err = atf_text_format_ap(&value, fmt, ap); va_end(ap); if (!atf_is_error(err)) err = atf_map_insert(&tc->pimpl->m_vars, name, value, true); else free(value); return err; } /* --------------------------------------------------------------------- * Free functions, as they should be publicly but they can't. * --------------------------------------------------------------------- */ static void _atf_tc_fail(struct context *, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_fail_nonfatal(struct context *, const char *, va_list); static void _atf_tc_fail_check(struct context *, const char *, const size_t, const char *, va_list); static void _atf_tc_fail_requirement(struct context *, const char *, const size_t, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_pass(struct context *) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_require_prog(struct context *, const char *); static void _atf_tc_skip(struct context *, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN; static void _atf_tc_check_errno(struct context *, const char *, const size_t, const int, const char *, const bool); static void _atf_tc_require_errno(struct context *, const char *, const size_t, const int, const char *, const bool); static void _atf_tc_expect_pass(struct context *); static void _atf_tc_expect_fail(struct context *, const char *, va_list); static void _atf_tc_expect_exit(struct context *, const int, const char *, va_list); static void _atf_tc_expect_signal(struct context *, const int, const char *, va_list); static void _atf_tc_expect_death(struct context *, const char *, va_list); static void _atf_tc_fail(struct context *ctx, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); fail_requirement(ctx, &reason); UNREACHABLE; } static void _atf_tc_fail_nonfatal(struct context *ctx, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); fail_check(ctx, &reason); } static void _atf_tc_fail_check(struct context *ctx, const char *file, const size_t line, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, file, line, fmt, ap2); va_end(ap2); fail_check(ctx, &reason); } static void _atf_tc_fail_requirement(struct context *ctx, const char *file, const size_t line, const char *fmt, va_list ap) { va_list ap2; atf_dynstr_t reason; va_copy(ap2, ap); format_reason_ap(&reason, file, line, fmt, ap2); va_end(ap2); fail_requirement(ctx, &reason); UNREACHABLE; } static void _atf_tc_pass(struct context *ctx) { pass(ctx); UNREACHABLE; } static void _atf_tc_require_prog(struct context *ctx, const char *prog) { check_fatal_error(check_prog(ctx, prog)); } static void _atf_tc_skip(struct context *ctx, const char *fmt, va_list ap) { atf_dynstr_t reason; va_list ap2; va_copy(ap2, ap); format_reason_ap(&reason, NULL, 0, fmt, ap2); va_end(ap2); skip(ctx, &reason); } static void _atf_tc_check_errno(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { errno_test(ctx, file, line, exp_errno, expr_str, expr_result, fail_check); } static void _atf_tc_require_errno(struct context *ctx, const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { errno_test(ctx, file, line, exp_errno, expr_str, expr_result, fail_requirement); } static void _atf_tc_expect_pass(struct context *ctx) { validate_expect(ctx); ctx->expect = EXPECT_PASS; } static void _atf_tc_expect_fail(struct context *ctx, const char *reason, va_list ap) { va_list ap2; validate_expect(ctx); ctx->expect = EXPECT_FAIL; atf_dynstr_fini(&ctx->expect_reason); va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&ctx->expect_reason, reason, ap2)); va_end(ap2); ctx->expect_previous_fail_count = ctx->expect_fail_count; } static void _atf_tc_expect_exit(struct context *ctx, const int exitcode, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_EXIT; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); - create_resfile(ctx->resfile, "expected_exit", exitcode, &formatted); + create_resfile(ctx, "expected_exit", exitcode, &formatted); } static void _atf_tc_expect_signal(struct context *ctx, const int signo, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_SIGNAL; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); - create_resfile(ctx->resfile, "expected_signal", signo, &formatted); + create_resfile(ctx, "expected_signal", signo, &formatted); } static void _atf_tc_expect_death(struct context *ctx, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_DEATH; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); - create_resfile(ctx->resfile, "expected_death", -1, &formatted); + create_resfile(ctx, "expected_death", -1, &formatted); } static void _atf_tc_expect_timeout(struct context *ctx, const char *reason, va_list ap) { va_list ap2; atf_dynstr_t formatted; validate_expect(ctx); ctx->expect = EXPECT_TIMEOUT; va_copy(ap2, ap); check_fatal_error(atf_dynstr_init_ap(&formatted, reason, ap2)); va_end(ap2); - create_resfile(ctx->resfile, "expected_timeout", -1, &formatted); + create_resfile(ctx, "expected_timeout", -1, &formatted); +} + +static void +_atf_tc_set_resultsfile(struct context *ctx, const char *file) +{ + + context_set_resfile(ctx, file); } /* --------------------------------------------------------------------- * Free functions. * --------------------------------------------------------------------- */ static struct context Current; atf_error_t atf_tc_run(const atf_tc_t *tc, const char *resfile) { context_init(&Current, tc, resfile); tc->pimpl->m_body(tc); validate_expect(&Current); if (Current.fail_count > 0) { atf_dynstr_t reason; format_reason_fmt(&reason, NULL, 0, "%d checks failed; see output for " "more details", Current.fail_count); fail_requirement(&Current, &reason); } else if (Current.expect_fail_count > 0) { atf_dynstr_t reason; format_reason_fmt(&reason, NULL, 0, "%d checks failed as expected; " "see output for more details", Current.expect_fail_count); expected_failure(&Current, &reason); } else { pass(&Current); } UNREACHABLE; return atf_no_error(); } atf_error_t atf_tc_cleanup(const atf_tc_t *tc) { if (tc->pimpl->m_cleanup != NULL) tc->pimpl->m_cleanup(tc); return atf_no_error(); /* XXX */ } /* --------------------------------------------------------------------- * Free functions that depend on Current. * --------------------------------------------------------------------- */ /* * All the functions below provide delegates to other internal functions * (prefixed by _) that take the current test case as an argument to * prevent them from accessing global state. This is to keep the side- * effects of the internal functions clearer and easier to understand. * * The public API should never have hid the fact that it needs access to * the current test case (other than maybe in the macros), but changing it * is hard. TODO: Revisit in the future. */ void atf_tc_fail(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail(&Current, fmt, ap); va_end(ap); } void atf_tc_fail_nonfatal(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_nonfatal(&Current, fmt, ap); va_end(ap); } void atf_tc_fail_check(const char *file, const size_t line, const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_check(&Current, file, line, fmt, ap); va_end(ap); } void atf_tc_fail_requirement(const char *file, const size_t line, const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_fail_requirement(&Current, file, line, fmt, ap); va_end(ap); } void atf_tc_pass(void) { PRE(Current.tc != NULL); _atf_tc_pass(&Current); } void atf_tc_require_prog(const char *prog) { PRE(Current.tc != NULL); _atf_tc_require_prog(&Current, prog); } void atf_tc_skip(const char *fmt, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, fmt); _atf_tc_skip(&Current, fmt, ap); va_end(ap); } void atf_tc_check_errno(const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { PRE(Current.tc != NULL); _atf_tc_check_errno(&Current, file, line, exp_errno, expr_str, expr_result); } void atf_tc_require_errno(const char *file, const size_t line, const int exp_errno, const char *expr_str, const bool expr_result) { PRE(Current.tc != NULL); _atf_tc_require_errno(&Current, file, line, exp_errno, expr_str, expr_result); } void atf_tc_expect_pass(void) { PRE(Current.tc != NULL); _atf_tc_expect_pass(&Current); } void atf_tc_expect_fail(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_fail(&Current, reason, ap); va_end(ap); } void atf_tc_expect_exit(const int exitcode, const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_exit(&Current, exitcode, reason, ap); va_end(ap); } void atf_tc_expect_signal(const int signo, const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_signal(&Current, signo, reason, ap); va_end(ap); } void atf_tc_expect_death(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_death(&Current, reason, ap); va_end(ap); } void atf_tc_expect_timeout(const char *reason, ...) { va_list ap; PRE(Current.tc != NULL); va_start(ap, reason); _atf_tc_expect_timeout(&Current, reason, ap); va_end(ap); } + +/* Internal! */ +void +atf_tc_set_resultsfile(const char *file) +{ + + PRE(Current.tc != NULL); + + _atf_tc_set_resultsfile(&Current, file); +} diff --git a/contrib/atf/atf-c/utils.c b/contrib/atf/atf-c/utils.c index 1e2aac1ed3b6..d8355bc68936 100644 --- a/contrib/atf/atf-c/utils.c +++ b/contrib/atf/atf-c/utils.c @@ -1,456 +1,466 @@ /* Copyright (c) 2010 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include "atf-c/detail/dynstr.h" +/* No prototype in header for this one, it's a little sketchy (internal). */ +void atf_tc_set_resultsfile(const char *); + /** Allocate a filename to be used by atf_utils_{fork,wait}. * * In case of a failure, marks the calling test as failed when in_parent is * true, else terminates execution. * * \param [out] name String to contain the generated file. * \param pid PID of the process that will write to the file. * \param suffix Either "out" or "err". * \param in_parent If true, fail with atf_tc_fail; else use err(3). */ static void init_out_filename(atf_dynstr_t *name, const pid_t pid, const char *suffix, const bool in_parent) { atf_error_t error; error = atf_dynstr_init_fmt(name, "atf_utils_fork_%d_%s.txt", (int)pid, suffix); if (atf_is_error(error)) { char buffer[1024]; atf_error_format(error, buffer, sizeof(buffer)); if (in_parent) { atf_tc_fail("Failed to create output file: %s", buffer); } else { err(EXIT_FAILURE, "Failed to create output file: %s", buffer); } } } /** Searches for a regexp in a string. * * \param regex The regexp to look for. * \param str The string in which to look for the expression. * * \return True if there is a match; false otherwise. */ static bool grep_string(const char *regex, const char *str) { int res; regex_t preg; printf("Looking for '%s' in '%s'\n", regex, str); ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0); res = regexec(&preg, str, 0, NULL, 0); ATF_REQUIRE(res == 0 || res == REG_NOMATCH); regfree(&preg); return res == 0; } /** Prints the contents of a file to stdout. * * \param name The name of the file to be printed. * \param prefix An string to be prepended to every line of the printed * file. */ void atf_utils_cat_file(const char *name, const char *prefix) { const int fd = open(name, O_RDONLY); ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name); char buffer[1024]; ssize_t count; bool continued = false; while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) { buffer[count] = '\0'; if (!continued) printf("%s", prefix); char *iter = buffer; char *end; while ((end = strchr(iter, '\n')) != NULL) { *end = '\0'; printf("%s\n", iter); iter = end + 1; if (iter != buffer + count) printf("%s", prefix); else continued = false; } if (iter < buffer + count) { printf("%s", iter); continued = true; } } ATF_REQUIRE(count == 0); } /** Compares a file against the given golden contents. * * \param name Name of the file to be compared. * \param contents Expected contents of the file. * * \return True if the file matches the contents; false otherwise. */ bool atf_utils_compare_file(const char *name, const char *contents) { const int fd = open(name, O_RDONLY); ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name); const char *pos = contents; ssize_t remaining = strlen(contents); char buffer[1024]; ssize_t count; while ((count = read(fd, buffer, sizeof(buffer))) > 0 && count <= remaining) { if (memcmp(pos, buffer, count) != 0) { close(fd); return false; } remaining -= count; pos += count; } close(fd); return count == 0 && remaining == 0; } /** Copies a file. * * \param source Path to the source file. * \param destination Path to the destination file. */ void atf_utils_copy_file(const char *source, const char *destination) { const int input = open(source, O_RDONLY); ATF_REQUIRE_MSG(input != -1, "Failed to open source file during " "copy (%s)", source); const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777); ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during " "copy (%s)", destination); char buffer[1024]; ssize_t length; while ((length = read(input, buffer, sizeof(buffer))) > 0) ATF_REQUIRE_MSG(write(output, buffer, length) == length, "Failed to write to %s during copy", destination); ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source); struct stat sb; ATF_REQUIRE_MSG(fstat(input, &sb) != -1, "Failed to stat source file %s during copy", source); ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1, "Failed to chmod destination file %s during copy", destination); close(output); close(input); } /** Creates a file. * * \param name Name of the file to create. * \param contents Text to write into the created file. * \param ... Positional parameters to the contents. */ void atf_utils_create_file(const char *name, const char *contents, ...) { va_list ap; atf_dynstr_t formatted; atf_error_t error; va_start(ap, contents); error = atf_dynstr_init_ap(&formatted, contents, ap); va_end(ap); ATF_REQUIRE(!atf_is_error(error)); const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644); ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name); ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted), atf_dynstr_length(&formatted)) != -1); close(fd); atf_dynstr_fini(&formatted); } /** Checks if a file exists. * * \param path Location of the file to check for. * * \return True if the file exists, false otherwise. */ bool atf_utils_file_exists(const char *path) { const int ret = access(path, F_OK); if (ret == -1) { if (errno != ENOENT) atf_tc_fail("Failed to check the existence of %s: %s", path, strerror(errno)); else return false; } else return true; } /** Spawns a subprocess and redirects its output to files. * * Use the atf_utils_wait() function to wait for the completion of the spawned * subprocess and validate its exit conditions. * * \return 0 in the new child; the PID of the new child in the parent. Does * not return in error conditions. */ pid_t atf_utils_fork(void) { const pid_t pid = fork(); if (pid == -1) atf_tc_fail("fork failed"); if (pid == 0) { atf_dynstr_t out_name; init_out_filename(&out_name, getpid(), "out", false); atf_dynstr_t err_name; init_out_filename(&err_name, getpid(), "err", false); atf_utils_redirect(STDOUT_FILENO, atf_dynstr_cstring(&out_name)); atf_utils_redirect(STDERR_FILENO, atf_dynstr_cstring(&err_name)); atf_dynstr_fini(&err_name); atf_dynstr_fini(&out_name); } return pid; } +void +atf_utils_reset_resultsfile(void) +{ + + atf_tc_set_resultsfile("/dev/null"); +} + /** Frees an dynamically-allocated "argv" array. * * \param argv A dynamically-allocated array of dynamically-allocated * strings. */ void atf_utils_free_charpp(char **argv) { char **ptr; for (ptr = argv; *ptr != NULL; ptr++) free(*ptr); free(argv); } /** Searches for a regexp in a file. * * \param regex The regexp to look for. * \param file The file in which to look for the expression. * \param ... Positional parameters to the regex. * * \return True if there is a match; false otherwise. */ bool atf_utils_grep_file(const char *regex, const char *file, ...) { int fd; va_list ap; atf_dynstr_t formatted; atf_error_t error; va_start(ap, file); error = atf_dynstr_init_ap(&formatted, regex, ap); va_end(ap); ATF_REQUIRE(!atf_is_error(error)); ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1); bool found = false; char *line = NULL; while (!found && (line = atf_utils_readline(fd)) != NULL) { found = grep_string(atf_dynstr_cstring(&formatted), line); free(line); } close(fd); atf_dynstr_fini(&formatted); return found; } /** Searches for a regexp in a string. * * \param regex The regexp to look for. * \param str The string in which to look for the expression. * \param ... Positional parameters to the regex. * * \return True if there is a match; false otherwise. */ bool atf_utils_grep_string(const char *regex, const char *str, ...) { bool res; va_list ap; atf_dynstr_t formatted; atf_error_t error; va_start(ap, str); error = atf_dynstr_init_ap(&formatted, regex, ap); va_end(ap); ATF_REQUIRE(!atf_is_error(error)); res = grep_string(atf_dynstr_cstring(&formatted), str); atf_dynstr_fini(&formatted); return res; } /** Reads a line of arbitrary length. * * \param fd The descriptor from which to read the line. * * \return A pointer to the read line, which must be released with free(), or * NULL if there was nothing to read from the file. */ char * atf_utils_readline(const int fd) { char ch; ssize_t cnt; atf_dynstr_t temp; atf_error_t error; error = atf_dynstr_init(&temp); ATF_REQUIRE(!atf_is_error(error)); while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) && ch != '\n') { error = atf_dynstr_append_fmt(&temp, "%c", ch); ATF_REQUIRE(!atf_is_error(error)); } ATF_REQUIRE(cnt != -1); if (cnt == 0 && atf_dynstr_length(&temp) == 0) { atf_dynstr_fini(&temp); return NULL; } else return atf_dynstr_fini_disown(&temp); } /** Redirects a file descriptor to a file. * * \param target_fd The file descriptor to be replaced. * \param name The name of the file to direct the descriptor to. * * \pre Should only be called from the process spawned by fork_for_testing * because this exits uncontrolledly. * \post Terminates execution if the redirection fails. */ void atf_utils_redirect(const int target_fd, const char *name) { if (target_fd == STDOUT_FILENO) fflush(stdout); else if (target_fd == STDERR_FILENO) fflush(stderr); const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (new_fd == -1) err(EXIT_FAILURE, "Cannot create %s", name); if (new_fd != target_fd) { if (dup2(new_fd, target_fd) == -1) err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd); } close(new_fd); } /** Waits for a subprocess and validates its exit condition. * * \param pid The process to be waited for. Must have been started by * testutils_fork(). * \param exitstatus Expected exit status. * \param expout Expected contents of stdout. * \param experr Expected contents of stderr. */ void atf_utils_wait(const pid_t pid, const int exitstatus, const char *expout, const char *experr) { int status; ATF_REQUIRE(waitpid(pid, &status, 0) != -1); atf_dynstr_t out_name; init_out_filename(&out_name, pid, "out", true); atf_dynstr_t err_name; init_out_filename(&err_name, pid, "err", true); atf_utils_cat_file(atf_dynstr_cstring(&out_name), "subprocess stdout: "); atf_utils_cat_file(atf_dynstr_cstring(&err_name), "subprocess stderr: "); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status)); const char *save_prefix = "save:"; const size_t save_prefix_length = strlen(save_prefix); if (strlen(expout) > save_prefix_length && strncmp(expout, save_prefix, save_prefix_length) == 0) { atf_utils_copy_file(atf_dynstr_cstring(&out_name), expout + save_prefix_length); } else { ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&out_name), expout)); } if (strlen(experr) > save_prefix_length && strncmp(experr, save_prefix, save_prefix_length) == 0) { atf_utils_copy_file(atf_dynstr_cstring(&err_name), experr + save_prefix_length); } else { ATF_REQUIRE(atf_utils_compare_file(atf_dynstr_cstring(&err_name), experr)); } ATF_REQUIRE(unlink(atf_dynstr_cstring(&out_name)) != -1); ATF_REQUIRE(unlink(atf_dynstr_cstring(&err_name)) != -1); } diff --git a/contrib/atf/atf-c/utils.h b/contrib/atf/atf-c/utils.h index e4162b215fe5..422186a31e76 100644 --- a/contrib/atf/atf-c/utils.h +++ b/contrib/atf/atf-c/utils.h @@ -1,50 +1,51 @@ /* Copyright (c) 2010 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if !defined(ATF_C_UTILS_H) #define ATF_C_UTILS_H #include #include #include void atf_utils_cat_file(const char *, const char *); bool atf_utils_compare_file(const char *, const char *); void atf_utils_copy_file(const char *, const char *); void atf_utils_create_file(const char *, const char *, ...) ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(2, 3); bool atf_utils_file_exists(const char *); pid_t atf_utils_fork(void); void atf_utils_free_charpp(char **); bool atf_utils_grep_file(const char *, const char *, ...) ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(1, 3); bool atf_utils_grep_string(const char *, const char *, ...) ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(1, 3); char *atf_utils_readline(int); void atf_utils_redirect(const int, const char *); void atf_utils_wait(const pid_t, const int, const char *, const char *); +void atf_utils_reset_resultsfile(void); #endif /* !defined(ATF_C_UTILS_H) */ diff --git a/contrib/atf/atf-c/utils_test.c b/contrib/atf/atf-c/utils_test.c index fb81cd3a1d9e..9d8f69683b9a 100644 --- a/contrib/atf/atf-c/utils_test.c +++ b/contrib/atf/atf-c/utils_test.c @@ -1,570 +1,571 @@ /* Copyright (c) 2010 The NetBSD Foundation, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "atf-c/utils.h" #include #include #include #include #include #include #include #include #include #include "atf-c/detail/dynstr.h" #include "atf-c/detail/test_helpers.h" /** Reads the contents of a file into a buffer. * * Up to buflen-1 characters are read into buffer. If this function returns, * the contents read into the buffer are guaranteed to be nul-terminated. * Note, however, that if the file contains any nul characters itself, * comparing it "as a string" will not work. * * \param path The file to be read, which must exist. * \param buffer Buffer into which to store the file contents. * \param buflen Size of the target buffer. * * \return The count of bytes read. */ static ssize_t read_file(const char *path, void *const buffer, const size_t buflen) { const int fd = open(path, O_RDONLY); ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", path); const ssize_t length = read(fd, buffer, buflen - 1); close(fd); ATF_REQUIRE(length != -1); ((char *)buffer)[length] = '\0'; return length; } ATF_TC_WITHOUT_HEAD(cat_file__empty); ATF_TC_BODY(cat_file__empty, tc) { atf_utils_create_file("file.txt", "%s", ""); atf_utils_redirect(STDOUT_FILENO, "captured.txt"); atf_utils_cat_file("file.txt", "PREFIX"); fflush(stdout); close(STDOUT_FILENO); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("", buffer); } ATF_TC_WITHOUT_HEAD(cat_file__one_line); ATF_TC_BODY(cat_file__one_line, tc) { atf_utils_create_file("file.txt", "This is a single line\n"); atf_utils_redirect(STDOUT_FILENO, "captured.txt"); atf_utils_cat_file("file.txt", "PREFIX"); fflush(stdout); close(STDOUT_FILENO); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("PREFIXThis is a single line\n", buffer); } ATF_TC_WITHOUT_HEAD(cat_file__several_lines); ATF_TC_BODY(cat_file__several_lines, tc) { atf_utils_create_file("file.txt", "First\nSecond line\nAnd third\n"); atf_utils_redirect(STDOUT_FILENO, "captured.txt"); atf_utils_cat_file("file.txt", ">"); fflush(stdout); close(STDOUT_FILENO); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ(">First\n>Second line\n>And third\n", buffer); } ATF_TC_WITHOUT_HEAD(cat_file__no_newline_eof); ATF_TC_BODY(cat_file__no_newline_eof, tc) { atf_utils_create_file("file.txt", "Foo\n bar baz"); atf_utils_redirect(STDOUT_FILENO, "captured.txt"); atf_utils_cat_file("file.txt", "PREFIX"); fflush(stdout); close(STDOUT_FILENO); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("PREFIXFoo\nPREFIX bar baz", buffer); } ATF_TC_WITHOUT_HEAD(compare_file__empty__match); ATF_TC_BODY(compare_file__empty__match, tc) { atf_utils_create_file("test.txt", "%s", ""); ATF_REQUIRE(atf_utils_compare_file("test.txt", "")); } ATF_TC_WITHOUT_HEAD(compare_file__empty__not_match); ATF_TC_BODY(compare_file__empty__not_match, tc) { atf_utils_create_file("test.txt", "%s", ""); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "\n")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "foo")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", " ")); } ATF_TC_WITHOUT_HEAD(compare_file__short__match); ATF_TC_BODY(compare_file__short__match, tc) { atf_utils_create_file("test.txt", "this is a short file"); ATF_REQUIRE(atf_utils_compare_file("test.txt", "this is a short file")); } ATF_TC_WITHOUT_HEAD(compare_file__short__not_match); ATF_TC_BODY(compare_file__short__not_match, tc) { atf_utils_create_file("test.txt", "this is a short file"); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "\n")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "this is a Short file")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "this is a short fil")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "this is a short file ")); } ATF_TC_WITHOUT_HEAD(compare_file__long__match); ATF_TC_BODY(compare_file__long__match, tc) { char long_contents[3456]; size_t i = 0; for (; i < sizeof(long_contents) - 1; i++) long_contents[i] = '0' + (i % 10); long_contents[i] = '\0'; atf_utils_create_file("test.txt", "%s", long_contents); ATF_REQUIRE(atf_utils_compare_file("test.txt", long_contents)); } ATF_TC_WITHOUT_HEAD(compare_file__long__not_match); ATF_TC_BODY(compare_file__long__not_match, tc) { char long_contents[3456]; size_t i = 0; for (; i < sizeof(long_contents) - 1; i++) long_contents[i] = '0' + (i % 10); long_contents[i] = '\0'; atf_utils_create_file("test.txt", "%s", long_contents); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "\n")); ATF_REQUIRE(!atf_utils_compare_file("test.txt", "0123456789")); long_contents[i - 1] = 'Z'; ATF_REQUIRE(!atf_utils_compare_file("test.txt", long_contents)); } ATF_TC_WITHOUT_HEAD(copy_file__empty); ATF_TC_BODY(copy_file__empty, tc) { atf_utils_create_file("src.txt", "%s", ""); ATF_REQUIRE(chmod("src.txt", 0520) != -1); atf_utils_copy_file("src.txt", "dest.txt"); ATF_REQUIRE(atf_utils_compare_file("dest.txt", "")); struct stat sb; ATF_REQUIRE(stat("dest.txt", &sb) != -1); ATF_REQUIRE_EQ(0520, sb.st_mode & 0xfff); } ATF_TC_WITHOUT_HEAD(copy_file__some_contents); ATF_TC_BODY(copy_file__some_contents, tc) { atf_utils_create_file("src.txt", "This is a\ntest file\n"); atf_utils_copy_file("src.txt", "dest.txt"); ATF_REQUIRE(atf_utils_compare_file("dest.txt", "This is a\ntest file\n")); } ATF_TC_WITHOUT_HEAD(create_file); ATF_TC_BODY(create_file, tc) { atf_utils_create_file("test.txt", "This is a test with %d", 12345); char buffer[128]; read_file("test.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("This is a test with 12345", buffer); } ATF_TC_WITHOUT_HEAD(file_exists); ATF_TC_BODY(file_exists, tc) { atf_utils_create_file("test.txt", "foo"); ATF_REQUIRE( atf_utils_file_exists("test.txt")); ATF_REQUIRE( atf_utils_file_exists("./test.txt")); ATF_REQUIRE(!atf_utils_file_exists("./test.tx")); ATF_REQUIRE(!atf_utils_file_exists("test.txt2")); } ATF_TC_WITHOUT_HEAD(fork); ATF_TC_BODY(fork, tc) { fprintf(stdout, "Should not get into child\n"); fprintf(stderr, "Should not get into child\n"); pid_t pid = atf_utils_fork(); if (pid == 0) { fprintf(stdout, "Child stdout\n"); fprintf(stderr, "Child stderr\n"); exit(EXIT_SUCCESS); } int status; ATF_REQUIRE(waitpid(pid, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); atf_dynstr_t out_name; RE(atf_dynstr_init_fmt(&out_name, "atf_utils_fork_%d_out.txt", (int)pid)); atf_dynstr_t err_name; RE(atf_dynstr_init_fmt(&err_name, "atf_utils_fork_%d_err.txt", (int)pid)); char buffer[1024]; read_file(atf_dynstr_cstring(&out_name), buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("Child stdout\n", buffer); read_file(atf_dynstr_cstring(&err_name), buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("Child stderr\n", buffer); atf_dynstr_fini(&err_name); atf_dynstr_fini(&out_name); } ATF_TC_WITHOUT_HEAD(free_charpp__empty); ATF_TC_BODY(free_charpp__empty, tc) { char **array = malloc(sizeof(char *) * 1); array[0] = NULL; atf_utils_free_charpp(array); } ATF_TC_WITHOUT_HEAD(free_charpp__some); ATF_TC_BODY(free_charpp__some, tc) { char **array = malloc(sizeof(char *) * 4); array[0] = strdup("first"); array[1] = strdup("second"); array[2] = strdup("third"); array[3] = NULL; atf_utils_free_charpp(array); } ATF_TC_WITHOUT_HEAD(grep_file); ATF_TC_BODY(grep_file, tc) { atf_utils_create_file("test.txt", "line1\nthe second line\naaaabbbb\n"); ATF_CHECK(atf_utils_grep_file("line1", "test.txt")); ATF_CHECK(atf_utils_grep_file("line%d", "test.txt", 1)); ATF_CHECK(atf_utils_grep_file("second line", "test.txt")); ATF_CHECK(atf_utils_grep_file("aa.*bb", "test.txt")); ATF_CHECK(!atf_utils_grep_file("foo", "test.txt")); ATF_CHECK(!atf_utils_grep_file("bar", "test.txt")); ATF_CHECK(!atf_utils_grep_file("aaaaa", "test.txt")); } ATF_TC_WITHOUT_HEAD(grep_string); ATF_TC_BODY(grep_string, tc) { const char *str = "a string - aaaabbbb"; ATF_CHECK(atf_utils_grep_string("a string", str)); ATF_CHECK(atf_utils_grep_string("^a string", str)); ATF_CHECK(atf_utils_grep_string("aaaabbbb$", str)); ATF_CHECK(atf_utils_grep_string("a%s*bb", str, "a.")); ATF_CHECK(!atf_utils_grep_string("foo", str)); ATF_CHECK(!atf_utils_grep_string("bar", str)); ATF_CHECK(!atf_utils_grep_string("aaaaa", str)); } ATF_TC_WITHOUT_HEAD(readline__none); ATF_TC_BODY(readline__none, tc) { atf_utils_create_file("empty.txt", "%s", ""); const int fd = open("empty.txt", O_RDONLY); ATF_REQUIRE(fd != -1); ATF_REQUIRE(atf_utils_readline(fd) == NULL); close(fd); } ATF_TC_WITHOUT_HEAD(readline__some); ATF_TC_BODY(readline__some, tc) { const char *l1 = "First line with % formatting % characters %"; const char *l2 = "Second line; much longer than the first one"; const char *l3 = "Last line, without terminator"; atf_utils_create_file("test.txt", "%s\n%s\n%s", l1, l2, l3); const int fd = open("test.txt", O_RDONLY); ATF_REQUIRE(fd != -1); char *line; line = atf_utils_readline(fd); ATF_REQUIRE_STREQ(l1, line); free(line); line = atf_utils_readline(fd); ATF_REQUIRE_STREQ(l2, line); free(line); line = atf_utils_readline(fd); ATF_REQUIRE_STREQ(l3, line); free(line); close(fd); } ATF_TC_WITHOUT_HEAD(redirect__stdout); ATF_TC_BODY(redirect__stdout, tc) { printf("Buffer this"); atf_utils_redirect(STDOUT_FILENO, "captured.txt"); printf("The printed message"); fflush(stdout); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("The printed message", buffer); } ATF_TC_WITHOUT_HEAD(redirect__stderr); ATF_TC_BODY(redirect__stderr, tc) { fprintf(stderr, "Buffer this"); atf_utils_redirect(STDERR_FILENO, "captured.txt"); fprintf(stderr, "The printed message"); fflush(stderr); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ("The printed message", buffer); } ATF_TC_WITHOUT_HEAD(redirect__other); ATF_TC_BODY(redirect__other, tc) { const char *message = "Foo bar\nbaz\n"; atf_utils_redirect(15, "captured.txt"); ATF_REQUIRE(write(15, message, strlen(message)) != -1); close(15); char buffer[1024]; read_file("captured.txt", buffer, sizeof(buffer)); ATF_REQUIRE_STREQ(message, buffer); } static void fork_and_wait(const int exitstatus, const char* expout, const char* experr) { const pid_t pid = atf_utils_fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { fprintf(stdout, "Some output\n"); fprintf(stderr, "Some error\n"); exit(123); } + atf_utils_reset_resultsfile(); atf_utils_wait(pid, exitstatus, expout, experr); exit(EXIT_SUCCESS); } ATF_TC_WITHOUT_HEAD(wait__ok); ATF_TC_BODY(wait__ok, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } } ATF_TC_WITHOUT_HEAD(wait__ok_nested); ATF_TC_BODY(wait__ok_nested, tc) { const pid_t parent = atf_utils_fork(); ATF_REQUIRE(parent != -1); if (parent == 0) { const pid_t child = atf_utils_fork(); ATF_REQUIRE(child != -1); if (child == 0) { fflush(stderr); fprintf(stdout, "Child output\n"); fflush(stdout); fprintf(stderr, "Child error\n"); exit(50); } else { fprintf(stdout, "Parent output\n"); fprintf(stderr, "Parent error\n"); atf_utils_wait(child, 50, "Child output\n", "Child error\n"); exit(40); } } else { atf_utils_wait(parent, 40, "Parent output\n" "subprocess stdout: Child output\n" "subprocess stderr: Child error\n", "Parent error\n"); } } ATF_TC_WITHOUT_HEAD(wait__invalid_exitstatus); ATF_TC_BODY(wait__invalid_exitstatus, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(120, "Some output\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TC_WITHOUT_HEAD(wait__invalid_stdout); ATF_TC_BODY(wait__invalid_stdout, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output foo\n", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TC_WITHOUT_HEAD(wait__invalid_stderr); ATF_TC_BODY(wait__invalid_stderr, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "Some error foo\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_FAILURE, WEXITSTATUS(status)); } } ATF_TC_WITHOUT_HEAD(wait__save_stdout); ATF_TC_BODY(wait__save_stdout, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "save:my-output.txt", "Some error\n"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); ATF_REQUIRE(atf_utils_compare_file("my-output.txt", "Some output\n")); } } ATF_TC_WITHOUT_HEAD(wait__save_stderr); ATF_TC_BODY(wait__save_stderr, tc) { const pid_t control = fork(); ATF_REQUIRE(control != -1); if (control == 0) fork_and_wait(123, "Some output\n", "save:my-output.txt"); else { int status; ATF_REQUIRE(waitpid(control, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); ATF_REQUIRE(atf_utils_compare_file("my-output.txt", "Some error\n")); } } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, cat_file__empty); ATF_TP_ADD_TC(tp, cat_file__one_line); ATF_TP_ADD_TC(tp, cat_file__several_lines); ATF_TP_ADD_TC(tp, cat_file__no_newline_eof); ATF_TP_ADD_TC(tp, compare_file__empty__match); ATF_TP_ADD_TC(tp, compare_file__empty__not_match); ATF_TP_ADD_TC(tp, compare_file__short__match); ATF_TP_ADD_TC(tp, compare_file__short__not_match); ATF_TP_ADD_TC(tp, compare_file__long__match); ATF_TP_ADD_TC(tp, compare_file__long__not_match); ATF_TP_ADD_TC(tp, copy_file__empty); ATF_TP_ADD_TC(tp, copy_file__some_contents); ATF_TP_ADD_TC(tp, create_file); ATF_TP_ADD_TC(tp, file_exists); ATF_TP_ADD_TC(tp, fork); ATF_TP_ADD_TC(tp, free_charpp__empty); ATF_TP_ADD_TC(tp, free_charpp__some); ATF_TP_ADD_TC(tp, grep_file); ATF_TP_ADD_TC(tp, grep_string); ATF_TP_ADD_TC(tp, readline__none); ATF_TP_ADD_TC(tp, readline__some); ATF_TP_ADD_TC(tp, redirect__stdout); ATF_TP_ADD_TC(tp, redirect__stderr); ATF_TP_ADD_TC(tp, redirect__other); ATF_TP_ADD_TC(tp, wait__ok); ATF_TP_ADD_TC(tp, wait__ok_nested); ATF_TP_ADD_TC(tp, wait__save_stdout); ATF_TP_ADD_TC(tp, wait__save_stderr); ATF_TP_ADD_TC(tp, wait__invalid_exitstatus); ATF_TP_ADD_TC(tp, wait__invalid_stdout); ATF_TP_ADD_TC(tp, wait__invalid_stderr); return atf_no_error(); } diff --git a/contrib/atf/atf-sh/.gitignore b/contrib/atf/atf-sh/.gitignore new file mode 100644 index 000000000000..a29438f1a9b8 --- /dev/null +++ b/contrib/atf/atf-sh/.gitignore @@ -0,0 +1,2 @@ +atf-check +atf-sh diff --git a/contrib/atf/atf-sh/atf-check.1 b/contrib/atf/atf-sh/atf-check.1 index a423e3ac3b1c..b03058e8442c 100644 --- a/contrib/atf/atf-sh/atf-check.1 +++ b/contrib/atf/atf-sh/atf-check.1 @@ -1,162 +1,175 @@ .\" Copyright (c) 2008 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND .\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, .\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE .\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -.Dd March 6, 2017 +.Dd June 21, 2020 .Dt ATF-CHECK 1 .Os .Sh NAME .Nm atf-check .Nd executes a command and analyzes its results .Sh SYNOPSIS .Nm .Op Fl s Ar qual:value .Op Fl o Ar action:arg ... .Op Fl e Ar action:arg ... .Op Fl x .Ar command .Sh DESCRIPTION .Nm executes a given command and analyzes its results, including exit code, stdout and stderr. .Pp .Bf Em Test cases must use .Xr atf-sh 3 Ns ' Ns s .Nm atf_check builtin function instead of calling this utility directly. .Ef .Pp In the first synopsis form, .Nm will execute the provided command and apply checks specified by arguments. By default it will act as if it was run with .Fl s .Ar exit:0 .Fl o .Ar empty .Fl e .Ar empty . Multiple checks for the same output channel are allowed and, if specified, their results will be combined as a logical and (meaning that the output must match all the provided checks). .Pp In the second synopsis form, .Nm will print information about all supported options and their purpose. .Pp The following options are available: .Bl -tag -width XqualXvalueXX .It Fl s Ar qual:value Analyzes termination status. Must be one of: .Bl -tag -width signal: -compact .It Ar exit: checks that the program exited cleanly and that its exit status is equal to .Va value . The exit code can be omitted altogether, in which case any clean exit is accepted. .It Ar ignore ignores the exit check. .It Ar signal: checks that the program exited due to a signal and that the signal that terminated it is .Va value . The signal can be specified both as a number or as a name, or it can also be omitted altogether, in which case any signal is accepted. .El .Pp Most of these checkers can be prefixed by the .Sq not- string, which effectively reverses the check. .It Fl o Ar action:arg Analyzes standard output. Must be one of: .Bl -tag -width inline: -compact .It Ar empty checks that stdout is empty .It Ar ignore ignores stdout .It Ar file: compares stdout with given file .It Ar inline: compares stdout with inline value .It Ar match: looks for a regular expression in stdout .It Ar save: saves stdout to given file .El .Pp Most of these checkers can be prefixed by the .Sq not- string, which effectively reverses the check. .It Fl e Ar action:arg Analyzes standard error (syntax identical to above) .It Fl x Executes .Ar command as a shell command line, executing it with the system shell defined by .Va ATF_SHELL . You should avoid using this flag if at all possible to prevent shell quoting issues. +.It Fl r Ar timeout[:interval] +Repeats failed checks until the +.Ar timeout +(in seconds) expires. +If unspecified, the default +.Ar interval +(in milliseconds) is 50 ms. +This can be used to wait for an expected update to the contents of a file. .El .Sh ENVIRONMENT .Bl -tag -width ATFXSHELLXX -compact .It Va ATF_SHELL Path to the system shell to be used when the .Fl x is given to run commands. .El .Sh EXIT STATUS .Nm exits 0 on success, and other (unspecified) value on failure. .Sh EXAMPLES The following are sample invocations from within a test case. Note that we use the .Nm atf_check function provided by .Xr atf-sh 3 instead of executing .Nm directly: .Bd -literal -offset indent # Exit code 0, nothing on stdout/stderr atf_check 'true' # Typical usage if failure is expected atf_check -s not-exit:0 'false' # Checking stdout/stderr echo foobar >expout atf_check -o file:expout -e inline:"xx\etyy\en" \e 'echo foobar ; printf "xx\etyy\en" >&2' # Checking for a crash atf_check -s signal:sigsegv my_program # Combined checks atf_check -o match:foo -o not-match:bar echo foo baz + +# Wait 5 seconds for a line to show up in a file +( sleep 2 ; echo "testing 123" > $test_path ) & +atf-check -o ignore -e ignore -s exit:0 -r 5 \e + grep "testing 123" $test_path .Ed .Sh SEE ALSO .Xr atf-sh 1 diff --git a/contrib/atf/atf-sh/atf-check.cpp b/contrib/atf/atf-sh/atf-check.cpp index 414b64ea91f0..38ab527aab54 100644 --- a/contrib/atf/atf-sh/atf-check.cpp +++ b/contrib/atf/atf-sh/atf-check.cpp @@ -1,835 +1,943 @@ // Copyright (c) 2008 The NetBSD Foundation, Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. extern "C" { #include #include #include #include +#include #include } #include #include #include #include #include #include #include #include #include #include #include "atf-c++/check.hpp" #include "atf-c++/detail/application.hpp" #include "atf-c++/detail/auto_array.hpp" #include "atf-c++/detail/env.hpp" #include "atf-c++/detail/exceptions.hpp" #include "atf-c++/detail/fs.hpp" #include "atf-c++/detail/process.hpp" #include "atf-c++/detail/sanity.hpp" #include "atf-c++/detail/text.hpp" +static const useconds_t seconds_in_useconds = (1000 * 1000); +static const useconds_t mseconds_in_useconds = 1000; +static const useconds_t useconds_in_nseconds = 1000; + // ------------------------------------------------------------------------ // Auxiliary functions. // ------------------------------------------------------------------------ namespace { enum status_check_t { sc_exit, sc_ignore, sc_signal, }; struct status_check { status_check_t type; bool negated; int value; status_check(const status_check_t& p_type, const bool p_negated, const int p_value) : type(p_type), negated(p_negated), value(p_value) { } }; enum output_check_t { oc_ignore, oc_inline, oc_file, oc_empty, oc_match, oc_save }; struct output_check { output_check_t type; bool negated; std::string value; output_check(const output_check_t& p_type, const bool p_negated, const std::string& p_value) : type(p_type), negated(p_negated), value(p_value) { } }; class temp_file : public std::ostream { std::auto_ptr< atf::fs::path > m_path; int m_fd; public: temp_file(const char* pattern) : std::ostream(NULL), m_fd(-1) { const atf::fs::path file = atf::fs::path( atf::env::get("TMPDIR", "/tmp")) / pattern; atf::auto_array< char > buf(new char[file.str().length() + 1]); std::strcpy(buf.get(), file.c_str()); m_fd = ::mkstemp(buf.get()); if (m_fd == -1) throw atf::system_error("atf_check::temp_file::temp_file(" + file.str() + ")", "mkstemp(3) failed", errno); m_path.reset(new atf::fs::path(buf.get())); } ~temp_file(void) { close(); try { remove(*m_path); } catch (const atf::system_error&) { // Ignore deletion errors. } } const atf::fs::path& get_path(void) const { return *m_path; } void write(const std::string& text) { if (::write(m_fd, text.c_str(), text.size()) == -1) throw atf::system_error("atf_check", "write(2) failed", errno); } void close(void) { if (m_fd != -1) { flush(); ::close(m_fd); m_fd = -1; } } }; } // anonymous namespace +static useconds_t +get_monotonic_useconds(void) +{ + struct timespec ts; + useconds_t res; + int rc; + + rc = clock_gettime(CLOCK_MONOTONIC, &ts); + if (rc != 0) + throw std::runtime_error("clock_gettime: " + + std::string(strerror(errno))); + + res = ts.tv_sec * seconds_in_useconds; + res += ts.tv_nsec / useconds_in_nseconds; + return res; +} + +static bool +timo_expired(useconds_t timeout) +{ + + if (get_monotonic_useconds() >= timeout) + return true; + return false; +} + + static int parse_exit_code(const std::string& str) { try { const int value = atf::text::to_type< int >(str); if (value < 0 || value > 255) throw std::runtime_error("Unused reason"); return value; } catch (const std::runtime_error&) { throw atf::application::usage_error("Invalid exit code for -s option; " "must be an integer in range 0-255"); } } static struct name_number { const char *name; int signo; } signal_names_to_numbers[] = { { "hup", SIGHUP }, { "int", SIGINT }, { "quit", SIGQUIT }, { "trap", SIGTRAP }, { "abrt", SIGABRT }, { "kill", SIGKILL }, { "segv", SIGSEGV }, { "pipe", SIGPIPE }, { "alrm", SIGALRM }, { "term", SIGTERM }, { "usr1", SIGUSR1 }, { "usr2", SIGUSR2 }, { NULL, INT_MIN }, }; static int signal_name_to_number(const std::string& str) { struct name_number* iter = signal_names_to_numbers; int signo = INT_MIN; while (signo == INT_MIN && iter->name != NULL) { if (str == iter->name || str == std::string("sig") + iter->name) signo = iter->signo; else iter++; } return signo; } static int parse_signal(const std::string& str) { const int signo = signal_name_to_number(str); if (signo == INT_MIN) { try { return atf::text::to_type< int >(str); - } catch (std::runtime_error) { + } catch (const std::runtime_error&) { throw atf::application::usage_error("Invalid signal name or number " "in -s option"); } } INV(signo != INT_MIN); return signo; } static status_check parse_status_check_arg(const std::string& arg) { const std::string::size_type delimiter = arg.find(':'); bool negated = (arg.compare(0, 4, "not-") == 0); const std::string action_str = arg.substr(0, delimiter); const std::string action = negated ? action_str.substr(4) : action_str; const std::string value_str = ( delimiter == std::string::npos ? "" : arg.substr(delimiter + 1)); int value; status_check_t type; if (action == "eq") { // Deprecated; use exit instead. TODO: Remove after 0.10. type = sc_exit; if (negated) throw atf::application::usage_error("Cannot negate eq checker"); negated = false; value = parse_exit_code(value_str); } else if (action == "exit") { type = sc_exit; if (value_str.empty()) value = INT_MIN; else value = parse_exit_code(value_str); } else if (action == "ignore") { if (negated) throw atf::application::usage_error("Cannot negate ignore checker"); type = sc_ignore; value = INT_MIN; } else if (action == "ne") { // Deprecated; use not-exit instead. TODO: Remove after 0.10. type = sc_exit; if (negated) throw atf::application::usage_error("Cannot negate ne checker"); negated = true; value = parse_exit_code(value_str); } else if (action == "signal") { type = sc_signal; if (value_str.empty()) value = INT_MIN; else value = parse_signal(value_str); } else throw atf::application::usage_error("Invalid status checker"); return status_check(type, negated, value); } static output_check parse_output_check_arg(const std::string& arg) { const std::string::size_type delimiter = arg.find(':'); const bool negated = (arg.compare(0, 4, "not-") == 0); const std::string action_str = arg.substr(0, delimiter); const std::string action = negated ? action_str.substr(4) : action_str; output_check_t type; if (action == "empty") type = oc_empty; else if (action == "file") type = oc_file; else if (action == "ignore") { if (negated) throw atf::application::usage_error("Cannot negate ignore checker"); type = oc_ignore; } else if (action == "inline") type = oc_inline; else if (action == "match") type = oc_match; else if (action == "save") { if (negated) throw atf::application::usage_error("Cannot negate save checker"); type = oc_save; } else throw atf::application::usage_error("Invalid output checker"); return output_check(type, negated, arg.substr(delimiter + 1)); } +static void +parse_repeat_check_arg(const std::string& arg, useconds_t *m_timo, + useconds_t *m_interval) +{ + const std::string::size_type delimiter = arg.find(':'); + const bool has_interval = (delimiter != std::string::npos); + const std::string timo_str = arg.substr(0, delimiter); + + long l; + char *end; + + // There is no reason this couldn't be a non-integer number of seconds, + // this was just easy to do for now. + errno = 0; + l = strtol(timo_str.c_str(), &end, 10); + if (errno == ERANGE) + throw atf::application::usage_error("Bogus timeout in seconds"); + else if (errno != 0) + throw atf::application::usage_error("Timeout must be a number"); + + if (*end != 0) + throw atf::application::usage_error("Timeout must be a number"); + + *m_timo = get_monotonic_useconds() + (l * seconds_in_useconds); + // 50 milliseconds is chosen arbitrarily. There is a tradeoff between + // longer and shorter poll times. A shorter poll time makes for faster + // tests. A longer poll time makes for lower CPU overhead for the polled + // operation. 50ms is chosen with these tradeoffs in mind: on + // microcontrollers, the hope is that we can still avoid meaningful CPU use + // with a small test every 50ms. And on typical fast x86 hardware, our + // tests can be much more precise with time wasted than they typically are + // without this feature. + *m_interval = 50 * mseconds_in_useconds; + + if (!has_interval) + return; + + const std::string intv_str = arg.substr(delimiter + 1, std::string::npos); + + // Same -- this could be non-integer milliseconds. + errno = 0; + l = strtol(intv_str.c_str(), &end, 10); + if (errno == ERANGE) + throw atf::application::usage_error( + "Bogus repeat interval in milliseconds"); + else if (errno != 0) + throw atf::application::usage_error( + "Repeat interval must be a number"); + + if (*end != 0) + throw atf::application::usage_error( + "Repeat interval must be a number"); + + *m_interval = l * mseconds_in_useconds; +} + static std::string flatten_argv(char* const* argv) { std::string cmdline; char* const* arg = &argv[0]; while (*arg != NULL) { if (arg != &argv[0]) cmdline += ' '; cmdline += *arg; arg++; } return cmdline; } static std::auto_ptr< atf::check::check_result > execute(const char* const* argv) { // TODO: This should go to stderr... but fixing it now may be hard as test // cases out there might be relying on stderr being silent. std::cout << "Executing command [ "; for (int i = 0; argv[i] != NULL; ++i) std::cout << argv[i] << " "; std::cout << "]\n"; std::cout.flush(); atf::process::argv_array argva(argv); return atf::check::exec(argva); } static std::auto_ptr< atf::check::check_result > execute_with_shell(char* const* argv) { const std::string cmd = flatten_argv(argv); const std::string shell = atf::env::get("ATF_SHELL", ATF_SHELL); const char* sh_argv[4]; sh_argv[0] = shell.c_str(); sh_argv[1] = "-c"; sh_argv[2] = cmd.c_str(); sh_argv[3] = NULL; return execute(sh_argv); } static void cat_file(const atf::fs::path& path) { std::ifstream stream(path.c_str()); if (!stream) throw std::runtime_error("Failed to open " + path.str()); stream >> std::noskipws; std::istream_iterator< char > begin(stream), end; std::ostream_iterator< char > out(std::cerr); std::copy(begin, end, out); stream.close(); } static bool grep_file(const atf::fs::path& path, const std::string& regexp) { std::ifstream stream(path.c_str()); if (!stream) throw std::runtime_error("Failed to open " + path.str()); bool found = false; std::string line; while (!found && !std::getline(stream, line).fail()) { if (atf::text::match(line, regexp)) found = true; } stream.close(); return found; } static bool file_empty(const atf::fs::path& p) { atf::fs::file_info f(p); return (f.get_size() == 0); } static bool compare_files(const atf::fs::path& p1, const atf::fs::path& p2) { bool equal = false; std::ifstream f1(p1.c_str()); if (!f1) throw std::runtime_error("Failed to open " + p1.str()); std::ifstream f2(p2.c_str()); if (!f2) throw std::runtime_error("Failed to open " + p1.str()); for (;;) { char buf1[512], buf2[512]; f1.read(buf1, sizeof(buf1)); if (f1.bad()) throw std::runtime_error("Failed to read from " + p1.str()); f2.read(buf2, sizeof(buf2)); if (f2.bad()) throw std::runtime_error("Failed to read from " + p1.str()); if ((f1.gcount() == 0) && (f2.gcount() == 0)) { equal = true; break; } if ((f1.gcount() != f2.gcount()) || (std::memcmp(buf1, buf2, f1.gcount()) != 0)) { break; } } return equal; } static void print_diff(const atf::fs::path& p1, const atf::fs::path& p2) { const atf::process::status s = atf::process::exec(atf::fs::path("diff"), atf::process::argv_array("diff", "-u", p1.c_str(), p2.c_str(), NULL), atf::process::stream_connect(STDOUT_FILENO, STDERR_FILENO), atf::process::stream_inherit()); if (!s.exited()) std::cerr << "Failed to run diff(3)\n"; if (s.exitstatus() != 1) std::cerr << "Error while running diff(3)\n"; } static std::string decode(const std::string& s) { size_t i; std::string res; res.reserve(s.length()); i = 0; while (i < s.length()) { char c = s[i++]; if (c == '\\') { switch (s[i++]) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'c': break; case 'e': c = 033; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\\': break; case '0': { int count = 3; c = 0; while (--count >= 0 && (unsigned)(s[i] - '0') < 8) c = (c << 3) + (s[i++] - '0'); break; } default: --i; break; } } res.push_back(c); } return res; } static bool run_status_check(const status_check& sc, const atf::check::check_result& cr) { bool result; if (sc.type == sc_exit) { if (cr.exited() && sc.value != INT_MIN) { const int status = cr.exitcode(); if (!sc.negated && sc.value != status) { std::cerr << "Fail: incorrect exit status: " << status << ", expected: " << sc.value << "\n"; result = false; } else if (sc.negated && sc.value == status) { std::cerr << "Fail: incorrect exit status: " << status << ", expected: " << "anything else\n"; result = false; } else result = true; } else if (cr.exited() && sc.value == INT_MIN) { result = true; } else { std::cerr << "Fail: program did not exit cleanly\n"; result = false; } } else if (sc.type == sc_ignore) { result = true; } else if (sc.type == sc_signal) { if (cr.signaled() && sc.value != INT_MIN) { const int status = cr.termsig(); if (!sc.negated && sc.value != status) { std::cerr << "Fail: incorrect signal received: " << status << ", expected: " << sc.value << "\n"; result = false; } else if (sc.negated && sc.value == status) { std::cerr << "Fail: incorrect signal received: " << status << ", expected: " << "anything else\n"; result = false; } else result = true; } else if (cr.signaled() && sc.value == INT_MIN) { result = true; } else { std::cerr << "Fail: program did not receive a signal\n"; result = false; } } else { UNREACHABLE; result = false; } if (result == false) { std::cerr << "stdout:\n"; cat_file(atf::fs::path(cr.stdout_path())); std::cerr << "\n"; std::cerr << "stderr:\n"; cat_file(atf::fs::path(cr.stderr_path())); std::cerr << "\n"; } return result; } static bool run_status_checks(const std::vector< status_check >& checks, const atf::check::check_result& result) { bool ok = false; for (std::vector< status_check >::const_iterator iter = checks.begin(); !ok && iter != checks.end(); iter++) { ok |= run_status_check(*iter, result); } return ok; } static bool run_output_check(const output_check oc, const atf::fs::path& path, const std::string& stdxxx) { bool result; if (oc.type == oc_empty) { const bool is_empty = file_empty(path); if (!oc.negated && !is_empty) { std::cerr << "Fail: " << stdxxx << " not empty\n"; print_diff(atf::fs::path("/dev/null"), path); result = false; } else if (oc.negated && is_empty) { std::cerr << "Fail: " << stdxxx << " is empty\n"; result = false; } else result = true; } else if (oc.type == oc_file) { const bool equals = compare_files(path, atf::fs::path(oc.value)); if (!oc.negated && !equals) { std::cerr << "Fail: " << stdxxx << " does not match golden " "output\n"; print_diff(atf::fs::path(oc.value), path); result = false; } else if (oc.negated && equals) { std::cerr << "Fail: " << stdxxx << " matches golden output\n"; cat_file(atf::fs::path(oc.value)); result = false; } else result = true; } else if (oc.type == oc_ignore) { result = true; } else if (oc.type == oc_inline) { temp_file temp("atf-check.XXXXXX"); temp.write(decode(oc.value)); temp.close(); const bool equals = compare_files(path, temp.get_path()); if (!oc.negated && !equals) { std::cerr << "Fail: " << stdxxx << " does not match expected " "value\n"; print_diff(temp.get_path(), path); result = false; } else if (oc.negated && equals) { std::cerr << "Fail: " << stdxxx << " matches expected value\n"; cat_file(temp.get_path()); result = false; } else result = true; } else if (oc.type == oc_match) { const bool matches = grep_file(path, oc.value); if (!oc.negated && !matches) { std::cerr << "Fail: regexp " + oc.value + " not in " << stdxxx << "\n"; cat_file(path); result = false; } else if (oc.negated && matches) { std::cerr << "Fail: regexp " + oc.value + " is in " << stdxxx << "\n"; cat_file(path); result = false; } else result = true; } else if (oc.type == oc_save) { INV(!oc.negated); std::ifstream ifs(path.c_str(), std::fstream::binary); ifs >> std::noskipws; std::istream_iterator< char > begin(ifs), end; std::ofstream ofs(oc.value.c_str(), std::fstream::binary | std::fstream::trunc); std::ostream_iterator obegin(ofs); std::copy(begin, end, obegin); result = true; } else { UNREACHABLE; result = false; } return result; } static bool run_output_checks(const std::vector< output_check >& checks, const atf::fs::path& path, const std::string& stdxxx) { bool ok = true; for (std::vector< output_check >::const_iterator iter = checks.begin(); iter != checks.end(); iter++) { ok &= run_output_check(*iter, path, stdxxx); } return ok; } // ------------------------------------------------------------------------ // The "atf_check" application. // ------------------------------------------------------------------------ namespace { class atf_check : public atf::application::app { + bool m_rflag; bool m_xflag; + useconds_t m_timo; + useconds_t m_interval; + std::vector< status_check > m_status_checks; std::vector< output_check > m_stdout_checks; std::vector< output_check > m_stderr_checks; static const char* m_description; bool run_output_checks(const atf::check::check_result&, const std::string&) const; std::string specific_args(void) const; options_set specific_options(void) const; void process_option(int, const char*); void process_option_s(const std::string&); public: atf_check(void); int main(void); }; } // anonymous namespace const char* atf_check::m_description = "atf-check executes given command and analyzes its results."; atf_check::atf_check(void) : app(m_description, "atf-check(1)"), + m_rflag(false), m_xflag(false) { } bool atf_check::run_output_checks(const atf::check::check_result& r, const std::string& stdxxx) const { if (stdxxx == "stdout") { return ::run_output_checks(m_stdout_checks, atf::fs::path(r.stdout_path()), "stdout"); } else if (stdxxx == "stderr") { return ::run_output_checks(m_stderr_checks, atf::fs::path(r.stderr_path()), "stderr"); } else { UNREACHABLE; return false; } } std::string atf_check::specific_args(void) const { return ""; } atf_check::options_set atf_check::specific_options(void) const { using atf::application::option; options_set opts; opts.insert(option('s', "qual:value", "Handle status. Qualifier " "must be one of: ignore exit: signal:")); opts.insert(option('o', "action:arg", "Handle stdout. Action must be " "one of: empty ignore file: inline: match:regexp " "save:")); opts.insert(option('e', "action:arg", "Handle stderr. Action must be " "one of: empty ignore file: inline: match:regexp " "save:")); + opts.insert(option('r', "timeout[:interval]", "Repeat failed check until " + "the timeout expires.")); opts.insert(option('x', "", "Execute command as a shell command")); return opts; } void atf_check::process_option(int ch, const char* arg) { switch (ch) { case 's': m_status_checks.push_back(parse_status_check_arg(arg)); break; case 'o': m_stdout_checks.push_back(parse_output_check_arg(arg)); break; case 'e': m_stderr_checks.push_back(parse_output_check_arg(arg)); break; + case 'r': + m_rflag = true; + parse_repeat_check_arg(arg, &m_timo, &m_interval); + break; + case 'x': m_xflag = true; break; default: UNREACHABLE; } } int atf_check::main(void) { if (m_argc < 1) throw atf::application::usage_error("No command specified"); int status = EXIT_FAILURE; - std::auto_ptr< atf::check::check_result > r = - m_xflag ? execute_with_shell(m_argv) : execute(m_argv); - if (m_status_checks.empty()) m_status_checks.push_back(status_check(sc_exit, false, EXIT_SUCCESS)); else if (m_status_checks.size() > 1) { // TODO: Remove this restriction. throw atf::application::usage_error("Cannot specify -s more than once"); } if (m_stdout_checks.empty()) m_stdout_checks.push_back(output_check(oc_empty, false, "")); if (m_stderr_checks.empty()) m_stderr_checks.push_back(output_check(oc_empty, false, "")); - if ((run_status_checks(m_status_checks, *r) == false) || - (run_output_checks(*r, "stderr") == false) || - (run_output_checks(*r, "stdout") == false)) - status = EXIT_FAILURE; - else - status = EXIT_SUCCESS; + do { + std::auto_ptr< atf::check::check_result > r = + m_xflag ? execute_with_shell(m_argv) : execute(m_argv); + + if ((run_status_checks(m_status_checks, *r) == false) || + (run_output_checks(*r, "stderr") == false) || + (run_output_checks(*r, "stdout") == false)) + status = EXIT_FAILURE; + else + status = EXIT_SUCCESS; + + if (m_rflag && status == EXIT_FAILURE) { + if (timo_expired(m_timo)) + break; + usleep(m_interval); + } + } while (m_rflag && status == EXIT_FAILURE); return status; } int main(int argc, char* const* argv) { return atf_check().run(argc, argv); } diff --git a/contrib/atf/atf-sh/atf-sh.3 b/contrib/atf/atf-sh/atf-sh.3 index 974c2aee1009..5d1119b2b5dc 100644 --- a/contrib/atf/atf-sh/atf-sh.3 +++ b/contrib/atf/atf-sh/atf-sh.3 @@ -1,375 +1,384 @@ .\" Copyright (c) 2008 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND .\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, .\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE .\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -.Dd March 6, 2017 +.Dd June 08, 2017 .Dt ATF-SH 3 .Os .Sh NAME .Nm atf_add_test_case , .Nm atf_check , .Nm atf_check_equal , +.Nm atf_check_not_equal , .Nm atf_config_get , .Nm atf_config_has , .Nm atf_expect_death , .Nm atf_expect_exit , .Nm atf_expect_fail , .Nm atf_expect_pass , .Nm atf_expect_signal , .Nm atf_expect_timeout , .Nm atf_fail , .Nm atf_get , .Nm atf_get_srcdir , .Nm atf_init_test_cases , .Nm atf_pass , .Nm atf_require_prog , .Nm atf_set , .Nm atf_skip , .Nm atf_test_case .Nd POSIX shell API to write ATF-based test programs .Sh SYNOPSIS .Nm atf_add_test_case .Qq name .Nm atf_check .Qq command .Nm atf_check_equal .Qq expected_expression .Qq actual_expression +.Nm atf_check_not_equal +.Qq expected_expression +.Qq actual_expression .Nm atf_config_get .Qq var_name .Nm atf_config_has .Qq var_name .Nm atf_expect_death .Qq reason .Qq ... .Nm atf_expect_exit .Qq exitcode .Qq reason .Qq ... .Nm atf_expect_fail .Qq reason .Qq ... .Nm atf_expect_pass .Qq .Nm atf_expect_signal .Qq signo .Qq reason .Qq ... .Nm atf_expect_timeout .Qq reason .Qq ... .Nm atf_fail .Qq reason .Nm atf_get .Qq var_name .Nm atf_get_srcdir .Nm atf_init_test_cases .Qq name .Nm atf_pass .Nm atf_require_prog .Qq prog_name .Nm atf_set .Qq var_name .Qq value .Nm atf_skip .Qq reason .Nm atf_test_case .Qq name .Qq cleanup .Sh DESCRIPTION ATF provides a simple but powerful interface to easily write test programs in the POSIX shell language. These are extremely helpful given that they are trivial to write due to the language simplicity and the great deal of available external tools, so they are often ideal to test other applications at the user level. .Pp Test programs written using this library must be run using the .Xr atf-sh 1 interpreter by putting the following on their very first line: .Bd -literal -offset indent #! /usr/bin/env atf-sh .Ed .Pp Shell-based test programs always follow this template: .Bd -literal -offset indent atf_test_case tc1 tc1_head() { ... first test case's header ... } tc1_body() { ... first test case's body ... } atf_test_case tc2 cleanup tc2_head() { ... second test case's header ... } tc2_body() { ... second test case's body ... } tc2_cleanup() { ... second test case's cleanup ... } \&... additional test cases ... atf_init_test_cases() { atf_add_test_case tc1 atf_add_test_case tc2 ... add additional test cases ... } .Ed .Ss Definition of test cases Test cases have an identifier and are composed of three different parts: the header, the body and an optional cleanup routine, all of which are described in .Xr atf-test-case 4 . To define test cases, one can use the .Nm atf_test_case function, which takes a first parameter specifying the test case's name and instructs the library to set things up to accept it as a valid test case. The second parameter is optional and, if provided, must be .Sq cleanup ; providing this parameter allows defining a cleanup routine for the test case. It is important to note that this function .Em does not set the test case up for execution when the program is run. In order to do so, a later registration is needed through the .Nm atf_add_test_case function detailed in .Sx Program initialization . .Pp Later on, one must define the three parts of the body by providing two or three functions (remember that the cleanup routine is optional). These functions are named after the test case's identifier, and are .Nm \*(Ltid\*(Gt_head , .Nm \*(Ltid\*(Gt_body and .Nm \*(Ltid\*(Gt_cleanup . None of these take parameters when executed. .Ss Program initialization The test program must define an .Nm atf_init_test_cases function, which is in charge of registering the test cases that will be executed at run time by using the .Nm atf_add_test_case function, which takes the name of a test case as its single parameter. This main function should not do anything else, except maybe sourcing auxiliary source files that define extra variables and functions. .Ss Configuration variables The test case has read-only access to the current configuration variables through the .Nm atf_config_has and .Nm atf_config_get methods. The former takes a single parameter specifying a variable name and returns a boolean indicating whether the variable is defined or not. The latter can take one or two parameters. If it takes only one, it specifies the variable from which to get the value, and this variable must be defined. If it takes two, the second one specifies a default value to be returned if the variable is not available. .Ss Access to the source directory It is possible to get the path to the test case's source directory from anywhere in the test program by using the .Nm atf_get_srcdir function. It is interesting to note that this can be used inside .Nm atf_init_test_cases to silently include additional helper files from the source directory. .Ss Requiring programs Aside from the .Va require.progs meta-data variable available in the header only, one can also check for additional programs in the test case's body by using the .Nm atf_require_prog function, which takes the base name or full path of a single binary. Relative paths are forbidden. If it is not found, the test case will be automatically skipped. .Ss Test case finalization The test case finalizes either when the body reaches its end, at which point the test is assumed to have .Em passed , or at any explicit call to .Nm atf_pass , .Nm atf_fail or .Nm atf_skip . These three functions terminate the execution of the test case immediately. The cleanup routine will be processed afterwards in a completely automated way, regardless of the test case's termination reason. .Pp .Nm atf_pass does not take any parameters. .Nm atf_fail and .Nm atf_skip take a single string parameter that describes why the test case failed or was skipped, respectively. It is very important to provide a clear error message in both cases so that the user can quickly know why the test did not pass. .Ss Expectations Everything explained in the previous section changes when the test case expectations are redefined by the programmer. .Pp Each test case has an internal state called .Sq expect that describes what the test case expectations are at any point in time. The value of this property can change during execution by any of: .Bl -tag -width indent .It Nm atf_expect_death Qo reason Qc Qo ... Qc Expects the test case to exit prematurely regardless of the nature of the exit. .It Nm atf_expect_exit Qo exitcode Qc Qo reason Qc Qo ... Qc Expects the test case to exit cleanly. If .Va exitcode is not .Sq -1 , the runtime engine will validate that the exit code of the test case matches the one provided in this call. Otherwise, the exact value will be ignored. .It Nm atf_expect_fail Qo reason Qc Any failure raised in this mode is recorded, but such failures do not report the test case as failed; instead, the test case finalizes cleanly and is reported as .Sq expected failure ; this report includes the provided .Fa reason as part of it. If no error is raised while running in this mode, then the test case is reported as .Sq failed . .Pp This mode is useful to reproduce actual known bugs in tests. Whenever the developer fixes the bug later on, the test case will start reporting a failure, signaling the developer that the test case must be adjusted to the new conditions. In this situation, it is useful, for example, to set .Fa reason as the bug number for tracking purposes. .It Nm atf_expect_pass This is the normal mode of execution. In this mode, any failure is reported as such to the user and the test case is marked as .Sq failed . .It Nm atf_expect_signal Qo signo Qc Qo reason Qc Qo ... Qc Expects the test case to terminate due to the reception of a signal. If .Va signo is not .Sq -1 , the runtime engine will validate that the signal that terminated the test case matches the one provided in this call. Otherwise, the exact value will be ignored. .It Nm atf_expect_timeout Qo reason Qc Qo ... Qc Expects the test case to execute for longer than its timeout. .El .Ss Helper functions for common checks .Bl -tag -width indent .It Nm atf_check Qo [options] Qc Qo command Qc Qo [args] Qc Executes a command, performs checks on its exit code and its output, and fails the test case if any of the checks is not successful. This function is particularly useful in integration tests that verify the correct functioning of a binary. .Pp Internally, this function is just a wrapper over the .Xr atf-check 1 tool (whose manual page provides all details on the calling syntax). You should always use the .Nm atf_check function instead of the .Xr atf-check 1 tool in your scripts; the latter is not even in the path. .It Nm atf_check_equal Qo expected_expression Qc Qo actual_expression Qc This function takes two expressions, evaluates them and, if their results differ, aborts the test case with an appropriate failure message. The common style is to put the expected value in the first parameter and the actual value in the second parameter. +.It Nm atf_check_not_equal Qo expected_expression Qc Qo actual_expression Qc +This function takes two expressions, evaluates them and, if their +results are equal, aborts the test case with an appropriate failure message. +The common style is to put the expected value in the first parameter and the +actual value in the second parameter. .El .Sh EXAMPLES The following shows a complete test program with a single test case that validates the addition operator: .Bd -literal -offset indent atf_test_case addition addition_head() { atf_set "descr" "Sample tests for the addition operator" } addition_body() { atf_check_equal 0 $((0 + 0)) atf_check_equal 1 $((0 + 1)) atf_check_equal 1 $((1 + 0)) atf_check_equal 2 $((1 + 1)) atf_check_equal 300 $((100 + 200)) } atf_init_test_cases() { atf_add_test_case addition } .Ed .Pp This other example shows how to include a file with extra helper functions in the test program: .Bd -literal -offset indent \&... definition of test cases ... atf_init_test_cases() { . $(atf_get_srcdir)/helper_functions.sh atf_add_test_case foo1 atf_add_test_case foo2 } .Ed .Pp This example demonstrates the use of the very useful .Nm atf_check function: .Bd -literal -offset indent # Check for silent output atf_check -s exit:0 -o empty -e empty 'true' # Check for silent output and failure atf_check -s exit:1 -o empty -e empty 'false' # Check for known stdout and silent stderr echo foo >expout atf_check -s exit:0 -o file:expout -e empty 'echo foo' # Generate a file for later inspection atf_check -s exit:0 -o save:stdout -e empty 'ls' grep foo ls || atf_fail "foo file not found in listing" # Or just do the match along the way atf_check -s exit:0 -o match:"^foo$" -e empty 'ls' .Ed .Sh SEE ALSO .Xr atf-check 1 , .Xr atf-sh 1 , .Xr atf-test-program 1 , .Xr atf-test-case 4 diff --git a/contrib/atf/atf-sh/atf_check_test.sh b/contrib/atf/atf-sh/atf_check_test.sh index 9e3cfb955f68..6fe2bb775274 100644 --- a/contrib/atf/atf-sh/atf_check_test.sh +++ b/contrib/atf/atf-sh/atf_check_test.sh @@ -1,197 +1,221 @@ # Copyright (c) 2007 The NetBSD Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # TODO: Bring in the checks in the bootstrap testsuite for atf_check. atf_test_case info_ok info_ok_head() { atf_set "descr" "Verifies that atf_check prints an informative" \ "message even when the command is successful" } info_ok_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:0 -o save:stdout -e save:stderr -x \ "${h} atf_check_info_ok" grep 'Executing command.*true' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" atf_check -s eq:0 -o save:stdout -e save:stderr -x \ "${h} atf_check_info_fail" grep 'Executing command.*false' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" } atf_test_case expout_mismatch expout_mismatch_head() { atf_set "descr" "Verifies that atf_check prints a diff of the" \ "stdout and the expected stdout if the two do not" \ "match" } expout_mismatch_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:1 -o save:stdout -e save:stderr -x \ "${h} atf_check_expout_mismatch" grep 'Executing command.*echo bar' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" grep 'stdout does not match golden output' stderr >/dev/null || \ atf_fail "atf_check does not print the stdout header" grep 'stderr' stderr >/dev/null && \ atf_fail "atf_check prints the stderr header" grep '^-foo' stderr >/dev/null || \ atf_fail "atf_check does not print the stdout's diff" grep '^+bar' stderr >/dev/null || \ atf_fail "atf_check does not print the stdout's diff" } atf_test_case experr_mismatch experr_mismatch_head() { atf_set "descr" "Verifies that atf_check prints a diff of the" \ "stderr and the expected stderr if the two do not" \ "match" } experr_mismatch_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:1 -o save:stdout -e save:stderr -x \ "${h} atf_check_experr_mismatch" grep 'Executing command.*echo bar' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" grep 'stdout' stderr >/dev/null && \ atf_fail "atf_check prints the stdout header" grep 'stderr does not match golden output' stderr >/dev/null || \ atf_fail "atf_check does not print the stderr header" grep '^-foo' stderr >/dev/null || \ atf_fail "atf_check does not print the stderr's diff" grep '^+bar' stderr >/dev/null || \ atf_fail "atf_check does not print the stderr's diff" } atf_test_case null_stdout null_stdout_head() { atf_set "descr" "Verifies that atf_check prints a the stdout it got" \ "when it was supposed to be null" } null_stdout_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:1 -o save:stdout -e save:stderr -x \ "${h} atf_check_null_stdout" grep 'Executing command.*echo.*These.*contents' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" grep 'stdout not empty' stderr >/dev/null || \ atf_fail "atf_check does not print the stdout header" grep 'stderr' stderr >/dev/null && \ atf_fail "atf_check prints the stderr header" grep 'These are the contents' stderr >/dev/null || \ atf_fail "atf_check does not print stdout's contents" } atf_test_case null_stderr null_stderr_head() { atf_set "descr" "Verifies that atf_check prints a the stderr it got" \ "when it was supposed to be null" } null_stderr_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:1 -o save:stdout -e save:stderr -x \ "${h} atf_check_null_stderr" grep 'Executing command.*echo.*These.*contents' stdout >/dev/null || \ atf_fail "atf_check does not print an informative message" grep 'stdout' stderr >/dev/null && \ atf_fail "atf_check prints the stdout header" grep 'stderr not empty' stderr >/dev/null || \ atf_fail "atf_check does not print the stderr header" grep 'These are the contents' stderr >/dev/null || \ atf_fail "atf_check does not print stderr's contents" } atf_test_case equal equal_head() { atf_set "descr" "Verifies that atf_check_equal works" } equal_body() { h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_equal_ok" atf_check -s eq:1 -o ignore -e ignore -x \ "${h} -r resfile atf_check_equal_fail" atf_check -s eq:0 -o ignore -e empty grep '^failed: a != b (a != b)$' \ resfile atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_equal_eval_ok" atf_check -s eq:1 -o ignore -e ignore -x \ "${h} -r resfile atf_check_equal_eval_fail" atf_check -s eq:0 -o ignore -e empty \ grep '^failed: \${x} != \${y} (a != b)$' resfile } +atf_test_case not_equal +not_equal_head() +{ + atf_set "descr" "Verifies that atf_check_not_equal works" +} +not_equal_body() +{ + h="$(atf_get_srcdir)/misc_helpers -s $(atf_get_srcdir)" + + atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_not_equal_ok" + + atf_check -s eq:1 -o ignore -e ignore -x \ + "${h} -r resfile atf_check_not_equal_fail" + atf_check -s eq:0 -o ignore -e empty grep '^failed: a == b (a == b)$' \ + resfile + + atf_check -s eq:0 -o ignore -e ignore -x "${h} atf_check_not_equal_eval_ok" + + atf_check -s eq:1 -o ignore -e ignore -x \ + "${h} -r resfile atf_check_not_equal_eval_fail" + atf_check -s eq:0 -o ignore -e empty \ + grep '^failed: \${x} == \${y} (a == b)$' resfile +} + atf_test_case flush_stdout_on_death flush_stdout_on_death_body() { CONTROL_FILE="$(pwd)/done" "$(atf_get_srcdir)/misc_helpers" \ -s "$(atf_get_srcdir)" atf_check_flush_stdout >out 2>err & pid="${!}" while [ ! -f ./done ]; do echo "Still waiting for helper to create control file" ls sleep 1 done kill -9 "${pid}" grep 'Executing command.*true' out \ || atf_fail 'First command not in output' grep 'Executing command.*false' out \ || atf_fail 'Second command not in output' } atf_init_test_cases() { atf_add_test_case info_ok atf_add_test_case expout_mismatch atf_add_test_case experr_mismatch atf_add_test_case null_stdout atf_add_test_case null_stderr atf_add_test_case equal atf_add_test_case flush_stdout_on_death } # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/contrib/atf/atf-sh/libatf-sh.subr b/contrib/atf/atf-sh/libatf-sh.subr index a078975400af..a2478b6a9be1 100644 --- a/contrib/atf/atf-sh/libatf-sh.subr +++ b/contrib/atf/atf-sh/libatf-sh.subr @@ -1,770 +1,798 @@ # Copyright (c) 2007 The NetBSD Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ------------------------------------------------------------------------ # GLOBAL VARIABLES # ------------------------------------------------------------------------ # Values for the expect property. Expect=pass Expect_Reason= # A boolean variable that indicates whether we are parsing a test case's # head or not. Parsing_Head=false # The program name. Prog_Name=${0##*/} # The file to which the test case will print its result. Results_File= # The test program's source directory: i.e. where its auxiliary data files # and helper utilities can be found. Can be overriden through the '-s' flag. Source_Dir="$(dirname ${0})" # Indicates the test case we are currently processing. Test_Case= # List of meta-data variables for the current test case. Test_Case_Vars= # The list of all test cases provided by the test program. Test_Cases= # ------------------------------------------------------------------------ # PUBLIC INTERFACE # ------------------------------------------------------------------------ # # atf_add_test_case tc-name # # Adds the given test case to the list of test cases that form the test # program. The name provided here must be accompanied by two functions # named after it: _head and _body, and optionally by # a _cleanup function. # atf_add_test_case() { Test_Cases="${Test_Cases} ${1}" } # # atf_check cmd expcode expout experr # # Executes atf-check with given arguments and automatically calls # atf_fail in case of failure. # atf_check() { ${Atf_Check} "${@}" || \ atf_fail "atf-check failed; see the output of the test for details" } # # atf_check_equal expected_expression actual_expression # # Checks that expected_expression's value matches actual_expression's # and, if not, raises an error. Ideally expected_expression and # actual_expression should be provided quoted (not expanded) so that # the error message is helpful; otherwise it will only show the values, # not the expressions themselves. # atf_check_equal() { eval _val1=\"${1}\" eval _val2=\"${2}\" test "${_val1}" = "${_val2}" || \ atf_fail "${1} != ${2} (${_val1} != ${_val2})" } +# +# atf_check_not_equal expected_expression actual_expression +# +# Checks that expected_expression's value does not match actual_expression's +# and, if it does, raises an error. Ideally expected_expression and +# actual_expression should be provided quoted (not expanded) so that +# the error message is helpful; otherwise it will only show the values, +# not the expressions themselves. +# +atf_check_not_equal() +{ + eval _val1=\"${1}\" + eval _val2=\"${2}\" + test "${_val1}" != "${_val2}" || \ + atf_fail "${1} == ${2} (${_val1} == ${_val2})" +} + # # atf_config_get varname [defvalue] # # Prints the value of a configuration variable. If it is not # defined, prints the given default value. # atf_config_get() { _varname="__tc_config_var_$(_atf_normalize ${1})" if [ ${#} -eq 1 ]; then eval _value=\"\${${_varname}-__unset__}\" [ "${_value}" = __unset__ ] && \ _atf_error 1 "Could not find configuration variable \`${1}'" echo ${_value} elif [ ${#} -eq 2 ]; then eval echo \${${_varname}-${2}} else _atf_error 1 "Incorrect number of parameters for atf_config_get" fi } # # atf_config_has varname # # Returns a boolean indicating if the given configuration variable is # defined or not. # atf_config_has() { _varname="__tc_config_var_$(_atf_normalize ${1})" eval _value=\"\${${_varname}-__unset__}\" [ "${_value}" != __unset__ ] } # # atf_expect_death reason # # Sets the expectations to 'death'. # atf_expect_death() { _atf_validate_expect Expect=death _atf_create_resfile "expected_death: ${*}" } # # atf_expect_timeout reason # # Sets the expectations to 'timeout'. # atf_expect_timeout() { _atf_validate_expect Expect=timeout _atf_create_resfile "expected_timeout: ${*}" } # # atf_expect_exit exitcode reason # # Sets the expectations to 'exit'. # atf_expect_exit() { _exitcode="${1}"; shift _atf_validate_expect Expect=exit if [ "${_exitcode}" = "-1" ]; then _atf_create_resfile "expected_exit: ${*}" else _atf_create_resfile "expected_exit(${_exitcode}): ${*}" fi } # # atf_expect_fail reason # # Sets the expectations to 'fail'. # atf_expect_fail() { _atf_validate_expect Expect=fail Expect_Reason="${*}" } # # atf_expect_pass # # Sets the expectations to 'pass'. # atf_expect_pass() { _atf_validate_expect Expect=pass Expect_Reason= } # # atf_expect_signal signo reason # # Sets the expectations to 'signal'. # atf_expect_signal() { _signo="${1}"; shift _atf_validate_expect Expect=signal if [ "${_signo}" = "-1" ]; then _atf_create_resfile "expected_signal: ${*}" else _atf_create_resfile "expected_signal(${_signo}): ${*}" fi } # # atf_expected_failure msg1 [.. msgN] # # Makes the test case report an expected failure with the given error # message. Multiple words can be provided, which are concatenated with # a single blank space. # atf_expected_failure() { _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}" exit 0 } # # atf_fail msg1 [.. msgN] # # Makes the test case fail with the given error message. Multiple # words can be provided, in which case they are joined by a single # blank space. # atf_fail() { case "${Expect}" in fail) atf_expected_failure "${@}" ;; pass) _atf_create_resfile "failed: ${*}" exit 1 ;; *) _atf_error 128 "Unreachable" ;; esac } # # atf_get varname # # Prints the value of a test case-specific variable. Given that one # should not get the value of non-existent variables, it is fine to # always use this function as 'val=$(atf_get var)'. # atf_get() { eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})} } # # atf_get_srcdir # # Prints the value of the test case's source directory. # atf_get_srcdir() { echo ${Source_Dir} } # # atf_pass # # Makes the test case pass. Shouldn't be used in general, as a test # case that does not explicitly fail is assumed to pass. # atf_pass() { case "${Expect}" in fail) Expect=pass atf_fail "Test case was expecting a failure but got a pass instead" ;; pass) _atf_create_resfile passed exit 0 ;; *) _atf_error 128 "Unreachable" ;; esac } # # atf_require_prog prog # # Checks that the given program name (either provided as an absolute # path or as a plain file name) can be found. If it is not available, # automatically skips the test case with an appropriate message. # # Relative paths are not allowed because the test case cannot predict # where it will be executed from. # atf_require_prog() { _prog= case ${1} in /*) _prog="${1}" [ -x ${_prog} ] || \ atf_skip "The required program ${1} could not be found" ;; */*) atf_fail "atf_require_prog does not accept relative path names \`${1}'" ;; *) _prog=$(_atf_find_in_path "${1}") [ -n "${_prog}" ] || \ atf_skip "The required program ${1} could not be found" \ "in the PATH" ;; esac } # # atf_set varname val1 [.. valN] # # Sets the test case's variable 'varname' to the specified values # which are concatenated using a single blank space. This function # is supposed to be called form the test case's head only. # atf_set() { ${Parsing_Head} || \ _atf_error 128 "atf_set called from the test case's body" Test_Case_Vars="${Test_Case_Vars} ${1}" _var=$(_atf_normalize ${1}); shift eval __tc_var_${Test_Case}_${_var}=\"\${*}\" } # # atf_skip msg1 [.. msgN] # # Skips the test case because of the reason provided. Multiple words # can be given, in which case they are joined by a single blank space. # atf_skip() { _atf_create_resfile "skipped: ${*}" exit 0 } # # atf_test_case tc-name cleanup # # Defines a new test case named tc-name. The name provided here must be # accompanied by two functions named after it: _head and # _body. If cleanup is set to 'cleanup', then this also expects # a _cleanup function to be defined. # atf_test_case() { eval "${1}_head() { :; }" eval "${1}_body() { atf_fail 'Test case not implemented'; }" if [ "${2}" = cleanup ]; then eval __has_cleanup_${1}=true eval "${1}_cleanup() { :; }" else eval "${1}_cleanup() { _atf_error 1 'Test case ${1} declared without a cleanup routine'; }" fi } # ------------------------------------------------------------------------ # PRIVATE INTERFACE # ------------------------------------------------------------------------ # # _atf_config_set varname val1 [.. valN] # # Sets the test case's private variable 'varname' to the specified # values which are concatenated using a single blank space. # _atf_config_set() { _var=$(_atf_normalize ${1}); shift eval __tc_config_var_${_var}=\"\${*}\" Config_Vars="${Config_Vars} __tc_config_var_${_var}" } # # _atf_config_set_str varname=val # # Sets the test case's private variable 'varname' to the specified # value. The parameter is of the form 'varname=val'. # _atf_config_set_from_str() { _oldifs=${IFS} IFS='=' set -- ${*} _var=${1} shift _val="${@}" IFS=${_oldifs} _atf_config_set "${_var}" "${_val}" } # # _atf_create_resfile contents # # Creates the results file. # _atf_create_resfile() { if [ -n "${Results_File}" ]; then echo "${*}" >"${Results_File}" || \ _atf_error 128 "Cannot create results file '${Results_File}'" else echo "${*}" fi } # # _atf_error error_code [msg1 [.. msgN]] # # Prints the given error message (which can be composed of multiple # arguments, in which case are joined by a single space) and exits # with the specified error code. # # This must not be used by test programs themselves (hence making # the function private) to indicate a test case's failure. They # have to use the atf_fail function. # _atf_error() { _error_code="${1}"; shift echo "${Prog_Name}: ERROR:" "$@" 1>&2 exit ${_error_code} } # # _atf_warning msg1 [.. msgN] # # Prints the given warning message (which can be composed of multiple # arguments, in which case are joined by a single space). # _atf_warning() { echo "${Prog_Name}: WARNING:" "$@" 1>&2 } # # _atf_find_in_path program # # Looks for a program in the path and prints the full path to it or # nothing if it could not be found. It also returns true in case of # success. # _atf_find_in_path() { _prog="${1}" _oldifs=${IFS} IFS=: for _dir in ${PATH} do if [ -x ${_dir}/${_prog} ]; then IFS=${_oldifs} echo ${_dir}/${_prog} return 0 fi done IFS=${_oldifs} return 1 } # # _atf_has_tc name # # Returns true if the given test case exists. # _atf_has_tc() { for _tc in ${Test_Cases}; do [ "${_tc}" != "${1}" ] || return 0 done return 1 } # # _atf_list_tcs # # Describes all test cases and prints the list to the standard output. # _atf_list_tcs() { echo 'Content-Type: application/X-atf-tp; version="1"' echo set -- ${Test_Cases} while [ ${#} -gt 0 ]; do _atf_parse_head ${1} echo "ident: $(atf_get ident)" for _var in ${Test_Case_Vars}; do [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})" done [ ${#} -gt 1 ] && echo shift done } # # _atf_normalize str # # Normalizes a string so that it is a valid shell variable name. # _atf_normalize() { - echo ${1} | tr .- __ + # Check if the string contains any of the forbidden characters using + # POSIX parameter expansion (the ${var//} string substitution is + # unfortunately not supported in POSIX sh) and only use tr(1) then. + # tr(1) is generally not a builtin, so doing the substring check first + # avoids unnecessary fork()+execve() calls. As this function is called + # many times in each test script startup, those overheads add up + # (especially when running on emulated platforms such as QEMU). + if [ "${1#*[.-]}" != "$1" ]; then + echo "$1" | tr .- __ + else + echo "$1" + fi } # # _atf_parse_head tcname # # Evaluates a test case's head to gather its variables and prepares the # test program to run it. # _atf_parse_head() { Parsing_Head=true Test_Case="${1}" Test_Case_Vars= if _atf_has_cleanup "${1}"; then atf_set has.cleanup "true" fi ${1}_head atf_set ident "${1}" Parsing_Head=false } # # _atf_run_tc tc # # Runs the specified test case. Prints its exit status to the # standard output and returns a boolean indicating if the test was # successful or not. # _atf_run_tc() { case ${1} in *:*) _tcname=${1%%:*} _tcpart=${1#*:} if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then _atf_syntax_error "Unknown test case part \`${_tcpart}'" fi ;; *) _tcname=${1} _tcpart=body ;; esac _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'" if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then _atf_warning "Running test cases outside of kyua(1) is unsupported" _atf_warning "No isolation nor timeout control is being applied;" \ "you may get unexpected failures; see atf-test-case(4)" fi _atf_parse_head ${_tcname} case ${_tcpart} in body) if ${_tcname}_body; then _atf_validate_expect _atf_create_resfile passed else Expect=pass atf_fail "Test case body returned a non-ok exit code, but" \ "this is not allowed" fi ;; cleanup) if _atf_has_cleanup "${_tcname}"; then ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \ "returned a non-ok exit code, but this is not allowed" fi ;; *) _atf_error 128 "Unknown test case part" ;; esac } # # _atf_syntax_error msg1 [.. msgN] # # Formats and prints a syntax error message and terminates the # program prematurely. # _atf_syntax_error() { echo "${Prog_Name}: ERROR: ${@}" 1>&2 echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2 exit 1 } # # _atf_has_cleanup tc-name # # Returns a boolean indicating if the given test case has a cleanup # routine or not. # _atf_has_cleanup() { _found=true eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false" [ "${_found}" = true ] } # # _atf_validate_expect # # Ensures that the current test case state is correct regarding the expect # status. # _atf_validate_expect() { case "${Expect}" in death) Expect=pass atf_fail "Test case was expected to terminate abruptly but it" \ "continued execution" ;; exit) Expect=pass atf_fail "Test case was expected to exit cleanly but it continued" \ "execution" ;; fail) Expect=pass atf_fail "Test case was expecting a failure but none were raised" ;; pass) ;; signal) Expect=pass atf_fail "Test case was expected to receive a termination signal" \ "but it continued execution" ;; timeout) Expect=pass atf_fail "Test case was expected to hang but it continued execution" ;; *) _atf_error 128 "Unreachable" ;; esac } # # _atf_warning [msg1 [.. msgN]] # # Prints the given warning message (which can be composed of multiple # arguments, in which case are joined by a single space). # # This must not be used by test programs themselves (hence making # the function private). # _atf_warning() { echo "${Prog_Name}: WARNING:" "$@" 1>&2 } # # main [options] test_case # # Test program's entry point. # main() { # Process command-line options first. _numargs=${#} _lflag=false while getopts :lr:s:v: arg; do case ${arg} in l) _lflag=true ;; r) Results_File=${OPTARG} ;; s) Source_Dir=${OPTARG} ;; v) _atf_config_set_from_str "${OPTARG}" ;; \?) _atf_syntax_error "Unknown option -${OPTARG}." # NOTREACHED ;; esac done - shift `expr ${OPTIND} - 1` + shift $((OPTIND - 1)) case ${Source_Dir} in /*) ;; *) Source_Dir=$(pwd)/${Source_Dir} ;; esac [ -f ${Source_Dir}/${Prog_Name} ] || \ _atf_error 1 "Cannot find the test program in the source" \ "directory \`${Source_Dir}'" # Call the test program's hook to register all available test cases. atf_init_test_cases # Run or list test cases. if `${_lflag}`; then if [ ${#} -gt 0 ]; then _atf_syntax_error "Cannot provide test case names with -l" fi _atf_list_tcs else if [ ${#} -eq 0 ]; then _atf_syntax_error "Must provide a test case name" elif [ ${#} -gt 1 ]; then _atf_syntax_error "Cannot provide more than one test case name" else _atf_run_tc "${1}" fi fi } # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/contrib/atf/atf-sh/misc_helpers.sh b/contrib/atf/atf-sh/misc_helpers.sh index ca0f4650d99b..a2b2c0b53d73 100644 --- a/contrib/atf/atf-sh/misc_helpers.sh +++ b/contrib/atf/atf-sh/misc_helpers.sh @@ -1,309 +1,357 @@ # Copyright (c) 2007 The NetBSD Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ------------------------------------------------------------------------- # Helper tests for "t_atf_check". # ------------------------------------------------------------------------- atf_test_case atf_check_info_ok atf_check_info_ok_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_info_ok_body() { atf_check -s eq:0 -o empty -e empty true } atf_test_case atf_check_info_fail atf_check_info_fail_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_info_fail_body() { # In Solaris, /usr/bin/false returns 255 rather than 1. Use the # built-in version for the check. atf_check -s eq:1 -o empty -e empty sh -c "false" } atf_test_case atf_check_expout_mismatch atf_check_expout_mismatch_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_expout_mismatch_body() { cat >expout <experr <&2' } atf_test_case atf_check_null_stdout atf_check_null_stdout_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_null_stdout_body() { atf_check -s eq:0 -o empty -e empty echo "These are the contents" } atf_test_case atf_check_null_stderr atf_check_null_stderr_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_null_stderr_body() { atf_check -s eq:0 -o empty -e empty -x 'echo "These are the contents" 1>&2' } atf_test_case atf_check_equal_ok atf_check_equal_ok_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_equal_ok_body() { atf_check_equal a a } atf_test_case atf_check_equal_fail atf_check_equal_fail_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_equal_fail_body() { atf_check_equal a b } atf_test_case atf_check_equal_eval_ok atf_check_equal_eval_ok_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_equal_eval_ok_body() { x=a y=a atf_check_equal '${x}' '${y}' } atf_test_case atf_check_equal_eval_fail atf_check_equal_eval_fail_head() { atf_set "descr" "Helper test case for the t_atf_check test program" } atf_check_equal_eval_fail_body() { x=a y=b atf_check_equal '${x}' '${y}' } +atf_test_case atf_check_not_equal_ok +atf_check_not_equal_ok_head() +{ + atf_set "descr" "Helper test case for the t_atf_check test program" +} +atf_check_not_equal_ok_body() +{ + atf_check_not_equal a b +} + +atf_test_case atf_check_not_equal_fail +atf_check_not_equal_fail_head() +{ + atf_set "descr" "Helper test case for the t_atf_check test program" +} +atf_check_not_equal_fail_body() +{ + atf_check_not_equal a a +} + +atf_test_case atf_check_not_equal_eval_ok +atf_check_not_equal_eval_ok_head() +{ + atf_set "descr" "Helper test case for the t_atf_check test program" +} +atf_check_not_equal_eval_ok_body() +{ + x=a + y=b + atf_check_not_equal '${x}' '${y}' +} + +atf_test_case atf_check_not_equal_eval_fail +atf_check_not_equal_eval_fail_head() +{ + atf_set "descr" "Helper test case for the t_atf_check test program" +} +atf_check_not_equal_eval_fail_body() +{ + x=a + y=a + atf_check_not_equal '${x}' '${y}' +} + atf_test_case atf_check_flush_stdout atf_check_flush_stdout_head() { atf_set "descr" "Helper test case for the t_atf_check test program" atf_set "timeout" "30" } atf_check_flush_stdout_body() { atf_check true atf_check -s exit:1 false touch "${CONTROL_FILE:-done}" while :; do sleep 1 done } # ------------------------------------------------------------------------- # Helper tests for "t_config". # ------------------------------------------------------------------------- atf_test_case config_get config_get_head() { atf_set "descr" "Helper test case for the t_config test program" } config_get_body() { if atf_config_has ${TEST_VARIABLE}; then echo "${TEST_VARIABLE} = $(atf_config_get ${TEST_VARIABLE})" fi } atf_test_case config_has config_has_head() { atf_set "descr" "Helper test case for the t_config test program" } config_has_body() { if atf_config_has ${TEST_VARIABLE}; then echo "${TEST_VARIABLE} found" else echo "${TEST_VARIABLE} not found" fi } # ------------------------------------------------------------------------- # Helper tests for "t_normalize". # ------------------------------------------------------------------------- atf_test_case normalize normalize_head() { atf_set "descr" "Helper test case for the t_normalize test program" atf_set "a.b" "test value 1" atf_set "c-d" "test value 2" } normalize_body() { echo "a.b: $(atf_get a.b)" echo "c-d: $(atf_get c-d)" } # ------------------------------------------------------------------------- # Helper tests for "t_tc". # ------------------------------------------------------------------------- atf_test_case tc_pass_true tc_pass_true_head() { atf_set "descr" "Helper test case for the t_tc test program" } tc_pass_true_body() { true } atf_test_case tc_pass_false tc_pass_false_head() { atf_set "descr" "Helper test case for the t_tc test program" } tc_pass_false_body() { false } atf_test_case tc_pass_return_error tc_pass_return_error_head() { atf_set "descr" "Helper test case for the t_tc test program" } tc_pass_return_error_body() { return 1 } atf_test_case tc_fail tc_fail_head() { atf_set "descr" "Helper test case for the t_tc test program" } tc_fail_body() { echo "An error" 1>&2 exit 1 } atf_test_case tc_missing_body tc_missing_body_head() { atf_set "descr" "Helper test case for the t_tc test program" } # ------------------------------------------------------------------------- # Helper tests for "t_tp". # ------------------------------------------------------------------------- atf_test_case tp_srcdir tp_srcdir_head() { atf_set "descr" "Helper test case for the t_tp test program" } tp_srcdir_body() { echo "Calling helper" helper_subr || atf_fail "Could not call helper subroutine" } # ------------------------------------------------------------------------- # Main. # ------------------------------------------------------------------------- atf_init_test_cases() { # Add helper tests for t_atf_check. atf_add_test_case atf_check_info_ok atf_add_test_case atf_check_info_fail atf_add_test_case atf_check_expout_mismatch atf_add_test_case atf_check_experr_mismatch atf_add_test_case atf_check_null_stdout atf_add_test_case atf_check_null_stderr atf_add_test_case atf_check_equal_ok atf_add_test_case atf_check_equal_fail atf_add_test_case atf_check_equal_eval_ok atf_add_test_case atf_check_equal_eval_fail + atf_add_test_case atf_check_not_equal_ok + atf_add_test_case atf_check_not_equal_fail + atf_add_test_case atf_check_not_equal_eval_ok + atf_add_test_case atf_check_not_equal_eval_fail atf_add_test_case atf_check_flush_stdout # Add helper tests for t_config. atf_add_test_case config_get atf_add_test_case config_has # Add helper tests for t_normalize. atf_add_test_case normalize # Add helper tests for t_tc. atf_add_test_case tc_pass_true atf_add_test_case tc_pass_false atf_add_test_case tc_pass_return_error atf_add_test_case tc_fail atf_add_test_case tc_missing_body # Add helper tests for t_tp. [ -f $(atf_get_srcdir)/subrs ] && . $(atf_get_srcdir)/subrs atf_add_test_case tp_srcdir } # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 diff --git a/contrib/atf/doc/.gitignore b/contrib/atf/doc/.gitignore new file mode 100644 index 000000000000..7c3185645015 --- /dev/null +++ b/contrib/atf/doc/.gitignore @@ -0,0 +1 @@ +atf.7 diff --git a/contrib/atf/doc/atf-test-case.4 b/contrib/atf/doc/atf-test-case.4 index 34f5e1bce1e9..46690bdcb0ef 100644 --- a/contrib/atf/doc/atf-test-case.4 +++ b/contrib/atf/doc/atf-test-case.4 @@ -1,321 +1,328 @@ .\" Copyright (c) 2007 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND .\" CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, .\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE .\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .Dd March 6, 2017 .Dt ATF-TEST-CASE 4 .Os .Sh NAME .Nm atf-test-case .Nd generic description of test cases .Sh DESCRIPTION A .Em test case is a piece of code that stress-tests a specific feature of the software. This feature is typically self-contained enough, either in the amount of code that implements it or in the general idea that describes it, to warrant its independent testing. Given this, test cases are very fine-grained, but they attempt to group similar smaller tests which are semantically related. .Pp A test case is defined by three components regardless of the language it is implemented in: a header, a body and a cleanup routine. The .Em header is, basically, a declarative piece of code that defines several properties to describe what the test case does and how it behaves. In other words: it defines the test case's .Em meta-data , further described in the .Sx Meta-data section. The .Em body is the test case itself. It executes all actions needed to reproduce the test, and checks for failures. This body is only executed if the abstract conditions specified by the header are met. The .Em cleanup routine is a piece of code always executed after the body, regardless of the exit status of the test case. It can be used to undo side-effects of the test case. Note that almost all side-effects of a test case are automatically cleaned up by the library; this is explained in more detail in the rest of this document. .Pp It is extremely important to keep the separation between a test case's header and body well-defined, because the header is .Em always parsed, whereas the body is only executed when the conditions defined in the header are met and when the user specifies that test case. .Pp At last, test cases are always contained into test programs. The test programs act as a front-end to them, providing a consistent interface to the user and several APIs to ease their implementation. .Ss Results Upon termination, a test case reports a status and, optionally, a textual reason describing why the test reported such status. The caller must ensure that the test case really performed the task that its status describes, as the test program may be bogus and therefore providing a misleading result, e.g., providing a result that indicates success but the error code of the program says otherwise. .Pp The possible exit status of a test case are one of the following: .Bl -tag -width expectedXfailureXX .It expected_death The test case expects to terminate abruptly. .It expected_exit The test case expects to exit cleanly. .It expected_failure The test case expects to exit with a controller fatal/non-fatal failure. If this happens, the test program exits with a success error code. .It expected_signal The test case expects to receive a signal that makes it terminate. .It expected_timeout The test case expects to execute for longer than its timeout. .It passed The test case was executed successfully. The test program exits with a success error code. .It skipped The test case could not be executed because some preconditions were not met. This is not a failure because it can typically be resolved by adjusting the system to meet the necessary conditions. This is always accompanied by a .Em reason , a message describing why the test was skipped. The test program exits with a success error code. .It failed An error appeared during the execution of the test case. This is always accompanied by a .Em reason , a message describing why the test failed. The test program exits with a failure error code. .El .Pp The usefulness of the .Sq expected_* results comes when writing test cases that verify known failures caused, in general, due to programming errors (aka bugs). Whenever the faulty condition that the .Sq expected_* result is trying to cover is fixed, then the test case will be reported as .Sq failed and the developer will have to adjust it to match its new condition. .Pp It is important to note that all .Sq expected_* results are only provided as a .Em hint to the caller; the caller must verify that the test case did actually terminate as the expected condition says. .Ss Input/output Test cases are free to print whatever they want to their .Xr stdout 4 and .Xr stderr 4 file descriptors. They are, in fact, encouraged to print status information as they execute to keep the user informed of their actions. This is specially important for long test cases. .Pp Test cases will log their results to an auxiliary file, which is then collected by the test program they are contained in. The developer need not care about this as long as he uses the correct APIs to implement the test cases. .Pp The standard input of the test cases is unconditionally connected to .Sq /dev/zero . .Ss Meta-data -The following list describes all meta-data properties interpreted -internally by ATF. -You are free to define new properties in your test cases and use them as -you wish, but non-standard properties must be prefixed by -.Sq X- . +The following metadata properties can be exposed via the test case's head: .Bl -tag -width requireXmachineXX .It descr Type: textual. Required. .Pp A brief textual description of the test case's purpose. Will be shown to the user in reports. Also good for documentation purposes. .It has.cleanup Type: boolean. Optional. .Pp If set to true, specifies that the test case has a cleanup routine that has to be executed by the runtime engine during the cleanup phase of the execution. This property is automatically set by the framework when defining a test case with a cleanup routine, so it should never be set by hand. .It ident Type: textual. Required. .Pp The test case's identifier. Must be unique inside the test program and should be short but descriptive. .It require.arch Type: textual. Optional. .Pp A whitespace separated list of architectures that the test case can be run under without causing errors due to an architecture mismatch. .It require.config Type: textual. Optional. .Pp A whitespace separated list of configuration variables that must be defined to execute the test case. If any of the required variables is not defined, the test case is .Em skipped . .It require.diskspace Type: integer. Optional. Specifies the minimum amount of available disk space needed by the test. The value can have a size suffix such as .Sq K , .Sq M , .Sq G or .Sq T to make the amount of bytes easier to type and read. .It require.files Type: textual. Optional. .Pp A whitespace separated list of files that must be present to execute the test case. The names of these files must be absolute paths. If any of the required files is not found, the test case is .Em skipped . .It require.machine Type: textual. Optional. .Pp A whitespace separated list of machine types that the test case can be run under without causing errors due to a machine type mismatch. .It require.memory Type: integer. Optional. Specifies the minimum amount of physical memory needed by the test. The value can have a size suffix such as .Sq K , .Sq M , .Sq G or .Sq T to make the amount of bytes easier to type and read. .It require.progs Type: textual. Optional. .Pp A whitespace separated list of programs that must be present to execute the test case. These can be given as plain names, in which case they are looked in the user's .Ev PATH , or as absolute paths. If any of the required programs is not found, the test case is .Em skipped . .It require.user Type: textual. Optional. .Pp The required privileges to execute the test case. Can be one of .Sq root or .Sq unprivileged . .Pp If the test case is running as a regular user and this property is .Sq root , the test case is .Em skipped . .Pp If the test case is running as root and this property is .Sq unprivileged , the runtime engine will automatically drop the privileges if the .Sq unprivileged-user configuration property is set; otherwise the test case is .Em skipped . .It timeout Type: integral. Optional; defaults to .Sq 300 . .Pp Specifies the maximum amount of time the test case can run. This is particularly useful because some tests can stall either because they are incorrectly coded or because they trigger an anomalous behavior of the program. It is not acceptable for these tests to stall the whole execution of the test program. .Pp Can optionally be set to zero, in which case the test case has no run-time limit. This is discouraged. +.It X- Ns Sq NAME +Type: textual. +Optional. +.Pp +A user-defined property named +.Sq NAME . +These properties are free form, have no special meaning within ATF, and can +be specified at will by the test case. +The runtime engine should propagate these properties from the test case to +the end user so that the end user can rely on custom properties for test case +tagging and classification. .El .Ss Environment Every time a test case is executed, several environment variables are cleared or reseted to sane values to ensure they do not make the test fail due to unexpected conditions. These variables are: .Bl -tag -width LCXMESSAGESXX .It Ev HOME Set to the work directory's path. .It Ev LANG Undefined. .It Ev LC_ALL Undefined. .It Ev LC_COLLATE Undefined. .It Ev LC_CTYPE Undefined. .It Ev LC_MESSAGES Undefined. .It Ev LC_MONETARY Undefined. .It Ev LC_NUMERIC Undefined. .It Ev LC_TIME Undefined. .It Ev TZ Hardcoded to .Sq UTC . .El .Ss Work directories The test program always creates a temporary directory and switches to it before running the test case's body. This way the test case is free to modify its current directory as it wishes, and the runtime engine will be able to clean it up later on in a safe way, removing any traces of its execution from the system. To do so, the runtime engine will perform a recursive removal of the work directory without crossing mount points; if a mount point is found, the file system will be unmounted (if possible). .Ss File creation mode mask (umask) Test cases are always executed with a file creation mode mask (umask) of .Sq 0022 . The test case's code is free to change this during execution. .Sh SEE ALSO .Xr atf-test-program 1 diff --git a/lib/atf/libatf-c++/tests/Makefile b/lib/atf/libatf-c++/tests/Makefile index 851be7af97c4..d045b0526ed6 100644 --- a/lib/atf/libatf-c++/tests/Makefile +++ b/lib/atf/libatf-c++/tests/Makefile @@ -1,37 +1,39 @@ # $FreeBSD$ .include PACKAGE= tests TESTS_SUBDIRS= detail ATF= ${SRCTOP}/contrib/atf .PATH: ${ATF}/atf-c++ .PATH: ${ATF}/atf-c++/detail +CFLAGS+= -DATF_BUILD_CXX='"c++"' +CFLAGS+= -DATF_BUILD_CXXFLAGS='"-Wall"' CFLAGS+= -DATF_C_TESTS_BASE='"${TESTSBASE}/lib/atf/libatf-c"' CFLAGS+= -DATF_INCLUDEDIR='"${INCLUDEDIR}"' CFLAGS+= -I${ATF} ${PACKAGE}FILES+= macros_hpp_test.cpp ${PACKAGE}FILES+= unused_test.cpp .for _T in atf_c++_test \ build_test \ check_test \ macros_test \ tests_test \ utils_test ATF_TESTS_CXX+= ${_T} SRCS.${_T}= ${_T}.cpp test_helpers.cpp .endfor .for _T in atf_c++_test \ build_test \ check_test \ macros_test TEST_METADATA.${_T}+= required_programs="c++" .endfor .include