// Copyright (c) 2006-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:
// e32test\mmu\t_codepaging.cpp
// This test relies on four dlls which it loads dynamically:
// - t_codepaging_dll		Very simple dll, contains a single function.  Used for testing state
// changes	of pages
// - t_codepaging_dll2	 	Contains 8 pages of data, used for testing the correct data is paged
// - t_codepaging_dll3		Statically links to t_codepaging_sll, used for testing ReadExportDir
// - t_codepaging_dll4		Large dll, used for testing code segment that span more than one page
// table
// - t_codepaging_dll5		Contains relocatable const data.
// - t_codepaging_dll6		Contains relocatable writable data.
// - t_codepaging_dll7		Statically linked to t_codepaging_dll5 to check dependent DLLs
// are initialised correctly.
// Suite of tests specifically to test the code paging portion of demand 
// paging.
// 002 Exercise ReadExportDir with one code seg mapped already into current process
// 003 Exercise ReadExportDir with one code seg mapped into different process
// 004 Check locking of code which then gets unloaded
// 004.01 Load test driver...
// 004.02 Load/unload dll
// 004.03 Load dll again
// 004.04 Get data from DLL
// 004.05 Lock DLL data
// 004.06 Check DLL data
// 004.07 Close DLL
// 004.08 Check DLL loaded at different address
// 004.09 Unlock DLL data
// 004.10 Check DLL loaded at original address
// 004.11 Cleanup
// 005 Test writing to paged code
// 005.01 Load DLL
// 005.02 Get data from DLL
// 005.03 Write to pages in DLL
// 006 Running tests on drive I:
// 007 Test accessing pages by executing code
// 008 Test accessing pages by reading code
// 009 Test accessing pages by reading code from another process via an alias
// 010 Test unmapping paged code
// 011 Test interactions between two processes
// 012 Test that the contents of a paged DLL are as expected
// 013 Test relocated const data in DLL
// 014 Test relocated writable data in DLL
// 015 Test relocated writable data in dependent DLL
// 016 Test relocated writable data in preloaded dependent DLL
// 017 Test relocated writable data in preloaded dependent DLL opened in other process
// 018 Test killing a thread while it is taking paging faults
// 019 Test unloading a library while another thread is executing it
// 020 Test random access to a large dll
// 021 Test accessing paged code from 2 processes at 1 priority level(s) for 5 seconds
// 022 Test accessing paged code from 5 processes at 1 priority level(s) for 10 seconds
// 023 Test accessing paged code from 10 processes at 1 priority level(s) for 20 seconds
// 024 Test accessing paged code from 5 processes at 2 priority level(s) for 10 seconds
// 025 Test accessing paged code from 50 processes at 1 priority level(s) for 2 seconds
// 026 Running tests on drive Z:
// 027 Test accessing pages by executing code
// 028 Test accessing pages by reading code
// 029 Test accessing pages by reading code from another process via an alias
// 030 Test unmapping paged code
// 031 Test interactions between two processes
// 032 Test that the contents of a paged DLL are as expected
// 033 Test relocated const data in DLL
// 034 Test relocated writable data in DLL
// 035 Test relocated writable data in dependent DLL
// 036 Test relocated writable data in preloaded dependent DLL
// 037 Test relocated writable data in preloaded dependent DLL opened in other process
// 038 Test killing a thread while it is taking paging faults
// 039 Test unloading a library while another thread is executing it
// 040 Test random access to a large dll
// 041 Test accessing paged code from 2 processes at 1 priority level(s) for 5 seconds
// 042 Test accessing paged code from 5 processes at 1 priority level(s) for 10 seconds
// 043 Test accessing paged code from 10 processes at 1 priority level(s) for 20 seconds
// 044 Test accessing paged code from 5 processes at 2 priority level(s) for 10 seconds
// 045 Test accessing paged code from 50 processes at 1 priority level(s) for 2 seconds
// 
//

//! @SYMTestCaseID			KBASE-T_CODEPAGING-0335
//! @SYMTestType			UT
//! @SYMPREQ				PREQ1110
//! @SYMTestCaseDesc		Demand Paging Code Paging tests.
//! @SYMTestActions			001 Code paging tests
//! @SYMTestExpectedResults All tests should pass.
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented


#define __E32TEST_EXTENSION__
#include <e32test.h>
#include <f32file.h>
#include <e32math.h>
#include <dptest.h>

#include "mmudetect.h"
#include "d_memorytest.h"
#include "d_demandpaging.h"
#include "t_codepaging_dll.h"
#include "paging_info.h"

class TPagingDriveInfo
	{
public:
	TChar iDriveLetter;
	TDriveInfo iDriveInfo;
	};

RArray<TPagingDriveInfo> SupportedDrives;

/// Page attributes, cut-n-paste'd from mmubase.h
enum TType
	{
//	EInvalid=0,			// No physical RAM exists for this page
//	EFixed=1,			// RAM fixed at boot time
//	EUnused=2,			// Page is unused
//	EChunk=3,
//	ECodeSeg=4,
//	EHwChunk=5,
//	EPageTable=6,
//	EPageDir=7,
//	EPtInfo=8,
//	EShadow=9,

	EPagedROM=10,
	EPagedCode=11,
	EPagedData=12,
	EPagedCache=13,
	EPagedFree=14,
	};

enum TState
	{
	EStateNormal 			= 0,	// no special state
	EStatePagedYoung 		= 1,
	EStatePagedOld 			= 2,
	EStatePagedDead 		= 3,	// Not possible on the flexible memory model.
	EStatePagedLocked 		= 4,
	EStatePagedOldestClean 	= 5,	// Flexible memory model only.
	EStatePagedOldestDirty 	= 6,	// Flexible memory model only.
	};



/// The possible states for a logical page of RAM loaded code
enum TPageState
	{
	EStateUnmapped,
	EStatePagedOut,
	EStateYoung,
	EStateOld,
	EStateOldestClean,
	EStateOldestDirty,

	ENumPageStates
	};

const TUint KPagedStateShift = 8;
const TUint KPagedStateMask = 0xff00;


/// The possible states for a physical page of RAM loaded code
enum TPhysState
	{
	EPhysNotPresent,
	EPhysYoung,
	EPhysOld,
	EPhysOldestClean,
	EPhysOldestDirty,

	ENumPhysStates
	};

/// Names of the logical page states
const char* StateNames[ENumPageStates] =
	{
	"Unmapped",
	"PagedOut",
	"Young",
	"Old",
	"OldestClean",
	"OldestDirty"
	};

/// Names of the physical page states
const char* PhysStateNames[ENumPhysStates] =
	{
	"NotPresent",
	"Young",
	"Old",
	"OldestClean",
	"OldestDirty"
	};

/// Array of physical page states indexed by logical page state
TPhysState PhysStateFromPageState[ENumPageStates] =
	{
	EPhysNotPresent,
	EPhysNotPresent,
	EPhysYoung,
	EPhysOld,
	EPhysOldestClean,
	EPhysOldestDirty,
	};

