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