среда, марта 31, 2010

Тесты и Exception вида "System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used"....

В проекте используется com interrop,вкратце, создается COM-инстанс и обращение к нему происходит через singletone-объект;

Необходимо было покрыть тестами часть данной функциональности, связанной с обращением к COM, и вот тут-то при запуске нескольких тестов и стало возникать исключение:

"System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used"

Проблема связана с тем, что каждый тест по умолчанию запускается в отдельном потоке, или даже домене (подробнее не разбирался), и доступ к инстансу COM-server происходил из другого потока...

Проблему пофиксил пока путем настройки не паралельного запуска тестов, а их последовательного выполнения. 

Настраивается такое поведение путем внесения изменений в Local.testsettings файл, а именно, путем указания количества одновременно выполняемых тестов в 



<Execution parallelTestCount=""<вставить нужное="" значение="">




  если parallelTestCount == 0, то тесты выполняются параллельно, количество тестов в пуле выбирается самой студией...



  Мне же был нужен следующий вариант:

  <TestSettings name="Local" id="38863d1e-30b7-4b4a-a629-4add84f4982e" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
    <Description>These are default test settings for a local test run.</Description>
    <Execution parallelTestCount="1">
      <TestTypeSpecific />
      <Timeouts runTimeout="900000" testTimeout="900000" />
      <ExecutionThread apartmentState="MTA"/>
      <AgentRule name="Execution Agents">
      </AgentRule>
    </Execution>
  </TestSettings>


Важно: чтобы ваши изменения вступили в силу, нужно перегрузить проект/решение;

ps: попутно читал следующие ресурсы:

    http://connect.microsoft.com/VisualStudio/feedback/details/534124/exception-running-tests-that-use-waithandle-waitall-waithandles

    VS2010 tip: How to run unit tests in parallel
    http://blogs.microsoft.co.il/blogs/dhelper/archive/2010/03/02/vs2010-tip-how-to-run-unit-tests-in-parallel.aspx

    Parallel Test Execution in Visual Studio 2010
    http://msmvps.com/blogs/p3net/pages/parallel-test-execution-in-visual-studio-2010.aspx

    Executing Unit Tests in parallel on a multi-CPU/core machine
    http://blogs.msdn.com/vstsqualitytools/archive/2009/12/01/executing-unit-tests-in-parallel-on-a-multi-cpu-core-machine.aspx

четверг, марта 25, 2010

Конфигурация WCF Service без app.config

Недавно столкнулся с такой задачей - потребовалось произвести настройку WCF службы напрямую из кода, т.е. без использования .config файла приложения. Потребовалось немало времени, чтобы выгуглить вменяемые примеры, потому ту часть решения, которая получилась у
меня, выкладываю сюда, надеюсь пригодится кому в качестве отправной точки...


Итак, исходный конфиг:

<system.servicemodel>
            <!--  диагностика - включим трассировку сообщений, который будут сохраняться в файлах, указанных листенерах -->
            <diagnostics>
              <messagelogging logmalformedmessages="true" logmessagesattransportlevel="true">
            </messagelogging>

            <behaviors>
              <servicebehaviors>
                <behavior name="SampleService.Behavior.Service">

                  <!--  отдаем пока исключение в процесс отдладки -->
                  <servicedebug includeexceptiondetailinfaults="true">

                  <!--  разрешаем сервису отдавтать описание - wsdl -->
                  <servicemetadata  httpgetenabled="true">

                </servicemetadata >
              </servicedebug>

            </behavior>

            <!--  привязки и их настройки  -->
            <bindings>
              <webhttpbinding>
                <binding name="SampleService.EndPointConfiguration.Web">
                         allowCookies="true"
                         closeTimeout="00:10:00"
                         openTimeout="00:10:00"
                         receiveTimeout="00:10:00"
                         sendTimeout="00:10:00"
                         bypassProxyOnLocal="false"
                         maxBufferPoolSize="10000000"
                         useDefaultWebProxy="true"
                         maxReceivedMessageSize="4096">
                  <security mode="None"></security>
                </binding>
              </webhttpbinding>
            </bindings>

            <services>
              <!--  указываем сервис  -->
              <service  name="SampleService">
                        behaviorConfiguration="SampleService.Behavior.Service">
                <endpoint address="http://127.0.0.1:9999/">
                                      binding="webHttpBinding"
                          behaviorConfiguration="SampleService.Behavior.EndPointBehavior"
                                      bindingConfiguration="SampleService.EndPointConfiguration.Web"
                                      contract="SampleService.IServiceContract">
                </endpoint>
                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange">
                <host>
                  <baseaddresses>
                    <add baseaddress="http://127.0.0.1:9999/">
                  </add>
                </baseaddresses>
              </host>
            </endpoint>


          </service >
</system.servicemodel>


превращается в пару строку из ini файла:

[sample_service]
host=127.0.0.1
port=9999 

c# код программного создания конечной точки и описанных поведений, аналогичных тем, которые были ранее в .config, приведен ниже:

            #region binding
            WebHttpBinding  binding = new WebHttpBinding();
            binding.AllowCookies = true;
            
            binding.CloseTimeout = TimeSpan.FromMinutes(10);
            binding.SendTimeout = TimeSpan.FromMinutes( 10 );
            binding.OpenTimeout = TimeSpan.FromMinutes( 10 );
            binding.ReceiveTimeout = TimeSpan.FromMinutes( 10 );

            binding.BypassProxyOnLocal = false;
            binding.UseDefaultWebProxy = true;
            
            binding.MaxBufferPoolSize = 1000000;
            binding.MaxReceivedMessageSize = 1000000;

            binding.Security.Mode = WebHttpSecurityMode.None;
            #endregion

            #region endpoint adress
            
            Uri endpointUri = GlobalConfiguration.SETTINGS_SAMPLE_SERVICE_BASE_ADDRESS;
            Uri mexEndpointUri = new Uri(String.Format("{0}/mex", endpointUri.ToString()));
            #endregion

            SampleServiceHost.Instance = new ServiceHost(typeof(SampleService), endpointUri);

            #region wcf behaviour setup

            //  behoviours
            SampleServiceHost.Instance.Description.Behaviors.Clear();

            ServiceBehaviorAttribute behaviour = new ServiceBehaviorAttribute();
            behaviour.InstanceContextMode = InstanceContextMode.Single;
            behaviour.ConcurrencyMode = ConcurrencyMode.Single;

            SampleServiceHost.Instance.Description.Behaviors.Add(behaviour);


            ServiceMetadataBehavior serviceMetadataBehavior = new ServiceMetadataBehavior();
            serviceMetadataBehavior.HttpGetEnabled = true;
            SampleServiceHost.Instance.Description.Behaviors.Add(serviceMetadataBehavior);


            ServiceDebugBehavior serviceDebugBehavior = new ServiceDebugBehavior();
            serviceDebugBehavior.HttpHelpPageEnabled = true;
            serviceDebugBehavior.HttpsHelpPageEnabled = true;
            serviceDebugBehavior.IncludeExceptionDetailInFaults = true;
            SampleServiceHost.Instance.Description.Behaviors.Add(serviceDebugBehavior);


            #endregion

            
            //  listening end point
            SampleServiceHost.Instance.AddServiceEndpoint(typeof(SampleService.IServiceContract), binding, endpointUri.ToString()).Behaviors.Add(new WebHttpBehavior());
            //  and mex endpoint
            SampleServiceHost.Instance.AddServiceEndpoint(typeof(IMetadataExchange), binding, mexEndpointUri.ToString());

            //  start service
            SampleServiceHost.Instance.Open();