polserver/testsuite/pol/testpkgs/debugger/debugger_client.src
Kevin Eady 3ccf701ce9
Format testsuite scripts, pol-core support modules (#713)
* Format escript testsuite

* Manual formatting for escript testsuite
- Add format-off to places where formatter does what it should, but we don't like the results
- Manually change places where formatter outputs weird things _without_ format-off, so next format-all would show changes if not fixed

* Update .err files; add update_err.py helper script

Use the format `:line:col: message` for all .err file checks. This
allows us to change the file names without needing to edit the .err
contents.

Note that `in/inc0{1,2}.err` need have error messages between POSIX and
Windows, so their messages are a bit more "generic" than the other errs.

* Format pol testsuite

* Manual formatting for pol testsuite

* Format pol-core modules

* Manual formatting for pol-core modules

* Additional format pol-core modules

Use FormatterLineWidth=10000 since module functions must be on one line
because of the parse_modules target

* Address Discord comments

- Support multi-line module function declarations in parse_modules

* Format on df07831
2024-10-04 10:29:55 +02:00

648 lines
19 KiB
Text

use os;
use file;
include "testutil";
const C_RED := "\x1b[31m";
const C_GREEN := "\x1b[32m";
const C_YELLOW := "\x1b[33m";
const C_CYAN := "\x1b[36m";
const C_MAGENTA_LIGHT := "\x1b[95m";
const C_RESET := "\x1b[0m";
const CONTENT_LENGTH_HEADER_KEY := "Content-Length: ";
const CONTENT_LENGTH_HEADER_KEY_LENGTH := 16;
const CONTENT_LENGTH_HEADER_VALUE_START := CONTENT_LENGTH_HEADER_KEY_LENGTH + 1;
// List of events
var dapEvents := {};
// Maps a request sequence to a response object
var dapResponses := dictionary{};
// Global aux script connection
var connection;
// Next request sequence number
var nextSequence := 1;
var recvBuffer := "";
program auxservice( conn, params )
connection := conn;
var result;
var test_script_pid := params.test_script_pid;
var test_type := params.test_type;
if ( test_type == "attach" )
result := run_attach_test( params.debuggee_pid );
elseif ( test_type == "launch" )
result := run_launch_test();
else
result := ret_error( $"Unknown debugger test type '{test_type}'" );
endif
GetProcess( test_script_pid ).SendEvent( struct{ result := result } );
connection := 0;
endprogram
function run_launch_test()
// Get script source
var source := ReadFile( "debuggee.src" );
if ( !source )
return ret_error( source );
endif
var print_linenum;
foreach line in source
if ( line.find( "/* print */" ) )
print_linenum := _line_iter;
endif
endforeach
var err;
// Send request: initialize
if ( !request_succeeded( err, "initialize", struct{ adapterID := "pol", password := "debugme" } ) )
return err;
endif
// Wait for event: initialized
if ( !event_received( err, "initialized" ) )
return err;
endif
// Send request: launch
// Note: Sets the program's parameter as "stop"
if ( !request_succeeded( err, "launch", struct{ "script" := ":testdebugger:debuggee",
arg := "S4:stop" /* packed */ }
) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: threads
// Note: Since we only have one thread that never changes, test sends this message only once.
var threads;
if ( !( threads := request_succeeded( err, "threads" ) ) )
return err;
endif
var threadId := threads.threads[1].id;
// Send request: stackTrace
var stackTrace;
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
// Send request: scopes
var scopes;
if ( !( scopes := request_succeeded( err, "scopes", struct{ frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
var evaluate;
if ( !( evaluate := request_succeeded( err, "evaluate", struct{ expression := "running",
frameId := scopes.scopes[1].variablesReference }
) ) )
return err;
endif
if ( evaluate.result != "\"stop\"" )
return ret_error( $"Program argument from launch request `running` does not equal '\"stop\"': '{evaluate.result}' != '\"stop\"'" );
endif
// Send request: continue
if ( !request_succeeded( err, "continue", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: output
var output;
if ( !( output := event_received( err, "output" ) ) )
return err;
endif
var expected := struct{ category := "stdout",
output := "Loop finished!\n",
line := print_linenum,
source := struct{ path := stackTrace.stackFrames[1].source.path } };
if ( output.category != expected.category || output.output != expected.output ||
output.line != expected.line || output.source.path != expected.source.path )
return ret_error( $"Incorrect output event. Expected: {expected}, actual {output}" );
endif
// Wait for event: exited
if ( !event_received( err, "exited" ) )
return err;
endif
return 1;
endfunction
function run_attach_test( debuggee_pid )
var err;
// Send request: initialize
if ( !request_succeeded( err, "initialize", struct{ adapterID := "pol", password := "debugme" } ) )
return err;
endif
// Wait for event: initialized
if ( !event_received( err, "initialized" ) )
return err;
endif
// Send request: processes
var processes;
if ( !( processes := request_succeeded( err, "processes", struct{ filter := "debuggee" } ) ) )
return err;
endif
if ( processes.processes[1].id != debuggee_pid )
return ret_error( $"Process id does not match! Expected {processes.processes[1].id} == {debuggee_pid}" );
endif
// Send request: attach
if ( !request_succeeded( err, "attach", struct{ pid := debuggee_pid } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: threads
// Note: Since we only have one thread that never changes, test sends this message only once.
var threads;
if ( !( threads := request_succeeded( err, "threads" ) ) )
return err;
endif
var threadId := threads.threads[1].id;
// Send request: stackTrace
var stackTrace;
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
// Send request: scopes
var scopes;
if ( !( scopes := request_succeeded( err, "scopes", struct{ frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
var scope_varibles := array{};
foreach scope in ( scopes.scopes )
var variables;
if ( !( variables := request_succeeded( err, "variables", struct{ variablesReference := scope.variablesReference }
) ) )
return err;
endif
foreach variable in ( variables.variables )
var evaluate;
if ( !( evaluate := request_succeeded( err, "evaluate", struct{ expression := variable.name,
frameId := scope.variablesReference }
) ) )
return err;
endif
if ( variable.value != evaluate.result )
return ret_error( $"Variables value does not match evaluate result: '{variable.value}' != '{evaluate.result}'" );
endif
if ( variable.variablesReference )
var inner_variables;
if ( !( inner_variables := request_succeeded( err, "variables", struct{ variablesReference := variable.variablesReference }
) ) )
return err;
endif
endif
endforeach
endforeach
// Get script source
var source := ReadFile( "debuggee.src" );
if ( !source )
return ret_error( source );
endif
var setting_breakpoints := array{};
foreach line in source
if ( line.find( "/* set_bp */" ) )
setting_breakpoints.append( struct{ line := _line_iter } );
endif
endforeach
// Send request: setBreakpoints
if ( !request_succeeded( err, "setBreakpoints", struct{ source := struct{
path := stackTrace.stackFrames[1].source.path },
breakpoints := setting_breakpoints }
) )
return err;
endif
// Send request: continue
if ( !request_succeeded( err, "continue", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "func2" )
return ret_error( $"Expected to be in `func2` after first continue: '{stackTrace.stackFrames[1].name}' != 'func2'" );
endif
// Send request: stepIn
if ( !request_succeeded( err, "stepIn", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "func3" )
return ret_error( $"Expected to be in `func3` after first stepIn: '{stackTrace.stackFrames[1].name}' != 'func3'" );
endif
if ( stackTrace.stackFrames[2].name != "func2" )
return ret_error( $"Expected previous frame to be in `func2` after first stepIn: '{stackTrace.stackFrames[2].name}' != 'func2'" );
endif
// Send request: continue
if ( !request_succeeded( err, "continue", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "funcShadowed" )
return ret_error( $"Expected to be in `funcShadowed` after second continue: '{stackTrace.stackFrames[1].name}' != 'funcShadowed'" );
endif
var expectedNextLine := stackTrace.stackFrames[1].line + 1;
// Send request: evaluate
var evaluate;
if ( !( evaluate := request_succeeded( err, "evaluate", struct{ expression := "shadowed_variable",
frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
if ( evaluate.result != "\"funcShadowed:param1\"" )
return ret_error( $"Expected value of `shadowed_variable` to be '\"funcShadowed:param1\"': '{evaluate.result}' != '\"funcShadowed:param1\"'" );
endif
// Send request: next
if ( !request_succeeded( err, "next", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].line != expectedNextLine )
return ret_error( $"Expected to be on line {expectedNextLine} after next: {stackTrace.stackFrames[1].line} != ${expectedNextLine}" );
endif
// Send request: continue
if ( !request_succeeded( err, "continue", struct{ threadId := threadId } ) )
return err;
endif
// Send request: pause
if ( !request_succeeded( err, "pause", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "funcShadowed" )
return ret_error( $"Expected to be in `funcShadowed` after third continue: '{stackTrace.stackFrames[1].name}' != 'funcShadowed'" );
endif
// Send request: scopes
if ( !( scopes := request_succeeded( err, "scopes", struct{ frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
// Send request: setVariable [local]
var setVariable;
if ( !( setVariable := request_succeeded( err, "setVariable", struct{ name := "was_paused",
value := "1",
variablesReference := scopes.scopes[1].variablesReference
}
) ) )
return err;
endif
if ( setVariable.type != "number" || setVariable.value != "1" )
return ret_error( $"Expected setVariable for `was_paused` to be type = number, value = '1': {setVariable.type} != 'number' || {setVariable.value} != '1'" );
endif
// Send request: stepOut
if ( !request_succeeded( err, "stepOut", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "(program)" )
return ret_error( $"Expected to be in `(program)` after stepOut: '{stackTrace.stackFrames[1].name}' != '(program)'" );
endif
// Send request: next
if ( !request_succeeded( err, "next", struct{ threadId := threadId } ) )
return err;
endif
// Wait for event: stopped
if ( !event_received( err, "stopped" ) )
return err;
endif
// Send request: stackTrace
if ( !( stackTrace := request_succeeded( err, "stackTrace", struct{ threadId := threadId } ) ) )
return err;
endif
if ( stackTrace.stackFrames[1].name != "(program)" )
return ret_error( $"Expected to be in `(program)` after stepOut: '{stackTrace.stackFrames[1].name}' != '(program)'" );
endif
// Send request: evaluate
if ( !( evaluate := request_succeeded( err, "evaluate", struct{ expression := "shadowed_result",
frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
if ( evaluate.result != "1" )
return ret_error( $"Expected value of `shadowed_result` to be '1': '{evaluate.result}' != '1'" );
endif
// Send request: scopes
if ( !( scopes := request_succeeded( err, "scopes", struct{ frameId := stackTrace.stackFrames[1].id }
) ) )
return err;
endif
// Send request: setVariable [global]
if ( !( setVariable := request_succeeded( err, "setVariable", struct{ name := "global_float",
value := "1.5",
variablesReference := scopes.scopes[Len(
scopes.scopes )].variablesReference
}
) ) )
return err;
endif
// Send request: evaluate
if ( !( evaluate := request_succeeded( err, "evaluate", struct{ expression := "global_float",
frameId := stackTrace.stackFrames[Len(
stackTrace.stackFrames )].id }
) ) )
return err;
endif
if ( evaluate.result != "1.5" )
return ret_error( $"Expected value of `global_float` to be '1.5': '{evaluate.result}' != '1.5'" );
endif
return 1;
endfunction
// Test helper methods
function fill_buffers( timeout, request_seq := -1, event := "" )
var ev;
var found := 0;
outer: while ( ev := wait_for_event( timeout ) )
if ( !ev && timeout > 0 )
return error{ errortext := "Error filling buffer" };
endif
recvBuffer += ev.value;
if ( recvBuffer.length() < CONTENT_LENGTH_HEADER_KEY_LENGTH )
continue outer;
elseif ( recvBuffer[1, CONTENT_LENGTH_HEADER_KEY_LENGTH] != CONTENT_LENGTH_HEADER_KEY )
return error{ errortext := "Invalid header in buffer" };
else
var previousBufferLength := recvBuffer.length();
while ( recvBuffer )
var newlineIndex := recvBuffer.find( "\n", 3 );
if ( newlineIndex == 0 || recvBuffer == "" )
continue outer;
endif
var content_length := CInt( recvBuffer[CONTENT_LENGTH_HEADER_VALUE_START, newlineIndex] );
if ( content_length == 0 )
continue outer;
endif
var needed_length := newlineIndex + 2 + content_length;
if ( recvBuffer.length() >= needed_length )
var msg := recvBuffer[newlineIndex + 3, content_length];
var obj := UnpackJSON( msg );
if ( obj.type == "response" )
print( $"{C_MAGENTA_LIGHT}++ [response#{request_seq}] [{obj.command}]{C_RESET} {PackJSON(
obj.body )}" );
dapResponses[obj.request_seq] := obj;
if ( request_seq == obj.request_seq )
found := 1;
endif
elseif ( obj.type == "event" )
dapEvents.append( obj );
print( $"{C_MAGENTA_LIGHT}++ [event] [{obj.event}]{C_RESET} {PackJSON( obj.body )}" );
if ( event != "" && event == obj.event )
found := 1;
endif
endif
if ( needed_length == recvBuffer.length() )
recvBuffer := "";
else
recvBuffer := recvBuffer[needed_length + 1, recvBuffer.length()];
endif
endif
if ( previousBufferLength == recvBuffer.length() )
continue outer;
endif
previousBufferLength := recvBuffer.length();
Sleepms( 100 );
endwhile
if ( found )
return found;
endif
endif
endwhile
return found;
endfunction
function fill_buffers_for_request( request_seq )
var filled := fill_buffers( 0, request_seq := request_seq );
var found := dapResponses.exists( request_seq );
if ( found )
return 1;
endif
filled := fill_buffers( 5, request_seq := request_seq );
if ( filled == error )
return filled;
endif
return dapResponses.exists( request_seq );
endfunction
function fill_buffers_for_event( event, timeout )
var filled := fill_buffers( 0, event := event );
foreach ev in dapEvents
if ( ev.event == event )
return 1;
endif
endforeach
filled := fill_buffers( timeout, event := event );
foreach ev in dapEvents
if ( ev.event == event )
return 1;
endif
endforeach
if ( filled == error )
return filled;
endif
return 0;
endfunction
function event_wait( event, timeout := 10 )
var found := fill_buffers_for_event( event, timeout );
if ( found )
foreach result in dapEvents
if ( result.event == event )
dapEvents.erase( _result_iter );
return result.body;
endif
endforeach
endif
return error{ errortext := $"No event for type {event}" };
endfunction
function request( command, arguments )
var request_seq := nextSequence;
var body := struct{ command := command, arguments := arguments,
type := "request", seq := nextSequence++ };
var content := PackJSON( body );
print( $"{C_GREEN}-> [request#{request_seq}] [{command}]{C_RESET} {PackJSON( arguments )}" );
var request_text := "Content-Length: " + ( content.length() ) + CChr( 13 ) + CChr( 10 ) + CChr( 13 )
+ CChr( 10 ) + content;
connection.transmit( request_text );
var found := fill_buffers_for_request( request_seq );
if ( found )
var result := dapResponses[request_seq];
dapResponses.erase( request_seq );
if ( result.success )
return result.body;
endif
print( $"{C_RED}!! [response#{request_seq}]{C_RESET} failed: {result.message}" );
return error{ errortext := $"Request {command}#{request_seq} failed: {result.message}" };
endif
print( $"{C_RED}[req seq#{request_seq}]{C_RESET} timed out: {found}" );
return error{ errortext := $"Request {command}#{request_seq} timed out: {found}" };
endfunction
function request_succeeded( byref err, command, arguments := struct{} )
err := 0;
var response := request( command, arguments );
if ( response )
print( $"{C_CYAN}<- [response] [{command}]{C_RESET}" );
return response;
endif
err := ret_error( response );
return 0;
endfunction
function event_received( byref err, event, timeout := 5 )
err := 0;
var response := event_wait( event, timeout );
if ( response )
print( $"{C_CYAN}<- [event] [{event}]{C_RESET}" );
return response;
endif
err := ret_error( response );
return 0;
endfunction