Migrated ciftest and various fixes from FCL to MCL.
authorTom Sutcliffe <thomas.sutcliffe@accenture.com>
Wed, 13 Oct 2010 12:41:05 +0100
changeset 66 2a78c4ff2eab
parent 63 6a2083f7eeb8
child 67 84fefe1cd57f
Migrated ciftest and various fixes from FCL to MCL.
build/common/fshell.iby
build/sf/3tshell/platform.mmh
commands/cat/cat.cif
commands/cat/cat.cpp
commands/clipboard/clipboard.cif
commands/drvinfo/drvinfo.cif
commands/drvinfo/drvinfo.cpp
commands/fed/src/filebuffer.cpp
commands/focus/focus.cif
commands/fzip/fzip.cpp
commands/fzip/fzip.h
commands/grabscreen/grabscreen.cif
commands/group/fshell_commands.iby
commands/hal/hal.cif
commands/iap/iap.cif
commands/kerninfo/kerninfo.cif
commands/listapps/listapps.cif
commands/localdrive/localdrive.cif
commands/localdrive/localdrive.cpp
commands/mrouter/mrouter.cif
commands/rcomm/rcomm.cif
commands/rconn/rconn.cif
commands/screenmode/screenmode.cif
commands/snake/snake.cif
commands/snake/snake.cpp
commands/swi/swi.cif
commands/sysinfo/sysinfo.cif
commands/uidinfo/uidinfo.cif
commands/usb/usb.cif
commands/variant/variant.cif
commands/variant/variant.cpp
commands/wslog/wslog.cif
core/builtins/cd.cif
core/builtins/chunkinfo.cif
core/builtins/ciftest.cif
core/builtins/ciftest.cpp
core/builtins/ciftest.h
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/ls.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/docs/persistent_consoles.pod
core/group/bld.inf
core/group/fshell_core.iby
core/src/command_factory.cpp
core/src/commands.cpp
core/src/commands.h
core/src/fshell.cpp
core/src/fshell.mmp
core/src/parser.cpp
core/src/parser.h
core/tsrc/fshell-basic-test.script
core/tsrc/smoketest.script
documentation/change_history.pod
libraries/iosrv/bwins/iocliu.def
libraries/iosrv/client/command_base.cpp
libraries/iosrv/client/command_info_file.cpp
libraries/iosrv/eabi/iocliu.def
libraries/iosrv/inc/ioutils.h
libraries/iosrv/server/console.cpp
plugins/consoles/docs/consoles.pod
plugins/consoles/tefcons/tefcons.cpp
plugins/consoles/vt100cons/src/usb/vtc_usb.cpp
plugins/consoles/win32cons/src/console.cpp
tools/baserom
--- a/build/common/fshell.iby	Mon Sep 20 16:46:34 2010 +0100
+++ b/build/common/fshell.iby	Wed Oct 13 12:41:05 2010 +0100
@@ -49,4 +49,8 @@
 FSHELL_EXECUTABLE_FILE(terminalkeyboardcons.dll) // TODO move this to the new fshell_consoles.iby
 #endif
 
+#ifdef FSHELL_TESTEXECUTE_SUPPORT
+FSHELL_EXECUTABLE_FILE(tefcons.dll) // TODO move this to the new fshell_consoles.iby
+#endif
+
 #endif // FSHELL_IBY
--- a/build/sf/3tshell/platform.mmh	Mon Sep 20 16:46:34 2010 +0100
+++ b/build/sf/3tshell/platform.mmh	Wed Oct 13 12:41:05 2010 +0100
@@ -33,4 +33,6 @@
 #define FSHELL_NO_SPCRE_SUPPORT
 #define FSHELL_NO_CRYPTO_SUPPORT
 
+#define EXCLUDE_SERIALKEYBOARD // Needed on beagleboard to stop serial keyboard getting in the way on the uart
+
 #endif
--- a/commands/cat/cat.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/cat/cat.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -30,7 +30,7 @@
 
 ==option bool b binary
 
-Legacy option, kept for compatability. Equivalent to C<--encoding binary>.
+Similar to C<--encoding binary>, but in addition sets the console mode to binary (so, for eg, line endings are not translated)
 
 ==option enum e encoding
 
--- a/commands/cat/cat.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/cat/cat.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -132,6 +132,7 @@
 		else
 			{
 			iEncoding = EBinary;
+			LeaveIfErr(Stdout().SetMode(RIoReadWriteHandle::EBinary), _L("Unable to set stdout to binary mode")); // To tell iosrv to not mess about with line endings.
 			}
 		}
 
--- a/commands/clipboard/clipboard.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/clipboard/clipboard.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -24,7 +24,16 @@
 
 Read the text from C<stdin> instead of from the command line.
 
+==smoke-test
+
+clipboard "Test data"
+clipboard | export -s RESULT
+var RESULT == "Test data" || $Error
+
+echo "$RESULT" | clipboard --stdin
+clipboard | export -s RES2
+var RES2 == "Test data^r^n" || $Error
+
 ==copyright
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
-
--- a/commands/drvinfo/drvinfo.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/drvinfo/drvinfo.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -28,7 +28,32 @@
 
 Display sizes in human readable form.
 
+==option bool l long
+
+Display information one drive per line, similar to ls's --long option. The attributes have the following format:
+
+=over 5
+
+=item r
+
+read only
+
+=item e
+
+removable
+
+=item m
+
+RAM disk
+
+=back
+
+For example, C<re-> indicates a removable disk that is read-only. The attributes are followed by the drive letter, the free space on disk, the total disk size, and the volume name if there is one.
+
 ==copyright
 
 Copyright (c) 2007-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+drvinfo --long $Quiet
--- a/commands/drvinfo/drvinfo.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/drvinfo/drvinfo.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -34,6 +34,7 @@
 	HBufC* iDriveLetter;
 	TBool iVerbose;
 	TBool iHuman;
+	TBool iLong;
 	};
 
 
@@ -56,7 +57,7 @@
 
 void CCmdDrvinfo::ArgumentErrorL()
 	{
-	Stderr().Write(_L("Invalid drive specification - use \'<drive_letter>:\', e.g. \'drvinfo c:\'\r\n"));
+	Stderr().Write(_L("Invalid drive specification - use '<drive_letter>:', e.g. 'drvinfo c:'\r\n"));
 	User::Leave(KErrArgument);
 	}
 
@@ -155,20 +156,42 @@
 		}
 	if (iVerbose)
 		{
-		aPrintBuf.AppendFormatL(_L("VolName:\t\'%S\'"), &volInfo.iName);
+		aPrintBuf.AppendFormatL(_L("VolName:\t\'%S\'\r\n"), &volInfo.iName);
 		}
-	aPrintBuf.AppendL(_L("\r\n"));
 	}
 
 void CCmdDrvinfo::PrintDriveInfoL(TInt aDriveNum)
 	{
-	IoUtils::CTextBuffer* buf = IoUtils::CTextBuffer::NewLC(0x100);
-
-	TDriveInfo 	driveInfo;
+	TDriveInfo driveInfo;
 	User::LeaveIfError(FsL().Drive(driveInfo, aDriveNum));
 
 	TVolumeInfo volInfo;
-	User::LeaveIfError(Fs().Volume(volInfo, aDriveNum));
+	TInt volErr = Fs().Volume(volInfo, aDriveNum);
+	
+	if (iLong)
+		{
+		TText readonly = '-';
+		TText removable = '-';
+		TText ram = '-';
+		if (driveInfo.iMediaAtt & KMediaAttWriteProtected) readonly = 'r';
+		if (driveInfo.iDriveAtt & KDriveAttRemovable) removable = 'e';
+		if (driveInfo.iType == EMediaRam) ram = 'm';
+
+		Printf(_L("%c%c%c %c: "), readonly, removable, ram, 'a' + aDriveNum);
+		TInt64 free = 0;
+		TInt64 size = 0;
+		if (volErr == KErrNone)
+			{
+			free = volInfo.iFree;
+			size = volInfo.iSize;
+			}
+		Printf(_L("%Ld %Ld %S"), free, size, &volInfo.iName);
+		return;
+		}
+
+	User::LeaveIfError(volErr); // Long listing handles volErr itself so didn't want to leave
+
+	IoUtils::CTextBuffer* buf = IoUtils::CTextBuffer::NewLC(0x100);
 
 	if (iVerbose || (iDriveLetter == NULL))
 		{
@@ -191,7 +214,7 @@
 		FormatMediaAttInfoL(driveInfo, *buf);
 		}
 
-	FormatVolInfoL(volInfo, *buf);
+	if (volErr == KErrNone) FormatVolInfoL(volInfo, *buf);
 
 	CTextFormatter* formatter = CTextFormatter::NewLC(Stdout());
 	formatter->TabulateL(0, 2, buf->Descriptor());
@@ -231,7 +254,10 @@
 			ArgumentErrorL();
 			}
 		}
