Error: "Attempting managed execution inside OS Loader lock" when displaying a WinForms window on a separate thread

Oct 13, 2009 at 2:21 AM

First of all, kudos on all the great work!  Here's my problem.  I'm on debug mode stepping into the code when I get the following error:

LoaderLock was detected

Message: Attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.

I'm working on a unit test (VS Team System Team edition). My goal is to instantiate a Form class, display it, trigger events with White, then check the resultant changes on my Form instance.  I've been running into threading and thread apartment problems all week, and here's what I have at this point:

namespace MyTest
{
    /// <summary>
    ///This is a test class for MyFormTest and is intended
    ///to contain all MyFormTest Unit Tests
    ///</summary>
    [TestClass()]
    public class MyFormTest
    {
        // initialization...
    
        /// <summary>
        ///A test for myMenu_Click
        ///</summary>
        ///
        [TestMethod()]
        [DeploymentItem("MyTest.exe")]
        public void myMenu_ClickTest()
        {
            Core.Application app = Core.Application.Attach(Process.GetCurrentProcess());

            myForm myForm = null;

            Thread mainThread = new Thread(
                        new ThreadStart(
                            delegate()
                            {
                                try
                                {
                                    myForm = new myForm();
                                    myForm.Show();
                                }
                                catch (Exception ex)
                                {
                                    System.Diagnostics.Debug.WriteLine(ex.Message);
                                }
                            }
                        )
                    );
            mainThread.SetApartmentState(ApartmentState.STA);
            
            try
            {
                mainThread.Start();

                // wait for myForm
                while (myForm == null)
                {
                    Thread.Sleep(1000);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            Window myFormWindow = app.GetWindow("MyTest Title", InitializeOption.NoCache);
            myFormWindow.MenuBar.MenuItem("File", "Import", "Click Me").Click();

            Assert.IsTrue(myForm.isClicked);
        }
    }
}

I have my testrunconfig on <ExecutionThread apartmentState="STA"/> by the way.  Can anyone tell me what I'm doing wrong?

Oct 15, 2009 at 11:54 AM

Hm.. I'll have a go at it.

Is there a reason why you want to use threading? For me creating and showing the form is a concern for the application and not the tests (and threading complicates testing in general since it is difficult to keep track of the order of things)
A simpler way of using White is to include the path to your executable:

[TestMethod()]
public void myFirstTest()
{
Application application = Application.Launch("Path\myprogram.exe");

Window mainWindow = application.GetWindow("Form 1", initializeOption.NoCache);

Assert.IsNotNull(mainWindow);
}

and have the application handle all possible threads and such. 
"My goal is to instantiate a Form class, display it, trigger events with White, then check the resultant changes on my Form instance."
I would instantiate and display the form using my application and then trigger events and check the results with White.
Oct 16, 2009 at 4:10 AM

Thanks for the reply.  I do in fact have tests that do exactly what you say.  The reason I want to do this is to retain a reference to my Form object and test some internal, non-displayable properties in my application.  In effect I want to do something like this:

[TestMethod()]
public void myFirstTest()
{
    MyForm form = new MyForm();
    form.Show();

    Application application = Application.Attach(Process.GetCurrentProcess());

    Window mainWindow = application.GetWindow("My Form", initializeOption.NoCache);

    Assert.IsNotNull(form.InternalProperty);
    Assert.IsEqual(42, form.DoWork());
}

Is this at all possible, or is this a misuse of White and should look at other frameworks to do what I want?

Oct 16, 2009 at 11:45 AM

I would say keep it simple. If the instanciation/threading/referencing is giving you problems, call the form as you normally would and check those properties that way. Or perhaps I'm missing some benefit of doing it another way?

Oh, it occured to me... Do you mean that by using White you only gain access the visible components and you are looking for a way to get ahold of other properties?
My spontaneous thoughts then are: Are you separating your concerns? What sort of work is your form doing that isn't user interactions?
Or, your concerns are separeted and perhaps you can check these properties by regular unit testing?

Do you have an example of a property or work?

Oct 16, 2009 at 1:14 PM
Edited Oct 16, 2009 at 1:15 PM

You hit the nail right on the head.  The thing is I'm starting work on an extremely complex project that unfortunately started out without separation of business logic and IU.  I'm trying to retrofit the code and separate the logic and UI, but I was hoping to put unit tests on the more complex of the operations before I go in and mess around with refactoring to make sure I don't break stuff.

I guess this is possible to a certain extent, but right now the effort required for such a task seems to exceed the benefits.  I will probably go ahead and refactor, then put unit tests on the more modular, testable results.

Oct 16, 2009 at 1:47 PM

Are we working at the same company? I'll be doing the same very soon so I understand your problem. I'm at the first step, setting up a GUI test harness to be able to catch some issues (and because we need GUI tests anyway).
Hey, worst case you have source control, right? So you can always roll back your code. :-) Good luck with your refactoring!