diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/GitHubWorkItemAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/GitHubWorkItemAttribute.cs new file mode 100644 index 0000000000..a8320e61f5 --- /dev/null +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/GitHubWorkItemAttribute.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// GitHubWorkItem attribute; used to specify a GitHub issue associated with this test. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed partial class GitHubWorkItemAttribute : WorkItemAttribute +{ + /// + /// Initializes a new instance of the class for the GitHub WorkItem Attribute. + /// + /// The URL to a GitHub issue, pull request, or discussion. + public GitHubWorkItemAttribute(string url) + : base(ExtractId(url)) + => Url = url; + + /// + /// Gets the URL to the GitHub issue associated. + /// + public string Url { get; } + + /// + /// Extracts the ID from the GitHub issue/pull/discussion URL. + /// + /// The URL to a GitHub ticket. + /// The ticket ID. + private static int ExtractId(string url) + { +#if NET7_0_OR_GREATER + Match match = GitHubTicketRegex().Match(url); +#else + Match match = Regex.Match(url, @"https:\/\/github\.com\/.+\/.+\/(issues|pull|discussions)\/(\d+)(#.+)?"); +#endif + return match.Success && int.TryParse(match.Groups[2].Value, out int issueId) + ? issueId + : throw new ArgumentException(FrameworkMessages.InvalidGitHubUrl, nameof(url)); + } + +#if NET7_0_OR_GREATER + [GeneratedRegex("https:\\/\\/github\\.com\\/.+\\/.+\\/(issues|pull|discussions)\\/(\\d+)(#.+)?")] + private static partial Regex GitHubTicketRegex(); +#endif +} diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/WorkItemAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/WorkItemAttribute.cs index 65dc5f16db..68ba3f62fd 100644 --- a/src/TestFramework/TestFramework/Attributes/TestMethod/WorkItemAttribute.cs +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/WorkItemAttribute.cs @@ -7,7 +7,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; /// WorkItem attribute; used to specify a work item associated with this test. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public sealed class WorkItemAttribute : Attribute +public class WorkItemAttribute : Attribute { /// /// Initializes a new instance of the class for the WorkItem Attribute. diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt index cd52b9d0ba..c1e8cb75a1 100644 --- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt @@ -244,6 +244,9 @@ Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.IgnoreMessage. Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.AutoDetect = 2 -> Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType *REMOVED*Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType = Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.Property) -> void *REMOVED*Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataAttribute.DynamicDataAttribute(string! dynamicDataSourceName, System.Type! dynamicDataDeclaringType, Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType dynamicDataSourceType = Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataSourceType.Property) -> void +Microsoft.VisualStudio.TestTools.UnitTesting.GitHubWorkItemAttribute +Microsoft.VisualStudio.TestTools.UnitTesting.GitHubWorkItemAttribute.GitHubWorkItemAttribute(string! url) -> void +Microsoft.VisualStudio.TestTools.UnitTesting.GitHubWorkItemAttribute.Url.get -> string! Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.IgnoreMessage.get -> string? Microsoft.VisualStudio.TestTools.UnitTesting.ITestDataSourceIgnoreCapability.IgnoreMessage.set -> void diff --git a/src/TestFramework/TestFramework/Resources/FrameworkMessages.Designer.cs b/src/TestFramework/TestFramework/Resources/FrameworkMessages.Designer.cs index 8495430c23..a4fcfb2340 100644 --- a/src/TestFramework/TestFramework/Resources/FrameworkMessages.Designer.cs +++ b/src/TestFramework/TestFramework/Resources/FrameworkMessages.Designer.cs @@ -386,6 +386,15 @@ internal static string HasCountFailMsg { } } + /// + /// Looks up a localized string similar to Invalid GitHub ticket URL. + /// + internal static string InvalidGitHubUrl { + get { + return ResourceManager.GetString("InvalidGitHubUrl", resourceCulture); + } + } + /// /// Looks up a localized string similar to The property {0} has type {1}; expected type {2}.. /// diff --git a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx index 1d7681f175..2050ca74b8 100644 --- a/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx +++ b/src/TestFramework/TestFramework/Resources/FrameworkMessages.resx @@ -296,4 +296,7 @@ Actual: {2} Expected collection to contain any item but it is empty. {0} + + Invalid GitHub ticket URL + \ No newline at end of file diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf index 371236303c..10583c04f8 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.cs.xlf @@ -131,6 +131,11 @@ Skutečnost: {2} Očekávala se kolekce {1} velikosti. Skutečnost: {2} {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Očekávaný typ:<{1}>. Aktuální typ:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf index dd35a72d08..a88b227d92 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.de.xlf @@ -131,6 +131,11 @@ Tatsächlich: {2} Es wurde eine Sammlung mit einer Größe {1} erwartet. Tatsächlich: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0}Erwarteter Typ:<{1}>. Tatsächlicher Typ:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf index 48db8534e2..256bab594e 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.es.xlf @@ -131,6 +131,11 @@ Real: {2} Se esperaba una colección de tamaño {1}. Real: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Tipo esperado:<{1}>. Tipo real:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf index 7038f2f401..36ead28fe2 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.fr.xlf @@ -131,6 +131,11 @@ Réel : {2} Collection de tailles attendue {1}. Réel : {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0}Type attendu :<{1}>. Type réel :<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf index 8c99d0653a..1e1af217f2 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.it.xlf @@ -131,6 +131,11 @@ Effettivo: {2} Prevista raccolta di dimensioni {1}. Effettivo: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Tipo previsto:<{1}>. Tipo effettivo:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf index 7b8d082850..f50ec98709 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ja.xlf @@ -131,6 +131,11 @@ Actual: {2} サイズ {1} のコレクションが必要です。実際: {2}。{0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} には型 <{1}> が必要ですが、型 <{2}> が指定されました。 diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf index 00c6b4425f..1d2ed260fd 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ko.xlf @@ -131,6 +131,11 @@ Actual: {2} {1} 크기 컬렉션이 필요합니다. 실제: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} 예상 형식: <{1}>, 실제 형식: <{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf index 703108990b..9cf05e688f 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pl.xlf @@ -131,6 +131,11 @@ Rzeczywiste: {2} Oczekiwano kolekcji rozmiaru {1}. Wartość rzeczywista: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Oczekiwany typ:<{1}>. Rzeczywisty typ:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf index 4256672987..5aa1ead121 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.pt-BR.xlf @@ -131,6 +131,11 @@ Real: {2} Coleção esperada de tamanho {1}. Real: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Tipo esperado:<{1}>. Tipo real:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf index ddb2397c00..37d42fb093 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.ru.xlf @@ -131,6 +131,11 @@ Actual: {2} Ожидается коллекция размеров {1}. Фактически: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0}Ожидается тип: <{1}>. Фактический тип: <{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf index 818c8c95ba..5c9334d859 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.tr.xlf @@ -131,6 +131,11 @@ Gerçekte olan: {2} Beklenen boyut {1}. Gerçek: {2}. {0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} Beklenen tür:<{1}>. Gerçek tür:<{2}>. diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf index 8fec1f371e..a54a6e8d7e 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hans.xlf @@ -131,6 +131,11 @@ Actual: {2} 大小 {1} 的预期集合。实际: {2}。{0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} 类型应为: <{1}>。类型实为: <{2}>。 diff --git a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf index 2f681dc303..dd37e65d78 100644 --- a/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf +++ b/src/TestFramework/TestFramework/Resources/xlf/FrameworkMessages.zh-Hant.xlf @@ -131,6 +131,11 @@ Actual: {2} 預期的大小集合 {1}。實際: {2}。{0} + + Invalid GitHub ticket URL + Invalid GitHub ticket URL + + {0} Expected type:<{1}>. Actual type:<{2}>. {0} 預期的類型: <{1}>,實際的類型: <{2}>。 diff --git a/test/UnitTests/TestFramework.UnitTests/GitHubWorkItemTests.cs b/test/UnitTests/TestFramework.UnitTests/GitHubWorkItemTests.cs new file mode 100644 index 0000000000..deb9db355b --- /dev/null +++ b/test/UnitTests/TestFramework.UnitTests/GitHubWorkItemTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using TestFramework.ForTestingMSTest; + +namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests; + +public class GitHubWorkItemTests : TestContainer +{ + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_IssueUrl() + { + string url = "https://github.com/microsoft/testfx/issues/1234"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_IssueUrlWithEndingSlash() + { + string url = "https://github.com/microsoft/testfx/issues/1234/"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_IssueUrlWithComment() + { + string url = "https://github.com/microsoft/testfx/issues/1234#issuecomment-2581012838"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_PRUrl() + { + string url = "https://github.com/microsoft/testfx/pull/1234"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_PRUrlWithEndingSlash() + { + string url = "https://github.com/microsoft/testfx/pull/1234/"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_PRUrlWithComment() + { + string url = "https://github.com/microsoft/testfx/pull/1234#discussion_r1932733213"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_DiscussionUrl() + { + string url = "https://github.com/microsoft/testfx/discussions/1234"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_DiscussionUrlWithEndingSlash() + { + string url = "https://github.com/microsoft/testfx/discussions/1234/"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } + + public void GitHubWorkItemAttributeShouldExtractIdFromUrl_DiscussionUrlWithComment() + { + string url = "https://github.com/microsoft/testfx/discussions/1234#discussioncomment-11865020"; + GitHubWorkItemAttribute attribute = new(url); + Verify(attribute.Url == url); + Verify(attribute.Id == 1234); + } +}