/// The expected logical page state bitmask for each state
TInt ExpectedPageState[ENumPageStates] =
	{
	0,
	EPageStatePageTablePresent | EPageStateInRamCode | EPageStatePaged,
	EPageStatePageTablePresent | EPageStateInRamCode | EPageStatePaged | EPageStatePtePresent | EPageStatePteValid,
	EPageStatePageTablePresent | EPageStateInRamCode | EPageStatePaged | EPageStatePtePresent,
	EPageStatePageTablePresent | EPageStateInRamCode | EPageStatePaged | EPageStatePtePresent,
	EPageStatePageTablePresent | EPageStateInRamCode | EPageStatePaged | EPageStatePtePresent
	};

/// Extra bits we expect to be set on the multiple memory model
TInt ExpectedPageStateMultipleExtra[ENumPageStates] =
	{
	EPageStateCodeChunkPresent,
	EPageStateCodeChunkPresent,
	EPageStateCodeChunkPresent | EPageStatePhysAddrPresent,
	EPageStateCodeChunkPresent | EPageStatePhysAddrPresent
	};

/// Mask for the bits of the page state related to the physicsal page that we check
TInt PhysStateMask = 0xffff;

/// The expected physical page state bitmask for each state
TInt ExpectedPhysState[ENumPhysStates] =
	{
	0,
	EPagedCode | (EStatePagedYoung<<8),
	EPagedCode | (EStatePagedOld<<8),
	EPagedCode | (EStatePagedOldestClean<<8),
	EPagedCode | (EStatePagedOldestDirty<<8)
	};

typedef void (*TFunc)(void);
typedef void (*TFunc1)(TInt aArg1);
typedef TFunc TTransitionTable[ENumPageStates][ENumPageStates];

void LoadLibrary();
void UnloadLibrary();
void AccessPage();
void MakeOld();
void MakeOldest();
void MakePagedOut();

TTransitionTable StateTransitions =
	{
// Current:			Next:	EStateUnmapped	EStatePagedOut	EStateYoung		EStateOld	EStateOldestClean	EStateOldestDirty	
/* EStateUnmapped 	*/	{	0,				LoadLibrary,	0,				0,			0,					0			},
/* EStatePagedOut	*/	{	UnloadLibrary,	0,				AccessPage,		0,			0,					0			},
/* EStateYoung		*/	{	UnloadLibrary,	MakePagedOut,	AccessPage,		MakeOld,	0,					0			},
/* EStateOld		*/	{	UnloadLibrary,	MakePagedOut,	AccessPage,		0,			MakeOldest,			MakeOldest	},
/* EStateOldestClean*/	{	UnloadLibrary,	MakePagedOut,	AccessPage,		0,			0,					0			},
/* EStateOldestDirty*/	{	UnloadLibrary,	MakePagedOut,	AccessPage,		0,			0,					0			},
	};

const TInt KMaxPathLen = 16;
typedef TPageState TStatePath[KMaxPathLen];

// Test paths through the possible states that excercises all transitions except those back to unmapped
// Doesn't consider dirty pages.
TStatePath TestPathNoOldest =
	{
	EStateUnmapped,
	EStatePagedOut,
	EStateYoung,
	EStateOld,
	EStateYoung,
	EStateOld,
	EStatePagedOut,
	EStateUnmapped,
	};

TStatePath TestPathOldest =
	{
	EStateUnmapped,
	EStatePagedOut,
	EStateYoung,
	EStateOld,
	EStateOldestClean,
	EStateYoung,
	EStateOld,
	EStateYoung,
	EStateOld,
	EStatePagedOut,
	EStateYoung,
	EStateOld,
	EStateOldestClean,
	EStatePagedOut,
	EStateUnmapped,
	};

TStatePath* TestPath = NULL;

/// The different ways of accessing paged code
enum TAccessMethod
	{
	EAccessExec,
	EAccessRead,
	EAccessAliasRead
	};

_LIT(KLibraryName, "t_codepaging_dll");
_LIT(KSearchPathTemplate, "?:\\sys\\bin");

// RTest stuff /////////////////////////////////////////////////////////////////

RTest test(_L("T_CODEPAGING"));

#define test_noError(x) { TInt _r = (x); if (_r < 0) HandleError(_r, __LINE__); }
#define test_notNull(x) { TAny* _a = (TAny*)(x); if (_a == NULL) HandleNull(__LINE__); }
#define test_equal(e, a) { TInt _e = TInt(e); TInt _a = TInt(a); if (_e != _a) HandleNotEqual(_e, _a, __LINE__); }

void HandleError(TInt aError, TInt aLine)
	{
	test.Printf(_L("Error %d\n"), aError);
	test.operator()(EFalse, aLine);
	}

void HandleNull(TInt aLine)
	{
	test.Printf(_L("Null value\n"));
	test.operator()(EFalse, aLine);
	}

void HandleNotEqual(TInt aExpected, TInt aActual, TInt aLine)
	{
	test.Printf(_L("Expected 0x%x but got 0x%x\n"), aExpected, aActual);
	test.operator()(EFalse, aLine);
	}

//  Server session /////////////////////////////////////////////////////////////

_LIT(KServerName, "t_codepaging_server");

class RTestSession : public RSessionBase
	{
public:
	enum TMessage
		{
		EKill,
		EExec,
		ESetCurrentDrive,
		EDesRead,
		ETestPageState,
		ETestStateTransition,
		EStartRandomAccessThread
		};
public:
	TInt Connect(TInt aProcessNum);
	inline void Kill()
		{ test_noError(RSessionBase::SendReceive(EKill,TIpcArgs())); }
	inline void Exec(TFunc aFunc)
		{ test_noError(RSessionBase::SendReceive(EExec,TIpcArgs((TInt)aFunc))); }
	inline void SetCurrentDrive(TUint16 aDrive)
		{ test_noError(RSessionBase::SendReceive(ESetCurrentDrive,TIpcArgs(aDrive))); }
	inline void DesRead(const TDesC8& aData)
		{ test_noError(RSessionBase::SendReceive(EDesRead,TIpcArgs(&aData))); }
	inline void TestPageState(TPageState aState, TPhysState aPhysState)
		{ test_noError(RSessionBase::SendReceive(ETestPageState,TIpcArgs(aState, aPhysState))); }
	inline void TestStateTransition(TPageState aState)
		{ test_noError(RSessionBase::SendReceive(ETestStateTransition,TIpcArgs(aState))); }
	inline void StartRandomAccessThread(TThreadPriority aPriority)
		{ test_noError(RSessionBase::SendReceive(EStartRandomAccessThread,TIpcArgs(aPriority))); }
	};

TInt RTestSession::Connect(TInt aProcessNum)
	{
	TBuf<32> name;
	name.AppendFormat(_L("%S-%d"), &KServerName, aProcessNum);
	return CreateSession(name,TVersion());
	}


// Global data /////////////////////////////////////////////////////////////////

TBool MovingMemoryModel;
TBool MultipleMemoryModel;
TBool FlexibleMemoryModel;
TBool HaveOldestLists;
TInt ProcessNum;

RTestSession OtherProcess;

RLibrary PagedLibrary;
TBool LibraryLoaded = EFalse;

TTestFunction Library_TestFunction = NULL;

TAccessMethod AccessMethod;

RLibrary LargeLibrary;
TBool LargeLibraryLoaded = EFalse;
const TUint8* LargeDataStart;
const TUint8* LargeDataEnd;
const TUint8* LargeDataPtr;
TInt PagesReadSinceLastAccess = 0;

TInt LiveListSize;
TInt PageSize;

TPageState State;
TPhysState PhysState;

TUint16 CurrentDrive;
TInt LocalDriveNumber;

