using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Workflow.Runtime;
using System.Threading;
using CommonWFLibrary;
using System.IO;
namespace CommonWFLibraryTest
{
/// <summary>
/// Summary description for t_ExternalProgramActivity
/// </summary>
[TestClass]
public class t_ExternalProgramActivity
{
public t_ExternalProgramActivity()
{
//
// TODO: Add constructor logic here
//
}
private TestContext testContextInstance;
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
#region Additional test attributes
//
// You can use the following additional attributes as you write your tests:
//
// Use ClassInitialize to run code before running the first test in the class
// [ClassInitialize()]
// public static void MyClassInitialize(TestContext testContext) { }
//
// Use ClassCleanup to run code after all tests in a class have run
// [ClassCleanup()]
// public static void MyClassCleanup() { }
//
// Use TestInitialize to run code before running each test
// [TestInitialize()]
// public void MyTestInitialize() { }
//
// Use TestCleanup to run code after each test has run
// [TestCleanup()]
// public void MyTestCleanup() { }
//
#endregion
/// <summary>
/// Run a test, throw exceptions back to user.
/// </summary>
/// <param name="wfActivity"></param>
/// <param name="dict"></param>
private static Dictionary<string, object> RunWFByType(Type wfActivity, Dictionary<string, object> dict)
{
using (WorkflowRuntime runtime = new WorkflowRuntime())
using (AutoResetEvent reset = new AutoResetEvent(false))
{
Exception exp = null;
Dictionary<string, object> results = null;
runtime.WorkflowCompleted += (o, args) => { results = args.OutputParameters; reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { exp = args.Exception; reset.Set(); };
LongRunningActivityBase.RegisterService(runtime);
runtime.StartRuntime();
WorkflowInstance instance;
instance = runtime.CreateWorkflow(wfActivity, dict);
instance.Start();
bool result = reset.WaitOne(15*1000);
if (exp != null)
{
throw exp;
}
return results;
}
}
/// <summary>
/// Run a test, throw exceptions back to user. Only, once we go into an idle state, then
/// crash the WF and restart it. And hook up all the required persistancy!
/// </summary>
/// <param name="wfActivity"></param>
/// <param name="dict"></param>
/// <returns>A list of the WF public properties upon finish up!</returns>
private static Dictionary<string,object> RunWFByTypeWithPersistance(Type wfActivity, Dictionary<string, object> dict, int timeout, DirectoryInfo cache, int numberCrashToIdles, int ticksToPauseInbetween)
{
///
/// Crash the runtime n times.
///
bool start_wf = true;
bool has_entered_idle = false;
Dictionary<string, object> results = null;
while (numberCrashToIdles >= 0)
{
numberCrashToIdles = numberCrashToIdles - 1;
bool waitForComplete = numberCrashToIdles < 0;
///
/// Run until the WF enters the idle state. If it crashes, then, of course, we are outta here.
///
using (AutoResetEvent reset = new AutoResetEvent(false))
using (WorkflowRuntime runtime = new WorkflowRuntime())
{
try
{
Exception e = null;
bool completed = false;
if (!waitForComplete)
{
runtime.WorkflowIdled += (o, args) => { reset.Set(); has_entered_idle = true; };
}
runtime.WorkflowCompleted += (o, args) => { completed = true; results = args.OutputParameters; reset.Set(); };
runtime.WorkflowTerminated += (o, args) => { e = args.Exception; reset.Set(); };
runtime.AddService(new FilePersistenceService(true, cache));
LongRunningActivityBinaryPersister lrp = new LongRunningActivityBinaryPersister(new FileInfo(cache.FullName + "\\longrunning"));
LongRunningActivityBase.RegisterService(runtime, obj => lrp.Save(obj), () => lrp.Restore());
runtime.StartRuntime();
///
/// If this is the first time through, then we should start the WF. Otherwise, it
/// should have been picked up by the persitancy service from the last run.
///
if (start_wf)
{
WorkflowInstance instance;
instance = runtime.CreateWorkflow(wfActivity, dict);
instance.Start();
start_wf = false;
}
///
/// Great. Now wait for it to finish. We expect the WF to enter
/// an idled state within 1 second. On the last one, waiting for something
/// real to happen, we expect that to happen in the timeout parameter passed
/// to us.
///
/// We shut down the runtime here so that we can step through the other statements in the
/// debugger!
///
/// We wait for 100 ms after the idle guy goes because we have to make sure
/// that the service has time to close and save state. In real life, there is
/// a potential race condition - that is, if the wf crashes before our system has a chance
/// to write out its state then the long running WF will, indeed, be left hanging. But that
/// is proper behavior. Just not for a test harness.
///
int waittime = waitForComplete ? timeout : 1000;
bool result = reset.WaitOne(waittime);
Thread.Sleep(100);
runtime.StopRuntime();
runtime.Dispose();
///
/// First, if the WF ended due to an exception, we are outta here no matter what
///
if (e != null)
{
throw e;
}
///
/// Next, if we are waiting for a complete, then we just return the result
///
if (waitForComplete)
{
return results;
}
///
/// Ok -- we were waiting for things to go into the idle state. Bomb if they didn't,
/// otherwise we go around again!
///
if (completed)
{
throw new Exception("The WF unexpectedly completed before going idle!");
}
if (!result && !has_entered_idle)
{
throw new Exception("The workflow was never idled");
}
///
/// Pause before the next iteration if requested...
///
if (ticksToPauseInbetween > 0)
{
Thread.Sleep(ticksToPauseInbetween);
}
}
finally
{
if (runtime.IsStarted)
{
runtime.StopRuntime();
}
}
}
}
return null;
}
[TestMethod]
public void TestSimpleRun()
{
/// Run the external program
Assert.IsTrue(null != RunWFByType(typeof(ExternalProgramActivity), new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" } }), "Failed to get program return!");
}
[TestMethod]
public void TestSimpleRunBadPath()
{
try
{
/// Make sure the exception comes back when we can't find the program we are trying to run!
RunWFByType(typeof(ExternalProgramActivity), new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArgFreak.exe" } });
Assert.Fail("Should have thrown exception because the path of the program we are trying to run does not exist!");
}
catch (Exception)
{
}
}
[TestMethod]
public void TestRunArgs()
{
/// Run external guys with argument string - make sure it gets there!
DateTime start = DateTime.Now;
RunWFByType(typeof(ExternalProgramActivity),
new Dictionary<string, object> {
{ "ProgramPath", "RunAndPauseWithArg.exe" },
{ "ProgramArguments", "3000" }
});
DateTime finish = DateTime.Now;
TimeSpan delta = finish - start;
Assert.IsTrue(delta.Seconds >= 3, "The wait was supposed to be more than 3 seconds, but was not!");
Assert.IsTrue(delta.Seconds <= 4, "Should have waited less than 4 seconds!");
}
[TestMethod]
public void TestRunResult()
{
/// Run external program, make sure its result is saved
var results = RunWFByType(typeof(ExternalProgramActivity),
new Dictionary<string, object> {
{ "ProgramPath", "RunAndPauseWithArg.exe" },
{ "ProgramArguments", "50 5" }
});
Assert.IsTrue(results.ContainsKey("FinalStatus"), "The final status property did not come back!");
Assert.IsTrue((int) results["FinalStatus"] == 5, "Final status was not 5!");
}
[TestMethod]
public void TestRunHostCrashWhileProgramRunning()
{
DateTime start = DateTime.Now;
var results = RunWFByTypeWithPersistance(
typeof(ExternalProgramActivity),
new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "5000" } },
6000,
new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
1,
2000);
DateTime finish = DateTime.Now;
TimeSpan delta = finish - start;
Assert.IsTrue(results != null, "The WF failed to complete!");
Assert.IsTrue(delta.Seconds >= 5, "The WF should have taken at least 5 seconds -- but took less!?");
Assert.IsTrue(delta.Seconds <= 6, "The WF shoudl not have taken more than 6 seconds -- something is wrong!");
}
[TestMethod]
public void TestRunHostCrashNoRestart()
{
///
/// Crash the host, but set the "restart" value to false (normally it is true).
///
try
{
var results = RunWFByTypeWithPersistance(
typeof(ExternalProgramActivity),
new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "500" }, { "AllowRestart", false } },
2000,
new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
1,
2000);
Assert.Fail("Run should have thrown an exception that we couldn't restart the program");
}
catch (AssertFailedException e)
{
throw e;
}
catch (Exception e)
{
Assert.IsTrue(e.InnerException.GetType() == typeof(ExternalProgramActivityException), "Incorrect exception type came back");
Assert.IsTrue(e.InnerException.Message.Contains("Program crashed"), "Exception was not correct");
}
}
[TestMethod]
public void TestRunHostCrashRestart()
{
/// Have the program crash while the WF host is down. So it should be re-run.
var results = RunWFByTypeWithPersistance(
typeof(ExternalProgramActivity),
new Dictionary<string, object> { { "ProgramPath", "RunAndPauseWithArg.exe" }, { "ProgramArguments", "500" } },
6000,
new DirectoryInfo(".\\TestRunHostCrashWhileProgramRunning"),
1,
2000);
Assert.IsTrue(results != null, "The WF failed to complete!");
}
}
}