1import feature ;
2
3# This module is imported by testing.py. The definitions here are
4# too tricky to do in Python
5
6# Causes the 'target' to exist after bjam invocation if and only if all the
7# dependencies were successfully built.
8#
9rule expect-success ( target : dependency + : requirements * )
10{
11    **passed** $(target) : $(sources) ;
12}
13IMPORT testing : expect-success : : testing.expect-success ;
14
15# Causes the 'target' to exist after bjam invocation if and only if all some of
16# the dependencies were not successfully built.
17#
18rule expect-failure ( target : dependency + : properties * )
19{
20    local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ;
21    local marker = $(dependency:G=$(grist)*fail) ;
22    (failed-as-expected) $(marker) ;
23    FAIL_EXPECTED $(dependency) ;
24    LOCATE on $(marker) = [ on $(dependency) return $(LOCATE) ] ;
25    RMOLD $(marker) ;
26    DEPENDS $(marker) : $(dependency) ;
27    DEPENDS $(target) : $(marker) ;
28    **passed** $(target) : $(marker) ;
29}
30IMPORT testing : expect-failure : : testing.expect-failure ;
31
32# The rule/action combination used to report successful passing of a test.
33#
34rule **passed**
35{
36    # Force deletion of the target, in case any dependencies failed to build.
37    RMOLD $(<) ;
38}
39
40
41# Used to create test files signifying passed tests.
42#
43actions **passed**
44{
45    echo passed > "$(<)"
46}
47
48
49# Used to create replacement object files that do not get created during tests
50# that are expected to fail.
51#
52actions (failed-as-expected)
53{
54    echo failed as expected > "$(<)"
55}
56
57
58if [ os.name ] = VMS
59{
60    actions **passed**
61    {
62        PIPE WRITE SYS$OUTPUT "passed" > $(<:W)
63    }
64
65    actions (failed-as-expected)
66    {
67        PIPE WRITE SYS$OUTPUT "failed as expected" > $(<:W)
68    }
69}
70
71
72# Runs executable 'sources' and stores stdout in file 'target'. Unless
73# --preserve-test-targets command line option has been specified, removes the
74# executable. The 'target-to-remove' parameter controls what should be removed:
75#   - if 'none', does not remove anything, ever
76#   - if empty, removes 'source'
77#   - if non-empty and not 'none', contains a list of sources to remove.
78#
79rule capture-output ( target : source : properties * : targets-to-remove * )
80{
81    output-file on $(target) = $(target:S=.output) ;
82    LOCATE on $(target:S=.output) = [ on $(target) return $(LOCATE) ] ;
83
84    # The INCLUDES kill a warning about independent target...
85    INCLUDES $(target) : $(target:S=.output) ;
86    # but it also puts .output into dependency graph, so we must tell jam it is
87    # OK if it cannot find the target or updating rule.
88    NOCARE $(target:S=.output) ;
89
90    # This has two-fold effect. First it adds input files to the dependency
91    # graph, preventing a warning. Second, it causes input files to be bound
92    # before target is created. Therefore, they are bound using SEARCH setting
93    # on them and not LOCATE setting of $(target), as in other case (due to jam
94    # bug).
95    DEPENDS $(target) : [ on $(target) return $(INPUT_FILES) ] ;
96
97    if $(targets-to-remove) = none
98    {
99        targets-to-remove = ;
100    }
101    else if ! $(targets-to-remove)
102    {
103        targets-to-remove = $(source) ;
104    }
105
106    if [ on $(target) return $(REMOVE_TEST_TARGETS) ]
107    {
108        TEMPORARY $(targets-to-remove) ;
109        # Set a second action on target that will be executed after capture
110        # output action. The 'RmTemps' rule has the 'ignore' modifier so it is
111        # always considered succeeded. This is needed for 'run-fail' test. For
112        # that test the target will be marked with FAIL_EXPECTED, and without
113        # 'ignore' successful execution will be negated and be reported as
114        # failure. With 'ignore' we do not detect a case where removing files
115        # fails, but it is not likely to happen.
116        RmTemps $(target) : $(targets-to-remove) ;
117    }
118
119    if ! [ feature.get-values testing.launcher : $(properties) ]
120    {
121        ## On VMS set default launcher to MCR
122        if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
123    }
124}
125
126
127if [ os.name ] = NT
128{
129    .STATUS        = %status% ;
130    .SET_STATUS    = "set status=%ERRORLEVEL%" ;
131    .RUN_OUTPUT_NL = "echo." ;
132    .THEN          = "(" ;
133    .EXIT_SUCCESS  = "0" ;
134    .STATUS_0      = "%status% EQU 0 $(.THEN)" ;
135    .STATUS_NOT_0  = "%status% NEQ 0 $(.THEN)" ;
136    .VERBOSE       = "%verbose% EQU 1 $(.THEN)" ;
137    .ENDIF         = ")" ;
138    .SHELL_SET     = "set " ;
139    .CATENATE      = type ;
140    .CP            = copy ;
141    .NULLIN        = ;
142}
143else if [ os.name ] = VMS
144{
145    local nl = "
146" ;
147
148    .STATUS        = "''status'" ;
149    .SET_STATUS    = "status=$STATUS" ;
150    .SAY           = "pipe write sys$output" ; ## not really echo
151    .RUN_OUTPUT_NL = "$(.SAY) \"\"" ;
152    .THEN          = "$(nl)then" ;
153    .EXIT_SUCCESS  = "1" ;
154    .SUCCESS       = "status .eq. $(.EXIT_SUCCESS) $(.THEN)" ;
155    .STATUS_0      = "status .eq. 0 $(.THEN)" ;
156    .STATUS_NOT_0  = "status .ne. 0 $(.THEN)" ;
157    .VERBOSE       = "verbose .eq. 1 $(.THEN)" ;
158    .ENDIF         = "endif" ;
159    .SHELL_SET     = "" ;
160    .CATENATE      = type ;
161    .CP            = copy ;
162    .NULLIN        = ;
163}
164else
165{
166    .STATUS        = "$status" ;
167    .SET_STATUS    = "status=$?" ;
168    .RUN_OUTPUT_NL = "echo" ;
169    .THEN          = "; then" ;
170    .EXIT_SUCCESS  = "0" ;
171    .STATUS_0      = "test $status -eq 0 $(.THEN)" ;
172    .STATUS_NOT_0  = "test $status -ne 0 $(.THEN)" ;
173    .VERBOSE       = "test $verbose -eq 1 $(.THEN)" ;
174    .ENDIF         = "fi" ;
175    .SHELL_SET     = "" ;
176    .CATENATE      = cat ;
177    .CP            = cp ;
178    .NULLIN        = "<" "/dev/null" ;
179}
180
181
182.VERBOSE_TEST = 0 ;
183if --verbose-test in [ modules.peek : ARGV ]
184{
185    .VERBOSE_TEST = 1 ;
186}
187
188
189.RM = [ common.rm-command ] ;
190
191
192actions capture-output bind INPUT_FILES output-file
193{
194    $(PATH_SETUP)
195    $(LAUNCHER) "$(>)" $(ARGS) "$(INPUT_FILES)" > "$(output-file)" 2>&1
196    $(.SET_STATUS)
197    $(.RUN_OUTPUT_NL) >> "$(output-file)"
198    echo EXIT STATUS: $(.STATUS) >> "$(output-file)"
199    if $(.STATUS_0)
200        $(.CP) "$(output-file)" "$(<)"
201    $(.ENDIF)
202    $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
203    if $(.STATUS_NOT_0)
204        $(.SHELL_SET)verbose=1
205    $(.ENDIF)
206    if $(.VERBOSE)
207        echo ====== BEGIN OUTPUT ======
208        $(.CATENATE) "$(output-file)"
209        echo ====== END OUTPUT ======
210    $(.ENDIF)
211    exit $(.STATUS)
212}
213
214IMPORT testing : capture-output : : testing.capture-output ;
215
216
217actions quietly updated ignore piecemeal together RmTemps
218{
219    $(.RM) "$(>)"
220}
221
222
223if [ os.name ] = VMS
224{
225    actions capture-output bind INPUT_FILES output-file
226    {
227        $(PATH_SETUP)
228        !! Execute twice - first for status, second for output
229        set noon
230        pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) 2>NL: >NL:
231        $(.SET_STATUS)
232        pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) | type sys$input /out=$(output-file:W)
233        set on
234        !! Harmonize VMS success status with POSIX
235        if $(.SUCCESS)
236            $(.SHELL_SET)status="0"
237        $(.ENDIF)
238        $(.RUN_OUTPUT_NL) | append /new sys$input $(output-file:W)
239        $(.SAY) "EXIT STATUS: $(.STATUS)" | append /new sys$input $(output-file:W)
240        if $(.STATUS_0)
241            $(.CP) $(output-file:W) $(<:W)
242        $(.ENDIF)
243        $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
244        if $(.STATUS_NOT_0)
245            $(.SHELL_SET)verbose=1
246        $(.ENDIF)
247        if $(.VERBOSE)
248            $(.SAY) "====== BEGIN OUTPUT ======"
249            $(.CATENATE) $(output-file:W)
250            $(.SAY) "====== END OUTPUT ======"
251        $(.ENDIF)
252        !! Harmonize VMS success status with POSIX on exit
253        if $(.STATUS_0)
254            $(.SHELL_SET)status="$(.EXIT_SUCCESS)"
255        $(.ENDIF)
256        exit "$(.STATUS)"
257    }
258
259    actions quietly updated ignore piecemeal together RmTemps
260    {
261        $(.RM) $(>:WJ=;*,);*
262    }
263}
264
265
266.MAKE_FILE = [ common.file-creation-command ] ;
267
268
269rule unit-test ( target : source : properties * )
270{
271    if ! [ feature.get-values testing.launcher : $(properties) ]
272    {
273        ## On VMS set default launcher to MCR
274        if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
275    }
276}
277
278actions unit-test
279{
280    $(PATH_SETUP)
281    $(LAUNCHER) "$(>)" $(ARGS) && $(.MAKE_FILE) "$(<)"
282}
283
284if [ os.name ] = VMS
285{
286    actions unit-test
287    {
288        $(PATH_SETUP)
289        pipe $(LAUNCHER) $(>:W) $(ARGS) && $(.MAKE_FILE) $(<:W)
290    }
291}
292
293# Note that this rule may be called multiple times for a single target in case
294# there are multiple actions operating on the same target in sequence. One such
295# example are msvc exe targets first created by a linker action and then updated
296# with an embedded manifest file by a separate action.
297rule record-time ( target : source : start end user system )
298{
299    local src-string = [$(source:G=:J=",")"] " ;
300    USER_TIME on $(target) += $(src-string)$(user) ;
301    SYSTEM_TIME on $(target) += $(src-string)$(system) ;
302
303    # We need the following variables because attempting to perform such
304    # variable expansion in actions would not work due to quotes getting treated
305    # as regular characters.
306    USER_TIME_SECONDS on $(target) += $(src-string)$(user)" seconds" ;
307    SYSTEM_TIME_SECONDS on $(target) += $(src-string)$(system)" seconds" ;
308}
309
310# Calling this rule requests that Boost Build time how long it takes to build
311# the 'source' target and display the results both on the standard output and in
312# the 'target' file.
313#
314rule time ( target : sources + : properties *  )
315{
316    # Set up rule for recording timing information.
317    __TIMING_RULE__ on $(sources) = testing.record-time $(target) ;
318
319    # Make sure the sources get rebuilt any time we need to retrieve that
320    # information.
321    REBUILDS $(target) : $(sources) ;
322}
323
324
325actions time
326{
327    echo user: $(USER_TIME)
328    echo system: $(SYSTEM_TIME)
329
330    echo user: $(USER_TIME_SECONDS) > "$(<)"
331    echo system: $(SYSTEM_TIME_SECONDS) >> "$(<)"
332}
333
334if [ os.name ] = VMS
335{
336    actions time
337    {
338        WRITE SYS$OUTPUT "user: ", "$(USER_TIME)"
339        WRITE SYS$OUTPUT "system: ", "(SYSTEM_TIME)"
340
341        PIPE WRITE SYS$OUTPUT "user: ", "$(USER_TIME_SECONDS)" | TYPE SYS$INPUT /OUT=$(<:W)
342        PIPE WRITE SYS$OUTPUT "system: ", "$(SYSTEM_TIME_SECONDS)" | APPEND /NEW SYS$INPUT $(<:W)
343    }
344}
345