RThread RandomAccessThread;
volatile TBool RandomAccessKill = EFalse;

TBool CanForcePageOut = ETrue;

// Utility functions ///////////////////////////////////////////////////////////

TPtrC16 GetMediaType(TInt aMediaType)
	{
	_LIT(KMediaNotPresent, "MediaNotPresent");
	_LIT(KMediaUnknown, "MediaUnknown");
	_LIT(KMediaFloppy, "MediaFloppy");
	_LIT(KMediaHardDisk, "MediaHardDisk");
	_LIT(KMediaCdRom, "MediaCdRom");
	_LIT(KMediaRam, "MediaRam");
	_LIT(KMediaFlash, "MediaFlash");
	_LIT(KMediaRom, "MediaRom");
	_LIT(KMediaRemote, "MediaRemote");
	_LIT(KMediaNANDFlash, "MediaNANDFlash");
	_LIT(KMediaUnKnown, "MediaUnKnown");

	switch(aMediaType)
		{
		case EMediaNotPresent:
			return KMediaNotPresent();
		case EMediaUnknown:
			return KMediaUnknown();
		case EMediaFloppy:
			return KMediaFloppy();
		case EMediaHardDisk:
			return KMediaHardDisk();
		case EMediaCdRom:
			return KMediaCdRom();
		case EMediaRam:
			return KMediaRam();
		case EMediaFlash:
			return KMediaFlash();
		case EMediaRom:
			return KMediaRom();
		case EMediaRemote:
			return KMediaRemote();
		case EMediaNANDFlash:
			return KMediaNANDFlash();
		default:
			return KMediaUnKnown();
		}
	}

// Get the list of pageable drives
void GetSupportedDrives(TBool aVerbose = EFalse)
	{
	if (aVerbose)
		{
		test.Printf(_L("Supported drives:\n"));
		test.Printf(_L("     Type             Attr     MedAttr  Filesystem\n"));
		}
		
	RFs fs;
	test_noError(fs.Connect());

	TDriveList driveList;
	TDriveInfo driveInfo;

	TInt r = fs.DriveList(driveList);
    test_noError(r);

	TBool NandPageableMediaFound = EFalse;

	for (TInt drvNum=0; drvNum<KMaxDrives; ++drvNum)
		{
	    if(!driveList[drvNum])
	        continue;   //-- skip unexisting drive
	
	    r = fs.Drive(driveInfo, drvNum);
	    test_noError(r);


		TChar ch;
		r = fs.DriveToChar(drvNum, ch);
		test_noError(r);

		TBuf<256> fileSystemName;
		r = fs.FileSystemName(fileSystemName, drvNum);
		test_noError(r);
	
		if ((driveInfo.iDriveAtt & KDriveAttPageable) && (driveInfo.iType == EMediaNANDFlash))
			NandPageableMediaFound = ETrue;

		TBool pageable = EFalse;
		if (driveInfo.iDriveAtt & KDriveAttPageable)
			pageable = ETrue;

		// If we've already found a pageable NAND drive, 
		// then assume the Z: drive is pageable too if it's got a composite file system
		_LIT(KCompositeName,"Composite");
		if ((fileSystemName == KCompositeName()) && NandPageableMediaFound)
			pageable = ETrue;
			
		if (pageable)
			{
			TChar ch;
			r = fs.DriveToChar(drvNum, ch);
			test_noError(r);

			TPagingDriveInfo pagingDriveInfo;
			pagingDriveInfo.iDriveLetter = ch;
			pagingDriveInfo.iDriveInfo = driveInfo;

			r = SupportedDrives.Append(pagingDriveInfo);
			test_noError(r);
			}
		
		if (aVerbose)
			{
			TPtrC16 mediaType = GetMediaType(driveInfo.iType);
			_LIT(KPageable, "pageable");
			test.Printf(_L("  %c: %16S %08x %08x %10S %S\n"), 
						(TInt) ch, &mediaType, driveInfo.iDriveAtt, driveInfo.iMediaAtt,
						&fileSystemName, (pageable ? &KPageable : &KNullDesC));
			}
		
		}

	fs.Close();
	}

TInt GetPageState(TAny* aPage)
	{
	TInt r = UserSvr::HalFunction(EHalGroupVM, EVMPageState, aPage, 0);
	test_noError(r);
	return r;
	}

// Force a page to be paged in or rejuvenated, to simulate aging of pages in the live list
void ForcePageIn()
	{
	// Find a page that's old or paged out
	do
		{
		LargeDataPtr += PageSize;
		if (LargeDataPtr >= LargeDataEnd)
			LargeDataPtr = LargeDataStart;
		}
	while (GetPageState((TAny*)LargeDataPtr) & EPageStatePteValid);

	// and read from it to make it young
	TUint32 value = *(volatile TUint8*)LargeDataPtr;
	(void)value;
	++PagesReadSinceLastAccess;
	}

void FlushAllPages()
	{
	test_noError(UserSvr::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0));
	}

void TestCurrentState()
	{
	test_Value(State, State >= 0 && State < ENumPageStates);
	test_Value(PhysState, PhysState >= 0 && PhysState < ENumPhysStates);
	
	TInt stateBits = GetPageState((TAny*)Library_TestFunction);
	TInt expected = ExpectedPageState[State];
	if (MultipleMemoryModel)
		expected |= ExpectedPageStateMultipleExtra[State];
	TUint physStateIgnore = 0;
	if (FlexibleMemoryModel)
		{
		expected &= ~EPageStatePageTablePresent; // flexible memory model allocates page tables on demand
		physStateIgnore = 0xff; // flexible memory model doesn't have separate page types for code/data/ROM
		}

	test_equal(expected, stateBits & (~PhysStateMask))
	test_equal(ExpectedPhysState[PhysState] & ~physStateIgnore, stateBits & PhysStateMask & ~physStateIgnore)
	}

void TestPageState(TPageState aExpected, TPhysState aExpectedPhys)
	{
	RDebug::Printf("%d:  %-12s %-12s", ProcessNum, StateNames[aExpected], PhysStateNames[aExpectedPhys]);
	test_equal(State, aExpected);
	test_equal(PhysState, aExpectedPhys);
	TestCurrentState();
	}

TInt PathLength(const TStatePath& aPath)
	{
	TInt i = 1;
	while (aPath[i] != EStateUnmapped && i < KMaxPathLen)
		++i;
	return i + 1;
	}

TInt FindState(const TStatePath& aPath, TPageState aTarget)
	{
	TInt len = PathLength(aPath);
	TInt j;
	for (j = 1 ; j < len ; ++j)
		{
		if (aPath[j] == aTarget)
			return j;
		}
	return -1;
	}

TInt WriteByte(TAny* aArg)
	{
	TUint8* ptr = (TUint8*)aArg;
	*ptr = 23;
	return KErrNone;
	}

void StartOtherProcess(TInt aProcessNum, RTestSession& aSession)
	{
	RProcess me, other;
	TBuf<16> arg;
	arg.AppendNum(aProcessNum);
	test_noError(other.Create(me.FileName(), arg));
	TRequestStatus status;
	other.Rendezvous(status);
	other.Resume();
	User::WaitForRequest(status);
	test_noError(status.Int());
	test_equal(EExitPending, other.ExitType());
	test_noError(aSession.Connect(aProcessNum));
	other.Close();
	}