-
+	if (iLong && (iVerbose || iHuman))
+		{
+		LeaveIfErr(KErrArgument, _L("--long cannot be specified at same time as either --human or --verbose"));
+		}
 
 	TDriveList driveList;
 	User::LeaveIfError(FsL().DriveList(driveList));
@@ -272,9 +298,11 @@
 	{
 	_LIT(KOptVerbose, "verbose");
 	_LIT(KOptHuman, "human");
+	_LIT(KOptLong, "long");
 
 	aOptions.AppendBoolL(iVerbose, KOptVerbose);
 	aOptions.AppendBoolL(iHuman, KOptHuman);
+	aOptions.AppendBoolL(iLong, KOptLong);
 	}
 
 
--- a/commands/fed/src/filebuffer.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/fed/src/filebuffer.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -852,7 +852,7 @@
 				{
 				// Can be null if charconv is busted
 				bytesLeft = aCharconvForUtf8Conversion->ConvertToUnicode(iData, data, state, numUnconverted, firstUnconverted);
-				ASSERT(bytesLeft <= 0); // Otherwise we didn't make our buffer big enough
+				ASSERT(bytesLeft <= 3); // Otherwise we didn't make our buffer big enough. There could be up to 3 bytes remaining if the block ends in a truncated UTF-8 sequence
 				// bytesLeft less than zero means an invalid sequence at the start of the buffer. Since our block size will never be so small there's no risk it's the start of a truncated sequence
 				}
 
@@ -865,16 +865,16 @@
 			else if (firstUnconverted >= 0)
 				{
 				// Got some bad data
-				if (iNext && data.Length() - firstUnconverted < 4)
+				if (iNext && data.Length() - bytesLeft < 4 && firstUnconverted == data.Length() - bytesLeft)
 					{
 					// Possibly a UTF-8 sequence spread over a block boundary
 					blockEncoding = EEncodingUtf8;
-					TInt err = iNext->TransferDataFromPreviousBlock(this, data.Mid(firstUnconverted));
+					TInt err = iNext->TransferDataFromPreviousBlock(this, data.Right(bytesLeft));
 					if (!err)
 						{
-						iBlockSize = firstUnconverted;
+						iBlockSize -= bytesLeft;
 						}
-					iData.SetLength(iData.Locate(0xFFFD)); // No better way of figuring out where charconv barfed than to scan for the first instance of the substitution character
+					iData.SetLength(iData.LocateReverse(0xFFFD)); // No better way of figuring out where charconv barfed than to scan for the last instance of the substitution character
 					}
 				else
 					{
--- a/commands/focus/focus.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/focus/focus.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -44,3 +44,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+focus $Quiet
--- a/commands/fzip/fzip.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/fzip/fzip.cpp	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/fzip/fzip.h	Wed Oct 13 12:41:05 2010 +0100
@@ -56,6 +56,7 @@
 	TFileName2 iArchive;
 	TFileName2 iUnzipPath;
 	RArray<TFileName2> iFileToZip;
+	TBool iOverwrite;
 	};
 
 
--- a/commands/grabscreen/grabscreen.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/grabscreen/grabscreen.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -58,3 +58,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+grabscreen > /dev/null
--- a/commands/group/fshell_commands.iby	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/group/fshell_commands.iby	Wed Oct 13 12:41:05 2010 +0100
@@ -374,4 +374,7 @@
 FSHELL_COMMAND_INFO_FILE(fshell,testexecute.cif)
 #endif
 
+FSHELL_EXECUTABLE_FILE(snake.exe)
+FSHELL_COMMAND_INFO_FILE(fshell,snake.cif)
+
 #endif // __FSHELL_COMMANDS_IBY__
--- a/commands/hal/hal.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/hal/hal.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -60,3 +60,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+hal $Quiet
--- a/commands/iap/iap.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/iap/iap.cif	Wed Oct 13 12:41:05 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/kerninfo/kerninfo.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/kerninfo/kerninfo.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -86,3 +86,17 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+kerninfo process $Quiet
+kerninfo thread $Quiet
+kerninfo chunk $Quiet
+kerninfo server $Quiet
+kerninfo codeseg $Quiet
+kerninfo hal $Quiet
+# Don't test windowgroup, mimetype - we may be on tshell
+kerninfo openfile $Quiet
+kerninfo msgq $Quiet
+kerninfo mutex $Quiet
+kerninfo semaphore $Quiet
+kerninfo timer $Quiet
--- a/commands/listapps/listapps.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/listapps/listapps.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/localdrive/localdrive.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/localdrive/localdrive.cpp	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/mrouter/mrouter.cif	Wed Oct 13 12:41:05 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/rcomm/rcomm.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/rcomm/rcomm.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -74,3 +74,6 @@
 
 Copyright (c) 2007-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+rcomm $Quiet
--- a/commands/rconn/rconn.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/rconn/rconn.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -48,3 +48,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+rconn list $Quiet
--- a/commands/screenmode/screenmode.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/screenmode/screenmode.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -48,3 +48,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+screenmode list $Quiet
--- a/commands/snake/snake.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/snake/snake.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -26,6 +26,10 @@
 
 Speed of the game, in microseconds. This is the interval between each step forward the snake makes. Values in the range of 200000 (very easy) to 10000 (very hard) are reasonable.
 
+==option bool u unicode
+
+Draw the game using the unicode box drawing characters. Not guaranteed to display correctly on all consoles.
+
 ==copyright
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
--- a/commands/snake/snake.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/snake/snake.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -16,7 +16,9 @@
 using namespace IoUtils;
 
 _LIT(KSnakeSeg, "*");
+_LIT(KUnicodeSnakeSeg, "\u2588"); // Full block
 _LIT(KBait, "$");
+_LIT(KUnicodeBait, "\u2665"); // heart
 
 class CKeyWatcher;
 
@@ -45,6 +47,7 @@
 	void SetBoard(TInt aX, TInt aY, TBool aSet);
 	TBool GetBoard(TInt aX, TInt aY);
 	void DrawBoardL();
+	TUint16 BoardCharacter(TInt aX, TInt aY);
 	void InitSnakeL(TInt aLen, TPoint aPos);
 	void DrawSnakeL();
 	void DrawScore();
@@ -64,6 +67,7 @@
 	TPoint iBait;
 	TInt iScore;
 	TInt iSpeed;
+	TBool iUnicode;
 	
 	CKeyWatcher* iKeys;
 	CPeriodic* iTimer;
@@ -163,6 +167,8 @@
 	{
 	_LIT(KOptSpeed, "speed");
 	aOptions.AppendIntL(iSpeed, KOptSpeed);
+	_LIT(KOptUnicode, "unicode");
+	aOptions.AppendBoolL(iUnicode, KOptUnicode);
 	}
 	
 void CCmdSnake::ConstructL()
@@ -230,7 +236,8 @@
 		{
 		for (TInt x=0; x<iBoardSize.iWidth; ++x)
 			{
-			line[x] = GetBoard(x,y) ? '#' : ' ';
+			//line[x] = GetBoard(x,y) ? '#' : ' ';
+			line[x] = BoardCharacter(x, y);
 			}
 		User::LeaveIfError(iCons.SetCursorPosAbs(TPoint(0, y)));
 		User::LeaveIfError(iCons.Write(line));
@@ -239,7 +246,57 @@
 	
 	CleanupStack::PopAndDestroy();
 	}
-	
+
+TUint16 CCmdSnake::BoardCharacter(TInt aX, TInt aY)
+	{
+	if (!GetBoard(aX, aY))
+		{
+		return ' ';
+		}
+	else if (!iUnicode)
+		{
+		return '#';
+		}
+	else
+		{
+		// TODO this is too simplistic for anything other than the basic board
+		if (aX == 0)
+			{
+			if (aY == 0)
+				{
+				return 0x2554; // TL
+				}
+			else if (aY == iBoardSize.iHeight - 1)
+				{
+				return 0x255A; // BL
+				}
+			else
+				{
+				return 0x2551;
+				}
+			}
+		else if (aX == iBoardSize.iWidth - 1)
+			{
+			if (aY == 0)
+				{
+				return 0x2557; // TR
+				}
+			else if (aY == iBoardSize.iHeight - 1)
+				{
+				return 0x255D; // BR;
+				}
+			else
+				{
+				return 0x2551;
+				}
+			}
+		else
+			{
+			return 0x2550;
+			}
+		}
+	}
+
 void CCmdSnake::InitSnakeL(TInt aLen, TPoint aPos)
 	{
 	iSnake.Reset();
@@ -255,7 +312,7 @@
 	for (TInt i=0; i<iSnake.Count(); ++i)
 		{
 		User::LeaveIfError(iCons.SetCursorPosAbs(iSnake[i]));
-		User::LeaveIfError(iCons.Write(KSnakeSeg));
+		User::LeaveIfError(iCons.Write(iUnicode ? KUnicodeSnakeSeg : KSnakeSeg));
 		}
 	}
 
@@ -355,7 +412,7 @@
 		iCons.Write(_L(" "));
 		}
 	iCons.SetCursorPosAbs(newHead);
