kerneltest/f32test/fsstress/t_soak1.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// f32test\fsstress\t_soak1.cpp
// Suggestions for future improvements: The createVerifyFileX method uses local 
// RBufs for writing and reading. By making these buffers global (at least the 
// write buffers), the overhead of allocating and filling memory can be avoided.
// 
//

#include <f32file.h>
#include <e32test.h>
#include "t_chlffs.h"
#include "t_server.h"
#define __TESTING_LFFS__

LOCAL_D TInt gDriveNumber;

const TInt KMaxNumberThreads     = 4;
const TInt KHeapSize             = 0x600000;
const TInt KDiskUnitThreshold    = 0x1000000; // if disk metrics are above threshold, display in MB rather than KB
const TInt KNotificationInterval = 10 * 1000000;

const TInt KMaxSizeArray         = 3;
const TInt KMaxFilesPerDirectory = 100;

struct TTestMetrics
	{
	TInt SizeArray[KMaxSizeArray][5];
	TInt KSpaceRequiredForMakeAndDelete;
	TInt KFillDiskFileSize;
	};




///#define SINGLE__THREAD

// 256 sectors, 17 sectors+1, 3 bytes, 254 sectors+4, 100 sectors
//  50 sectors, 113 sectors+25, 10 sectors, 103 bytes, 199 sectors+44
// 24 sectors+166, 189 bytes, 225 sectors, 4 sectors+221, 117 sectors+40
LOCAL_D const TTestMetrics DefMetrics =
				{
				{{65536,4353,3,65028,25600},	// Sizearray[0]
				{12800,28953,2560,103,50988},	// Sizearray[1]
				{6310,189,57600,1245,29992}},	// Sizearray[2]
				655360,							// KSpaceRequiredForMakeAndDelete
				65536							// KFillDiskFileSize
				};

// 16 sectors, 1 sectors+1, 3 bytes, 15 sectors+4, 6 sectors
//  3 sectors, 7 sectors+25, 1 sector, 103 bytes, 12 sectors+44
// 2 sectors+166, 189 bytes, 15 sectors, 4 sectors+221, 7 sectors+40
LOCAL_D const TTestMetrics LffsMetrics = 
				{
				{{4096,257,3,3844,1536},		// Sizearray[0]
				{768,1817,256,103,3116},		// Sizearray[1]
				{678,189,3840,1245,1832}},		// Sizearray[2]
				36864,							// KSpaceRequiredForMakeAndDelete
				4096							// KFillDiskFileSize
				};

// 1600 sectors, 100 sectors+100, 1 sector+44, 1501 sectors+144, 600 sectors
// 300 sectors, 709 sectors+196, 100 sectors, 40 sectors+60, 1217 sectors+48
// 264 sectors+216, 73 sectors+212, 486 sectors+84, 715 sectors+160
LOCAL_D const TTestMetrics FAT32Metrics =
				{
				{{409600,25700,300,384400,153600},		// Sizearray[0]
				{76800,181700,25600,10300,311600},		// Sizearray[1]
				{67800,18900,384000,124500,183200}},	// Sizearray[2]
				3686400,								// KSpaceRequiredForMakeAndDelete
				409600									// KFillDiskFileSize
				};

// 8 sectors, 1 sectors+1, 3 bytes, 7 sectors+4, 3 sectors
//  1 sectors, 3 sectors+25, 1 sector, 103 bytes, 6 sectors+44
// 1 sector+166 bytes, 189 bytes, 7 sectors, 2 sectors+221, 3 sectors+40

LOCAL_D const TTestMetrics NandMetrics = 
				{
				{{2048,257,3,1796,768},			// Sizearray[0]
				{256,793,256,103,1580},			// Sizearray[1]
				{422,189,1792,833,808}},		// Sizearray[2]
				20480,							// KSpaceRequiredForMakeAndDelete
				2048							// KFillDiskFileSize
				};



