*** Settings ***
Library         String
Library         Process
Library         Collections
Library         OperatingSystem
Library         helper.py

*** Variables ***
${SERVER_REMOTE_DEBUG}       False
${SERVER_REMOTE_PORT}        12345
${SERVER_REMOTE_SUSPEND}     y
${SKIP_RUNNING_SERVER}       False
${CONFIGURATION}             Release
${PORT_NUMBER}               9999
${DIRECTORY}                 ${CURDIR}/../output/bin/${CONFIGURATION}
${RENODETOOLS}               ${CURDIR}/../tools
${BINARY_NAME}               Renode.dll
${HOTSPOT_ACTION}            None
${DISABLE_GUI}               False
${DEFAULT_UART_TIMEOUT}      8
${CREATE_SNAPSHOT_ON_FAIL}   True
${SAVE_LOGS}                 True
${SAVE_LOGS_WHEN}            Fail
${HOLD_ON_ERROR}             False
${CREATE_EXECUTION_METRICS}  False
${PROFILER_PROCESS}          None

*** Keywords ***
Setup
    ${SYSTEM}=          Evaluate    platform.system()    modules=platform

    ${CONFIGURATION}=  Set Variable If  not ${SKIP_RUNNING_SERVER} and ${SERVER_REMOTE_DEBUG}
    ...    Debug
    ...    ${CONFIGURATION}

    # without --hide-log the output buffers may get full and the program can hang
    # http://robotframework.org/robotframework/latest/libraries/Process.html#Standard%20output%20and%20error%20streams
    @{PARAMS}=           Create List  --robot-server-port  ${PORT_NUMBER}  --hide-log

    IF  ${DISABLE_GUI}
        Insert Into List  ${PARAMS}  0  --disable-gui
    END

    IF  not ${SKIP_RUNNING_SERVER}
        File Should Exist    ${DIRECTORY}/${BINARY_NAME}  msg=Robot Framework remote server binary not found (${DIRECTORY}/${BINARY_NAME}). Did you forget to build it in ${CONFIGURATION} configuration?
    END

    IF  not ${SKIP_RUNNING_SERVER} and ${SERVER_REMOTE_DEBUG} and '${SYSTEM}' == 'Windows'
         Fatal Error  Windows doesn't support server remote debug option.
    END

    #The distinction between operating systems is because localhost is not universally understood on Linux and 127.0.0.1 is not always available on Windows.
    IF  not '${SYSTEM}' == 'Windows'
        Wait Until Keyword Succeeds  60s  1s
          ...   Import Library  Remote  http://127.0.0.1:${PORT_NUMBER}/    AS  Renode
    END

    IF  '${SYSTEM}' == 'Windows'
        Wait Until Keyword Succeeds  60s  1s
          ...   Import Library  Remote  http://localhost:${PORT_NUMBER}/    AS  Renode
    END

    Setup Renode

Setup Renode
    Set Default Uart Timeout  ${DEFAULT_UART_TIMEOUT}

    IF  ${SAVE_LOGS}
        Enable Logging To Cache
    END

    ${allowed_chars}=   Set Variable                 abcdefghijklmnopqrstuvwxyz01234567890_-
    ${metrics_fname}=   Convert To Lower Case        ${SUITE_NAME}
    ${metrics_fname}=   Replace String               ${metrics_fname}      ${SPACE}              _
    ${metrics_fname}=   Replace String Using Regexp  ${metrics_fname}      [^${allowed_chars}]+  ${EMPTY}
    ${metrics_path}=    Join Path                    ${RESULTS_DIRECTORY}  profiler-${metrics_fname}

    IF      ${CREATE_EXECUTION_METRICS}
        Execute Command    EnableProfilerGlobally "${metrics_path}"
    END

    Reset Emulation

Teardown
    IF  not ${SKIP_RUNNING_SERVER}
        Stop Remote Server
    END

    IF  not ${SKIP_RUNNING_SERVER}
        Wait For Process
    END

Sanitize Test Name
    [Arguments]        ${test_name}
    ${test_name}=      Replace String  ${test_name}  ${SPACE}  _
    # double quotes because editor syntax highlighting gets confused with a single one
    ${test_name}=      Replace String Using Regexp  ${test_name}  [/""]  -
    RETURN             ${test_name}

Get Sanitized Test Name
    ${retry_index}=    Get Variable Value   \${RETRYFAILED_RETRY_INDEX}  0
    ${test_name}=      Set Variable  ${SUITE NAME}.${TEST NAME}.fail${retry_index}
    ${test_name}=      Sanitize Test Name  ${test_name}
    RETURN             ${test_name}

Create Snapshot Of Failed Test
    Return From Keyword If   'skipped' in @{TEST TAGS}

    ${test_name}=      Get Sanitized Test Name

    ${snapshots_dir}=  Set Variable  ${RESULTS_DIRECTORY}/snapshots
    Create Directory   ${snapshots_dir}

    ${snapshot_path}=  Set Variable  "${snapshots_dir}/${test_name}.save"
    Execute Command  Save ${snapshot_path}
    Log To Console   !!!!! Emulation's state saved to ${snapshot_path}

Save Test Log
    Return From Keyword If   'skipped' in @{TEST TAGS}

    ${test_name}=      Get Sanitized Test Name

    ${logs_dir}=       Set Variable  ${RESULTS_DIRECTORY}/logs
    Create Directory   ${logs_dir}

    ${log_path}=       Set Variable  ${logs_dir}/${test_name}.log
    Log To Console     !!!!! Log saved to "${log_path}"
    Save Cached Log    ${log_path}

Test Setup
    IF  'profiling' in @{TEST TAGS}
        Start Profiler
    END

