Fixed threadpool hang and added lots of smoketests.
authorTom Sutcliffe <thomas.sutcliffe@accenture.com>
Fri, 17 Sep 2010 20:24:30 +0100
changeset 75 3c3961c1ae26
parent 71 264162c6ed91
child 76 b9edfff731fb
Fixed threadpool hang and added lots of smoketests. * Fixed bug in threadpool logic when calling CleanupAnyWorkersSharingAllocator - this was deleting the worker's thread death notifier in the context of a worker thread (which is only allowed from the context of the main thread). * Added lots more smoketests * Added --overwrite option to fzip (previously unzipping would always overwrite)
commands/focus/focus.cif
commands/fzip/fzip.cif
commands/fzip/fzip.cpp
commands/fzip/fzip.h
commands/grabscreen/grabscreen.cif
commands/iap/iap.cif
commands/listapps/listapps.cif
commands/localdrive/localdrive.cif
commands/localdrive/localdrive.cpp
commands/mrouter/mrouter.cif
commands/screenmode/screenmode.cif
commands/swi/swi.cif
commands/usb/usb.cif
commands/variant/variant.cif
commands/wslog/wslog.cif
core/builtins/chunkinfo.cif
core/builtins/ciftest.cif
core/builtins/ciftest.cpp
core/builtins/clear.cif
core/builtins/compare.cif
core/builtins/console.cif
core/builtins/date.cif
core/builtins/dialog.cif
core/builtins/driver.cif
core/builtins/dump.cif
core/builtins/echo.cif
core/builtins/exists.cif
core/builtins/exit.cif
core/builtins/match.cif
core/builtins/ps.cif
core/builtins/repeat.cif
core/builtins/rm.cif
core/builtins/rom.cif
core/builtins/sort.cif
core/builtins/source.cif
core/builtins/svrinfo.cif
core/builtins/touch.cif
core/builtins/var.cif
core/builtins/version.cif
core/builtins/which.cif
core/src/worker_thread.cpp
core/src/worker_thread.h
core/tsrc/fshell-basic-test.script
--- a/commands/focus/focus.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/focus/focus.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -44,3 +44,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+focus $Quiet
--- a/commands/fzip/fzip.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/fzip/fzip.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -20,9 +20,9 @@
 
 Generates compressed archives of files or extracts files from a compressed archive. Both 'zip' and 'gzip' file formats are support ('zip' is used by default).
 
-==argument filename archive optional
+==argument filename zipfile optional
 
-The zip archive to create or extract.
+The zip archive to create or extract. If the zipfile argument is not specified when creating a zip, the first C<file> argument's name is used with a .zip or .gz extension appended.
 
 ==option bool v verbose
 
@@ -30,11 +30,11 @@
 
 ==option bool u unzip
 
-Extract files from the specified archive.
+Extract files from the specified zipfile.
 
 ==option filename d directory
 
-The directory to extract files into. Must be used in conjunction with '--unzip'.
+The directory to extract files into. Must be used in conjunction with C<--unzip>. If not specified, the current working directory is used.
 
 ==option bool r recurse
 
@@ -42,7 +42,7 @@
 
 ==option filename f file multiple
 
-A file or directory to archive. Only applicable when creating a new archive. If a directory is specified then it and any files contained immediately within that directory are archived. Use --recurse to archive all sub-directories and files within the directory. 
+A file or directory to add to the zipfile. Only applicable when creating a new archive. If a directory is specified then it and any files contained immediately within that directory are archived. Use --recurse to archive all sub-directories and files within the directory. 
 
 ==option enum t compression-type
 
@@ -54,7 +54,37 @@
 
 GNU Zip format. Note, this format can only handle a single file.
 