LOCAL_D const TTestMetrics* TheMetrics = 0;


#if defined(__WINS__)
#define WIN32_LEAN_AND_MEAN
#pragma warning (disable:4201) // warning C4201: nonstandard extension used : nameless struct/union
#pragma warning (default:4201) // warning C4201: nonstandard extension used : nameless struct/union
#endif

#if defined(_UNICODE)
	#if !defined(UNICODE)
		#define UNICODE
	#endif
#endif

#ifndef SINGLE__THREAD
	#define REUSE_THREAD
#endif

#ifdef REUSE_THREAD
	LOCAL_D TBool gRequestEnd;
#endif

class TThreadTestInfo
	{
public:
	TInt iCycles;
	TInt iErrors;
	TInt iSizeArrayPos;
	TInt iErrorInfo;
	};

GLDEF_D RTest test(_L("T_SOAK1"));
LOCAL_D TThreadTestInfo ThreadTestInfo[KMaxNumberThreads];
LOCAL_D TBool CurrentlyFillingDisk;
LOCAL_D TInt FillDiskCount;
LOCAL_D TInt FillDiskCycle;

LOCAL_C TInt MakeAndDeleteFilesThread(TAny* anId);
LOCAL_C TInt FillAndEmptyDiskThread(TAny* anId);
LOCAL_C TInt CreateVerifyFileX(const TDesC& aBaseName,TInt aSize,RFs &aFs,TInt aPattern);
LOCAL_C TInt MakeFileName(TInt aThreadId, TInt aFileNumber, RFs & aFs, TFileName& aName, TBool aMakeDir = ETrue);
LOCAL_C TInt AppendPath(TFileName& aPath, TInt aNumber, TInt aLevel);

_LIT( KConnect, "Connect" );
_LIT( KDelete, "Delete" );
_LIT( KDriveToChar, "DriveToChar" );
_LIT( KAppendPath, "AppendPath" );
_LIT( KSetSessPath, "SetSessPath" );
_LIT( KMdAll, "MkdirAll" );
_LIT( KVolInfo, "VolInfo" );
_LIT( KReplace, "Replace" );
_LIT( KRead, "Read" );
_LIT( KWrite, "Write" );
_LIT( KFlush, "Flush" );
_LIT( KDataCompare, "DataCompare" );
_LIT( KMemory, "Memory" );
_LIT( KFilePrefix, "FILE_" );

_LIT(KPath, "?:\\SOAK_TEST\\SESSION%d\\");

LOCAL_C void LogError( TInt aError, const TDesC& aAction, const TDesC& aFileName, TUint a1, TUint a2, TInt line = -1 )
	{
	if(line >= 0)
		{
		_LIT(KFmt, "TSOAK1 ERROR (line %d): %d, file [%S], %S, (0x%x, 0x%x)");
		RDebug::Print(KFmt, line, aError, &aFileName, &aAction, a1, a2);
		}
	else 
		{
		_LIT(KFmt, "TSOAK1 ERROR: %d, file [%S], %S, (0x%x, 0x%x)");
		RDebug::Print(KFmt, aError, &aFileName, &aAction, a1, a2);
		}
	}

LOCAL_C TInt MakeFileName(TInt aThreadId, TInt aFileNumber, RFs & aFs, TFileName& aName, TBool aMakeDir)
//
// creates a file name and makes all the directory components, if required
//
	{
	
	TFileName path;
	path.Format(KPath, aThreadId);
	
	TChar driveLetter;
	TInt  r;
	r = aFs.DriveToChar(gDriveNumber, driveLetter);
	if (r != KErrNone)
		{
		LogError(r, KDriveToChar, KNullDesC, driveLetter, 0);
		aFs.Close();
		return(r);
		}
		
	path[0] = (TText) driveLetter;
	r = aFs.SetSessionPath(path);
	if (r != KErrNone)
		{
		LogError(r, KSetSessPath, path, 0, 0);
		aFs.Close();
		return(r);
		}
		
	// add additional directories
	TInt fileNumber;
	fileNumber = aFileNumber;
	r = AppendPath(path, fileNumber, 0);
	if(r != KErrNone)
		{
		LogError(r, KAppendPath, path, fileNumber, 0);
		aFs.Close();
		return(r);
		}
		
	if(aMakeDir)
		{
		r = aFs.MkDirAll(path);
		if (r != KErrNone && r != KErrAlreadyExists)
			{
			LogError(r, KMdAll, path, 0, 0);
			aFs.Close();
			return(r);
			}
		}
		
	// and finally add file name
	path.Append(KFilePrefix);
	path.AppendNum(aFileNumber);

	aName = path;
	return(KErrNone);
	}
	

