//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1996. // // File: ftest.cxx // // Contents: Code for launching the filter test // // Classes: none // // Functions: main() -------- Processes command line switches, handles // directory filtering // usage() ------- Displays a usage message // LaunchTest() -- Creates a CFiltTest object, initializes it, // and executes the test // // Coupling: // // Notes: About allocations: In this project, the keyword 'new' does // no appear explicitely. Instead, I use a 'NEW' macro. If the // code is compiled with the DEBUG flag, NEW will expand to // new( __FILE__, __LINE__ ), which will invoke an overloaded // new operator to keeps track of my allocations. Otherwise, // NEW simply expands to 'new'. // // More about allocations: This program uses a newhandler to // handle failed allocations. The newhandler loops until enough // free memory is available, so every allocation is guaranteed // to succeed. // // History: 9-16-1996 ericne Created // //---------------------------------------------------------------------------- #include "pch.cxx" #include #include #include "clog.hxx" #include "workq.cxx" #include "mydebug.hxx" #include "filttest.hxx" #include "cmdparse.cxx" #include "oleinit.hxx" // Must be compiled with the UNICODE flag #if ! defined( UNICODE ) && ! defined( _UNICODE ) #error( "UNICODE must be defined" ) #endif // Global work queue of size 10 CWorkQueue< WCHAR*, 10 > g_WorkQueue; // Global parameters int g_iDepth = 1; // Recursion depth, -1 is full int g_cLoops = 1; // Loop counter, -1 is loop forever BOOL g_fLogToFile = FALSE; // TRUE if log should be sent to BOOL g_fDumpToFile = FALSE; BOOL g_fIsLoggingEnabled = TRUE; BOOL g_fIsDumpingEnabled = TRUE; BOOL g_fLegitOnly = FALSE; WCHAR *g_szIniFileName = L"ifilttst.ini"; CLog *g_pLog = NULL; Verbosity g_verbosity = HIGH; //+--------------------------------------------------------------------------- // // Function: out_of_store // // Synopsis: A new handeler function. When new fails, this function gets // invoked. It sleeps for a while and then returns 1, indicating // that the allocation should be retried. In addition, it // displays a message so the user knows what's happening. // // Arguments: [size] -- The size, in bytes, of the memory block to be // allocated // // Returns: 1 // // History: 10-03-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- int __cdecl out_of_store( size_t ) { printf( "An allocation failed. Will re-try in %d milliseconds\r\n", dwSleepTime ); Sleep( dwSleepTime ); return( 1 ); } //out_of_store //+--------------------------------------------------------------------------- // // Function: Usage // // Synopsis: Displays a usage message // // Arguments: [pcExecName] -- name of the executable ( call with argv[0] ) // // Returns: none // // History: 10-01-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- void Usage( const WCHAR * szExecName ) { printf( "\r\nUSAGE:\r\n" ); printf( "%ls /i [...] [/ini ] [/l []] [/d]" " [/-l] [/-d] [/legit] [/stress] [/v ] [/t ]" " [/r []] [/c ]\r\n", szExecName ); printf( "\r\n" ); printf( "\t is the file/directory/pattern to which to bind.\r\n" "\t\tWildcards are OK. More than one input file is OK.\r\n" ); printf( "\t is the initialization file to use. If none is\r\n" "\t\tspecified, it defaults to ifilttst.ini.\r\n"); printf( "\t[/l] enables logging to a file. By default, the log filename\r\n" "\t\tis the input file name with a .log extension. If you\r\n" "\t\tspecify a log file name, all the log messages will be sent\r\n" "\t\tto a single file.\r\n"); printf( "\t[/d] enables dumping to a file. The dump filename is the\r\n" "\t\tinput file name with a .dmp extension.\r\n" ); printf( "\t[/-l] disables logging. This flag overrides /l.\r\n" ); printf( "\t[/-d] disables dumping. This flag overrides /d.\r\n" ); printf( "\t[/legit] forces the test to run only the Validation Test.\r\n" "\t\tThe Consistency and Invalid Input Tests are skipped.\r\n" ); printf( "\t[/stress] forces the test to run in stress mode. This is the\r\n" "\t\t same as specifying /-l /-d /legit /v 0 /c 0\r\n" ); printf( "\t is an integer representing the verbosity level\r\n" "\t\tAcceptable values are from %d through %d, with %d being the\r\n" "\t\tmost verbose. (default is %d)\r\n", MUTE, HIGH, HIGH, HIGH ); printf( "\t is an integer representing the number of threads\r\n" "\t\tto launch. Only useful if filtering multiple files.\r\n" "\t\t(default is 1)\r\n" ); printf( "\t is an integer representing the depth to recurse.\r\n" "\t\tNo value or a value of 0 indicates full recursion. (default" " is 1)\r\n" ); printf( "\t is an integer representing the number of times to\r\n" "\t\tloop. A value of 0 means loop infinetly. (default is 1)\r\n"); } //Usage //+--------------------------------------------------------------------------- // // Function: LaunchTest // // Synopsis: If necessary, determines what the log and dump file names should // be, creates a CFiltTest object, initializes it, and executes the // test. // // Arguments: [szInputFileName] -- Full path to input file // // Returns: void // // History: 9-26-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- void LaunchTest( WCHAR * szInputFileName ) { // This function determines the log and dump filenames from the input // filename WCHAR *szLogFileName = NULL; WCHAR *szDumpFileName = NULL; CFiltTest *pThisFiltTest = NULL; // Try-finally block simplifies clean-up try { // If we are logging to a file, create the filename if( g_fLogToFile ) { szLogFileName = NEW WCHAR[ MAX_PATH ]; wcscpy( szLogFileName, szInputFileName ); wcscat( szLogFileName, L".log" ); } // If we are dumping to a file, create the filename if( g_fDumpToFile ) { szDumpFileName = NEW WCHAR[ MAX_PATH ]; wcscpy( szDumpFileName, szInputFileName ); wcscat( szDumpFileName, L".dmp" ); } // Create the Filter test object pThisFiltTest = NEW CFiltTest( g_pLog ); if( pThisFiltTest->Init( szInputFileName, szLogFileName, szDumpFileName, g_szIniFileName, g_verbosity, g_fLegitOnly ) ) { pThisFiltTest->ExecuteTests( ); } } // _try catch(...) { } // _finally { // Clean up the heap if( pThisFiltTest ) delete pThisFiltTest; if( szLogFileName ) delete [] szLogFileName; if( szDumpFileName ) delete [] szDumpFileName; } } //LaunchTest //+--------------------------------------------------------------------------- // // Function: FindAllFiles // // Synopsis: Find all the files that meet the restriction and put them in // the queue. // // Arguments: [szPath] -- // [iDepth] -- // // Returns: // // History: 10-14-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- void FindAllFiles( WCHAR *szPath, int iDepth ) { HANDLE hSearch = INVALID_HANDLE_VALUE; WIN32_FIND_DATA FindData; WCHAR szNewPath[ MAX_PATH + 2 ]; WCHAR szNewSearch[ MAX_PATH + 2 ]; WCHAR *szWorkItem = NULL; // Check the depth restriction: if( 0 == iDepth ) return; hSearch = FindFirstFile( szPath, &FindData ); if ( 0 == *szPath ) return; if( INVALID_HANDLE_VALUE != hSearch ) { // Find all the files that match the pattern and put them in the queue do { WCHAR *szExtension = wcsrchr( FindData.cFileName, '.' ); // If this is a directory, continue if( FILE_ATTRIBUTE_DIRECTORY & FindData.dwFileAttributes ) continue; // If the extension equals ".log" or ".dmp", continue if( NULL != szExtension && ( 0 == wcscmp( szExtension, L".log" ) || 0 == wcscmp( szExtension, L".dmp" ) ) ) continue; // Copy the path into NewPath wcscpy( szNewPath, szPath ); // Remove the restriction part of the path *( wcsrchr( szNewPath, L'\\' ) + 1 ) = L'\0'; // Append the file name of the matching file wcscat( szNewPath, FindData.cFileName ); // Dynamically create a new work item szWorkItem = NEW WCHAR[ wcslen( szNewPath ) + 1 ]; // Copy the full path into the new work item wcscpy( szWorkItem, szNewPath ); // Put the work item in the queue g_WorkQueue.AddItem( szWorkItem ); // We don't own this item anymore, so set the pointer to NULL szWorkItem = NULL; } while( FindNextFile( hSearch, &FindData ) ); // Close the search handle FindClose( hSearch ); hSearch = INVALID_HANDLE_VALUE; } // Now, recurse into the subdirectories and search for the same pattern. // Copy the origional path into a new path for the new search wcscpy( szNewSearch, szPath ); // Remove the restriction *( wcsrchr( szNewSearch, L'\\' ) + 1 ) = L'\0'; // Append a star wcscat( szNewSearch, L"*" ); // Since we're looking for a wildcard, this should succeed hSearch = FindFirstFile( szNewSearch, &FindData ); if( INVALID_HANDLE_VALUE == hSearch ) { return; } do { // Recurse into this subdirectory looking for the same pattern if( ( FILE_ATTRIBUTE_DIRECTORY & FindData.dwFileAttributes ) && ( 0 != wcscmp( FindData.cFileName, L"." ) ) && ( 0 != wcscmp( FindData.cFileName, L".." ) ) ) { // Copy the search string into NewPath wcscpy( szNewPath, szNewSearch ); // Remove the "*" at the end *( wcsrchr( szNewPath, L'\\' ) + 1 ) = L'\0'; // Append the directory name found wcscat( szNewPath, FindData.cFileName ); // Append a slash wcscat( szNewPath, L"\\" ); // Finally, append the origional search restriction wcscat( szNewPath, wcsrchr( szPath, L'\\' ) + 1 ); // Recurse FindAllFiles( szNewPath, iDepth - 1 ); } } while( FindNextFile( hSearch, &FindData ) ); FindClose( hSearch ); hSearch = INVALID_HANDLE_VALUE; } //FindAllFiles //+--------------------------------------------------------------------------- // // Function: Producer // // Synopsis: A thread which collects all the input file names and puts them // in the work queue // // Arguments: [lpvThreadParam] -- The input file name // // Returns: 0 // // History: 10-01-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- DWORD WINAPI Producer( PVOID pvThreadParam ) { WCHAR szFullPath[ MAX_PATH + 2 ]; DWORD dwAttrib = 0; int cLoops = g_cLoops; GetFullPathName( (WCHAR*)pvThreadParam, MAX_PATH, szFullPath, NULL ); // If the input is recognized as a directory, only recurse into this // directory. Otherwise, recurse into all subdirectories and try to // match the pattern dwAttrib = GetFileAttributes( szFullPath ); if( ( 0xFFFFFFFF != dwAttrib ) && ( FILE_ATTRIBUTE_DIRECTORY & dwAttrib ) ) { // It's a directory. If the last character is a '\\', add "*" // Otherwise, add "\\*" if( L'\\' == szFullPath[ wcslen( szFullPath ) - 1 ] ) { wcscat( szFullPath, L"*" ); } else { wcscat( szFullPath, L"\\*" ); } } while( cLoops-- ) { FindAllFiles( szFullPath, g_iDepth ); } return( 0 ); } //Producer //+--------------------------------------------------------------------------- // // Function: Consumer // // Synopsis: Pulls stuff out of the work queue and calls LaunchTest on the // file name. It is also responsible for deleting the file // names it pulls out of the list // // Arguments: [lpvThreadParam] -- Thread number // // Returns: 0 // // History: 10-01-1996 ericne Created // // Notes: // //---------------------------------------------------------------------------- DWORD WINAPI Consumer( PVOID pvThreadParam ) { WCHAR *szInputFileName = NULL; COleInitialize OleIsInitialized; // This object ensures OLE is initialized // for this thread while( g_WorkQueue.GetItem( szInputFileName ) ) { // Display which thread is filtering which document if( NORMAL <= g_verbosity ) { wprintf(L"Thread %d is filtering %s\r\n", (UINT_PTR)pvThreadParam, szInputFileName ); } LaunchTest( szInputFileName ); delete [] szInputFileName; } return( 0 ); } //Consumer //+--------------------------------------------------------------------------- // // Function: wmain // // Synopsis: Processes command switches, Launched producer and consumer // threads // // Arguments: [argc] -- The number of command line parameters // [argv] -- The value of the command line parameters // // Returns: 0 // // History: 10-01-1996 ericne Created // // Notes: extern "C" to satisfy the linker // //---------------------------------------------------------------------------- extern "C" int __cdecl wmain( int argc, WCHAR **argv ) { int iLoop = 0; int iCount = 0; int iNbrConsumers = 1; int iNbrProducers = 1; WCHAR **ppwcParams = NULL; WCHAR *pwcBadParam = NULL; _PNH pfOldNewHandler = NULL; LPCWSTR *rgszInputFileName; TCHAR szLogFileName[ MAX_PATH ]; DWORD dwThreadID = 0; HANDLE *hConsumerThreads = NULL; HANDLE *hProducerThreads = NULL; CCmdLineParserW CmdLineParser( argc, argv ); try { // Set the new handeler routine pfOldNewHandler = _set_new_handler( out_of_store ); // Check the command line parameters: if( CmdLineParser.IsFlagExist( L"?" ) ) { Usage( argv[0] ); exit( 0 ); } // Find /d flag if( CmdLineParser.IsFlagExist( L"d" ) ) g_fDumpToFile = TRUE; // Find /-l flag if( CmdLineParser.IsFlagExist( L"-l" ) ) g_fIsLoggingEnabled = FALSE; // Find /-d flag if( CmdLineParser.IsFlagExist( L"-d" ) ) g_fIsDumpingEnabled = FALSE; // Find the legit flag if( CmdLineParser.IsFlagExist( L"legit" ) ) g_fLegitOnly = TRUE; // Find /i flag if( CmdLineParser.EnumerateFlag( L"i", ppwcParams, iCount ) ) { if( 1 > iCount ) { Usage( argv[0] ); printf( "ERROR: 1 or more input files must be specified\r\n" ); exit( -1 ); } iNbrProducers = iCount; // Create array of file patterns rgszInputFileName = NEW LPCWSTR[ iCount ]; for( int iParam=0; iParam < iCount; iParam++ ) { rgszInputFileName[ iParam ] = ppwcParams[ iParam ]; } } else { Usage( argv[0] ); printf( "ERROR: An input file must be specified\r\n" ); exit( -1 ); } // find the /ini flag if( CmdLineParser.EnumerateFlag( L"ini", ppwcParams, iCount ) ) { if( 1 != iCount ) { Usage( argv[0] ); printf( "ERROR: You must specify exactly one" " initialization file.\r\n" ); exit( -1 ); } g_szIniFileName = ppwcParams[0]; } // Find the /v flag if( CmdLineParser.EnumerateFlag( L"v", ppwcParams, iCount ) ) { if( 1 != iCount ) { Usage( argv[0] ); printf( "ERROR: You must specify exactly 1 verbosity\r\n" ); exit( -1 ); } // Get the new verbosity: g_verbosity = (Verbosity) _wtoi( ppwcParams[0] ); if( MUTE > g_verbosity || HIGH < g_verbosity ) { printf( "ERROR: The verbosity must be between %d and %d" " inclusive.\r\n", MUTE, HIGH ); exit( -1 ); } } // Find the /l flag if( CmdLineParser.EnumerateFlag( L"l", ppwcParams, iCount ) ) { g_fLogToFile = TRUE; if( 1 < iCount ) { Usage( argv[0] ); printf( "ERROR: You may only specity one log file\r\n" ); exit( -1 ); } else if( 1 == iCount ) { // Create a common log file object g_pLog = NEW CLog; // Should succeed because of the new handler _ASSERT( NULL != g_pLog ); // Convert to tchar: _stprintf( szLogFileName, _T("%ls"), ppwcParams[0] ); // Initialize the log if( ! g_pLog->InitLog( szLogFileName ) ) { printf( "ERROR: Could not initialize log file %s\r\n", szLogFileName ); exit( -1 ); } // Set the log threshold g_pLog->SetThreshold( VerbosityToLogStyle( g_verbosity ) ); } } // Find the /t flag if( CmdLineParser.EnumerateFlag( L"t", ppwcParams, iCount ) ) { if( 1 != iCount ) { Usage( argv[0] ); printf( "ERROR: You must specify only 1 thread count\r\n" ); exit( -1 ); } // Get the new thread count iNbrConsumers = _wtoi( ppwcParams[0] ); if( 1 > iNbrConsumers || MAXIMUM_WAIT_OBJECTS < iNbrConsumers ) { printf( "The thread count must be between 1 and %d inclusive\r\n", MAXIMUM_WAIT_OBJECTS ); exit( -1 ); } } // Find the /r flag if( CmdLineParser.EnumerateFlag( L"r", ppwcParams, iCount ) ) { // If no depth is specified, assume full recursion if( 0 == iCount ) { g_iDepth = -1; } else { g_iDepth = _wtoi( ppwcParams[0] ); // Special case, if the recurse depth is 0, perform full recursion if( 0 == g_iDepth ) g_iDepth = -1; } } // Find the /c flag if( CmdLineParser.EnumerateFlag( L"c", ppwcParams, iCount ) ) { if( 1 != iCount ) { Usage( argv[0] ); printf( "ERROR: You may only specify 1 loop count\r\n" ); exit( -1 ); } // Get the new thread count g_cLoops = _wtoi( ppwcParams[0] ); // Special case: if cLoops == 0, loop forever if( 0 == g_cLoops ) g_cLoops = -1; } // This flag configures for a stress test if( CmdLineParser.IsFlagExist( L"stress" ) ) { g_fIsLoggingEnabled = FALSE; g_fIsDumpingEnabled = FALSE; g_fLegitOnly = TRUE; g_verbosity = MUTE; g_cLoops = -1; } if( CmdLineParser.GetNextFlag( pwcBadParam ) ) { Usage( argv[0] ); printf( "ERROR: Unknown command line switch : %ls\r\n", pwcBadParam ); exit( -1 ); } // Done processing switches // Allocate memory for the thread handles hProducerThreads = NEW HANDLE[ iNbrProducers ]; hConsumerThreads = NEW HANDLE[ iNbrConsumers ]; // Launch the producer threads for( iLoop = 0; iLoop < iNbrProducers; iLoop++ ) { while( NULL == ( hProducerThreads[ iLoop ] = CreateThread( NULL, 0, &Producer, (void*)rgszInputFileName[ iLoop ], 0, &dwThreadID ) ) ) { Sleep( dwSleepTime ); } } // Launch the consumer threads for( iLoop = 0; iLoop < iNbrConsumers; iLoop++ ) { while( NULL == ( hConsumerThreads[ iLoop ] = CreateThread( NULL, 0, &Consumer, (void*)IntToPtr(iLoop), 0, &dwThreadID ) ) ) { Sleep( dwSleepTime ); } } // Wait for all the producers to finish WaitForMultipleObjects( (DWORD) iNbrProducers, hProducerThreads, TRUE, INFINITE ); // Signal the Consumer threads to finish. g_WorkQueue.Done(); // Wait for the consumer threads to finish WaitForMultipleObjects( (DWORD) iNbrConsumers, hConsumerThreads, TRUE, INFINITE ); } catch (...) { } { // Close all the handles if( NULL != hProducerThreads ) { for( iLoop=0; iLoop < iNbrProducers; iLoop++ ) { if( NULL != hProducerThreads[ iLoop ] ) { (void)CloseHandle( hProducerThreads[ iLoop ] ); hProducerThreads[ iLoop ] = NULL; } } delete[] hProducerThreads; hProducerThreads = NULL; } if( NULL != hConsumerThreads ) { for( iLoop = 0; iLoop < iNbrConsumers; iLoop++ ) { if( NULL != hConsumerThreads[ iLoop ] ) { (void)CloseHandle( hConsumerThreads[ iLoop ] ); hConsumerThreads[ iLoop ] = NULL; } } delete[] hConsumerThreads; hConsumerThreads = NULL; } if( NULL != rgszInputFileName ) { delete[] rgszInputFileName; rgszInputFileName = NULL; } // If there is a shared log, report stats and quit if( NULL != g_pLog ) { g_pLog->ReportStats( ); delete g_pLog; g_pLog = NULL; } // Restore the old new handler _set_new_handler( pfOldNewHandler ); // Shut down CI to prevent memory leaks CIShutdown(); } return( 0 ); } //main