nunit / nunit3-tdnet-adapter

NUnit 3.x adapter for TestDrivent.NET
https://TestDriven.NET
1 stars 4 forks source link

Found two different objects associated with the same URI, '.../TestAgency'. #9

Open jcansdale opened 8 years ago

jcansdale commented 8 years ago

Any idea what might be going wrong here? Seems to happen pretty randomly inside my test runner unit tests. It has been a while since I wrestled with this kind of stuff. :(

Test 'NUnitTDNet.Adapter.Tests.EngineTestRunnerTests.RunMember_SomeTestsPass_PassedCount1' failed: System.Runtime.Remoting.RemotingException : Found two different objects associated with the same URI, '/6d3534b8_a5c9_4bef_b5a4_34fb5c548621/TestAgency'.
    at System.Runtime.Remoting.IdentityHolder.SetIdentity(Identity idObj, String URI, DuplicateIdentityOption duplicateOption)
    at System.Runtime.Remoting.IdentityHolder.FindOrCreateServerIdentity(MarshalByRefObject obj, String objURI, Int32 flags)
    at System.Runtime.Remoting.RemotingServices.GetOrCreateIdentity(MarshalByRefObject Obj, String ObjURI, Boolean isInitializing)
    at System.Runtime.Remoting.RemotingServices.MarshalInternal(MarshalByRefObject Obj, String ObjURI, Type RequestedType, Boolean updateChannelData, Boolean isInitializing)
    at System.Runtime.Remoting.RemotingServices.MarshalInternal(MarshalByRefObject Obj, String ObjURI, Type RequestedType)
    at System.Runtime.Remoting.RemotingServices.Marshal(MarshalByRefObject Obj, String URI)
    at NUnit.Engine.Internal.ServerBase.Start()
    at NUnit.Engine.Services.TestAgency.StartService()
    at NUnit.Engine.Services.ServiceManager.StartServices()
    at NUnit.Engine.TestEngine.Initialize()
    at NUnit.Engine.TestEngine.GetRunner(TestPackage package)
    EngineTestRunner.cs(53,0): at NUnitTDNet.Adapter.EngineTestRunner.run(ITestListener testListener, Assembly testAssembly, String where)
    EngineTestRunner.cs(34,0): at NUnitTDNet.Adapter.EngineTestRunner.RunMember(ITestListener testListener, Assembly assembly, MemberInfo member)
    EngineTestRunnerTests.cs(45,0): at NUnitTDNet.Adapter.Tests.EngineTestRunnerTests.RunMember_SomeTestsPass_PassedCount1()
CharliePoole commented 8 years ago

Looks like you are either trying to start two test agencies at once or starting an agency without terminating one that's already running. Since there can only be one Test Agency service running, there can only be one Test Engine using the default set of services.

jcansdale commented 8 years ago

It looks like the TestAgency is created when TestEngine.GetRunner(TestPackage) is called. Is there something I should be doing to dispose of the TestAgency?

When the adapter is used properly, this method will only be called once so this isn't an issue. It's only when it's created multiple times (when unit testing) that it might be a problem.

jcansdale commented 8 years ago

Here's the call in question: https://github.com/nunit/nunit3-tdnet-adapter/blob/master/src/NUnitTDNet.Adapter/EngineTestRunner.cs#L53

CharliePoole commented 8 years ago

OK, in your code, you tell the engine that it should run in process and without creating any appdomain. You want it to use the process you have already created and run in the primary domain, which I assume you have set up such that the tests will be found.

However, you are using the default setup of the TestEngine, which gets initialized automatically when you ask for a runner. That default initialization includes the creation of some services you don't actually use. One of them is the TestAgency.

There's no harm in this. It's the same as what I do in the NUnit adapter. It just means you can't create multiple TestEngines, which you appear to be doing in your tests.

The easiest workaround is to do the initialization of the engine services yourself, before you ask for a runner, only including those services you will use. Take a look at nunit-agent to see how this is done there. The agent only runs tests in its own process, so it doesn't use TestAgency at all.

jcansdale commented 8 years ago

Thanks for the info. I tried changing it to the following, but I'm still seeing that exception. :disappointed:

            using (TestEngineClass engine = new TestEngineClass())
            {
                engine.Services.Add(new SettingsService(false));
                //engine.Services.Add(new ProjectService());
                engine.Services.Add(new DomainManager());
                engine.Services.Add(new InProcessTestRunnerFactory());
                engine.Services.Add(new DriverService());
                engine.Initialize();

How does the TestEngine know when it has been fully initialized and that it doesn't have to add TestAgency and other services?

CharliePoole commented 8 years ago

That looks good down to the point where you call engine.Initialize(). Since you are doing your own initialization, you don't want the engine to also do it. Call Services.ServiceManager.StartServices() instead. If you want to initialize internal trace, you can do that as well.

jcansdale commented 8 years ago

If I change it to:

            using (TestEngineClass engine = new TestEngineClass())
            {
                engine.Services.Add(new SettingsService(false));
                //engine.Services.Add(new ProjectService());
                engine.Services.Add(new DomainManager());
                engine.Services.Add(new InProcessTestRunnerFactory());
                engine.Services.Add(new DriverService());
                engine.Services.ServiceManager.StartServices();

I get the following:

    System.NullReferenceException: Object reference not set to an instance of an object.
    at NUnit.Engine.Services.DriverService.StartService()
    at NUnit.Engine.Services.ServiceManager.StartServices()
    EngineTestRunner.cs(53,0): at NUnitTDNet.Adapter.EngineTestRunner.run(ITestListener testListener, Assembly testAssembly, String where)

How does engine.Services.ServiceManager get initialized?

CharliePoole commented 8 years ago

You left out ExtensionService. This should be OK if you didn't want any extensions but the code in DriverService depends on it's being there. This should be fixed.

Meanwhile, just add it right after SettingsService - those two services are the two most likely to be needed by other services in the future.

jcansdale commented 8 years ago

I've ended up with the following set, which seems to work:

                engine = new TestEngineClass();
                engine.Services.Add(new SettingsService(false));
                engine.Services.Add(new ExtensionService());
                engine.Services.Add(new DriverService());
                engine.Services.Add(new InProcessTestRunnerFactory());
                engine.Services.Add(new ProjectService()); // +
                engine.Services.Add(new RuntimeFrameworkService()); // +
                engine.Services.Add(new TestFilterService()); // +
                //engine.Services.Add(new DomainManager()); // -
                //engine.Services.Add(new RecentFilesService());
                //engine.Services.Add(new DefaultTestRunnerFactory());
                //engine.Services.Add(new TestAgency());
                //engine.Services.Add(new ResultService());
                engine.Services.ServiceManager.StartServices();

I've tagged with \\ + the new ones I added and // - the ones I removed since last time. Is there anything you think I might be missing?

CharliePoole commented 8 years ago

I don't see anything missing, but do you actually need the ones you added?

ProjectService is a no-op unless you add extensions for specific project types and I don't imagine you want to open nunit or vs project files.

RuntimeFrameworkservice is used to select the required .NET runtime for a test, but you can't affect that without creating a new process.

TestFilterService is needed if you want to parse text to create filters.

I think the more natural starting place for you is the list of services used by the agent, from which you could remove what you don't need. I say that because you are basically in the same situation as the agent... you're in a process that has already been created... except that you don't even need to create new app domains.

CharliePoole commented 8 years ago

Of course, there is no loss to add a service you don't use, since it's already loaded. But in the future, some services will become extensions and adding them will mean you need to provide the extension assembly somewhere as well. I think it's also good documentation to only load the ones you actually need plus (for now) ExtensionService due to the bug I pointed out.

jcansdale commented 8 years ago

I think they're all necessary. I tried removing each in turn and added it back the test runner failed (generally with null-ref exceptions). It didn't actually fail when SettingsService was removed. Is this strictly necessary?

CharliePoole commented 8 years ago

I added code to most services that use SettingsService to have a hard-coded default if it was not available. You would have to look at the failing service's startup code to see what it needs. If it seems as if there is no good reason for a particular failure, a bug report would be great. I think that the services should generally be as forgiving as possible when other services are not available. If nothing else, it makes testing easier.

CharliePoole commented 8 years ago

Anyway... they can't all be needed, else nunit-agent wouldn't start!

CharliePoole commented 8 years ago

Bear in mind that order of adding is significant - I wish it were not, but it is. :-)

jcansdale commented 8 years ago

Removing either of these:

            engine.Services.Add(new ProjectService()); // +
            engine.Services.Add(new RuntimeFrameworkService()); // +

cause it to null-ref on this line: var totalTests = runner.CountTestCases(filter);

I'd dig more but our child minder is on holiday and we're potty training! :wink:

CharliePoole commented 8 years ago

No need... I think you are running into issue #1732, which has just been fixed for a 3.4.2 release. You can work around it by explicitly calling Load before you call Explore, CountTestCases or Run.

pamle117 commented 4 years ago

I am using Test API and facing same issue, I am new to NUnit and looks like API has been changed and I am not able to follow steps mentioned to customize initialization of the engine I am using this method to get engine, engine = TestEngineActivator.CreateInstance(); how can I disable Test Agency Service? When more than one thread of execution reaches at this point runner = engine.GetRunner(package); I get the error: Found two different objects associated with the same URI, /xxxxx/TestAgency

CharliePoole commented 4 years ago

@pamle117 The nunit3-tdnet-adapter is an NUnit component supported by @jcansdale for use in running tests under his td.net VS extension. It's usualy not a good idea to piggyback new problems on top of old issues, but especially when your problem is with use of a different piece of software. :smile:

I'm transferring this issue to the nunit-console project, which supports the console runner and engine, which you are using.

UPDATE - Sorry, I realized I can't transfer this because it still needs to be worked in the tdnet adapter project. Please file your own issue under nunit-console, explaining what you are trying to accomplish and how you want to use the engine.

TestAgency is one of the major services the engine provides. It controls all processes started by the engine. So if you don't need it, you must be using the engine in a very specific way.

pamle117 commented 4 years ago

Oh I am sorry. I have created new issue under nunit console. Thanks!