LOCAL_C TInt AppendPath(TFileName& aPath, TInt aNumber, TInt aLevel)
//
// helper function for MakeFileName()
//
	{
	
	if(aNumber > KMaxFilesPerDirectory)
		{
		aNumber /= KMaxFilesPerDirectory;
		AppendPath(aPath, aNumber, aLevel + 1);
		}
		
	if(aLevel)
		{	
		aPath.AppendNum(aNumber % KMaxFilesPerDirectory);	
		aPath.Append('\\');	
		}
	
	return(KErrNone);
	}

LOCAL_C TInt MakeAndDeleteFilesThread(TAny* anId)
//
// The entry point for the 'MakeAndDeleteFiles' thread.
//
	{

	TInt thrdId=(TInt)anId;
	TInt pattern=(ThreadTestInfo[thrdId].iCycles)%2;
    TInt r;
	RFs f;
	r=f.Connect();
	if (r!=KErrNone)
		{
		LogError( r, KConnect, KNullDesC, 0, 0 );
		ThreadTestInfo[thrdId].iErrorInfo=1;
		return(r);
		}

	TFileName fileName;
	TInt sizeArrayPos = ThreadTestInfo[thrdId].iSizeArrayPos;
#ifdef REUSE_THREAD
	while(!gRequestEnd)
		{
#endif	
		for(TInt i = 0; i < 5; i++)
			{
			// create files
			r = MakeFileName(thrdId, i, f, fileName);
			if(r != KErrNone)
				{
				ThreadTestInfo[thrdId].iErrorInfo=2;
				f.Close();
				return(r);
				}
			
			r = CreateVerifyFileX(fileName, TheMetrics->SizeArray[sizeArrayPos][0], f, pattern);
			if (r!=KErrNone)
				{
				ThreadTestInfo[thrdId].iErrorInfo=3;
				f.Close();
				return(r);
				}
			
			// delete selected files at certain points in the cycle
			TInt deleteFile = EFalse;
			switch(i)
				{
				default:
					// nothing to be done
				break;
				case 2:
					// delete file 0
					deleteFile = ETrue;
					r = MakeFileName(thrdId, 0, f, fileName, EFalse);
				break;
				case 3:
					// delete file 1
					deleteFile = ETrue;
					r = MakeFileName(thrdId, 1, f, fileName, EFalse);
				break;
				}
				
				if(deleteFile)
					{
					// check MakeFileName() error code
					if(r != KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo = 4;
						f.Close();
						return(r);
						}
						
					// and delete file
					r = f.Delete(fileName);
					if(r != KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo = 5;
						f.Close();
						return(r);
						}
					}
			}
			
		sizeArrayPos++;
		if(sizeArrayPos >= KMaxSizeArray)
			{
			sizeArrayPos = 0;
			}
			
		ThreadTestInfo[thrdId].iSizeArrayPos=sizeArrayPos;
#ifdef REUSE_THREAD
	ThreadTestInfo[thrdId].iCycles++;
	pattern = (ThreadTestInfo[thrdId].iCycles) % 2;
	}
#endif

	f.Close();
	return(KErrNone);
	}

