Skip to content

Commit

Permalink
Add support for OperationContext/OperationContextScope flow through a…
Browse files Browse the repository at this point in the history
…n await

Fixed the ability to run tests in Visual Studio
  • Loading branch information
mconnew committed Apr 2, 2021
1 parent 0e2d05e commit 97d949f
Show file tree
Hide file tree
Showing 16 changed files with 478 additions and 49 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,7 @@ Session.vim
# Private test configuration and binaries.
config.ps1
**/IISApplications

# Debug files
*.etl
*.dmp
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
<XUnitPublishTargetFramework>netcoreapp3.1</XUnitPublishTargetFramework>
</PropertyGroup>

<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<!-- TODO: Remove this condition when VSTest is used in CI. -->
<EnableRunSettingsSupport Condition="'$(ContinuousIntegrationBuild)' != 'true'">true</EnableRunSettingsSupport>
</PropertyGroup>


<ItemGroup>
<!-- Include license and third party files to packages -->
<Content Include="$(RepoRoot)THIRD-PARTY-NOTICES.TXT" Pack="true" PackagePath="\" />
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<Import Project="eng\FacadeAssemblies.targets" />
<Import Project="eng\harvest.targets" Condition="'$(HarvestFromPackage)' == 'true'" />
<Import Project="eng\uwp.targets" />
<Import Project="eng\testing\runsettings.targets" Condition="'$(EnableRunSettingsSupport)' == 'true'" />
</Project>
49 changes: 49 additions & 0 deletions eng/testing/.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<!-- Timeout in ms, 5 minutes -->
<TestSessionTimeout>300000</TestSessionTimeout>
<!-- Directory for test run reports. E.g. trx, coverage etc. -->
<ResultsDirectory>.\TestResults\</ResultsDirectory>
<!-- Working directory for test invocation. Results directory can be relative to this. Used by IDEs. -->
<SolutionDirectory>.\</SolutionDirectory>
<!-- Degree of parallelization, spawns n test hosts to run tests. -->
<MaxCpuCount>$$MAXCPUCOUNT$$</MaxCpuCount>
<!-- If true, an adapter should disable any test case parallelization. -->
<DisableParallelization>$$DISABLEPARALLELIZATION$$</DisableParallelization>
<!-- If true, an adapter shouldn't create appdomains to run tests. -->
<DisableAppDomain>$$DISABLEAPPDOMAIN$$</DisableAppDomain>
<!-- Filter out failing (wrong framwork, platform, runtime or activeissue) tests -->
<TestCaseFilter>$$TESTCASEFILTER$$</TestCaseFilter>
<DotNetHostPath>$$DOTNETHOSTPATH$$</DotNetHostPath>
<TargetFrameworkVersion>FrameworkCore10</TargetFrameworkVersion>
</RunConfiguration>
<LoggerRunSettings>
<Loggers>
<Logger friendlyName="trx" />
<Logger friendlyName="html" />
<Logger friendlyName="console">
<Configuration>
<Verbosity>Minimal</Verbosity>
</Configuration>
</Logger>
</Loggers>
</LoggerRunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<!-- Enabling the code coverage data collector as a later task -->
<!--<DataCollector friendlyName="XPlat code coverage" enabled="$$COVERAGE_ENABLED$$">
<Configuration>
<Include>$$COVERAGE_INCLUDE$$</Include>
<ExcludeByFile>$$COVERAGE_EXCLUDEBYFILE$$</ExcludeByFile>
<IncludeDirectory>$$COVERAGE_INCLUDEDIRECTORY$$</IncludeDirectory>
<Format>opencover</Format>
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<IncludeTestAssembly>false</IncludeTestAssembly>
</Configuration>
</DataCollector>-->
<DataCollector friendlyName="blame" enabled="true" />
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
55 changes: 55 additions & 0 deletions eng/testing/runsettings.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<Project>
<PropertyGroup>
<RunSettingsInputFilePath>$(MSBuildThisFileDirectory).runsettings</RunSettingsInputFilePath>
<RunSettingsIntermediateOutputFilePath>$(ArtifactsObjDir)$(TargetOS)-$(Configuration)-$(TargetArchitecture).runsettings</RunSettingsIntermediateOutputFilePath>
<RunSettingsAppOutputFilePath>$(OutDir).runsettings</RunSettingsAppOutputFilePath>