-	iCons.Write(KSnakeSeg);
+	iCons.Write(iUnicode ? KUnicodeSnakeSeg : KSnakeSeg);
 	iSnakeHead = (iSnakeHead+1) % iSnake.Count();
 	iSnake[iSnakeHead] = newHead;
 	
@@ -398,7 +455,7 @@
 			}
 		} while (!ok);
 	iCons.SetCursorPosAbs(iBait);
-	iCons.Write(KBait);
+	iCons.Write(iUnicode ? KUnicodeBait : KBait);
 	}
 
 void CCmdSnake::Grow()
--- a/commands/swi/swi.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/swi/swi.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -56,3 +56,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+swi list $Quiet
--- a/commands/sysinfo/sysinfo.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/sysinfo/sysinfo.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -60,3 +60,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+sysinfo $Silent # Warnings about not being able to open TSY etc are acceptable so use $Silent
--- a/commands/uidinfo/uidinfo.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/uidinfo/uidinfo.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -32,3 +32,8 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+# Use the fileserver as it's guaranteed to be there
+uidinfo 0x100039e3 | export -s RESULT
+var RESULT == "0x100039e3 efile.exe^r^n" || var RESULT == "0x100039e3 EFile.exe^r^n" || $Error # Emulator capitalises differently, sigh
--- a/commands/usb/usb.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/usb/usb.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -24,3 +24,6 @@
 
 Copyright (c) 2010 Accenture. All rights reserved.
 
+==smoke-test
+
+usb $Quiet
--- a/commands/variant/variant.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/variant/variant.cif	Wed Oct 13 12:41:05 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/variant/variant.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/variant/variant.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -68,7 +68,8 @@
 	{ 0x2000DA56, DESC("5800") },
 	{ 0x2001F0A1, DESC("satio") }, // I assume this is what it shipped with...
 	{ 0x20029a73, DESC("n8") }, // Likewise...
-	{ 0x102734e3, DESC("qemu") },
+	{ 0x102734e3, DESC("qemu") }, // This is the same as H4! Doh...
+	{ 0x10286F79, DESC("beagle") },
 	};
 const TInt KMachineIdVariantCount = sizeof(KMachineIdVariants) / sizeof(TVariant);
 
--- a/commands/wslog/wslog.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/commands/wslog/wslog.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -42,3 +42,6 @@
 
 Copyright (c) 2009-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+wslog status $Quiet
--- a/core/builtins/cd.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/cd.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -16,11 +16,20 @@
 
 Change the current working directory.
 
+==long-description
+
+Note, C<cd> can be used to navigate between drives (eg C<cd e:\>), but the DOS approach of switching between drives with commands like C<c:> and C<e:> is also supported.
+
+The current working directory is stored in the environment variable C<PWD>, but you should use the cd command rather than updating this variable directly.
+
 ==argument filename directory
 
 The directory to change to.
 