const TDesC& LibrarySearchPath(TUint16 aDrive)
	{
	static TBuf<32> path;
	path = KSearchPathTemplate;
	path[0] = aDrive;
	return path;
	}

const TDesC& LibraryName(TInt aLibraryNum, TUint16 aDrive)
	{
	// this gives dlls a different name on each drive so we can be sure we're loading the right one
	static TBuf<32> name;
	name = KLibraryName;
	if (aLibraryNum > 1)
		name.AppendNum(aLibraryNum);
	if (aDrive != 'Z')
		name.AppendFormat(_L("_%c"), aDrive);
	return name;
	}

const TDesC& LibraryFilename(TInt aLibraryNum, TUint16 aDrive)
	{
	static TBuf<40> filename;
	filename = LibrarySearchPath(aDrive);
	filename.AppendFormat(_L("\\%S.dll"), &LibraryName(aLibraryNum, aDrive));
	return filename;
	}

TInt LoadSpecificLibrary(RLibrary& aLibrary, TInt aLibraryNum, TUint16 aDrive)
	{
	const TDesC& name = LibraryName(aLibraryNum, aDrive);
	const TDesC& path = LibrarySearchPath(aDrive);
	return aLibrary.Load(name, path);
	}

TInt GetLocDrvNumber(TUint16 aDrive)
	{
	RFs fs;
	RFile file;

	TBuf<40> libname = LibraryFilename(1, aDrive);
	
	fs.Connect();
	TInt r=file.Open(fs,libname,EFileRead);
	if(r!=KErrNone)
		test.Printf(_L("%d: Error %d: could not open file %S\n"),ProcessNum, r, &libname);
	test(r==KErrNone);

	SBlockMapInfo info;
	TInt64 start=0;
	r=file.BlockMap(info,start, -1,ETestDebug);

	if (r!=KErrNone && r!=KErrCompletion)
		test.Printf(_L("Error %d: could not obtain block map\n"),r);
	test(r==KErrNone || r==KErrCompletion);
	TInt locDriveNumber=info.iLocalDriveNumber;

	file.Close();
	fs.Close();
	return locDriveNumber;
	}

void LoadLargeLibrary()
	{
	test(!LargeLibraryLoaded);
	test_noError(LoadSpecificLibrary(LargeLibrary, 4, CurrentDrive));
	TGetAddressOfDataFunction func = (TGetAddressOfDataFunction)LargeLibrary.Lookup(KGetAddressOfDataFunctionOrdinal);
	TInt size;
	LargeDataStart = (TUint8*)func(size);
	test_notNull(LargeDataStart);
	if (size < LiveListSize*PageSize)
		{
		// We need an area of paged data large enough to ensure we can cause a page of our choice to
		// be paged out.  If the size of the live list for testing is too small, we'll skip some tests
		CanForcePageOut = EFalse;
		}
	LargeDataEnd = LargeDataStart + size;
	LargeDataPtr = LargeDataStart;
 	LargeLibraryLoaded = ETrue;
	}

void UnloadLargeLibrary()
	{
	test(LargeLibraryLoaded);
	LargeLibrary.Close();
	LargeDataStart = NULL;
	LargeDataEnd = NULL;
	LargeDataPtr = NULL;
	LargeLibraryLoaded = EFalse;
	}

// Page in a page and keep aging it to see if it ever reaches an oldest list. 
TBool SetHaveOldestLists()
	{
	AccessMethod = EAccessExec;
	AccessPage();
	TInt pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
	do	
		{
		ForcePageIn();
		pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
		if (EStatePagedOldestClean == pagedState || EStatePagedOldestDirty == pagedState)
			break;
		}
	while (	PagesReadSinceLastAccess <= LiveListSize);

	HaveOldestLists = EStatePagedOldestClean == pagedState || EStatePagedOldestDirty == pagedState;
	return HaveOldestLists;
	}

void SetCurrentDrive(TUint16 aDrive)
	{
	if (LargeLibraryLoaded)
		UnloadLargeLibrary();
	CurrentDrive = aDrive;
	LocalDriveNumber = GetLocDrvNumber(aDrive);
	LoadLargeLibrary();
	if (!Library_TestFunction)
		{
		LoadLibrary();
		Library_TestFunction = (TTestFunction)PagedLibrary.Lookup(KTestFunctionOrdinal);
		test_notNull(Library_TestFunction);
		if (SetHaveOldestLists())
			TestPath = &TestPathOldest;
		else
			TestPath = &TestPathNoOldest;
		UnloadLibrary();
		FlushAllPages();
		}
	}

// State transition functions //////////////////////////////////////////////////

void LoadLibrary()
	{
	test_noError(LoadSpecificLibrary(PagedLibrary, 1, CurrentDrive));
	if (MovingMemoryModel)
		FlushAllPages(); // to make sure pages aren't already mapped
	LibraryLoaded = ETrue;
	}

void UnloadLibrary()
	{
	PagedLibrary.Close();
	LibraryLoaded = EFalse;
	}

void AccessPage()
	{
	switch (AccessMethod)
		{
		case EAccessExec:
			Library_TestFunction();
			break;

		case EAccessRead:
			{
			TUint8 x = *(volatile TUint8*)Library_TestFunction;
			(void)x;
			}
			break;

		case EAccessAliasRead:
			{
			TPtrC8 des((TUint8*)Library_TestFunction, 4);  // descriptor header must be in different page to data
			OtherProcess.DesRead(des);
			}
			break;

		}
	PagesReadSinceLastAccess = 0;
	}

void MakeOld()
	{
	TInt initialState = GetPageState((TAny*)Library_TestFunction);
	do	
		ForcePageIn();
	while (PagesReadSinceLastAccess <= LiveListSize &&
		   initialState == GetPageState((TAny*)Library_TestFunction));
	TUint pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
	test_Equal(EStatePagedOld, pagedState);
	}

void MakeOldest()
	{
	TInt pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
	do	
		{
		ForcePageIn();
		pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
		if (EStatePagedOldestClean == pagedState ||	EStatePagedOldestDirty == pagedState)
			break;
		}
	while (PagesReadSinceLastAccess <= LiveListSize);
	test_Value(pagedState, EStatePagedOldestClean == pagedState || EStatePagedOldestDirty == pagedState);
	}

void MakePagedOut()
	{
	TInt finalListState1 = EStatePagedOld;
	TInt finalListState2 = EStatePagedOld;
	if (HaveOldestLists)
		{
		finalListState1 = EStatePagedOldestClean;
		finalListState2 = EStatePagedOldestDirty;
		}

	TInt pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
	// Get the page onto the final list(s) so it can be detected when it is paged out.
	while (	pagedState != finalListState1 && pagedState != finalListState2 &&
			PagesReadSinceLastAccess <= LiveListSize)
		{
		ForcePageIn();
		pagedState = (GetPageState((TAny*)Library_TestFunction) & KPagedStateMask) >> KPagedStateShift;
		}
	// Now force the page off the paging lists.
	pagedState = GetPageState((TAny*)Library_TestFunction);
	do
		{
		ForcePageIn();
		}
	while (	PagesReadSinceLastAccess <= LiveListSize &&
			pagedState == GetPageState((TAny*)Library_TestFunction));
	}

// Test functions //////////////////////////////////////////////////////////////