<CreateIntermediateRunSettingsFile Condition="'$(CreateIntermediateRunSettingsFile)' == ''">false</CreateIntermediateRunSettingsFile>
<RunSettingsOutputFilePath Condition="'$(CreateIntermediateRunSettingsFile)' == 'true'">$(RunSettingsIntermediateOutputFilePath)</RunSettingsOutputFilePath>
<RunSettingsOutputFilePath Condition="'$(CreateIntermediateRunSettingsFile)' != 'true'">$(RunSettingsAppOutputFilePath)</RunSettingsOutputFilePath>

<!-- Set RunSettingsFilePath property which is read by VSTest. -->
<RunSettingsFilePath Condition="Exists('$(RunSettingsAppOutputFilePath)')">$(RunSettingsAppOutputFilePath)</RunSettingsFilePath>
<!-- Use an intermediate runsettings file if the app hasn't been built yet to enable VSTest discovery. -->
<RunSettingsFilePath Condition="'$(RunSettingsFilePath)' == '' and Exists('$(RunSettingsIntermediateOutputFilePath)')">$(RunSettingsIntermediateOutputFilePath)</RunSettingsFilePath>

<PrepareForRunDependsOn>GenerateRunSettingsFile;$(PrepareForRunDependsOn)</PrepareForRunDependsOn>
</PropertyGroup>

<PropertyGroup>
<_testFilter Condition="'$(_withCategories)' != ''">$(_withCategories.Replace(';', '&amp;amp;category='))</_testFilter>
<_testFilter Condition="'$(_withoutCategories)' != ''">$(_testFilter)$(_withoutCategories.Replace(';', '&amp;amp;category!='))</_testFilter>
<_testFilter>$(_testFilter.Trim('&amp;amp;'))</_testFilter>
</PropertyGroup>

<Target Name="GenerateRunSettingsFile" >
<PropertyGroup>
<RunSettingsFileContent>$([System.IO.File]::ReadAllText('$(RunSettingsInputFilePath)'))</RunSettingsFileContent>
<RunSettingsFileContent Condition="'$(TestDisableParallelization)' == 'true'">$(RunSettingsFileContent.Replace('$$MAXCPUCOUNT$$', '1'))</RunSettingsFileContent>
<RunSettingsFileContent Condition="'$(TestDisableParallelization)' != 'true'">$(RunSettingsFileContent.Replace('$$MAXCPUCOUNT$$', '0'))</RunSettingsFileContent>
<!-- Arm64 is currently not a known TargetPlatform value in VSTEST: https://github.com/microsoft/vstest/issues/2566 -->
<!--
<RunSettingsFileContent Condition="'$(TargetArchitecture)' != 'arm64'">$(RunSettingsFileContent.Replace('$$TARGETPLATFORM$$', '<TargetPlatform>$(TargetArchitecture)</TargetPlatform>'))</RunSettingsFileContent>
<RunSettingsFileContent Condition="'$(TargetArchitecture)' == 'arm64'">$(RunSettingsFileContent.Replace('$$TARGETPLATFORM$$', ''))</RunSettingsFileContent>
-->
<RunSettingsFileContent>$(RunSettingsFileContent.Replace('$$COVERAGE_INCLUDE$$', '$(CoverageIncludeFilter)')
.Replace('$$COVERAGE_EXCLUDEBYFILE$$', '$(CoverageExcludeByFileFilter)')
.Replace('$$COVERAGE_INCLUDEDIRECTORY$$', '$(CoverageIncludeDirectoryFilter)')
.Replace('$$COVERAGE_ENABLED$$', '$([MSBuild]::ValueOrDefault('$(Coverage)', 'false'))')
.Replace('$$DISABLEPARALLELIZATION$$', '$([MSBuild]::ValueOrDefault('$(TestDisableParallelization)', 'false'))')
.Replace('$$DISABLEAPPDOMAIN$$', '$([MSBuild]::ValueOrDefault('$(TestDisableAppDomain)', 'false'))')
.Replace('$$TESTCASEFILTER$$', '$(_testFilter)')
.Replace('$$DOTNETHOSTPATH$$', '$(TestHostRootPath)$([System.IO.Path]::GetFileName('$(DotNetTool)'))'))</RunSettingsFileContent>
</PropertyGroup>