+==see-also
+
+L<ls|ls>
+
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
-
--- a/core/builtins/chunkinfo.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/chunkinfo.cif	Wed Oct 13 12:41:05 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/builtins/ciftest.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -0,0 +1,98 @@
+# ciftest.cif
+# 
+# Copyright (c) 2010 Accenture. All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of the "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:
+# Accenture - Initial contribution
+#
+
+==name ciftest
+
+==short-description
+
+Run fshell command smoke tests.
+
+==long-description
+
+This command runs smoke-tests for any or all commands that define a C<==smoke-test> section in their CIF file. A C<==smoke-test> section defines a short snippet of fshell script which tests the basic functionality offered by the command. It can be as simple as running the command with no arguments to make sure nothing catastrophic is wrong, or it can be a more in-depth test of all the command's functionality, or anything in between.
+
+Example CIF file that supports ciftest:
+
+    ==name mycmd
+    
+    [...]
+    
+    ==smoke-test
+    
+    mycmd | export -s RESULT
+    var RESULT == "Expected results of running mycmd" || $Error
+
+The following environment variables are defined for convenience when ciftest runs a smoke-test section:
+
+=over 5
+
+=item * Error
+
+Expands to a string that will cause a test to fail. Additionally it prints the current environment, hence is useful to use when C<var> commands fail, as in the above example. Equivalent to something like C<env && error>.
+
+=item * SCRIPT_NAME
+
+The script name is appended with ":smoke-test", eg "cifname.cif:smoke-test".
+
+=item * SCRIPT_PATH, 0
+
+Set as in any other script.
+
+=item * SCRIPT_LINE
+
+Set as in any other script. Line numbers are relative to the start of the CIF file, not the first line of the smoke-test section.
+
+=item * Quiet
+
+Used to supress stdout from a command, for when you don't want it to appear in the smoketest results. Usage:
+
+    mynoisycommand $Quiet
+
+Equivalent to putting C<E<gt>/dev/null> on the end of the command.
+
+=item * Silent
+
+Supresses both stdout and stderr. Useful when an operation is expected to fail. Usage:
+
+    mycommand expectfailure $Silent && $Error
+
+Note how $Silent is combined with C<&& $Error> such that if the command actually succeeded where it was expected to fail, the $Error case would cause the script to abort.
+
+=item * Verbose
+
+Defined if the C<--verbose> option was given to ciftest. Example usage:
+
+    var Verbose defined && echo "About to test something-or-other"
+
+=back
+
+The environment used for running the smoke-test snippets is not shared between commands, so do not set things in one smoketest script and expect to be able to see them in another. (Ie the snippets are run as if with "fshell" not "source").
+
+==argument string command optional
+
+If specified, run the tests associated with the specified command. If not specified, run tests for all commands.
+
+==option bool v verbose
+
+Print information about every test even when they succeed. By default only failures are printed. Also causes a summary to be printed at the end. Scripts can also print extra information themselves if this flag is set, by checking for the C<Verbose> environment variable.
+
+==option bool k keep-going
+
+Rather than stop on the first failure, attempt to run all tests even if some of them fail. Only relevant if no command argument is given.
+
+==copyright
+
+Copyright (c) 2010 Accenture. All rights reserved.
+
+==smoke-test
+
+# Ciftest itself doesn't have a smoketest!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/builtins/ciftest.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -0,0 +1,192 @@
+// ciftest.cpp
+//
+// Copyright (c) 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "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:
+// Accenture - Initial contribution
+//
+
+#include "ciftest.h"
+#include "fshell.h"
+#include "command_factory.h"
+
+CCommandBase* CCmdCifTest::NewLC()
+	{
+	CCmdCifTest* self = new(ELeave) CCmdCifTest();
+	CleanupStack::PushL(self);
+	self->BaseConstructL();
+	return self;
+	}
+
+CCmdCifTest::~CCmdCifTest()
+	{
+	delete iCmd;
+	delete iParser;
+	delete iEnvForScript;
+	delete iCurrentCif;
+	iCifFiles.ResetAndDestroy();
+	}
+
+CCmdCifTest::CCmdCifTest()
+	: CCommandBase(EManualComplete | EReportAllErrors)
+	{
+	}
+
+const TDesC& CCmdCifTest::Name() const
+	{
+	_LIT(KName, "ciftest");	
+	return KName;
+	}
+
+void CCmdCifTest::ArgumentsL(RCommandArgumentList& aArguments)
+	{
+	aArguments.AppendStringL(iCmd, _L("command"));
+	}
+
+void CCmdCifTest::OptionsL(RCommandOptionList& aOptions)
+	{
+	aOptions.AppendBoolL(iVerbose, _L("verbose"));
+	aOptions.AppendBoolL(iKeepGoing, _L("keep-going"));
+	}
+
+void CCmdCifTest::DoRunL()
+	{
+	if (iCmd)
+		{
+		CCommandInfoFile* cif = CCommandInfoFile::NewL(FsL(), Env(), *iCmd);
+		TestCifL(cif); // Takes ownership
+		}
+	else
+		{
+		_LIT(KCifDir, "y:\\resource\\cif\\fshell\\");
+		TFindFile find(FsL());
+		CDir* dir = NULL;
+		TInt found = find.FindWildByDir(_L("*.cif"), KCifDir, dir);
+		while (found == KErrNone)
+			{
+			for (TInt i = 0; i < dir->Count(); i++)
+				{
+				iFileName.Copy(TParsePtrC(find.File()).DriveAndPath()); // The docs for TFindFile state you shouldn't need the extra TParsePtrC::DriveAndPath(). Sigh.
+				iFileName.Append((*dir)[i].iName);
+				iCifFiles.AppendL(iFileName.AllocLC());
+				CleanupStack::Pop();
+				}
+			delete dir;
+			dir = NULL;
+			found = find.FindWild(dir);
+			}
+		NextCif();
+		}
+	}
+
+void CCmdCifTest::NextCif()
+	{
+	if (iNextCif == iCifFiles.Count())
+		{
+		if (iVerbose)
+			{
+			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);
+		}
+	else
+		{
+		CCommandInfoFile* cif = NULL;
+		TRAPD(err, cif = CCommandInfoFile::NewL(FsL(), *iCifFiles[iNextCif]));
+		if (!err)
+			{
+			TRAP(err, TestCifL(cif));
+			if (err) PrintError(err, _L("Error setting up test for CIF %S"), iCifFiles[iNextCif]);
+			}
+		iNextCif++;
+		
+		if (err)
+			{
+			iFailures++;
+			TestCompleted(err);
+			}
+		}
+	}
+
+void CCmdCifTest::TestCifL(CCommandInfoFile* aCif)
+	{
+	iCurrentCif = aCif;
+	if (iVerbose) Printf(_L("Checking %S\r\n"), &aCif->CifFileName());
+
+	const TDesC& scriptData = aCif->SmokeTest();
+	if (scriptData.Length() == 0)
+		{
+		if (iVerbose) Printf(_L("Cif has no smoketest section\r\n"));
+		TestCompleted(KErrNone);
+		return;
+		}
+
+	iEnvForScript = CEnvironment::NewL(Env());
+	iEnvForScript->SetL(_L("Error"), _L("fshell -e 'echo \"Test failed, env is:\" && env && error'"));
+	iEnvForScript->SetL(_L("Quiet"), _L(">/dev/null"));
+	iEnvForScript->SetL(_L("Silent"), _L("2>&1 >/dev/null"));
+	iEnvForScript->Remove(_L("Verbose")); // In case it's ended up in our parent env
+	if (iVerbose) iEnvForScript->SetL(_L("Verbose"), 1);
+	iFileName.Copy(aCif->CifFileName());
+	iFileName.Append(_L(":smoke-test"));
+	TParsePtrC parse(iFileName);
+	iEnvForScript->SetL(KScriptName, parse.NameAndExt());
+	iEnvForScript->SetL(KScriptPath, parse.DriveAndPath());
+	iEnvForScript->SetL(_L("0"), iFileName);
+
+	iParser = CParser::NewL(CParser::EExportLineNumbers, scriptData, IoSession(), Stdin(), Stdout(), Stderr(), *iEnvForScript, gShell->CommandFactory(), this, aCif->GetSmokeTestStartingLineNumber());
+	iParser->Start();
+	}
+
+void CCmdCifTest::HandleParserComplete(CParser& /*aParser*/, const TError& aError)
+	{
+	TInt err = aError.Error();
+	if (err)
+		{
+		iFailures++;
+		PrintError(err, _L("%S failed at line %d"), &aError.ScriptFileName(), aError.ScriptLineNumber());
+		}
+	else
+		{
+		if (iVerbose)
+			{
+			Printf(_L("Smoketest for %S completed ok.\r\n"), &iCurrentCif->Name());
+			}
+		iPasses++;
+		}
+	TestCompleted(err);
+	}
+
+void CCmdCifTest::TestCompleted(TInt aError)
+	{
+	// Delete interim data
+	delete iEnvForScript;
+	iEnvForScript = NULL;
+	delete iParser;
+	iParser = NULL;
+	delete iCurrentCif;
+	iCurrentCif = NULL;
+
+	if (aError == KErrNone || iKeepGoing)
+		{
+		// Async call NextCif()
+		TRequestStatus* stat = &iStatus;
+		User::RequestComplete(stat, KErrNone);
+		SetActive();
+		}
+	else
+		{
+		Complete(aError);
+		}
+	}
+
+void CCmdCifTest::RunL()
+	{
+	NextCif();
+	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/builtins/ciftest.h	Wed Oct 13 12:41:05 2010 +0100
@@ -0,0 +1,56 @@
+// ciftest.h
+//
+// Copyright (c) 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "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:
+// Accenture - Initial contribution
+//
+
+#ifndef CIFTEST_H
+#define CIFTEST_H
+
+#include <fshell/ioutils.h>
+#include "parser.h"
+
+using namespace IoUtils;
+
+class CCmdCifTest : public CCommandBase, public MParserObserver
+	{
+public:
+	static CCommandBase* NewLC();
+	~CCmdCifTest();
+private:
+	CCmdCifTest();
+	void TestCifL(CCommandInfoFile* aCif);
+	void NextCif();
+	void TestCompleted(TInt aError);
+private: // From CCommandBase.
+	void RunL();
+	virtual const TDesC& Name() const;
+	virtual void DoRunL();
+	virtual void ArgumentsL(RCommandArgumentList& aArguments);
+	virtual void OptionsL(RCommandOptionList& aOptions);
+private: // From MParserObserver.
+	virtual void HandleParserComplete(CParser& aParser, const TError& aError);
+
+private:
+	HBufC* iCmd;
+	TBool iVerbose;
+	TBool iKeepGoing;
+
+	TFileName iFileName;
+	CCommandInfoFile* iCurrentCif;
+	CParser* iParser;
+	CEnvironment* iEnvForScript;
+	RPointerArray<HBufC> iCifFiles;
+
+	TInt iPasses;
+	TInt iFailures;
+	TInt iNextCif;
+	};
+
+#endif
--- a/core/builtins/clear.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/clear.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+cls --formfeed $Quiet
--- a/core/builtins/compare.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/compare.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/console.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -40,3 +40,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+console $Quiet
--- a/core/builtins/date.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/date.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -63,7 +63,7 @@
 
 ==option bool r raw
 
-Displays the time and date as the number of microseconds since 2000AD (Symbian OS's native time format).
+Displays the time and date as the number of microseconds since 0AD nominal Gregorian (Symbian OS's native time format).
 
 ==option bool j just-display
 
@@ -71,17 +71,22 @@
 
 ==option int64 R raw-set
 
-Sets the time and date from a number corresponding to the number of microseconds since 2000AD (Symbian OS's native time format).
+Sets the time and date from a number corresponding to the number of microseconds since 0AD nominal Gregorian (Symbian OS's native time format).
 
 ==option bool t timestamp
 
 Display the date in timestamp format C<YYYYMMDD-HHMM.SS> suitable for use in a file name.
 
-==option bool k kern
+==option bool Y y2k
 
-Only applicable with C<--raw> and/or C<--raw-set>. Instead of using 2000AD as the epoc, use 0AD nominal Gregorian, the kernel's internal native time format.
+Only applicable with C<--raw> and/or C<--raw-set>. Instead of using 0AD as the epoc, assume 2000AD. Some APIs use 2000 instead of 0AD as the epoc so this option is occasionally useful for converting between the two.
 
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+date $Quiet
+date --raw $Quiet
+date --timestamp $Quiet
--- a/core/builtins/dialog.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/dialog.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -60,3 +60,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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/driver.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/dump.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/echo.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/exists.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/exit.cif	Wed Oct 13 12:41:05 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/ls.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/ls.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -20,8 +20,6 @@
 
 By default ls will columnize the output list. On very large directory listings this may cause the ls command to run out of memory. If this happens, try using the C<-1>/C<--one> option, which will not attempt to format the output.
 
-Note, C<cd> can be used to navigate between drives, but the DOS approach of switching between drives with commands like C<c:> and C<e:> is also supported.
-
 ==argument filename dir_name optional
 
 The directory to list (defaults to the current working directory).
@@ -58,12 +56,16 @@
 
 =back
 
-For example, C<d--s-> indicates a drectory with the system bit set.
+For example, C<d--s-> indicates a directory with the system bit set.
 
 ==option bool H human
 
 Display file sizes in human readable form.
 
+==option bool N no-localise
+
+Do not display localised time and date formats; instead use microseconds from 0AD nominal Gregorian.
+
 ==option bool 1 one
 
 Outputs one entry per line rather than trying to columnise the output. Is implied if C<stdout> is redirected or if C<--long> is specified.
@@ -72,6 +74,10 @@
 
 Recursively list any directories that are encountered. Currently only supported for C<--long> listings.
 
+==see-also
+
+L<cd|cd>
+
 ==copyright
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
--- a/core/builtins/match.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/match.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/ps.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -104,3 +104,6 @@
 
 Copyright (c) 2005-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+ps $Quiet
--- a/core/builtins/repeat.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/repeat.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/rm.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/rom.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/sort.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/source.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/svrinfo.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -28,3 +28,6 @@
 
 Copyright (c) 2006-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+svrinfo $Quiet
--- a/core/builtins/touch.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/touch.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/var.cif	Wed Oct 13 12:41:05 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	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/version.cif	Wed Oct 13 12:41:05 2010 +0100
@@ -24,3 +24,6 @@
 
 Copyright (c) 2008-2010 Accenture. All rights reserved.
 
+==smoke-test
+
+version $Quiet
--- a/core/builtins/which.cif	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/builtins/which.cif	Wed Oct 13 12:41:05 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/docs/persistent_consoles.pod	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/docs/persistent_consoles.pod	Wed Oct 13 12:41:05 2010 +0100
@@ -79,7 +79,7 @@
 	my pcons      fshell::pcons_00  -       -
 	fshell_pcons  fshell(2)         -       -
 
-This equivilant to typing C<pcons new fshell_pcons>.
+This equivalent to typing C<pcons new fshell_pcons>.
 
 You can also specify a process to launch when using C<pcons new> by specifying a command and arguments after the console name. For example,
 
--- a/core/group/bld.inf	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/group/bld.inf	Wed Oct 13 12:41:05 2010 +0100
@@ -81,6 +81,7 @@
 ..\builtins\ymodem.cif            z:\resource\cif\fshell\ymodem.cif
 ..\builtins\version.cif           z:\resource\cif\fshell\version.cif
 ..\builtins\undertaker.cif        z:\resource\cif\fshell\undertaker.cif
+..\builtins\ciftest.cif           z:\resource\cif\fshell\ciftest.cif
 
 #ifdef FSHELL_CORE_SUPPORT_CHUNKINFO
 ..\builtins\chunkinfo.cif         z:\resource\cif\fshell\chunkinfo.cif
--- a/core/group/fshell_core.iby	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/group/fshell_core.iby	Wed Oct 13 12:41:05 2010 +0100
@@ -168,6 +168,7 @@
 #ifdef FSHELL_CORE_SUPPORT_BUILTIN_REBOOT
 FSHELL_COMMAND_INFO_FILE(fshell,reboot.cif)
 #endif
+FSHELL_COMMAND_INFO_FILE(fshell,ciftest.cif)
 
 #ifdef FSHELL_REPLACE_ECONS
 FSHELL_EXECUTABLE_AS_DATA(iocons.dll,iocons.dll)
@@ -189,4 +190,29 @@
 FSHELL_DATA_FILE(ZSYSTEM\perl\lib\Fshell\TextServerClient.pm, system\perl\lib\Fshell\TextServerClient.pm)
 #endif
 
+#ifdef FSHELL_INCLUDE_TESTS
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\args.script, system\console\scripts\args.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\checkargs.script, system\console\scripts\checkargs.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\setenv.script, system\console\scripts\setenv.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\addenv.script, system\console\scripts\addenv.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\printerror.script, system\console\scripts\printerror.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\fshell-basic-test.script, system\console\scripts\fshell-basic-test.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\fshell-last-test.script, system\console\scripts\fshell-last-test.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\fshell-ccommandbase-test.script, system\console\scripts\fshell-ccommandbase-test.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\fshell-unicode-test.script, system\console\scripts\fshell-unicode-test.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\fshell-scriptcif-test.script, system\console\scripts\fshell-scriptcif-test.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\smoketest.script, system\console\scripts\smoketest.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\tscriptargs.script, system\console\scripts\tscriptargs.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\tlotsofscriptargs.script, system\console\scripts\tlotsofscriptargs.script)
+FSHELL_DATA_FILE(ZSYSTEM\console\scripts\errordef.script, system\console\scripts\errordef.script)
+FSHELL_COMMAND_INFO_FILE(fshell,tenvarguments.cif)
+
+FSHELL_EXECUTABLE_FILE(tlast.exe)
+FSHELL_EXECUTABLE_FILE(tconsole.exe)
+FSHELL_EXECUTABLE_FILE(fshell_tinteger.exe)
+FSHELL_EXECUTABLE_FILE(tfshellarguments.exe)
+FSHELL_EXECUTABLE_FILE(tenvarguments.exe)
+FSHELL_EXECUTABLE_FILE(tnoncifenvarguments.exe)
 #endif
+
+#endif
--- a/core/src/command_factory.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/command_factory.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -24,7 +24,7 @@
 #include "xmodem.h"
 #include "ymodem.h"
 #include "version.h"
-
+#include "ciftest.h"
 
 //
 // Constants.
@@ -301,7 +301,7 @@
 	AddThreadCommandL(CCmdStart::NewLC);
 	AddThreadCommandL(CCmdCompare::NewLC);
 	AddThreadCommandL(CCmdTime::NewLC);
-	AddThreadCommandL(CCmdRepeat::NewLC);
+	AddThreadCommandL(CCmdRepeat::NewLC); // TODO: Should this have EUpdateEnvironment? It seems weird that source and foreach do but repeat doesn't. -TomS
 	AddThreadCommandL(CCmdDebug::NewLC);
 	AddThreadCommandL(CCmdReadMem::NewLC);
 	AddThreadCommandL(CCmdE32Header::NewLC);
@@ -323,6 +323,7 @@
 #ifdef FSHELL_CORE_SUPPORT_BUILTIN_REBOOT
 	AddThreadCommandL(CCmdReboot::NewLC);
 #endif
+	AddThreadCommandL(CCmdCifTest::NewLC);
 
 	// Add some DOS-style namings of common commands.
 	AddThreadCommandL(_L("del"), CCmdRm::NewLC, CCommandConstructorBase::EAttAlias);
--- a/core/src/commands.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/commands.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -228,7 +228,14 @@
 			{
 			iFormatter->AppendFormatL(_L("%+ 10d  "), aEntry.iSize);
 			}
-		aEntry.iModified.FormatL(iTempBuf, _L("%1%/1%2%/2%3 %H%:1%T%:2%S "));
+		if (iOptNoLocalise)
+			{
+			iTempBuf.Format(_L("%+ 19Ld "), aEntry.iModified.Int64()); // 19 so the output is the same width whether or not iOptNoLocalise is specified (assuming roughly European date settings)
+			}
+		else
+			{
+			aEntry.iModified.FormatL(iTempBuf, _L("%1%/1%2%/2%3 %H%:1%T%:2%S "));
+			}
 		TPtrC pathRelativeToBaseDir = iFileName.Mid(iBaseDir.Length());
 		iFormatter->AppendFormatL(_L("%S%S%S\r\n"), &iTempBuf, &pathRelativeToBaseDir, &aEntry.iName);
 		}
@@ -289,13 +296,15 @@
 	_LIT(KCmdLsOptLong, "long");
 	_LIT(KCmdLsOptHuman, "human");
 	_LIT(KCmdLsOptOnePerLine, "one");
-	_LIT(KCmdLsOpRecurse, "recurse");
+	_LIT(KCmdLsOptRecurse, "recurse");
+	_LIT(KCmdLsOptNoLocalise, "no-localise");
 
 	aOptions.AppendBoolL(iOptAll, KCmdLsOptAll);
 	aOptions.AppendBoolL(iOptLong, KCmdLsOptLong);
 	aOptions.AppendBoolL(iOptHuman, KCmdLsOptHuman);
 	aOptions.AppendBoolL(iOptOnePerLine, KCmdLsOptOnePerLine);
-	aOptions.AppendBoolL(iOptRecurse, KCmdLsOpRecurse);
+	aOptions.AppendBoolL(iOptRecurse, KCmdLsOptRecurse);
+	aOptions.AppendBoolL(iOptNoLocalise, KCmdLsOptNoLocalise);
 	}
 
 void CCmdLs::ArgumentsL(RCommandArgumentList& aArguments)
@@ -734,23 +743,18 @@
 		{
 		TFileName2& fileName = iFileNames[i];
 		LeaveIfFileNotFound(fileName);
-		if (iRecurse)
+		if (fileName.IsDirL(FsL()))
 			{
-			if (fileName.IsDirL(FsL()))
+			if (iRecurse)
 				{
 				fileName.SetTypeL(TFileName2::EDirectory);
 				}
 			else
 				{
-				PrintError(KErrArgument, _L("Invalid use of \"-r\" option - \"%S\" is not a directory."), &fileName);
+				PrintError(KErrArgument, _L("Couldn't remove \"%S\" because it is a directory - use \"rm -r\" or \"rmdir\" instead."), &fileName);
 				User::Leave(KErrArgument);
 				}
 			}
-		else if (fileName.IsDirL(FsL()))
-			{
-			PrintError(KErrArgument, _L("Couldn't remove \"%S\" because it is a directory - use \"rm -r\" or \"rmdir\" instead."), &fileName);
-			User::Leave(KErrArgument);
-			}
 
 		TInt err = DoDelete(fileName);
 		if (err == KErrAccessDenied && iForce)
@@ -776,7 +780,7 @@
 TInt CCmdRm::DoDelete(const TDesC& aFileName)
 	{
 	TInt err;
-	if (iRecurse)
+	if (iRecurse && aFileName[aFileName.Length()-1] == '\\')
 		{
 		err = iFileMan->RmDir(aFileName);
 		}
@@ -2384,7 +2388,7 @@
 	if (iRaw)
 		{
 		TInt64 time = aTime.Int64();
-		if (iUseKernelFormat) time += KY2kInMicroSeconds;
+		if (iUseY2k) time -= KY2kInMicroSeconds;
 		Printf(_L("%Ld\r\n"), time);
 		}
 	else if (iUseTimestampFormat)
@@ -2441,7 +2445,7 @@
 	else
 		{
 		_LIT(KSettingError, "Cannot set the time");
-		if (iUseKernelFormat) iRawTimeToSet -= KY2kInMicroSeconds;
+		if (iUseY2k) iRawTimeToSet += KY2kInMicroSeconds;
 		TTime time(iRawTimeToSet);
 		if (iDateToSet)
 			{
@@ -2500,7 +2504,7 @@
 	_LIT(KCmdDateOptJustDisplay, "just-display");
 	_LIT(KCmdDateOptSetRaw, "raw-set");
 	_LIT(KCmdDateOptTimestamp, "timestamp");
-	_LIT(KCmdDateOptKern, "kern");
+	_LIT(KCmdDateOptY2k, "y2k");
 
 	aOptions.AppendBoolL(iUniversalTime, KCmdDateOptUniversal);
 	aOptions.AppendStringL(iDateToSet, KCmdDateOptSet);
@@ -2512,7 +2516,7 @@
 	aOptions.AppendBoolL(iJustDisplay, KCmdDateOptJustDisplay);
 	aOptions.AppendIntL(iRawTimeToSet, KCmdDateOptSetRaw);
 	aOptions.AppendBoolL(iUseTimestampFormat, KCmdDateOptTimestamp);
-	aOptions.AppendBoolL(iUseKernelFormat, KCmdDateOptKern);
+	aOptions.AppendBoolL(iUseY2k, KCmdDateOptY2k);
 	}
 
 
--- a/core/src/commands.h	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/commands.h	Wed Oct 13 12:41:05 2010 +0100
@@ -91,6 +91,7 @@
 	TBool iOptHuman;
 	TBool iOptOnePerLine;
 	TBool iOptRecurse;
+	TBool iOptNoLocalise;
 	};
 
 
@@ -556,7 +557,7 @@
 	TBool iJustDisplay;
 	TInt64 iRawTimeToSet;
 	TBool iUseTimestampFormat;
-	TBool iUseKernelFormat;
+	TBool iUseY2k;
 	};
 
 
--- a/core/src/fshell.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/fshell.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -572,9 +572,9 @@
 	aArguments.AppendStringL(iScriptArgs, KArgArgs);
 	}
 	
-void CShell::StdinChange(TUint)
+void CShell::StdinChange(TUint aChange)
 	{
-	if (iLineEditor && !iIgnoreNextStdinChange)
+	if (iLineEditor && !iIgnoreNextStdinChange && (aChange & RIoReadHandle::EGainedForeground)) // We don't care if the console has just resized
 		{
 		iLineEditor->Redraw();
 		}
--- a/core/src/fshell.mmp	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/fshell.mmp	Wed Oct 13 12:41:05 2010 +0100
@@ -76,6 +76,7 @@
 source          xmodem.cpp
 source          ymodem.cpp
 source          version.cpp
+source          ciftest.cpp
 
 // There doesn't seem to be a nice way of turning the platform into a string, like you have $(PLATFORM) in extension makefiles, sigh.
 #if defined(WINSCW)
--- a/core/src/parser.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/parser.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -52,9 +52,9 @@
 	{
 	}
 
-CParser* CParser::NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver)
+CParser* CParser::NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber)
 	{
-	CParser* self = new(ELeave) CParser(aMode, aDes, aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver);
+	CParser* self = new(ELeave) CParser(aMode, aDes, aIoSession, aStdin, aStdout, aStderr, aEnv, aFactory, aObserver, aStartingLineNumber);
 	CleanupStack::PushL(self);
 	self->ConstructL();
 	CleanupStack::Pop();
@@ -78,8 +78,8 @@
 		}
 	}
 
