import feature ;

# This module is imported by testing.py. The definitions here are
# too tricky to do in Python

# Causes the 'target' to exist after bjam invocation if and only if all the
# dependencies were successfully built.
#
rule expect-success ( target : dependency + : requirements * )
{
    **passed** $(target) : $(sources) ;
}
IMPORT testing : expect-success : : testing.expect-success ;

# Causes the 'target' to exist after bjam invocation if and only if all some of
# the dependencies were not successfully built.
#
rule expect-failure ( target : dependency + : properties * )
{
    local grist = [ MATCH ^<(.*)> : $(dependency:G) ] ;
    local marker = $(dependency:G=$(grist)*fail) ;
    (failed-as-expected) $(marker) ;
    FAIL_EXPECTED $(dependency) ;
    LOCATE on $(marker) = [ on $(dependency) return $(LOCATE) ] ;
    RMOLD $(marker) ;
    DEPENDS $(marker) : $(dependency) ;
    DEPENDS $(target) : $(marker) ;
    **passed** $(target) : $(marker) ;
}
IMPORT testing : expect-failure : : testing.expect-failure ;

# The rule/action combination used to report successful passing of a test.
#
rule **passed**
{
    # Force deletion of the target, in case any dependencies failed to build.
    RMOLD $(<) ;
}


# Used to create test files signifying passed tests.
#
actions **passed**
{
    echo passed > "$(<)"
}


# Used to create replacement object files that do not get created during tests
# that are expected to fail.
#
actions (failed-as-expected)
{
    echo failed as expected > "$(<)"
}


if [ os.name ] = VMS
{
    actions **passed**
    {
        PIPE WRITE SYS$OUTPUT "passed" > $(<:W)
    }

    actions (failed-as-expected)
    {
        PIPE WRITE SYS$OUTPUT "failed as expected" > $(<:W)
    }
}


# Runs executable 'sources' and stores stdout in file 'target'. Unless
# --preserve-test-targets command line option has been specified, removes the
# executable. The 'target-to-remove' parameter controls what should be removed:
#   - if 'none', does not remove anything, ever
#   - if empty, removes 'source'
#   - if non-empty and not 'none', contains a list of sources to remove.
#
rule capture-output ( target : source : properties * : targets-to-remove * )
{
    output-file on $(target) = $(target:S=.output) ;
    LOCATE on $(target:S=.output) = [ on $(target) return $(LOCATE) ] ;

    # The INCLUDES kill a warning about independent target...
    INCLUDES $(target) : $(target:S=.output) ;
    # but it also puts .output into dependency graph, so we must tell jam it is
    # OK if it cannot find the target or updating rule.
    NOCARE $(target:S=.output) ;

    # This has two-fold effect. First it adds input files to the dependency
    # graph, preventing a warning. Second, it causes input files to be bound
    # before target is created. Therefore, they are bound using SEARCH setting
    # on them and not LOCATE setting of $(target), as in other case (due to jam
    # bug).
    DEPENDS $(target) : [ on $(target) return $(INPUT_FILES) ] ;

    if $(targets-to-remove) = none
    {
        targets-to-remove = ;
    }
    else if ! $(targets-to-remove)
    {
        targets-to-remove = $(source) ;
    }

    if [ on $(target) return $(REMOVE_TEST_TARGETS) ]
    {
        TEMPORARY $(targets-to-remove) ;
        # Set a second action on target that will be executed after capture
        # output action. The 'RmTemps' rule has the 'ignore' modifier so it is
        # always considered succeeded. This is needed for 'run-fail' test. For
        # that test the target will be marked with FAIL_EXPECTED, and without
        # 'ignore' successful execution will be negated and be reported as
        # failure. With 'ignore' we do not detect a case where removing files
        # fails, but it is not likely to happen.
        RmTemps $(target) : $(targets-to-remove) ;
    }

    if ! [ feature.get-values testing.launcher : $(properties) ]
    {
        ## On VMS set default launcher to MCR
        if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
    }
}