<WriteLinesToFile File="$(RunSettingsOutputFilePath)"
Lines="$(RunSettingsFileContent)"
WriteOnlyWhenDifferent="true"
Overwrite="true" />

<!-- Set RunSettingsFilePath property which is read by VSTest. -->
<PropertyGroup>
<RunSettingsFilePath>$(RunSettingsOutputFilePath)</RunSettingsFilePath>
</PropertyGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,24 @@
using System.Security.Principal;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Threading;

namespace System.ServiceModel
{
public sealed class OperationContext : IExtensibleObject<OperationContext>
{
static OperationContext()
{
DisableAsyncFlow = AppContext.TryGetSwitch("System.ServiceModel.OperationContext.DisableAsyncFlow", out var enabled) && enabled;
if (!DisableAsyncFlow)
{
s_asyncContext = new AsyncLocal<Holder>();
}
}

[ThreadStatic]
private static Holder s_currentContext;
private static AsyncLocal<Holder> s_asyncContext;
private Message _clientReply;
private bool _closeClientReply;
private ExtensionCollection<OperationContext> _extensions;
Expand Down Expand Up @@ -93,16 +104,32 @@ internal static Holder CurrentHolder
{
get
{
Holder holder = OperationContext.s_currentContext;
if (holder == null)
Holder holder;
if (DisableAsyncFlow)
{
holder = s_currentContext;
if (holder == null)
{
holder = new Holder();
s_currentContext = holder;
}
}
else
{
holder = new Holder();
OperationContext.s_currentContext = holder;
holder = s_asyncContext.Value;
if (holder == null)
{
holder = new Holder();
s_asyncContext.Value = holder;
}
}

return holder;
}
}

internal static bool DisableAsyncFlow { get; }

public EndpointDispatcher EndpointDispatcher
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


using System.Threading;

namespace System.ServiceModel
{
public sealed class OperationContextScope : IDisposable
{
static OperationContextScope()
{
if (!OperationContext.DisableAsyncFlow)
{
s_asyncCurrentScope = new AsyncLocal<OperationContextScope>();
}
}

[ThreadStatic]
private static OperationContextScope s_currentScope;

private static AsyncLocal<OperationContextScope> s_asyncCurrentScope;
private OperationContext _currentContext;
private bool _disposed;
private readonly OperationContext _originalContext = OperationContext.Current;
private readonly OperationContextScope _originalScope = OperationContextScope.s_currentScope;
private readonly OperationContextScope _originalScope = OperationContext.DisableAsyncFlow ? s_currentScope : s_asyncCurrentScope.Value;

public OperationContextScope(IContextChannel channel)
{
Expand All @@ -38,13 +46,21 @@ public void Dispose()
private void PushContext(OperationContext context)
{
_currentContext = context;
OperationContextScope.s_currentScope = this;
if (OperationContext.DisableAsyncFlow)
{
s_currentScope = this;
}
else
{
s_asyncCurrentScope.Value = this;
}

OperationContext.Current = _currentContext;
}

private void PopContext()
{
if (OperationContextScope.s_currentScope != this)
if ((OperationContext.DisableAsyncFlow ? s_currentScope : s_asyncCurrentScope.Value) != this)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.SFxInterleavedContextScopes0));
}
Expand All @@ -54,7 +70,15 @@ private void PopContext()
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.SFxContextModifiedInsideScope0));
}

OperationContextScope.s_currentScope = _originalScope;
if (OperationContext.DisableAsyncFlow)
{
s_currentScope = _originalScope;
}
else
{
s_asyncCurrentScope.Value = _originalScope;
}

OperationContext.Current = _originalContext;