-CParser::CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver)
-	: iMode(aMode), iData(aDes), iIoSession(aIoSession), iStdin(aStdin), iStdout(aStdout), iStderr(aStderr), iEnv(aEnv), iFactory(aFactory), iObserver(aObserver), iCompletionError(aStderr, aEnv), iNextLineNumber(1)
+CParser::CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber)
+	: iMode(aMode), iData(aDes), iIoSession(aIoSession), iStdin(aStdin), iStdout(aStdout), iStderr(aStderr), iEnv(aEnv), iFactory(aFactory), iObserver(aObserver), iCompletionError(aStderr, aEnv), iNextLineNumber(aStartingLineNumber)
 	{
 	}
 
--- a/core/src/parser.h	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/src/parser.h	Wed Oct 13 12:41:05 2010 +0100
@@ -41,7 +41,7 @@
 		EExportLineNumbers	= 0x00000004
 		};
 public:
-	static CParser* NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver);
+	static CParser* NewL(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber = 1);
 	~CParser();
 	void Start();
 	void Start(TBool& aIsForeground);
@@ -62,7 +62,7 @@
 		EAndOr
 		};
 private:
-	CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver);
+	CParser(TUint aMode, const TDesC& aDes, RIoSession& aIoSession, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, IoUtils::CEnvironment& aEnv, CCommandFactory& aFactory, MParserObserver* aObserver, TInt aStartingLineNumber=1);
 	void ConstructL();
 	void CreateNextPipeLine(TBool* aIsForeground);
 	void CreateNextPipeLineL(TBool* aIsForeground);