void Initialise()
	{
	CurrentDrive = 'Z';
	
	TUint32 memModelAttrs = MemModelAttributes();
	MovingMemoryModel = ((memModelAttrs & EMemModelTypeMask) == EMemModelTypeMoving);
	MultipleMemoryModel = ((memModelAttrs & EMemModelTypeMask) == EMemModelTypeMultiple);
	FlexibleMemoryModel = ((memModelAttrs & EMemModelTypeMask) == EMemModelTypeFlexible);

	test_noError(UserSvr::HalFunction(EHalGroupKernel, EKernelHalPageSizeInBytes, &PageSize, 0));
	
	SVMCacheInfo info;
	test_noError(UserSvr::HalFunction(EHalGroupVM, EVMHalGetCacheSize, &info, 0));
	LiveListSize = info.iMaxSize / PageSize;
	}

void CopyDllFragmented(RFs& aFs, const TDesC& aSourceName, const TDesC& aDestName)
	{
	test.Printf(_L("  %S\n"), &aDestName);

	TInt r = aFs.MkDirAll(aDestName);
	test(r == KErrNone || r == KErrAlreadyExists);

	TBuf<40> tempName(aDestName);
	tempName.Append(_L(".tmp"));

	RFile in, out, temp;
	test_noError(in.Open(aFs, aSourceName, EFileRead));
	test_noError(out.Replace(aFs, aDestName, EFileWrite));
	test_noError(temp.Replace(aFs, tempName, EFileWrite));

	const TInt KBufferSize = 3333;
	TBuf8<KBufferSize> buffer;
	
	test_noError(temp.Write(buffer));
	test_noError(temp.Flush());

	TInt size;
	test_noError(in.Size(size));
	TInt pos = 0;
	while (pos < size)
		{
		test_noError(in.Read(buffer));
		test_noError(out.Write(buffer));
		test_noError(out.Flush());
		test_noError(temp.Write(buffer));
		test_noError(temp.Flush());
		pos += buffer.Length();
		}
	
	in.Close();
	out.Close();
	temp.Close();
	}

void CopyDllToSupportedDrives(RFs& aFs, CFileMan* aFileMan, TInt aLibraryNum)
	{
	TBuf<40> source = LibraryFilename(aLibraryNum, 'Z');

	test.Printf(_L("Copying %S to:\n"), &source);
	
	for (TInt i = 0 ; i < SupportedDrives.Count() ; ++i)
		{
		TUint8 drive = SupportedDrives[i].iDriveLetter;
		if (!(SupportedDrives[i].iDriveInfo.iMediaAtt & KMediaAttWriteProtected))
			{
			TBuf<40> dest = LibraryFilename(aLibraryNum, drive);
			CopyDllFragmented(aFs, source, dest);
			}
		}
	}

void CopyDllsToSupportedDrives()
	{
	RFs fs;
	test_noError(fs.Connect());

	CTrapCleanup* cleanup = CTrapCleanup::New();
	test_notNull(cleanup);
	
	CFileMan* fileMan = NULL;
	TRAPD(r, fileMan = CFileMan::NewL(fs));
	test_noError(r);

	for (TInt i = 1 ; i <= 7 ; ++i)
		CopyDllToSupportedDrives(fs, fileMan, i);
	
	delete fileMan;
	delete cleanup;	
	fs.Close();
	}

void TestStateTransition(TPageState aNext)
	{
	TPhysState nextPhys = PhysStateFromPageState[aNext];
	RDebug::Printf("%d:  %-12s            -> %-12s", ProcessNum, StateNames[State], StateNames[aNext]);
	TFunc func = StateTransitions[State][aNext];
	test_notNull(func);
	func();
	State = aNext;
	PhysState = nextPhys;
	TestCurrentState();
	}

void RunPathTest(const TStatePath& aPath, TInt aStart = 0, TInt aEnd = -1)
	{
	if (aEnd == -1)
		aEnd = PathLength(aPath) - 1;

	// Check we're already in the starting state
	TestPageState(aPath[aStart], PhysStateFromPageState[aPath[aStart]]);

	for (TInt i = aStart + 1 ; i <= aEnd ; ++i)
		TestStateTransition(aPath[i]);
	}

void RunUnmapTest(const TStatePath& aPath)
	{
	TInt len = PathLength(aPath);
	
	// Test an unmodified code paged page can be unmapped from all the possible 
	// states it can be in.
	TInt endState = EStateOld;
	if (HaveOldestLists)
		endState = EStateOldestClean;
		
	for (TInt i = EStateUnmapped + 1; i <= endState; ++i)
		{
		TPageState target = (TPageState)i;
		RDebug::Printf("\nUnmap from %s:\n", StateNames[target]);

		TStatePath path;
		memcpy(path, aPath, sizeof(path));

		TInt j = FindState(path, target) + 1;
		test_Value(j, j > 0 && j < len + 1);
		path[j] = EStateUnmapped;
		
		RunPathTest(path, 0, j);
		}
	}

void GoToState(TPageState aState)
	{
	if (LibraryLoaded)
		{
		UnloadLibrary();
		State = EStateUnmapped;
		PhysState = PhysStateFromPageState[State];
		}
		
	TInt i = FindState(*TestPath, aState);
	test(i != -1);
	RunPathTest(*TestPath, 0, i);
	}

void RunMultiProcessTest()
	{
	TStatePath& testPath = *TestPath;
	TInt len = PathLength(testPath);
	
	TInt endState = EStateOld;
	if (HaveOldestLists)
		endState = EStateOldestClean;
	for (TInt i = EStateUnmapped; i <= endState; ++i)
		{
		TPageState target = (TPageState)i;
		RDebug::Printf("\nTesting interaction with second process in state %s:\n", StateNames[target]);
		
		GoToState(target);
		TPageState state2 = testPath[0];  // current state in other process
		OtherProcess.TestPageState(state2, PhysStateFromPageState[state2]);
		for (TInt i = 1 ; i < len ; ++i)
			{
			TPageState next2 = testPath[i];
			OtherProcess.TestStateTransition(next2);
			
			// Update physical state if affected by transition in other process
			if ((State == EStateYoung || State == EStateOld || State == EStateOldestClean) &&
				(state2 != EStateUnmapped && next2 != EStateUnmapped))
				PhysState = PhysStateFromPageState[next2];

			// Update logical state in this process if affected by transition in other process
			if (State == EStateYoung && next2 == EStateOld)
				State = EStateOld;
			else if (State == EStateOld && next2 == EStateOldestClean)
				State = EStateOldestClean;
			else if ((State == EStateYoung || State == EStateOld || State == EStateOldestClean) &&
					 (state2 == EStateOld  || state2 == EStateOldestClean) && next2 == EStatePagedOut)
				State = EStatePagedOut;

			RDebug::Printf("%d:  %-12s %-12s", ProcessNum, StateNames[State], PhysStateNames[PhysState]);
			TestCurrentState();
			state2 = next2;
			}
		}

	if (LibraryLoaded)
		{
		UnloadLibrary();
		State = EStateUnmapped;
		PhysState = PhysStateFromPageState[State];
		}	
	}

void TestReadExportDir()
	{
	RLibrary library;
	test_noError(LoadSpecificLibrary(library, 3, CurrentDrive));
	TTestFunction func = (TTestFunction)library.Lookup(KTestFunctionOrdinal);
	test_notNull(func);
	test_noError(func());
	library.Close();
	}

