Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

Testing the Flow Trace System 379

debugFlowTracerManager *debugFlowTracerManager::m_Instance = NULL;

void debugFlowTracer::AddFlow()

{

}

debugFlowTracerManager::Instance()->addFlow( *this );

2

void debugFlowTracer::RemoveFlow()

{

debugFlowTracerManager::Instance()->removeFlow( *this );

}

The purpose of the debugFlowTracerManager class is to keep track of the various flows within the system. Because a flow can really start and end anywhere in the source code of the application, we need a single place to store all of them. This allows us to print them out at the point that gives us the best view of how the processing went. The flow manager contains a single method, Instance (shown at 1), to return an instance of the class. Otherwise, you will notice that the constructors are all private, so that users cannot create an instance of the class. This ensures that there is only a single object of this class in any given application.

Testing the Flow Trace System

After you create a class, you should create a test driver that not only ensures your code is correct, but also shows people how to use your code. The following steps tell you how:

1. Append the code from Listing 62-3 into your source file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 62-3: THE FLOW TRACE TEST PROGRAM

void func_3()

{

debugFlowTracer flow(“func_3”);

}

void func_2()

{

debugFlowTracer flow(“func_2”); func_3();

}

void func_1()

{

debugFlowTracer flow(“func_1”); func_2();

}

int main(int argc, char* argv[])

{

debugFlowTracer mainFlow(“main”); func_1();

func_2(); func_3(); return 0;

}

The test driver is constructed to illustrate the way in which the flow manager works. Note that we simply define a debugFlowTracer object in each of the functions in the code, and then use the functions as we would normally. The debugFlowTracer

380 Technique 62: Building Tracing into Your Applications

object attaches itself to the the instance of the manager (shown back in Listing 62-2 at 2) and adds itself as a flow when it is created. The manager keeps track of all of the flows, printing them out for diagnostic purposes as they are created.

2. Save the source code in the source-code editor and close the source-editor application.

3. Compile the application using your favorite compiler on your favorite operating system.

4. Run the application on your favorite operating system.

If you have done everything properly, you should see the following output in the console window of your operating system:

$ ./a

 

 

Flow [main]:

 

 

Flow [func_1]:

 

 

Flow [func_2]:

 

 

Flow [func_3]:

 

 

Flow [func_2]:

 

3

func_3

 

Flow [func_3]:

 

As you can see, the program indicates what flows are running in the application, and how it got from one point to another. The names of the flows are printed within the square brackets as they are created. When a flow goes out of scope (when the function ends) the sub-flows (calls within that function) are printed out. You can see that in our case, only one of the functions called calls another function. This is shown in the listing at the line marked with 3, indicating where func2 called func3. This gives us a good example of tracing and shows us how func3 was called throughout the program.

Adding in Tracing

After the Fact

after it has been designed, coded, and released. After all, you say, if it was up to you, the code would have been in there in the first place. Why should you pay for the mistakes of the developers before you? The answer is, because that’s the way the world works. Someone comes along, writes a bunch of ugly, unmaintainable code, and then moves on to do it again someplace else. You get to move in and fix the disaster that was left behind.

Fortunately, this isn’t really one of those times. It is possible to add in flow tracing after the fact, even automating the process. Let’s create a simple little application that will do just that.

1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.

In this example, the file is named ch62a.cpp, although you can use whatever you choose.

2. Type the code from Listing 62-4 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

A word of warning

Before you use the insertion program, here are a few things you need to know about it:

Consider it a jumping-off point for your own creations. It is not intended to be used in a production environment. Using it that way is likely to confuse the program; its interaction with complex programs may require you to update the output. (That is why it does not overwrite your original source.)

It does not handle all cases. Inline code in a class will not be detected and updated.

The code will sometimes detect a function or method when one does not exist. For example, the code will sometimes make this mistake in a macro.

One of the more annoying things in the software world is being told to add something to your application

The insertion program will insert tracing objects in most, but not all, of your application functions with the exceptions listed in the notes above. Use it in good health.

Adding in Tracing After the Fact 381

LISTING 62-4: A UTILITY PROGRAM TO INSERT TRACING INTO AN EXISTING FILE

#include <string> #include <ctype.h>

void eat_line( FILE *fp, std::string& real_line )

{

// Just read to the end of the line. while ( !feof(fp) )

{

char c = fgetc(fp); real_line += c;

if ( c == ‘\n’ ) break;

}

}

void eat_comment_block( FILE *fp, std::string& real_line )

{

char sLastChar = 0;

// Find the matching comment-close character. while ( !feof(fp) )

{

char c = fgetc(fp); real_line += c;

if ( c == ‘/’ && sLastChar == ‘*’ ) break;

sLastChar = c;

}

}

std::string get_line( FILE *fp, std::string& real_line )