LOCAL_C TInt FillAndEmptyDiskThread(TAny* anId)
//
// The entry point for the 'FillAndEmptyDisk' thread.
//
	{

	TInt thrdId=(TInt)anId;
	TInt pattern=(ThreadTestInfo[thrdId].iCycles)%2;
    TInt r;
	RFs f;
	r=f.Connect();
	if (r!=KErrNone)
		{
		LogError( r, KConnect, KNullDesC, 0, 0 );
		ThreadTestInfo[thrdId].iErrorInfo=6;
		return(r);
		}

	TInt i;
#ifdef REUSE_THREAD
	while(!gRequestEnd)
		{
#endif	
		for (i=0;i<5;i++) // Create/Delete 5 files each time
			{
			if (CurrentlyFillingDisk)
				{
				TVolumeInfo v;
				r=f.Volume(v,gDriveNumber);
				if (r!=KErrNone)
					{
					ThreadTestInfo[thrdId].iErrorInfo=7;
					LogError( r, KVolInfo, KNullDesC, gDriveNumber, 0);
					f.Close();
					return(r);
					}
				if (v.iFree<=(TheMetrics->KSpaceRequiredForMakeAndDelete+TheMetrics->KFillDiskFileSize))
					CurrentlyFillingDisk=EFalse;
				else
					{
					TFileName fileName;
					r = MakeFileName(thrdId, FillDiskCount, f, fileName);
					if(r != KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo = 8;
						f.Close();
						return(r);
						}			
					
					r = CreateVerifyFileX(fileName, TheMetrics->KFillDiskFileSize, f, pattern);
					if (r!=KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo=9;
						f.Close();
						return(r);
						}
					FillDiskCount++;
					}
				}
			else
				{
				if (FillDiskCount<=0)
					{
					CurrentlyFillingDisk=ETrue;
					FillDiskCount=0;
					FillDiskCycle++;
					}
				else
					{
					FillDiskCount--;
					TFileName fileName;
					r = MakeFileName(thrdId, FillDiskCount, f, fileName, EFalse);
					if(r != KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo = 10;
						f.Close();
						return(r);
						}
					
					r = f.Delete(fileName);				
					if (r!=KErrNone)
						{
						ThreadTestInfo[thrdId].iErrorInfo=11;
						LogError(r, KDelete, fileName,FillDiskCount, 0);
						f.Close();
						return(r);
						}
					}
				}
			}
#ifdef REUSE_THREAD
		ThreadTestInfo[thrdId].iCycles++;
		pattern = (ThreadTestInfo[thrdId].iCycles) % 2;
		}			
#endif

	f.Close();
	return(KErrNone);
	}

