unit TestBreakPoint; {$mode objfpc}{$H+} interface uses SysUtils, fpcunit, testutils, testregistry, DbgIntfBaseTypes, DbgIntfDebuggerBase, DbgIntfMiscClasses, TestBase, GDBMIDebugger, TestDbgControl, TestDbgTestSuites, TestDbgConfig, LCLProc, TestWatches; type { TTestBrkGDBMIDebugger } TTestBrkGDBMIDebugger = class(TGDBMIDebugger) public procedure TestInterruptTarget; end; { TTestBreakPoint } TTestBreakPoint = class(TGDBTestCase) private FCurLine: Integer; FCurFile: string; FBrkErr: TDbgBreakpoint; protected function DoGetFeedBack(Sender: TObject; const AText, AInfo: String; AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult; function GdbClass: TGDBMIDebuggerClass; override; procedure DoCurrent(Sender: TObject; const ALocation: TDBGLocationRec); published // Due to a linker error breakpoints can point to invalid addresses procedure TestStartMethod; procedure TestStartMethodBadLinker; // not called prog in front of MAIN // causes bad linker with dwarf procedure TestStartMethodStep; procedure TestBadAddrBreakpoint; procedure TestInteruptWhilePaused; end; const BREAK_LINE_BREAKPROG = 28; BREAK_LINE_BREAKPROG_MAIN = 24; /// ..26 implementation var ControlTestTestBreakPoint, ControlTestTestBreakPointStartMethod, ControlTestTestBreakPointBadAddr, ControlTestTestBreakPointBadInterrupt, ControlTestTestBreakPointBadInterruptAll: Pointer; procedure TTestBrkGDBMIDebugger.TestInterruptTarget; begin InterruptTarget; end; { TTestBrkGDBMIDebugger } { TTestBreakPoint } procedure TTestBreakPoint.DoCurrent(Sender: TObject; const ALocation: TDBGLocationRec); begin FCurFile := ALocation.SrcFile; FCurLine := ALocation.SrcLine; end; procedure TTestBreakPoint.TestStartMethod; var dbg: TGDBMIDebugger; TestExeName, s: string; i: TGDBMIDebuggerStartBreak; IgnoreRes: String; begin if SkipTest then exit; if not TestControlCanTest(ControlTestTestBreakPointStartMethod) then exit; ClearTestErrors; FBrkErr := nil; TestCompile(AppDir + 'WatchesPrg.pas', TestExeName); for i := Low(TGDBMIDebuggerStartBreak) to high(TGDBMIDebuggerStartBreak) do begin WriteStr(s, i); try dbg := StartGDB(AppDir, TestExeName); dbg.OnCurrent := @DoCurrent; TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := i; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; dbg.Run; IgnoreRes := ''; case DebuggerInfo.Version of 070400..070499: if i = gdsbAddZero then IgnoreRes:= 'gdb 7.4.x does not work with gdsbAddZero'; end; TestTrue(s+' not in error state 1', dbg.State <> dsError, 0, IgnoreRes); TestTrue(s+' at break', FCurLine = BREAK_LINE_FOOFUNC, 0, IgnoreRes); TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := gdsbDefault; finally dbg.Done; CleanGdb; dbg.Free; end; end; AssertTestErrors; end; procedure TTestBreakPoint.TestStartMethodBadLinker; var dbg: TGDBMIDebugger; TestExeName, s: string; i: TGDBMIDebuggerStartBreak; IgnoreRes: String; begin if SkipTest then exit; if not TestControlCanTest(ControlTestTestBreakPointStartMethod) then exit; ClearTestErrors; FBrkErr := nil; TestCompile(AppDir + 'breakprog.pas', TestExeName); for i := Low(TGDBMIDebuggerStartBreak) to high(TGDBMIDebuggerStartBreak) do begin WriteStr(s, i); try dbg := StartGDB(AppDir, TestExeName); dbg.OnCurrent := @DoCurrent; TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := i; with dbg.BreakPoints.Add('breakprog.pas', BREAK_LINE_BREAKPROG) do begin InitialEnabled := True; Enabled := True; end; dbg.Run; IgnoreRes := ''; case DebuggerInfo.Version of 000000..070399: if (i = gdsbAddZero) and (CompilerInfo.Version = 020604) then IgnoreRes:= 'gdb below 7.4 and fpc 2.6.4 does not work with gdsbAddZero'; 070400..070499: if i = gdsbAddZero then IgnoreRes:= 'gdb 7.4.x does not work with gdsbAddZero'; end; TestTrue(s+' not in error state 1', dbg.State <> dsError, 0, IgnoreRes); TestTrue(s+' at break', FCurLine = BREAK_LINE_BREAKPROG, 0, IgnoreRes); TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := gdsbDefault; finally dbg.Done; CleanGdb; dbg.Free; end; end; AssertTestErrors; end; procedure TTestBreakPoint.TestStartMethodStep; var dbg: TGDBMIDebugger; TestExeName, s: string; i: TGDBMIDebuggerStartBreak; IgnoreRes: String; begin if SkipTest then exit; if not TestControlCanTest(ControlTestTestBreakPointStartMethod) then exit; ClearTestErrors; FBrkErr := nil; TestCompile(AppDir + 'breakprog.pas', TestExeName, '_callall', ' -dCALL_ALL '); for i := Low(TGDBMIDebuggerStartBreak) to high(TGDBMIDebuggerStartBreak) do begin WriteStr(s, i); try dbg := StartGDB(AppDir, TestExeName); dbg.OnCurrent := @DoCurrent; TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := i; dbg.StepOver; IgnoreRes := ''; if i = gdsbAddZero then IgnoreRes:= 'launch with step does not work with gdsbAddZero'; TestTrue(s+' not in error state 1', dbg.State <> dsError, 0, IgnoreRes); TestTrue(s+' at break', (FCurLine >= BREAK_LINE_BREAKPROG_MAIN) AND (FCurLine <= BREAK_LINE_BREAKPROG_MAIN + 2), 0, IgnoreRes); TGDBMIDebuggerProperties(dbg.GetProperties).InternalStartBreak := gdsbDefault; finally dbg.Done; CleanGdb; dbg.Free; end; end; AssertTestErrors; end; function TTestBreakPoint.DoGetFeedBack(Sender: TObject; const AText, AInfo: String; AType: TDBGFeedbackType; AButtons: TDBGFeedbackResults): TDBGFeedbackResult; begin Result := frOk; ReleaseRefAndNil(FBrkErr); end; function TTestBreakPoint.GdbClass: TGDBMIDebuggerClass; begin Result := TTestBrkGDBMIDebugger; end; procedure TTestBreakPoint.TestBadAddrBreakpoint; var TestExeName: string; dbg: TTestBrkGDBMIDebugger; i: LongInt; begin if SkipTest then exit; if not TestControlCanTest(ControlTestTestBreakPointBadAddr) then exit; ClearTestErrors; FBrkErr := nil; TestCompile(AppDir + 'WatchesPrg.pas', TestExeName); try dbg := TTestBrkGDBMIDebugger(StartGDB(AppDir, TestExeName)); dbg.OnCurrent := @DoCurrent; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; dbg.OnFeedback := @DoGetFeedBack; dbg.Run; // hit breakpoint FBrkErr := dbg.BreakPoints.Add(TDBGPtr(200)); with FBrkErr do begin InitialEnabled := True; Enabled := True; end; TestTrue('not in error state 1', dbg.State <> dsError); i := FCurLine; dbg.StepOver; TestTrue('not in error state 2', dbg.State <> dsError); //TestTrue('gone next line 2', i <> FCurLine); i := FCurLine; dbg.StepOver; TestTrue('not in error state 3', dbg.State <> dsError); //TestTrue('gone next line 3', i <> FCurLine); i := FCurLine; dbg.StepOver; TestTrue('not in error state 4', dbg.State <> dsError); //TestTrue('gone next line 4', i <> FCurLine); finally dbg.Done; CleanGdb; dbg.Free; end; AssertTestErrors; end; procedure TTestBreakPoint.TestInteruptWhilePaused; var TestExeName, Err, IgnoreRes: string; dbg: TTestBrkGDBMIDebugger; i, m: LongInt; begin if SkipTest then exit; if not TestControlCanTest(ControlTestTestBreakPointBadInterrupt) then exit; IgnoreRes := ''; case DebuggerInfo.Version of 0..069999: IgnoreRes:= 'all gdb 6.x may or may not fail'; 070000: IgnoreRes:= 'gdb 7.0.0 may or may not fail'; // 7.0.50 seems to always pass // 7.1.x seems to always pass // 7.2.x seems to always pass 070300..070399: IgnoreRes:= 'gdb 7.3.x may or may not fail'; 070400..070499: IgnoreRes:= 'gdb 7.4.x may or may not fail'; 070500..070599: IgnoreRes:= 'gdb 7.5.x may or may not fail'; 070600..070699: IgnoreRes:= 'gdb 7.6.x may or may not fail'; 070700..070700: IgnoreRes:= 'gdb 7.7.0 may or may not fail'; end; (* Trigger a InterruptTarget while paused. Test if the app can continue, and reach it normal exit somehow (even if multiply interupts must be skipped) *) ClearTestErrors; FBrkErr := nil; TestCompile(AppDir + 'WatchesPrg.pas', TestExeName, '_wsleep', ' -dWITH_SLEEP '); try LogToFile(LineEnding+'###################### with pause -- 1 break ########################'+LineEnding+LineEnding); Err := ''; dbg := TTestBrkGDBMIDebugger(StartGDB(AppDir, TestExeName)); dbg.OnCurrent := @DoCurrent; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; dbg.OnFeedback := @DoGetFeedBack; dbg.Run; // at main break if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Never reached breakpoint to start with'; if dbg.State <> dsPause then Err := Err + 'Never entered dsPause to start with'; //dbg.StepOver; //dbg.StepOver; LogToFile('##### INTERRUPT #####'); dbg.TestInterruptTarget; dbg.Run; // at main break if dbg.State = dsError then Err := Err + 'Enterred dsError after 1st exec-continue'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop after 1st exec-continue'; // try to skip to next break if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if dbg.State = dsError then Err := Err + 'Enterred dsError before reaching break the 2nd time'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop before reaching break the 2nd time'; if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Did not reached breakpoint for the 2nd time'; dbg.Run; if (dbg.State = dsPause) then dbg.Run; // got the break really late if (dbg.State = dsPause) then dbg.Run; // got the break really late if dbg.State <> dsStop then Err := Err + 'Never reached final stop'; finally TestEquals('Passed pause run', '', Err); dbg.Done; CleanGdb; dbg.Free; end; if TestControlCanTest(ControlTestTestBreakPointBadInterruptAll) then begin try LogToFile(LineEnding+'###################### with pause -- 2 breaks ########################'+LineEnding+LineEnding); Err := ''; dbg := TTestBrkGDBMIDebugger(StartGDB(AppDir, TestExeName)); dbg.OnCurrent := @DoCurrent; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC_NEST) do begin InitialEnabled := True; Enabled := True; end; dbg.OnFeedback := @DoGetFeedBack; dbg.Run; // at nested break dbg.Run; // at main break if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Never reached breakpoint to start with'; if dbg.State <> dsPause then Err := Err + 'Never entered dsPause to start with'; //dbg.StepOver; //dbg.StepOver; LogToFile('##### INTERRUPT #####'); dbg.TestInterruptTarget; dbg.Run; // at main break if dbg.State = dsError then Err := Err + 'Enterred dsError after 1st exec-continue'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop after 1st exec-continue'; // try to skip to next break if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC_NEST) then dbg.Run; if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC_NEST) then dbg.Run; if dbg.State = dsError then Err := Err + 'Enterred dsError before reaching nest break the 2nd time'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop before reaching nest break the 2nd time'; if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC_NEST then Err := Err + 'Did not reached best breakpoint for the 2nd time'; dbg.Run; // try to skip to next break if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if dbg.State = dsError then Err := Err + 'Enterred dsError before reaching break the 2nd time'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop before reaching break the 2nd time'; if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Did not reached breakpoint for the 2nd time'; dbg.Run; if (dbg.State = dsPause) then dbg.Run; // got the break really late if (dbg.State = dsPause) then dbg.Run; // got the break really late if dbg.State <> dsStop then Err := Err + 'Never reached final stop'; finally TestEquals('Passed pause run 2 breaks', '', Err); dbg.Done; CleanGdb; dbg.Free; end; end; TestCompile(AppDir + 'WatchesPrg.pas', TestExeName); m := 1; if TestControlCanTest(ControlTestTestBreakPointBadInterruptAll) then m := 5; // run extra tests of Passed none-pause run Err := ''; for i := 1 to m do begin try LogToFile(LineEnding+'###################### withOUT pause -- NO stepping ########################'+LineEnding+LineEnding); dbg := TTestBrkGDBMIDebugger(StartGDB(AppDir, TestExeName)); dbg.OnCurrent := @DoCurrent; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; dbg.OnFeedback := @DoGetFeedBack; dbg.Run; // at main break if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Never reached breakpoint to start with'; if dbg.State <> dsPause then Err := Err + 'Never entered dsPause to start with'; //dbg.StepOver; //dbg.StepOver; LogToFile('##### INTERRUPT #####'); dbg.TestInterruptTarget; dbg.Run; // at main break if dbg.State = dsError then Err := Err + 'Enterred dsError after 1st exec-continue'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop after 1st exec-continue'; // try to skip to next break if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if dbg.State = dsError then Err := Err + 'Enterred dsError before reaching break the 2nd time'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop before reaching break the 2nd time'; if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Did not reached breakpoint for the 2nd time'; dbg.Run; if (dbg.State = dsPause) then dbg.Run; // got the break really late if (dbg.State = dsPause) then dbg.Run; // got the break really late if dbg.State <> dsStop then Err := Err + 'Never reached final stop'; finally dbg.Done; CleanGdb; dbg.Free; end; end; TestEquals('Passed none-pause run', '', Err, 0, IgnoreRes); if TestControlCanTest(ControlTestTestBreakPointBadInterruptAll) then begin try LogToFile(LineEnding+'###################### withOUT pause -- with stepping ########################'+LineEnding+LineEnding); Err := ''; dbg := TTestBrkGDBMIDebugger(StartGDB(AppDir, TestExeName)); dbg.OnCurrent := @DoCurrent; with dbg.BreakPoints.Add('WatchesPrg.pas', BREAK_LINE_FOOFUNC) do begin InitialEnabled := True; Enabled := True; end; dbg.OnFeedback := @DoGetFeedBack; dbg.Run; // at main break if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Never reached breakpoint to start with'; if dbg.State <> dsPause then Err := Err + 'Never entered dsPause to start with'; dbg.StepOver; dbg.StepOver; LogToFile('##### INTERRUPT #####'); dbg.TestInterruptTarget; dbg.Run; // at main break if dbg.State = dsError then Err := Err + 'Enterred dsError after 1st exec-continue'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop after 1st exec-continue'; // try to skip to next break if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if (dbg.State = dsPause) and (dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC) then dbg.Run; if dbg.State = dsError then Err := Err + 'Enterred dsError before reaching break the 2nd time'; if dbg.State = dsStop then Err := Err + 'Enterred dsStop before reaching break the 2nd time'; if dbg.GetLocation.SrcLine <> BREAK_LINE_FOOFUNC then Err := Err + 'Did not reached breakpoint for the 2nd time'; dbg.Run; if (dbg.State = dsPause) then dbg.Run; // got the break really late if (dbg.State = dsPause) then dbg.Run; // got the break really late if dbg.State <> dsStop then Err := Err + 'Never reached final stop'; finally TestEquals('Passed none-pause run with steps', '', Err, 0, IgnoreRes); dbg.Done; CleanGdb; dbg.Free; end; end; AssertTestErrors; end; initialization RegisterDbgTest(TTestBreakPoint); ControlTestTestBreakPoint := TestControlRegisterTest('TestBreakPoint'); ControlTestTestBreakPointStartMethod := TestControlRegisterTest('StartMethod', ControlTestTestBreakPoint); ControlTestTestBreakPointBadAddr := TestControlRegisterTest('BadAddr', ControlTestTestBreakPoint); ControlTestTestBreakPointBadInterrupt := TestControlRegisterTest('BadInterrupt', ControlTestTestBreakPoint); ControlTestTestBreakPointBadInterruptAll := TestControlRegisterTest('All', ControlTestTestBreakPointBadInterrupt); end.