Using DUnit from the Delphi IDE and avoid breakpoint on exceptions - delphi

I'm using Delphi XE and I've got a project group containing the main application and a DUnit test application. From time to time, I go to the DUnit test application to add some tests and run existing one.
Some test code generates exceptions which are handled by the application, but displayed multiple time by the Delphi Debugger as I'm used to running the test application using the F9 shortcut like I do with standard application: this is not very handy in that case.
I know about the SHIFT+CTRL+F9 shortcut to run without debugging and that's great when I remember to use it but I often find myself hitting F9, then grunting, then closing the test application, then hitting SHIFT+CTRL+F9. What a lost of time.
So my question: is there a better way ? Can I define some settings or use some expert to make that particular application run without debugging by default ? Surely I'm not the only one having this issue.
Thanks in advance.

Nope (at least not up until D2009). Run without debugging is an IDE thing. A comiler flag would not help as it is the IDE that hooks the exe, not the other way around. The only place where you could have such an option would be in the project settings. But having it could make the IDE slightly confusing as the normal distinction between Run and Run without debugging would be overruled. You would then need a third option I guess, "Run", "Run with debugging" and "Run without debugging" where the simple "Run" would take its cue from the project options.

Disable "notify on language exceptions".

Add the run-without-debugging icon to the toolbar. Your problem is you forget the hotkey, and click the icon. So remove the other icon, or move them both around, like this:
The bigger your application grows, I find, the slower the debugger startup gets. I run without debugging about 99% of the time now, because the startup time of my app goes from 2 seconds, to 2 minutes, because it uses a lot of runtime packages, and every BPL load in the debugger comes at a huge hit to my productivity. So long, slow painful experience has re-educated me to ask myself "Do I really need to debug?". If I don't, I click the nice green icon (in XE) that replaces the exclamation mark icon in older versions. (A smart UI improvement I think.). In previous delphi versions, the green play button meant "run with debugging".

Well kind of. You can use non-breaking breakpoints[McKeeth][Jensen] to ignore the exceptions you are forcing in your tests. The only way to save breakpoints I know of is to enable Tools > Options > Autosave > Project desktop.

I understand your problem, but personally I don't think it's that useful having an option to change the default behaviour of F9.
You have some test cases which are expected to raise exceptions. (NOTE: I'm actually thinking tests that check for particular exceptions when bad inputs are given. Not exceptions being 'handled' by application.) And I agree there is no point in being alerted to these in most circumstances. However, an exception in other test cases would be a problem that I'd like to be alerted to as quickly as possible.
So my preferred running mode is to usually be notified of exceptions. And have explicit code in certain test cases that explicitly disables exception notification only within the context of the tests that will trigger production code exceptions.
I have a technique that works very well for this.
In a test that's expected to raise exceptions I write the following code:
begin
TIDEDebugTools.DisableBreakOnExceptions;
try
//Test code ...
finally
TIDEDebugTools.EnableBreakOnExceptions;
end;
end;
I guess you want the source code for those 2 methods? :)
unit IDEDebugTools;
interface
type
TIDEDebugTools = class(TObject)
public
class procedure DisableBreakOnExceptions;
class procedure EnableBreakOnExceptions;
end;
implementation
{ TIDEDebugTools }
class procedure TIDEDebugTools.DisableBreakOnExceptions;
begin
end;
class procedure TIDEDebugTools.EnableBreakOnExceptions;
begin
end;
end.
Where's the rest you ask?
There isn't any - that's it!
... but there are a few instructions you need to follow:
Add a breakpoint to each of the method.
Edit the breakpoint-properties.
Select advanced options.
Turn off the "Break" option
And turn on the "Ignore subsequent exceptions" and "Handle subsequent exceptions" options for the appropriate respective method.
This is close to jpfollenius' idea for compiler directive options. It also addresses David's concern about how you enable those exceptions again. All you need to do is disable the breakpoints to get all exceptions reported again.
Additional thoughts:
You mention:
Some test code generates exceptions which are handled by the application.
If your application is handling all these exceptions, then:
Are your tests localised enough? If you're testing such large chunks of functionality, the tests are more like system tests - and can be very difficult to maintain.
Are you making too much use of exceptions for main-line business processing?
Is your application doing a whole lot of inappropriate exception swallowing?
Basically it seems fishy to me that your wording suggested "a bunch of exceptions you're not too concerned about because they're 'handled'". Many people make the mistake of thinking they're handling a exceptions when really they're just swallowing them and hiding the root problems.

