--- a/commands/clipboard/clipboard.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/clipboard/clipboard.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/drvinfo/drvinfo.cif Fri Sep 17 20:28:05 2010 +0100
@@ -32,3 +32,6 @@
Copyright (c) 2007-2010 Accenture. All rights reserved.
+==smoke-test
+
+drvinfo $Quiet
--- a/commands/focus/focus.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/focus/focus.cif Fri Sep 17 20:28:05 2010 +0100
@@ -44,3 +44,6 @@
Copyright (c) 2008-2010 Accenture. All rights reserved.
+==smoke-test
+
+focus $Quiet
--- a/commands/fzip/fzip.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/fzip/fzip.cif Fri Sep 17 20:28:05 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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/fzip/fzip.cpp Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/fzip/fzip.h Fri Sep 17 20:28:05 2010 +0100
@@ -56,6 +56,7 @@
TFileName2 iArchive;
TFileName2 iUnzipPath;
RArray<TFileName2> iFileToZip;
+ TBool iOverwrite;
};
--- a/commands/grabscreen/grabscreen.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/grabscreen/grabscreen.cif Fri Sep 17 20:28:05 2010 +0100
@@ -58,3 +58,6 @@
Copyright (c) 2008-2010 Accenture. All rights reserved.
+==smoke-test
+
+grabscreen > /dev/null
--- a/commands/hal/hal.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/hal/hal.cif Fri Sep 17 20:28:05 2010 +0100
@@ -60,3 +60,6 @@
Copyright (c) 2009-2010 Accenture. All rights reserved.
+==smoke-test
+
+hal $Quiet
--- a/commands/iap/iap.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/iap/iap.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/kerninfo/kerninfo.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/listapps/listapps.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/localdrive/localdrive.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/localdrive/localdrive.cpp Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/mrouter/mrouter.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/rcomm/rcomm.cif Fri Sep 17 20:28:05 2010 +0100
@@ -74,3 +74,6 @@
Copyright (c) 2007-2010 Accenture. All rights reserved.
+==smoke-test
+
+rcomm $Quiet
--- a/commands/rconn/rconn.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/rconn/rconn.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/screenmode/screenmode.cif Fri Sep 17 20:28:05 2010 +0100
@@ -48,3 +48,6 @@
Copyright (c) 2009-2010 Accenture. All rights reserved.
+==smoke-test
+
+screenmode list $Quiet
--- a/commands/swi/swi.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/swi/swi.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/sysinfo/sysinfo.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/uidinfo/uidinfo.cif Fri Sep 17 20:28:05 2010 +0100
@@ -32,3 +32,7 @@
Copyright (c) 2009-2010 Accenture. All rights reserved.
+==smoke-test
+
+uidinfo 0x100041af | export -s RESULT
+var RESULT == "0x100041af EKern.exe^r^n" || $Error
--- a/commands/usb/usb.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/usb/usb.cif Fri Sep 17 20:28:05 2010 +0100
@@ -24,3 +24,6 @@
Copyright (c) 2010 Accenture. All rights reserved.
+==smoke-test
+
+usb $Quiet
--- a/commands/variant/variant.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/variant/variant.cif Fri Sep 17 20:28: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/wslog/wslog.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/commands/wslog/wslog.cif Fri Sep 17 20:28:05 2010 +0100
@@ -42,3 +42,6 @@
Copyright (c) 2009-2010 Accenture. All rights reserved.
+==smoke-test
+
+wslog status $Quiet
--- a/core/builtins/chunkinfo.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/chunkinfo.cif Fri Sep 17 20:28: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 Fri Sep 17 20:28: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 Fri Sep 17 20:28: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 Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/clear.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/compare.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/console.cif Fri Sep 17 20:28:05 2010 +0100
@@ -40,3 +40,6 @@
Copyright (c) 2006-2010 Accenture. All rights reserved.
+==smoke-test
+
+console $Quiet
--- a/core/builtins/date.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/date.cif Fri Sep 17 20:28:05 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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/dialog.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/driver.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/dump.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/echo.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/exists.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/exit.cif Fri Sep 17 20:28: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/match.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/match.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/ps.cif Fri Sep 17 20:28:05 2010 +0100
@@ -104,3 +104,6 @@
Copyright (c) 2005-2010 Accenture. All rights reserved.
+==smoke-test
+
+ps $Quiet
--- a/core/builtins/repeat.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/repeat.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/rm.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/rom.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/sort.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/source.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/svrinfo.cif Fri Sep 17 20:28:05 2010 +0100
@@ -28,3 +28,6 @@
Copyright (c) 2006-2010 Accenture. All rights reserved.
+==smoke-test
+
+svrinfo $Quiet
--- a/core/builtins/touch.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/touch.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/var.cif Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/version.cif Fri Sep 17 20:28:05 2010 +0100
@@ -24,3 +24,6 @@
Copyright (c) 2008-2010 Accenture. All rights reserved.
+==smoke-test
+
+version $Quiet
--- a/core/builtins/which.cif Thu Sep 16 15:06:08 2010 +0100
+++ b/core/builtins/which.cif Fri Sep 17 20:28: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/group/bld.inf Thu Sep 16 15:06:08 2010 +0100
+++ b/core/group/bld.inf Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/group/fshell_core.iby Fri Sep 17 20:28: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)
--- a/core/src/command_constructors.cpp Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_constructors.cpp Fri Sep 17 20:28:05 2010 +0100
@@ -66,32 +66,32 @@
// CThreadCommandConstructor.
//
-CThreadCommandConstructor* CThreadCommandConstructor::NewLC(TCommandConstructor aConstructor, TUint aFlags)
+CThreadCommandConstructor* CThreadCommandConstructor::NewLC(TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
{
CCommandBase* command = (*aConstructor)();
- CThreadCommandConstructor* self = CThreadCommandConstructor::NewLC(command->Name(), aConstructor, aFlags);
+ CThreadCommandConstructor* self = CThreadCommandConstructor::NewLC(command->Name(), aConstructor, aFlags, aTaskRunner);
CleanupStack::Pop(self);
CleanupStack::PopAndDestroy(command);
CleanupStack::PushL(self);
return self;
}
-CThreadCommandConstructor* CThreadCommandConstructor::NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags)
+CThreadCommandConstructor* CThreadCommandConstructor::NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
{
- CThreadCommandConstructor* self = new(ELeave) CThreadCommandConstructor(aFlags, aConstructor);
+ CThreadCommandConstructor* self = new(ELeave) CThreadCommandConstructor(aFlags, aConstructor, aTaskRunner);
CleanupStack::PushL(self);
self->BaseConstructL(aCommandName);
return self;
}
-CThreadCommandConstructor::CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor)
- : CCommandConstructorBase(ETypeThread), iFlags(aFlags), iConstructor(aConstructor)
+CThreadCommandConstructor::CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor, MTaskRunner* aTaskRunner)
+ : CCommandConstructorBase(ETypeThread), iFlags(aFlags), iConstructor(aConstructor), iTaskRunner(aTaskRunner)
{
}
MCommand* CThreadCommandConstructor::ConstructCommandL()
{
- return CThreadCommand::NewL(CommandName(), iConstructor, iFlags);
+ return CThreadCommand::NewL(CommandName(), iConstructor, iFlags, iTaskRunner);
}
void CThreadCommandConstructor::AppendDescriptionL(RLtkBuf16& aBuf) const
--- a/core/src/command_constructors.h Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_constructors.h Fri Sep 17 20:28:05 2010 +0100
@@ -18,6 +18,8 @@
#include <fshell/ioutils.h>
class MCommand;
+class MTaskRunner;
+
namespace LtkUtils { class RLtkBuf16; }
using LtkUtils::RLtkBuf16;
@@ -61,16 +63,17 @@
class CThreadCommandConstructor : public CCommandConstructorBase
{
public:
- static CThreadCommandConstructor* NewLC(TCommandConstructor aConstructor, TUint aFlags);
- static CThreadCommandConstructor* NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags);
+ static CThreadCommandConstructor* NewLC(TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
+ static CThreadCommandConstructor* NewLC(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
private:
- CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor);
+ CThreadCommandConstructor(TUint aFlags, TCommandConstructor aConstructor, MTaskRunner* aTaskRunner);
private: // From CCommandConstructorBase.
virtual MCommand* ConstructCommandL();
virtual void AppendDescriptionL(RLtkBuf16& aBuf) const;
private:
TUint iFlags;
TCommandConstructor iConstructor;
+ MTaskRunner* iTaskRunner;
};
class CExeCommandConstructor : public CCommandConstructorBase
--- a/core/src/command_factory.cpp Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_factory.cpp Fri Sep 17 20:28:05 2010 +0100
@@ -24,7 +24,8 @@
#include "xmodem.h"
#include "ymodem.h"
#include "version.h"
-
+#include "ciftest.h"
+#include "worker_thread.h"
//
// Constants.
@@ -84,6 +85,7 @@
Cancel();
iCommands.ResetAndDestroy();
iLock.Close();
+ delete iThreadPool;
}
TInt CompareCommandNames(const CCommandConstructorBase& aCommand1, const CCommandConstructorBase& aCommand2)
@@ -250,6 +252,7 @@
{
User::LeaveIfError(iLock.CreateLocal());
User::LeaveIfError(iFs.DriveList(iDriveList));
+ iThreadPool = CThreadPool::NewL();
AddThreadCommandL(CCmdExit::NewLC); // Note, this command should never execute as 'exit' has handled explicitly by CParser. It exists so that 'exit' appears in fshell's help list and also to support 'exit --help'.
AddThreadCommandL(CCmdHelp::NewLC, CThreadCommand::ESharedHeap);
@@ -301,7 +304,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 +326,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);
@@ -417,14 +421,14 @@
void CCommandFactory::AddThreadCommandL(TCommandConstructor aConstructor, TUint aFlags)
{
- CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aConstructor, aFlags);
+ CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aConstructor, aFlags, iThreadPool);
AddCommandL(constructor);
CleanupStack::Pop(constructor);
}
void CCommandFactory::AddThreadCommandL(const TDesC& aCommandName, TCommandConstructor aConstructor, TUint aAttributes, TUint aFlags)
{
- CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aCommandName, aConstructor, aFlags);
+ CCommandConstructorBase* constructor = CThreadCommandConstructor::NewLC(aCommandName, aConstructor, aFlags, iThreadPool);
constructor->SetAttributes(aAttributes);
AddCommandL(constructor);
CleanupStack::Pop(constructor);
@@ -432,7 +436,7 @@
void CCommandFactory::AddAliasCommandL(const TDesC& aAliasName, TCommandConstructor aConstructor, const TDesC* aAdditionalArguments, const TDesC* aReplacementArguments, TUint aAttributes, TUint aFlags)
{
- CCommandConstructorBase* aliasedCommand = CThreadCommandConstructor::NewLC(aConstructor, aFlags);
+ CCommandConstructorBase* aliasedCommand = CThreadCommandConstructor::NewLC(aConstructor, aFlags, iThreadPool);
CCommandConstructorBase* constructor = CAliasCommandConstructor::NewLC(aAliasName, aliasedCommand, aAdditionalArguments, aReplacementArguments);
CleanupStack::Pop(2, aliasedCommand); // Now owned by "constructor".
CleanupStack::PushL(constructor);
--- a/core/src/command_factory.h Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_factory.h Fri Sep 17 20:28:05 2010 +0100
@@ -15,7 +15,9 @@
#define __COMMAND_FACTORY_H__
#include <e32base.h>
-#include "command_constructors.h"
+#include "command_wrappers.h"
+class CCommandConstructorBase;
+class CThreadPool;
#include "error.h"
class RFs;
@@ -51,6 +53,7 @@
virtual void RunL();
virtual void DoCancel();
virtual TInt RunError(TInt aError);
+
private:
RFs& iFs;
mutable RMutex iLock;
@@ -58,6 +61,7 @@
TDriveList iDriveList;
TBool iFileSystemScanned;
TBool iFailedToScanFileSystem;
+ CThreadPool* iThreadPool;
};
--- a/core/src/command_wrappers.cpp Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_wrappers.cpp Fri Sep 17 20:28:05 2010 +0100
@@ -11,20 +11,14 @@
//
#include "command_wrappers.h"
-
-
-//
-// Constants.
-//
-
-const TInt KMaxHeapSize = 1024*1024; // 1 MB
-
+#include "worker_thread.h"
//
// CCommandWrapperBase.
//
CCommandWrapperBase::CCommandWrapperBase()
+ : CActive(CActive::EPriorityStandard)
{
}
@@ -96,14 +90,23 @@
delete this;
}
+void CCommandWrapperBase::RunL()
+ {
+ // Optionally for use by subclasses
+ }
+
+void CCommandWrapperBase::DoCancel()
+ {
+ // Optionally for use by subclasses
+ }
//
// CThreadCommand.
//
-CThreadCommand* CThreadCommand::NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags)
+CThreadCommand* CThreadCommand::NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
{
- CThreadCommand* self = new(ELeave) CThreadCommand(aCommandConstructor, aFlags);
+ CThreadCommand* self = new(ELeave) CThreadCommand(aCommandConstructor, aFlags, aTaskRunner);
CleanupStack::PushL(self);
self->ConstructL(aName);
CleanupStack::Pop(self);
@@ -112,14 +115,15 @@
CThreadCommand::~CThreadCommand()
{
- delete iWatcher;
- delete iArgs;
+ Cancel();
+ delete iCommandLine;
iThread.Close();
}
-CThreadCommand::CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags)
- : iFlags(aFlags), iCommandConstructor(aCommandConstructor)
+CThreadCommand::CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner)
+ : iFlags(aFlags), iCommandConstructor(aCommandConstructor), iTaskRunner(aTaskRunner)
{
+ CActiveScheduler::Add(this);
iThread.SetHandle(0); // By default RThread refers to the current thread. This results in fshell's thread exiting if this object gets killed before it has managed to open a real thread handle.
if (iFlags & EUpdateEnvironment) iFlags |= ESharedHeap; // Update environment implies a shared heap, ever since we did away with the explict SwitchAllocator
}
@@ -127,66 +131,31 @@
void CThreadCommand::ConstructL(const TDesC& aName)
{
BaseConstructL(aName);
- iWatcher = CThreadWatcher::NewL();
}
-void CommandThreadStartL(CThreadCommand::TArgs& aArgs)
+void CThreadCommand::DoCommandThreadStartL(TAny* aSelf)
{
- if (aArgs.iFlags & CThreadCommand::ESharedHeap)
- {
- // If we're sharing the main fshell heap, we have to play by the rules and not crash
- User::SetCritical(User::EProcessCritical);
- }
-
- CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
- CleanupStack::PushL(scheduler);
- CActiveScheduler::Install(scheduler);
-
- HBufC* commandLine = aArgs.iCommandLine.AllocLC();
+ CThreadCommand* self = static_cast<CThreadCommand*>(aSelf);
IoUtils::CEnvironment* env;
- if (aArgs.iFlags & CThreadCommand::EUpdateEnvironment)
+ if (self->iFlags & CThreadCommand::EUpdateEnvironment)
{
- env = aArgs.iEnv.CreateSharedEnvironmentL();
+ env = self->iSuppliedEnv->CreateSharedEnvironmentL();
}
else
{
// A straight-forward copy
- env = IoUtils::CEnvironment::NewL(aArgs.iEnv);
+ env = IoUtils::CEnvironment::NewL(*self->iSuppliedEnv);
}
CleanupStack::PushL(env);
- CCommandBase* command = (*aArgs.iCommandConstructor)();
- RThread parentThread;
- User::LeaveIfError(parentThread.Open(aArgs.iParentThreadId));
- parentThread.RequestComplete(aArgs.iParentStatus, KErrNone);
- parentThread.Close();
-
- command->RunCommandL(commandLine, env);
- CleanupStack::PopAndDestroy(4, scheduler); // env, command, commandline, scheduler
+ CCommandBase* command = (*self->iCommandConstructor)();
+ //RDebug::Print(_L("5. DoCommandThreadStartL rendezvousing for %S %S"), &self->CmndName(), self->iCommandLine);
+ RThread::Rendezvous(KErrNone);
+ command->RunCommandL(self->iCommandLine, env);
+ CleanupStack::PopAndDestroy(2, env); // command, env
}
-TInt CommandThreadStart(TAny* aPtr)
- {
- CThreadCommand::TArgs args = *(CThreadCommand::TArgs*)aPtr;
- TBool sharedHeap = (args.iFlags & CThreadCommand::ESharedHeap);
- if (!sharedHeap)
- {
- __UHEAP_MARK;
- }
- TInt err = KErrNoMemory;
- CTrapCleanup* cleanup = CTrapCleanup::New();
- if (cleanup)
- {
- TRAP(err, CommandThreadStartL(args));
- delete cleanup;
- }
- if (!sharedHeap)
- {
- __UHEAP_MARKEND;
- }
- return err;
- }
void SetHandleOwnersL(TThreadId aThreadId, RIoReadHandle& aStdin, RIoWriteHandle& aStdout, RIoWriteHandle& aStderr)
{
@@ -199,64 +168,36 @@
{
ASSERT(iObserver == NULL);
- TRequestStatus status(KRequestPending);
- iArgs = new TArgs(iFlags, aEnv, iCommandConstructor, aCommandLine, status);
- if (iArgs == NULL)
+ MThreadedTask* thread = NULL;
+ TRAPD(err, thread = iTaskRunner->NewTaskInSeparateThreadL(CmndName(), iFlags & ESharedHeap, &DoCommandThreadStartL, this));
+ if (err) return err;
+
+ TRAP(err, SetHandleOwnersL(thread->GetThreadId(), CmndStdin(), CmndStdout(), CmndStderr()));
+
+ if (!err)
{
- return KErrNoMemory;
+ iCommandLine = aCommandLine.Alloc();
+ if (!iCommandLine) err = KErrNoMemory;
}
- TInt i = 0;
- TName threadName;
- TInt err = KErrNone;
- do
+ if (!err)
{
- const TDesC& name = CmndName();
- threadName.Format(_L("%S_%02d"), &name, i++);
- if (iFlags & ESharedHeap)
- {
- err = iThread.Create(threadName, CommandThreadStart, KDefaultStackSize, NULL, iArgs);
- }
- else
- {
- err = iThread.Create(threadName, CommandThreadStart, KDefaultStackSize, KMinHeapSize, KMaxHeapSize, iArgs);
- }
- }
- while (err == KErrAlreadyExists);
-
- if (err)
- {
- return err;
+ err = iThread.Open(thread->GetThreadId());
}
- err = iWatcher->Logon(*this, iThread, aObserver);
- if (err)
+ if (!err)
{
- iThread.Kill(0);
- iThread.Close();
- return err;
+ iSuppliedEnv = &aEnv;
+ iObserver = &aObserver;
+ thread->ExecuteTask(iStatus);
+ SetActive();
+ }
+ else
+ {
+ thread->AbortTask();
}
- TThreadId threadId = iThread.Id();
- TRAP(err, SetHandleOwnersL(threadId, CmndStdin(), CmndStdout(), CmndStderr()));
- if (err)
- {
- iThread.Kill(0);
- iThread.Close();
- return err;
- }
-
- iThread.Resume();
- User::WaitForRequest(status, iWatcher->iStatus);
- if (status == KRequestPending)
- {
- iThread.Close();
- return iWatcher->iStatus.Int();
- }
-
- iWatcher->SetActive();
- iObserver = &aObserver;
- return KErrNone;
+ return err;
}
void CThreadCommand::CmndForeground()
@@ -300,71 +241,14 @@
return iThread.ExitCategory();
}
-
-//
-// CThreadCommand::TArgs.
-//
-
-CThreadCommand::TArgs::TArgs(TUint aFlags, IoUtils::CEnvironment& aEnv, TCommandConstructor aCommandConstructor, const TDesC& aCommandLine, TRequestStatus& aParentStatus)
- : iFlags(aFlags), iEnv(aEnv), iCommandConstructor(aCommandConstructor), iCommandLine(aCommandLine), iParentStatus(&aParentStatus), iParentThreadId(RThread().Id())
+void CThreadCommand::RunL()
{
- }
-
-
-//
-// CThreadCommand::CThreadWatcher.
-//
-
-CThreadCommand::CThreadWatcher* CThreadCommand::CThreadWatcher::NewL()
- {
- return new(ELeave) CThreadWatcher();
- }
-
-CThreadCommand::CThreadWatcher::~CThreadWatcher()
- {
- Cancel();
- }
-
-CThreadCommand::CThreadWatcher::CThreadWatcher()
- : CActive(CActive::EPriorityStandard)
- {
- CActiveScheduler::Add(this);
+ iObserver->HandleCommandComplete(*this, iStatus.Int());
}
-TInt CThreadCommand::CThreadWatcher::Logon(CThreadCommand& aCommand, RThread& aThread, MCommandObserver& aObserver)
+void CThreadCommand::DoCancel()
{
- TInt ret = KErrNone;
- aThread.Logon(iStatus);
- if (iStatus != KRequestPending)
- {
- User::WaitForRequest(iStatus);
- ret = iStatus.Int();
- }
- else
- {
- iCommand = &aCommand;
- iThread = &aThread;
- iObserver = &aObserver;
- }
- return ret;
- }
-
-void CThreadCommand::CThreadWatcher::SetActive()
- {
- CActive::SetActive();
- }
-
-void CThreadCommand::CThreadWatcher::RunL()
- {
- iObserver->HandleCommandComplete(*iCommand, iStatus.Int());
- }
-
-void CThreadCommand::CThreadWatcher::DoCancel()
- {
- if (iThread)
- {
- iThread->LogonCancel(iStatus);
- }
+ CmndKill(); // This is a bit drastic, but effective...
}
--- a/core/src/command_wrappers.h Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/command_wrappers.h Fri Sep 17 20:28:05 2010 +0100
@@ -55,8 +55,7 @@
virtual void HandleCommandComplete(MCommand& aCommand, TInt aError) = 0;
};
-
-class CCommandWrapperBase : public CBase, public MCommand
+class CCommandWrapperBase : public CActive, public MCommand
{
protected:
CCommandWrapperBase();
@@ -74,6 +73,10 @@
virtual void CmndDisown();
private: // From MCommand.
virtual void CmndRelease();
+protected: // From CActive
+ void RunL();
+ void DoCancel();
+
private:
HBufC* iName; ///< This is used by concrete classes as they see fit.
RIoReadHandle iStdin;
@@ -91,11 +94,14 @@
ESharedHeap = 0x00000002, // Any command that accesses gShell must have this set
};
public:
- static CThreadCommand* NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags);
+ static CThreadCommand* NewL(const TDesC& aName, TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
~CThreadCommand();
private:
- CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags);
+ CThreadCommand(TCommandConstructor aCommandConstructor, TUint aFlags, MTaskRunner* aTaskRunner);
void ConstructL(const TDesC& aName);
+ void RunL();
+ void DoCancel();
+ static void DoCommandThreadStartL(TAny* aSelf);
private: // From MCommand.
virtual TInt CmndRun(const TDesC& aCommandLine, IoUtils::CEnvironment& aEnv, MCommandObserver& aObserver, RIoSession& aIoSession);
virtual void CmndForeground();
@@ -105,43 +111,14 @@
virtual TInt CmndResume();
virtual TExitType CmndExitType() const;
virtual TExitCategoryName CmndExitCategory() const;
-public:
- class TArgs
- {
- public:
- TArgs(TUint aFlags, CEnvironment& aEnv, TCommandConstructor aCommandConstructor, const TDesC& aCommandLine, TRequestStatus& aParentStatus);
- public:
- TUint iFlags;
- CEnvironment& iEnv;
- TCommandConstructor iCommandConstructor;
- const TPtrC iCommandLine;
- TRequestStatus* iParentStatus;
- TThreadId iParentThreadId;
- };
- class CThreadWatcher : public CActive
- {
- public:
- static CThreadWatcher* NewL();
- ~CThreadWatcher();
- TInt Logon(CThreadCommand& aCommand, RThread& aThread, MCommandObserver& aObserver);
- void SetActive();
- private:
- CThreadWatcher();
- private: // From CActive.
- virtual void RunL();
- virtual void DoCancel();
- private:
- CThreadCommand* iCommand;
- RThread* iThread;
- MCommandObserver* iObserver;
- };
private:
TUint iFlags;
TCommandConstructor iCommandConstructor;
MCommandObserver* iObserver;
- TArgs* iArgs;
RThread iThread;
- CThreadWatcher* iWatcher;
+ MTaskRunner* iTaskRunner;
+ CEnvironment* iSuppliedEnv;
+ HBufC* iCommandLine;
};
--- a/core/src/fshell.mmp Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/fshell.mmp Fri Sep 17 20:28:05 2010 +0100
@@ -65,6 +65,7 @@
source lexer.cpp
source file_reader.cpp
source script_command.cpp
+source worker_thread.cpp
sourcepath ..\builtins
source hello.cpp
@@ -76,6 +77,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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/parser.cpp Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/src/parser.h Fri Sep 17 20:28: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);
void ConstructL();
void CreateNextPipeLine(TBool* aIsForeground);
void CreateNextPipeLineL(TBool* aIsForeground);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/worker_thread.cpp Fri Sep 17 20:28:05 2010 +0100
@@ -0,0 +1,705 @@
+// worker_thread.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
+//
+
+/*
+
+Some notes on how CThreadPool works:
+ * There are 2 types of CWorkerThread - ones that share a specific heap, and those that have their own heap.
+ * Tasks that need a shared heap can only be run using a CWorkerThread that is bound to that heap (one will be created if necessary)
+ * Tasks that don't need a shared heap can be run on any CWorkerThread that has its own heap.
+ * Excess CWorkerThreads are deleted after they've been idle for KCacheTime.
+ * CThreadPool uses an RFastLock on all operations that could be called from multiple threads.
+ * All member data of CThreadPool (including CWorkerThreads and their members) is kept on iThreadPoolAllocator, which was the heap
+ that the CThreadPool was created on. User::SwitchAllocator() is used where necessary to manipulate CThreadPool data from other threads
+ while the Lock is held
+ * When a task is queued, a non-busy CWorkerThread is found (or created).
+ * When a task is completed in a worker thread, it calls iParentThread.RequestComplete(iCompletionStatus, err). It then calls
+ CThreadPool::WorkerFinished() to set the CWorkerThread to be non-busy again.
+ * When a CWorkerThread is created it arranges (via SignalSelf()) to add a CThreadDeathWatcher to the main thread.
+ It follows that notifications about tasks failed because of thread death are always serviced by the main thread, which must therefore
+ remain responsive. (In the non-dead case, the completion notification is sent directly from the worker thread).
+
+*/
+
+#include "worker_thread.h"
+#include <e32debug.h>
+
+#define WT_LOG(args...)
+
+// These defines are for debugging only
+//#define WT_LOG(args...) RDebug::Print(args)
+// These probably don't even work any more - use with extreme caution!
+//#define NO_REUSE
+//#define IMMEDIATE_CLEANUP
+
+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();
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+CThreadPool::CThreadPool()
+ : CActive(CActive::EPriorityLow) // Cleanup doesn't need to be a priority
+ {
+ CActiveScheduler::Add(this);
+ }
+
+void CThreadPool::ConstructL()
+ {
+ User::LeaveIfError(iMainThread.Open(RThread().Id()));
+ iIdleTimer = CPeriodic::NewL(CActive::EPriorityLow-1); // Interactions around iPendingThreadLogons mean it's probably a good idea to have this priority below that of CThreadPool itself
+ 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, %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;
+ iMainThread.Close();
+ iPendingThreadLogons.Close();
+ }
+
+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()
+ {
+ //ASSERT(iLock.IsHeld());
+ RAllocator* allocator = &User::Allocator();
+ if (allocator != iThreadPoolAllocator)
+ {
+ WT_LOG(_L("Thread %d switching from 0x%x to thread pool allocator 0x%x"), TUint(RThread().Id()), allocator, iThreadPoolAllocator);
+ iTempThreadAllocator = User::SwitchAllocator(iThreadPoolAllocator);
+ }
+ }
+
+void CThreadPool::RestoreHeap()
+ {
+ if (iTempThreadAllocator)
+ {
+ WT_LOG(_L("Thread %d restoring heap 0x%x"), TUint(RThread().Id()), iTempThreadAllocator);
+ User::SwitchAllocator(iTempThreadAllocator);
+ iTempThreadAllocator = NULL;
+ }
+ }
+
+void CThreadPool::Unlock()
+ {
+ RestoreHeap();
+ WT_LOG(_L("Releasing lock from thread %d"), TUint(RThread().Id()));
+ iLock.Signal();
+ }
+
+void CThreadPool::LockLC()
+ {
+ Lock();
+ CleanupStack::PushL(TCleanupItem(&DoUnlock, &iLock));
+ }
+
+#define _LOFF(p,T,f) ((T*)(((TUint8*)(p))-_FOFF(T,f))) // Why isn't this defined user-side?
+
+void CThreadPool::DoUnlock(TAny* aLock)
+ {
+ CThreadPool* self = _LOFF(aLock, CThreadPool, iLock);
+ self->Unlock();
+ }
+
+MThreadedTask* CThreadPool::NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext)
+ {
+ WT_LOG(_L("1. NewTaskInSeparateThreadL task %d (%S)"), iTaskCounter, &aThreadName);
+
+ LockLC();
+ // Hunt for a non-busy thread
+ CWorkerThread* foundThread = NULL;
+ RAllocator* requiredAllocator = aSharedHeap ? &User::Allocator() : NULL;
+#ifndef NO_REUSE // This code normally does run, except during debugging when NO_REUSE is defined
+ for (TInt i = 0; i < iThreads.Count(); i++)
+ {
+ CWorkerThread* thread = iThreads[i];
+ if (!thread->Busy() && thread->SharedAllocator() == requiredAllocator)
+ {
+ // If the worker thread is sharing an allocator, it must be sharing the *same* one as this current thread
+ ASSERT(thread->Running());
+ foundThread = thread;
+ break;
+ }
+ }
+#endif
+
+ 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("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();
+ }
+
+ WT_LOG(_L("Using worker thread %d for task %d (%S)"), TUint(foundThread->GetThreadId()), iTaskCounter, &aThreadName);
+
+ User::LeaveIfError(foundThread->Setup(iTaskCounter, aThreadName, aThreadFunction, aThreadContext));
+ foundThread->SetBusy(ETrue);
+ iTaskCounter++;
+ CleanupStack::PopAndDestroy(&iLock);
+ return foundThread;
+ }
+
+void CThreadPool::WorkerDied(CWorkerThread* aWorker)
+ {
+ Lock();
+ // This is now always called in the main thread
+ ASSERT(RThread().Id() == iMainThread.Id());
+
+ for (TInt i = 0; i < iThreadDeathWatchers.Count(); i++)
+ {
+ if (iThreadDeathWatchers[i]->WorkerThread() == aWorker)
+ {
+ // 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;
+ }
+ }
+ Unlock();
+ }
+
+void CThreadPool::WorkerFinished(CWorkerThread* aWorker)
+ {
+ Lock();
+ aWorker->SetBusy(EFalse);
+#if defined(NO_REUSE)
+ // Nothing
+#else
+ // This is the normal case - queue ourself to run (on the main thread) so we can trigger the idle timer from our RunL
+ // Can't do that directly as timers are all thread-local
+ SignalSelf();
+#endif
+ Unlock();
+ }
+
+
+void CThreadPool::SignalSelf()
+ {
+ // TODO: Optimise if we actually are the main thread?
+
+ // Must be holding lock
+ iPendingCallbacks++;
+ TRequestStatus* stat = &iStatus;
+ if (iPendingCallbacks == 1) // Can't use IsActive() as we might not be in the same thread
+ {
+ iStatus = KRequestPending;
+ SetActive();
+ }
+ iMainThread.RequestComplete(stat, KErrNone); // Fortunately RThread::RequestComplete doesn't set the status to KRequestPending before completing it (unlike User::RequestComplete)
+ }
+
+void CThreadPool::CleanupAnyWorkersSharingAllocator(RAllocator* aAllocator)
+ {
+ Lock();
+ SwitchToThreadPoolHeap();
+ for (TInt i = iThreads.Count() - 1; i >= 0; i--)
+ {
+ CWorkerThread* worker = iThreads[i];
+ if (worker->SharedAllocator() == aAllocator)
+ {
+ ASSERT(!worker->Busy());
+ worker->Shutdown();
+ // We don't delete the worker here, let CThreadDeathWatcher handle that
+ }
+ }
+ Unlock();
+ }
+
+void CThreadPool::PerformHouseKeeping()
+ {
+ // Time to do some housekeeping. Algorithm is:
+ // * Keep a single spare non-busy shared worker around, but only for the main heap (ie a CWorkerThread whose SharedAllocator() equals iThreadPoolAllocator)
+ // * Bin everything else that isn't busy
+
+ WT_LOG(_L("Idle timer expired, cleaning up spare workers"));
+ Lock();
+ ASSERT(&User::Allocator() == iThreadPoolAllocator); // We're running in the thread pool's main thread so no need to switch allocators
+
+#ifdef IMMEDIATE_CLEANUP
+ // Everything gets nuked if this is defined
+ TBool foundSpareWorker = ETrue;
+#else
+ TBool foundSpareWorker = EFalse;
+#endif
+ for (TInt i = iThreads.Count() - 1; i >= 0; i--)
+ {
+ CWorkerThread* worker = iThreads[i];
+ if (!worker->Busy())
+ {
+ RAllocator* allocator = worker->SharedAllocator();
+ if (allocator == iThreadPoolAllocator && !foundSpareWorker)
+ {
+ // This one can stay
+ foundSpareWorker = ETrue;
+ }
+ else
+ {
+ // Everything else (that isn't busy) gets cleaned up
+ delete iThreadDeathWatchers[i];
+ iThreadDeathWatchers.Remove(i);
+ worker->Shutdown();
+ delete worker;
+ iThreads.Remove(i);
+ }
+ }
+ }
+ Unlock();
+ }
+
+void CThreadPool::DoCancel()
+ {
+ // There's nothing we can cancel as we complete ourself
+ }
+
+void CThreadPool::RunL()
+ {
+ Lock();
+ iPendingCallbacks--;
+ while (iPendingCallbacks)
+ {
+ // Consume any further signals currently pending - we're somewhat abusing the active scheduler by completing the same repeatedly without it running in between so we have to consume the abuse here to balance things
+ User::WaitForRequest(iStatus);
+ iPendingCallbacks--;
+ }
+
+ // We kinda overload our RunL, using it for both registering logons and triggering the idle cleanup. Ah well.
+
+ if (iPendingThreadLogons.Count())
+ {
+ for (TInt i = 0; i < iPendingThreadLogons.Count(); i++)
+ {
+ iPendingThreadLogons[i]->StartWatching();
+ }
+ SwitchToThreadPoolHeap();
+ iPendingThreadLogons.Reset();
+ }
+
+#ifdef IMMEDIATE_CLEANUP
+ Unlock();
+ // Delete the thread straight away
+ PerformHouseKeeping();
+#else
+ // Normally go through the timer
+ iIdleTimer->Cancel(); // Reset it if it's already counting
+ iIdleTimer->Start(KCacheTime, KCacheTime, TCallBack(&TimerCallback, this));
+ Unlock();
+#endif
+ }
+
+TInt CThreadPool::TimerCallback(TAny* aSelf)
+ {
+ CThreadPool* self = static_cast<CThreadPool*>(aSelf);
+ self->iIdleTimer->Cancel(); // Stop the thing being periodic
+ self->PerformHouseKeeping();
+ return 0;
+ }
+
+////
+
+const TInt KMaxHeapSize = 1024 * 1024;
+
+// CWorkerThreadDispatchers run in the context of the worker thread they're owned by
+class CWorkerThreadDispatcher : public CActive
+ {
+public:
+ CWorkerThreadDispatcher(CWorkerThread* aThread)
+ : CActive(CActive::EPriorityStandard), iThread(aThread)
+ {
+ CActiveScheduler::Add(this);
+ iStatus = KRequestPending;
+ SetActive();
+ }
+
+ void RunL()
+ {
+ if (iStatus.Int() == KErrNone)
+ {
+ iStatus = KRequestPending;
+ SetActive();
+ iThread->ThreadRun();
+ }
+ else
+ {
+ // Time to die
+ iThread->iAsWait.AsyncStop();
+ }
+ }
+
+ void DoCancel() {} // Can never happen because we can only ever be destroyed as a result of the AsyncStop in RunL from which we can never be active
+
+private:
+ CWorkerThread* iThread;
+ };
+
+CWorkerThread* CWorkerThread::NewLC(CThreadPool* aParentPool, RAllocator* aSharedAllocator)
+ {
+ CWorkerThread* self = new(ELeave) CWorkerThread(aParentPool, aSharedAllocator);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ return self;
+ }
+
+CWorkerThread::CWorkerThread(CThreadPool* aParentPool, RAllocator* aSharedAllocator)
+ : iParentPool(aParentPool), iSharedAllocator(aSharedAllocator)
+ {
+ iWorkerThread.SetHandle(0);
+ }
+
+void CWorkerThread::ConstructL()
+ {
+ TInt err = KErrNone;
+ TName name;
+ name.Format(_L("WorkerThread_%x"), this);
+ if (iSharedAllocator)
+ {
+ err = iWorkerThread.Create(name, &ThreadFn, KDefaultStackSize, iSharedAllocator, this);
+ }
+ else
+ {
+ // Create a new heap with default heap size
+ err = iWorkerThread.Create(name, &ThreadFn, KDefaultStackSize, KMinHeapSize, KMaxHeapSize, this);
+ }
+ User::LeaveIfError(err);
+
+ TRequestStatus stat;
+ iWorkerThread.Rendezvous(stat);
+
+ ASSERT(stat == KRequestPending);
+ if (stat == KRequestPending)
+ {
+ iWorkerThread.Resume();
+ }
+ else
+ {
+ iWorkerThread.Kill(stat.Int());
+ }
+
+ User::WaitForRequest(stat);
+ User::LeaveIfError(stat.Int());
+ // Thread is now ready to do stuff
+ }
+
+CWorkerThread::~CWorkerThread()
+ {
+ WT_LOG(_L("Deleting worker thread %d"), TUint(GetThreadId()));
+ ASSERT(iWorkerThread.Handle() == 0 || iWorkerThread.ExitType() != EExitPending);
+ iParentThread.Close();
+ iWorkerThread.Close();
+ }
+
+TInt CWorkerThread::Setup(TInt aTaskId, const TDesC& aThreadName, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext)
+ {
+ ASSERT(!Busy());
+
+ // Things to do in preparation for running a command:
+ // 1. Check what thread we're running in now and set ourselves and our thread death watcher on its scheduler (may not be the thread that created us originally)
+ // 2. Store the required context for the worker thread to access
+
+ TInt err = iParentThread.Open(RThread().Id());
+ if (!err)
+ {
+ iTaskId = aTaskId;
+ iName = &aThreadName;
+ iFn = aThreadFunction;
+ iContext = aThreadContext;
+ }
+ return err;
+ }
+
+TInt CWorkerThread::ExecuteTask(TRequestStatus& aCompletionStatus)
+ {
+ WT_LOG(_L("2. + Go task %d (%S)"), iTaskId, iName);
+
+ ASSERT(iCompletionStatus == NULL);
+ aCompletionStatus = KRequestPending;
+ iCompletionStatus = &aCompletionStatus;
+
+ // Setup rendezvous and completion requestStatuses before signalling iDispatchStatus
+ TRequestStatus rendezvousStat;
+ iWorkerThread.Rendezvous(rendezvousStat);
+
+ // Signal the worker thread to do its thing
+ TRequestStatus* dispatchStat = iDispatchStatus;
+ iWorkerThread.RequestComplete(dispatchStat, KErrNone);
+
+ User::WaitForRequest(rendezvousStat);
+
+ TInt err = rendezvousStat.Int();
+ if (iWorkerThread.ExitType() != EExitPending && rendezvousStat.Int() >= 0)
+ {
+ err = KErrDied;
+ }
+
+ WT_LOG(_L("6. - Go task %d (%S) err=%d"), iTaskId, iName, err);
+
+ if (err != KErrNone)
+ {
+ // We don't signal completion if there was an error prior to the rendezvous, we just return it here
+ iCompletionStatus = NULL;
+ }
+ return err;
+ }
+
+TThreadId CWorkerThread::GetThreadId() const
+ {
+ return iWorkerThread.Id();
+ }
+
+void CWorkerThread::AbortTask()
+ {
+ // Undo setup
+ iName = NULL;
+ iFn = NULL;
+ iContext = NULL;
+ iParentThread.Close();
+ iParentPool->WorkerFinished(this);
+ }
+
+void CWorkerThread::ThreadRun()
+ {
+ // Runs in the worker thread
+
+ if (!UsingSharedAllocator())
+ {
+ __UHEAP_MARK;
+ }
+
+ TInt i = 0;
+ TInt err = KErrNone;
+ do
+ {
+ TName threadName;
+ threadName.Format(_L("%S_%02d"), iName, i++);
+ err = User::RenameThread(threadName);
+ WT_LOG(_L("3. Running thread for task %d (%S)"), iTaskId, &threadName);
+ }
+ while (err == KErrAlreadyExists);
+
+ // Execute the actual function
+ WT_LOG(_L("4. + ThreadRun for task %d (%S)"), iTaskId, iName);
+ TRAP(err, (*iFn)(iContext));
+ WT_LOG(_L("7. - ThreadRun for task %d (%S) signalling thread %d with err=%d"), iTaskId, iName, TUint(iParentThread.Id()), err);
+
+ if (!UsingSharedAllocator())
+ {
+ // Do this before saying we've actually finished the task, otherwise we risk deadlocking on the thread pool lock when IMMEDIATE_CLEANUP is defined
+ // because CleanupAnyWorkersSharingAllocator takes the lock, but the lock can already be held by that point around the Shutdown that IMMEDIATE_CLEANUP does.
+ iParentPool->CleanupAnyWorkersSharingAllocator(&User::Allocator()); // Otherwise the heap check will fail
+ }
+
+ // And signal back the result
+ iParentPool->WorkerFinished(this);
+ iParentThread.RequestComplete(iCompletionStatus, err);
+
+ // Finally put our name back to what it was
+#ifdef _DEBUG
+ _LIT(KDebugName, "WorkerThread_%x (was %S)");
+ TName threadName = RThread().Name();
+ TPtrC oldName = threadName.Left(threadName.MaxLength() - KDebugName().Length());
+ TName newName;
+ newName.Format(KDebugName, this, &oldName);
+ User::RenameThread(newName);
+#else
+ TName threadName;
+ threadName.Format(_L("WorkerThread_%x"), this);
+ User::RenameThread(threadName);
+#endif
+
+ if (!UsingSharedAllocator())
+ {
+ __UHEAP_MARKEND;
+ }
+ }
+
+TBool CWorkerThread::Busy() const
+ {
+ return iBusy;
+ }
+
+void CWorkerThread::SetBusy(TBool aBusy)
+ {
+ iBusy = aBusy;
+ }
+
+TBool CWorkerThread::UsingSharedAllocator() const
+ {
+ return iSharedAllocator != NULL;
+ }
+
+RAllocator* CWorkerThread::SharedAllocator() const
+ {
+ return iSharedAllocator;
+ }
+
+void CWorkerThread::SignalClientThatThreadHasDied()
+ {
+ WT_LOG(_L("Task %d died with exittype %d reason %d"), iTaskId, iWorkerThread.ExitType(), iWorkerThread.ExitReason());
+ TInt err = iWorkerThread.ExitReason();
+ if (err >= 0) err = KErrDied;
+ if (iCompletionStatus)
+ {
+ iParentThread.RequestComplete(iCompletionStatus, err);
+ }
+ }
+
+TInt CWorkerThread::ThreadFn(TAny* aSelf)
+ {
+ CWorkerThread* self = static_cast<CWorkerThread*>(aSelf);
+ if (self->UsingSharedAllocator())
+ {
+ // If we're sharing the main fshell heap, we have to play by the rules and not crash
+ User::SetCritical(User::EProcessCritical);
+
+ // We also need to temporarily switch to the thread pool's allocator, because our CTrapCleanup, CActiveScheduler etc conceptually belong with the worker thread object and not with the heap we're sharing (which could be different... damn repeat command again)
+ //self->iParentPool->Lock();
+ //self->iParentPool->SwitchToThreadPoolHeap();
+ }
+ else
+ {
+ __UHEAP_MARK;
+ }
+ TInt err = KErrNoMemory;
+ CTrapCleanup* cleanup = CTrapCleanup::New();
+ //WT_LOG(_L("Worker thread %d creating trapcleanup 0x%x"), TUint(RThread().Id()), cleanup);
+ if (cleanup)
+ {
+ TRAP(err, self->ThreadFnL());
+ //WT_LOG(_L("Worker thread %d deleting trapcleanup 0x%x"), TUint(RThread().Id()), cleanup);
+ delete cleanup;
+ }
+ if (!self->UsingSharedAllocator())
+ {
+ __UHEAP_MARKEND;
+ }
+ return err;
+ }
+
+void CWorkerThread::ThreadFnL()
+ {
+ CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
+ CleanupStack::PushL(scheduler);
+ CActiveScheduler::Install(scheduler);
+
+ CWorkerThreadDispatcher* dispatcher = new(ELeave) CWorkerThreadDispatcher(this);
+ CleanupStack::PushL(dispatcher);
+ iDispatchStatus = &dispatcher->iStatus;
+ RThread::Rendezvous(KErrNone);
+ iAsWait.Start();
+ CleanupStack::PopAndDestroy(2, scheduler); // dispatcher, scheduler
+ }
+
+void CWorkerThread::Shutdown()
+ {
+ // Can be called from (potentially) any thread
+ WT_LOG(_L("Shutting down worker thread %d whose exittype is %d"), TUint(GetThreadId()), iWorkerThread.ExitType());
+
+ if (Busy())
+ {
+ WT_LOG(_L("Thread is still busy - killing it"));
+ iWorkerThread.Kill(KErrAbort);
+ // The thread death watcher should take care of everything else, eventually
+ }
+ else if (iWorkerThread.ExitType() == EExitPending)
+ {
+ TRequestStatus stat;
+ iWorkerThread.Logon(stat);
+ iWorkerThread.RequestComplete(iDispatchStatus, KErrCancel);
+ User::WaitForRequest(stat);
+ WT_LOG(_L("Shut down worker %d exit=%d"), TUint(GetThreadId()), iWorkerThread.ExitType());
+ }
+ }
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/worker_thread.h Fri Sep 17 20:28:05 2010 +0100
@@ -0,0 +1,130 @@
+// worker_thread.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 WORKER_THREAD_H
+#define WORKER_THREAD_H
+
+#include <e32base.h>
+
+class MThreadedTask
+ {
+public:
+ virtual TThreadId GetThreadId() const=0;
+ virtual TInt ExecuteTask(TRequestStatus& aCompletionStatus)=0;
+ virtual void AbortTask()=0; // If you decide you don't want to call ExecuteTask
+ };
+
+class MTaskRunner
+ {
+public:
+ typedef void (*TThreadFunctionL)(TAny*);
+ virtual MThreadedTask* NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, TThreadFunctionL aThreadFunction, TAny* aThreadContext)=0;
+ };
+
+class CWorkerThread;
+class CThreadDeathWatcher;
+
+class CThreadPool : public CActive, public MTaskRunner
+ {
+public:
+ static CThreadPool* NewL();
+ ~CThreadPool();
+ void WorkerDied(CWorkerThread* aWorker);
+ void WorkerFinished(CWorkerThread* aWorker);
+ void CleanupAnyWorkersSharingAllocator(RAllocator* aAllocator);
+
+public: // From MTaskRunner
+ virtual MThreadedTask* NewTaskInSeparateThreadL(const TDesC& aThreadName, TBool aSharedHeap, TThreadFunctionL aThreadFunction, TAny* aThreadContext);
+
+private:
+ CThreadPool();
+ void ConstructL();
+ static TInt TimerCallback(TAny* aSelf);
+ void PerformHouseKeeping();
+ void RunL();
+ void DoCancel();
+ void SignalSelf();
+
+ void Lock();
+ void LockLC();
+ void SwitchToThreadPoolHeap();
+ void RestoreHeap();
+ void Unlock();
+ static void DoUnlock(TAny* aLock);
+
+private:
+ RFastLock iLock; // Thread pool is shared between eg fshell main thread and any source threads, so needs locking around WorkerFinished and NewTaskInSeparateThreadL etc
+ RAllocator* iThreadPoolAllocator; // The thread pool can be modified from a thread that isn't using the main heap, so we need to track that and use User::SwitchAllocator as needed
+ RAllocator* iTempThreadAllocator; // Only used while lock is held
+ RPointerArray<CWorkerThread> iThreads;
+ TInt iTaskCounter;
+ RThread iMainThread;
+ TInt iPendingCallbacks;
+ CPeriodic* iIdleTimer;
+ TInt iCountThreadsCreated; // This is for statistics gathering, not involved in the logic
+ RArray<CThreadDeathWatcher*> iPendingThreadLogons; // Not owned
+ RPointerArray<CThreadDeathWatcher> iThreadDeathWatchers;
+ };
+
+class CWorkerThread : public CBase, public MThreadedTask
+ {
+public:
+ static CWorkerThread* NewLC(CThreadPool* aParentPool, RAllocator* aSharedAllocator);
+ // For safety these 2 need to be called with the thread pool lock held
+ TBool Busy() const;
+ void SetBusy(TBool aBusy);
+
+ TBool UsingSharedAllocator() const;
+ RAllocator* SharedAllocator() const;
+ TInt Setup(TInt aTaskId, const TDesC& aThreadName, MTaskRunner::TThreadFunctionL aThreadFunction, TAny* aThreadContext);
+ void Shutdown();
+ ~CWorkerThread();
+
+ TBool Running() const { return iWorkerThread.Handle() && iWorkerThread.ExitType() == EExitPending; }
+
+public: // From MThreadedTask
+ TThreadId GetThreadId() const;
+ TInt ExecuteTask(TRequestStatus& aCompletionStatus);
+ void AbortTask();
+
+public: // For CThreadDeathWatcher to use
+ void SignalClientThatThreadHasDied();
+
+private:
+ CWorkerThread(CThreadPool* aParentPool, RAllocator* aSharedAllocator);
+ void ConstructL();
+ void ThreadRun();
+ static TInt ThreadFn(TAny* aSelf);
+ void ThreadFnL();
+
+private:
+ CThreadPool* iParentPool;
+ RThread iWorkerThread;
+ RAllocator* iSharedAllocator;
+ TRequestStatus* iDispatchStatus;
+ CActiveSchedulerWait iAsWait; // Use a CActiveSchedulerWait to protect ourselves from whatever aThreadFunction may try and do
+ CThreadDeathWatcher* iThreadDeathWatcher;
+ TBool iBusy;
+
+ // Things specific to the task currently being executed
+ RThread iParentThread; // Worker needs this to signal completion to us
+ const TDesC* iName;
+ MTaskRunner::TThreadFunctionL iFn;
+ TAny* iContext;
+ TInt iTaskId;
+ TRequestStatus* iCompletionStatus;
+
+ friend class CWorkerThreadDispatcher;
+ friend class CThreadDeathWatcher;
+ };
+
+#endif
--- a/core/tsrc/fshell-basic-test.script Thu Sep 16 15:06:08 2010 +0100
+++ b/core/tsrc/fshell-basic-test.script Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/core/tsrc/smoketest.script Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/documentation/change_history.pod Fri Sep 17 20:28:05 2010 +0100
@@ -14,6 +14,20 @@
=head1 FShell Change History
+=head2 FCL features not yet in MCL (move this section before committing to MCL)
+
+=over 5
+
+=item *
+
+Fshell now reuses threads for built-in commands that execute in quick succession. The thread pool takes into account the requirements of the command when assigning a thread (eg whether it needs to share a heap with its parent command) and creates a new one if necessary. Excess threads are cleaned up after a short idle period (currently 1 second).
+
+=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.
+
+=back
+
=head2 Release 001
=over 5
--- a/libraries/iosrv/bwins/iocliu.def Thu Sep 16 15:06:08 2010 +0100
+++ b/libraries/iosrv/bwins/iocliu.def Fri Sep 17 20:28: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_info_file.cpp Thu Sep 16 15:06:08 2010 +0100
+++ b/libraries/iosrv/client/command_info_file.cpp Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/libraries/iosrv/eabi/iocliu.def Fri Sep 17 20:28: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 Thu Sep 16 15:06:08 2010 +0100
+++ b/libraries/iosrv/inc/ioutils.h Fri Sep 17 20:28: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;