1 # Copyright 2005 Dave Abrahams
2 # Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
3 # Copyright 2014-2015 Rene Rivera
4 # Copyright 2014 Microsoft Corporation
5 # Distributed under the Boost Software License, Version 1.0.
6 # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
8 # This module implements regression testing framework. It declares a number of
9 # main target rules which perform some action and, if the results are OK,
10 # creates an output file.
12 # The exact list of rules is:
13 # 'compile' -- creates .test file if compilation of sources was
15 # 'compile-fail' -- creates .test file if compilation of sources failed.
16 # 'run' -- creates .test file is running of executable produced from
17 # sources was successful. Also leaves behind .output file
18 # with the output from program run.
19 # 'run-fail' -- same as above, but .test file is created if running fails.
21 # In all cases, presence of .test file is an indication that the test passed.
22 # For more convenient reporting, you might want to use C++ Boost regression
23 # testing utilities (see http://www.boost.org/more/regression.html).
25 # For historical reason, a 'unit-test' rule is available which has the same
26 # syntax as 'exe' and behaves just like 'run'.
29 # - Teach compiler_status handle Jamfile.v2.
31 # - <no-warn> is not implemented, since it is Como-specific, and it is not
32 # clear how to implement it
33 # - std::locale-support is not implemented (it is used in one test).
52 import virtual-target ;
60 # Feature controlling the command used to launch test programs.
61 feature.feature testing.launcher : : free optional ;
63 feature.feature test-info : : free incidental ;
64 feature.feature testing.arg : : free incidental ;
65 feature.feature testing.input-file : : free dependency ;
67 feature.feature preserve-test-targets : on off : incidental propagated ;
69 # Feature to control whether executable binaries are run as part of test.
70 # This can be used to just compile test cases in cross compilation situations.
71 feature.feature testing.execute : on off : incidental propagated ;
72 feature.set-default testing.execute : on ;
74 # Register target types.
75 type.register TEST : test ;
76 type.register COMPILE : : TEST ;
77 type.register COMPILE_FAIL : : TEST ;
78 type.register RUN_OUTPUT : run ;
79 type.register RUN : : TEST ;
80 type.register RUN_FAIL : : TEST ;
81 type.register LINK_FAIL : : TEST ;
82 type.register LINK : : TEST ;
83 type.register UNIT_TEST : passed : TEST ;
86 # Suffix to denote test target directory
88 .TEST-DIR-SUFFIX = ".test" ;
91 .TEST-DIR-SUFFIX = "$test" ;
94 # Declare the rules which create main targets. While the 'type' module already
95 # creates rules with the same names for us, we need extra convenience: default
96 # name of main target, so write our own versions.
98 # Helper rule. Create a test target, using basename of first source if no target
99 # name is explicitly passed. Remembers the created target in a global variable.
101 rule make-test ( target-type : sources + : requirements * : target-name ? )
103 target-name ?= $(sources[1]:D=:S=) ;
105 # Having periods (".") in the target name is problematic because the typed
106 # generator will strip the suffix and use the bare name for the file
107 # targets. Even though the location-prefix averts problems most times it
108 # does not prevent ambiguity issues when referring to the test targets. For
109 # example when using the XML log output. So we rename the target to remove
110 # the periods, and provide an alias for users.
111 local real-name = [ regex.replace $(target-name) "[.]" "~" ] ;
113 local project = [ project.current ] ;
114 # The <location-prefix> forces the build system for generate paths in the
115 # form '$build_dir/array1$(.TEST-DIR-SUFFIX)/gcc/debug'. This is necessary
116 # to allow post-processing tools to work.
117 local t = [ targets.create-typed-target [ type.type-from-rule-name
118 $(target-type) ] : $(project) : $(real-name) : $(sources) :
119 $(requirements) <location-prefix>$(real-name)$(.TEST-DIR-SUFFIX) ] ;
121 # The alias to the real target, per period replacement above.
122 if $(real-name) != $(target-name)
124 alias $(target-name) : $(t) ;
127 # Remember the test (for --dump-tests). A good way would be to collect all
128 # given a project. This has some technical problems: e.g. we can not call
129 # this dump from a Jamfile since projects referred by 'build-project' are
130 # not available until the whole Jamfile has been loaded.
136 # Note: passing more that one cpp file here is known to fail. Passing a cpp file
137 # and a library target works.
139 rule compile ( sources + : requirements * : target-name ? )
141 return [ make-test compile : $(sources) : $(requirements) : $(target-name) ]
146 rule compile-fail ( sources + : requirements * : target-name ? )
148 return [ make-test compile-fail : $(sources) : $(requirements) :
153 rule link ( sources + : requirements * : target-name ? )
155 return [ make-test link : $(sources) : $(requirements) : $(target-name) ] ;
159 rule link-fail ( sources + : requirements * : target-name ? )
161 return [ make-test link-fail : $(sources) : $(requirements) : $(target-name)
166 rule handle-input-files ( input-files * )
170 # Check that sorting made when creating property-set instance will not
171 # change the ordering.
172 if [ sequence.insertion-sort $(input-files) ] != $(input-files)
174 errors.user-error "Names of input files must be sorted alphabetically"
175 : "due to internal limitations" ;
178 return <testing.input-file>$(input-files) ;
182 rule run ( sources + : args * : input-files * : requirements * : target-name ? :
185 requirements += <testing.arg>$(args:J=" ") ;
186 requirements += [ handle-input-files $(input-files) ] ;
187 return [ make-test run : $(sources) : $(requirements) : $(target-name) ] ;
191 rule run-fail ( sources + : args * : input-files * : requirements * :
192 target-name ? : default-build * )
194 requirements += <testing.arg>$(args:J=" ") ;
195 requirements += [ handle-input-files $(input-files) ] ;
196 return [ make-test run-fail : $(sources) : $(requirements) : $(target-name)
201 # Use 'test-suite' as a synonym for 'alias', for backward compatibility.
202 IMPORT : alias : : test-suite ;
205 # For all main targets in 'project-module', which are typed targets with type
206 # derived from 'TEST', produce some interesting information.
210 for local t in $(.all-tests)
217 # Given a project location in normalized form (slashes are forward), compute the
218 # name of the Boost library.
220 local rule get-library-name ( path )
222 # Path is in normalized form, so all slashes are forward.
223 local match1 = [ MATCH /(tools|libs)/(.*)/(test|example) : $(path) ] ;
224 local match2 = [ MATCH /(tools|libs)/(.*)$ : $(path) ] ;
225 local match3 = [ MATCH (/status$) : $(path) ] ;
227 if $(match1) { return $(match1[2]) ; }
228 else if $(match2) { return $(match2[2]) ; }
229 else if $(match3) { return "" ; }
230 else if --dump-tests in [ modules.peek : ARGV ]
232 # The 'run' rule and others might be used outside boost. In that case,
233 # just return the path, since the 'library name' makes no sense.
239 # Was an XML dump requested?
240 .out-xml = [ MATCH --out-xml=(.*) : [ modules.peek : ARGV ] ] ;
243 # Takes a target (instance of 'basic-target') and prints
246 # - comments specified via the <test-info> property
247 # - relative location of all source from the project root.
249 rule dump-test ( target )
251 local type = [ $(target).type ] ;
252 local name = [ $(target).name ] ;
253 local project = [ $(target).project ] ;
255 local project-root = [ $(project).get project-root ] ;
256 local library = [ get-library-name [ path.root [ $(project).get location ]
260 name = $(library)/$(name) ;
263 local sources = [ $(target).sources ] ;
265 for local s in $(sources)
267 if [ class.is-a $(s) : file-reference ]
269 local location = [ path.root [ path.root [ $(s).name ]
270 [ $(s).location ] ] [ path.pwd ] ] ;
272 source-files += [ path.relative-to [ path.root $(project-root)
273 [ path.pwd ] ] $(location) ] ;
278 [ $(project).get location ] // [ $(target).name ] $(.TEST-DIR-SUFFIX) ;
279 target-name = $(target-name:J=) ;
281 local r = [ $(target).requirements ] ;
282 # Extract values of the <test-info> feature.
283 local test-info = [ $(r).get <test-info> ] ;
285 # If the user requested XML output on the command-line, add the test info to
286 # that XML file rather than dumping them to stdout.
291 .contents on $(.out-xml) +=
292 "$(nl) <test type=\"$(type)\" name=\"$(name)\">"
293 "$(nl) <target><![CDATA[$(target-name)]]></target>"
294 "$(nl) <info><![CDATA[$(test-info)]]></info>"
295 "$(nl) <source><![CDATA[$(source-files)]]></source>"
301 # Format them into a single string of quoted strings.
302 test-info = \"$(test-info:J=\"\ \")\" ;
304 ECHO boost-test($(type)) \"$(name)\" [$(test-info)] ":"
305 \"$(source-files)\" ;
310 # Register generators. Depending on target type, either 'expect-success' or
311 # 'expect-failure' rule will be used.
312 generators.register-standard testing.expect-success : OBJ : COMPILE ;
313 generators.register-standard testing.expect-failure : OBJ : COMPILE_FAIL ;
314 generators.register-standard testing.expect-success : RUN_OUTPUT : RUN ;
315 generators.register-standard testing.expect-failure : RUN_OUTPUT : RUN_FAIL ;
316 generators.register-standard testing.expect-failure : EXE : LINK_FAIL ;
317 generators.register-standard testing.expect-success : EXE : LINK ;
319 # Generator which runs an EXE and captures output.
320 generators.register-standard testing.capture-output : EXE : RUN_OUTPUT ;
322 # Generator which creates a target if sources run successfully. Differs from RUN
323 # in that run output is not captured. The reason why it exists is that the 'run'
324 # rule is much better for automated testing, but is not user-friendly (see
325 # http://article.gmane.org/gmane.comp.lib.boost.build/6353).
326 generators.register-standard testing.unit-test : EXE : UNIT_TEST ;
329 # The action rules called by generators.
331 # Causes the 'target' to exist after bjam invocation if and only if all the
332 # dependencies were successfully built.
334 rule expect-success ( target : dependency + : requirements * )
336 **passed** $(target) : $(dependency) ;
340 # Causes the 'target' to exist after bjam invocation if and only if all some of
341 # the dependencies were not successfully built.
343 rule expect-failure ( target : dependency + : properties * )
345 local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ;
346 local marker = $(dependency:G=$(grist)*fail) ;
347 (failed-as-expected) $(marker) ;
348 FAIL_EXPECTED $(dependency) ;
349 LOCATE on $(marker) = [ on $(dependency) return $(LOCATE) ] ;
351 DEPENDS $(marker) : $(dependency) ;
352 DEPENDS $(target) : $(marker) ;
353 **passed** $(target) : $(marker) ;
357 # The rule/action combination used to report successful passing of a test.
361 remove-test-targets $(<) ;
363 # Dump all the tests, if needed. We do it here, since dump should happen
364 # only after all Jamfiles have been read, and there is no such place
365 # currently defined (but there should be).
366 if ! $(.dumped-tests) && ( --dump-tests in [ modules.peek : ARGV ] )
368 .dumped-tests = true ;
372 # Force deletion of the target, in case any dependencies failed to build.
378 # Used to create test files signifying passed tests.
385 # Used to create replacement object files that do not get created during tests
386 # that are expected to fail.
388 actions (failed-as-expected)
390 echo failed as expected > "$(<)"
398 PIPE WRITE SYS$OUTPUT "passed" > $(<:W)
401 actions (failed-as-expected)
403 PIPE WRITE SYS$OUTPUT "failed as expected" > $(<:W)
407 rule run-path-setup ( target : source : properties * )
409 # For testing, we need to make sure that all dynamic libraries needed by the
410 # test are found. So, we collect all paths from dependency libraries (via
411 # xdll-path property) and add whatever explicit dll-path user has specified.
412 # The resulting paths are added to the environment on each test invocation.
413 local target-os = [ feature.get-values <target-os> : $(properties) ] ;
414 local dll-paths = [ feature.get-values <dll-path> : $(properties) ] ;
415 dll-paths += [ feature.get-values <xdll-path> : $(properties) ] ;
416 if $(target-os) != vxworks
418 dll-paths += [ on $(source) return $(RUN_PATH) ] ;
420 dll-paths = [ sequence.unique $(dll-paths) ] ;
423 translate-to-os = path.native ;
426 translate-to-os = path.to-VMS ;
428 if $(target-os) = vxworks
430 # map <build-os> paths to <target-os> paths
431 local save-os = [ modules.peek os : .name ] ;
432 modules.poke os : .name : VXWORKS ;
433 local parent = [ os.environ PKG_SRC_BUILD_DIR ] ;
434 local prefix = [ os.environ LAYER_SRC_PATH ] ;
435 local target-dll-paths ;
436 for local e in $(dll-paths)
438 target-dll-paths += [ path.join $(prefix) [ path.relative $(e) $(parent) : noerror ] ] ;
440 PATH_SETUP on $(target) = [ common.prepend-path-variable-command
441 [ os.shared-library-path-variable ] : $(target-dll-paths) ] ;
442 modules.poke os : .name : $(save-os) ;
446 dll-paths = [ sequence.transform $(translate-to-os) : $(dll-paths) ] ;
447 PATH_SETUP on $(target) = [ common.prepend-path-variable-command
448 [ os.shared-library-path-variable ] : $(dll-paths) ] ;
454 local argv = [ modules.peek : ARGV ] ;
456 toolset.flags testing.capture-output ARGS <testing.arg> ;
457 toolset.flags testing.capture-output INPUT_FILES <testing.input-file> ;
458 toolset.flags testing.capture-output LAUNCHER <testing.launcher> ;
460 .preserve-test-targets = on ;
461 if --remove-test-targets in [ modules.peek : ARGV ]
463 .preserve-test-targets = off ;
467 # Runs executable 'sources' and stores stdout in file 'target'. Unless
468 # --preserve-test-targets command line option has been specified, removes the
469 # executable. The 'target-to-remove' parameter controls what should be removed:
470 # - if 'none', does not remove anything, ever
471 # - if empty, removes 'source'
472 # - if non-empty and not 'none', contains a list of sources to remove.
474 rule capture-output ( target : source : properties * : targets-to-remove * )
476 output-file on $(target) = $(target:S=.output) ;
477 LOCATE on $(target:S=.output) = [ on $(target) return $(LOCATE) ] ;
479 # The INCLUDES kill a warning about independent target...
480 INCLUDES $(target) : $(target:S=.output) ;
481 # but it also puts .output into dependency graph, so we must tell jam it is
482 # OK if it cannot find the target or updating rule.
483 NOCARE $(target:S=.output) ;
485 # This has two-fold effect. First it adds input files to the dependency
486 # graph, preventing a warning. Second, it causes input files to be bound
487 # before target is created. Therefore, they are bound using SEARCH setting
488 # on them and not LOCATE setting of $(target), as in other case (due to jam
490 DEPENDS $(target) : [ on $(target) return $(INPUT_FILES) ] ;
492 if $(targets-to-remove) = none
494 targets-to-remove = ;
496 else if ! $(targets-to-remove)
498 targets-to-remove = $(source) ;
501 run-path-setup $(target) : $(source) : $(properties) ;
503 DISABLE_TEST_EXECUTION on $(target) = 0 ;
504 if [ feature.get-values testing.execute : $(properties) ] = off
506 DISABLE_TEST_EXECUTION on $(target) = 1 ;
509 if [ feature.get-values preserve-test-targets : $(properties) ] = off
510 || $(.preserve-test-targets) = off
512 rmtemp-sources $(target) : $(targets-to-remove) ;
513 for local to-remove in $(targets-to-remove)
515 rmtemp-all-sources $(to-remove) ;
519 if ! [ feature.get-values testing.launcher : $(properties) ]
521 ## On VMS set default launcher to MCR
522 if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
526 .types-to-remove = EXE OBJ ;
528 local rule remove-test-targets ( targets + )
530 if $(.preserve-test-targets) = off
532 rmtemp-all-sources $(target) ;
536 local rule rmtemp-all-sources ( target )
539 local action = [ on $(target) return $(.action) ] ;
542 local action-sources = [ $(action).sources ] ;
543 for local source in $(action-sources)
545 local source-type = [ $(source).type ] ;
546 if $(source-type) in $(.types-to-remove)
548 sources += [ $(source).actual-name ] ;
552 # ECHO IGNORED: $(source) :: $(source-type) ;
557 rmtemp-sources $(target) : $(sources) ;
558 for local source in $(sources)
560 rmtemp-all-sources $(source) ;
566 local rule rmtemp-sources ( target : sources * )
570 TEMPORARY $(sources) ;
571 # Set a second action on target that will be executed after capture
572 # output action. The 'RmTemps' rule has the 'ignore' modifier so it is
573 # always considered succeeded. This is needed for 'run-fail' test. For
574 # that test the target will be marked with FAIL_EXPECTED, and without
575 # 'ignore' successful execution will be negated and be reported as
576 # failure. With 'ignore' we do not detect a case where removing files
577 # fails, but it is not likely to happen.
578 RmTemps $(target) : $(sources) ;
586 .SET_STATUS = "set status=%ERRORLEVEL%" ;
587 .RUN_OUTPUT_NL = "echo." ;
589 .EXIT_SUCCESS = "0" ;
590 .STATUS_0 = "%status% EQU 0 $(.THEN)" ;
591 .STATUS_NOT_0 = "%status% NEQ 0 $(.THEN)" ;
592 .VERBOSE = "%verbose% EQU 1 $(.THEN)" ;
594 .SHELL_SET = "set " ;
599 else if [ os.name ] = VMS
604 .STATUS = "''status'" ;
605 .SET_STATUS = "status=$STATUS" ;
606 .SAY = "pipe write sys$output" ; ## not really echo
607 .RUN_OUTPUT_NL = "$(.SAY) \"\"" ;
608 .THEN = "$(nl)then" ;
609 .EXIT_SUCCESS = "1" ;
610 .SUCCESS = "status .eq. $(.EXIT_SUCCESS) $(.THEN)" ;
611 .STATUS_0 = "status .eq. 0 $(.THEN)" ;
612 .STATUS_NOT_0 = "status .ne. 0 $(.THEN)" ;
613 .VERBOSE = "verbose .eq. 1 $(.THEN)" ;
622 .STATUS = "$status" ;
623 .SET_STATUS = "status=$?" ;
624 .RUN_OUTPUT_NL = "echo" ;
626 .EXIT_SUCCESS = "0" ;
627 .STATUS_0 = "test $status -eq 0 $(.THEN)" ;
628 .STATUS_NOT_0 = "test $status -ne 0 $(.THEN)" ;
629 .VERBOSE = "test $verbose -eq 1 $(.THEN)" ;
634 .NULLIN = "<" "/dev/null" ;
639 if --verbose-test in [ modules.peek : ARGV ]
645 .RM = [ common.rm-command ] ;
648 actions capture-output bind INPUT_FILES output-file
651 $(.SHELL_SET)status=$(DISABLE_TEST_EXECUTION)
653 echo Skipping test execution due to testing.execute=off
654 exit $(.EXIT_SUCCESS)
656 $(LAUNCHER) "$(>)" $(ARGS) "$(INPUT_FILES)" > "$(output-file)" 2>&1 $(.NULLIN)
658 $(.RUN_OUTPUT_NL) >> "$(output-file)"
659 echo EXIT STATUS: $(.STATUS) >> "$(output-file)"
661 $(.CP) "$(output-file)" "$(<)"
663 $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
665 $(.SHELL_SET)verbose=1
668 echo ====== BEGIN OUTPUT ======
669 $(.CATENATE) "$(output-file)"
670 echo ====== END OUTPUT ======
676 actions quietly updated ignore piecemeal together RmTemps
683 actions capture-output bind INPUT_FILES output-file
686 $(.SHELL_SET)status=$(DISABLE_TEST_EXECUTION)
688 $(.SAY) "Skipping test execution due to testing.execute=off"
689 exit "$(.EXIT_SUCCESS)"
691 !! Execute twice - first for status, second for output
693 pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) 2>NL: >NL:
695 pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) | type sys$input /out=$(output-file:W)
697 !! Harmonize VMS success status with POSIX
699 $(.SHELL_SET)status="0"
701 $(.RUN_OUTPUT_NL) | append /new sys$input $(output-file:W)
702 $(.SAY) "EXIT STATUS: $(.STATUS)" | append /new sys$input $(output-file:W)
704 $(.CP) $(output-file:W) $(<:W)
706 $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
708 $(.SHELL_SET)verbose=1
711 $(.SAY) "====== BEGIN OUTPUT ======"
712 $(.CATENATE) $(output-file:W)
713 $(.SAY) "====== END OUTPUT ======"
715 !! Harmonize VMS success status with POSIX on exit
717 $(.SHELL_SET)status="$(.EXIT_SUCCESS)"
722 actions quietly updated ignore piecemeal together RmTemps
728 .MAKE_FILE = [ common.file-creation-command ] ;
730 toolset.flags testing.unit-test LAUNCHER <testing.launcher> ;
731 toolset.flags testing.unit-test ARGS <testing.arg> ;
734 rule unit-test ( target : source : properties * )
736 run-path-setup $(target) : $(source) : $(properties) ;
738 if ! [ feature.get-values testing.launcher : $(properties) ]
740 ## On VMS set default launcher to MCR
741 if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
749 $(LAUNCHER) "$(>)" $(ARGS) && $(.MAKE_FILE) "$(<)"
757 pipe $(LAUNCHER) $(>:W) $(ARGS) && $(.MAKE_FILE) $(<:W)
761 IMPORT $(__name__) : compile compile-fail run run-fail link link-fail
762 : : compile compile-fail run run-fail link link-fail ;
765 # This is a composing generator to support cases where a generator for the
766 # specified target constructs other targets as well. One such example is msvc's
767 # exe generator that constructs both EXE and PDB targets.
768 type.register TIME : time ;
769 generators.register-composing testing.time : : TIME ;
772 # Note that this rule may be called multiple times for a single target in case
773 # there are multiple actions operating on the same target in sequence. One such
774 # example are msvc exe targets first created by a linker action and then updated
775 # with an embedded manifest file by a separate action.
776 rule record-time ( target : source : start end user system clock )
778 local src-string = [$(source:G=:J=",")"] " ;
779 USER_TIME on $(target) += $(src-string)$(user) ;
780 SYSTEM_TIME on $(target) += $(src-string)$(system) ;
781 CLOCK_TIME on $(target) += $(src-string)$(clock) ;
783 # We need the following variables because attempting to perform such
784 # variable expansion in actions would not work due to quotes getting treated
785 # as regular characters.
786 USER_TIME_SECONDS on $(target) += $(src-string)$(user)" seconds" ;
787 SYSTEM_TIME_SECONDS on $(target) += $(src-string)$(system)" seconds" ;
788 CLOCK_TIME_SECONDS on $(target) += $(src-string)$(clock)" seconds" ;
792 # Support for generating timing information for any main target. To use
793 # declare a custom make target that uses the testing.time generator rule
794 # specified here. For example:
796 # make main.cpp : main_cpp.pro : @do-something ;
797 # time main.time : main.cpp ;
798 # actions do-something
800 # sleep 2 && echo "$(<)" > "$(<)"
803 # The above will generate a "main.time", and echo to output, timing
804 # information for the action of source "main.cpp".
807 IMPORT testing : record-time : : testing.record-time ;
810 # Calling this rule requests that Boost Build time how long it takes to build
811 # the 'source' target and display the results both on the standard output and in
814 rule time ( target : sources + : properties * )
816 # Set up rule for recording timing information.
817 local action = [ on $(target) return $(.action) ] ;
818 for local action.source in [ $(action).sources ]
820 # Yes, this uses the private "actual-name" of the target action.
821 # But it's the only way to get at the real name of the sources
822 # given the context of header scanners.
823 __TIMING_RULE__ on [ $(action.source).actual-name ] = testing.record-time $(target) ;
826 # Make sure the sources get rebuilt any time we need to retrieve that
828 REBUILDS $(target) : $(sources) ;
834 echo user: $(USER_TIME)
835 echo system: $(SYSTEM_TIME)
836 echo clock: $(CLOCK_TIME)
838 echo user: $(USER_TIME_SECONDS) > "$(<)"
839 echo system: $(SYSTEM_TIME_SECONDS) >> "$(<)"
840 echo clock: $(CLOCK_TIME_SECONDS) >> "$(<)"
847 WRITE SYS$OUTPUT "user: ", "$(USER_TIME)"
848 WRITE SYS$OUTPUT "system: ", "(SYSTEM_TIME)"
849 WRITE SYS$OUTPUT "clock: ", "(CLOCK_TIME)"
851 PIPE WRITE SYS$OUTPUT "user: ", "$(USER_TIME_SECONDS)" | TYPE SYS$INPUT /OUT=$(<:W)
852 PIPE WRITE SYS$OUTPUT "system: ", "$(SYSTEM_TIME_SECONDS)" | APPEND /NEW SYS$INPUT $(<:W)
853 PIPE WRITE SYS$OUTPUT "clock: ", "$(CLOCK_TIME_SECONDS)" | APPEND /NEW SYS$INPUT $(<:W)