Related

How do I stop F7 from going into Embarcadero code?

I am trying to debug a dense piece of code with multiple function calls in single lines. I want to single step through all the code that I have written, but in doing that I keep finding myself in the supplied source code, including the assembler code.
This is happening at a client's site on his machine running XE5. It doesn't happen on my own code, running XE2 and XE4.
What is the magic setting that makes this work they way I want?
The best you can do is make sure that Debug DCUs is disabled in the project options. But even doing that sometimes is not sufficient to stop yourself landing in RTL code, in modern Delphi versions. I suspect that you have Debug DCUs disabled and are being caught out by this behaviour change.
The only thing you can do is get used to knowing when to use step out (F8) rather than step in (F7), and being able to escape from a function as quickly as possible. Usually that involves putting the cursor on the last line, running to cursor (F4), and stepping in again.
You might also be interested in the Selective Debugging tool by Uwe Raabe which allows you a fine grained selection for which DCUs are used with or without debug information.

What's the easiest way to access other .exe data in delphi?

I trying to implement some basic automated testing on a 10 million LOC project that don't follow good OO pratices (ex: isolating business logic into classes/units) with the DUnit that comes along with Delphi 2010. I can't do normal unit testing on this project since each part of business logic is spread across a dozens of interdependent units, these 'groups' of units are, however, centered around certain 'main business logic screens' (ex: all invoice logic related units are centered on the main invoice screen), and since those screens are classes I can do 'main business logic screen class testing' instead of unit testing, but those 'main screens' still need a lot of stuff that is created during the process startup.
So I need to both:
Be able to run the bad project's startup stuff
Be able to access its objects
The bad project already have some exported functions that return pointers that I can cast to access it's objects, but I'm unable to call them either way:
If I create the bad project as a child process of the test process, the startup code run fine, but I can't find a way to call the exported functions without complex IPC methods or substantial change on the bad project's structure.
If I load the bad project's .exe as an dll as with the LoadLibrary function, calling any function exported by bad project's result in access violation and/or segfault errors, even this simple procedure:
procedure Test; {safecall;} {stdcall;}
begin
showmessage('Yay!');
end;
How can I do both?
The approach you're talking about (using exported functions) is not going to fly. The simplest form of communication between two Win32 programs is to have them use SendMessage or PostMessage to talk to each other. Locating the window handle (usually by window class name) is step 1, sending a message is step 2.
Secondly, DUnit gets you nowhere near your goal, and TTestCase cannot be extended neatly to be a GUI Controller as that's not what it's for. It's for unit testing. Round peg, square hole. Write TTestCases for classes you can hive off and test, and use DUnit to provide test coverage for those parts of your system that you can provide test coverage for.
For UI testing, use a completely separate framework. There are two basic approaches done by Delphi programmers for automated integration tests of the sort you're proposing.
A custom hack job. Such is what you are describing. Inside Embarcadero, there exists a framework which is called Zombie, something Nick blogged about back in 2007. Its approach is based on several kinds of "primitive IPC", usually involving a Win32 SendMessage or PostMessage window message from outside the program to a window handle of a control inside the program. However, the internal code IS SIGNIFICANTLY MODIFIED to permit zombie testing. No you can't have Teh Codez, they're internal and proprietary to Embarcadero. But it does illustrate that the approach does work, and does not require rewriting the whole application or writing a huge number of mock-classes, like unit testing
would have done. If you want to go down the hack route, you will be writing your own User Interface Testing Framework, which should probably be completely separate from and use no DUnit code. You are welcome to try, but I'm telling you, it's a serious impedance mismatch. If I was starting my own custom framework in 2013, it would be DCOM based, because Delphi DCOM server code could be simply conditionally compiled into many programs, and DCOM would handle the function call "marshalling" details for you. I suspect I would get a year into the project, and I would give up, because in the end, I doubt I could make any system (DCOM or Win32 message based) pay off.
A complete external testing tool which you write test scripts in, like AutomatedQA/SmartBear TestComplete. Your tests would not be compiled into a delphi test program, but run inside TestComplete, and Pascal-like script syntax is just one of the available options for writing your test scripts.
We have had the same problem here. It looks like you really need a delphi library project (*.dll) for the export functions to work, (I suspect no initalization of the framework takes place when calling the function directly on an executable, no warranties).
NOTE: We are still using Delphi 5, so no dunit intergration here.
The solution we've used is to adding the dunit sources to our project (the .exe project) with a conditional DEFINE, and use this conditional define in the startup unit.
Sample startup code from our application:
if ComServer.StartMode <> smAutomation then
begin
OurApplication.Login ;
end;
{$IFDEF _AS_TESTRUNNER_}
GUITestRunner.RunRegisteredTests;
{$ELSE}
if OurApplication.HasStartCommands then
begin
Application.ShowMainForm := False ;
end
else begin
if ComServer.StartMode = smAutomation then
Application.ShowMainForm := False
end;
Application.Run;
{$ENDIF}
OurApplication.Finalize;
When I use the _AS_TESTRUNNER_ conditional define, I must login first so our app (and db connections) get initialised. Followed bij the GUITestrunner of DUnit.
Testcases can be registered in the initialization part exactly as in the examples.
Works like a charm.

