Code Coverage not looking at newly spawned AppDomains

Code Coverage not looking at newly spawned AppDomains

Hi,

In one of my unit test assemblies, I create multiple AppDomains to test the components in 'simulation' to client/server calls, and testing different configuration settings in App.config files.

I have noted that the code within the new AppDomains are not reported as covered. I haven't tried to see if the "typical" cross-AppDomain calls (inheritting MarshalByRef) reproduces the issue/bug, however attached below is the code which I use to do these new spawns in my test code. (I can't remember why I used that interface instead of inheritting from MarshalByRef, but there was a good reason why I couldn't go with MarshalByRef).

I would assume (imagine [:)]) that the bug is due to that NCover detects which AppDomains are loaded at the beginning of the process' lifetime and only watches those ones - never noticing any new AppDomains which comes into the picture. Is it possible - through the profiling API - for NCover to detect the spawning of new AppDomains?

NCover 1.5.4 Beta

// general implementation

 public interface IAppDomainHostedClient {
  string ApplicationConfigurationFile {
   get;
  }

  void Started();
  void Execute();
  void ShuttingDown();
 }

 public sealed class RemoteAppDomainFixtureHelper {
  private ArrayList executionBlocks = new ArrayList();
  private ArrayList loadedBlocks = new ArrayList();
  private ArrayList loadedDomains = new ArrayList();

  private void CreateAppDomain(IAppDomainHostedClient client) {
   AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
   Evidence evidence = AppDomain.CurrentDomain.Evidence;

   if ((client.ApplicationConfigurationFile != null) && (client.ApplicationConfigurationFile.Length > 0))
    setup.ConfigurationFile = client.ApplicationConfigurationFile;

   AppDomain appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), evidence, setup);
   Type proxyType = typeof(RemoteClientProxy);
   RemoteClientProxy proxy = (RemoteClientProxy)appDomain.CreateInstanceAndUnwrap(proxyType.Assembly.FullName, proxyType.FullName);

   this.loadedDomains.Add(appDomain);
   proxy.InitializeClient(client);
   this.loadedBlocks.Add(proxy);
  }

  public void RegisterCodeBlock(IAppDomainHostedClient client) {
   if (client == null)
    throw new ArgumentNullException("client");

   this.executionBlocks.Add(client);
  }

  public void Execute() {
   try {
    foreach (IAppDomainHostedClient client in this.executionBlocks)
     this.CreateAppDomain(client);

    foreach (IAppDomainHostedClient client in this.loadedBlocks)
     client.Started();

    foreach (IAppDomainHostedClient client in this.loadedBlocks)
     client.Execute();
   }
   finally {
    try {
     Exception caughtException = null;

     foreach (IAppDomainHostedClient client in this.loadedBlocks) {
      try {
       client.ShuttingDown();
      }
      catch (Exception ex) {
       if (caughtException == null)
        caughtException = ex;
      }
     }

     if (caughtException != null)
      throw caughtException;
    }
    finally {
     foreach (AppDomain domain in this.loadedDomains)
      AppDomain.Unload(domain);
    }
   }
  }

  public RemoteAppDomainFixtureHelper() {
  }

  #region Classes

  private sealed class RemoteClientProxy : MarshalByRefObject, IAppDomainHostedClient {
   private IAppDomainHostedClient client;

   public void InitializeClient(IAppDomainHostedClient client) {
    this.client = client;
   }

   public RemoteClientProxy() {
   }

   #region IAppDomainHostedClient Members

   public string ApplicationConfigurationFile {
    get {
     return this.client.ApplicationConfigurationFile;
    }
   }

   public void Started() {
    this.client.Started();
   }

   public void Execute() {
    this.client.Execute();
   }

   public void ShuttingDown() {
    this.client.ShuttingDown();
   }

   #endregion
  }

  #endregion
 }

// usage of the above implementation

