os/ossrv/genericservices/taskscheduler/Test/Robustness/TC_TSCH_ROBUSTNESS.cpp
author sl
Tue, 10 Jun 2014 14:32:02 +0200
changeset 1 260cb5ec6c19
permissions -rw-r--r--
Update contrib.
     1 // Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
     2 // All rights reserved.
     3 // This component and the accompanying materials are made available
     4 // under the terms of "Eclipse Public License v1.0"
     5 // which accompanies this distribution, and is available
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
     7 //
     8 // Initial Contributors:
     9 // Nokia Corporation - initial contribution.
    10 //
    11 // Contributors:
    12 //
    13 // Description:
    14 //
    15 
    16 #include <csch_cli.h>
    17 #include "Thelpers.h"
    18 
    19 #include <e32base.h>
    20 #include <e32test.h>
    21 #include <f32file.h>
    22 #include <s32file.h>
    23 #include <e32property.h>
    24 #include <schinfointernal.h>
    25 #include "TestUtils.h"
    26 
    27 _LIT(KTestName,	"Task Scheduler Robustness Test");
    28 _LIT(KTaskScheduler, "schexe");
    29 
    30 RTest	TheTest(KTestName);
    31 
    32 typedef CArrayFixFlat<TTaskInfo>			CTaskInfoArray;
    33 typedef CArrayFixFlat<TSchedulerItemRef>    CSchItemRefArray;
    34 typedef CArrayFixFlat<TTaskSchedulerCondition>	CSchConditionArray;
    35 
    36 static RScheduler	TheScheduler;
    37 static CTrapCleanup*	TheCleanup;
    38 static RFs			TheFsSession;
    39 
    40 const TInt KTestKey1 = 1;
    41 
    42 _LIT(KSeparator, "|"); // Invalid filepath char used to separate filenames
    43 
    44 // This function launches the TPropertyDefine process which
    45 //	has WriteDeviceData Capabilities enabling it to create the P&S 
    46 //	variables used by this test.
    47 static void LaunchHelperL(TUid aCategory, TInt aKey, TInt aAttr)
    48 	{
    49 	_LIT(KConditionHelper, "TPropertyDefine");
    50 	TRequestStatus stat;
    51 	RProcess p;
    52 	
    53 	TBuf<32> args;
    54 	args.AppendNum(aCategory.iUid);
    55 	args.Append(KSeparator);
    56 	args.AppendNum(aKey);
    57 	args.Append(KSeparator);
    58 	args.AppendNum(aAttr);
    59 	
    60 	User::LeaveIfError(p.Create(KConditionHelper, args,EOwnerProcess));
    61 	
    62 	// Asynchronous logon: completes when process terminates with process exit code
    63 	p.Logon(stat);
    64 	p.Resume();
    65 
    66 	User::WaitForRequest(stat);
    67 	TInt exitReason = p.ExitReason();
    68 	p.Close();
    69 	User::LeaveIfError(exitReason);
    70 	}
    71 	
    72 	
    73 static void CreateTestVariables()
    74 	{
    75 	LaunchHelperL(KUidSystemCategory, KTestKey1,RProperty::EInt);
    76 	}	
    77 
    78 static void ResetVariablesL(TInt aKey1Val)
    79 	{
    80 	User::LeaveIfError(RProperty::Set(KUidSystemCategory, KTestKey1,aKey1Val));		
    81 	}
    82 	
    83 	
    84 // single condition with default time set to 1 year in the future
    85 static TInt CreateScheduleL(TSchedulerItemRef& aRef, 
    86 									RScheduler& aScheduler,
    87 									const TUid& aConditionUID,
    88 									TUint aConditionUInt)
    89 	{
    90 	aRef.iName = _L("Schedule created using CreateScheduleSingle");
    91 
    92 	CSchConditionArray* conditionList = new (ELeave) CSchConditionArray(1);
    93 	CleanupStack::PushL(conditionList);
    94 	
    95 	//create a single condition
    96 	TTaskSchedulerCondition condition1;
    97 	condition1.iCategory = aConditionUID;
    98 	condition1.iKey		= aConditionUInt;
    99 	condition1.iState	= 10;
   100 	condition1.iType	= TTaskSchedulerCondition::EEquals;
   101 	
   102 	conditionList->AppendL(condition1);
   103 		
   104 	//create a persistent schedule
   105 	TTime time = SchSvrHelpers::TimeBasedOnOffset(0, 0, 0, 0, 0, 1); //1 year in the future
   106 	TInt res = aScheduler.CreatePersistentSchedule(aRef, *conditionList, time);
   107 	CleanupStack::PopAndDestroy(); // conditionList
   108 	return res;
   109 	}
   110 
   111 //Add a single task to a schedule	
   112 static TInt AddTaskToScheduleL(const TDesC& aName, 
   113 									TInt& aNewId, 
   114 									TInt aScheduleId, 
   115 									RScheduler& aScheduler)
   116 	{
   117 	TTaskInfo taskInfo;
   118 	taskInfo.iTaskId = aNewId;
   119 	taskInfo.iName = aName;
   120 	taskInfo.iPriority = 2;
   121 	taskInfo.iRepeat = 0;
   122 	HBufC* data = _L("the data").AllocLC();
   123 	TInt res = aScheduler.ScheduleTask(taskInfo, *data, aScheduleId);
   124 	aNewId = taskInfo.iTaskId;
   125 
   126 	CleanupStack::PopAndDestroy(); // data
   127 	return res;
   128 	}
   129 	
   130 	
   131 static TInt ScheduleTaskL()
   132 	{
   133 	//reset the p&s variables before creating the schedule
   134 	ResetVariablesL(0);
   135 	
   136 	//Create a schedule
   137 	TSchedulerItemRef ref1;
   138 	TheTest.Printf(_L("Create a schedule\n"));
   139 	TInt res = CreateScheduleL(ref1, TheScheduler, KUidSystemCategory, KTestKey1);
   140 	TEST2(res, KErrNone);
   141 	
   142 	//Add task to the schedule
   143 	TInt task1 = 0;
   144 	_LIT(KName1, "Test Task");
   145 	TheTest.Printf(_L("Schedule a task\n"));
   146 
   147 	res = AddTaskToScheduleL(KName1, task1, ref1.iHandle, TheScheduler);
   148 	TEST2(res, KErrNone);
   149 	 
   150 	return res;	
   151 	}
   152 	
   153 static void ExecuteTaskL()
   154 	{
   155 	TheTest.Printf(_L("Execute Task\n"));
   156 	//Set property causing schedule to be run
   157 	User::LeaveIfError(RProperty::Set(KUidSystemCategory, KTestKey1,10));
   158 	
   159 	//Pause to wait for the task to be executed
   160 	SchSvrHelpers::Pause(TheTest, 2);
   161 	
   162 	}
   163 
   164 static TInt ScheduleAndExecuteTaskL()
   165 	{
   166 	TInt res = ScheduleTaskL();
   167 
   168 	ExecuteTaskL();
   169 	 
   170 	return res;	
   171 	}
   172 
   173 LOCAL_C void AddTaskFunctionL()
   174 	{
   175 	RScheduler	localScheduler;
   176 	// Connect to the server
   177 	TInt res = localScheduler.Connect();
   178 	TEST2(res, KErrNone);
   179 	//Schedule a task and execute it
   180 	//reset the p&s variables before creating the schedule
   181 	ResetVariablesL(0);
   182 		
   183 	//Create a schedule
   184 	TSchedulerItemRef ref1;
   185 	ref1.iName = _L("Schedule created using CreateScheduleSingle");
   186 	CSchConditionArray* conditionList = new (ELeave) CSchConditionArray(1);
   187 	CleanupStack::PushL(conditionList);
   188 		
   189 	//create a single condition
   190 	TTaskSchedulerCondition condition1;
   191 	condition1.iCategory = KUidSystemCategory;
   192 	condition1.iKey		= KTestKey1;
   193 	condition1.iState	= 10;
   194 	condition1.iType	= TTaskSchedulerCondition::EEquals;
   195 			
   196 	conditionList->AppendL(condition1);
   197 				
   198 	//create a persistent schedule
   199 	TTime time = SchSvrHelpers::TimeBasedOnOffset(0, 0, 0, 0, 0, 1); //1 year in the future
   200 	res = localScheduler.CreatePersistentSchedule(ref1, *conditionList, time);
   201 	CleanupStack::PopAndDestroy(); // conditionList
   202 			
   203 	//Add task to the schedule
   204 	TInt task1 = 0;
   205 	_LIT(KName1, "Test Task");
   206 		
   207 	TTaskInfo taskInfo;
   208 	taskInfo.iTaskId = task1;
   209 	taskInfo.iName = KName1;
   210 	taskInfo.iPriority = 2;
   211 	taskInfo.iRepeat = 0;
   212 	HBufC* data = _L("the data").AllocLC();
   213 
   214 	TInt ret = localScheduler.ScheduleTask(taskInfo, *data, ref1.iHandle);
   215 	TEST2(ret, 0);//EPanicNotRegistered == 0
   216 		
   217 	task1 = taskInfo.iTaskId;
   218 
   219 	CleanupStack::PopAndDestroy(); // data
   220 		
   221 	//Tidying up so next test will be clear.
   222 	SchSvrHelpers::DeleteAllSchedulesL(localScheduler);
   223 
   224 	localScheduler.Close();
   225 	}
   226 
   227 // Helper function for DEF124488 
   228 LOCAL_C TInt TestPanicThread(TAny*)
   229 	{
   230 	CTrapCleanup* cleanup = CTrapCleanup::New();
   231 	if(!cleanup)
   232 		return KErrNoMemory;
   233 	
   234 
   235 	TRAPD(err,AddTaskFunctionL())
   236 	TEST2(err,KErrNone);
   237 	
   238 	delete cleanup;
   239 	return KErrNone;
   240 	}
   241 
   242 /**	
   243 @SYMTestCaseID 	SYSLIB-SCHSVR-CT-3369
   244 @SYMTestCaseDesc	Test deletion of temporary files with non existent client
   245 @SYMTestPriority	High
   246 @SYMTestActions 	Schedule a task with a client that does not exist.  
   247 					Ensure that all temporary files are deleted after 
   248 					schedule excecutes
   249 @SYMTestExpectedResults All temporary files should be deleted by task scheduler
   250 @SYMDEF 		 PDEF101876
   251 */
   252 static void DoTest1L()
   253 	{	
   254 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-3369 Test handling of non existent client "));	
   255 	// Connect to the server
   256 	TheTest.Next(_L("===== Connect to Scheduler ====="));
   257 	TInt res = TheScheduler.Connect();
   258 	TEST2(res, KErrNone);
   259 	
   260 	// Register a client with the server - this client does not exist
   261 	TheTest.Next(_L("===== Registering Client ====="));
   262 	res = SchSvrHelpers::RegisterNonExistentClient(TheScheduler);
   263 	TEST2(res, KErrNone);
   264 
   265 	//Schedule a task and execute it
   266 	ScheduleAndExecuteTaskL();
   267 	
   268 	// Check for left task files after scheduled tasks completed
   269 	// To access private data cage, uses SchSvrHelplers::CheckTaskFilesL()
   270 	TheTest.Next(_L("Now checking no files left when tasks completed"));
   271 	TInt err = SchSvrHelpers::CheckTaskFilesL();
   272 	
   273 	// If there's any task files left, test fails with error code KErrGeneral
   274 	TEST(err == KErrNone);
   275 	TheTest.Next(_L("All files deleted as expected..."));
   276 
   277 	//Tidying up so next test will be clear.
   278 	TheTest.Next(_L("Delete all schedules"));
   279 	SchSvrHelpers::DeleteAllSchedulesL(TheScheduler);
   280 
   281 	TheScheduler.Close();
   282 
   283 	}
   284 	
   285 /**
   286 @SYMTestCaseID 	SYSLIB-SCHSVR-CT-3370
   287 @SYMTestCaseDesc	Test deletion of temporary files with faulty client
   288 @SYMTestPriority	High
   289 @SYMTestActions 	Schedule a task with a client that panics and does not 
   290 					release the temporary file handle. 
   291 					Ensure that all temporary files are deleted after schedule excecutes
   292 @SYMTestExpectedResults All temporary files should be deleted by task scheduler
   293 @SYMDEF 		 PDEF101876
   294 */	
   295 static void DoTest2L()
   296 	{
   297 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-3370 Test handling of panicing client "));
   298 	
   299 	// Connect to the server
   300 	TheTest.Next(_L("===== Connect to Scheduler ====="));
   301 	TInt res = TheScheduler.Connect();
   302 	TEST2(res, KErrNone);
   303 	
   304 	// Register a client with the server - this client panics
   305 	//after calling RFile::AdoptFromClient
   306 	TheTest.Next(_L("===== Registering Client ====="));
   307 	res = SchSvrHelpers::RegisterPanicingClient(TheScheduler);
   308 	TEST2(res, KErrNone);
   309 		
   310 	//Schedule a task and execute it - we expect the client to panic
   311 	ScheduleAndExecuteTaskL();
   312 	
   313 	// Check for left task files after scheduled tasks completed
   314 	// To access private data cage, uses SchSvrHelplers::CheckTaskFilesL()
   315 	TheTest.Next(_L("Now checking no files left when tasks completed"));
   316 	TInt err = SchSvrHelpers::CheckTaskFilesL();
   317 
   318 	// If there's any task files left, test fails with error code KErrGeneral
   319 	TEST(err == KErrNone);	
   320 	
   321 	TheTest.Next(_L("All files deleted as expected..."));
   322 
   323 	//Tidying up so next test will be clear.
   324 	TheTest.Next(_L("Delete all schedules"));
   325 	SchSvrHelpers::DeleteAllSchedulesL(TheScheduler);
   326 
   327 	TheScheduler.Close();
   328 	}
   329 
   330 /**
   331 @SYMTestCaseID 	SYSLIB-SCHSVR-CT-3371
   332 @SYMTestCaseDesc	Test deletion of temporary files on task scheduler startup
   333 @SYMTestPriority	High
   334 @SYMTestActions 	Create temporary files in the task schedulers private data cage.
   335 					Start the task scheduler and verify that these files are deleted.
   336 @SYMTestExpectedResults All temporary files should be deleted by task scheduler on startup
   337 @SYMDEF 		 PDEF101876
   338 */	
   339 static void DoTest3L()
   340 	{	
   341 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-3371 Test deletion of temporary files on startup "));
   342 	
   343 	//Connect to the scheduler
   344 	TInt res = TheScheduler.Connect();
   345 	TEST2(res, KErrNone);
   346 
   347  	// Kill the server to ensure we restart it when we connect
   348 	res = CleanupHelpers::KillProcess(KTaskScheduler);
   349 	TEST2(res, KErrNone);
   350  	TheScheduler.Close();
   351  	
   352 	// Create task files to test cleanup
   353 	// To access private data cage, uses SchSvrHelplers::CreateTaskFilesL()
   354 	TheTest.Next(_L("Creating dummy task files"));
   355 	res = SchSvrHelpers::CreateTaskFilesL();
   356 
   357 	//Restart the scheduler which should clean up temp files on startup
   358 	TheTest.Next(_L("===== Connect to Scheduler ====="));
   359 	res = TheScheduler.Connect();
   360 	TEST2(res, KErrNone);
   361 	
   362 	//wait for the server to start up
   363 	SchSvrHelpers::Pause(TheTest, 2);
   364 		
   365 	TheScheduler.Close();
   366 	
   367 	// Check for left task files after scheduled tasks completed
   368 	// To access private data cage, uses SchSvrHelplers::CheckTaskFilesL()
   369 	TheTest.Next(_L("Now checking no files left after task scheduler starts"));
   370 	res = SchSvrHelpers::CheckTaskFilesL();
   371 	
   372 	TEST2(res, KErrNone);
   373 	
   374 	TheTest.Next(_L("All files deleted as expected..."));
   375 	}
   376 
   377 /**
   378 @SYMTestCaseID 		SYSLIB-SCHSVR-CT-3402
   379 @SYMTestCaseDesc	Test memory cleanup on Task Scheduler exit
   380 @SYMTestPriority	High
   381 @SYMTestActions 	Start the scheduler and register a client.
   382 					Execute a schedule and then terminate the scheduler.
   383 					When the scheduler is restarted it should exit as there are no
   384 					pending schedules. On exit all allocated memory should be freed	
   385 @SYMTestExpectedResults All allocated memory should be freed when the scheduler exits
   386 @SYMDEF 		 DEF102414
   387 */	
   388 static void DoTest4L()
   389 	{	
   390 	__UHEAP_MARK;
   391 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-3402 Test memory cleanup on Task Scheduler exit "));
   392 	
   393 	// Connect to the server
   394 	TheTest.Next(_L("===== Connect to Scheduler ====="));
   395 	TInt res = TheScheduler.Connect();
   396 	TEST2(res, KErrNone);
   397 	
   398 	// Register a client with the server
   399 	TheTest.Next(_L("===== Registering Client ====="));
   400 	res = SchSvrHelpers::RegisterClientL(TheScheduler);
   401 	TEST2(res, KErrNone);
   402 	
   403 	//Schedule a task and execute it
   404 	ScheduleAndExecuteTaskL();
   405 	
   406  	// Kill the server
   407 	res = CleanupHelpers::KillProcess(KTaskScheduler);
   408 	TEST2(res, KErrNone);
   409 	
   410  	TheScheduler.Close();
   411 	
   412 	//Restarting with a registered client and no schedule should
   413 	//cause the server to exit.  Ther server should free all allocated
   414 	//memory.  If all memory is not freed, heap check macros within
   415 	// the task scheduler code will cause a panic 
   416 	SchSvrHelpers::LaunchTaskSchedulerL();
   417 	
   418 	//wait for the server to exit
   419 	SchSvrHelpers::Pause(TheTest, 2);
   420 	
   421 	//Verify that the server has already exited - there are two valid 
   422 	//error codes depending on how quickly the process is cleaned up
   423 	//KErrDied - Process is dead but hasn't been cleaned up yet by the kernel
   424 	//KErrNotFound - Process has been cleaned up
   425 	res = CleanupHelpers::KillProcess(KTaskScheduler);
   426 	
   427 	TEST((res == KErrDied)||(res == KErrNotFound));
   428 	
   429 	__UHEAP_MARKEND;
   430 
   431 	TheTest.Next(_L("All memory freed..."));
   432 	}
   433 	
   434 	
   435 /**
   436 @SYMTestCaseID 		SYSLIB-SCHSVR-CT-3412
   437 @SYMTestCaseDesc	Test Task Scheduler startup with pending schedule
   438 @SYMTestPriority	High
   439 @SYMTestActions 	Start the scheduler and register a client.
   440 					Create a scheduled task and then terminate the scheduler without executing the task.
   441 					When the scheduler is restarted it should not exit as there is a 
   442 					pending schedule. Verify that the scheduler is still active by executing
   443 					the schedule
   444 @SYMTestExpectedResults	The task scheduler should not exit and the schedule should execute
   445 @SYMDEF 		 DEF102414
   446 */	
   447 static void DoTest5L()
   448 	{
   449 	__UHEAP_MARK;
   450 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-3412 Test Task Scheduler startup with pending schedule "));
   451 	
   452 	// Connect to the server
   453 	TheTest.Next(_L("===== Connect to Scheduler ====="));
   454 	TInt res = TheScheduler.Connect();
   455 	TEST2(res, KErrNone);
   456 	
   457 	// Register a client with the server
   458 	TheTest.Next(_L("===== Registering Client ====="));
   459 	res = SchSvrHelpers::RegisterClientL(TheScheduler);
   460 	TEST2(res, KErrNone);
   461 	
   462 	//Schedule a task
   463 	ScheduleTaskL();
   464 	
   465  	// Kill the server
   466 	res = CleanupHelpers::KillProcess(KTaskScheduler);
   467 	TEST2(res, KErrNone);
   468  	TheScheduler.Close();
   469  	
   470  	TheTest.Next(_L("Create Task notification semaphore"));
   471 	//initialise task notification semaphore
   472 	STaskSemaphore sem;
   473 	sem.CreateL();
   474 	
   475 	//Restart the scheduler - task scheduler should not exit as there is a 
   476 	//pending schedule
   477 	res = SchSvrHelpers::LaunchTaskSchedulerL();
   478 	TEST2(res, KErrNone);
   479 	
   480 	//Execute task and wait for it to run - this would not succeed
   481 	//if task scheduler had exited above
   482 	ExecuteTaskL();
   483 	TEST2(STaskSemaphore::WaitL(KDefaultTimeout), KErrNone); 
   484 	
   485 	//Kill the process and verify that the scheduler was active
   486 	//If the task scheduler isnt active when we try to kill it
   487 	//KillProcess would return KErrDied
   488 	res = CleanupHelpers::KillProcess(KTaskScheduler);
   489 	TEST2(res, KErrNone);
   490 	
   491 	//close handle to semaphore
   492 	sem.Close();
   493 
   494 	__UHEAP_MARKEND;	
   495 	
   496 	}
   497 
   498 /**	
   499 @SYMTestCaseID 	SYSLIB-SCHSVR-CT-4010
   500 @SYMTestCaseDesc	Test that adding a task using an unregistered client panics the client and does not crash the server.
   501 @SYMTestPriority	High
   502 @SYMTestActions 	Schedule a task with a client that has not been registered.  
   503 @SYMTestExpectedResults Client should be panicked.
   504 @SYMDEF 		DEF124488 
   505 */
   506 static void DoTest6L()
   507 	{	
   508 	TheTest.Next(_L(" @SYMTestCaseID:SYSLIB-SCHSVR-CT-4010 Test handling of unregistered client Should Panic Client thread "));	
   509 	
   510 	RThread testThread;
   511 	_LIT(KThreadName, "PanicClientThread");
   512 
   513 	testThread.Create(KThreadName, TestPanicThread, KDefaultStackSize, 0x1000, 0x100000, NULL);
   514 
   515 	TRequestStatus requestStatus;
   516 	// Request notification when the thread terminates
   517 	testThread.Logon(requestStatus);
   518 	
   519 	TBool justInTime=User::JustInTime(); 
   520 	User::SetJustInTime(EFalse); 
   521 	// Let the thread execute
   522 	testThread.Resume();
   523 
   524 	// Wait for termination
   525 	User::WaitForRequest(requestStatus);
   526 	User::SetJustInTime(justInTime); 
   527 
   528 	TEST2(testThread.ExitReason(), 0);
   529 	testThread.Close();
   530 
   531 	}
   532 
   533 static TInt RunTestsL()
   534 	{
   535 	TheTest.Next(_L("Delete old files"));
   536 	SchSvrHelpers::DeleteScheduleFilesL();
   537 	
   538 	//create P&S variables for the test
   539 	CreateTestVariables();
   540 	
   541 	TheTest.Next(_L("Start tests"));
   542 
   543 	CActiveScheduler* scheduler = new (ELeave) CActiveScheduler();
   544 	CleanupStack::PushL(scheduler);
   545 	CActiveScheduler::Install(scheduler);
   546 	
   547 	DoTest1L();
   548 	DoTest2L();
   549 	DoTest3L();
   550 	DoTest4L();
   551 	DoTest5L();	
   552 	DoTest6L();
   553 	
   554 	TheTest.Next(_L("Tidying up"));
   555 	CleanupStack::PopAndDestroy(scheduler);
   556 
   557 	return KErrNone;
   558 	}
   559 
   560 GLDEF_C TInt E32Main()
   561     {
   562 	__UHEAP_MARK;
   563 	TheTest.Start(_L("TC_TSCH_ROBUSTNESS"));
   564 	TheTest.Title();
   565 	TheCleanup = CTrapCleanup::New();
   566 
   567 	//If the previous test fails, SCHSVR.exe may stay in memory.
   568 	TRAPD(error,CleanupHelpers::TestCleanupL());
   569 	TEST2(error, KErrNone);
   570 	TheTest(TheFsSession.Connect() == KErrNone);;
   571 	TRAP(error, RunTestsL());
   572 	TEST2(error, KErrNone);	
   573 	TRAP(error,CleanupHelpers::TestCleanupL());
   574 	TEST2(error, KErrNone);
   575 	delete TheCleanup;	
   576 	
   577 	TheFsSession.Close();
   578 	TheTest.End();
   579 	TheTest.Close();
   580 	__UHEAP_MARKEND;
   581 
   582 	return KErrNone;
   583 	}