diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 5b14ae88..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,17 +0,0 @@ -init: -- git config --global core.autocrlf true -branches: - only: - - dev - - /^release\/.*$/ - - /^(.*\/)?ci-.*$/ -build_script: -- cmd: .\build.cmd -clone_depth: 1 -environment: - global: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 -test: 'off' -deploy: 'off' -os: Visual Studio 2017 diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml new file mode 100644 index 00000000..1e6e66e7 --- /dev/null +++ b/.github/workflows/pr-build.yml @@ -0,0 +1,52 @@ +name: azure-relay-aspnetserver + +on: [push, pull_request] + +jobs: + build-windows: + name: Build 'azure-relay-aspnetserver' on windows-latest + + runs-on: 'windows-latest' + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET Core 2.2 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '2.2.x' + - name: Setup .NET Core 3.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + - run: dotnet --info + - name: Restore + run: .\restore.cmd + - name: Build + run: .\build.cmd + - name: Package + run: .\package.cmd + build-nix: + strategy: + matrix: + platform: [ 'ubuntu-latest', 'macos-latest' ] + name: Build 'azure-relay-aspnetserver' on ${{ matrix.platform }} + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET Core 2.2 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '2.2.x' + - name: Setup .NET Core 3.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + - run: dotnet --info + - name: Restore + run: ./restore.sh + - name: Build + run: ./build.sh + - name: Package + run: ./package.sh diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 73f8b2fe..13fbe265 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -11,7 +11,7 @@ environment: version: name: 'DefaultVersion' major: 1 - minor: 2 + minor: 3 system: 'RevisionCounter' exclude_commit: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3ab7adb3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: csharp -sudo: false -dist: trusty -env: - global: - - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - matrix: - - secure: gvg/6MhNPOKJyk4JrOPsqi6jH9IQ5mSYQR+S+q/jHYNetsm9yZHKproqQEMegW/L99Y/WNpi7EzBfasCBfQsJrJq9/WYBrj0VmqstszcsWYbUpU9V8B0vQSAewSHq/FkxFjqMRtA7AZCYViddBloD3Sb9/FVy7yfFv7BRZFX5ukYnK2EU6foJaz/5r8A2efLZ5C/CdEEMsn/OcrtnYMu5tstWFy1JWS2Gk+vgzrwchVqncLI60AvwnRuPyMMbsyIY8cJidcKojr6gcGJ9S5ATtK0DfIoGV9+9CPLDtzVJJltYl1677zEoRL7RIz9Ly5Yydk17b5whES6Mh+GbPokhRxx8B/9Ag9I7TjAyMNVwTZ6AlQSYycG0m/FLZmKJvBa2RkzhALUuN/oXL/SLkU7isCzbk03wrWYY6/gv5T6249FNf5z5I8GNhm7eJGhz+A3FudRDkqCIGHV8rs3iiQLrXfHZCpyBlA8g+oHHmAPtmfpXmNALUD6Y6c4OCAaApjFj3UIN+LGtV/AMFGq0yvNXd14qo5h1doI27A5sIeOhRXntpOapPkgmlDWe048NNDUnLdRj0pUMebm0uqepK/M8etQ38HSTQpKFdrY6JVfaFiq/5Dd0S6M8rdYEdUh9E4lGZqzhr5WKTV8vkUtdsWIMvi2wY5q6DT//kSLxzmOjLA= - - secure: HquUNjKDznaeW2fLV1+Xv9pJlTu8zCrggC/Et+22emgdANv3CYczQ/M9vLzdOEyfkSjWKfKQngNX/YbSQPzIT+Gty6NFFXYDOrxtI7M4ceTEDw2BUmZtNk0wmidifoBUy27yn88iFxssxRsdNMYXhNqV5GJSWf+6y6v2CFlb/nws6HcGcelPeQJqSSMMplo4B+hbLbce2t12L+VV77A9rWt52DL4OtEAb012l6JofW0k3WwQsWo8/j/AbkkMmofPQowIY9Y+MnsifynRdv26PjOgGkCODS6jVWskKZJlfH+raH7rW6v87BpMVVfPPon6vQO6jVHQWhOQ1nPG4VUVuyAPIIZ7A15ZmiXZpMklMvBtiGv8n5lRCz4qQvgOa5WCZuWxFboFOiwcoCmdqgGk1SJx4Lf6ySBX3hSer3Kd9aZY9OoxuOd+dL5DAsxK8pr4mCZDZcYSFSD3N486Sbmp9/ZyU1qV4wda2yXIdtQtTYix5u5AoxdROUAitaayr2DNDnmzokDON54hNK6t7UPEGSww8g8cywWKQSwSK0p00tGQXtHdYFQCPwMJptAQ4YBh3CvOmyR5ZN1P5gjrdOig+SKc8pilCobOYsqM+LbKy9I5tFjX06GDFsS189ECH+OgNl0hzQgX1dAN3QHCifYIevrblVmmPnkk5j8wUlEfqQY= -mono: none -os: -- linux -- osx -osx_image: xcode8.2 -addons: - apt: - packages: - - libunwind8 -branches: - only: - - dev - - "/^release\\/.*$/" - - "/^(.*\\/)?ci-.*$/" -before_install: -- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s - /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib - /usr/local/lib/; fi -script: -- "./build.sh" diff --git a/.version/PipelineAssemblyInfo.cs b/.version/PipelineAssemblyInfo.cs index a4e58e64..5ba0f2c2 100644 --- a/.version/PipelineAssemblyInfo.cs +++ b/.version/PipelineAssemblyInfo.cs @@ -4,6 +4,6 @@ // This is a CDP xPlat pipeline generated file. Do not modify. This will be replaced with the actual versions in the actual Pipeline. using System.Reflection; -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] -[assembly: AssemblyInformationalVersion("1.2.0.0-dev-00000000")] \ No newline at end of file +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] +[assembly: AssemblyInformationalVersion("1.3.0.0-dev-00000000")] \ No newline at end of file diff --git a/NuGet.config b/NuGet.config index 996d887c..3a9f6b32 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,7 +2,6 @@ - \ No newline at end of file diff --git a/README.md b/README.md index b0672e18..5540b50a 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ ASP.NET Core Hosting for Azure Relay ================= -[![Build Status](https://travis-ci.org/Azure/azure-relay-aspnetserver.svg?branch=dev)](https://travis-ci.org/Azure/azure-relay-aspnetserver) +![](https://github.com/Azure/azure-relay-aspnetserver/workflows/azure-relay-aspnetserver/badge.svg?branch=dev) -This repo contains a web server for ASP.NET Core based on Azure Relay Hybrid Connections HTTP. A NuGet package produced from this repo is available on NuGet as ["Microsoft.Azure.Relay.AspNetCore"](https://www.nuget.org/packages/Microsoft.Azure.Relay.AspNetCore). +This repo contains a web server for ASP.NET Core based on Azure Relay Hybrid Connections HTTP. A NuGet package produced from this repo is available on NuGet as [Microsoft.Azure.Relay.AspNetCore](https://www.nuget.org/packages/Microsoft.Azure.Relay.AspNetCore). The integration supports most ASP.NET scenarios, with a few exceptions. WebSocket support will be added in the near future, for instance. To use the extension, take the following steps: -1. Add the Microsoft.Azure.Relay.AspNetCore assembly to your project. The assembly must be built from -this sampe repo at the moment. An "official" Nuget package will be available in a little while. +1. Add the [Microsoft.Azure.Relay.AspNetCore](https://www.nuget.org/packages/Microsoft.Azure.Relay.AspNetCore) assembly to your project. 2. [Create a Hybrid Connection](https://docs.microsoft.com/en-us/azure/service-bus-relay/relay-hybrid-connections-http-requests-dotnet-get-started) 3. After you have created the Hybrid Connection, find its "Shared Access Policies" portal tab and add a new rule "listen" with the Listen-permission checked. diff --git a/build.sh b/build.sh index 1467a1da..6fb96fda 100755 --- a/build.sh +++ b/build.sh @@ -11,6 +11,21 @@ while [[ -h $source ]]; do # symlink file was located [[ $source != /* ]] && source="$scriptroot/$source" done - scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" -"$scriptroot/eng/common/build.sh" --build --restore $@ \ No newline at end of file + +if [ ${TF_BUILD}="True" ]; then + BuildConfiguration="Release" +else + BuildConfiguration="Debug" +fi + +echo "dotnet build '${scriptroot}/AzureRelayServer.sln' --configuration ${BuildConfiguration}" +dotnet build "${scriptroot}/AzureRelayServer.sln" --configuration ${BuildConfiguration} + +if [ $? -eq 0 ] +then + exit 0 +else + echo "Error during running dotnet build" >&2 + exit 1 +fi \ No newline at end of file diff --git a/global.json b/global.json index 5270200d..2c63c085 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,2 @@ { - "sdk": { - "version": "3.0" - } -} \ No newline at end of file +} diff --git a/package.cmd b/package.cmd index 8d29ef25..c77e5495 100644 --- a/package.cmd +++ b/package.cmd @@ -2,7 +2,7 @@ PUSHD "%~dp0" rem CDP_PACKAGE_VERSION_SEMANTIC set by internal -if not defined CDP_PACKAGE_VERSION_SEMANTIC set CDP_PACKAGE_VERSION_SEMANTIC=1.0.0.0-dev +if not defined CDP_PACKAGE_VERSION_SEMANTIC set CDP_PACKAGE_VERSION_SEMANTIC=1.3.0.0-dev if defined TF_BUILD ( set BuildConfiguration=Release diff --git a/package.sh b/package.sh new file mode 100755 index 00000000..b0e7a30b --- /dev/null +++ b/package.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +if [ ${TF_BUILD}="True" ]; then + BuildConfiguration="Release" +else + BuildConfiguration="Debug" +fi + +echo "dotnet pack '${scriptroot}/AzureRelayServer.sln' --no-build --no-dependencies --no-restore /p:Version=${CDP_PACKAGE_VERSION_SEMANTIC:-1.0.0.0-dev} --configuration ${BuildConfiguration}" +dotnet pack "${scriptroot}/AzureRelayServer.sln" --no-build --no-dependencies --no-restore /p:Version=${CDP_PACKAGE_VERSION_SEMANTIC:-1.0.0.0-dev} --configuration ${BuildConfiguration} + +if [ $? -eq 0 ] +then + exit 0 +else + echo "Error during running dotnet build" >&2 + exit 1 +fi \ No newline at end of file diff --git a/restore.sh b/restore.sh new file mode 100755 index 00000000..f1722387 --- /dev/null +++ b/restore.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +echo "dotnet restore '${scriptroot}/AzureRelayServer.sln'" +dotnet restore "${scriptroot}/AzureRelayServer.sln" + +if [ $? -eq 0 ] +then + exit 0 +else + echo "Error during running dotnet restore" >&2 + exit 1 +fi \ No newline at end of file diff --git a/samples/SelfHostServer/SelfHostServer.csproj b/samples/SelfHostServer/SelfHostServer.csproj index 9e20cba7..54faeb54 100644 --- a/samples/SelfHostServer/SelfHostServer.csproj +++ b/samples/SelfHostServer/SelfHostServer.csproj @@ -1,15 +1,11 @@  - netcoreapp3.0;netcoreapp2.1 + netcoreapp3.1;netcoreapp2.1 $(TargetFrameworks);net461 Exe true - - - - diff --git a/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayListener.cs b/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayListener.cs index 179463ac..f0b438ee 100644 --- a/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayListener.cs +++ b/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayListener.cs @@ -48,7 +48,7 @@ public AzureRelayListener(AzureRelayOptions options, ILoggerFactory loggerFactor Logger = LogHelper.CreateLogger(loggerFactory, typeof(AzureRelayListener)); } - + Task WebSocketAcceptHandler(RelayedHttpListenerContext arg) { return Task.FromResult(true); @@ -78,7 +78,7 @@ internal enum State public bool IsListening { get { return _state == State.Started; } - } + } /// /// Start accepting incoming requests. @@ -109,12 +109,17 @@ public void Start() var rcb = new RelayConnectionStringBuilder(); var tokenProvider = urlPrefix.TokenProvider != null ? urlPrefix.TokenProvider : Options.TokenProvider; - if ( tokenProvider == null ) + if (tokenProvider == null) { throw new InvalidOperationException("No relay token provider defined."); } var relayListener = new HybridConnectionListener( - new UriBuilder(urlPrefix.FullPrefix) { Scheme = "sb", Port = -1 }.Uri, tokenProvider ); + new UriBuilder(urlPrefix.FullPrefix) { Scheme = "sb", Port = -1 }.Uri, tokenProvider); + + if (Options.UseCustomProxy) + { + relayListener.Proxy = Options.Proxy; + } relayListener.RequestHandler = (ctx) => requestHandler(new RequestContext(ctx, new Uri(urlPrefix.FullPrefix))); // TODO: CR: An accept handler which simply returns true is the same as no handler at all. diff --git a/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayOptions.cs b/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayOptions.cs index 7cc2a1bc..4f4546de 100644 --- a/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayOptions.cs +++ b/src/Microsoft.Azure.Relay.AspNetCore/AzureRelayOptions.cs @@ -1,12 +1,15 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Net; namespace Microsoft.Azure.Relay.AspNetCore { public class AzureRelayOptions { + private IWebProxy proxy; + public AzureRelayOptions() { } @@ -15,12 +18,24 @@ public AzureRelayOptions() public UrlPrefixCollection UrlPrefixes { get; } = new UrlPrefixCollection(); + public IWebProxy Proxy + { + get { return proxy; } + set + { + UseCustomProxy = true; + proxy = value; + } + } + + public bool UseCustomProxy { get; set; } + internal bool ThrowWriteExceptions { get; set; } internal long MaxRequestBodySize { get; set; } internal int RequestQueueLimit { get; set; } - internal int? MaxConnections { get; set; } + internal int? MaxConnections { get; set; } } } diff --git a/test.sh b/test.sh new file mode 100755 index 00000000..e7ae7f24 --- /dev/null +++ b/test.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +if [ ${TF_BUILD}="True" ]; then + BuildConfiguration="Release" +else + BuildConfiguration="Debug" +fi + +echo "dotnet test '${scriptroot}/AzureRelayServer.sln' --no-build --no-restore --blame --logger:trx --configuration ${BuildConfiguration}" +dotnet test "${scriptroot}/AzureRelayServer.sln" --no-build --no-restore --blame --logger:trx --configuration ${BuildConfiguration} + +if [ $? -eq 0 ] +then + exit 0 +else + echo "Error during running dotnet test" >&2 + exit 1 +fi \ No newline at end of file diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 6d7438fe..b373456d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -2,7 +2,7 @@ - netcoreapp2.1;netcoreapp3.0 + netcoreapp2.1;netcoreapp3.1 $(DeveloperBuildTestTfms) $(StandardTestTfms) $(StandardTestTfms);net461 diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Microsoft.Azure.Relay.AspNetCore.FunctionalTests.csproj b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Microsoft.Azure.Relay.AspNetCore.FunctionalTests.csproj index 38d3f804..83bd800f 100644 --- a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Microsoft.Azure.Relay.AspNetCore.FunctionalTests.csproj +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Microsoft.Azure.Relay.AspNetCore.FunctionalTests.csproj @@ -9,7 +9,6 @@ - diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/OSDontSkipConditionAttribute.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/OSDontSkipConditionAttribute.cs deleted file mode 100644 index c657240a..00000000 --- a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/OSDontSkipConditionAttribute.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Testing.xunit -{ - // Skip except on a specific OS and version - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] - public class OSDontSkipConditionAttribute : Attribute, ITestCondition - { - private readonly OperatingSystems _includedOperatingSystem; - private readonly IEnumerable _includedVersions; - private readonly OperatingSystems _osPlatform; - private readonly string _osVersion; - - public OSDontSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : - this( - operatingSystem, - GetCurrentOS(), - GetCurrentOSVersion(), - versions) - { - } - - // to enable unit testing - internal OSDontSkipConditionAttribute( - OperatingSystems operatingSystem, OperatingSystems osPlatform, string osVersion, params string[] versions) - { - _includedOperatingSystem = operatingSystem; - _includedVersions = versions ?? Enumerable.Empty(); - _osPlatform = osPlatform; - _osVersion = osVersion; - } - - public bool IsMet - { - get - { - var currentOSInfo = new OSInfo() - { - OperatingSystem = _osPlatform, - Version = _osVersion, - }; - - var skip = (_includedOperatingSystem & currentOSInfo.OperatingSystem) != currentOSInfo.OperatingSystem; - if (!skip && _includedVersions.Any()) - { - skip = !_includedVersions.Any(inc => _osVersion.StartsWith(inc, StringComparison.OrdinalIgnoreCase)); - } - - // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip - return !skip; - } - } - - public string SkipReason { get; set; } = "Test cannot run on this operating system."; - - static private OperatingSystems GetCurrentOS() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return OperatingSystems.Windows; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return OperatingSystems.Linux; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return OperatingSystems.MacOSX; - } - throw new PlatformNotSupportedException(); - } - - static private string GetCurrentOSVersion() - { - // currently not used on other OS's - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Environment.OSVersion.Version.ToString(); - } - else - { - return string.Empty; - } - } - - private class OSInfo - { - public OperatingSystems OperatingSystem { get; set; } - - public string Version { get; set; } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactAttribute.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactAttribute.cs new file mode 100644 index 00000000..b0e7fe7f --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactAttribute.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalFactDiscoverer), "Microsoft.Azure.Relay.AspNetCore.FunctionalTests")] + public class ConditionalFactAttribute : FactAttribute + { + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactDiscoverer.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactDiscoverer.cs new file mode 100644 index 00000000..434147ef --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ConditionalFactDiscoverer.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit.Abstractions; +using Xunit.Sdk; + +// Do not change this namespace without changing the usage in ConditionalFactAttribute +namespace Microsoft.AspNetCore.Testing.xunit +{ + internal class ConditionalFactDiscoverer : FactDiscoverer + { + private readonly IMessageSink _diagnosticMessageSink; + + public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + _diagnosticMessageSink = diagnosticMessageSink; + } + + protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + var skipReason = testMethod.EvaluateSkipConditions(); + return skipReason != null + ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) + : base.CreateTestCase(discoveryOptions, testMethod, factAttribute); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ITestCondition.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ITestCondition.cs new file mode 100644 index 00000000..e3882b60 --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/ITestCondition.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public interface ITestCondition + { + bool IsMet { get; } + + string SkipReason { get; } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OSSkipCondition.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OSSkipCondition.cs new file mode 100644 index 00000000..e8670c43 --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OSSkipCondition.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class OSSkipConditionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _excludedOperatingSystem; + private readonly OperatingSystems _osPlatform; + + public OSSkipConditionAttribute(OperatingSystems operatingSystem) : + this(operatingSystem, GetCurrentOS()) + { + } + + [Obsolete("Use the Minimum/MaximumOSVersionAttribute for version checks.", error: true)] + public OSSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : + this(operatingSystem, GetCurrentOS()) + { + } + + // to enable unit testing + internal OSSkipConditionAttribute(OperatingSystems operatingSystem, OperatingSystems osPlatform) + { + _excludedOperatingSystem = operatingSystem; + _osPlatform = osPlatform; + } + + public bool IsMet + { + get + { + var skip = (_excludedOperatingSystem & _osPlatform) == _osPlatform; + // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip + return !skip; + } + } + + public string SkipReason { get; set; } = "Test cannot run on this operating system."; + + static private OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OperatingSystems.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OperatingSystems.cs new file mode 100644 index 00000000..7683890d --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/OperatingSystems.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [Flags] + public enum OperatingSystems + { + Linux = 1, + MacOSX = 2, + Windows = 4, + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/SkippedTestCase.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/SkippedTestCase.cs new file mode 100644 index 00000000..f76b197a --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/SkippedTestCase.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class SkippedTestCase : XunitTestCase + { + private string _skipReason; + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippedTestCase() : base() + { + } + + public SkippedTestCase( + string skipReason, + IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + TestMethodDisplayOptions defaultMethodDisplayOptions, + ITestMethod testMethod, + object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) + { + _skipReason = skipReason; + } + + protected override string GetSkipReason(IAttributeInfo factAttribute) + => _skipReason ?? base.GetSkipReason(factAttribute); + + public override void Deserialize(IXunitSerializationInfo data) + { + _skipReason = data.GetValue(nameof(_skipReason)); + + // We need to call base after reading our value, because Deserialize will call + // into GetSkipReason. + base.Deserialize(data); + } + + public override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + data.AddValue(nameof(_skipReason), _skipReason); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/TestMethodExtensions.cs b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/TestMethodExtensions.cs new file mode 100644 index 00000000..113ae295 --- /dev/null +++ b/test/Microsoft.Azure.Relay.AspNetCore.FunctionalTests/Testing/TestMethodExtensions.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public static class TestMethodExtensions + { + public static string EvaluateSkipConditions(this ITestMethod testMethod) + { + var testClass = testMethod.TestClass.Class; + var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; + var conditionAttributes = testMethod.Method + .GetCustomAttributes(typeof(ITestCondition)) + .Concat(testClass.GetCustomAttributes(typeof(ITestCondition))) + .Concat(assembly.GetCustomAttributes(typeof(ITestCondition))) + .OfType() + .Select(attributeInfo => attributeInfo.Attribute); + + foreach (ITestCondition condition in conditionAttributes) + { + if (!condition.IsMet) + { + return condition.SkipReason; + } + } + + return null; + } + } +} \ No newline at end of file