[TestFixture()]
 public class RemoteAppDomainFixtureHelperFixture {
  [Test()]
  [ExpectedException(typeof(Exception), "The method or operation is not implemented.")]
  public void ExceptionTest() {
   RemoteAppDomainFixtureHelper h = new RemoteAppDomainFixtureHelper();

   h.RegisterCodeBlock(new ExceptionalTestClass());
   h.Execute();
  }

  [Test()]
  public void RemotingTest() {
   RemoteAppDomainFixtureHelper h = new RemoteAppDomainFixtureHelper();

   h.RegisterCodeBlock(new RemotingServerHost());
   h.RegisterCodeBlock(new RemotingClientHost());
   h.Execute();
  }

  public RemoteAppDomainFixtureHelperFixture() {
  }

  #region Classes

  [Serializable()]
  private sealed class ExceptionalTestClass : IAppDomainHostedClient {
   #region IAppDomainHostedClient Members

   public void Started() {
    throw new Exception("The method or operation is not implemented.");
   }

   public void Execute() {
    throw new Exception("The method or operation is not implemented.");
   }

   public void ShuttingDown() {
    throw new Exception("The method or operation is not implemented.");
   }

   public string ApplicationConfigurationFile {
    get {
     throw new Exception("The method or operation is not implemented.");
    }
   }

   #endregion
  }

  [Serializable()]
  private sealed class RemotingServerHost : IAppDomainHostedClient {
   #region IAppDomainHostedClient Members

   public string ApplicationConfigurationFile {
    get {
     return null;
    }
   }

   public void Started() {
    RemotingConfiguration.Configure("RemoteAppFixtureServer.config");
   }

   public void Execute() {
   }

   public void ShuttingDown() {
   }

   #endregion
  }

  [Serializable()]
  private sealed class RemotingClientHost : IAppDomainHostedClient {
   #region IAppDomainHostedClient Members

   public string ApplicationConfigurationFile {
    get {
     return null;
    }
   }

   public void Started() {
    RemotingConfiguration.Configure("RemoteAppFixtureClient.config");
   }

   public void Execute() {
    RemotingCalledClass c = new RemotingCalledClass();

    Assert.AreEqual("Hello work", c.DoSomething("Hello work"));
    Assert.AreNotEqual(AppDomain.CurrentDomain.FriendlyName, c.CurrentAppDomain);
   }

   public void ShuttingDown() {
   }

   #endregion
  }

  private sealed class RemotingCalledClass : MarshalByRefObject {
   public string DoSomething(string message) {
    return message;
   }

   public string CurrentAppDomain {
    get {
     return AppDomain.CurrentDomain.FriendlyName;
    }
   }
  }

  #endregion
 }


Re: Exception when running two ncover instances at once

You have a few choices. One is to use CC.Net 1.3 and use the integration queue feature to make sure the two projects never do build at the same time.

Another is to make sure your projects are using seperate copies of the assembly - that error about the access to the dll I would guess is due to one build trying to delete it while the other is running or something similar?

I would suggest just running nunit without ncover with two projects at the same time and verify whether you get the same issue - i.e. that it isnt in fact related to ncover at all?


Re: NCover and Manual Testing ...

Yes you can profile any executable - so in your case (assuming you use NUnit) you want to be profiling nunit-console.exe while executing tests.

Getting the command line right can be tricky and it depends on the NCover version you are running - there's a bunch of examples around the forums if you have a browse. Alternatively you could try the "NCover Runner" gui dialog option within NCoverExplorer. It will generate command lines as well as NAnt/MSBuild scripts, or indeed run inline and display the coverage results straight away. You will want to set the working directory to be where your application dlls/pdbs are located, the "application to profile" to be "nunit-console.exe" and the "application arguments" to be the name of the test fixture .dll (or .nunit project) and any additional arguments you want to pass to NUnit.

e.g. (for NCover 1.5.x)
"C:\Program Files\NCover\NCover.Console.exe"
  //w "E:\Dev\NCoverExplorer\NurseryRhymes\build\v1.0"
  "C:\Program Files\NUnit-2.2.8-net-1.1\bin\nunit-console.exe"
  NurseryRhymes.Georgie.Tests.dll

If using VS.Net I also highly recommend the TestDriven.Net add-in with its "Test with Coverage" options that way you dont have to bother with command line syntax.


Re: NCover and Manual Testing ...

Grant:

Thanks and it worked.