Troubleshooting runtime exceptions when in Design mode

When I open a form, I am suddenly getting an error message:
"Error reading form: 'X' "
Cache is not initialised. Must call TCache.Initialise first. Ignore the Error and continue? NOTE: Ignoring the error may cause components to be deleted or property values lost"
The "Cache is not initialised" bit is an exception raised by one of our classes.
My first question is, why is the Delphi IDE running my code without me asking it to "Run Program"? What code does it run? Is there any way to turn this off?
Secondly, is there any way I can trap this error in the debugger, so I can work out where in the call stack this exception occurs? I have tried putting a breakpoint where the exception is thrown, but Delphi ignores it.
And also, once I have worked out where this exception is coming from, is there a way to tell when I am in "design mode" and not run that code, or run different code? Or even better, not run that code at all if in design mode?
[Update: See Francois' answer to how to stop code running in design mode.]
You have some component code that you wrote that is executed when the IDE is trying to load the dfm.
You can test in your code if your component is in "design mode" with
if (csDesigning in ComponentState) then
When the IDE loads a DFM, it creates live objects within the Form Designer, which means component run-time code is actually run within the IDE. As such, component code needs to check the TComponent.ComponentState property for the csDesigning flag if it wants to skip running portions of its code at design-time.

"exe has encountered a problem and needs to close" message when exe is run. Runs OK on Dev machine

This is occurring on 2 machines that are both running Windows XP Pro SP3, yet it runs OK on my development machine within in or outside the Delphi IDE.
Running Windows XP Pro. Exe compiled under Delphi 2010.
When I run the exe I get the Windows Reporting error "Neopos.exe has encountered a problem and needs to close. We are sorry for the inconvenience"
I know it is happening somewhere in the form create of the main form.
Application.Initialize; //Runs this
Application.CreateForm(TfmMain, fmMain); //FAILS HERE
It does not get to: procedure TfmMain.FormCreate(Sender: TObject); in the Main Form and I don't know how to track down this error and debug it.
What happens between: Application.CreateForm(TfmUDF, fmUDF); AND procedure TfmMain.FormCreate(Sender: TObject) in my main form.
How can I trace this to find out what the hell is causing the Windows Error.
Of course the Windows Error report contains a long listing of information. Where can I look in that to find the cause or at least a clue on the cause of the error.
This error has now stopped all development work (and ruined my weekend) so I urgently need to fix this.
The most straightforward route to take would be to include a product like MadExcept or JCL Debugger into your application, to get a full call stack (including line number) of the point of failure. We've rolled our own years ago, and it has been a tremendous help in situations like this.
One alternative, but lots more cumbersome, would be to generate a MAP file from your project, use MAP2DBG to generate a .MAP file, and use the Windows Debbuging Tools to get about the same information. This approach is a lot more hardcore, and only advisable if you really want to learn a lot about the internals of windows debugging (and enjoy working with arcane tools).
Another alternative would be to attach to the failing application from your development environment using Remote Debugging. Only applicable if you have a fair amount of control over the failing machines.
#user576639, here are some debugging ideas:
Look into the System's Event Viewer
If you got the exe has encountered a problem and needs to close chances are you'll find something about it in the System's Event Viewer. That should be your first step.
Any special DLL's required?
Do you need MIDAS.DLL?
Are you using an database engine? Does it require some sort of client library?
I'm talking from experience here: My development machine obviously has all the libraries I might need. Most of my clients also have most of those libraries because they have my software installed. At times I put out small helping applications that don't go throw extensive testing and they fail to work on SOME machines but work fine on other machines. Why? I used TClientDataset and forgot to include MIDAS.DLL with the application; Or the application is trying to access a Firebird SQL server but the user doesn't have Firebird client library installed.
Printer driver issues
Boy I hate Delphi's printer handling. Also hate buggy printer drivers, haven't made up my mind about what's worst. If you have something on your main form that might be requesting information about the default Windows printer (example: an REPORT) give this a try: Install an sane/simple printer and set it as the default printer. If the user has Office 2007+ installed, set the "Microsoft XPS Document Writer" the default printer.
I have seen bad printer driver + delphi issues manifest themselves with the "exe needs to close" symptom.
Prepare an special build of your application
If you got this far without fixing your issue it's time to create an special build of your application that's capable of providing more information. First of all I'd try adding this to your DPR file; Don't know if this is still useful for Delphi 2010 but it did help me see some early exceptions with a Delphi 7 application:
function HandleUnhandledException:integer;stdcall;
begin
Result := 1; // 1 = EXCEPTION_EXECUTE_HANDLER
end;
// and then immediately after "begin" in your DPR file:
begin
SetUnhandledExceptionFilter(#HandleUnhandledException);
// ... the usual stuff goes here
end;
Add some ShowMessage-s to your Main Form's code, in your OnCreate handler (if you have one), in your Create constructor (again, if you have one). If you're adding an ShowMessage to your Create destructor, make sure it's after the "inherited" call. This will help pin-point how far the loading of the form goes before it fails.
If all else fails...
Create a new, blank form; Make it the new Main Form (so it's initialized before your former Main Form). Test it on the client's machine - does it show up? It most likely will, if it doesn't you've got some serious problems.
Start copying the components from the former main form to the new main form; Only the components need to be copied, not the code: Your error is probably caused by some component failing to initialize properly. Make sure no component has "Active=True"! Copy the components in small batches, test often. If you spot the component that causes your form not to load on the client's computer, tell us about it and we'll try to help.
If you manage to get all your components on the new form, write an OnCreate handler that sets Active := True for all the components that need that. Did that fix the issue?
If you got this far then all the components you used on your main form can load properly. The problem's related to YOUR CODE. Start copying all the code from your old main form to your new main form, in small bits, and test. You're bound to find the peace of code that causes your application to stop loading.
Use dependency walker to see if you're missing a required DLL.
You can use information from system reporting (your error and suggestion to send it ) with Error Report Grabber ( http://www.maxerist.net/main/soft-for-win/err-rep-grabber ). I developed this tool when I desperately needed to track a error that appeared very rarely so almost non-reproducible. It helped me to track the information from stack to find actual place in the code.
The tool works only on XP (MS removed this dialog in Win7 and probably Vista), but I see that your cases are XP so this can help.
UPDATE: if you're not familiar with assembler and everything, this can work like this.
You should compile you program and don't change anything. Save the report on a bad machine, copy the file to your developer machine and open to view the contents. Look at the stack of your main thread in the report and find numbers more than $00400000, they're usually the addresses inside the procedures that called some other procedure and wait for return. In your developer machine, start the program and stop at any line, open CPU Window and on the main list with assembler instruction right-click and choose go to address, enter this address. You will see other assembler lines, but wrapped with pascal constructions you can probably recognize as yours
Thanks a lot for the help.
In the end I reverted to a recent backup and traced it down to a particular form.
I did not actually find the error, which is a bit worrying, but in any case I am back up and running (phew!!)
I made the error to occur on my development machine also, when, and only when, I use my install program (Inno Setup) to compile a setup.exe and which installs the exe as well as installing postgreSQL. Seems really strange, as though there is a problem with the setup compiler. In any case I have not seen the error again. I guess it will remains a mystery, like women.
In Delphi withing debugging options select debug dcu's, this will allow you to debug into the Delphi source code for TForm and its descendants and you may be able to track down a more likely culprit.
Set a breakpoint on
Application.CreateForm(TfmMain, fmMain); //FAILS HERE
and then step into the code to see where the issue is.

Debug Breakpoint doesn´t work only in DataModule unit - Delphi

Debug breakpoint's works fine in all other 38 units of my system. But, in my DataModule, that have +- 10.000 lines, delphi disables then after I launch by F9/F8/F7. In any part of source that unit, even on obrigatory steps like OnCreate, SQLConnection.Active:=true, etc.
Detail: works fine until +- 20 days ago.
I'm using D7 and have all sources of components also.
Thanks
Felipe
Try doing a full build (Shift+F9). If that does not work, then what happens if you simulate a breakpoint in code with the below?
asm int 3 end;
Check for multiple copies of your source file for the datamodule. Sometimes the code you think you're running isn't the code the compiler and debugger are seeing.
Next, make sure you haven't accidentally turned off debugging in your code with {$D-} or {$DEBUGINFO OFF}. This can turn off debugging info for a single unit.
Also, make sure you've turned on Integrated Debugging in Tools|Options|Debugger Options. I know you said you could debug other places, but it can't hurt to make sure that integrated debugging didn't get accidentally turned off somehow.
I discovered this problem. It's a weird behavior in Delphi7 that limits the number of fields in interface class section, between: type TDM=class(TDataModule) and private section. I deleted some fields (DataSet Fields (+-40 fields)) and degub runs again. I add these fields again, and debug not runs. I'm sure that's a limit, because doesn't mather which component fields I deleted. I tried with several fields, from different tables too, adding, testing and deleting. It's a shame, but is true...
Thanks for your help.
I have a vague memory that debugging very large files was buggy in old D7.
Try to break you big unit in several classes and se if you can debug outside your unit.
Another option could be to turn off debuginfo in your big file except the section you want to debug. It is worth a try.
There's a blog post from Steve Trefethen, a while back, explaining some possible reasons, although if you say you can debug other units, I doubt they'll apply.
We had the same issue with a large DataModule in Delphi 5 that wouldn't let us debug it, and kind of put it down to it being too large a file to debug and Delphi not liking it. When we moved to Delphi 2007, we could debug it again. Not sure why, nothing had changed (code wise).
Felipe, is the unit that will not allow you to debug in a dll that is being moved into or out of memory? I've found when debugging dll's that if I do something in the executable that executes code in another dll or unloads the dll that the problem unit is in - Delphi will disable all breakpoints. Usually a restart of Delphi and being sure to keep a single instance of the problem unit's dll in memory is the only solution for this problem.
I doubt size is the issue, as I have a 17k line unit that I debug regularly.

Resources