const TInt KCreateFileBufSize = 0x80000; 
LOCAL_C TInt CreateVerifyFileX(const TDesC& aFileName, TInt aSize, RFs& aFs, TInt aPattern)
//
// Create and verify a file.
//
	{
	// Note, the directory structure is provided by MakeFileName(). Hence it 
	// is assumed at this point that the path to the file exists already.
	RFile file;
	TInt r;
	r = file.Replace(aFs, aFileName, EFileWrite);
	if (r!=KErrNone)
		{
		LogError( r, KReplace, aFileName, EFileWrite, 0 );
		return(r);
		}

	// Grow it to the specified size by writing a pattern buffer to it
	// Alternate the pattern buffer each time
	RBuf8 wBuf;
	r = wBuf.CreateMax(KCreateFileBufSize);
	if(r != KErrNone)
		{
		LogError(r, KMemory, aFileName, 0, 0, __LINE__);
		wBuf.Close();
		file.Close();
		return(r);
		}
		
	TInt i;
	
	if (aPattern)
		{
		// ascending pattern
		for (i = 0; i < KCreateFileBufSize; i++)
			{			
			wBuf[i] = (TUint8) i;			
			}
		}
	else
		{
		// descending pattern
		for (i = 0; i < KCreateFileBufSize; i++)
			{
			wBuf[i] = (TUint8) ((KCreateFileBufSize - 1) - i);
			}
		}


	TInt pos;
	TInt chunkSize;
	TInt sectorCount = 0;
	
	for (pos = 0; pos < aSize; pos += chunkSize)
		{
		wBuf[0]=(TUint8)i;	// Insert sector count
		chunkSize = Min((aSize-pos), KCreateFileBufSize);
		r = file.Write(pos, wBuf, chunkSize);
		if (r != KErrNone)
			{
			LogError(r, KWrite, aFileName, pos, chunkSize, __LINE__);
			file.Close();
			wBuf.Close();
			return(r);
			}
			
		sectorCount++;
		}

	// Flush it
	r=file.Flush();
	if (r!=KErrNone)
		{
		LogError( r, KFlush, aFileName, 0, 0, __LINE__);
		file.Close();
		wBuf.Close();
		return(r);
		}

//	Test still works if this is commented out just doesn't verify
	// Read back and verify it
	RBuf8 rBuf;
	r = rBuf.CreateMax(KCreateFileBufSize);
	if(r != KErrNone)
		{
		LogError( r, KMemory, aFileName, 0, 0, __LINE__);
		file.Close();
		wBuf.Close();
		rBuf.Close();
		return(KErrGeneral);
		}
	
	
	for (pos = 0;pos < aSize; pos += chunkSize)
		{
		chunkSize = Min((aSize-pos), KCreateFileBufSize);
		r = file.Read(pos, rBuf, chunkSize);
		if (r != KErrNone)
			{
			LogError(r, KRead, aFileName, pos, 0, __LINE__);
			file.Close();
			wBuf.Close();
			rBuf.Close();
			return(r);
			}
			
		wBuf[0] = (TUint8) i; // Insert sector count
		wBuf.SetLength(chunkSize);
		r = rBuf.Compare(wBuf);
		if (r != 0)
			{
			LogError(r, KDataCompare, aFileName, 0, 0, __LINE__);
			file.Close();
			wBuf.Close();
			rBuf.Close();
			return(KErrGeneral);
			}
		}
//

	file.Close();
	wBuf.Close();
	rBuf.Close();
	return(KErrNone);
	}
	

