From c98d58d38d2774e826b5dff73d66aaa081592c77 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:34:46 +0700 Subject: [PATCH 01/13] Add json schemas for allureConfig and testplan --- Allure.Features/allureConfig.json | 1 + Allure.NUnit.Examples/allureConfig.json | 1 + Allure.NUnit/Schemas/allureConfig.schema.json | 22 ++++ .../Schemas/allureConfig.schema.json | 29 +++++ .../Schemas/testplan.schema.json | 22 ++++ Allure.SpecFlowPlugin.Tests/allureConfig.json | 1 + .../Schemas/allureConfig.schema.json | 108 ++++++++++++++++++ Allure.XUnit.Examples/allureConfig.json | 1 + Allure.XUnit/Schemas/allureConfig.schema.json | 33 ++++++ 9 files changed, 218 insertions(+) create mode 100644 Allure.NUnit/Schemas/allureConfig.schema.json create mode 100644 Allure.Net.Commons/Schemas/allureConfig.schema.json create mode 100644 Allure.Net.Commons/Schemas/testplan.schema.json create mode 100644 Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json create mode 100644 Allure.XUnit/Schemas/allureConfig.schema.json diff --git a/Allure.Features/allureConfig.json b/Allure.Features/allureConfig.json index 7af5ca41..59af8ccf 100644 --- a/Allure.Features/allureConfig.json +++ b/Allure.Features/allureConfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json", "allure": { "title": "5994A3F7-AF84-46AD-9393-000BB45553CC", "directory": "allure-results", diff --git a/Allure.NUnit.Examples/allureConfig.json b/Allure.NUnit.Examples/allureConfig.json index 7c59a8f0..9f08d804 100644 --- a/Allure.NUnit.Examples/allureConfig.json +++ b/Allure.NUnit.Examples/allureConfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.NUnit/Schemas/allureConfig.schema.json", "allure": { "directory": "allure-results", "links": [ diff --git a/Allure.NUnit/Schemas/allureConfig.schema.json b/Allure.NUnit/Schemas/allureConfig.schema.json new file mode 100644 index 00000000..9006399a --- /dev/null +++ b/Allure.NUnit/Schemas/allureConfig.schema.json @@ -0,0 +1,22 @@ +{ + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.Net.Commons/Schemas/allureConfig.schema.json" + } + ], + "properties": { + "allure": { + "properties": { + "brokenTestData": { + "type": "array", + "description": "A list of exceptions type names. If any of them is thrown, the test is marked as broken. Otherwise, the test state is determined by the inner logic of allure-nunit.", + "items": { + "type": "string", + "description": "The full class name of an exception type.", + "examples": [ "System.Exception" ] + } + } + } + } + } +} \ No newline at end of file diff --git a/Allure.Net.Commons/Schemas/allureConfig.schema.json b/Allure.Net.Commons/Schemas/allureConfig.schema.json new file mode 100644 index 00000000..adffce16 --- /dev/null +++ b/Allure.Net.Commons/Schemas/allureConfig.schema.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "properties": { + "allure": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "directory": { + "type": "string", + "default": "allure-results", + "description": "The absolute or relative path to an output directory where allure result files will be written." + }, + "links": { + "type": "array", + "description": "An array containing link patterns. Each pattern must contain a link type placeholder in the form of {link-type}.", + "examples": [ + "https://github.com/allure-framework/allure-csharp/issues/{issue}", + "https://example.org/{tms}" + ], + "items": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/Allure.Net.Commons/Schemas/testplan.schema.json b/Allure.Net.Commons/Schemas/testplan.schema.json new file mode 100644 index 00000000..3b9c82ed --- /dev/null +++ b/Allure.Net.Commons/Schemas/testplan.schema.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "properties": { + "tests": { + "type": "array", + "description": "An array of selectors each specifying a test to run. All other tests will be skipped.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of a test (usually assigned by TestOps)." + }, + "selector": { + "type": "string", + "description": "The fullName of a test." + } + } + } + } + } +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin.Tests/allureConfig.json b/Allure.SpecFlowPlugin.Tests/allureConfig.json index 97f5a0bd..fe335dcc 100644 --- a/Allure.SpecFlowPlugin.Tests/allureConfig.json +++ b/Allure.SpecFlowPlugin.Tests/allureConfig.json @@ -1,3 +1,4 @@ { + "$schema": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json", "allure": {} } \ No newline at end of file diff --git a/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json b/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json new file mode 100644 index 00000000..b412c901 --- /dev/null +++ b/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json @@ -0,0 +1,108 @@ +{ + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.Net.Commons/Schemas/allureConfig.schema.json" + } + ], + "properties": { + "specflow": { + "type": "object", + "properties": { + "stepArguments": { + "type": "object", + "properties": { + "convertToParameters": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ "true", "false" ] + } + ] + }, + "paramNameRegex": { + "type": "string" + }, + "paramValueRegex": { + "type": "string" + } + } + }, + "grouping": { + "type": "object", + "properties": { + "suites": { + "type": "object", + "properties": { + "parentSuite": { + "type": "string" + }, + "suite": { + "type": "string" + }, + "subSuite": { + "type": "string" + } + } + }, + "behaviors": { + "type": "object", + "properties": { + "epic": { + "type": "string" + }, + "story": { + "type": "string" + } + } + }, + "packages": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "testClass": { + "type": "string" + }, + "testMethod": { + "type": "string" + } + } + } + } + }, + "labels": { + "type": "object", + "properties": { + "owner": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "label": { + "type": "string" + } + } + }, + "links": { + "type": "object", + "properties": { + "link": { + "type": "string" + }, + "issue": { + "type": "string" + }, + "tms": { + "type": "string" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Allure.XUnit.Examples/allureConfig.json b/Allure.XUnit.Examples/allureConfig.json index c2ef3daf..bb780baf 100644 --- a/Allure.XUnit.Examples/allureConfig.json +++ b/Allure.XUnit.Examples/allureConfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.XUnit/Schemas/allureConfig.schema.json", "allure": { "directory": "allure-results", "links": [ diff --git a/Allure.XUnit/Schemas/allureConfig.schema.json b/Allure.XUnit/Schemas/allureConfig.schema.json new file mode 100644 index 00000000..d5deb4f9 --- /dev/null +++ b/Allure.XUnit/Schemas/allureConfig.schema.json @@ -0,0 +1,33 @@ +{ + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/allure-framework/allure-csharp/selective-run/Allure.Net.Commons/Schemas/allureConfig.schema.json" + } + ], + "properties": { + "allure": { + "properties": { + "xunitRunnerReporter": { + "description": "An xUnit runner reporter that will be run in addition to allure-xunit.", + "examples": [ + "auto", + "none", + "teamcity", + "xunit.runner.reporters.netcoreapp10, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c" + ], + "default": "auto", + "oneOf": [ + { + "type": "string", + "enum": [ "none", "auto" ] + }, + { + "type": "string", + "description": "The runner switch or the assembly qualified class name of a runner reporter." + } + ] + } + } + } + } +} \ No newline at end of file From 24624f5ef288d6358474b39cac505440db57549a Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:11:27 +0700 Subject: [PATCH 02/13] Fix schema for allure-xunit and allure-specflow config --- .../Schemas/allureConfig.schema.json | 54 ++++++++++++------- Allure.XUnit/Schemas/allureConfig.schema.json | 2 +- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json b/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json index b412c901..9d2fe028 100644 --- a/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json +++ b/Allure.SpecFlowPlugin/Schemas/allureConfig.schema.json @@ -12,7 +12,8 @@ "type": "object", "properties": { "convertToParameters": { - "anyOf": [ + "description": "If set to true, allure-specflow converts a step table data to step parameters.", + "oneOf": [ { "type": "boolean" }, @@ -20,13 +21,16 @@ "type": "string", "enum": [ "true", "false" ] } - ] + ], + "default": false }, "paramNameRegex": { - "type": "string" + "type": "string", + "description": "If convertToParameters is true, allure-specflow checks if the first column header of a step table matches this regular expression. If it's not, the table is skipped.'" }, "paramValueRegex": { - "type": "string" + "type": "string", + "description": "If convertToParameters is true, allure-specflow checks if the second column header of a step table matches this regular expression. If it's not, the table is skipped.'" } } }, @@ -37,13 +41,16 @@ "type": "object", "properties": { "parentSuite": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the parentSuite label. If there is a capture group defined, only the part matching this group is used as parentSuite. Otherwise, the whole tag is used." }, "suite": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the suite label. If there is a capture group defined, only the part matching this group is used as suite. Otherwise, the whole tag is used." }, "subSuite": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the subSuite label. If there is a capture group defined, only the part matching this group is used as subSuite. Otherwise, the whole tag is used." } } }, @@ -51,10 +58,12 @@ "type": "object", "properties": { "epic": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the epic label. If there is a capture group defined, only the part matching this group is used as epic. Otherwise, the whole tag is used." }, "story": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the story label. If there is a capture group defined, only the part matching this group is used as story. Otherwise, the whole tag is used." } } }, @@ -62,13 +71,16 @@ "type": "object", "properties": { "package": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the package label. If there is a capture group defined, only the part matching this group is used as package. Otherwise, the whole tag is used." }, "testClass": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the testClass label. If there is a capture group defined, only the part matching this group is used as testClass. Otherwise, the whole tag is used." }, "testMethod": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the testMethod label. If there is a capture group defined, only the part matching this group is used as testMethod. Otherwise, the whole tag is used." } } } @@ -78,13 +90,16 @@ "type": "object", "properties": { "owner": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the owner label. If there is a capture group defined, only the part matching this group is used as owner. Otherwise, the whole tag is used." }, "severity": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the severity label. If there is a capture group defined, only the part matching this group is used as severity. Otherwise, the whole tag is used." }, "label": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to the specified label. You must define two capture groups in the expression. The first one is used to match a name of the label, while the second one is used to match a value." } } }, @@ -92,13 +107,16 @@ "type": "object", "properties": { "link": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to a link. If there is a capture group defined, only the part matching this group is used as a link's value. Otherwise, the whole tag is used." }, "issue": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to an issue link. If there is a capture group defined, only the part matching this group is used as a link's value. Otherwise, the whole tag is used." }, "tms": { - "type": "string" + "type": "string", + "description": "If a gherkin tag matches this regular expression, it's converted to a TMS item link. If there is a capture group defined, only the part matching this group is used as a link's value. Otherwise, the whole tag is used." } } } diff --git a/Allure.XUnit/Schemas/allureConfig.schema.json b/Allure.XUnit/Schemas/allureConfig.schema.json index d5deb4f9..7904e56b 100644 --- a/Allure.XUnit/Schemas/allureConfig.schema.json +++ b/Allure.XUnit/Schemas/allureConfig.schema.json @@ -16,7 +16,7 @@ "xunit.runner.reporters.netcoreapp10, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c" ], "default": "auto", - "oneOf": [ + "anyOf": [ { "type": "string", "enum": [ "none", "auto" ] From c25b6c5b74ea46d162fb5e4bcb964462d37fc846 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:08:15 +0700 Subject: [PATCH 03/13] Implement selective run commons --- .../TestPlanProviderTests.cs | 120 +++++++++ .../SelectiveRunTests/TestPlanTests.cs | 238 ++++++++++++++++++ Allure.Net.Commons/AllureConstants.cs | 6 + Allure.Net.Commons/TestPlan/AllureTestPlan.cs | 126 ++++++++++ .../TestPlan/AllureTestPlanItem.cs | 10 + 5 files changed, 500 insertions(+) create mode 100644 Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanProviderTests.cs create mode 100644 Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs create mode 100644 Allure.Net.Commons/TestPlan/AllureTestPlan.cs create mode 100644 Allure.Net.Commons/TestPlan/AllureTestPlanItem.cs diff --git a/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanProviderTests.cs b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanProviderTests.cs new file mode 100644 index 00000000..bf4a8fe6 --- /dev/null +++ b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanProviderTests.cs @@ -0,0 +1,120 @@ +using Allure.Net.Commons.TestPlan; +using NUnit.Framework; +using System; +using System.IO; +using System.Text; + +namespace Allure.Net.Commons.Tests.SelectiveRunTests +{ + class TestPlanProviderTests + { + private string testPlanPath; + + [SetUp] + public void SetUp() + { + this.testPlanPath = Path.GetTempFileName(); + File.WriteAllText( + this.testPlanPath, + "{\"tests\": [{\"id\": \"100\"}]}", + Encoding.UTF8 + ); + } + + [TearDown] + public void TearDown() + { + if (File.Exists(this.testPlanPath)) + { + File.Delete(this.testPlanPath); + } + Environment.SetEnvironmentVariable("ALLURE_TESTPLAN_PATH", null); + Environment.SetEnvironmentVariable("AS_TESTPLAN_PATH", null); + } + + [Test] + public void TestPlanCreatedFromFileByNewEnvName() + { + Environment.SetEnvironmentVariable( + "ALLURE_TESTPLAN_PATH", + this.testPlanPath + ); + + var testplan = AllureTestPlan.FromEnvironment(); + + Assert.That( + testplan.Tests, + Is.EqualTo( + new[] { new AllureTestPlanItem() { Id = "100" } } + ) + ); + } + + [Test] + public void TestPlanCreatedFromFileByOldEnvName() + { + Environment.SetEnvironmentVariable( + "AS_TESTPLAN_PATH", + this.testPlanPath + ); + + var testplan = AllureTestPlan.FromEnvironment(); + + Assert.That( + testplan.Tests, + Is.EqualTo( + new[] { new AllureTestPlanItem() { Id = "100" } } + ) + ); + } + + [Test] + public void DefaultTestPlanCreatedIfNoEnvVarDefined() + { + Assert.That( + AllureTestPlan.FromEnvironment(), + Is.SameAs( + AllureTestPlan.DEFAULT_TESTPLAN + ) + ); + } + + [Test] + public void DefaultTestPlanCreatedIfFIleDoesntExist() + { + Environment.SetEnvironmentVariable( + "ALLURE_TESTPLAN_PATH", + Guid.NewGuid().ToString() + ); + + Assert.That( + AllureTestPlan.FromEnvironment(), + Is.SameAs( + AllureTestPlan.DEFAULT_TESTPLAN + ) + ); + } + + [Test] + public void IfBothEnvVarsPresentNewIsPreferred() + { + Environment.SetEnvironmentVariable( + "AS_TESTPLAN_PATH", + Guid.NewGuid().ToString() + ); + Environment.SetEnvironmentVariable( + "ALLURE_TESTPLAN_PATH", + this.testPlanPath + ); + + var testplan = AllureTestPlan.FromEnvironment(); + + Assert.That( + testplan.Tests, + Is.EqualTo( + new[] { new AllureTestPlanItem() { Id = "100" } } + ) + ); + } + } +} diff --git a/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs new file mode 100644 index 00000000..7fe0c0d6 --- /dev/null +++ b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs @@ -0,0 +1,238 @@ +using Allure.Net.Commons.TestPlan; +using NUnit.Framework; +using System.Linq; + +#nullable enable + +namespace Allure.Net.Commons.Tests.SelectiveRunTests +{ + class TestPlanTests + { + [TestCase("null")] + [TestCase("{}")] + [TestCase("{\"tests\": []}")] + public void EmptyTestPlan(string json) + { + var testPlan = AllureTestPlan.FromJson(json); + + Assert.That(testPlan, Is.Not.Null); + Assert.That( + testPlan.IsMatch("", null), + Is.False + ); + } + + [TestCase(null, false)] + [TestCase("100", true)] + [TestCase("101", false)] + public void TestPlanWithIdEntry( + string allureIdOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = "{\"tests\": [{\"id\": \"100\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + "", + allureIdOfTestCase + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("a", true)] + [TestCase("A", false)] + [TestCase("aa", false)] + public void TestPlanWithSelectorEntry( + string fullNameOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"selector\": \"a\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + fullNameOfTestCase, + null + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("a", "100", true)] + [TestCase("b", "100", true)] + [TestCase("a", "101", true)] + [TestCase("a", null, true)] + [TestCase("b", "101", false)] + [TestCase("b", null, false)] + public void TestPlanWithIdAndSelectorEntry( + string fullNameOfTestCase, + string allureIdOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"selector\": \"a\", \"id\": \"100\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + fullNameOfTestCase, + allureIdOfTestCase + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("100", true)] + [TestCase("101", true)] + [TestCase("102", false)] + public void TestPlanWithMultipleIdEntries( + string allureIdOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"id\": \"100\"}, {\"id\": \"101\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + "", + allureIdOfTestCase + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("a", true)] + [TestCase("b", true)] + [TestCase("c", false)] + public void TestPlanWithMultipleSelectorEntries( + string fullNameOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"selector\": \"a\"}, {\"selector\": \"b\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + fullNameOfTestCase, + null + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("a", "100", true)] + [TestCase("b", "100", true)] + [TestCase("a", "101", true)] + [TestCase("a", null, true)] + [TestCase("b", "101", false)] + [TestCase("b", null, false)] + public void TestPlanWithMultipleDifferentEntries( + string fullNameOfTestCase, + string allureIdOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"id\": \"100\"}, {\"selector\": \"a\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + fullNameOfTestCase, + allureIdOfTestCase + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase("a", null, true)] + [TestCase("a", "100", true)] + [TestCase("a", "101", true)] + [TestCase("a", "102", true)] + [TestCase("b", null, true)] + [TestCase("b", "100", true)] + [TestCase("b", "101", true)] + [TestCase("b", "102", true)] + [TestCase("c", null, false)] + [TestCase("c", "100", true)] + [TestCase("c", "101", true)] + [TestCase("c", "102", false)] + public void TestPlanWithMultipleFullEntries( + string fullNameOfTestCase, + string allureIdOfTestCase, + bool expectedMatch + ) + { + var testPlanJson = + "{\"tests\": [{\"selector\": \"a\", \"id\": \"100\"}, {\"selector\": \"b\", \"id\": \"101\"}]}"; + var testPlan = AllureTestPlan.FromJson(testPlanJson); + + Assert.That( + testPlan.IsMatch( + fullNameOfTestCase, + allureIdOfTestCase + ), + Is.EqualTo(expectedMatch) + ); + } + + [TestCase(new string[] { }, null)] + [TestCase(new[] { "ALLURE_ID", "100" }, "100")] + [TestCase(new[] { "l1", "v1" }, null)] + [TestCase(new[] { "AS_ID", "100" }, "100")] + [TestCase(new[] { "allure_id", "100" }, "100")] + [TestCase(new[] { "as_id", "100" }, "100")] + [TestCase(new[] + { + "l", "v", + "ALLURE_ID", "100" + }, "100")] + [TestCase(new[] + { + "l", "v", + "AS_ID", "100" + }, "100")] + [TestCase(new[] + { + "allure_id", "100", + "ALLURE_ID", "101", + "AS_ID", "102" + }, "100")] + [TestCase(new[] + { + "AS_ID", "100", + "ALLURE_ID", "101" + }, "101")] + public void GetAllureIdTest(string[] labels, string? expectedAllureId) + { + Assert.That( + AllureTestPlan.GetAllureId( + CreateTestCaseWithLabels(labels) + ), + Is.EqualTo(expectedAllureId) + ); + } + + static TestResult CreateTestCaseWithLabels(params string[] labels) => + new() + { + labels = Enumerable.Range(0, labels.Length / 2).Select( + i => new Label() + { + name = labels[2 * i], + value = labels[2 * i + 1] + } + ).ToList() + }; + } +} diff --git a/Allure.Net.Commons/AllureConstants.cs b/Allure.Net.Commons/AllureConstants.cs index 33e959be..28fa9321 100644 --- a/Allure.Net.Commons/AllureConstants.cs +++ b/Allure.Net.Commons/AllureConstants.cs @@ -10,5 +10,11 @@ public sealed class AllureConstants public const string TEST_RESULT_CONTAINER_FILE_SUFFIX = "-container.json"; public static string TEST_RUN_FILE_SUFFIX = "-testrun.json"; public const string ATTACHMENT_FILE_SUFFIX = "-attachment"; + + public const string OLD_ALLURE_ID_LABEL_NAME = "AS_ID"; + public const string NEW_ALLURE_ID_LABEL_NAME = "ALLURE_ID"; + + public const string OLD_ALLURE_TESTPLAN_ENV_NAME = "AS_TESTPLAN_PATH"; + public const string NEW_ALLURE_TESTPLAN_ENV_NAME = "ALLURE_TESTPLAN_PATH"; } } \ No newline at end of file diff --git a/Allure.Net.Commons/TestPlan/AllureTestPlan.cs b/Allure.Net.Commons/TestPlan/AllureTestPlan.cs new file mode 100644 index 00000000..8a13f161 --- /dev/null +++ b/Allure.Net.Commons/TestPlan/AllureTestPlan.cs @@ -0,0 +1,126 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +#nullable enable + +namespace Allure.Net.Commons.TestPlan; + +/// +/// Used by integrations to enable selective run of tests. +/// +public class AllureTestPlan +{ + /// + /// Testplan entries that denote tests included in the run. + /// + public List Tests + { + get => this.tests; + set + { + this.tests = value ?? new(); + this.RecreateFilters(); + } + } + + /// + /// Used by integrations to check if a test is selected by the testplan. + /// + /// A fullName of the test. + /// + /// An identifier of the test case (if any). + /// Use to get it from the test result. + /// + /// true if the test should be executed. false otherwise. + public bool IsMatch(string fullName, string? allureId) => + this.IsFullNameMatch(fullName) || this.IsAllureIdMatch(allureId); + + /// + /// Creates the testplan from a JSON string. + /// + public static AllureTestPlan FromJson(string jsonContent) => + JsonConvert.DeserializeObject( + jsonContent, + new JsonSerializerSettings() + { + ObjectCreationHandling = ObjectCreationHandling.Replace, + } + ) ?? DEFAULT_TESTPLAN; + + /// + /// Creates the testplan from the file pointed by the environment variable. + /// + /// + public static AllureTestPlan FromEnvironment() + { + var testPlanPath = ResolveTestPlanPath(); + return GetTestPlanByPath(testPlanPath); + } + + /// + /// Extracts the id from a test result. If there is no id associated, + /// returns null. + /// + public static string? GetAllureId(TestResult testResult) => + FindLabel(testResult, AllureConstants.NEW_ALLURE_ID_LABEL_NAME) + ?? FindLabel(testResult, AllureConstants.OLD_ALLURE_ID_LABEL_NAME); + + /// + /// A testplan that doesn't filter any test. + /// + public static readonly AllureTestPlan DEFAULT_TESTPLAN = new(); + + static string? ResolveTestPlanPath() => + new[] + { + AllureConstants.NEW_ALLURE_TESTPLAN_ENV_NAME, + AllureConstants.OLD_ALLURE_TESTPLAN_ENV_NAME + }.Select(Environment.GetEnvironmentVariable).Where( + ev => !string.IsNullOrWhiteSpace(ev) + ).FirstOrDefault(); + + static AllureTestPlan GetTestPlanByPath(string? testPlanPath) => + testPlanPath is null || !File.Exists(testPlanPath) + ? DEFAULT_TESTPLAN + : ReadTestPlanFromFile(testPlanPath); + + static AllureTestPlan ReadTestPlanFromFile(string testPlanPath) => + FromJson( + File.ReadAllText(testPlanPath, Encoding.UTF8) + ); + + static string? FindLabel(TestResult testResult, string labelName) => + testResult.labels.FirstOrDefault( + l => labelName.Equals(l.name, StringComparison.OrdinalIgnoreCase) + )?.value; + + List tests = new(); + HashSet allIds = new(); + HashSet allSelectors = new(); + + void RecreateFilters() + { + this.allIds = new HashSet( + from entry in this.tests + where entry.Id is not null + select entry.Id, + StringComparer.Ordinal + ); + this.allSelectors = new HashSet( + from entry in this.tests + where entry.Selector is not null + select entry.Selector, + StringComparer.Ordinal + ); + } + + bool IsAllureIdMatch(string? allureId) => + allureId is not null && this.allIds.Contains(allureId); + + bool IsFullNameMatch(string fullName) => + this.allSelectors.Contains(fullName); +} diff --git a/Allure.Net.Commons/TestPlan/AllureTestPlanItem.cs b/Allure.Net.Commons/TestPlan/AllureTestPlanItem.cs new file mode 100644 index 00000000..70adc1fb --- /dev/null +++ b/Allure.Net.Commons/TestPlan/AllureTestPlanItem.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace Allure.Net.Commons.TestPlan; + +public record class AllureTestPlanItem +{ + public string? Id { get; set; } + + public string? Selector { get; set; } +} From 63fe1de73f92399a17bc22f7774fb211378c6c50 Mon Sep 17 00:00:00 2001 From: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> Date: Thu, 28 Sep 2023 00:01:16 +0700 Subject: [PATCH 04/13] Change GetAllureId to take an enumeration --- .../SelectiveRunTests/TestPlanTests.cs | 26 +++++++++---------- Allure.Net.Commons/TestPlan/AllureTestPlan.cs | 20 ++++++++------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs index 7fe0c0d6..4016b012 100644 --- a/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs +++ b/Allure.Net.Commons.Tests/SelectiveRunTests/TestPlanTests.cs @@ -1,5 +1,6 @@ using Allure.Net.Commons.TestPlan; using NUnit.Framework; +using System.Collections.Generic; using System.Linq; #nullable enable @@ -11,14 +12,14 @@ class TestPlanTests [TestCase("null")] [TestCase("{}")] [TestCase("{\"tests\": []}")] - public void EmptyTestPlan(string json) + public void EmptyTestPlanEnablesAllTests(string json) { var testPlan = AllureTestPlan.FromJson(json); Assert.That(testPlan, Is.Not.Null); Assert.That( testPlan.IsMatch("", null), - Is.False + Is.True ); } @@ -217,22 +218,19 @@ public void GetAllureIdTest(string[] labels, string? expectedAllureId) { Assert.That( AllureTestPlan.GetAllureId( - CreateTestCaseWithLabels(labels) + CreateLabels(labels) ), Is.EqualTo(expectedAllureId) ); } - static TestResult CreateTestCaseWithLabels(params string[] labels) => - new() - { - labels = Enumerable.Range(0, labels.Length / 2).Select( - i => new Label() - { - name = labels[2 * i], - value = labels[2 * i + 1] - } - ).ToList() - }; + static IEnumerable