void RunReadExportDirTest()
	{
	test.Next(_L("Exercise ReadExportDir with one code seg mapped already into current process"));
	LoadLibrary();
	TestReadExportDir();
	UnloadLibrary();

	test.Next(_L("Exercise ReadExportDir with one code seg mapped into different process"));
	OtherProcess.Exec(LoadLibrary);
	TestReadExportDir();
	OtherProcess.Exec(UnloadLibrary);
	}

void RunWriteToPagedCodeTest()
	{
	test.Next(_L("Test writing to paged code"));

	RMemoryTestLdd memoryTest;
	test(KErrNone==memoryTest.Open());

	FlushAllPages();
	TUint8* ptr = (TUint8*)LargeDataStart;
	while(ptr<LargeDataEnd)
		{
		TInt stateBits = GetPageState(ptr);
		// write to paged out memory should cause exception...
		test(KErrBadDescriptor==memoryTest.WriteMemory(ptr,0));
		// page state should be unchanged...
		test_equal(stateBits,GetPageState(ptr))
		// page-in in memory...
		TUint32 value = *(TUint32*)ptr;
		// page state should be changed...
		test(stateBits!=GetPageState(ptr));
		// write to paged out memory should still cause exception...
		test(KErrBadDescriptor==memoryTest.WriteMemory(ptr,~value));
		// memory should be unchanged...
		test(value==*(TUint32*)ptr);
		ptr += PageSize;
		}

	memoryTest.Close();
	}

void RunPageLockingTest()
	{
	test.Next(_L("Check locking of code which then gets unloaded"));

	// load test driver...
	test.Start(_L("Load test driver..."));
	RDemandPagingTestLdd ldd;
	TInt r = User::LoadLogicalDevice(KDemandPagingTestLddName);
	test(r==KErrNone || r==KErrAlreadyExists);
	test(ldd.Open()==KErrNone);

	// load once to get address that code will be loaded at...
	test.Next(_L("Load/unload dll"));
	RLibrary library;
	test_noError(LoadSpecificLibrary(library, 5, CurrentDrive));
	TGetAddressOfRelocatedDataFunction func = (TGetAddressOfRelocatedDataFunction)library.Lookup(KGetAddressOfDataFunctionOrdinal);
	test_notNull(func);
	library.Close();

	// load again and check it's at the same place...
	test.Next(_L("Load dll again"));
	test_noError(LoadSpecificLibrary(library, 5, CurrentDrive));
	TGetAddressOfRelocatedDataFunction func2 = (TGetAddressOfRelocatedDataFunction)library.Lookup(KGetAddressOfDataFunctionOrdinal);
	test_equal(func,func2);

	// get address of data in the DLL...
	test.Next(_L("Get data from DLL"));
	void* d;
	void* c;
	TInt size;
	void** data = func(size,d,c);

	// lock pages...
	test.Next(_L("Lock DLL data"));
	r = ldd.Lock(data,size);
	test_equal(r,1);

	// check data...
	test.Next(_L("Check DLL data"));
	for (TInt i = 0 ; i < size / 4 ; i+=2)
		{
		test_equal(c, data[i]);
		test_equal(d, data[i+1]);
		}

	// close library...
	test.Next(_L("Close DLL"));
	library.Close();
	User::After(1000000);

	if(!FlexibleMemoryModel) // flexible memory model doesn't actually hog virtual address when locked (pinned)
		{
		// load again and check it's at a different place
		// (because the locked memory is hogging the old place)...
		test.Next(_L("Check DLL loaded at different address"));
		test_noError(LoadSpecificLibrary(library, 5, CurrentDrive));
		func2 = (TGetAddressOfRelocatedDataFunction)library.Lookup(KGetAddressOfDataFunctionOrdinal);
		test(func!=func2);
		library.Close();
		User::After(1000000);

		// unlock pages...
		test.Next(_L("Unlock DLL data"));
		r = ldd.Unlock();
		User::After(1000000);

		// load again and check it's back at the original place
		// (because the locked memory now gone)...
		test.Next(_L("Check DLL loaded at original address"));
		test_noError(LoadSpecificLibrary(library, 5, CurrentDrive));
		func2 = (TGetAddressOfRelocatedDataFunction)library.Lookup(KGetAddressOfDataFunctionOrdinal);
		test(func==func2);
		library.Close();
		}

	// cleanup...
	test.Next(_L("Cleanup"));
	ldd.Close();

	test.End();
	}

void TestContentsOfPagedDll()
	{
	test.Next(_L("Test that the contents of a paged DLL are as expected"));

	RLibrary library2;
	test_noError(LoadSpecificLibrary(library2, 2, CurrentDrive));

	TGetAddressOfDataFunction func = (TGetAddressOfDataFunction)library2.Lookup(KGetAddressOfDataFunctionOrdinal);
	test_notNull(func);

	TInt size;
	TUint* data;
	data = func(size);
	test_notNull(data);

	// Data contents are psuedorandom numbers generated according to the following scheme
	const TInt A = 1664525;
	const TInt B = 1013904223;
	TUint v = 23;
	for (TInt i = 0 ; i < size / 4 ; ++i)
		{
		v = A * v + B;
		test_equal(v, data[i]);
		}

	library2.Close();
	}


void CheckRelocatableData(RLibrary& library)
	{
	TGetAddressOfRelocatedDataFunction func = (TGetAddressOfRelocatedDataFunction)library.Lookup(KGetAddressOfDataFunctionOrdinal);
	test_notNull(func);
	void* d;
	void* c;
	TInt size;
	void** data = func(size,d,c);
	test_equal(d, data);
	for (TInt i = 0 ; i < size / 4 ; i+=2)
		{
		test_equal(c, data[i]);
		test_equal(d, data[i+1]);
		}
	}


void OtherProcessCheckRelocatableData()
	{
	RLibrary library;
	test_noError(LoadSpecificLibrary(library, 7, CurrentDrive));
	CheckRelocatableData(library);
	library.Close();
	}


void TestContentsOfPagedDllWithRelocatedData()
	{
	test.Next(_L("Test relocated const data in DLL"));
	PagingInfo::ResetBenchmarks();
	RLibrary library;
	test_noError(LoadSpecificLibrary(library, 5, CurrentDrive));
	CheckRelocatableData(library);
	library.Close();
	PagingInfo::PrintBenchmarks();	// worst case fixups

	test.Next(_L("Test relocated writable data in DLL"));
	test_noError(LoadSpecificLibrary(library, 6, CurrentDrive));
	CheckRelocatableData(library);
	library.Close();

	test.Next(_L("Test relocated writable data in dependent DLL"));
	test_noError(LoadSpecificLibrary(library, 7, CurrentDrive));
	CheckRelocatableData(library);
	library.Close();

	test.Next(_L("Test relocated writable data in preloaded dependent DLL"));
	RLibrary library2;
	test_noError(LoadSpecificLibrary(library2, 6, CurrentDrive));
	test_noError(LoadSpecificLibrary(library, 7, CurrentDrive));
	CheckRelocatableData(library);
	library.Close();
	library2.Close();

	test.Next(_L("Test relocated writable data in preloaded dependent DLL opened in other process"));
	test_noError(LoadSpecificLibrary(library2, 6, CurrentDrive));
	OtherProcess.Exec(OtherProcessCheckRelocatableData);
	library2.Close();
	}


