How to stop NUnit ITestRunner? - nunit

Using nunit.engine 3.10.0, I can't stop an asynchronously running ITestRunner. The TestPackage is set up to be executed locally, i.e. InProcess and in the current AppDomain. No more tests are started after the second test as expected, but the while loop never ends.
public static void Main(string[] args)
{
// 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
// Nunit 3.10.0
var minVersion = new Version("3.4");
ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);
// configure a test package that executes
// in the current process and in the current domain
var testPackage = new TestPackage(testAssemblyFileNames);
testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);
ITestRunner testRunner = testEngine.GetRunner(testPackage);
// prepare a listener that stops the test runner
// when the second test has been started
const bool StopAfterSecondTest = true;
int testStartedCount = 0;
var listener = new MyTestEventListener();
listener.TestStarted += (sender, eventArgs) =>
{
testStartedCount++;
if ( StopAfterSecondTest && testStartedCount == 2 )
{
testRunner.StopRun(force: true);
}
};
var testFilterBuilder = new TestFilterBuilder();
TestFilter testFilter = testFilterBuilder.GetFilter();
ITestRun testRun = testRunner.RunAsync(listener, testFilter);
bool keepRunning;
int loopCount = 0;
do
{
bool completed = testRun.Wait(500);
bool running = testRunner.IsTestRunning;
keepRunning = !completed && running;
loopCount++;
} while ( keepRunning );
Console.WriteLine($"Loop count: {loopCount}");
XmlNode resultNode = testRun.Result;
Console.WriteLine(resultNode.InnerText);
Console.ReadKey();
}
private class MyTestEventListener : ITestEventListener
{
private const string TestCaseStartPrefix = "<start-test";
private const string TestMethodTypeAttribute = " type=\"TestMethod\"";
public event EventHandler<EventArgs> TestStarted;
public void OnTestEvent(string report)
{
if ( report.StartsWith(TestCaseStartPrefix) &&
report.Contains(TestMethodTypeAttribute) )
{
TestStarted?.Invoke(this, new EventArgs());
}
}
}
If I skip waiting and try to get the test result, I get an InvalidOperationException: 'Cannot retrieve Result from an incomplete or cancelled TestRun.'
How can I stop the test runner and get the results of the tests that were completed before the stopping?

You can't do it from inside a test. Your listener is executed in the context of the test itself. For that reason, listeners are specifically forbidden from trying to change the outcome of a test. Additionally, the event is buffered and may not even be received in this case until after the test run is complete.
StopRun is intended to be called by the main runner itself, generally as triggered by some user input.
You should also take note of this issue: https://github.com/nunit/nunit/issues/3276 which prevents StopRun(true) from working under any circumstances. It was fixed in PR https://github.com/nunit/nunit/pull/3281 but is not yet in any release of the framework. You will have to either use a recent dev build of the framework or switch to StopRun(false).