+==option bool o overwrite
+
+By default fzip will exit with an error if a file it is creating already exists on disk. Use this flag to silently overwrite instead.
+
 ==copyright
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+export TESTDATA "This is some test data for fzip"
+echo -n "$TESTDATA" > test.txt
+rm test.txt.zip $Silent &| echo -n ""
+
+# Test zip
+fzip --file test.txt
+exists test.txt.zip || $Error
+rm test.txt
+
+# Test unzip
+fzip --unzip test.txt.zip
+exists test.txt || $Error
+cat -b test.txt | export -s RESULT
+var RESULT == "$TESTDATA" || $Error
+
+# Test that we don't overwrite files unless --overwrite is specified
+fzip --file test.txt $Silent &| var ? == "-11" || $Error
+fzip --file test.txt --overwrite
+
+fzip --unzip test.txt.zip $Silent &| var ? == "-11" || $Error
+fzip --unzip test.txt.zip --overwrite
+
+rm test.txt
+rm test.txt.zip
--- a/commands/fzip/fzip.cpp	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/fzip/fzip.cpp	Fri Sep 17 20:24:30 2010 +0100
@@ -15,6 +15,7 @@
 #include "fzip.h"
 
 _LIT(KGzExtension, ".gz");
+_LIT(KZipExtension, ".zip");
 
 CCommandBase* CCmdZip::NewLC()
 	{
@@ -26,8 +27,7 @@
 
 CCmdZip::~CCmdZip()
 	{
-	if (iFileToZip.Count() > 0)
-		iFileToZip.Close();
+	iFileToZip.Close();
 	}
 	
 CCmdZip::CCmdZip() : CCommandBase(CCommandBase::EManualComplete)
@@ -55,11 +55,11 @@
 			// command-line sanity checks
 			if (iFileToZip.Count() > 0)
 				{
-				PrintWarning(_L("Ignoring \'-f\' file option."));
+				PrintWarning(_L("--file option is not relevant when unzipping."));
 				}
 			if (iRecurse)
 				{
-				PrintWarning(_L("Ignoring \'-r\' recurse option."));
+				PrintWarning(_L("--recurse option is not relevant when unzipping."));
 				}
 			}
 		ExpandArchiveL();
@@ -71,21 +71,14 @@
 			// command-line sanity checks
 			if (iUnzipPath.Length() > 0)
 				{
-				PrintWarning(_L("Ignoring '-d' directory option."));	
+				PrintWarning(_L("--directory option is not relevant when zipping."));	
 				}
 			}
 		if (iFileToZip.Count() == 0)
 			{
-			PrintError(KErrArgument, _L("Use '-f' to specify source files."));
-			User::Leave(KErrArgument);
+			LeaveIfErr(KErrArgument, _L("Specify some files to zip up using --file option."));
 			}
-		TRAPD(err, CreateArchiveL());
-		if (err != KErrNone)
-			{
-			PrintError(err, _L("Couldn't create archive"));
-			Fs().Delete(iArchive); // ignore error
-			User::Leave(err);
-			}
+		TRAPL(CreateArchiveL(), _L("Couldn't create %S"), &iArchive);
 		}
 	if (iVerbose)
 		{
@@ -96,7 +89,7 @@
 	
 void CCmdZip::ArgumentsL(RCommandArgumentList& aArguments)
 	{
-	_LIT(KArg1, "archive");
+	_LIT(KArg1, "zipfile");
 	aArguments.AppendFileNameL(iArchive, KArg1);
 	}
 
@@ -119,6 +112,9 @@
 
 	_LIT(KOptCompressionType, "compression-type");
 	aOptions.AppendEnumL((TInt&)iCompressionType, KOptCompressionType);
+
+	_LIT(KOptOverwrite, "overwrite");
+	aOptions.AppendBoolL(iOverwrite, KOptOverwrite);
 	}
 
 