TInt RandomAccessFunc(TAny* aArg)
	{
	const TUint8* dataStart = LargeDataStart;
	const TUint8* dataEnd = LargeDataEnd;	
	TInt size = dataEnd - dataStart;
	TUint32 random = (User::FastCounter() << 8) | ProcessNum;
	TInt i = 0;
	while (!RandomAccessKill)
		{
		random = random*69069+1;
		TInt offset = random % size;
		TInt value = dataStart[offset];
		if (offset != 0 && value != 0)
			return KErrGeneral;
		++i;
		}

	RDebug::Printf("%d: Performed %d accesses", ProcessNum, i);
	return KErrNone;
	}

void StartRandomAccessThread(TThreadPriority aPriority)
	{
	RandomAccessKill = EFalse;
	test_noError(RandomAccessThread.Create(_L("RandomAccessThread"), RandomAccessFunc, 4096, NULL, 0));
	RDebug::Printf("%d: starting thread with priority %d", ProcessNum, aPriority);
	RandomAccessThread.SetPriority(aPriority);
	RandomAccessThread.Resume();
	}

void KillRandomAccessThread()
	{
	test_equal(EExitPending, RandomAccessThread.ExitType());
	TRequestStatus status;
	RandomAccessThread.Logon(status);
	RandomAccessKill = ETrue;
	User::WaitForRequest(status);
	test_equal(EExitKill, RandomAccessThread.ExitType());
	test_equal(0, RandomAccessThread.ExitReason());
	RandomAccessThread.Close();
	PagedLibrary.Close();
	}

void TestLargeDll(TInt aDelay)
	{
	test.Next(_L("Test random access to a large dll"));
	StartRandomAccessThread(EPriorityLess);
	User::After(aDelay * 1000000);
	KillRandomAccessThread();
	}

void TestKillThreadWhilePaging()
	{
 	test.Next(_L("Test killing a thread while it is taking paging faults"));
	for (TInt i = 0 ; i < 50 ; ++i)
		{
		RDebug::Printf("  iteration %d", i);
		StartRandomAccessThread(EPriorityLess);
		User::After(10000);  // time for ~ 10 paging requests
		test_equal(EExitPending, RandomAccessThread.ExitType());
		TRequestStatus status;
		RandomAccessThread.Logon(status);
		RandomAccessThread.Terminate(666);
		User::WaitForRequest(status);
		test_equal(EExitTerminate, RandomAccessThread.ExitType());
		test_equal(666, RandomAccessThread.ExitReason());
		RandomAccessThread.Close();
		PagedLibrary.Close();
		}
	}

void TestUnloadDllWhilePaging()
	{
 	test.Next(_L("Test unloading a library while another thread is accessing it"));
	OtherProcess.Exec(UnloadLargeLibrary);
	for (TInt i = 0 ; i < 50 ; ++i)
		{
		RDebug::Printf("  iteration %d", i);
		StartRandomAccessThread(EPriorityLess);
		User::After(10000);  // time for ~ 10 paging requests
		test_equal(EExitPending, RandomAccessThread.ExitType());
		TRequestStatus status;
		RandomAccessThread.Logon(status);
		UnloadLargeLibrary();
		PagedLibrary.Close();
		User::WaitForRequest(status);
		test_equal(EExitPanic, RandomAccessThread.ExitType());
		test_equal(3, RandomAccessThread.ExitReason());  // KERN-EXEC 3
		RandomAccessThread.Close();
		LoadLargeLibrary();
		}
	OtherProcess.Exec(LoadLargeLibrary);
	}

void PrintElapsedTime(TTime& aStartTime)
	{		
	TTime timeNow;
	timeNow.UniversalTime();
	TTimeIntervalSeconds elapsed;
	test_noError(timeNow.SecondsFrom(aStartTime, elapsed));
	test.Printf(_L("%d seconds elapsed\n"), elapsed.Int());
	}

void TestManyProcesses(TInt aCount, TInt aDelay, TInt aPriorities = 1)
	{
	TBuf<128> name;
	name.AppendFormat(_L("Test accessing paged code from %d processes at %d priority level(s) for %d seconds"),
					  aCount, aPriorities, aDelay);
	test.Next(name);

	TTime startTime;
	startTime.UniversalTime();

	// start subprocesses and let them initialise
	RArray<RTestSession> processes;
	TInt threadsAtEachPriority = aCount / aPriorities;
	for (TInt i = 0 ; i < aCount ; ++i)
		{
		RTestSession sess;
		StartOtherProcess(i + 3, sess);
		test_noError(processes.Append(sess));
		sess.SetCurrentDrive(CurrentDrive);
		}
	test.Printf(_L("Started subprocesses: "));
	PrintElapsedTime(startTime);
	
	// then start random accesses to paged memory
	for (TInt i = 0 ; i < aCount ; ++i)
		{
		TThreadPriority pri;
		switch (i / threadsAtEachPriority)
			{
			case 0:  pri = EPriorityLess; break;
			default: pri = EPriorityMuchLess; break;
			}
		processes[i].StartRandomAccessThread(pri);
		}
	test.Printf(_L("Started threads: "));
	PrintElapsedTime(startTime);

	test_noError(PagingInfo::ResetAll(LocalDriveNumber,EMediaPagingStatsCode));
	User::After(aDelay * 1000000);
	test_noError(PagingInfo::PrintAll(LocalDriveNumber,EMediaPagingStatsCode));

	test.Printf(_L("Killing subprocesses: "));
	PrintElapsedTime(startTime);
		
	for (TInt i = 0 ; i < aCount ; ++i)
		{
		processes[i].Exec(KillRandomAccessThread);
		processes[i].Kill();
		processes[i].Close();
		}

	test.Printf(_L("Test finished: "));
	PrintElapsedTime(startTime);

	processes.Close();
	}

void TestCacheSize()
	{
	test.Next(_L("Test cache size within bounds"));
	TUint sizeMin = 0;
	TUint sizeMax = 0;
	TUint currentSize = 0;
	DPTest::CacheSize(sizeMin,sizeMax,currentSize);
	test.Printf(_L("  minimum size == %d pages\n"), sizeMin >> 12);
	test.Printf(_L("  maximum size == %d pages\n"), sizeMax >> 12);
	test.Printf(_L("  current size == %d pages\n"), currentSize >> 12);
	test(currentSize >= sizeMin);
	test(currentSize <= sizeMax);
	}

void RunUnalignedAliasAccessTest()
	{
	test.Next(_L("Test accesses to aliased non-word-aligned data"));
	
	for (TInt size = 0 ; size <= 28 ; ++ size)
		{
		test.Printf(_L("  size = %d:"), size);
		for (TInt align = 0 ; align <= 3 ; ++align)
			{
			test.Printf(_L(" %d"), align);
			TPtrC8 des(LargeDataStart + align, size);
			FlushAllPages();
			OtherProcess.DesRead(des);			
			}
		test.Printf(_L("\n"));
		}
	}

void TestCodeChunkCreated()
	{
	LoadLibrary();
	TAny* func = (TAny*)PagedLibrary.Lookup(KTestFunctionOrdinal);
	test_notNull(func);
	FlushAllPages();
	test(GetPageState(func) & EPageStateCodeChunkPresent);
	UnloadLibrary();
	FlushAllPages();
	test(!(GetPageState(func) & EPageStateCodeChunkPresent));
	}

