Skip to content

Commit

Permalink
Merge pull request #1172 from TestCentric/issue-1148
Browse files Browse the repository at this point in the history
Apply test filter for test run
  • Loading branch information
rowo360 authored Feb 5, 2025
2 parents 6d0ca17 + 89beaf6 commit 37f3f47
Show file tree
Hide file tree
Showing 14 changed files with 717 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,10 @@ private void RunFailedTests()
{
var test = entry.Value;
if (!test.IsSuite && test.Outcome.Status == TestStatus.Failed)
failedTests.Add(test);
{
TestNode testNode = _model.GetTestById(test.Id);
failedTests.Add(testNode);
}
}

_model.RunTests(failedTests);
Expand Down
4 changes: 3 additions & 1 deletion src/TestModel/model/Filter/CategoryFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class CategoryFilter : ITestFilter

private List<string> _condition = new List<string>();

internal CategoryFilter(ITestModel model)
public CategoryFilter(ITestModel model)
{
TestModel = model;
}
Expand All @@ -33,6 +33,8 @@ public IEnumerable<string> Condition
set { _condition = value.ToList(); }
}

public bool IsActive => AllCategories.Except(_condition).Any();

public IEnumerable<string> AllCategories { get; private set; }

public bool IsMatching(TestNode testNode)
Expand Down
5 changes: 5 additions & 0 deletions src/TestModel/model/Filter/ITestCentricTestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public interface ITestCentricTestFilter
/// </summary>
IEnumerable<string> AllCategories { get; }

/// <summary>
/// Checks if any filter is active
/// </summary>
bool IsActive { get; }

/// <summary>
/// Clear all actives filters and reset them to default
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/TestModel/model/Filter/ITestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,10 @@ internal interface ITestFilter
/// Checks if the testNode matches the filter condition
/// </summary>
bool IsMatching(TestNode testNode);

/// <summary>
/// Checks if the filter is active
/// </summary>
bool IsActive { get; }
}
}
4 changes: 3 additions & 1 deletion src/TestModel/model/Filter/OutcomeFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class OutcomeFilter : ITestFilter

private List<string> _condition = new List<string>();

internal OutcomeFilter(ITestModel model)
public OutcomeFilter(ITestModel model)
{
TestModel = model;
}
Expand All @@ -33,6 +33,8 @@ public IEnumerable<string> Condition
set { _condition = value.ToList(); }
}

public bool IsActive => _condition.Any();

public bool IsMatching(TestNode testNode)
{
// All kind of outcomes should be displayed (no outcome filtering)
Expand Down
2 changes: 2 additions & 0 deletions src/TestModel/model/Filter/TestCentricTestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public IEnumerable<string> AllCategories
}
}

public bool IsActive => _filters.Any(x => x.IsActive);