@@ -132,6 +128,24 @@
 //
 void CCmdZip::CreateArchiveL()
 	{
+	if (iArchive.Length() == 0)
+		{
+		iArchive = iFileToZip[0];
+		iArchive.Append(iCompressionType == EGZip ? KGzExtension() : KZipExtension());
+		}
+
+	if (iArchive.Exists(FsL()))
+		{
+		if (iOverwrite)
+			{
+			FsL().Delete(iArchive);
+			}
+		else
+			{
+			LeaveIfErr(KErrAlreadyExists, _L("File %S already exists on disk. Use --overwrite or specify a different file"), &iArchive);
+			}
+		}
+
 	if (iCompressionType == EGZip)
 		{
 		CreateGzArchiveL();
@@ -158,23 +172,17 @@
 		LeaveIfErr(KErrArgument, _L("GNU Zip format can only handle a single file"));
 		}
 
-	if (iArchive.Length() == 0)
-		{
-		iArchive = iFileToZip[0];
-		iArchive.Append(KGzExtension);
-		}
-
-	RFile input;
 	if (iVerbose)
 		{
 		Printf(_L("Creating '%S'\r\n"), &iArchive);
 		}
 
 	// open the input file
-	User::LeaveIfError(input.Open(Fs(), iFileToZip[0], EFileStream | EFileRead | EFileShareAny));
+	RFile input;
+	User::LeaveIfError(input.Open(FsL(), iFileToZip[0], EFileStream | EFileRead | EFileShareAny));
 	CleanupClosePushL(input);
 
-	CEZFileToGZip* zip = CEZFileToGZip::NewLC(Fs(), iArchive, input);
+	CEZFileToGZip* zip = CEZFileToGZip::NewLC(FsL(), iArchive, input);
 	while (zip->DeflateL())
 		{
 		// do nothing
@@ -297,7 +305,15 @@
 		{
 		LeaveIfErr(err, _L("Couldn't create path '%S'"), &dest);
 		}
-	User::LeaveIfError(newFile.Replace(Fs(), dest, EFileStream | EFileRead | EFileShareAny));
+	if (iOverwrite)
+		{
+		err = newFile.Replace(FsL(), dest, EFileStream | EFileRead | EFileWrite | EFileShareAny);
+		}
+	else
+		{
+		err = newFile.Create(FsL(), dest, EFileStream | EFileRead | EFileWrite | EFileShareAny);
+		}
+	LeaveIfErr(err, _L("Couldn't create file %S"), &dest);
 	CleanupClosePushL(newFile);
 
 	// inflate the compressed file
@@ -362,7 +378,15 @@
 	aZip.GetInputStreamL(&aMember, readStream);
 	CleanupStack::PushL(readStream);
 
-	LeaveIfErr(newFile.Replace(Fs(), dest, EFileShareExclusive), _L("Couldn't create file %S"), &dest);
+	if (iOverwrite)
+		{
+		err = newFile.Replace(Fs(), dest, EFileShareExclusive);
+		}
+	else
+		{
+		err = newFile.Create(Fs(), dest, EFileShareExclusive);
+		}
+	LeaveIfErr(err, _L("Couldn't create file %S"), &dest);
 	CleanupClosePushL(newFile);
 	if (iVerbose)
 		{
--- a/commands/fzip/fzip.h	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/fzip/fzip.h	Fri Sep 17 20:24:30 2010 +0100
@@ -56,6 +56,7 @@
 	TFileName2 iArchive;
 	TFileName2 iUnzipPath;
 	RArray<TFileName2> iFileToZip;
+	TBool iOverwrite;
 	};
 
 
--- a/commands/grabscreen/grabscreen.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/grabscreen/grabscreen.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -58,3 +58,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+grabscreen > /dev/null
--- a/commands/iap/iap.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/iap/iap.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -20,8 +20,10 @@
 
 Add a dummy IAP for use with WinSockPrt (an ESock protocol module that replaces Symbian's TCP/IP stack with a shim over Microsoft's WinSock API).
 
-
 ==copyright
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+iap $Quiet
--- a/commands/listapps/listapps.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/listapps/listapps.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -36,9 +36,10 @@
 
 Display the details of the application with the specified window group identifier. If not specified, details of all currently running applications are displayed.
 
-
-
 ==copyright
 
 Copyright (c) 2007-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+listapps $Quiet
--- a/commands/localdrive/localdrive.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/localdrive/localdrive.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -50,3 +50,6 @@
 
 Copyright (c) 2010 Accenture. All rights reserved.
 
+==smoke-test
+
+localdrive $Silent # Localdrive can print errors for drives that are ejected, etc
--- a/commands/localdrive/localdrive.cpp	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/localdrive/localdrive.cpp	Fri Sep 17 20:24:30 2010 +0100
@@ -226,7 +226,12 @@
 	if (err == KErrNone)
 		{
 		TPckg<TLocalDriveCapsV2> capsBuf(iCaps);
-		LeaveIfErr(iDrive.Caps(capsBuf), _L("Opened drive %d but couldn't read caps"), aDrive);
+		err = iDrive.Caps(capsBuf);
+		if (err)
+			{
+			iDrive.Close();
+			if (aLeaveOnConnectErr) LeaveIfErr(err, _L("Opened drive %d but couldn't read caps"), aDrive);
+			}
 		}
 	else if (aLeaveOnConnectErr)
 		{
--- a/commands/mrouter/mrouter.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/mrouter/mrouter.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -20,3 +20,7 @@
 
 Copyright (c) 2007-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Do any platforms still support this command?
+mrouter
--- a/commands/screenmode/screenmode.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/screenmode/screenmode.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -48,3 +48,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+screenmode list $Quiet
--- a/commands/swi/swi.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/swi/swi.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -56,3 +56,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+swi list $Quiet
--- a/commands/usb/usb.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/usb/usb.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -24,3 +24,6 @@
 
 Copyright (c) 2010 Accenture. All rights reserved.
 
+==smoke-test
+
+usb $Quiet
--- a/commands/variant/variant.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/variant/variant.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -50,3 +50,11 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+variant $Quiet
+
+# One of these has to be true
+variant wins || variant target || $Error
+# But they can't both be
+variant wins && variant target && $Error
--- a/commands/wslog/wslog.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/commands/wslog/wslog.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -42,3 +42,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+wslog status $Quiet
--- a/core/builtins/chunkinfo.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/chunkinfo.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -48,3 +48,9 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# This should fail with KErrNotFound, but shouldn't crash
+chunkinfo 1 $Silent &| var ? == "-1" || $Error
+
+chunkinfo $Quiet
--- a/core/builtins/ciftest.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/ciftest.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -92,3 +92,7 @@
 ==copyright
 
 Copyright (c) 2010 Accenture. All rights reserved.
+
+==smoke-test
+
+# Ciftest itself doesn't have a smoketest!
--- a/core/builtins/ciftest.cpp	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/ciftest.cpp	Fri Sep 17 20:24:30 2010 +0100
@@ -89,7 +89,9 @@
 		{
 		if (iVerbose)
 			{
-			Printf(_L("%d tests run, %d passes %d failures. %d commands have no tests defined.\r\n"), iPasses + iFailures, iPasses, iFailures, iCifFiles.Count() - iPasses - iFailures);
+			Printf(_L("%d tests run, %d passes %d failures."), iPasses + iFailures, iPasses, iFailures);
+			if (iCifFiles.Count()) Printf(_L(" %d commands have no tests defined."), iCifFiles.Count() - iPasses - iFailures);
+			Printf(_L("\r\n"));
 			}
 		Complete(KErrNone);
 		}
--- a/core/builtins/clear.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/clear.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+cls --formfeed $Quiet
--- a/core/builtins/compare.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/compare.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -36,3 +36,15 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+date --timestamp | export -s TIMESTAMP
+export Temp compare-test-$TIMESTAMP-
+echo "Stuff" > $Temp1
+echo "Stuff" > $Temp2
+echo "Stuff thats different" > $Temp3
+
+compare $Temp1 $Temp2 || $Error # Should be same
+compare $Temp1 $Temp3 && $Error # Should be different
+
+rm $Temp1 $Temp2 $Temp3
--- a/core/builtins/console.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/console.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -40,3 +40,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+console $Quiet
--- a/core/builtins/date.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/date.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -85,3 +85,8 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+date $Quiet
+date --raw $Quiet
+date --timestamp $Quiet
--- a/core/builtins/dialog.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/dialog.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -62,3 +62,9 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Don't actually post a dialog, the tests need to be unobtrusive (and non-blocking)
+export DIALOG_IMPL null
+dialog "Some question which isn't important"
+var ? == 0 || $Error
--- a/core/builtins/driver.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/driver.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -48,3 +48,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+driver list logical $Quiet
\ No newline at end of file
--- a/core/builtins/dump.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/dump.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -28,3 +28,7 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+echo "123" | dump | export -s RESULT
+var RESULT == "00000000: 31 00 32 00 33 00 0D 00 0A 00                   1.2.3.....^r^n" || $Error
--- a/core/builtins/echo.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/echo.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -102,3 +102,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-basic-test.script, this section is just so ciftest doesn't report it as a command without any tests
--- a/core/builtins/exists.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/exists.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-basic-test.script, this section is just so ciftest doesn't report it as a command without any tests
--- a/core/builtins/exit.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/exit.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -20,8 +20,11 @@
 
 Note, this causes fshell's command history to be persisted to a file.
 
-
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# This used to cause fshell problems
+fshell -e exit
--- a/core/builtins/match.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/match.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -43,3 +43,13 @@
 ==see-also
 
 L<grep|grep>
+
+==smoke-test
+
+echo "Test line of some sort^r^nIsn't fshell great?^r^nSome other line" | export -s TESTDATA
+echo "$TESTDATA" | match *fshell* | export -s RESULT
+var RESULT == "Isn't fshell great?^r^n" || $Error
+
+# Test anchored search
+echo "$TESTDATA" | match Some* | export -s RESULT
+var RESULT == "Some other line^r^n" || $Error
--- a/core/builtins/ps.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/ps.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -104,3 +104,6 @@
 
 Copyright (c) 2005-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+ps $Quiet
--- a/core/builtins/repeat.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/repeat.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -40,3 +40,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-last-test.script, this section is just so ciftest doesn't report it as a command without any tests
\ No newline at end of file
--- a/core/builtins/rm.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/rm.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -36,3 +36,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-basic-test.script, this section is just so ciftest doesn't report it as a command without any tests
--- a/core/builtins/rom.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/rom.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+variant target && rom --verbose $Quiet
--- a/core/builtins/sort.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/sort.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -24,9 +24,11 @@
 
 Reverse the sort order.
 
-
-
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+echo "wonderful^r^nfshell^r^njust^r^nis" | sort | export -s RESULT
+var RESULT == "fshell^r^nis^r^njust^r^nwonderful^r^n" || $Error
--- a/core/builtins/source.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/source.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -56,3 +56,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-basic-test.script, this section is just so ciftest doesn't report it as a command without any tests
--- a/core/builtins/svrinfo.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/svrinfo.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+svrinfo $Quiet
--- a/core/builtins/touch.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/touch.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -24,9 +24,15 @@
 
 The file to touch.
 
-
-
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+date --timestamp | export -s TIMESTAMP
+export TEMPFILE temp$TIMESTAMP
+exists $TEMPFILE && $Error
+touch $TEMPFILE
+exists $TEMPFILE || $Error
+rm $TEMPFILE
--- a/core/builtins/var.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/var.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -76,3 +76,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Tested by fshell-basic-test.script, this section is just so ciftest doesn't report it as a command without any tests
\ No newline at end of file
--- a/core/builtins/version.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/version.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -24,3 +24,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+version $Quiet
--- a/core/builtins/which.cif	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/builtins/which.cif	Fri Sep 17 20:24:30 2010 +0100
@@ -24,3 +24,7 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+which which | export -s RESULT
+var RESULT == "which: built-in command 'which'^r^n" || $Error
--- a/core/src/worker_thread.cpp	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/src/worker_thread.cpp	Fri Sep 17 20:24:30 2010 +0100
@@ -43,6 +43,49 @@
 
 const TInt KCacheTime = 1000000; // 1 second
 
+// Thread death watchers run in the context of the thread pool main thread
+class CThreadDeathWatcher : public CActive
+	{
+public:
+	CThreadDeathWatcher(CWorkerThread* aWorker)
+		: CActive(CActive::EPriorityHigh), iWorker(aWorker)
+		{
+		}
+	~CThreadDeathWatcher()
+		{
+		WT_LOG(_L("Deleting thread death watcher for worker %d"), TUint(iWorker->GetThreadId()));
+		Cancel();
+		}
+	void StartWatching()
+		{
+		CActiveScheduler::Add(this);
+		iWorker->iWorkerThread.Logon(iStatus);
+		SetActive();
+		}
+	CWorkerThread* WorkerThread() const
+		{
+		return iWorker;
+		}
+private:
+	void DoCancel()
+		{
+		iWorker->iWorkerThread.LogonCancel(iStatus);
+		}
+	void RunL()
+		{
+		iWorker->SignalClientThatThreadHasDied();
+		iWorker->iParentPool->WorkerDied(iWorker);
+		// WorkerDied may cause this object to be deleted, so don't add anything that accesses member data below this point!
+		}
+
+private:
+	CWorkerThread* iWorker;
+	};
+
+//
+
+
+
 CThreadPool* CThreadPool::NewL()
 	{
 	CThreadPool* self = new(ELeave) CThreadPool();
@@ -65,17 +108,19 @@
 	iThreadPoolAllocator = &User::Allocator();
 	User::LeaveIfError(iLock.CreateLocal());
 	iThreads.ReserveL(2);
+	iThreadDeathWatchers.ReserveL(2);
 	// We don't create any workers by default
 	}
 
 CThreadPool::~CThreadPool()
 	{
-	WT_LOG(_L("Deleting thread pool. %d threads created during its lifetime"), iCountThreadsCreated);
+	WT_LOG(_L("Deleting thread pool. %d threads created during its lifetime, %d currently and %d watchers"), iCountThreadsCreated, iThreads.Count(), iThreadDeathWatchers.Count());
 	Cancel();
 	for (TInt i = 0; i < iThreads.Count(); i++)
 		{
 		iThreads[i]->Shutdown();
 		}
+	iThreadDeathWatchers.ResetAndDestroy();
 	iThreads.ResetAndDestroy();
 	iLock.Close();
 	delete iIdleTimer;
@@ -85,7 +130,9 @@
 
 void CThreadPool::Lock()
 	{
+	WT_LOG(_L("Getting lock from thread %d"), TUint(RThread().Id()));
 	iLock.Wait();
+	WT_LOG(_L("Got lock from thread %d"), TUint(RThread().Id()));
 	}
 
 void CThreadPool::SwitchToThreadPoolHeap()
@@ -112,6 +159,7 @@
 void CThreadPool::Unlock()
 	{
 	RestoreHeap();
+	WT_LOG(_L("Releasing lock from thread %d"), TUint(RThread().Id()));
 	iLock.Signal();
 	}
 
@@ -154,12 +202,18 @@
 	if (foundThread == NULL)
 		{
 		SwitchToThreadPoolHeap();
+		iPendingThreadLogons.ReserveL(iPendingThreadLogons.Count() + 1); // So the Append below can't fail
+		iThreadDeathWatchers.ReserveL(iThreadDeathWatchers.Count() + 1);
+		iThreads.ReserveL(iThreads.Count() + 1);
 		foundThread = CWorkerThread::NewLC(this, requiredAllocator);
+		CThreadDeathWatcher* threadWatcher = new(ELeave) CThreadDeathWatcher(foundThread);
 		iCountThreadsCreated++;
-		WT_LOG(_L("Creating new worker thread %d"), TUint(foundThread->GetThreadId()));
-		iThreads.AppendL(foundThread);
-		CleanupStack::Pop(foundThread);
-		iPendingThreadLogons.AppendL(foundThread);
+		WT_LOG(_L("Created new worker thread %d"), TUint(foundThread->GetThreadId()));
+		iThreads.Append(foundThread);
+		iPendingThreadLogons.Append(threadWatcher);
+		iThreadDeathWatchers.Append(threadWatcher);
+		CleanupStack::Pop(foundThread);	
+
 		SignalSelf(); // So the iPendingThreadLogons gets sorted out in context of main thread
 		RestoreHeap();
 		}
@@ -179,14 +233,19 @@
 	// This is now always called in the main thread
 	ASSERT(RThread().Id() == iMainThread.Id());
 
-	// Find it and remove it - the next request will create a new worker if needed
-	for (TInt i = 0; i < iThreads.Count(); i++)
+	for (TInt i = 0; i < iThreadDeathWatchers.Count(); i++)
 		{
-		CWorkerThread* worker = iThreads[i];
-		if (worker == aWorker)
+		if (iThreadDeathWatchers[i]->WorkerThread() == aWorker)
 			{
-			delete worker;
+			// Clean up the watcher first
+			delete iThreadDeathWatchers[i];
+			iThreadDeathWatchers.Remove(i);
+
+			// The indexes in both arrays are guaranteed to be the same
+			ASSERT(iThreads[i] == aWorker);
+			delete aWorker;
 			iThreads.Remove(i);
+
 			break;
 			}
 		}
@@ -234,8 +293,7 @@
 			{
 			ASSERT(!worker->Busy());
 			worker->Shutdown();
-			delete worker;
-			iThreads.Remove(i);
+			// We don't delete the worker here, let CThreadDeathWatcher handle that
 			}
 		}
 	Unlock();
@@ -271,6 +329,8 @@
 			else
 				{
 				// Everything else (that isn't busy) gets cleaned up
+				delete iThreadDeathWatchers[i];
+				iThreadDeathWatchers.Remove(i);
 				worker->Shutdown();
 				delete worker;
 				iThreads.Remove(i);
@@ -302,7 +362,7 @@
 		{
 		for (TInt i = 0; i < iPendingThreadLogons.Count(); i++)
 			{
-			iPendingThreadLogons[i]->RegisterThreadDeathWatcherOnCurrentThread();
+			iPendingThreadLogons[i]->StartWatching();
 			}
 		SwitchToThreadPoolHeap();
 		iPendingThreadLogons.Reset();
@@ -328,44 +388,11 @@
 	return 0;
 	}
 
-
 ////
 
-class CThreadDeathWatcher : public CActive
-	{
-public:
-	CThreadDeathWatcher(CWorkerThread* aWorker, RThread& aUnderlyingThread)
-		: CActive(CActive::EPriorityHigh), iWorker(aWorker), iThread(aUnderlyingThread)
-		{
-		}
-	~CThreadDeathWatcher()
-		{
-		Cancel();
-		}
-	void StartWatching()
-		{
-		CActiveScheduler::Add(this);
-		iThread.Logon(iStatus);
-		SetActive();
-		}
-private:
-	void DoCancel()
-		{
-		iThread.LogonCancel(iStatus);
-		}
-	void RunL()
-		{
-		Deque(); // Our work is done. Might as well extract ourselves from whatever scheduler we're in while we can
-		iWorker->ThreadDied();
-		}
+const TInt KMaxHeapSize = 1024 * 1024;
 
-private:
-	CWorkerThread* iWorker;
-	RThread& iThread;
-	};
-
-const TInt KMaxHeapSize = KMinHeapSize * 1024;
-
+// CWorkerThreadDispatchers run in the context of the worker thread they're owned by
 class CWorkerThreadDispatcher : public CActive
 	{
 public:
@@ -428,8 +455,6 @@
 		}
 	User::LeaveIfError(err);
 
-	iThreadDeathWatcher = new(ELeave) CThreadDeathWatcher(this, iWorkerThread);
-
 	TRequestStatus stat;
 	iWorkerThread.Rendezvous(stat);
 
@@ -450,9 +475,8 @@
 
 CWorkerThread::~CWorkerThread()
 	{
+	WT_LOG(_L("Deleting worker thread %d"), TUint(GetThreadId()));
 	ASSERT(iWorkerThread.Handle() == 0 || iWorkerThread.ExitType() != EExitPending);
-	//Cancel();
-	delete iThreadDeathWatcher;
 	iParentThread.Close();
 	iWorkerThread.Close();
 	}
@@ -601,7 +625,7 @@
 	return iSharedAllocator;
 	}
 
-void CWorkerThread::ThreadDied()
+void CWorkerThread::SignalClientThatThreadHasDied()
 	{
 	WT_LOG(_L("Task %d died with exittype %d reason %d"), iTaskId, iWorkerThread.ExitType(), iWorkerThread.ExitReason());
 	TInt err = iWorkerThread.ExitReason();
@@ -610,7 +634,6 @@
 		{
 		iParentThread.RequestComplete(iCompletionStatus, err);
 		}
-	iParentPool->WorkerDied(this);
 	}
 
 TInt CWorkerThread::ThreadFn(TAny* aSelf)
@@ -680,7 +703,3 @@
 		}
 	}
 
-void CWorkerThread::RegisterThreadDeathWatcherOnCurrentThread()
-	{
-	iThreadDeathWatcher->StartWatching();
-	}
--- a/core/src/worker_thread.h	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/src/worker_thread.h	Fri Sep 17 20:24:30 2010 +0100
@@ -31,6 +31,7 @@
 	};
 
 class CWorkerThread;
