persistentstorage/sql/TEST/t_sqlload.cpp
changeset 0 08ec8eefde2f
child 12 6b6fd149daa2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/sql/TEST/t_sqlload.cpp	Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,563 @@
+// 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 "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:
+//
+
+#include <e32test.h>
+#include <e32math.h>
+#include <bautils.h>
+#include <sqldb.h>
+#include "SqlResourceTester.h"
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+#define UNUSED_VAR(a) (a) = (a)
+
+RTest TheTest(_L("t_sqlload test"));
+
+_LIT(KTestDir, "c:\\test\\");
+_LIT(KTestDbName1, "c:\\test\\t_sqlload_1.db");
+_LIT(KTestDbName2, "c:\\test\\t_sqlload_2.db");
+_LIT(KTestDbName3, "c:\\test\\t_sqlload_3.db");
+
+//Test thread count
+const TInt KTestThreadCnt = 4;
+
+//Test database names
+const TPtrC KTestDbNames[] =
+	{
+	KTestDbName1(),
+	KTestDbName2(),
+	KTestDbName3()
+	};
+
+//Test database count
+const TInt KTestDbCnt = sizeof(KTestDbNames) / sizeof(KTestDbNames[0]);
+
+//Test duration
+const TInt KTestDuration = 120;//seconds
+
+//Test record count
+const TInt KRecordCnt = 100;
+//Record count which will be used in the test SQL queries
+const TInt KQueriedRecordCnt = 40;
+//Every SQL query will be processed (stepped) in KTestStepCnt steps.
+const TInt KTestStepCnt = 4;
+//RSqlStatement object count which will be used in the tests
+const TInt KStatementCnt = 10;
+//Max allowed alive RSqlStatement objects per thread
+const TInt KMaxStatementPerThread = 30;
+//Binary data length
+const TInt KBinDataLen = 2003;
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void DeleteTestFiles()
+	{
+	RSqlDatabase::Delete(KTestDbName3);
+	RSqlDatabase::Delete(KTestDbName2);
+	RSqlDatabase::Delete(KTestDbName1);
+	}
+
+///////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////
+//Test macros and functions
+void Check1(TInt aValue, TInt aLine, TBool aPrintThreadName = EFalse)
+	{
+	if(!aValue)
+		{
+		DeleteTestFiles();
+		if(aPrintThreadName)
+			{
+			RThread th;
+			TName name = th.Name();
+			RDebug::Print(_L("*** Thread %S, Line %d\r\n"), &name, aLine);
+			}
+		else
+			{
+			RDebug::Print(_L("*** Line %d\r\n"), aLine);
+			}
+		TheTest(EFalse, aLine);
+		}
+	}
+void Check2(TInt aValue, TInt aExpected, TInt aLine, TBool aPrintThreadName = EFalse)
+	{
+	if(aValue != aExpected)
+		{
+		DeleteTestFiles();
+		if(aPrintThreadName)
+			{
+			RThread th;
+			TName name = th.Name();
+			RDebug::Print(_L("*** Thread %S, Line %d Expected error: %d, got: %d\r\n"), &name, aLine, aExpected, aValue);
+			}
+		else
+			{
+			RDebug::Print(_L("*** Line %d, Expected error: %d, got: %d\r\n"), aLine, aExpected, aValue);
+			}
+		TheTest(EFalse, aLine);
+		}
+	}
+#define TEST(arg) ::Check1((arg), __LINE__)
+#define TEST2(aValue, aExpected) ::Check2(aValue, aExpected, __LINE__)
+#define TTEST(arg) ::Check1((arg), __LINE__, ETrue)
+#define TTEST2(aValue, aExpected) ::Check2(aValue, aExpected, __LINE__, ETrue)
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void CreateTestDir()
+    {
+    RFs fs;
+	TInt err = fs.Connect();
+	TEST2(err, KErrNone);
+
+	err = fs.MkDir(KTestDir);
+	TEST(err == KErrNone || err == KErrAlreadyExists);
+
+	fs.Close();
+	}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+void CreateTestDatabases()
+	{
+	HBufC8* recData = HBufC8::New(KBinDataLen * 2 + 50);//"* 2" - hex values for the INSERT SQL statement
+	TEST(recData != NULL);
+	TPtr8 sql = recData->Des();
+
+	for(TInt dbIdx=0;dbIdx<KTestDbCnt;++dbIdx)
+		{
+		//Create test database
+		RSqlDatabase db;
+		TInt err = db.Create(KTestDbNames[dbIdx]);
+		TEST2(err, KErrNone);
+
+		//Create test table
+		_LIT8(KCreateSql, "CREATE TABLE A(F1 INTEGER, F2 BLOB)");
+		err = db.Exec(KCreateSql);
+		TEST(err >= 0);
+
+		//Insert records in the test table
+		for(TInt recIdx=1;recIdx<=KRecordCnt;++recIdx)
+			{
+			_LIT8(KInsertSql, "INSERT INTO A(F1, F2) VALUES(");
+			sql.Copy(KInsertSql);
+			sql.AppendNum((TInt64)recIdx);
+			sql.Append(_L(", X'"));
+			for(TInt k=0;k<KBinDataLen;++k)
+				{
+				sql.AppendFormat(_L8("%02X"), recIdx);
+				}
+			sql.Append(_L("')"));
+			err = db.Exec(sql);
+			TEST2(err, 1);
+			}
+
+		db.Close();
+		}
+
+	delete recData;
+	}
+
+//Structure used by the test thread function for orginizing its set of test data.
+struct TSqlStatement
+	{
+	RSqlStatement	iObj;
+	TBool 			iAlive;			//Non-zero if iObj is alive
+	TInt			iCurIndex;		//The number of the current record in the set controlled by iObj statement
+	TInt			iEndIndex;		//The last record number in the set controlled by iObj statement
+	TInt			iCount;			//Records count in the set controlled by iObj statement
+	};
+	
+typedef RArray<TSqlStatement> RSqlStatementArray;	
+
+//Inits the random numbers generator.
+//Opens one of the test databases.
+void PreTest(RSqlDatabase& aDb, TInt64& aSeed, TName& aThreadName)
+	{
+	RThread currThread;
+	
+	//Init the random numbers generator
+	TTime now;
+	now.UniversalTime();
+	aSeed = now.Int64() + currThread.Id();
+
+	//Open one of the test databases
+	const TInt KDbIndex = Math::Rand(aSeed) % KTestDbCnt;
+
+	aThreadName = currThread.Name();
+	RDebug::Print(_L("=== Thread %S, database %S\r\n"), &aThreadName, &KTestDbNames[KDbIndex]);
+
+	TInt err = aDb.Open(KTestDbNames[KDbIndex]);
+	TTEST2(err, KErrNone);
+	}
+
+//Creates N statements, where 0 < N < KStatementCnt
+TInt CreateStatements(RSqlDatabase& aDb, TInt64& aSeed, RSqlStatementArray& aStmtArray)
+	{
+	TInt stmtCount = Math::Rand(aSeed) % KStatementCnt;
+	if(stmtCount == 0)
+		{
+		stmtCount = 1;
+		}
+	for(TInt i=0;i<stmtCount;++i)
+		{
+		TSqlStatement stmt;
+		stmt.iAlive = EFalse;
+		stmt.iCount = KQueriedRecordCnt;
+		stmt.iCurIndex = Math::Rand(aSeed) % KRecordCnt;
+		if(stmt.iCurIndex == 0)
+			{
+			stmt.iCurIndex = 1;
+			}
+		if(stmt.iCurIndex > (KRecordCnt - KQueriedRecordCnt))
+			{
+			stmt.iCurIndex = KRecordCnt - KQueriedRecordCnt;
+			}
+		stmt.iEndIndex = stmt.iCurIndex + KQueriedRecordCnt;
+		TBuf8<100> sql;
+		sql.Copy(_L8("SELECT * FROM A WHERE F1 >= "));
+		sql.AppendNum(stmt.iCurIndex);
+		sql.Append(_L8(" AND F1 < "));
+		sql.AppendNum(stmt.iEndIndex);
+		TInt err = stmt.iObj.Prepare(aDb, sql);
+		TTEST2(err, KErrNone);
+		stmt.iAlive = ETrue;
+		err = aStmtArray.Append(stmt);
+		TTEST2(err, KErrNone);
+		}
+	return stmtCount;		
+	}
+	
+//For each alive statement object - do (TSqlStatement::iCount / KTestStepCnt)
+//RSqlStatement::Next() calls. If the Next() call reaches the end - close the statement object.
+TInt ProcessStatements(RSqlStatementArray& aStmtArray)
+	{
+	const TInt KTotalStmtCount = aStmtArray.Count();
+	TInt alive = 0;
+	TInt completed = 0;
+	for(TInt k=0;k<KTotalStmtCount;++k)
+		{
+		TSqlStatement& stmt = aStmtArray[k];
+		if(stmt.iAlive)
+			{
+			++alive;
+			TInt endIndex = stmt.iCurIndex + stmt.iCount / KTestStepCnt;
+			if(endIndex <= stmt.iEndIndex)
+				{
+				while(stmt.iCurIndex < endIndex)
+					{
+					TInt err = stmt.iObj.Next();
+					TTEST2(err, KSqlAtRow);
+					//test column values
+					TInt val1 = stmt.iObj.ColumnInt(0);
+					TTEST(val1 == stmt.iCurIndex);
+					RSqlColumnReadStream strm;
+					err = strm.ColumnBinary(stmt.iObj, 1);
+					TTEST2(err, KErrNone);
+					for(TInt ii=0;ii<KBinDataLen;++ii)
+						{
+						TUint8 byte = 0;
+						TRAP(err, byte = strm.ReadUint8L());
+						TTEST2(err, KErrNone);
+						TTEST(byte == (TUint8)val1);
+						}
+					strm.Close();
+					++stmt.iCurIndex;
+					}
+				}
+			if(stmt.iCurIndex >= stmt.iEndIndex)
+				{
+				stmt.iObj.Close();
+				stmt.iAlive = EFalse;
+				++completed;
+				}
+			}
+		}
+	return completed;
+	}
+
+//Close up to N statements, where 0 < N < KStatementCnt
+TInt CloseStatements(RSqlStatementArray& aStmtArray, TInt64& aSeed)
+	{
+	TInt stmtCount = Math::Rand(aSeed) % KStatementCnt;
+	if(stmtCount == 0)
+		{
+		stmtCount = 1;
+		}
+	const TInt KTotalStmtCount = aStmtArray.Count();
+	TInt closed = 0;
+	for(TInt j=0;j<stmtCount;++j)
+		{
+		const TInt KIdx = Math::Rand(aSeed) % KTotalStmtCount;
+		TInt idx = KIdx;
+		while((idx = (++idx % KTotalStmtCount)) != KIdx)
+			{
+			if(aStmtArray[idx].iAlive)
+				{
+				aStmtArray[idx].iObj.Close();
+				aStmtArray[idx].iAlive = EFalse;
+				++closed;
+				break;
+				}
+			}
+		}
+	return closed;
+	}
+
+//Counts the alive statements
+TInt AliveStatementsCount(RSqlStatementArray& aStmtArray)
+	{
+	TInt aliveCnt = 0;
+	const TInt KTotalStmtCount = aStmtArray.Count();
+	for(TInt l=0;l<KTotalStmtCount;++l)
+		{
+		if(aStmtArray[l].iAlive)
+			{
+			++aliveCnt;
+			}
+		}
+	return aliveCnt;
+	}
+	
+//Close all alive statements
+void CloseAllStatements(RSqlStatementArray& aStmtArray)
+	{
+	const TInt KTotalStmtCount = aStmtArray.Count();
+	for(TInt i=0;i<KTotalStmtCount;++i)
+		{
+		if(aStmtArray[i].iAlive)
+			{
+			aStmtArray[i].iObj.Close();
+			}
+		}
+	TTEST2(TSqlResourceTester::Count(), 0);
+	}
+
+//Removes the already closed statements and compresses the array
+void RemoveDeadStatements(RSqlStatementArray& aStmtArray)
+	{
+	for(TInt i=aStmtArray.Count()-1;i>=0;--i)
+		{
+		if(!aStmtArray[i].iAlive)
+			{
+			aStmtArray.Remove(i);
+			}
+		}
+	aStmtArray.Compress();		
+	}
+
+//Close statement objects, statements array and the database object
+TInt PostTest(RSqlDatabase& aDb, RSqlStatementArray& aStmtArray)
+	{
+	TInt statementsAlive = AliveStatementsCount(aStmtArray);
+	CloseAllStatements(aStmtArray);
+	aStmtArray.Close();
+	aDb.Close();
+	return statementsAlive;
+	}
+
+//Test thread function
+//The thread function works with a set of TSqlStatement objects
+//The test consists of 4 steps:
+//Step 1: the test thread creates m TSqlStatement objects, 0 < m < KStatementCnt.
+//		  With each of the created TSqlStatement objects the test thread prepares SELECT SQL query
+//        "SELECT * FROM A WHERE F1 >= K1 AND F1 < K2", where K1 is random generated number, such that:
+//        0 < K1 < (KRecordCnt - KQueriedRecordCnt)
+//        K2 = K1 + KQueriedRecordCnt
+//		  All just created TSqlStatement objects are marked as alive.
+//Step 2: For each alive TSqlStatement object the test thread calls iObj.Next() method KTestStepCnt times,
+//        KTestStepCnt < KQueriedRecordCnt.
+//        The column values are retrieved and checked.
+//Step 3: the test thread closes n TSqlStatement objects, 0 < n < KStatementCnt.
+//Step 4: the test thread counts how many alive TSqlStatement objects are there.
+//        If this count > KMaxStatementPerThread then the test thread closes all alive TSqlStatement objects
+//		  to avoid OOM errors during the test.
+//
+//		  Each test thread does steps 1..4 for a period of KTestDuration seconds.
+//		  At the end all TSqlStatement objects are closed.
+//
+//		  The idea of the test is to load the SQL server creating several amount of statement and stream objects
+//		  and see that it is working stable and without problems.
+TInt ThreadFunc(void*)
+	{
+	__UHEAP_MARK;
+
+	CTrapCleanup* tc = CTrapCleanup::New();
+	TTEST(tc != NULL);
+
+	TInt64 seed = 0;
+	RSqlDatabase db;
+	TName threadName;
+	RSqlStatementArray statements;
+	
+	//Init the random numbers generator, opens the database
+	PreTest(db, seed, threadName);
+
+	//Main test loop
+	TInt iteration = 0;
+	TTime currTime;
+	currTime.UniversalTime();
+	TTime endTime = currTime + TTimeIntervalSeconds(KTestDuration);
+	while(currTime < endTime)
+		{
+		++iteration;
+		///////////////////////////////////////////////////////////////////////
+		TInt statementsAliveBegin = statements.Count();
+		//Step 1: Create N statements, where 0 < N < KStatementCnt
+		TInt statementsCreated = CreateStatements(db, seed, statements);
+		///////////////////////////////////////////////////////////////////////
+		//Step 2: For each alive statement object - do (TSqlStatement::iCount / KTestStepCnt)
+		//        RSqlStatement::Next() calls. If the Next() call reaches the end - close the statement object.
+		TInt statementsCompleted = ProcessStatements(statements);
+		///////////////////////////////////////////////////////////////////////
+		//Step 3: Close up to N statements, where 0 < N < KStatementCnt
+		TInt statementsClosed = CloseStatements(statements, seed);
+		///////////////////////////////////////////////////////////////////////
+		//Step 4: If the alive statement count is more than KMaxStatementPerThread, then close them all
+		TInt statementsAliveEnd = AliveStatementsCount(statements);
+		if(statementsAliveEnd > KMaxStatementPerThread)
+			{
+			RDebug::Print(_L("!!! Thread %S, iteration %d, alive %d, close all\r\n"), &threadName, iteration, statementsAliveEnd);
+			CloseAllStatements(statements);
+			statementsAliveEnd = 0;
+			}
+		///////////////////////////////////////////////////////////////////////
+		RemoveDeadStatements(statements);
+		RDebug::Print(_L("=== Thread %S, iteration % 4d, begin: % 3d, created % 2d, closed % 2d, completed % 2d, end % 3d, \r\n"),
+							&threadName, iteration, statementsAliveBegin, 
+													statementsCreated, statementsClosed, statementsCompleted, 
+													statementsAliveEnd);
+		currTime.UniversalTime();
+		}
+
+	//Close statement objects and the database object
+	TInt statementsAlive = PostTest(db, statements);
+
+	delete tc;
+
+	__UHEAP_MARKEND;
+
+	RDebug::Print(_L("=== Thread %S exit, still alive %d\r\n"), &threadName, statementsAlive);
+
+	return KErrNone;
+	}
+
+void CreateTestThreads(RThread aThreads[], TRequestStatus aStatuses[], TInt aMaxCount)
+	{
+	_LIT(KThreadName, "TstThr");
+	for(TInt i=0;i<aMaxCount;++i)
+		{
+		TBuf<20> threadName(KThreadName);
+		threadName.AppendNum((TInt64)(i + 1));
+		TEST2(aThreads[i].Create(threadName, &ThreadFunc, 0x2000, 0x1000, 0x10000, NULL, EOwnerProcess), KErrNone);
+		aThreads[i].Logon(aStatuses[i]);
+		TEST2(aStatuses[i].Int(), KRequestPending);
+		}
+	}
+
+void ResumeTestThreads(RThread aThreads[], TInt aMaxCount)
+	{
+	for(TInt i=0;i<aMaxCount;++i)
+		{
+		aThreads[i].Resume();
+		}
+	}
+
+
+void CloseTestThreads(RThread aThreads[], TRequestStatus aStatuses[], TInt aMaxCount)
+	{
+	for(TInt i=0;i<aMaxCount;++i)
+		{
+		User::WaitForRequest(aStatuses[i]);
+		TEST(aThreads[i].ExitType() != EExitPanic);
+		aThreads[i].Close();
+		}
+	}
+
+/**
+@SYMTestCaseID			SYSLIB-SQL-CT-1627-0001
+@SYMTestCaseDesc		SQL server load test. The test creates KTestThreadCnt threads, KTestDbCnt test databases and
+						inserts in each of them KRecordCnt test records.
+						Pre-test step: each test thread randomly chooses and opens one of the test databases.
+						Then, each of the test threads is doing the following 4 test steps:
+						Step 1: the test thread creates m TSqlStatement objects, 0 < m < KStatementCnt.
+						With each of the created TSqlStatement objects the test thread prepares SELECT SQL query
+						"SELECT * FROM A WHERE F1 >= K1 AND F1 < K2", where K1 is random generated number, such that:
+						0 < K1 < (KRecordCnt - KQueriedRecordCnt)
+						K2 = K1 + KQueriedRecordCnt
+						All just created TSqlStatement objects are marked as alive.
+						Step 2: For each alive TSqlStatement object the test thread calls iObj.Next() method KTestStepCnt times,
+						KTestStepCnt < KQueriedRecordCnt.
+						The column values are retrieved and checked.
+						Step 3: the test thread closes n TSqlStatement objects, 0 < n < KStatementCnt.
+						Step 4: the test thread counts how many alive TSqlStatement objects are there.
+						If this count > KMaxStatementPerThread then the test thread closes all alive TSqlStatement objects
+						to avoid OOM errors during the test.
+
+						Each test thread does steps 1..4 for a period of KTestDuration seconds.
+						At the end all TSqlStatement objects are closed.
+
+						The idea of the test is to load the SQL server creating several amount of statement and stream objects
+						and see that it is working stable and without problems.
+@SYMTestPriority		High
+@SYMTestActions			SQL server load test
+@SYMTestExpectedResults Test must not fail
+@SYMREQ					REQ5792
+                        REQ5793
+*/
+void SqlLoadTest()
+	{
+	CreateTestDatabases();
+
+	RThread threads[KTestThreadCnt];
+	TRequestStatus statuses[KTestThreadCnt];
+
+	CreateTestThreads(threads, statuses, KTestThreadCnt);
+
+	ResumeTestThreads(threads, KTestThreadCnt);
+
+	User::After(2000000);
+
+	CloseTestThreads(threads, statuses, KTestThreadCnt);
+	}
+
+void DoTests()
+	{
+	TheTest.Start(_L(" @SYMTestCaseID:SYSLIB-SQL-CT-1627-0001 SQL server load test "));
+	SqlLoadTest();
+	}
+
+TInt E32Main()
+	{
+	TheTest.Title();
+
+	CTrapCleanup* tc = CTrapCleanup::New();
+
+	__UHEAP_MARK;
+
+	CreateTestDir();
+	DeleteTestFiles();
+	DoTests();
+	DeleteTestFiles();
+
+	__UHEAP_MARKEND;
+
+	TheTest.End();
+	TheTest.Close();
+
+	delete tc;
+
+	User::Heap().Check();
+	return KErrNone;
+	}