public void ClearAllFilters()
{
foreach (ITestFilter filter in _filters)
Expand Down
4 changes: 3 additions & 1 deletion src/TestModel/model/Filter/TextFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace TestCentric.Gui.Model.Filter
/// <summary>
/// Filters the TestNodes by matching a text (for example: Namespace, Class name or test method name - filter is case insensitive)
/// </summary>
internal class TextFilter : ITestFilter
public class TextFilter : ITestFilter
{
private string _condition = string.Empty;

Expand All @@ -24,6 +24,8 @@ public IEnumerable<string> Condition
set { _condition = value.FirstOrDefault(); }
}

public bool IsActive => string.IsNullOrEmpty( _condition) == false;

public bool IsMatching(TestNode testNode)
{
if (string.IsNullOrEmpty(_condition))
Expand Down
29 changes: 29 additions & 0 deletions src/TestModel/model/TestFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ public static TestFilter MakeCategoryFilter(IList<string> categories)
return new TestFilter(sb.ToString());
}

/// <summary>
/// Creates a TestFilter which contains the IDs of all visible child nodes
/// </summary>
public static TestFilter MakeVisibleIdFilter(IEnumerable<TestNode> testNodes)
{
StringBuilder sb = new StringBuilder("<filter><or>");

foreach (TestNode test in testNodes)
MakeVisibleIdFilter(test, sb);

sb.Append("</or></filter>");

return new TestFilter(sb.ToString());
}

private static void MakeVisibleIdFilter(TestNode testNode, StringBuilder sb)
{
// If testNode is not visible, don't add it or any child to filter
if (!testNode.IsVisible)
return;

// Add only Id for leaf nodes
if (!testNode.IsProject && !testNode.IsSuite && testNode.Children.Count == 0)
sb.Append($"<id>{testNode.Id}</id>");

foreach (TestNode childNode in testNode.Children)
MakeVisibleIdFilter(childNode, sb);
}

public static TestFilter MakeNotFilter(TestFilter filter)
{
return new TestFilter($"<filter><not>{filter.InnerXml}</not></filter>");
Expand Down
4 changes: 4 additions & 0 deletions src/TestModel/model/TestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,10 @@ private void RunTests(TestRunSpecification runSpec)
if (!runSpec.CategoryFilter.IsEmpty)
filter = TestFilter.MakeAndFilter(filter, runSpec.CategoryFilter);

// If a filter is active in the UI, a TestFilter must be created accordingly that contains the ID all visible children of the selected nodes.
if (Settings.Gui.TestTree.DisplayFormat == "NUNIT_TREE" && TestCentricTestFilter.IsActive)
filter = TestFilter.MakeVisibleIdFilter(runSpec.SelectedTests);

// We need to re-create the test runner because settings such
// as debugging have already been passed to the test runner.
// For performance reasons, we only do this if we did run
Expand Down
175 changes: 175 additions & 0 deletions src/TestModel/tests/Filter/CategoryFilterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// ***********************************************************************
// Copyright (c) Charlie Poole and TestCentric contributors.
// Licensed under the MIT License. See LICENSE file in root directory.
// ***********************************************************************

using NSubstitute;
using NUnit.Framework;
using System.Collections.Generic;

namespace TestCentric.Gui.Model.Filter
{
[TestFixture]
internal class CategoryFilterTests
{
[Test]
public void Create_TestFilter_ConditionIsEmpty()
{
// 1. Arrange + Act
ITestModel testModel = Substitute.For<ITestModel>();
CategoryFilter filter = new CategoryFilter(testModel);

// 2. Assert
Assert.That(filter.Condition, Is.Empty);
}

[TestCase(new[] { "CategoryA" }, new[] { "CategoryA" })]
[TestCase(new[] { "CategoryA", "CategoryB" }, new[] { "CategoryB" })]
[TestCase(new[] { "CategoryB" }, new[] { "CategoryA", "CategoryB" })]
[TestCase(new[] { CategoryFilter.NoCategory }, new string[]{ })]

public void IsMatching_CategoryMatchesCategoryFilter_ReturnsTrue(IList<string> categoryFilter, IList<string> testCategories)
{
// 1. Arrange
ITestModel testModel = Substitute.For<ITestModel>();
CategoryFilter filter = new CategoryFilter(testModel);
filter.Condition = categoryFilter;

string xml = CreateTestcaseXml("1", "TestA", testCategories);
TestNode testNode = new TestNode(xml);

// 2. Act
bool isMatch = filter.IsMatching(testNode);

// 3. Assert
Assert.That(isMatch, Is.True);
}

[TestCase(new[] { "CategoryA" }, new[] { "CategoryB" })]
[TestCase(new[] { "CategoryA" }, new[] { "" })]
[TestCase(new[] { CategoryFilter.NoCategory }, new[] { "CategoryB" })]
public void IsMatching_CategoryNotMatchesCategoryFilter_ReturnsFalse(IList<string> categoryFilter, IList<string> testCategories)
{
// 1. Arrange
ITestModel testModel = Substitute.For<ITestModel>();
CategoryFilter filter = new CategoryFilter(testModel);
filter.Condition = categoryFilter;

string xml = CreateTestcaseXml("1", "TestA", testCategories);
TestNode testNode = new TestNode(xml);

// 2. Act
bool isMatch = filter.IsMatching(testNode);

// 3. Assert
Assert.That(isMatch, Is.False);
}

[Test]
public void Init_Condition_ContainsAllCategories()
{
// 1. Arrange
var availableCategories = new List<string>() { "CategoryA", "CategoryB" };
ITestModel testModel = Substitute.For<ITestModel>();
testModel.AvailableCategories.Returns(availableCategories);
CategoryFilter filter = new CategoryFilter(testModel);
filter.Condition = new[] { "CategoryA" };

// 2. Act
filter.Init();

// 3. Assert
Assert.That(filter.Condition, Has.Exactly(3).Items);
Assert.That(filter.Condition, Does.Contain("CategoryA"));
Assert.That(filter.Condition, Does.Contain("CategoryB"));
Assert.That(filter.Condition, Does.Contain(CategoryFilter.NoCategory));
}

[Test]
public void Reset_Condition_ContainsAllCategories()
{
// 1. Arrange
var availableCategories = new List<string>() { "CategoryA", "CategoryB" };
ITestModel testModel = Substitute.For<ITestModel>();
testModel.AvailableCategories.Returns(availableCategories);
CategoryFilter filter = new CategoryFilter(testModel);
filter.Condition = new[] { "CategoryA" };

// 2. Act
filter.Reset();

// 3. Assert
Assert.That(filter.Condition, Has.Exactly(3).Items);
Assert.That(filter.Condition, Does.Contain("CategoryA"));
Assert.That(filter.Condition, Does.Contain("CategoryB"));
Assert.That(filter.Condition, Does.Contain(CategoryFilter.NoCategory));
}

[Test]
public void IsActive_Condition_IsSet_ReturnsTrue()
{
// 1. Arrange
var availableCategories = new List<string>() { "CategoryA", "CategoryB" };
ITestModel testModel = Substitute.For<ITestModel>();
testModel.AvailableCategories.Returns(availableCategories);
CategoryFilter filter = new CategoryFilter(testModel);
filter.Init();

// 2. Act
filter.Condition = new[] { "CategoryA" };

// 3. Assert
Assert.That(filter.IsActive, Is.EqualTo(true));
}

[Test]
public void IsActive_FilterIsReset_ReturnsFalse()
{
// 1. Arrange
var availableCategories = new List<string>() { "CategoryA", "CategoryB" };
ITestModel testModel = Substitute.For<ITestModel>();
testModel.AvailableCategories.Returns(availableCategories);
CategoryFilter filter = new CategoryFilter(testModel);
filter.Init();
filter.Condition = new[] { "CategoryA" };

// 2. Act
filter.Reset();

// 3. Assert
Assert.That(filter.IsActive, Is.False);
}

[Test]
public void IsActive_FilterIsInit_ReturnsFalse()
{
// 1. Arrange
var availableCategories = new List<string>() { "CategoryA", "CategoryB" };
ITestModel testModel = Substitute.For<ITestModel>();
testModel.AvailableCategories.Returns(availableCategories);
CategoryFilter filter = new CategoryFilter(testModel);
filter.Init();
filter.Condition = new[] { "CategoryA" };

// 2. Act
filter.Init();

// 3. Assert
Assert.That(filter.IsActive, Is.False);
}

private string CreateTestcaseXml(string testId, string testName, IList<string> categories)
{
string str = $"<test-case id='{testId}' fullname='{testName}'> ";

str += "<properties> ";
foreach (string category in categories)
str += $"<property name='Category' value='{category}' /> ";
str += "</properties> ";

str += "</test-case> ";

return str;
}
}
}
Loading

0 comments on commit 37f3f47

Please sign in to comment.