Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore the use of environment variables for controlling the active mutation #3122

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<GenerateProgramFile>true</GenerateProgramFile>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public void IdentifyNonCoveredMutants()
}

[TestMethod]
public void WorksWhenAllMutantsAreIgnoredPool()
public void ShouldIgnoreCoverageAnalysisWhenEmpty()
{
var options = new StrykerOptions
{
Expand All @@ -270,7 +270,7 @@ public void WorksWhenAllMutantsAreIgnoredPool()

var analyzer = new CoverageAnalyser(options);
analyzer.DetermineTestCoverage(SourceProjectInfo, runner, new[] { Mutant, OtherMutant }, TestGuidsList.NoTest());
Mutant.CoveringTests.Count.ShouldBe(0);
Mutant.CoveringTests.IsEveryTest.ShouldBeTrue();
}

[TestMethod]
Expand Down Expand Up @@ -666,8 +666,9 @@ public void DetectUnexpectedCase()

var mockVsTest = BuildVsTestRunnerPool(options, out var runner);

var testResult = BuildCoverageTestResult("T0", new[] { "0;", "" });
var buildCase = BuildCase("unexpected", TestFrameworks.NUnit);
SetupMockCoverageRun(mockVsTest, new[] { new VsTest.TestResult(buildCase) { Outcome = VsTest.TestOutcome.Passed } });
SetupMockCoverageRun(mockVsTest, new[] { new VsTest.TestResult(buildCase) { Outcome = VsTest.TestOutcome.Passed }, testResult });

var analyzer = new CoverageAnalyser(options);
analyzer.DetermineTestCoverage(SourceProjectInfo, runner, new[] { Mutant, OtherMutant }, TestGuidsList.NoTest());
Expand Down
22 changes: 17 additions & 5 deletions src/Stryker.Core/Stryker.Core/CoverageAnalysis/CoverageAnalyser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,33 @@ public void DetermineTestCoverage(IProjectAndTests project, ITestRunner runner,
if (!_options.OptimizationMode.HasFlag(OptimizationModes.SkipUncoveredMutants) &&
!_options.OptimizationMode.HasFlag(OptimizationModes.CoverageBasedTest))
{
foreach (var mutant in mutants)
{
mutant.CoveringTests = TestGuidsList.EveryTest();
mutant.AssessingTests = TestGuidsList.EveryTest();
}
AssumeAllTestsAreNeeded(mutants);

return;
}

ParseCoverage(runner.CaptureCoverage(project), mutants, new TestGuidsList(resultFailingTests.GetGuids()));
}

private static void AssumeAllTestsAreNeeded(IEnumerable<IMutant> mutants)
{
foreach (var mutant in mutants)
{
mutant.CoveringTests = TestGuidsList.EveryTest();
mutant.AssessingTests = TestGuidsList.EveryTest();
}
}

private void ParseCoverage(IEnumerable<CoverageRunResult> coverage, IEnumerable<IMutant> mutantsToScan,
TestGuidsList failedTests)
{
if (coverage.Sum(c => c.MutationsCovered.Count) == 0)
{
_logger.LogError("It looks like the test coverage capture failed. Disable coverage based optimisation.");
AssumeAllTestsAreNeeded(mutantsToScan);
return;
}

var dubiousTests = new HashSet<Guid>();
var trustedTests = new HashSet<Guid>();
var testIds = new HashSet<Guid>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private InitialTestRun InitialTest(IStrykerOptions options, SourceProjectInfo pr
}

throw new InputException(
"No test has been detected. Make sure your test project contains test and is compatible with VsTest." +
"No test result reported. Make sure your test project contains test and is compatible with VsTest." +
string.Join(Environment.NewLine, projectInfo.Warnings));
}

Expand Down
17 changes: 13 additions & 4 deletions src/Stryker.Core/Stryker.Core/InjectedHelpers/MutantControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,23 @@ public static bool IsActive(int id)
if (ActiveMutant == ActiveMutantNotInitValue)
{
#pragma warning disable CS8600
string environmentVariable = System.Environment.GetEnvironmentVariable("ActiveMutation");
if (string.IsNullOrEmpty(environmentVariable))
// get the environment variable storign the mutation id
string environmentVariableName = System.Environment.GetEnvironmentVariable("STRYKER_CONTROL_VAR");
if (environmentVariableName != null)
{
ActiveMutant = -1;
string environmentVariable = System.Environment.GetEnvironmentVariable(environmentVariableName);
if (string.IsNullOrEmpty(environmentVariable))
{
ActiveMutant = -1;
}
else
{
ActiveMutant = int.Parse(environmentVariable);
}
}
else
{
ActiveMutant = int.Parse(environmentVariable);
ActiveMutant = -1;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,22 @@ public void Dispose()
}

/// <summary>
/// Starts a new VsTest instance and returns a wrapper to control it.
/// Starts a new VsTest instance and returns a wrapper to control it.
/// </summary>
/// <param name="runnerId">Name of the instance to create (used in log files)</param>
/// <returns>a <see cref="IVsTestConsoleWrapper" /> controlling the created instance.</returns>
public IVsTestConsoleWrapper BuildVsTestWrapper(string runnerId)
public IVsTestConsoleWrapper BuildVsTestWrapper(string runnerId, string controlVariable)
{
var vsTestConsole = _wrapperBuilder(DetermineConsoleParameters(runnerId));
var env = DetermineConsoleParameters(runnerId);
env.EnvironmentVariables["DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX"]="2";
// we define a per runner control variable to prevent conflict
env.EnvironmentVariables["STRYKER_CONTROL_VAR"] = controlVariable;
var vsTestConsole = _wrapperBuilder(env);
try
{
// Set roll forward on no candidate fx so vstest console can start on incompatible dotnet core runtimes
Environment.SetEnvironmentVariable("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX", "2");
vsTestConsole.StartSession();
vsTestConsole.InitializeExtensions(Enumerable.Empty<string>());
vsTestConsole.InitializeExtensions([]);
}
catch (Exception e)
{
Expand Down Expand Up @@ -188,7 +191,7 @@ public bool AddTestSource(string source, string frameworkVersion = null, string

private void DiscoverTestsInSources(string newSource, string frameworkVersion = null, string platform = null)
{
var wrapper = BuildVsTestWrapper("TestDiscoverer");
var wrapper = BuildVsTestWrapper("TestDiscoverer", "NOT_NEEDED");
var messages = new List<string>();
var handler = new DiscoveryEventHandler(messages);
var settings = GenerateRunSettingsForDiscovery(frameworkVersion, platform);
Expand Down Expand Up @@ -223,6 +226,13 @@ private void DiscoverTestsInSources(string newSource, string frameworkVersion =
Tests.RegisterTests(VsTests.Values.Select(t => t.Description));
}

internal void RegisterDiscoveredTest(VsTestDescription vsTestDescription)
{
VsTests[vsTestDescription.Id] = vsTestDescription;
Tests.RegisterTest(vsTestDescription.Description);
TestsPerSource[vsTestDescription.Case.Source].Add(vsTestDescription.Id);
}

private void DetectTestFrameworks(ICollection<VsTestDescription> tests)
{
if (tests == null)
Expand Down Expand Up @@ -262,7 +272,7 @@ private string GenerateCoreSettings(int maxCpu, string frameworkVersion, string
return
$@"
<MaxCpuCount>{Math.Max(0, maxCpu)}</MaxCpuCount>
{frameworkConfig}{platformConfig}{testCaseFilter} <InIsolation>true</InIsolation>
{frameworkConfig}{platformConfig}{testCaseFilter}
<DisableAppDomain>true</DisableAppDomain>";
}

Expand Down Expand Up @@ -309,4 +319,5 @@ public string GenerateRunSettings(int? timeout, bool forCoverage, Dictionary<int

return runSettings;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Stryker.Core.Mutants;
using Stryker.Core.TestRunners;

namespace Stryker.Core.TestRunners.VsTest;

public sealed class VsTestDescription
{
private readonly ICollection<TestResult> _initialResults = new List<TestResult>();
private readonly ICollection<TestResult> _initialResults = [];
private int _subCases;

public VsTestDescription(TestCase testCase)
Expand Down
25 changes: 16 additions & 9 deletions src/Stryker.Core/Stryker.Core/TestRunners/VsTest/VsTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public sealed class VsTestRunner : IDisposable
private const int MaxAttempts = 3;

private string RunnerId => $"Runner {_id}";
private string ControlVariableName => $"ACTIVE_MUTATION_{_id}";

public VsTestRunner(VsTestContextInformation context,
int id,
Expand All @@ -43,7 +44,7 @@ public VsTestRunner(VsTestContextInformation context,
_context = context;
_id = id;
_logger = logger ?? ApplicationLogging.LoggerFactory.CreateLogger<VsTestRunner>();
_vsTestConsole = _context.BuildVsTestWrapper(RunnerId);
_vsTestConsole = _context.BuildVsTestWrapper(RunnerId, ControlVariableName);
}

public TestRunResult InitialTest(IProjectAndTests project)
Expand All @@ -60,8 +61,7 @@ public TestRunResult InitialTest(IProjectAndTests project)
if (!_context.VsTests.ContainsKey(result.TestCase.Id))
{
var vsTestDescription = new VsTestDescription(result.TestCase);
_context.VsTests[result.TestCase.Id] = vsTestDescription;
_context.Tests.RegisterTest(vsTestDescription.Description);
_context.RegisterDiscoveredTest(vsTestDescription);
_logger.LogWarning(
"{RunnerId}: Initial test run encounter an unexpected test case ({DisplayName}), mutation tests may be inaccurate. Disable coverage analysis if you have doubts.",
RunnerId, result.TestCase.DisplayName);
Expand All @@ -84,7 +84,7 @@ public TestRunResult TestMultipleMutants(IProjectAndTests project, ITimeoutValue
if (testCases?.Count == 0)
{
return new TestRunResult(_context.VsTests.Values, TestGuidsList.NoTest(), TestGuidsList.NoTest(),
TestGuidsList.NoTest(), "Mutants are not covered by any test!", Enumerable.Empty<string>(),
TestGuidsList.NoTest(), "Mutants are not covered by any test!", [],
TimeSpan.Zero);
}

Expand Down Expand Up @@ -115,7 +115,7 @@ void HandleUpdate(IRunResults handler)
: new WrappedGuidsEnumeration(handlerTestResults.Select(t => t.TestCase.Id));
var failedTest = new WrappedGuidsEnumeration(handlerTestResults
.Where(tr => tr.Outcome == TestOutcome.Failed)
.Select(t => t.TestCase.Id));
.Select(t => t.TestCase.Id));
var timedOutTest = new WrappedGuidsEnumeration(handler.TestsInTimeout?.Select(t => t.Id));
var remainingMutants = update?.Invoke(mutants, failedTest, tests, timedOutTest);

Expand Down Expand Up @@ -248,8 +248,13 @@ public IRunResults RunCoverageSession(ITestGuids testsToRun, IProjectAndTests pr
}
var runSettings = _context.GenerateRunSettings(timeOut, forCoverage, mutantTestsMap,
projectAndTests.HelperNamespace, source.TargetFramework, source.TargetPlatform());

_logger.LogTrace("{RunnerId}: testing assembly {source}.", RunnerId, source);
var activeId = -1;
if (mutantTestsMap!=null && mutantTestsMap.Count==1)
{
activeId = mutantTestsMap.Keys.First();
}
Environment.SetEnvironmentVariable(ControlVariableName, activeId.ToString());
RunVsTest(tests, source.GetAssemblyPath(), runSettings, options, timeOut, runEventHandler);

if (_currentSessionCancelled)
Expand Down Expand Up @@ -281,13 +286,15 @@ private void RunVsTest(ITestGuids tests, string source, string runSettings, Test
{
if (tests.IsEveryTest)
{
_vsTestConsole.RunTestsWithCustomTestHost(new[] { source }, runSettings, options, eventHandler,
_vsTestConsole.RunTestsWithCustomTestHost([source], runSettings, options, eventHandler,
strykerVsTestHostLauncher);
}
else
{
var actualTestCases = tests.GetGuids().Select(t => _context.VsTests[t].Case).ToList();
var testCases = actualTestCases;
_vsTestConsole.RunTestsWithCustomTestHost(
tests.GetGuids().Select(t => _context.VsTests[t].Case),
testCases,
runSettings, options, eventHandler, strykerVsTestHostLauncher);
}
});
Expand Down Expand Up @@ -371,7 +378,7 @@ private void PrepareVsTestConsole()
}
}

_vsTestConsole = _context.BuildVsTestWrapper($"{RunnerId}-{_instanceCount}");
_vsTestConsole = _context.BuildVsTestWrapper($"{RunnerId}-{_instanceCount}", ControlVariableName);
}

#region IDisposable Support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ private bool ConvertSingleResult(TestResult testResult, ISet<Guid> seenTestCases
// ==> we need it to use this test against every mutation
_logger.LogDebug("VsTestRunner: No coverage data for {TestCase}.", testResult.TestCase.DisplayName);
seenTestCases.Add(testDescription.Id);
coverageRunResult = new CoverageRunResult(testCaseId, CoverageConfidence.Dubious, Enumerable.Empty<int>(),
Enumerable.Empty<int>(), Enumerable.Empty<int>());
coverageRunResult = new CoverageRunResult(testCaseId, CoverageConfidence.Dubious, [], [], []);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public class CoverageCollector : InProcDataCollection
private IDataCollectionSink _dataSink;
private bool _coverageOn;
private int _activeMutation = -1;
private bool _reportFailure;

private Action<string> _logger;
private readonly IDictionary<string, int> _mutantTestedBy = new Dictionary<string, int>();
Expand Down Expand Up @@ -94,7 +93,6 @@ public void Initialize(IDataCollectionSink dataCollectionSink)
{
_dataSink = dataCollectionSink;
_throwingListener = new ThrowingListener();
SetLogger(Console.WriteLine);
}

public void SetLogger(Action<string> logger) => _logger = logger;
Expand Down Expand Up @@ -276,11 +274,7 @@ private void PublishCoverageData(DataCollectionContext dataCollectionContext)
{
// no test covered any mutations, so the controller was never properly initialized
_dataSink.SendData(dataCollectionContext, PropertyName, ";");
if (!_reportFailure)
{
_dataSink.SendData(dataCollectionContext, CoverageLog, $"Did not find type {_controlClassName}. Mutated assembly may not be covered by any test.");
_reportFailure = true;
}
_dataSink.SendData(dataCollectionContext, CoverageLog, $"Test {dataCollectionContext.TestCase.DisplayName} endend. No mutant covered so far.");
return;
}

Expand Down
Loading