{

std::string sLine = “”; char sLastChar = 0; while ( !feof(fp) )

{

// Get an input character. char c = fgetc(fp);

real_line += c;

// Check for pre-processor lines.

if ( c == ‘#’ && (sLastChar == 0 || sLastChar == ‘\n’) )

{

eat_line( fp, real_line ); continue;

}

(continued)

382 Technique 62: Building Tracing into Your Applications

LISTING 62-4 (continued)

// Check for comments. if ( c == ‘/’ )

{

sLastChar = c; c = fgetc(fp); real_line += c;

if ( c == ‘/’ )

{

eat_line( fp, real_line ); sLastChar = 0;

continue;

}

else

if ( c == ‘*’ )

{

eat_comment_block( fp, real_line ); sLastChar = 0;

continue;

}

else

{

sLine += sLastChar;

}

}

// Need to skip over stuff in quotes.

if ( c != ‘\r’ && c != ‘\n’ )

{

//Here it gets weird. If the last character was

//a parenthesis, we don’t want to allow white space. if ( sLastChar != ‘)’ || !isspace(c) )

sLine += c; else

continue;

}

// A line terminates with a {, a }, or a ; character. if ( c == ‘;’ || c == ‘{‘ || c == ‘}’ )

break;

sLastChar = c;

}

Adding in Tracing After the Fact 383

return sLine;

}

std::string parse_function_name( std::string& sLine )

{

std::string sName = “”;

//First, find the opening parenthesis. int sPos = (int)sLine.find(‘(‘);

//Skip over everything that is a space before that. for ( int i=sPos-1; i>=0; --i )

if ( !isspace(sLine[i]) )

{

sPos = i; break;

}

//Now everything backward from that is the name, until

//we hit either the beginning of the line or white space. int sStartPos = 0;

for ( int i=sPos; i>=0; --i )

{

if ( isspace(sLine[i]) ) break;

sStartPos = i;

}

sName = sLine.substr( sStartPos, sPos-sStartPos+1 );

return sName;

}

void ProcessFile( FILE *fp, FILE *ofp )

{

std::string real_line;

while ( !feof(fp) )

{

real_line = “”;

std::string sLine = get_line( fp, real_line );

// Check for functions/methods.

(continued)

384 Technique 62: Building Tracing into Your Applications

LISTING 62-4 (continued)

//Output the “real” line, and then (if we need to) the

//information we need to embed.

fprintf(ofp, “%s”, real_line.c_str() ); if ( sLine[sLine.length()-1] == ‘{‘ && sLine[sLine.length()-2] == ‘)’ )

{

std::string sName = parse_function_name( sLine );

fprintf(ofp, “\n\tdebugFlowTracer flow(\”%s\”);\n”, sName.c_str());

}

}

}

int main(int argc, char* argv[])

{

if ( argc < 2 )

{

printf(“Usage: cppparser filename [filename...]\n”); exit(1);

}

for ( int i=1; i<argc; ++i )

{

FILE *fp = fopen(argv[i], “r”); if ( fp == NULL )

{

printf(“Error: Unable to process file %s\n”, argv[i] ); continue;

}

std::string sOut = std::string(argv[i]) + “.tmp”; FILE *ofp = fopen(sOut.c_str(), “w”);

if ( ofp == NULL )

{

printf(“Error: Unable to create output file %s\n”, sOut.c_str() ); continue;

}

//Process this file ProcessFile( fp, ofp );

//Finish it up fclose(fp); fclose(ofp);

}

return 0;

4

5

Adding in Tracing After the Fact 385

There is nothing magical about Listing 62-4. The code takes an input file and writes it out to an output temporary file, appending certain information in the file as it parses the text within the input file. In our case, the code is looking for

opening braces (shown at

4) to indicate the

beginning of a function. When one is encountered,

it parses the function name (shown at 5) and

writes a debugFlowTracer object definition

into

the output file. This creates an automated system for inserting flow tracing into an existing source file.

3. Save the source code in the source-code editor.

4. Create a new file in the source-file editor to use as a test input to the insertion program.

This file simply acts as test input for the program; it doesn’t really have to do anything on its own. For now, just create a file that contains some functions and some class methods.

In this example, the file is named temp.cpp, although you can use whatever you choose.

5. Type the code from Listing 62-5 into your new file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 62-5: A TEMPORARY PROGRAM TO ILLUSTRATE TRACE

INSERTION INTO AN EXISTING FILE

#include <stdio.h> #include <string> #include <iostream>

int func1()

{

printf(“This is a test\n”);

}

void func2()

{

printf(“This is another test\n”);

}

class Foo

{

public:

Foo();

virtual ~Foo();

};

Foo::Foo(void)

{

}

Foo::~Foo(void)

{

}

int main()

{

Foo x;

}

Don’t spend any time studying the code in Listing 62-5; its sole purpose is to provide an input file to test out the parser. If it works properly, we will see an output file which contains debugFlowTracer objects in func1, func2, and the constructor and destructors for the Foo object and the main function.

6. Compile the insertion program with your favorite compiler on your favorite operating system.

7. Run the program on your favorite operating system.

If you have done everything right, you should see a file called temp.cpp.tmp created that contains the following text in it:

#include <stdio.h> #include <string> #include <iostream>

int func1()

 

{

 

debugFlowTracer flow(“func1”);

6

386 Technique 62: Building Tracing into Your Applications

printf(“This is a test\n”);

}

}

 

 

Foo::~Foo(void)

void func2()

{

{

debugFlowTracer flow(“Foo::~Foo”);

debugFlowTracer flow(“func2”);

 

 

}

printf(“This is another test\n”);

 

}

int main()

 

{

class Foo

{

public:

Foo();

virtual ~Foo();

};

Foo::Foo(void)

{

debugFlowTracer flow(“Foo::Foo”); 7

debugFlowTracer flow(“main”);

Foo x;

}

Note that the program properly inserted all of the flow-tracing objects into the existing file. The lines marked 6 and 7, for example, show you that the output is exactly what we wanted and expected.