Based on the answer by #Charlie, this is how to modify the code in order to stop all threads:
public static void Main(string[] args)
{
// 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
// each test case includes a 200 ms delay
string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
// Nunit 3.10.0
var minVersion = new Version("3.4");
ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);
// configure a test package that executes
// in the current process and in the current domain
var testPackage = new TestPackage(testAssemblyFileNames);
testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);
ITestRunner testRunner = testEngine.GetRunner(testPackage);
var listener = new TestStartListener();
var testFilterBuilder = new TestFilterBuilder();
TestFilter testFilter = testFilterBuilder.GetFilter();
ITestRun testRun = testRunner.RunAsync(listener, testFilter);
// wait until the first test case has been started
while ( listener.Count < 1 )
{
Thread.Sleep(50);
}
bool keepRunning = true;
while ( keepRunning )
{
int testStartedCount = listener.Count;
testRunner.StopRun(force: false);
Writer.WriteLine($"{GetTimeStamp()}, Stop requested after {testStartedCount} test cases.");
// wait for less time than a single test needs to complete
bool completed = testRun.Wait(100);
bool running = testRunner.IsTestRunning;
Writer.WriteLine($"{GetTimeStamp()} Completed: {completed}, running: {running}");
keepRunning = !completed && running;
}
listener.WriteReportsTo(Writer);
XmlNode resultNode = testRun.Result;
Writer.WriteLine("Test result:");
resultNode.WriteContentTo(ResultWriter);
Console.ReadKey();
}
private class TestStartListener : List<string>, ITestEventListener
{
private const string TestCaseStartPrefix = "<start-test";
private const string TestMethodTypeAttribute = " type=\"TestMethod\"";
public event EventHandler<EventArgs> TestStarted;
public void OnTestEvent(string report)
{
if ( report.StartsWith(TestCaseStartPrefix) &&
report.Contains(TestMethodTypeAttribute) )
{
Add($"{GetTimeStamp()}, {report}");
TestStarted?.Invoke(this, new EventArgs());
}
}
public void WriteReportsTo(TextWriter writer)
{
Writer.WriteLine($"Listener was called {Count} times.");
foreach ( var report in this )
{
Writer.WriteLine(report);
}
}
}
The two test assemblies get executed in the runner's process, in a single domain and on two threads, one for each test assembly. In total, two test methods get executed and pass; one for each of the two test assemblies. Other test methods do not get executed and not reported. Other test fixtures (classes) do not get executed and get reported with result="Failed" label="Cancelled".
Note that testRunner.StopRun(force: false) is called repeatedly. If only called once, the other thread will run to completion.

Related

Getting debugger command from active project configuration

In a VSIX package I have to get the debugger command for active startup configuration. In other words, the command that would be executed when 'sturt under debugger' is selected. Using the code below I was able to get active configuration for startup project, but I can't figure out how to get the debugger command from IVSHierarchy representing the startup project. Is this even possible without going back to DTE?
private void GetStartupProject()
{
ThreadHelper.ThrowIfNotOnUIThread();
IVsSolutionBuildManager bm = Package.GetGlobalService(typeof(IVsSolutionBuildManager)) as IVsSolutionBuildManager;
int hr;
IVsHierarchy project;
hr = bm.get_StartupProject(out project);
if (hr == VSConstants.S_OK)
{
project.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Name, out object projectName);
IVsProjectCfg[] activeCfgs = new IVsProjectCfg[1];
bm.FindActiveProjectCfg(IntPtr.Zero, IntPtr.Zero, project, activeCfgs);
activeCfgs[0].get_DisplayName(out string activeCfgName);
textOut.Text += String.Format("{0} {1}\r\n",(string)projectName, activeCfgName);
}
}
The IVsProjectCfg interface doesn't allow for enumerating the various configuration properties, or contain a method that would allow you to retrieve them. As you probably already suspect, the various project types expose their settings via automation, which for C# and VB.NET projects would correlate to using EnvDTE/VSLangProj interfaces to retrieve the specific debugger properties for a given configuration. For C#/VB.NET projects you'll want to retrieve/use the ProjectConfigurationProperties3 interface. For example:
private void OnGetDebuggerSettings(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
IVsHierarchy vsHierarchy = null;
IVsSolutionBuildManager slnBuildMgr = (IVsSolutionBuildManager)GetService(typeof(SVsSolutionBuildManager));
int hresult = slnBuildMgr.get_StartupProject(out vsHierarchy);
object objProject = null;
hresult = vsHierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_ExtObject, out objProject);
Project startupProject = (Project)objProject;
// Note, cannot enumerate the ProjectConfigurationProperties, as it's not a collection interface
// Refer to the documentation for ProjetConfigurationProperties3, or set a BP on the WriteLine below
// and view the Dynamic View of the cfgProperties in the debugger's locals or watch window.
Configuration cfg = startupProject.ConfigurationManager.ActiveConfiguration;
ProjectConfigurationProperties3 cfgProperties = cfg.Object as ProjectConfigurationProperties3;
if (cfgProperties!=null)
{
System.Diagnostics.Debug.WriteLine(cfgProperties.StartArguments);
}
}
Hopefully that'll get you up and running.
After spending some time debugging and with help from Ed Dore, I was able to put together code that gets complete debugging command and working dir for native C++ and managed code projects:
private void ListStartupProperties()
{
ThreadHelper.ThrowIfNotOnUIThread();
IVsHierarchy vsHierarchy = null;
int hresult = bm.get_StartupProject(out vsHierarchy);
object objProject = null;
if(vsHierarchy != null)
hresult = vsHierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_ExtObject, out objProject);
Project startupProject = (Project)objProject;
if (startupProject != null)
{
foreach (Property prop in startupProject.Properties)
{
try
{
textOut.Text += string.Format("{0} = {1}\r\n", prop.Name, prop.Value);
}
catch (Exception e)
{
textOut.Text += e.Message + "\r\n";
}
}
string cmd = "";
string args = "";
string wd = "";
VCProject vcp = startupProject.Object as VCProject;
if (vcp != null)
{ // This is VC project
VCConfiguration vcc = vcp.ActiveConfiguration;
VCDebugSettings dbg = vcc.DebugSettings;
cmd = vcc.Evaluate(dbg.Command);
args = vcc.Evaluate(dbg.CommandArguments);
wd = vcc.Evaluate(dbg.WorkingDirectory);
}
else
{ // Probably C# or VB
Configuration cfg = startupProject.ConfigurationManager.ActiveConfiguration;
ProjectConfigurationProperties cfgProperties = cfg.Object as ProjectConfigurationProperties;
if (cfgProperties != null)
{
string outPath = cfgProperties.OutputPath;
string localPath = startupProject.Properties.Item("FullPath").Value as string;
string outputName = startupProject.Properties.Item("OutputFileName").Value as string;
cmd = cfgProperties.StartProgram != "" ?
cfgProperties.StartProgram :
localPath + outPath + outputName;
args = cfgProperties.StartArguments;
wd = cfgProperties.StartWorkingDirectory;
}
}
textOut.Text += string.Format("StartProgram = {0}\r\n", cmd);
textOut.Text += string.Format("StartArguments = {0}\r\n", args);
textOut.Text += string.Format("WorkingDir = {0}\r\n", wd);
}
}