+class CThreadDeathWatcher;
 
 class CThreadPool : public CActive, public MTaskRunner
 	{
@@ -70,12 +71,10 @@
 	TInt iPendingCallbacks;
 	CPeriodic* iIdleTimer;
 	TInt iCountThreadsCreated; // This is for statistics gathering, not involved in the logic
-	RArray<CWorkerThread*> iPendingThreadLogons; // Not owned
+	RArray<CThreadDeathWatcher*> iPendingThreadLogons; // Not owned
+	RPointerArray<CThreadDeathWatcher> iThreadDeathWatchers;
 	};
 
-
-class CThreadDeathWatcher;
-
 class CWorkerThread : public CBase, public MThreadedTask
 	{
 public:
@@ -89,7 +88,6 @@
 	TInt Setup(TInt aTaskId, const TDesC& aThreadName, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext);
 	void Shutdown();
 	~CWorkerThread();
-	void RegisterThreadDeathWatcherOnCurrentThread();
 
 	TBool Running() const { return iWorkerThread.Handle() && iWorkerThread.ExitType() == EExitPending; }
 
@@ -99,7 +97,7 @@
 	void AbortTask();
 
 public: // For CThreadDeathWatcher to use
-	void ThreadDied();
+	void SignalClientThatThreadHasDied();
 
 private:
 	CWorkerThread(CThreadPool* aParentPool, RAllocator* aSharedAllocator);
@@ -126,6 +124,7 @@
 	TRequestStatus* iCompletionStatus;
 
 	friend class CWorkerThreadDispatcher;
+	friend class CThreadDeathWatcher;
 	};
 
 #endif
--- a/core/tsrc/fshell-basic-test.script	Wed Sep 15 00:45:50 2010 +0100
+++ b/core/tsrc/fshell-basic-test.script	Fri Sep 17 20:24:30 2010 +0100
@@ -107,7 +107,9 @@
 
 export FILE c:\fshell-basic-test.txt
 rm $FILE 2>/dev/null &| echo -n "" # I don't like using "&| echo" syntax to indicate don't care if it fails. Probably rm -f should be quiet like unix version
+exists $FILE && $Error
 echo -n "Testing file redirection" > c:\fshell-basic-test.txt
+exists $FILE || $Error
 
 # The redirect stdin operation doesn't get used much
 export -s FILECONTENTS < $FILE