void TestRepeatedLoading()
	{
	test.Next(_L("Test loading/unloading a DLL doesn't leak address space"));

	for (TInt dll = 1 ; dll <= 7 ; ++dll)
		{
		test.Printf(_L("  trying dll %d...\n"), dll);
		
		RLibrary library;
		test_noError(LoadSpecificLibrary(library, dll, CurrentDrive));
		TLibraryFunction func1 = library.Lookup(1);
		library.Close();
		
		test_noError(LoadSpecificLibrary(library, dll, CurrentDrive));
		TLibraryFunction func2 = library.Lookup(1);
		library.Close();

		test_equal(func1, func2);
		}
	}

void RunDriveIndependantTests()
	{
	if (MultipleMemoryModel)
		{
		test.Next(_L("Test code chunk created and destroyed correctly"));
		TestCodeChunkCreated();
		}
	
	SetCurrentDrive('Z');

	if (CanForcePageOut)
		{
		test.Next(_L("Test accessing pages by executing code"));
		AccessMethod = EAccessExec;
		RunPathTest(*TestPath);

		test.Next(_L("Test accessing pages by reading code"));
		AccessMethod = EAccessRead;
		RunPathTest(*TestPath);

		if (!MovingMemoryModel)
			{
			test.Next(_L("Test accessing pages by reading code from another process via an alias"));
			AccessMethod = EAccessAliasRead;
			RunPathTest(*TestPath);
			}

		test.Next(_L("Test unmapping paged code"));
		AccessMethod = EAccessExec;
		RunUnmapTest(*TestPath);

		if (!MovingMemoryModel)
			{
			test.Next(_L("Test interactions between two processes"));
			RunMultiProcessTest();
			}
		}

	RunReadExportDirTest();
	RunPageLockingTest();
	RunWriteToPagedCodeTest();
	RunUnalignedAliasAccessTest();
	TestRepeatedLoading();
	}

void RunPerDriveTests()
	{
	TestContentsOfPagedDll();
	TestContentsOfPagedDllWithRelocatedData();
	TestKillThreadWhilePaging();
	TestUnloadDllWhilePaging();
	
	TestLargeDll(5);

	TestManyProcesses(2, 5, 1);
	TestManyProcesses(5, 10, 1);
	TestManyProcesses(10, 20, 1);
	TestManyProcesses(5, 10, 2);
	TestManyProcesses(50, 2, 1);
	}

void RunAllTests()
	{

	RunDriveIndependantTests();
	
	for (TInt i = 0 ; i < SupportedDrives.Count() ; ++i)
		{
		SetCurrentDrive(SupportedDrives[i].iDriveLetter);
		OtherProcess.SetCurrentDrive(CurrentDrive);

		TBuf<32> message;
		message.AppendFormat(_L("Running tests on drive %c:"), (TUint) SupportedDrives[i].iDriveLetter);
		test.Next(message);		
		RunPerDriveTests();
		}
	TestCacheSize();
	}

// Server implementation ///////////////////////////////////////////////////////

class CTestSession : public CSession2
	{
public:
	virtual void ServiceL(const RMessage2& aMessage);
	};

void CTestSession::ServiceL(const RMessage2& aMessage)
	{
	TInt r = KErrNone;
	switch (aMessage.Function())
		{
		case RTestSession::EKill:
			CActiveScheduler::Stop();
			break;

		case RTestSession::EExec:
			((TFunc)aMessage.Int0())();		   
			break;

		case RTestSession::ESetCurrentDrive:
			SetCurrentDrive(aMessage.Int0());
			break;

		case RTestSession::EDesRead:
			{
			TBuf8<32> buf;
			if (buf.MaxSize() < aMessage.GetDesLength(0))
				r = KErrArgument;
			else
				r = aMessage.Read(0, buf);
			}
			break;

		case RTestSession::ETestPageState:
			TestPageState((TPageState)aMessage.Int0(), (TPhysState)aMessage.Int1());
			break;
			
		case RTestSession::ETestStateTransition:
			TestStateTransition((TPageState)aMessage.Int0());
			break;

		case RTestSession::EStartRandomAccessThread:
			StartRandomAccessThread((TThreadPriority)aMessage.Int0());
			break;

		default:
			r = KErrNotSupported;
			break;
		}
	
	aMessage.Complete(r);
	}

class CTestServer : public CServer2
	{
public:
	CTestServer() : CServer2(0) { }
	virtual CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const;
 };

CSession2* CTestServer::NewSessionL(const TVersion& /*aVersion*/,const RMessage2& /*aMessage*/) const
	{
	return new (ELeave) CTestSession();
	}

void DoStartServerL()
	{
	CActiveScheduler* activeScheduler = new CActiveScheduler;
	test_notNull(activeScheduler);
	CActiveScheduler::Install(activeScheduler);
	CTestServer* server = new CTestServer();
	test_notNull(server);
	TBuf<32> name;
	name.AppendFormat(_L("%S-%d"), &KServerName, ProcessNum);
	test_noError(server->Start(name));
	RProcess().Rendezvous(KErrNone);
	CActiveScheduler::Start();
	delete server;
	delete activeScheduler;
	}

void StartServer()
	{
	CTrapCleanup* cleanupStack = CTrapCleanup::New();
	test_notNull(cleanupStack);
	TRAPD(leaveError,DoStartServerL());	
	test_noError(leaveError);
	delete cleanupStack;
	}

void SecondaryProcess()
	{
	TBuf<16> cmd;
	User::CommandLine(cmd);
	TLex lex(cmd);
	lex.Val(ProcessNum);

	TBuf<32> name;
	name.AppendFormat(_L("t_codepaging-%d"), ProcessNum);
	RProcess me;
	test_noError(me.RenameMe(name));
	
	GetSupportedDrives(EFalse);
	Initialise();
	SetCurrentDrive('Z');
	StartServer();
	}

void MainProcess()
	{
	ProcessNum = 1;
		
	test.Title();
	test.Start(_L("Code paging tests"));
	
	TUint32 memModelAttributes=UserSvr::HalFunction(EHalGroupKernel, EKernelHalMemModelInfo, NULL, NULL);
	TUint32 pagingPolicy = E32Loader::PagingPolicy();
	TBool codePagingSupported = (memModelAttributes & EMemModelAttrCodePaging) != 0;
	TBool pagingPolicyAllowsPaging = pagingPolicy != EKernelConfigCodePagingPolicyNoPaging;
	test_Equal(codePagingSupported, pagingPolicyAllowsPaging);
	if(!codePagingSupported)
		{
		test.Printf(_L("TESTS NOT RUN - Code paging not enabled on system.\n"));
		test.End();
		return;
		}
	
	GetSupportedDrives(ETrue);
	test(SupportedDrives.Count() > 0);

	// Turn off evil lazy dll unloading
	RLoader l;
	test(l.Connect()==KErrNone);
	test(l.CancelLazyDllUnload()==KErrNone);
	l.Close();

	CopyDllsToSupportedDrives();

	Initialise();

	StartOtherProcess(2, OtherProcess);

	RunAllTests();

	OtherProcess.Kill();
	OtherProcess.Close();
	test.End();
	}


TInt E32Main()
	{
	if (User::CommandLineLength() == 0)
		MainProcess();
	else
		SecondaryProcess();
	
	return 0;
	}