Hangfire , EF context used in the Task is not updated, the query inside the task always gives the old values

The context used inside the Hangfire task , always gives the old database values, it seems like the context is not updating inside the task. How to get the updated info context data inside hangfire task.
calling the task
BLL.Extraction NewExtractionCls = new BLL.Extraction();
var jobId = BackgroundJob.Enqueue(() => NewExtractionCls.SearchEngineExtraction("SearchURL", "1","1", null));
This is implementation
[Authorize]
public void SearchEngineExtraction(string SearchURL, int PageLimit, int SearchEngineID, PerformContext context)
{
WebClient wc = new WebClient();
#region Main Table - SearchEngineTbl
var NewExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == SearchEngineID).FirstOrDefault();
var JobID = context.BackgroundJob.Id;
NewExtraction.JobID = Convert.ToInt32(JobID);
NewExtraction.SeachEngineURL = SearchURL;
NewExtraction.Status = "Processing";
db.SaveChanges();
var LinkCollectionRefined = ExtractLinkFromThisPage(i, SearchURL, wc).Distinct().ToList();//.Skip(10);
foreach (var Link in LinkCollectionRefined)
{
using (Entities dbRefreshed = new Entities())
{
// I get the same old value here, even if I update the table manually, when I rerun, everything is fine.
var CurrentStatusOfExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == NewExtraction.SearchEngineID).FirstOrDefault();
if (CurrentStatusOfExtraction.IsActive == false)
{
return;
}
}
}
#endregion
}

Unstable application uses SqlDependency. Several states and errors