--- a/core/tsrc/fshell-basic-test.script	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/tsrc/fshell-basic-test.script	Wed Oct 13 12:41:05 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
--- a/core/tsrc/smoketest.script	Mon Sep 20 16:46:34 2010 +0100
+++ b/core/tsrc/smoketest.script	Wed Oct 13 12:41:05 2010 +0100
@@ -18,3 +18,4 @@
 fshell -k $SCRIPT_PATH\fshell-ccommandbase-test.script
 fshell -k $SCRIPT_PATH\fshell-unicode-test.script
 fshell -k $SCRIPT_PATH\fshell-scriptcif-test.script
+ciftest -k
--- a/documentation/change_history.pod	Mon Sep 20 16:46:34 2010 +0100
+++ b/documentation/change_history.pod	Wed Oct 13 12:41:05 2010 +0100
@@ -20,8 +20,20 @@
 
 =item *
 
+Commands can now define a C<==smoke-test> section in their CIF files, which defines a snippet of fshell script that will be run as part of C<fshell smoketest> or by invoking L<ciftest|commands::ciftest> directly. See the ciftest documentation for more details.
+
+=item *
+
+Fixed crash in fed's handling of UTF-8 sequences split over a block boundary.
+
+=item *
+
 Added support for automatically starting a USB personality to vt100usbcons via a new C<personality=x> key-value pair. See the L<vt100usbcons|vt100cons/usb variant> documentation for more information.
 