if [ os.name ] = NT
{
    .STATUS        = %status% ;
    .SET_STATUS    = "set status=%ERRORLEVEL%" ;
    .RUN_OUTPUT_NL = "echo." ;
    .THEN          = "(" ;
    .EXIT_SUCCESS  = "0" ;
    .STATUS_0      = "%status% EQU 0 $(.THEN)" ;
    .STATUS_NOT_0  = "%status% NEQ 0 $(.THEN)" ;
    .VERBOSE       = "%verbose% EQU 1 $(.THEN)" ;
    .ENDIF         = ")" ;
    .SHELL_SET     = "set " ;
    .CATENATE      = type ;
    .CP            = copy ;
    .NULLIN        = ;
}
else if [ os.name ] = VMS
{
    local nl = "
" ;

    .STATUS        = "''status'" ;
    .SET_STATUS    = "status=$STATUS" ;
    .SAY           = "pipe write sys$output" ; ## not really echo
    .RUN_OUTPUT_NL = "$(.SAY) \"\"" ;
    .THEN          = "$(nl)then" ;
    .EXIT_SUCCESS  = "1" ;
    .SUCCESS       = "status .eq. $(.EXIT_SUCCESS) $(.THEN)" ;
    .STATUS_0      = "status .eq. 0 $(.THEN)" ;
    .STATUS_NOT_0  = "status .ne. 0 $(.THEN)" ;
    .VERBOSE       = "verbose .eq. 1 $(.THEN)" ;
    .ENDIF         = "endif" ;
    .SHELL_SET     = "" ;
    .CATENATE      = type ;
    .CP            = copy ;
    .NULLIN        = ;
}
else
{
    .STATUS        = "$status" ;
    .SET_STATUS    = "status=$?" ;
    .RUN_OUTPUT_NL = "echo" ;
    .THEN          = "; then" ;
    .EXIT_SUCCESS  = "0" ;
    .STATUS_0      = "test $status -eq 0 $(.THEN)" ;
    .STATUS_NOT_0  = "test $status -ne 0 $(.THEN)" ;
    .VERBOSE       = "test $verbose -eq 1 $(.THEN)" ;
    .ENDIF         = "fi" ;
    .SHELL_SET     = "" ;
    .CATENATE      = cat ;
    .CP            = cp ;
    .NULLIN        = "<" "/dev/null" ;
}


.VERBOSE_TEST = 0 ;
if --verbose-test in [ modules.peek : ARGV ]
{
    .VERBOSE_TEST = 1 ;
}


.RM = [ common.rm-command ] ;


actions capture-output bind INPUT_FILES output-file
{
    $(PATH_SETUP)
    $(LAUNCHER) "$(>)" $(ARGS) "$(INPUT_FILES)" > "$(output-file)" 2>&1
    $(.SET_STATUS)
    $(.RUN_OUTPUT_NL) >> "$(output-file)"
    echo EXIT STATUS: $(.STATUS) >> "$(output-file)"
    if $(.STATUS_0)
        $(.CP) "$(output-file)" "$(<)"
    $(.ENDIF)
    $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
    if $(.STATUS_NOT_0)
        $(.SHELL_SET)verbose=1
    $(.ENDIF)
    if $(.VERBOSE)
        echo ====== BEGIN OUTPUT ======
        $(.CATENATE) "$(output-file)"
        echo ====== END OUTPUT ======
    $(.ENDIF)
    exit $(.STATUS)
}

IMPORT testing : capture-output : : testing.capture-output ;


actions quietly updated ignore piecemeal together RmTemps
{
    $(.RM) "$(>)"
}


