Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Tests unreliable with multi-threaded code.
Cjdawson
#1 Posted : Saturday, June 27, 2015 4:23:42 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 6/27/2015(UTC)
Posts: 4
Location: United Kingdom

Thanks: 1 times
Hi all. I have a number of tests that call a method which is using some multi-threaded code. I don't understand why, but the tests fail under NCrunch. I'm using Moq in combination with my code so that I don't have to create a whole fake repository. Anyways. I'll past the relevent pieces of code here and hopefully someone can help. Not really sure what to ask, as it's failing I'll flag up where the code fails...

First up, here's one of the error messeages from when the code fails.

An exception was thrown in the Task Environment: System.NullReferenceException: Object reference not set to an instance of an object.
at SecurityModule.SecurityUserControlViewModel.<>c__DisplayClassa.<LoadSecurityItemsInCurrentProfile>b__9() in D:\Code\WPFDatabaseApplication\BasicDatabaseProgram\SecurityModule\SecurityUserControlViewModel.cs:line 127
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at SecurityModule.SecurityUserControlViewModel.<LoadCurrentAccessProfileDetailsAsync>d__0.MoveNext() in D:\Code\WPFDatabaseApplication\BasicDatabaseProgram\SecurityModule\SecurityUserControlViewModel.cs:line 89
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__5(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
System.NullReferenceException: Object reference not set to an instance of an object.
at SecurityModule.SecurityUserControlViewModel.<>c__DisplayClassa.<LoadSecurityItemsInCurrentProfile>b__9() in D:\Code\WPFDatabaseApplication\BasicDatabaseProgram\SecurityModule\SecurityUserControlViewModel.cs:line 127
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at SecurityModule.SecurityUserControlViewModel.<LoadCurrentAccessProfileDetailsAsync>d__0.MoveNext() in D:\Code\WPFDatabaseApplication\BasicDatabaseProgram\SecurityModule\SecurityUserControlViewModel.cs:line 89
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__5(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()



Here's one of the tests that sometimes fails.

[TestMethod]
public void RenameSecurityGroupGroupCreated()
{
//arrange
List<AccessProfile> accessProfiles = new List<AccessProfile>();
AccessProfile accessProfile = new AccessProfile("Profile1", false);
accessProfiles.Add(accessProfile);

Mock<ISecurityModuleRepository> mock = new Mock<ISecurityModuleRepository>();
mock.Setup(r => r.GetSecurityGroups()).Returns(accessProfiles);
mock.Setup(r => r.GetSecurityItemsInCurrentProfile("Profile1")).Returns(() => new List<SecurityItem> { });
mock.Setup(r => r.GetUsersInAccessProfile("Profile1")).Returns(() => new List<UserItem> { });

//act
SecurityUserControlViewModel securityUserControlViewModel = new SecurityUserControlViewModel(mock.Object);

//assert
Assert.IsNotNull(securityUserControlViewModel.RenameSecurityGroup);
}

creating the SecurityUserControlViewModel executes

The contructor in my class.

public class SecurityUserControlViewModel : ViewModelBase
{
public SecurityUserControlViewModel(ISecurityModuleRepository securityModuleDataAccess)
{
AccessProfiles.SelectedItemChanged += AccessProfiles_SelectedItemChanged;
_securityModuleDataAccess = securityModuleDataAccess;
CreateCommands();
LoadSecurityGroups();
}

The important part there is LoadSecurityGroups

private void LoadSecurityGroups()
{
foreach (AccessProfile accessProfile in _securityModuleDataAccess.GetSecurityGroups())
{
AccessProfiles.Add(accessProfile);
}

AccessProfiles.SelectedItem = AccessProfiles.Count > 0 ? AccessProfiles[0] : null;
}


Setting the selected Item triggers an event.

private void AccessProfiles_SelectedItemChanged(object sender, EventArgs e)
{
LoadCurrentAccessProfileDetailsAsync(AccessProfiles.SelectedItem);
}

Please note that this is using the Async/Await pattern so that two calls to a repository can be executed concurrently.

private async void LoadCurrentAccessProfileDetailsAsync(AccessProfile profile)
{
Task<ObservableCollection<IObservableSecurityItem>> taskSecurityItemsInCurrentProfile =
LoadSecurityItemsInCurrentProfile(profile);

Task<ObservableCollection<IObservableUserItem>> taskUsersInAccessProfile =
LoadUsersInAccessProfile(profile);

SecurityItemsInCurrentProfile = await taskSecurityItemsInCurrentProfile;
UsersInAccessProfile = await taskUsersInAccessProfile;
}

private Task<ObservableCollection<IObservableUserItem>> LoadUsersInAccessProfile(AccessProfile profile)
{
return Task<ObservableCollection<IObservableUserItem>>.Factory.StartNew(
() =>
{
var newList = new ObservableCollection<IObservableUserItem>();

IEnumerable<UserItem> usersInProfile = _securityModuleDataAccess.GetUsersInAccessProfile(profile.Name);
if (usersInProfile != null)
{
foreach (UserItem item in usersInProfile)
{
var observableUserItem = IocWrapper.Instance.Resolve<IObservableUserItem>();
observableUserItem.AccessProfileName = item.AccessProfileName;
observableUserItem.DisplayName = item.DisplayName;
observableUserItem.IsMember = item.IsMember;
observableUserItem.HasChanges = false;
observableUserItem.AutoSave = true;

newList.Add(observableUserItem);
}
}
return newList;
}
);
}

private Task<ObservableCollection<IObservableSecurityItem>> LoadSecurityItemsInCurrentProfile(AccessProfile profile)
{
return Task<ObservableCollection<IObservableSecurityItem>>.Factory.StartNew(
() =>
{
var newList = new ObservableCollection<IObservableSecurityItem>();

IEnumerable<SecurityItem> securityItems = _securityModuleDataAccess.GetSecurityItemsInCurrentProfile(profile.Name);
if (securityItems != null)
{
foreach (SecurityItem item in securityItems)
{
var observableSecurityItem = IocWrapper.Instance.Resolve<IObservableSecurityItem>();
observableSecurityItem.AccessProfileName = item.AccessProfileName;
observableSecurityItem.FunctionName = item.FunctionName;
observableSecurityItem.ModuleName = item.ModuleName;
observableSecurityItem.IsMember = item.IsMember;
observableSecurityItem.HasChanges = false;
observableSecurityItem.AutoSave = true;

newList.Add(observableSecurityItem);
}
}
return newList;
}
);
}

one of the two lines in red sometimes fails when run under nCrunch.
from the debugging that I've been able to do it appears that _securityModuleDataAccess isn't populated - but it should be every time. When I run the unit tests under MSTest everything works fine, also when I run the actual program is does not fail.

I love the product and fine it hard to believe that this problem is beyond the capabilities of the tool. Out of curiosity, I also changed the code over so that rather than using tasks, I made the code use a single thread, but that this had the same problem.

Can someone help me to make the unit tests stable under nCrunch please?
Remco
#2 Posted : Saturday, June 27, 2015 10:17:50 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,144

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Hi,

Thanks for sharing this issue.

NCrunch doesn't have knowledge over the code being executed - it basically just calls into the tests using reflection and lets your code do its thing. This means that it wouldn't really be possible for NCrunch to directly change the execution path of your test. Most likely the reason for test failure under NCrunch is due to some kind of trigger that is specific to the environment.

With the code you've shown, I can't see any reason why it would behave in the way you've described. I would suggest the following troubleshooting approaches for you to try and get more information about the behaviour here:

1. Try adding some tracing to the code (i.e. Debug.Write) statements to see the physical sequence of execution. In particular it's important to establish the lifespan of the _securityModuleDataAccess field to see where this is being set and whether the mock is physically working in the way the code expects it to.
2. Try setting the NCrunch running in compatibility mode - http://www.ncrunch.net/documentation/troubleshooting_compatibility-mode. This can often help to rule out whether other features in NCrunch might impact the code somehow (unlikely, but this is easy to do so worthwhile).
3. Try simplifying the code down deductively until you can establish the smallest possible sample that reproduces the problem. If you can share with me a minimal solution that compiles and demonstrates this problem, I may be able to help with further troubleshooting.
Cjdawson
#3 Posted : Sunday, June 28, 2015 10:10:08 AM(UTC)
Rank: Newbie

Groups: Registered
Joined: 6/27/2015(UTC)
Posts: 4
Location: United Kingdom

Thanks: 1 times
Remco;7443 wrote:
Hi,
2. Try setting the NCrunch running in compatibility mode - http://www.ncrunch.net/documentation/troubleshooting_compatibility-mode. This can often help to rule out whether other features in NCrunch might impact the code somehow (unlikely, but this is easy to do so worthwhile).


How exactly is this done? I cannot find any of the settings mentioned on the linked page.
I was looking in NCrunch --> configuration. Is there somewhere else that isn't obvious to locate?
Remco
#4 Posted : Sunday, June 28, 2015 11:27:14 AM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,144

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Cjdawson;7444 wrote:

How exactly is this done? I cannot find any of the settings mentioned on the linked page.
I was looking in NCrunch --> configuration. Is there somewhere else that isn't obvious to locate?


Yes - all of these settings are located under the NCrunch configuration. Most of them are project-level configuration settings (so you'll need to multi select your projects to apply the settings to all of them).
Cjdawson
#5 Posted : Sunday, June 28, 2015 1:00:10 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 6/27/2015(UTC)
Posts: 4
Location: United Kingdom

Thanks: 1 times
Remco;7445 wrote:
Cjdawson;7444 wrote:

How exactly is this done? I cannot find any of the settings mentioned on the linked page.
I was looking in NCrunch --> configuration. Is there somewhere else that isn't obvious to locate?


Yes - all of these settings are located under the NCrunch configuration. Most of them are project-level configuration settings (so you'll need to multi select your projects to apply the settings to all of them).



I must be really stupid, as I cannot see any of the options that are listed on the page. The instructions are not clear to me at all. In addition, I think the problem is somewhere in the way that NCrunch is running the tests. Sometimes whey work, sometimes they don't, and there's no reason for the test to fail - it is literally a case that I Open up NCrunch --> Tests, then press the Run all tests button and they will run - sometimes with a test fail, then I click the button again, and a different test will fail, then when I click again the tests might pass. There is no consistency to it, and objects are all created.

When I runt the tests using the build in Test runner (MS Test, I believe) they pass every time. When I run the tests with resharper, again they pass every time. At the moment, I'm beginning to think that I'd be better off uninstalling NCrunch as it appears to be inconsistent.

I have spent the morning rewriting all the tests so that moq isn't required, that made no difference, and resulted in inferior tests anyway, so I've revested back to using moq.

I'm happy to share the source code with you to help with debugging NCrunch - I really love the nice friendly OK on the Risk/Progress window. I love how it keeps updated when I'm adding code, it makes me happy to see the nice big green friendly OK.

I'm happy to share the source code with you in order to help diagnose the inconsistency.
Remco
#7 Posted : Sunday, June 28, 2015 10:40:07 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,144

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Cjdawson;7447 wrote:

I must be really stupid, as I cannot see any of the options that are listed on the page. The instructions are not clear to me at all. In addition, I think the problem is somewhere in the way that NCrunch is running the tests. Sometimes whey work, sometimes they don't, and there's no reason for the test to fail - it is literally a case that I Open up NCrunch --> Tests, then press the Run all tests button and they will run - sometimes with a test fail, then I click the button again, and a different test will fail, then when I click again the tests might pass. There is no consistency to it, and objects are all created.


This really does sound exactly like the tests are somehow sequence dependent. Considering the failure is intermittent, I wouldn't bother with compatibility mode - this won't make any difference. I recommend having a read of this documentation page as I have a feeling this will apply to your situation - http://www.ncrunch.net/documentation/considerations-and-constraints_test-atomicity.

NCrunch has a facility that allows you to run tests in a way that can re-use the test runner process. If you right click a test, then choose 'Advanced->Run test in existing process', you should be able to create custom test execution sequences if you run the tests one at a time. This is often a good way to identify sequence dependencies. It may well be that an execution of one of the tests is causing some kind of state to be left inside the process that interferes with (or facilitates) subsequent test runs.

It's important to consider that just because the tests can pass on a simple end to end run with another runner, this does not mean that they are completely safe and atomic. NCrunch has a reputation for uncovering these sorts of issues because unlike other runners, it will run tests in a heavily customised sequence and this sequence will change dynamically based on your activities in the IDE.

Trace code is really, really, useful when trying to diagnose issues like this, as they are too inconsistent to debug directly and usually can only be analysed postmortem. There is likely to be something very simple yet fundamental happening with these tests that is being masked by the problem you're experiencing.

Using the IsolatedAttribute is also worth trying as this removes many of the variables around process re-use and state being left behind in the test process by other tests. Placing this attribute on the tests involved may solve the issue entirely.
1 user thanked Remco for this useful post.
Cjdawson on 6/28/2015(UTC)
Cjdawson
#6 Posted : Sunday, June 28, 2015 10:57:27 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 6/27/2015(UTC)
Posts: 4
Location: United Kingdom

Thanks: 1 times
Figure it out. There was something funky going on with the threading code. I'd decided that as "nothing was wrong with my code, as the tests were passing and I'd never seen a problem at runtime" I'd go ahead and start adding some more unit tests to try and cover off the rest of the code. As a result of those extra tests, I found that there was a problem with sometime seemingly unrelated, but it was on the code path of all the test there were failing with object not set to an instance of an object on occasion.

The unit test that I was working on tested that the items were being set in the code properly. In the end there were two bugs in my code. Both in the method
LoadCurrentAccessProfileDetailsAsync

For information here's my revised version.


private void LoadCurrentAccessProfileDetailsAsync(AccessProfile profile)
{
if (profile == null)
{
SecurityItemsInCurrentProfile = null;
UsersInAccessProfile = null;
return;
}

Task<ObservableCollection<IObservableSecurityItem>> taskSecurityItemsInCurrentProfile =
LoadSecurityItemsInCurrentProfile(profile);

Task<ObservableCollection<IObservableUserItem>> taskUsersInAccessProfile =
LoadUsersInAccessProfile(profile);

SecurityItemsInCurrentProfile = taskSecurityItemsInCurrentProfile.Result;
UsersInAccessProfile = taskUsersInAccessProfile.Result;
}

The first bug was that did not handle the situation when a null AccessProfile is passed to the method - it's an edge case which should never happen in production.
The second was that as I was using the Async/Await, there was a possibility of the unit test code finishing before this method had completed! The intention was to allow both repository calls to take place concurrently, but for the method not to return until after both calls had completed. So, to facilitate that, I removed the async/await keywords and read from Task<>.Result instead which is a blocking call.
This has the intention that I was wanting.

My trust in NCrunch just went through the roof over the resharper test engine and the test runner built into VS. NCrunch was failing on occasion, and was correctly highlighting that something wasn't quite right.
Remco
#8 Posted : Sunday, June 28, 2015 11:35:20 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,144

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Great work in figuring this one out! Unit testing async code is often tough. I hope to add features in future to make analysing these sorts of issues easier, as they can get very frustrating.

Thanks for sharing the solution and I hope the tool continues to work well for you :)
Users browsing this topic
Guest
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.

YAF | YAF © 2003-2011, Yet Another Forum.NET
This page was generated in 0.189 seconds.
Trial NCrunch
Take NCrunch for a spin
Do your fingers a favour and supercharge your testing workflow
Free Download