+=item *
+
+Added support for beagleboard to sf\3tshell platform.
+
 =back
 
 =head2 Release 001
--- a/libraries/iosrv/bwins/iocliu.def	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/bwins/iocliu.def	Wed Oct 13 12:41:05 2010 +0100
@@ -550,4 +550,7 @@
 	?KeyPressed@MCommandExtensionsV2@IoUtils@@UAEXII@Z @ 549 NONAME ; void IoUtils::MCommandExtensionsV2::KeyPressed(unsigned int, unsigned int)
 	?ReadKey@CCommandBase@IoUtils@@QAEIXZ @ 550 NONAME ; unsigned int IoUtils::CCommandBase::ReadKey(void)
 	?Normalize@TFileName2@IoUtils@@QAEXAAVRFs@@@Z @ 551 NONAME ; void IoUtils::TFileName2::Normalize(class RFs &)
+	?SmokeTest@CCommandInfoFile@IoUtils@@QBEABVTDesC16@@XZ @ 552 NONAME ; class TDesC16 const & IoUtils::CCommandInfoFile::SmokeTest(void) const
+	?CifFileName@CCommandInfoFile@IoUtils@@QBEABVTDesC16@@XZ @ 553 NONAME ; class TDesC16 const & IoUtils::CCommandInfoFile::CifFileName(void) const
+	?GetSmokeTestStartingLineNumber@CCommandInfoFile@IoUtils@@QBEHXZ @ 554 NONAME ; int IoUtils::CCommandInfoFile::GetSmokeTestStartingLineNumber(void) const
 
--- a/libraries/iosrv/client/command_base.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/client/command_base.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -416,6 +416,7 @@
 			{
 			*(TInt*)iValue = value;
 			}
+		delete aString; // Because the command class knows nothing about how enums are converted from HBufCs in this case, it cannot be responsible for deleting the HBufC like it would be for a normal string argument. So we delete it here.
 		iIsSet = ETrue;
 		}
 	else // string
@@ -2392,8 +2393,8 @@
 			case KValueTypeEnum:
 				{
 				HBufC* string = ReadStringLC(aLex, EDisallowLeadingHyphen);
-				aValue.SetValueL(string);	
-				CleanupStack::PopAndDestroy(string);
+				aValue.SetValueL(string);
+				CleanupStack::Pop(string); // SetValueL takes ownership
 				break;
 				}
 			default:
--- a/libraries/iosrv/client/command_info_file.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/client/command_info_file.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -27,6 +27,7 @@
 _LIT(KCmndLongDescription, "long-description");
 _LIT(KCmndSeeAlso, "see-also");
 _LIT(KCmndCopyright, "copyright");
+_LIT(KCmndSmokeTest, "smoke-test");
 _LIT(KCmndArgument, "argument");
 _LIT(KCmndOption, "option");
 _LIT(KCmndInclude, "include");
@@ -315,6 +316,23 @@
 			{
 			iCopyright.Set(TextToNextCommand(aLex));
 			}
+		else if (command == KCmndSmokeTest)
+			{
+			// Hmm no easy way to get the line number we're currently on
+			iSmokeTestLineNumber = 1;
+			TLex lex(aLex);
+			lex.Inc(-aLex.Offset()); // Only way to put a TLex back to the beginning!
+			TPtrC preceding = lex.Remainder().Left(aLex.Offset());
+			const TUint16* ptr = preceding.Ptr();
+			const TUint16* end = ptr + preceding.Length();
+			while (ptr != end)
+				{
+				if (*ptr++ == '\n') iSmokeTestLineNumber++;
+				}
+			// At this point iSmokeTestLineNumber points to the "==smoketest" line - add 2 to skip this line and the blank line below it
+			iSmokeTestLineNumber += 2;
+			iSmokeTest.Set(TextToNextCommand(aLex));
+			}
 		else if (command == KCmndArgument)
 			{
 			ReadArgumentL(aLex, aFileName);
@@ -813,6 +831,16 @@
 	return iCopyright;
 	}
 
