Reputation: 1732
We're using the VS 2010 test runner (MSTest) for automated functional testing. When we run tests from Visual Studio, VS creates a process called QTAgent32.exe, and it runs the tests in that process.
We've found that when we do multiple test runs, MSTest will reuse the same QTAgent32 process - the process ID does not change. This is a problem for us, since the code we're testing is P/Invoking to an unmanaged DLL. The DLL needs to be initialized only once during the lifetime of the process. We have an [AssemblyInitialize] method, but that executes once per test run. If we perform multiple test runs, it will execute more than once in the same process.
Every time we do a test run, MSTest creates a new appdomain; but these appdomains are all in the same process.
So I'm wondering: is there a way to tell the Visual Studio test runner to use a new process every time we run tests? I looked at the ".testsettings" configuration but didn't see anything relevant.
Upvotes: 13
Views: 4629
Reputation: 2474
I created an (experimental) solution for MSTest + .Net Core in 2023
It requires https://www.nuget.org/packages/Tmds.ExecFunction
public class OutOfProcessTestAttribute : TestMethodAttribute
{
private static readonly FunctionExecutor Executor = new FunctionExecutor(
o =>
{
o.StartInfo.RedirectStandardError = true;
o.OnExit = p =>
{
if (p.ExitCode != 0)
{
throw new Exception(p.StandardError.ReadToEnd());
}
};
});
public override TestResult[] Execute(ITestMethod testMethod)
{
var hostFilename = Process.GetCurrentProcess().MainModule.FileName;
if (hostFilename.EndsWith("/testhost") || hostFilename.EndsWith("\\testhost.exe"))
{
return base.Execute(testMethod);
}
try
{
var obj = Activator.CreateInstance(testMethod.MethodInfo.DeclaringType!);
Action action = (Action)testMethod.MethodInfo.CreateDelegate(typeof(Action), obj);
Executor.Run(action);
return new TestResult[1] { new() { Outcome = UnitTestOutcome.Passed } };
}
catch (Exception e)
{
return new TestResult[1] { new() { TestFailureException = e, Outcome = UnitTestOutcome.Failed } };
}
}
}
Now you can replace [TestMethod]
with [OutOfProcessTest]
Important things to note:
It will create a new instance of the Test Class, because I couldn't get reference to the original instance. Therefor try to make the method self contained as possible, the [TestInitialize]
etc attributes won't apply.
When running inside Visual Studio Test Host, Tmds.ExecFunction
does not work because it cannot find the parent process (works fine from dotnet test
though). I choose to just run the test normally, but you should modify the attribute to behave as you want (e.g. perhaps skip the test instead of run it)
I did not test async
functions, perhaps it will require some modification to support.
Upvotes: 0
Reputation: 535
VS 2013 and forward now has a setting for this under Test > Test Settings > Keep Test Execution Engine Running. Unchecking this selection will start up a new engine each run.
Upvotes: 0
Reputation: 1732
I was able to get this working after reading Wiktor's comment about FreeLibrary().
I used this class created by Mike Stall, which provides wrappers around LoadLibrary, GetProcAddress, and FreeLibrary. That way, I can load the library once in each test run, call the necessary methods, and then free the library at the end of the test run.
Mike Stall's code uses Marshal.GetDelegateForFunctionPointer, which converts an unmanaged function pointer to a managed delegate type.
I had to replace the [DllImport] extern declarations with declarations for delegate types. So I converted this:
[DllImport("asesignal.dll")]
public static extern bool ASESDK_Initialize(string licenseCode);
to this:
public delegate bool ASESDK_Initialize(string licenseCode);
Mike Stall's code contained examples with generic delegates (Action<T> etc.). But I couldn't get that working, so I created my own delegate types.
I can load the DLL dynamically like this:
_ht = new UnmanagedLibrary(@"c:\windows\system32\asesignal.dll");
To call a function, I do this:
var function = _ht.GetUnmanagedFunction<ASESDK_Initialize>("ASESDK_Initialize");
function(licenseCode);
Thanks Wiktor and np-hard for your help!
Upvotes: 1
Reputation: 5815
dont know how far you want to go with it, but one solution could be to create your unit test host
http://technet.microsoft.com/fr-fr/query/bb166558
this link shows how to create adapters, also you could then launch a new process for evertest, create a piped communication and tear it down after the test.
I know MS itself uses a different host for running tests under moles
http://research.microsoft.com/en-us/projects/pex/molestutorial.pdf
Upvotes: 6