I have a windows application using SqlDependency running at separated thread pool, this application represents a log monitor UI get the latest rows added in a specific table in the database and view it in a DataGridView. You can see the application source code from this LINK, or follow this script.
const string tableName = "OutgoingLog";
const string statusMessage = "{0} changes have occurred.";
int changeCount = 0;
private static DataSet dataToWatch = null;
private static SqlConnection connection = null;
private static SqlCommand command = null;
public frmMain()
{
InitializeComponent();
}
private bool CanRequestNotifications()
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(tempDelegate, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
// At this point, the code is executing on the
// UI thread, so it is safe to update the UI.
++changeCount;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Reload the dataset that is bound to the grid.
GetData();
}
AutoResetEvent running = new AutoResetEvent(true);
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
running.WaitOne();
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
try
{
running.Set();
}
finally
{
// Update the UI
dgv.Invoke(new Action(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
//dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
}));
}
}
});
}
private void btnAction_Click(object sender, EventArgs e)
{
changeCount = 0;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
SqlDependency.Start("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
if (connection == null)
{
connection = new SqlConnection("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
if (command == null)
{
command = new SqlCommand("select * from OutgoingLog", connection);
//SqlParameter prm =
// new SqlParameter("#Quantity", SqlDbType.Int);
//prm.Direction = ParameterDirection.Input;
//prm.DbType = DbType.Int32;
//prm.Value = 100;
//command.Parameters.Add(prm);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private void frmMain_Load(object sender, EventArgs e)
{
btnAction.Enabled = CanRequestNotifications();
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
The problem:
I have many situations of errors, (images in the first comment)
(No. 1):
I got this error dialog, and I don't know its reason.
(No. 2):
I got nothing in my grid view (No errors, and no data).
(No. 3):
I got only columns names and no rows, although the table has rows.
I need help please.
I may be wrong but a DataSet does not seem to have notification capability so the DataGridView may be surprised if you change it behind its back.
You could try to explicitly show your're changing the data source by first setting it to null:
dgv.DataSource = null;
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
It's worth a try...

getNextFireTime of my existing job

I tried Quartz.com documentation & googled for couple for hours...but could not find single good article on how to get Next Job (which is supposr to fire in future).
I am using CronTrigger Expression to Schedule jobs, using VB.net (winforms). jobs works fine...however I would like my users to see when will it FIRE next. I am storing CronExpression in my database, Can I use that Expression to show next Fire Date/Time to my end users? or if there is any other way possible please advise (with a simple samply).
Thanks
Edit
Following Code Returns next job will fire after 12 minutes instead of 20 minute
Dim exp As CronExpression = New CronExpression("0 0/20 * * * ?")
Dim nextFire As String = exp.GetNextValidTimeAfter(DateTime.Now)
MsgBox(nextFire)
You can create a new JobKey
JobKey jobKey = new JobKey(jobName, groupName);
and use the key to fetch the job detail:
var detail = scheduler.GetJobDetail(jobKey);
this is a simple function which does what you're looking for:
private DateTime getNextFireTimeForJob(IScheduler scheduler, string jobName, string groupName = "")
{
JobKey jobKey = new JobKey(jobName, groupName);
DateTime nextFireTime = DateTime.MinValue;
bool isJobExisting = Scheduler.CheckExists(jobKey);
if (isJobExisting)
{
var detail = scheduler.GetJobDetail(jobKey);
var triggers = scheduler.GetTriggersOfJob(jobKey);
if (triggers.Count > 0)
{
var nextFireTimeUtc = triggers[0].GetNextFireTimeUtc();
nextFireTime = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTimeUtc.Value.DateTime);
}
}
return (nextFireTime);
}
It works only if you have one trigger per job.
If there's more than one trigger in your job you can loop through them:
foreach (ITrigger trigger in triggers)
{
Console.WriteLine(jobKey.Name);
Console.WriteLine(detail.Description);
Console.WriteLine(trigger.Key.Name);
Console.WriteLine(trigger.Key.Group);
Console.WriteLine(trigger.GetType().Name);
Console.WriteLine(scheduler.GetTriggerState(trigger.Key));
DateTimeOffset? nextFireTime = trigger.GetNextFireTimeUtc();
if (nextFireTime.HasValue)
{
Console.WriteLine(TimeZone.CurrentTimeZone.ToLocalTime(nextFireTime.Value.DateTime).ToString());
}
}
or using Linq (System.Linq) :
var myTrigger = triggers.Where(f => f.Key.Name == "[trigger name]").SingleOrDefault();
If you already know the cronExpression then you can call GetNextValidTimeAfter , eg
CronExpression exp = new CronExpression("0 0 0/1 1/1 * ? *");
var nextFire = exp.GetNextValidTimeAfter(DateTime.Now);
Console.WriteLine(nextFire);
and if you want more fire times, then
for(int i=0 ; i< 9; i++)
{
if (nextFire.HasValue)
{
nextFire = exp.GetNextValidTimeAfter(nextFire.Value);
Console.WriteLine(nextFire);
}
}
If you are looking for a more general way of showing next fire times for existing jobs, then check out the answer to this question which works even if you aren't using cron expressions. This is for Quartz Version 2
The Quartz Version 1 way of getting job and trigger information is something like the method below.
public void GetListOfJobs(IScheduler scheduler)
{
var query =
(from groupName in scheduler.JobGroupNames
from jobName in scheduler.GetJobNames(groupName)
let triggers = scheduler.GetTriggersOfJob(jobName, groupName)
select new
{
groupName,
jobName,
triggerInfo = (from trigger in triggers select trigger)
}
);
foreach (var r in query)
{
if (r.triggerInfo.Count() == 0)
{
var details = String.Format("No triggers found for : Group {0} Job {1}", r.groupName, r.jobName);
Console.WriteLine(details);
}
foreach (var t in r.triggerInfo)
{
var details = String.Format("{0,-50} {9} Next Due {1,30:r} Last Run {2,30:r} Group {3,-30}, Trigger {4,-50} {5,-50} Scheduled from {6:r} Until {7,30:r} {8,30:r} ",
t.JobName,
t.GetNextFireTimeUtc().ToLocalTime(),
t.GetPreviousFireTimeUtc().ToLocalTime(),
t.JobGroup,
t.Name,
t.Description,
t.StartTimeUtc.ToLocalTime(),
t.EndTimeUtc.ToLocalTime(),
t.FinalFireTimeUtc.ToLocalTime(),
((t.GetNextFireTimeUtc() > DateTime.UtcNow) ? "Active " : "InActive")
);
Console.WriteLine(details);
}
}
}
I had problem where i am getting next execution time even for the date which are past the end time.
Following is my implementation which eventually returning the correct result in Quartz 3.0.4
internal static DateTime? GetNextFireTimeForJob(IJobExecutionContext context)
{
JobKey jobKey = context.JobDetail.Key;
DateTime? nextFireTime = null;
var isJobExisting = MyQuartzScheduler.CheckExists(jobKey);
if (isJobExisting.Result)
{
var triggers = MyQuartzScheduler.GetTriggersOfJob(jobKey);
if (triggers.Result.Count > 0)
{
var nextFireTimeUtc = triggers.Result.First().GetNextFireTimeUtc();
if (nextFireTimeUtc.HasValue)
{
nextFireTime = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTimeUtc.Value.DateTime);
}
}
}
return nextFireTime;
}

Cannot run a JUnit test case containing threads from Eclipse

I am running JUnit test case from Eclipse 3.4.1 . This test case creates a class which starts a thread to do some stuff. When the test method ends it seems that Eclipse is forcibly shutting down the thread.
If I run the same test from the command line, then the thread runs properly.
Somehow I do not remember running into such problems with Eclipse before. Is this something that was always present in Eclipse or did they add it in 3.4.x ?
Here is an example:
When I run this test from Eclipse, I get a few printts of the cnt (till about 1800) and then the test case is terminated utomatically. However, if I run the main method, which starts JUnit's TestRunner, then the thread counts indefinetely.
import junit.framework.TestCase;
import junit.textui.TestRunner;
/**
* This class shows that Eclipses JUnit test case runner will forcibly
* terminate all running threads
*
* #author pshah
*
*/
public class ThreadTest extends TestCase {
static Runnable run = new Runnable() {
public void run() {
int cnt = 0;
while(true) System.out.println(cnt++);
}
};
public void testThread() {
Thread t = new Thread(run);
t.start();
}
public static void main(String args[]) {
TestRunner runner = new TestRunner();
runner.run(ThreadTest.class);
}
}
I adapted your code to JUnit NG and it's the same result: The thread is killed.
public class ThreadTest {
static Runnable run = new Runnable() {
public void run() {
int cnt = 0;
while (true)
System.out.println(cnt++);
}
};
#Test
public void threadRun() {
Thread t = new Thread(run);
t.start();
assertEquals("RUNNABLE", t.getState().toString());
}
}
If I use the JUnit jar (4.3.1 in my case) from the Eclipe plugin folder to execute the tests via the command line, it has the same behavior like executing it in Eclipse (It's logical :) ).
I tested JUnit 4.6 (just downloaded) in the commandline and it also stops after a short time! It's exactly the same behavior like in Eclipse
I found out, that it is killed if the last instruction is done. It's logical, if you consider how JUnit works:
For each test, a new object is created. If the test is over, it's killed. Everything belonging to this test is killed.
That means, that every thread must be stopped.
JUnit deals correctly with this situation. Unit test must be isolated and easy to execute. So it has to end all threads, if the end of the test is reached.
You may wait, till the test is finished and then execute your assertXXX instruction. This would be the right way to test threads.
But be carefull: It may kill your execution times!
I believe this modification will yield the desired result for unit testing various thread scenarios.
(sorry if the formatting is wonky)
public class ThreadTest {
static Runnable run = new Runnable() {
public void run() {
int cnt = 0;
while (true)
System.out.println(cnt++);
}
};
#Test
public void threadRun() {
Thread t = new Thread(run);
t.start();
//Run the thread, t, for 30 seconds total.
//Assert the thread's state is RUNNABLE, once per second
for(int i=0;i<30;i++){
assertEquals("RUNNABLE", t.getState().toString());
try {
Thread.sleep(1000);//1 second sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Done with my thread unit test.");
}
}
This works but you have to name your thread or find another way to refer to it.
protected boolean monitorSecondaryThread(String threadName, StringBuilder errorMessage, boolean ignoreFailSafe) {
int NUM_THREADS_BESIDES_SECONDARY_THREAD = 2;
int MAX_WAIT_TIME = 10000;
MyUncaughtExceptionHandler meh = new MyUncaughtExceptionHandler();
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for (Thread t : threadSet) {
t.setUncaughtExceptionHandler(meh);
}
Date start = Calendar.getInstance().getTime();
boolean stillAlive = true;
while (stillAlive) {
for (Thread t : threadSet) {
if (t.getName().equalsIgnoreCase(threadName) && !t.isAlive()) {
stillAlive = false;
}
}
Date end = Calendar.getInstance().getTime();
if (!ignoreFailSafe && (end.getTime() - start.getTime() > MAX_WAIT_TIME || Thread.activeCount() <= NUM_THREADS_BESIDES_SECONDARY_THREAD)) {
System.out.println("Oops, flawed thread monitor.");
stillAlive = false;
}
}
if (meh.errorCount > 0) {
System.out.println(meh.error);
errorMessage.append(meh.error);
return false;
}
return true;
}
private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
public int errorCount = 0;
public String error = "";
#Override
public void uncaughtException(Thread t, Throwable e) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(bs);
e.printStackTrace(ps);
error = bs.toString();
errorCount++;
}
}