+EXPORT_C const TDesC& CCommandInfoFile::SmokeTest() const
+	{
+	return iSmokeTest;
+	}
+
+EXPORT_C TInt CCommandInfoFile::GetSmokeTestStartingLineNumber() const
+	{
+	return iSmokeTestLineNumber;
+	}
+
 EXPORT_C const RCommandArgumentList& CCommandInfoFile::Arguments()
 	{
 	return iArguments;
@@ -891,3 +919,8 @@
 	: iFileName(aParent.iFileName), iParent(&aParent)
 	{
 	}
+
+EXPORT_C const TDesC& CCommandInfoFile::CifFileName() const
+	{
+	return iFileName;
+	}
--- a/libraries/iosrv/eabi/iocliu.def	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/eabi/iocliu.def	Wed Oct 13 12:41:05 2010 +0100
@@ -626,4 +626,7 @@
 	_ZTIN7IoUtils20MCommandExtensionsV2E @ 625 NONAME
 	_ZTVN7IoUtils20MCommandExtensionsV2E @ 626 NONAME
 	_ZN7IoUtils10TFileName29NormalizeER3RFs @ 627 NONAME
+	_ZNK7IoUtils16CCommandInfoFile9SmokeTestEv @ 628 NONAME
+	_ZNK7IoUtils16CCommandInfoFile11CifFileNameEv @ 629 NONAME
+	_ZNK7IoUtils16CCommandInfoFile30GetSmokeTestStartingLineNumberEv @ 630 NONAME
 
--- a/libraries/iosrv/inc/ioutils.h	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/inc/ioutils.h	Wed Oct 13 12:41:05 2010 +0100
@@ -579,11 +579,14 @@
 	IMPORT_C static CCommandInfoFile* NewL(RFs& aFs, const TDesC& aFileName);
 	IMPORT_C static CCommandInfoFile* NewL(RFs& aFs, const CEnvironment& aEnvironment, const TDesC& aCommandName);
 	IMPORT_C ~CCommandInfoFile();
+	IMPORT_C const TDesC& CifFileName() const;
 	IMPORT_C const TDesC& Name() const;
 	IMPORT_C const TDesC& ShortDescription() const;
 	IMPORT_C const TDesC& LongDescription() const;
 	IMPORT_C const TDesC& SeeAlso() const;
 	IMPORT_C const TDesC& Copyright() const;
+	IMPORT_C const TDesC& SmokeTest() const;
+	IMPORT_C TInt GetSmokeTestStartingLineNumber() const;
 	IMPORT_C const RCommandArgumentList& Arguments();
 	IMPORT_C const RCommandOptionList& Options() const;
 	IMPORT_C void AssignL(RCommandArgumentList& aArguments, RCommandOptionList& aOptions) const;
@@ -609,6 +612,8 @@
 	TPtrC iLongDescription;
 	TPtrC iSeeAlso;
 	TPtrC iCopyright;
+	TPtrC iSmokeTest;
+	TInt iSmokeTestLineNumber;
 	RCommandArgumentList iArguments;
 	RCommandOptionList iOptions;
 	RArray<RBuf> iBufs;
--- a/libraries/iosrv/server/console.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/libraries/iosrv/server/console.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -581,7 +581,7 @@
 //______________________________________________________________________________
 //						TConsoleSetTitleRequest
 CIoConsole::TConsoleSetTitleRequest::TConsoleSetTitleRequest(MIoWriter& aWriter)
-	: TConsoleWriterRequest(aWriter)
+	: TConsoleWriterRequest(aWriter), iTitle(NULL)
 	{
 	}
 
--- a/plugins/consoles/docs/consoles.pod	Mon Sep 20 16:46:34 2010 +0100
+++ b/plugins/consoles/docs/consoles.pod	Wed Oct 13 12:41:05 2010 +0100
@@ -222,6 +222,8 @@
 
 Cons:
 
+=over 5
+
 =item *
 
 Requires Carbide and the Carbide Terminal Keyboard plugin to be installed.
--- a/plugins/consoles/tefcons/tefcons.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/plugins/consoles/tefcons/tefcons.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -47,6 +47,8 @@
 	{
 	}
 
+_LIT(KDefaultLog, "c:\\logs\\testexecute\\fshell.htm");
+
 TInt CTefConsole::Create(const TDesC& aTitle, TSize /*aSize*/)
 	{
 	iLogger = new CTestExecuteLogger();
@@ -56,7 +58,11 @@
 	iLogger->SetLoggerOptions(logMode);
 	TInt err = iLogger->Connect();
 	if (err) return err;
-	const TDesC& logFilePath(aTitle);
+	TPtrC logFilePath(KDefaultLog);
+	if (aTitle.Left(5) == _L("name="))
+		{
+		logFilePath.Set(aTitle.Mid(5));
+		}
 	err = iLogger->HtmlLogger().CreateLog(logFilePath, RTestExecuteLogServ::ELogModeAppend);
 	if (err) return err;
 	iLogger->HtmlLogger().SetLogLevel(TLogSeverity(logLevel));
--- a/plugins/consoles/vt100cons/src/usb/vtc_usb.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/plugins/consoles/vt100cons/src/usb/vtc_usb.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -15,6 +15,8 @@
 #include "vtc_usb.h"
 #include <usbclassuids.h>
 
+; // This semicolon is to hack around usbclassuids.h having a typo in it that *still* isn't fixed in s60\tb92 and ncp builds...
+
 EXPORT_C TAny* NewConsole()
 	{
 	return new CUsbConsole;
--- a/plugins/consoles/win32cons/src/console.cpp	Mon Sep 20 16:46:34 2010 +0100
+++ b/plugins/consoles/win32cons/src/console.cpp	Wed Oct 13 12:41:05 2010 +0100
@@ -25,6 +25,7 @@
 
 CWin32Console::~CWin32Console()
 	{
+	CleanupUnderlyingConsole();
 	iWin32.FreeConsole();
 	// when call FreeConsole(), it should cause the reader thread to exit as the
 	// console read handle will become invalid, and windows will complete the
@@ -61,7 +62,8 @@
 	err = iReaderThread.Create(KReadThreadName, ReaderThread, 0x800, KMinHeapSize, KMinHeapSize*4, &iThreadParams);
 	if (err!=KErrNone) return err;
 	iReaderThread.Resume();	
-	
+	CleanupUnderlyingConsole();
+
 	return KErrNone;
 	}
 
--- a/tools/baserom	Mon Sep 20 16:46:34 2010 +0100
+++ b/tools/baserom	Wed Oct 13 12:41:05 2010 +0100
@@ -152,6 +152,11 @@
 	open TSHELL_NEW, ">$newObyName" or die "Can't write to $newObyName: $!\n";
 
 	print "Creating tshell_$obyName.oby\n";
+	
+	if ($extraInclude) {
+		print TSHELL_NEW "#include \"$obyName/$extraInclude\"\n";
+	}
+
 
 	while (my $line = <TSHELL_OBY>) {
 		print TSHELL_NEW "$line";
@@ -221,7 +226,6 @@
 		open IBYCPP, "$cmd |" or die "Can't run '$cmd': $!\n";
 		while (my $line = <IBYCPP>) {
 			chomp $line;
-			#print "TOMSCI: $line\n";
 			if ($line =~ m/^BASEROMHASHINCLUDE\s+[<"](.*)[>"]/ ) {
 				my $iby = $1;
 				if (!$ibysProcessed{lc($iby)}) {