Test Teardown
    Stop Profiler

    ${failed}=  Run Keyword If Test Failed  Set Variable  True
    IF  ${failed}
        # Some of the exception messages end with whitespace.
        ${message}=  Strip String  ${TEST_MESSAGE}  mode=right
        Set Test Message           ${message}
    END

    ${timed_out}=  Run Keyword If Timeout Occurred  Set Variable  True
    IF  ${timed_out}  RETURN

    IF  ${CREATE_SNAPSHOT_ON_FAIL}
        Run Keyword If Test Failed
          ...   Create Snapshot Of Failed Test
    END

    IF  ${SAVE_LOGS}
        IF  "${SAVE_LOGS_WHEN}" == "Always"
            Save Test Log
        ELSE IF  "${SAVE_LOGS_WHEN}" == "Fail"
            Run Keyword If Test Failed
              ...   Save Test Log
        END
    END

    IF  ${HOLD_ON_ERROR}
        ${res}=  Run Keyword If Test Failed
        ...    Run Keyword And Ignore Error
        ...    Import Library  Dialogs

        Run Keyword If Test Failed  Run Keywords
        ...         Run Keyword If    '${res[0]}' == 'FAIL'    Log                Couldn't load the Dialogs library - interactive debugging is not possible    console=True
        ...    AND  Run Keyword If    '${res[0]}' != 'FAIL'    Open GUI
        ...    AND  Run Keyword If    '${res[0]}' != 'FAIL'    Pause Execution    Test failed. Press OK once done debugging.
        ...    AND  Run Keyword If    '${res[0]}' != 'FAIL'    Close GUI
    END

    Reset Emulation
    Clear Cached Log

Hot Spot
    Handle Hot Spot  ${HOTSPOT_ACTION}

Start Profiler
    ${test_name}=               Get Sanitized Test Name

    ${traces_dir}=              Set Variable  ${RESULTS_DIRECTORY}/traces
    Create Directory            ${traces_dir}

    ${trace_path}=              Set Variable  ${traces_dir}/${test_name}
    Log To Console              !!!!! Writing nettrace to "${trace_path}.nettrace"
    Log To Console              !!!!! Writing speedscope trace to "${trace_path}.speedscope.json"
    # note that those logs may not be bundled with log and snapshot saving info

    ${proc}=                    Start Process  dotnet  trace  collect  -p  ${RENODE_PID}  --format  Speedscope  -o  ${trace_path}.nettrace
    Set Test Variable           ${PROFILER_PROCESS}  ${proc}

Stop Profiler
    IF  ${PROFILER_PROCESS}
        Terminate Process           ${PROFILER_PROCESS}
        Set Test Variable           ${PROFILER_PROCESS}  None
    END

Dump Registers Range
    [Tags]  robot:private
    [Arguments]     ${Device}   ${End}  ${Start}=0
    ${Dump}=        Create Dictionary

    FOR     ${index}    IN RANGE    ${Start}    ${End}+1    4
        ${RegValue}=    Execute Command     ${Device} ReadDoubleWord ${index}
        ${RegValue}=    Remove String   ${RegValue}     \n   # Remove newlines to make diff output more readable
        Set To Dictionary   ${Dump}     ${index}    ${RegValue}
    END
    RETURN  ${Dump}

Dump Devices Registers
    [Documentation]     Dump devices' registers values into a dictionary of dictionary
    ...                 ``{'device':{reg_offset:reg_value}}`` from a dictionary of ``devices:list of
    ...                 offset ranges``.
    ...
    ...                 Dumping registers when there is a gap in the model may work and simplify the
    ...                 test case.
    ...
    ...                 Example:
    ...                 | ${RegistersToDump} = | Create Dictionary      | adc1=${{ [(0x0, 0xC4), (0x308, 0x308)] }} |
    ...                 | &{Dump} =            | Dump Devices Registers | ${RegistersToDump} |
    ...
    ...                 Please not the usage of inline python evaluation ``${{}}`` to keep tuples
    ...                 instead of having strings.
    [Arguments]     ${DevicesAndRanges}
    ${Dump}=        Create Dictionary

    FOR     ${Device}   ${Ranges}    IN  &{DevicesAndRanges}
        FOR     ${Range}    IN  @{Ranges}
            ${RegsDump}=    Dump Registers Range  ${Device}    ${Range}[1]    ${Range}[0]
            Set To Dictionary   ${Dump}     ${Device}=${RegsDump}
            Log Dictionary  ${Dump}
        END
    END
    RETURN  ${Dump}

Devices Registers Dump Should Be Equal
    [Documentation]     Compare dumps returned by Dump Devices Registers.
    [Arguments]         ${DumpA}    ${DumpB}    ${NameDumpA}=DumpA  ${NameDumpB}=DumpB

    ${DevicesA}=    Get Dictionary Keys     ${DumpA}
    ${DevicesB}=    Get Dictionary Keys     ${DumpB}

    TRY
        Dictionaries Should Be Equal    ${DumpA}    ${DumpB}
    EXCEPT
        # On error, printing the whole device:'registers_dump' dictionary entry is hard to read. So
        # instead, let's compare device by device to print the first
        FOR     ${device}   IN  @{DevicesA}
            ${DeviceDumpA}=     Get From Dictionary     dictionary=${DumpA}     key=${device}
            ${DeviceDumpB}=     Get From Dictionary     dictionary=${DumpB}     key=${device}
            TRY
                Dictionaries Should Be Equal    ${DeviceDumpA}     ${DeviceDumpB}
            EXCEPT
                CONTINUE
            END
        END
        FAIL    ${NameDumpA} and ${NameDumpB} dumps differ, see logs of previous for loop for
        ...     more details on which entries are different.
    END