if (_currentContext != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public static X509Certificate2 RootCertificate
{
get
{
// When running under VS, the Condition checks are run in a different process than the test run
// which means the test running process won't have the certificate installed by the condition check code.
EnsureRootCertificateInstalled();
ThrowIfRootCertificateInstallationError();
return s_rootCertificate;
}
Expand All @@ -52,6 +55,9 @@ public static X509Certificate2 ClientCertificate
{
get
{
// When running under VS, the Condition checks are run in a different process than the test run
// which means the test running process won't have the certificate installed by the condition check code.
EnsureClientCertificateInstalled();
ThrowIfClientCertificateInstallationError();
return s_clientCertificate;
}
Expand All @@ -61,6 +67,9 @@ public static X509Certificate2 PeerCertificate
{
get
{
// When running under VS, the Condition checks are run in a different process than the test run
// which means the test running process won't have the certificate installed by the condition check code.
EnsurePeerCertificateInstalled();
ThrowIfPeerCertificateInstallationError();
return s_peerCertificate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,38 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Infrastructure.Common
{
/// <summary>Provides for custom IXunitTestCase.</summary>
internal class WcfTestCase : LongLivedMarshalByRefObject, IXunitTestCase
internal class WcfTestCase : XunitTestCase, IXunitTestCase
{
private readonly IXunitTestCase _testCase;
private readonly string _skippedReason;
private readonly bool _isTheory;
private string _skippedReason;
private bool _isTheory;
private readonly IMessageSink _diagnosticMessageSink;

static TestEventListener s_testListener = new TestEventListener(new List<string>() { "Microsoft-Windows-Application Server-Applications" }, EventLevel.Verbose);

[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public WcfTestCase()
{
}

internal WcfTestCase(IXunitTestCase testCase, string skippedReason = null, bool isTheory = false, IMessageSink diagnosticMessageSink = null)
internal WcfTestCase(XunitTestCase testCase,
TestMethodDisplay defaultMethodDisplay,
string skippedReason = null,
bool isTheory = false,
IMessageSink diagnosticMessageSink = null)
: base(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testCase.TestMethod, testCase.TestMethodArguments)
{
_testCase = testCase;
_skippedReason = skippedReason;
_isTheory = isTheory;
_diagnosticMessageSink = diagnosticMessageSink;
}

public string DisplayName { get { return _testCase.DisplayName; } }

public IMethodInfo Method { get { return _testCase.Method; } }

public string SkipReason { get { return _skippedReason; } }

public ISourceInformation SourceInformation { get { return _testCase.SourceInformation; } set { _testCase.SourceInformation = value; } }

public ITestMethod TestMethod { get { return _testCase.TestMethod; } }

public object[] TestMethodArguments { get { return _testCase.TestMethodArguments; } }

public Dictionary<string, List<string>> Traits { get { return _testCase.Traits; } }

public string UniqueID { get { return _testCase?.UniqueID; } }

public Exception InitializationException { get { return null; } }

public int Timeout { get { return 0; } }

public void Deserialize(IXunitSerializationInfo info) { _testCase?.Deserialize(info); }

public async Task<RunSummary> RunAsync(
public override async Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments,
ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
{
Expand All @@ -81,7 +62,7 @@ public async Task<RunSummary> RunAsync(
{
etwOutput.AppendLine(string.Format(DisplayName + ": " + item.Message, item.Payload.ToArray()));
}
//The mumber of parameters in Payload does not match the number of arguments in the item.Message and thus cause a
// The mumber of parameters in Payload does not match the number of arguments in the item.Message and thus cause a
// FormatException occationally, In this case, we catch and output all items in the payload and the Message without formatting the message.
// https://github.com/dotnet/wcf/issues/1440 is opened to investigate the root cause of the mismatch exception.
catch (FormatException e)
Expand All @@ -107,6 +88,18 @@ public async Task<RunSummary> RunAsync(
return runsummary;
}

public void Serialize(IXunitSerializationInfo info) { _testCase.Serialize(info); }
public override void Serialize(IXunitSerializationInfo data)
{
base.Serialize(data);
data.AddValue("_isTheory", _isTheory);
data.AddValue("_skippedReason", _skippedReason);
}

public override void Deserialize(IXunitSerializationInfo data)
{
_isTheory = data.GetValue<bool>("_isTheory");
_skippedReason = data.GetValue<string>("_skippedReason");
base.Deserialize(data);
}
}
}
Loading

0 comments on commit 97d949f

Please sign in to comment.