#ifdef SINGLE__THREAD
LOCAL_C void DoTests()
//
//  single thread
//
    {

	TInt r=KErrNone;
	test.Next(_L("Start continuous file Write/Read/Verify operation"));
	RThread t[KMaxNumberThreads];
	TRequestStatus tStat[KMaxNumberThreads];

	TInt i=0;

	TName threadName;
	TRequestStatus kStat=KRequestPending;
	test.Console()->Read(kStat);
	ThreadTestInfo[i].iCycles=0;
	ThreadTestInfo[i].iErrors=0;
	ThreadTestInfo[i].iSizeArrayPos=(i%KMaxSizeArray);
	ThreadTestInfo[i].iErrorInfo=0;
	if (i<(KMaxNumberThreads-1))
		{
		threadName.Format(_L("MakeAndDeleteFiles%d"),i);
    	r=t[i].Create(threadName,MakeAndDeleteFilesThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
		}
	else
		{
		// Last thread fills/empties disk
		threadName.Format(_L("FillAndEmptyDisk%d"),i);
    	r=t[i].Create(threadName,FillAndEmptyDiskThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
		}
	if (r!=KErrNone)
		test.Printf(_L("Error(%d) creating thread(%d)\r\n"),r,i);
	test(r==KErrNone);
	t[i].Logon(tStat[i]);
	t[i].Resume();
	CurrentlyFillingDisk=ETrue;
	FillDiskCount=0;

    TInt totalTime=0;
    TTime startTime;
    TTime time;
    startTime.UniversalTime();

	TInt ypos=test.Console()->WhereY();
	FOREVER
		{
		User::WaitForAnyRequest();
		if (kStat!=KRequestPending)
			{
    		t[i].LogonCancel(tStat[i]);
			User::WaitForRequest(tStat[i]);
			break;
			}
		else
			{
			TBool threadFinished=EFalse;
			if (tStat[i]!=KRequestPending && !threadFinished)
				{
				t[i].Close();
				(ThreadTestInfo[i].iCycles)++;
				if (tStat[i]!=KErrNone)
					(ThreadTestInfo[i].iErrors)++;
				threadFinished=ETrue;

				// Launch another thread
				TInt threadNameId=((ThreadTestInfo[i].iCycles)%2)?(i+KMaxNumberThreads):i; // Alternate thread name
				threadName.Format(_L("FillAndEmptyDisk%d"),threadNameId);
   				r=t[i].Create(threadName,FillAndEmptyDiskThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
				if (r!=KErrNone)
					test.Printf(_L("Error(%d) creating thread(%d)\r\n"),r,i);
    			test(r==KErrNone);
    			t[i].Logon(tStat[i]);
				t[i].Resume();
				}
				
			test.Console()->SetPos(0,(ypos+i));
			test.Printf(_L("Thread(%d): % 4d errors in % 4d cycles (%d)\r\n"),i,ThreadTestInfo[i].iErrors,ThreadTestInfo[i].iCycles,ThreadTestInfo[i].iErrorInfo);
			if (!threadFinished)
				{
				test.Printf(_L("Semaphore death"));
				break;
				}
			TVolumeInfo v;
			r=TheFs.Volume(v,gDriveNumber);
			test(r==KErrNone);
			test.Console()->SetPos(0,(ypos+KMaxNumberThreads));
			test.Printf(_L("Free space on disk: %u K(of %u K)\r\n"),(v.iFree/1024).Low(),(v.iSize/1024).Low());

            TTimeIntervalSeconds timeTaken;
            time.UniversalTime();
            r=time.SecondsFrom(startTime,timeTaken);
            test(r==KErrNone);
            totalTime=timeTaken.Int();

            TInt seconds = totalTime % 60;
            TInt minutes = (totalTime / 60) % 60;
            TInt hours   = (totalTime / 3600) % 24;
            TInt days    = totalTime / (60 * 60 * 24);
            test.Printf(_L("Elapsed Time: %d d %02d:%02d:%02d\r\n"), days, hours, minutes, seconds);
			}
		}
    }

#else 

LOCAL_C void DoTests()
//
//  multiple threads
//
    {

	TInt r=KErrNone;
	test.Next(_L("Start continuous file Write/Read/Verify operation"));
	RThread t[KMaxNumberThreads];
	TRequestStatus tStat[KMaxNumberThreads];

	TInt i=0;

	TName threadName;
	TRequestStatus kStat=KRequestPending;
	test.Console()->Read(kStat);
	for (i=0;i<KMaxNumberThreads;i++)
		{
		ThreadTestInfo[i].iCycles=0;
		ThreadTestInfo[i].iErrors=0;
		ThreadTestInfo[i].iSizeArrayPos=(i%KMaxSizeArray);
		ThreadTestInfo[i].iErrorInfo=0;
		if (i<(KMaxNumberThreads-1))
			{
			threadName.Format(_L("MakeAndDeleteFiles%d"),i);
	    	r=t[i].Create(threadName,MakeAndDeleteFilesThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
			}
		else
			{
			// Last thread fills/empties disk
			threadName.Format(_L("FillAndEmptyDisk%d"),i);
	    	r=t[i].Create(threadName,FillAndEmptyDiskThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
			}
		if (r!=KErrNone)
   			test.Printf(_L("Error(%d) creating thread(%d)\r\n"),r,i);
	    test(r==KErrNone);
	    t[i].Logon(tStat[i]);
		t[i].Resume();
		}
	CurrentlyFillingDisk=ETrue;
	FillDiskCount=0;

    TInt totalTime = 0;
    TTime cycleTime;
    TTime startTime;
    TTime time;
    startTime.UniversalTime();
    cycleTime.UniversalTime();
    
	TVolumeInfo v;
	r=TheFs.Volume(v,gDriveNumber);
	test(r==KErrNone);
//	TInt initialFreeSpace = I64LOW(v.iFree / 1024);

#ifdef __LIMIT_EXECUTION_TIME__
	RTimer timer;
	timer.CreateLocal();
	TRequestStatus reqStat;
	timer.After(reqStat,60000000); // After 60 secs
#endif

#ifdef REUSE_THREAD
	RTimer displayTimer;
	displayTimer.CreateLocal();
	TRequestStatus displayStat;
	displayTimer.After(displayStat, KNotificationInterval); // after 10 secs
#endif

	TInt ypos=test.Console()->WhereY();
	FOREVER
		{
		User::WaitForAnyRequest();
		if (kStat!=KRequestPending)
			{
			// user requested to end - let threads die
#ifdef REUSE_THREAD				
			gRequestEnd = ETrue;
#endif			
			for (i=0;i<KMaxNumberThreads;i++)
				{
				User::WaitForRequest(tStat[i]);
				}
			break;
			}
#ifdef __LIMIT_EXECUTION_TIME__
		else if (reqStat != KRequestPending)
			{
			// max execution exceeded - wait for threads to die
			TInt totalCycles = 0;
			for (i=0;i<KMaxNumberThreads;i++)
				{
				totalCycles+= ThreadTestInfo[i].iCycles;
				}
			test.Printf(_L("Total cycles = %d\r\n"), totalCycles);
			test.Printf(_L("Waiting for thread death...\r\n"));
			for (i=0;i<KMaxNumberThreads;i++)
				{
				User::WaitForRequest(tStat[i]);
				}
			break;
			}
#endif
		else
			{
			// other notification
			TBool threadFinished=EFalse;
			for (i=0;i<KMaxNumberThreads;i++)
				{
				if (tStat[i]!=KRequestPending && !threadFinished)
					{
					t[i].Close();
					(ThreadTestInfo[i].iCycles)++;
					if (tStat[i]!=KErrNone)
						(ThreadTestInfo[i].iErrors)++;
					threadFinished=ETrue;

					// Launch another thread
					TInt threadNameId=((ThreadTestInfo[i].iCycles)%2)?(i+KMaxNumberThreads):i; // Alternate thread name
					if (i<(KMaxNumberThreads-1))
						{
						threadName.Format(_L("MakeAndDeleteFiles%d"),threadNameId);
	    				r=t[i].Create(threadName,MakeAndDeleteFilesThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
						}
					else
						{
						// Last thread fills/empties disk
						threadName.Format(_L("FillAndEmptyDisk%d"),threadNameId);
	    				r=t[i].Create(threadName,FillAndEmptyDiskThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)i);
						}
					if (r!=KErrNone)
   						test.Printf(_L("Error(%d) creating thread(%d)\r\n"),r,i);
	    			test(r==KErrNone);
	    			t[i].Logon(tStat[i]);
					t[i].Resume();
					}
				test.Console()->SetPos(0,(ypos+i));
   				test.Printf(_L("Thread(%d): % 4d errors in % 4d cycles (%d)\r\n"),i,ThreadTestInfo[i].iErrors,ThreadTestInfo[i].iCycles,ThreadTestInfo[i].iErrorInfo);
				}
				
#ifdef REUSE_THREAD
			if(displayStat != KRequestPending)
				{
				// re-request notification
				displayTimer.After(displayStat, KNotificationInterval);
				}
			else if (!threadFinished)
				{
				test.Printf(_L("Semaphore death"));
				break;
				}
#else 
			if (!threadFinished)
				{
				test.Printf(_L("Semaphore death"));
				break;
				}
#endif					
				
			r=TheFs.Volume(v,gDriveNumber);
			test(r==KErrNone);
			test.Console()->SetPos(0,(ypos+KMaxNumberThreads));
			
			TInt  freeSpace;
			TInt8 freeSpaceUnit;
			TInt  totalSpace;
			TInt8 totalSpaceUnit;
			
			// switch t
			if(v.iFree > KDiskUnitThreshold)
				{
				// display in MB
				freeSpace = I64LOW(v.iFree / (1024 * 1024));
				freeSpaceUnit = 'M';
				}
			else
				{
				// display in KB
				freeSpace = I64LOW(v.iFree/1024);
				freeSpaceUnit = 'K';
				}
			
			if(v.iSize > KDiskUnitThreshold)
				{
				// display in MB
				totalSpace = I64LOW(v.iSize / (1024 * 1024));
				totalSpaceUnit = 'M';
				}
			else
				{
				// display in KB
				totalSpace = I64LOW(v.iSize/1024);
				totalSpaceUnit = 'K';
				}
			
			test.Printf(_L("Free space on disk: %u %cB (of %u %cB)\r\n"), 
						freeSpace, freeSpaceUnit, totalSpace, totalSpaceUnit);

            TTimeIntervalSeconds timeTaken;
            time.UniversalTime();            
            r=time.SecondsFrom(startTime,timeTaken);
            test(r==KErrNone);
            totalTime=timeTaken.Int();

            TInt seconds = totalTime % 60;
            TInt minutes = (totalTime / 60) % 60;
            TInt hours   = (totalTime / 3600) % 24;
            TInt days    = totalTime / (60 * 60 * 24);
            test.Printf(_L("Elapsed Time (%d): %d d %02d:%02d:%02d\r\n"), FillDiskCycle, days, hours, minutes, seconds);
            
            if(CurrentlyFillingDisk)
            	{
            	// work out ETA to full disk
	            r = time.SecondsFrom(cycleTime, timeTaken);
    	        if((r == KErrNone) && (v.iSize > v.iFree))
    	        	{
		            	totalTime = (TInt) ((v.iFree/1024 * (TInt64) timeTaken.Int()) / (v.iSize/1024 - v.iFree/1024));
		    	        seconds = totalTime % 60;
    		    	    minutes = (totalTime / 60) % 60;
        		    	hours   = (totalTime / 3600) % 24;
	            		days    = totalTime / (60 * 60 * 24);
            	
    	        		test.Printf(_L("ETA to full disk: %d d %02d:%02d:%02d\r\n"), days, hours, minutes, seconds);
        	    	}
            	}
            else 
            	{
            	// currently emptying disk, update time metrics
            	cycleTime.UniversalTime();
            	}            	
           	
			}
						
			test.Printf(_L("\n"));
		}
    }
#endif


GLDEF_C void CallTestsL()
//
// Call all tests
//
    {
	TInt r = TheFs.CharToDrive( gSessionPath[0], gDriveNumber );
	test( KErrNone == r );
	
	// select appropriate metrics table
	if(IsFileSystemFAT32(TheFs, gDriveNumber)) 
		{
		TheMetrics = &FAT32Metrics;
		RDebug::Printf("Using FAT32 metrics\r\n");
		}
	else if(IsTestingLFFS())
		{
		TheMetrics = &LffsMetrics;
		RDebug::Printf("Using LFFS metrics\r\n");
		}
	else
		{
		TDriveInfo driveInfo;
		r=TheFs.Drive(driveInfo, gDriveNumber);
		test(r==KErrNone);
		
		if((driveInfo.iType==EMediaNANDFlash) && !(driveInfo.iMediaAtt & KMediaAttWriteProtected))
			{
			TheMetrics = &NandMetrics;
			RDebug::Printf("Using NAND metrics\r\n");
			}
		else 
			{
			TheMetrics = &DefMetrics;
			RDebug::Printf("Using default metrics\r\n");
			}
		}
		
	FillDiskCycle = 1;
#ifdef REUSE_THREAD
	gRequestEnd = EFalse;
#endif
	DoTests();

    }