if [ os.name ] = VMS
{
    actions capture-output bind INPUT_FILES output-file
    {
        $(PATH_SETUP)
        !! Execute twice - first for status, second for output
        set noon
        pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) 2>NL: >NL:
        $(.SET_STATUS)
        pipe $(LAUNCHER) $(>:W) $(ARGS) $(INPUT_FILES:W) | type sys$input /out=$(output-file:W)
        set on
        !! Harmonize VMS success status with POSIX
        if $(.SUCCESS)
            $(.SHELL_SET)status="0"
        $(.ENDIF)
        $(.RUN_OUTPUT_NL) | append /new sys$input $(output-file:W)
        $(.SAY) "EXIT STATUS: $(.STATUS)" | append /new sys$input $(output-file:W)
        if $(.STATUS_0)
            $(.CP) $(output-file:W) $(<:W)
        $(.ENDIF)
        $(.SHELL_SET)verbose=$(.VERBOSE_TEST)
        if $(.STATUS_NOT_0)
            $(.SHELL_SET)verbose=1
        $(.ENDIF)
        if $(.VERBOSE)
            $(.SAY) "====== BEGIN OUTPUT ======"
            $(.CATENATE) $(output-file:W)
            $(.SAY) "====== END OUTPUT ======"
        $(.ENDIF)
        !! Harmonize VMS success status with POSIX on exit
        if $(.STATUS_0)
            $(.SHELL_SET)status="$(.EXIT_SUCCESS)"
        $(.ENDIF)
        exit "$(.STATUS)"
    }

    actions quietly updated ignore piecemeal together RmTemps
    {
        $(.RM) $(>:WJ=;*,);*
    }
}


.MAKE_FILE = [ common.file-creation-command ] ;


rule unit-test ( target : source : properties * )
{
    if ! [ feature.get-values testing.launcher : $(properties) ]
    {
        ## On VMS set default launcher to MCR
        if [ os.name ] = VMS { LAUNCHER on $(target) = MCR ; }
    }
}

actions unit-test
{
    $(PATH_SETUP)
    $(LAUNCHER) "$(>)" $(ARGS) && $(.MAKE_FILE) "$(<)"
}

if [ os.name ] = VMS
{
    actions unit-test
    {
        $(PATH_SETUP)
        pipe $(LAUNCHER) $(>:W) $(ARGS) && $(.MAKE_FILE) $(<:W)
    }
}

# Note that this rule may be called multiple times for a single target in case
# there are multiple actions operating on the same target in sequence. One such
# example are msvc exe targets first created by a linker action and then updated
# with an embedded manifest file by a separate action.
rule record-time ( target : source : start end user system )
{
    local src-string = [$(source:G=:J=",")"] " ;
    USER_TIME on $(target) += $(src-string)$(user) ;
    SYSTEM_TIME on $(target) += $(src-string)$(system) ;

    # We need the following variables because attempting to perform such
    # variable expansion in actions would not work due to quotes getting treated
    # as regular characters.
    USER_TIME_SECONDS on $(target) += $(src-string)$(user)" seconds" ;
    SYSTEM_TIME_SECONDS on $(target) += $(src-string)$(system)" seconds" ;
}

# Calling this rule requests that Boost Build time how long it takes to build
# the 'source' target and display the results both on the standard output and in
# the 'target' file.
#
rule time ( target : sources + : properties *  )
{
    # Set up rule for recording timing information.
    __TIMING_RULE__ on $(sources) = testing.record-time $(target) ;

    # Make sure the sources get rebuilt any time we need to retrieve that
    # information.
    REBUILDS $(target) : $(sources) ;
}


actions time
{
    echo user: $(USER_TIME)
    echo system: $(SYSTEM_TIME)

    echo user: $(USER_TIME_SECONDS) > "$(<)"
    echo system: $(SYSTEM_TIME_SECONDS) >> "$(<)"
}

if [ os.name ] = VMS
{
    actions time
    {
        WRITE SYS$OUTPUT "user: ", "$(USER_TIME)"
        WRITE SYS$OUTPUT "system: ", "(SYSTEM_TIME)"

        PIPE WRITE SYS$OUTPUT "user: ", "$(USER_TIME_SECONDS)" | TYPE SYS$INPUT /OUT=$(<:W)
        PIPE WRITE SYS$OUTPUT "system: ", "$(SYSTEM_TIME_SECONDS)" | APPEND /NEW SYS$INPUT $(<:W)
    }
}
