Caves Travel Diving Graphics Mizar Texts Cuisine Lemkov Contact Map RSS Polski
Trybiks' Dive Texts Programming Controlling Conditional Defines and Compilation Switches YAC Software












Rhino Mocks



UI Testing




Controlling Conditional Defines and Compilation Switches
When building applications I always kept forgetting to set conditional defines and compilation options properly for the given target (development, debug, release, etc.). And even though many development environments let you define names / configurations for any given set of switches and defines, it's quite easy to accidentally changes those settings. Actually, the usual scenario, I guess, is to change some of these settings temporarily (for instance, to lock down a bug), and then forgetting to change them back before a release build... :-)

Well, the best way (IMO) is to let the compiler check these settings automatically for you. The way I do it is to have a unit that is then used by other modules (usually, in the most often used units or "core" units), and in that unit add instructions that will verify conditional defines and compilation switches by... breaking the build if something is defined incorrectly.

Here's an example unit for Delphi:
  unit YACDefines;
  {$IFOPT A- } Error - alignment shall be off {$ENDIF }
  {$IFOPT B+ } Error - Boolean short-circuit evaluation shall be on {$ENDIF }
  {$IFOPT I- } Error - input / output checking shall be on {$ENDIF }
  {$IFOPT U+ } Error - Pentium-safe FDIV operations shall be off {$ENDIF }
  {$IFDEF YACAllowMemoryLeaks }
    {$IFNDEF YACTest }
      Error - YACAllowMemoryLeaks shall be used only in DUnit testing
    {$ENDIF }
  {$ENDIF }
  {$IFDEF YACRelease }
    {$IFNDEF YACTest }
      {$IFOPT C+ } Error - assertions in release builds shall be off {$ENDIF }
      {$IFOPT D+ } Error - debug information in release builds shall be off {$ENDIF }
      {$IFOPT L+ } Error - local symbol information in release builds shall be off {$ENDIF }
      {$IFOPT O- } Error - optimization in release builds shall be on {$ENDIF }
      {$IFOPT Q+ } Error - overflow checking in release builds shall be off {$ENDIF }
      {$IFOPT R+ } Error - range checking in release builds shall be off {$ENDIF }
      {$IFOPT Y+ } Error - symbol declaration and cross-reference information
                           in release builds shall be off {$ENDIF }
      {$IFOPT W+ } Error - windows stack frames in release builds shall be off {$ENDIF }
    {$ENDIF }
  {$ELSE }
    {$IFOPT C- } Error - assertions in development builds shall be on {$ENDIF }
    {$IFOPT O+ } Error - optimization in development builds shall be off {$ENDIF }
    {$IFOPT Q- } Error - overflow checking in development builds shall be on {$ENDIF }
    {$IFOPT R- } Error - range checking in development builds shall be on {$ENDIF }
  {$ENDIF }
The basic idea here is to place text that will not compile inside directives and combinations of directives that aren't legal in your development or release process.

For instance, I usually have three kinds of builds:
  • debug (development) builds for day to day work,
  • test builds for automated tests,
  • release builds for creating production versions of systems under development.
Now, regardless of the type of build, several switches (A, B, I, U above) should always be set so and so (for instance, I/O checking should always be on).

For debug (development) builds, I want to turn on as many checks as possible. That's why in these builds, assertions, overflow and range checking should be on, but optimizations should be off. The last one of these has to do with Delphi not being able to show values of certain variables during debugging when optimizations are on; namely those variables that are stripped by the compiler or not available shortly after they are stopped being used.

For test builds, OTOH, I don't want to control the switches at this level - various test projects often need different settings.

There's one more settings that I use often - a conditional define that allows for memory leaks during DUnit testing. In some tests it's quite hard to eliminate all memory leaks, for instance when testing the user interface, even though the application being tested has no memory leaks at all. However, there's no sense in defining the directive if it's not a test build!

Finally, most checks and debug data is stripped from release builds. Now, you might argue that settings in production (release) systems should be the same, or as close as possible, to those in systems being tested.

Well, I think I tend to disagree - unless you don't trust your compiler! :-) In my 20 years of software development, I can count on the fingers of one hand the number of situations where the behavior of the system changed unexpectedly after changing compilation switches. Well, actually, I remember only one - but maybe there are other reasons for not remembering more... ;-)

Mind you, I wrote "unexpectedly" above - the system may operate differently (and usually does). And anyway, the idea of this post was not to promote a specific set of tests, but to show a way of placing more control over you compilation / build / release process.

Obviously, there might be many more other things to check, for instance checking settings for alpha or beta builds, checking settings for systems released internally or to clients, checking settings of third party libraries, etc.

Next, I can't say that this "saved" me from any big mistakes or defects, but it sure comes in handy and speeds up development. For instance, I hate debugging an application, finally arriving at the point where the bug should surface, and finding only then that optimizations are on and I can't check the value of a variable... Or finding out that an application at a client's site works much slower then test builds - because of a range checking switch left on...

Finally, instead of using text that doesn't compile inside invalid switches and directives, you may prefer Delphi's $MESSAGE directive, probably in its ERROR version. FATAL stops compilation and will not report any problems after the FATAL one. And WARN/HINT, on the other hand, emit only a warning/hint respectively, so an automated build/release process may not stop on such an issue.

An excerpt from the example above using $MESSAGE:
  {$IFOPT A- } {$MESSAGE WARN  'Alignment shall be off.' } {$ENDIF }
  {$IFOPT B+ } {$MESSAGE ERROR 'Boolean short-circuit evaluation shall be on.' } {$ENDIF }
  {$IFOPT I- } {$MESSAGE ERROR 'Input / output checking shall be on.' } {$ENDIF }
  {$IFOPT U+ } {$MESSAGE WARN  'Pentium-safe FDIV operations shall be off.' } {$ENDIF }
Hmm... actually, this looks much nicer now. :-)


No comments yet...


Add a comment (fields with an asterisk are required)
Name / nick *
Mail (will remain hidden) *
Your website
Comment (no tags) *
Enter the text displayed below *





Related pages

TFS - The underlying connection was closed: an unexpected error occurred on a receive.

WCF - The underlying connection was closed: an unexpected error occurred on a receive.

Delphi interfaces... again

Saving / restoring window placements in .NET

Checking "Dangling" Event Handlers in Delphi Forms

Meaningful identifiers

Public fields vs. properties

Drag-n-drop files onto the application window

Intraweb and MaxConnections

A Case for FreeAndNIL

Intraweb as an Apache DSO module

"Device not supported" in Intraweb

Automated GUI Testing

Random()'s Determinism

Rounding and precision on the 8087 FPU

SessionTimeout in Intraweb

Using TChart with Intraweb

Unknown driver: MySQL

TIdMessage's CharSet

Software Guarantees

Automated Testing of Window Forms

TChart - Missing Labels in Axes

Memory Leaks and Connection Explosions in DBExpress

Detecting Memory Leaks with DUnit

last_insert_id() and DBExpress

Registering Extensions

DBExpress and Thread Safety

Forms as Frames

Checking Dangling Pointers vs. the New Memory Manager

Accessing Protected Members

Objects, interfaces, and memory management in Delphi