diff --git a/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ConnectorSampleTests.cs b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ConnectorSampleTests.cs new file mode 100644 index 00000000..e2a36519 --- /dev/null +++ b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ConnectorSampleTests.cs @@ -0,0 +1,15 @@ +namespace Reqnroll.VisualStudio.ReqnrollConnector.Tests; + +public class ConnectorSampleTests : SampleProjectTestBase +{ + public ConnectorSampleTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + + [Theory] + [MemberData(nameof(GetProjectsForRepository), @"Tests\VsExtConnectorTestSamples", "")] + public void All(string testCase, string projectFile, string repositoryDirectory) + { + ValidateProject(testCase, projectFile, repositoryDirectory); + } +} \ No newline at end of file diff --git a/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ExternalSampleTests.cs b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ExternalSampleTests.cs index 5f21df41..cd5e3502 100644 --- a/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ExternalSampleTests.cs +++ b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/ExternalSampleTests.cs @@ -1,23 +1,13 @@ -using System.Text.RegularExpressions; -using Reqnroll.VisualStudio.ReqnrollConnector.Models; - namespace Reqnroll.VisualStudio.ReqnrollConnector.Tests; /// /// This test class validates whether connector can work with various sample projects from external repositories. /// It clones/pulls the repository to a temp folder, builds each Reqnroll project, and runs discovery using the connector. /// -public class ExternalSampleTests +public class ExternalSampleTests : SampleProjectTestBase { - private const string ConnectorConfiguration = "Debug"; - private const string TargetFrameworkToBeUsedForNet4Projects = "net10.0"; - private static readonly string LatestReqnrollVersion = NuGetPackageVersionDetector.DetectLatestPackage("Reqnroll", Console.WriteLine) ?? "1.0.0"; - - protected readonly ITestOutputHelper TestOutputHelper; - - public ExternalSampleTests(ITestOutputHelper testOutputHelper) + public ExternalSampleTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { - TestOutputHelper = testOutputHelper; } [Theory] @@ -44,283 +34,4 @@ public void ExploratoryTestProjects(string testCase, string projectFile, string //{ // ValidateProject(testCase, projectFile, repositoryDirectory); //} - - protected void ValidateProject(string testCase, string projectFile, string repositoryDirectory) - { - TestOutputHelper.WriteLine("Running Test Case: " + testCase); - var fullProjectPath = Path.Combine(repositoryDirectory, projectFile); - BuildAndInspectProject(repositoryDirectory, fullProjectPath); - } - - private void BuildAndInspectProject(string repositoryDirectory, string projectFile) - { - var projectDirectory = Path.GetDirectoryName(projectFile)!; - bool isPackagesStyleProject = File.Exists(Path.Combine(projectDirectory, "packages.config")); - - if (isPackagesStyleProject) - { - TestOutputHelper.WriteLine($"Restore packages.config dependencies for {projectFile}"); - var solutionFolder = Path.GetFullPath(Path.Combine(projectDirectory, "..")); - string nugetPath = Path.Combine(solutionFolder, "nuget.exe"); - if (!File.Exists(nugetPath)) - { - RunProcess(solutionFolder, "curl", "-o nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"); - } - RunProcess(solutionFolder, nugetPath, "restore"); - } - - TestOutputHelper.WriteLine($"Building {projectFile}"); - RunProcess(repositoryDirectory, "dotnet", $"build \"{projectFile}\""); - - var projectName = Path.GetFileNameWithoutExtension(projectFile); - var configPath = GetConfigFile(projectDirectory); - TestOutputHelper.WriteLine(configPath is null ? "No config file found" : $"Config: {configPath}"); - - var debugDirectory = Path.Combine(projectDirectory, "bin", "Debug"); - Directory.Exists(debugDirectory).Should().BeTrue($"Build output folder not found for {projectFile}"); - - var assembliesToCheck = isPackagesStyleProject - ? GetAssembliesToCheckPackagesStyle() - : GetAssembliesToCheckSdkStyle(); - - (string targetFramework, string? assemblyPath)[] GetAssembliesToCheckSdkStyle() - { - var targetFrameworkDirectories = Directory.EnumerateDirectories(debugDirectory).ToArray(); - targetFrameworkDirectories.Should().NotBeEmpty($"No target frameworks found under {debugDirectory}"); - return targetFrameworkDirectories.Select(tfmDirectory => - { - var targetFramework = Path.GetFileName(tfmDirectory); - var assemblyPath = FindAssembly(tfmDirectory, projectName); - return (targetFramework, assemblyPath); - }).ToArray(); - } - - (string targetFramework, string? assemblyPath)[] GetAssembliesToCheckPackagesStyle() - { - var projectFileContent = File.ReadAllText(projectFile); - var targetFrameworkMatch = Regex.Match(projectFileContent, "v(?\\d+\\.\\d+.\\d+)"); - targetFrameworkMatch.Success.Should().BeTrue($"Cannot determine target framework from {projectFile}"); - var targetFramework = "net" + targetFrameworkMatch.Groups["tfm"].Value.Replace(".", string.Empty); - var assemblyPath = FindAssembly(debugDirectory, projectName); - return new[] { (targetFramework, assemblyPath) }; - } - - foreach (var (targetFramework, assemblyPath) in assembliesToCheck) - { - assemblyPath.Should().NotBeNull($"Cannot find assembly {projectName}.dll under {Path.GetDirectoryName(assemblyPath)}"); - - TestOutputHelper.WriteLine($"{targetFramework}: {assemblyPath}"); - CheckConnector(targetFramework, assemblyPath, configPath); - } - - static string? FindAssembly(string tfmDirectory, string projectName) - { - var candidate = Path.Combine(tfmDirectory, $"{projectName}.dll"); - if (File.Exists(candidate)) - return candidate; - - return Directory.EnumerateFiles(tfmDirectory, $"{projectName}.dll", SearchOption.AllDirectories).FirstOrDefault(); - } - } - - private void CheckConnector(string targetFramework, string assemblyPath, string? configPath) - { - var connectorPath = GetConnectorPath(targetFramework); - File.Exists(connectorPath).Should().BeTrue($"Connector not found: {connectorPath}"); - - var configArgument = configPath ?? string.Empty; - var args = $"exec \"{connectorPath}\" discovery \"{assemblyPath}\" \"{configArgument}\""; - var result = RunProcess(Path.GetDirectoryName(assemblyPath)!, "dotnet", args); - - if (!string.IsNullOrEmpty(result.StdError)) - { - TestOutputHelper.WriteLine("Connector error output:"); - TestOutputHelper.WriteLine(result.StdError); - } - - var discoveryResult = ExtractDiscoveryResult(result.StdOutput); - - if (discoveryResult.IsFailed) - { - TestOutputHelper.WriteLine("Connector output:"); - TestOutputHelper.WriteLine(result.StdOutput); - } - - discoveryResult.ConnectorType.Should().NotBeEmpty(); - discoveryResult.ReqnrollVersion.Should().NotBeEmpty(); - discoveryResult.ErrorMessage.Should().BeNullOrEmpty(); - discoveryResult.StepDefinitions.Should().NotBeEmpty(); - discoveryResult.SourceFiles.Should().NotBeEmpty(); - - discoveryResult.AnalyticsProperties.Should().NotBeNull(); - discoveryResult.AnalyticsProperties.Should().ContainKeys( - "Connector", - "ImageRuntimeVersion", - "TargetFramework", - "SFFile", - "SFFileVersion", - "SFProductVersion", - "TypeNames", - "SourcePaths", - "StepDefinitions", - "Hooks"); - - discoveryResult.Warnings.Should().BeNullOrEmpty(); - } - - public static TheoryData GetProjectsForRepository(string repositoryUrl, string excludedFolders) - { - var theoryData = new TheoryData(); - - var repositoryDirectory = PrepareRepository(repositoryUrl); - var repositoryName = GetRepositoryNameFromUrl(repositoryUrl); - var excludeFoldersList = string.IsNullOrEmpty(excludedFolders) ? Array.Empty() : excludedFolders.Split(';').Where(folder => !string.IsNullOrWhiteSpace(folder)).ToArray(); - - var projectsWithFeatures = Directory - .EnumerateFiles(repositoryDirectory, "*.*proj", SearchOption.AllDirectories) - .Where(projectFile => - { - var projectDirectory = Path.GetDirectoryName(projectFile)!; - if (excludeFoldersList.Any(exclude => projectDirectory.Contains(exclude))) - return false; - return Directory.EnumerateFiles(projectDirectory, "*.feature", SearchOption.AllDirectories).Any(); - }) - .ToArray(); - - foreach (var projectFile in projectsWithFeatures) - { - var testDisplayName = $"{Path.GetFileNameWithoutExtension(projectFile)} in {repositoryName}"; - var relativeProjectFile = Path.GetRelativePath(repositoryDirectory, projectFile); - theoryData.Add(testDisplayName, relativeProjectFile, repositoryDirectory); - Console.WriteLine($" Test added: {testDisplayName} ({relativeProjectFile})"); - } - - Console.WriteLine($"Total tests added: {projectsWithFeatures.Length}"); - - return theoryData; - } - - internal static string PrepareRepository(string repositoryUrl) - { - if (!repositoryUrl.StartsWith("https://")) - { - // assume folder (absolute or relative to the solution root) - var localRepositoryDirectory = - Path.GetFullPath(Path.Combine(GetSolutionRoot(), repositoryUrl)); - Directory.Exists(localRepositoryDirectory).Should().BeTrue($"Repository folder not found: {localRepositoryDirectory}"); - return localRepositoryDirectory; - } - - var repositoryName = GetRepositoryNameFromUrl(repositoryUrl); - var tempRootDirectory = Path.Combine(Path.GetTempPath(), "ReqnrollSamples"); - Directory.CreateDirectory(tempRootDirectory); - - var repositoryDirectory = Path.Combine(tempRootDirectory, repositoryName); - - if (!Directory.Exists(repositoryDirectory)) - { - RunProcessStatic(tempRootDirectory, "git", $"clone {repositoryUrl} \"{repositoryDirectory}\""); - } - else - { - RunProcessStatic(repositoryDirectory, "git", "reset --hard"); - RunProcessStatic(repositoryDirectory, "git", "clean -fd"); - RunProcessStatic(repositoryDirectory, "git", "pull"); - } - - var updateScript = Path.Combine(repositoryDirectory, "update-versions.ps1"); - if (File.Exists(updateScript)) - { - RunProcessStatic(repositoryDirectory, "powershell", $"-ExecutionPolicy Bypass -File \"{updateScript}\" {LatestReqnrollVersion}"); - } - - return repositoryDirectory; - } - - internal static string GetRepositoryNameFromUrl(string repositoryUrl) - { - if (!repositoryUrl.StartsWith("https://")) - { - return repositoryUrl.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Last(); - } - - var uri = new Uri(repositoryUrl); - var lastSegment = uri.Segments.Last().Trim('/'); - if (lastSegment.EndsWith(".git", StringComparison.OrdinalIgnoreCase)) - lastSegment = lastSegment[..^4]; - - return lastSegment; - } - - private static string GetConnectorPath(string targetFramework) - { - if (targetFramework.StartsWith("net4")) - targetFramework = TargetFrameworkToBeUsedForNet4Projects; - - var connectorDir = Path.Combine(GetSolutionRoot(), "Connectors", "bin", ConnectorConfiguration, $"Reqnroll-Generic-{targetFramework}"); - return Path.Combine(connectorDir, "reqnroll-vs.dll"); - } - - private static string GetSolutionRoot() - { - var assemblyLocation = Assembly.GetExecutingAssembly().Location; - var testAssemblyDir = Path.GetDirectoryName(assemblyLocation)!; - var solutionRoot = Path.GetFullPath(Path.Combine(testAssemblyDir, "..", "..", "..", "..", "..", "..")); - return solutionRoot; - } - - private static DiscoveryResult ExtractDiscoveryResult(string stdOutput) - { - var json = ExtractJson(stdOutput); - var deserialized = TestBase.DeserializeObject(json); - deserialized.Should().NotBeNull($"Cannot deserialize discovery result: {json}"); - return deserialized; - } - - private static string ExtractJson(string stdOutput) - { - const string startMarker = ">>>>>>>>>>"; - const string endMarker = "<<<<<<<<<<"; - var start = stdOutput.IndexOf(startMarker, StringComparison.Ordinal); - if (start >= 0) - { - start += startMarker.Length; - var end = stdOutput.IndexOf(endMarker, start, StringComparison.Ordinal); - if (end >= 0) - return stdOutput.Substring(start, end - start).Trim(); - } - - return stdOutput.Trim(); - } - - private static string? GetConfigFile(string projectDirectory) - { - var reqnrollConfig = Path.Combine(projectDirectory, "reqnroll.json"); - if (File.Exists(reqnrollConfig)) - return reqnrollConfig; - - var appConfig = Path.Combine(projectDirectory, "App.config"); - return File.Exists(appConfig) ? appConfig : null; - } - - private static ProcessResult RunProcessInternal(string workingDirectory, string executablePath, string arguments, Action logResult) - { - logResult($"{workingDirectory}> {executablePath} {arguments}"); - var result = new ProcessHelper().RunProcess(new ProcessStartInfoEx(workingDirectory, executablePath, arguments)); - - if (!string.IsNullOrWhiteSpace(result.StdOutput) && result.ExitCode != 0) - logResult(result.StdOutput); - - if (!string.IsNullOrWhiteSpace(result.StdError)) - logResult(result.StdError); - - result.ExitCode.Should().Be(0, $"command failed: {executablePath} {arguments}"); - return result; - } - - private ProcessResult RunProcess(string workingDirectory, string executablePath, string arguments) - => RunProcessInternal(workingDirectory, executablePath, arguments, TestOutputHelper.WriteLine); - - private static void RunProcessStatic(string workingDirectory, string executablePath, string arguments) - => RunProcessInternal(workingDirectory, executablePath, arguments, Console.WriteLine); } diff --git a/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/SampleProjectTestBase.cs b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/SampleProjectTestBase.cs new file mode 100644 index 00000000..cad052a2 --- /dev/null +++ b/Tests/Connector/Reqnroll.VisualStudio.ReqnrollConnector.Tests/SampleProjectTestBase.cs @@ -0,0 +1,297 @@ +using Reqnroll.VisualStudio.ReqnrollConnector.Models; +using System.Text.RegularExpressions; + +namespace Reqnroll.VisualStudio.ReqnrollConnector.Tests; + +public abstract class SampleProjectTestBase +{ + private const string ConnectorConfiguration = "Debug"; + private const string TargetFrameworkToBeUsedForNet4Projects = "net10.0"; + private static readonly string LatestReqnrollVersion = NuGetPackageVersionDetector.DetectLatestPackage("Reqnroll", Console.WriteLine) ?? "1.0.0"; + + protected readonly ITestOutputHelper TestOutputHelper; + + protected SampleProjectTestBase(ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + } + + protected void ValidateProject(string testCase, string projectFile, string repositoryDirectory) + { + TestOutputHelper.WriteLine("Running Test Case: " + testCase); + var fullProjectPath = Path.Combine(repositoryDirectory, projectFile); + BuildAndInspectProject(repositoryDirectory, fullProjectPath); + } + + private void BuildAndInspectProject(string repositoryDirectory, string projectFile) + { + var projectDirectory = Path.GetDirectoryName(projectFile)!; + bool isPackagesStyleProject = File.Exists(Path.Combine(projectDirectory, "packages.config")); + + if (isPackagesStyleProject) + { + TestOutputHelper.WriteLine($"Restore packages.config dependencies for {projectFile}"); + var solutionFolder = Path.GetFullPath(Path.Combine(projectDirectory, "..")); + string nugetPath = Path.Combine(solutionFolder, "nuget.exe"); + if (!File.Exists(nugetPath)) + { + RunProcess(solutionFolder, "curl", "-o nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"); + } + RunProcess(solutionFolder, nugetPath, "restore"); + } + + TestOutputHelper.WriteLine($"Building {projectFile}"); + RunProcess(repositoryDirectory, "dotnet", $"build \"{projectFile}\""); + + var projectName = Path.GetFileNameWithoutExtension(projectFile); + var configPath = GetConfigFile(projectDirectory); + TestOutputHelper.WriteLine(configPath is null ? "No config file found" : $"Config: {configPath}"); + + var debugDirectory = Path.Combine(projectDirectory, "bin", "Debug"); + Directory.Exists(debugDirectory).Should().BeTrue($"Build output folder not found for {projectFile}"); + + var assembliesToCheck = isPackagesStyleProject + ? GetAssembliesToCheckPackagesStyle() + : GetAssembliesToCheckSdkStyle(); + + (string targetFramework, string? assemblyPath)[] GetAssembliesToCheckSdkStyle() + { + var targetFrameworkDirectories = Directory.EnumerateDirectories(debugDirectory).ToArray(); + targetFrameworkDirectories.Should().NotBeEmpty($"No target frameworks found under {debugDirectory}"); + return targetFrameworkDirectories.Select(tfmDirectory => + { + var targetFramework = Path.GetFileName(tfmDirectory); + var assemblyPath = FindAssembly(tfmDirectory, projectName); + return (targetFramework, assemblyPath); + }).ToArray(); + } + + (string targetFramework, string? assemblyPath)[] GetAssembliesToCheckPackagesStyle() + { + var projectFileContent = File.ReadAllText(projectFile); + var targetFrameworkMatch = Regex.Match(projectFileContent, "v(?\\d+\\.\\d+.\\d+)"); + targetFrameworkMatch.Success.Should().BeTrue($"Cannot determine target framework from {projectFile}"); + var targetFramework = "net" + targetFrameworkMatch.Groups["tfm"].Value.Replace(".", string.Empty); + var assemblyPath = FindAssembly(debugDirectory, projectName); + return new[] { (targetFramework, assemblyPath) }; + } + + foreach (var (targetFramework, assemblyPath) in assembliesToCheck) + { + assemblyPath.Should().NotBeNull($"Cannot find assembly {projectName}.dll under {Path.GetDirectoryName(assemblyPath)}"); + + TestOutputHelper.WriteLine($"{targetFramework}: {assemblyPath}"); + CheckConnector(targetFramework, assemblyPath, configPath); + } + + static string? FindAssembly(string tfmDirectory, string projectName) + { + var candidate = Path.Combine(tfmDirectory, $"{projectName}.dll"); + if (File.Exists(candidate)) + return candidate; + + return Directory.EnumerateFiles(tfmDirectory, $"{projectName}.dll", SearchOption.AllDirectories).FirstOrDefault(); + } + } + + private void CheckConnector(string targetFramework, string assemblyPath, string? configPath) + { + var connectorPath = GetConnectorPath(targetFramework); + File.Exists(connectorPath).Should().BeTrue($"Connector not found: {connectorPath}"); + + var configArgument = configPath ?? string.Empty; + var args = $"exec \"{connectorPath}\" discovery \"{assemblyPath}\" \"{configArgument}\""; + var result = RunProcess(Path.GetDirectoryName(assemblyPath)!, "dotnet", args); + + if (!string.IsNullOrEmpty(result.StdError)) + { + TestOutputHelper.WriteLine("Connector error output:"); + TestOutputHelper.WriteLine(result.StdError); + } + + var discoveryResult = ExtractDiscoveryResult(result.StdOutput); + + if (discoveryResult.IsFailed) + { + TestOutputHelper.WriteLine("Connector output:"); + TestOutputHelper.WriteLine(result.StdOutput); + } + + discoveryResult.ConnectorType.Should().NotBeEmpty(); + discoveryResult.ReqnrollVersion.Should().NotBeEmpty(); + discoveryResult.ErrorMessage.Should().BeNullOrEmpty(); + discoveryResult.StepDefinitions.Should().NotBeEmpty(); + discoveryResult.SourceFiles.Should().NotBeEmpty(); + + discoveryResult.AnalyticsProperties.Should().NotBeNull(); + discoveryResult.AnalyticsProperties.Should().ContainKeys( + "Connector", + "ImageRuntimeVersion", + "TargetFramework", + "SFFile", + "SFFileVersion", + "SFProductVersion", + "TypeNames", + "SourcePaths", + "StepDefinitions", + "Hooks"); + + discoveryResult.Warnings.Should().BeNullOrEmpty(); + } + + public static TheoryData GetProjectsForRepository(string repositoryUrl, string excludedFolders) + { + var theoryData = new TheoryData(); + + var repositoryDirectory = PrepareRepository(repositoryUrl); + var repositoryName = GetRepositoryNameFromUrl(repositoryUrl); + var excludeFoldersList = string.IsNullOrEmpty(excludedFolders) ? Array.Empty() : excludedFolders.Split(';').Where(folder => !string.IsNullOrWhiteSpace(folder)).ToArray(); + + var projectsWithFeatures = Directory + .EnumerateFiles(repositoryDirectory, "*.*proj", SearchOption.AllDirectories) + .Where(projectFile => + { + var projectDirectory = Path.GetDirectoryName(projectFile)!; + if (excludeFoldersList.Any(exclude => projectDirectory.Contains(exclude))) + return false; + return Directory.EnumerateFiles(projectDirectory, "*.feature", SearchOption.AllDirectories).Any(); + }) + .ToArray(); + + foreach (var projectFile in projectsWithFeatures) + { + var testDisplayName = $"{Path.GetFileNameWithoutExtension(projectFile)} in {repositoryName}"; + var relativeProjectFile = Path.GetRelativePath(repositoryDirectory, projectFile); + theoryData.Add(testDisplayName, relativeProjectFile, repositoryDirectory); + Console.WriteLine($" Test added: {testDisplayName} ({relativeProjectFile})"); + } + + Console.WriteLine($"Total tests added: {projectsWithFeatures.Length}"); + + return theoryData; + } + + internal static string PrepareRepository(string repositoryUrl) + { + if (!repositoryUrl.StartsWith("https://")) + { + // assume folder (absolute or relative to the solution root) + var localRepositoryDirectory = + Path.GetFullPath(Path.Combine(GetSolutionRoot(), repositoryUrl)); + Directory.Exists(localRepositoryDirectory).Should().BeTrue($"Repository folder not found: {localRepositoryDirectory}"); + return localRepositoryDirectory; + } + + var repositoryName = GetRepositoryNameFromUrl(repositoryUrl); + var tempRootDirectory = Path.Combine(Path.GetTempPath(), "ReqnrollSamples"); + Directory.CreateDirectory(tempRootDirectory); + + var repositoryDirectory = Path.Combine(tempRootDirectory, repositoryName); + + if (!Directory.Exists(repositoryDirectory)) + { + RunProcessStatic(tempRootDirectory, "git", $"clone {repositoryUrl} \"{repositoryDirectory}\""); + } + else + { + RunProcessStatic(repositoryDirectory, "git", "reset --hard"); + RunProcessStatic(repositoryDirectory, "git", "clean -fd"); + RunProcessStatic(repositoryDirectory, "git", "pull"); + } + + var updateScript = Path.Combine(repositoryDirectory, "update-versions.ps1"); + if (File.Exists(updateScript)) + { + RunProcessStatic(repositoryDirectory, "powershell", $"-ExecutionPolicy Bypass -File \"{updateScript}\" {LatestReqnrollVersion}"); + } + + return repositoryDirectory; + } + + internal static string GetRepositoryNameFromUrl(string repositoryUrl) + { + if (!repositoryUrl.StartsWith("https://")) + { + return repositoryUrl.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Last(); + } + + var uri = new Uri(repositoryUrl); + var lastSegment = uri.Segments.Last().Trim('/'); + if (lastSegment.EndsWith(".git", StringComparison.OrdinalIgnoreCase)) + lastSegment = lastSegment[..^4]; + + return lastSegment; + } + + private static string GetConnectorPath(string targetFramework) + { + if (targetFramework.StartsWith("net4")) + targetFramework = TargetFrameworkToBeUsedForNet4Projects; + + var connectorDir = Path.Combine(GetSolutionRoot(), "Connectors", "bin", ConnectorConfiguration, $"Reqnroll-Generic-{targetFramework}"); + return Path.Combine(connectorDir, "reqnroll-vs.dll"); + } + + private static string GetSolutionRoot() + { + var assemblyLocation = Assembly.GetExecutingAssembly().Location; + var testAssemblyDir = Path.GetDirectoryName(assemblyLocation)!; + var solutionRoot = Path.GetFullPath(Path.Combine(testAssemblyDir, "..", "..", "..", "..", "..", "..")); + return solutionRoot; + } + + private static DiscoveryResult ExtractDiscoveryResult(string stdOutput) + { + var json = ExtractJson(stdOutput); + var deserialized = TestBase.DeserializeObject(json); + deserialized.Should().NotBeNull($"Cannot deserialize discovery result: {json}"); + return deserialized; + } + + private static string ExtractJson(string stdOutput) + { + const string startMarker = ">>>>>>>>>>"; + const string endMarker = "<<<<<<<<<<"; + var start = stdOutput.IndexOf(startMarker, StringComparison.Ordinal); + if (start >= 0) + { + start += startMarker.Length; + var end = stdOutput.IndexOf(endMarker, start, StringComparison.Ordinal); + if (end >= 0) + return stdOutput.Substring(start, end - start).Trim(); + } + + return stdOutput.Trim(); + } + + private static string? GetConfigFile(string projectDirectory) + { + var reqnrollConfig = Path.Combine(projectDirectory, "reqnroll.json"); + if (File.Exists(reqnrollConfig)) + return reqnrollConfig; + + var appConfig = Path.Combine(projectDirectory, "App.config"); + return File.Exists(appConfig) ? appConfig : null; + } + + private static ProcessResult RunProcessInternal(string workingDirectory, string executablePath, string arguments, Action logResult) + { + logResult($"{workingDirectory}> {executablePath} {arguments}"); + var result = new ProcessHelper().RunProcess(new ProcessStartInfoEx(workingDirectory, executablePath, arguments)); + + if (!string.IsNullOrWhiteSpace(result.StdOutput) && result.ExitCode != 0) + logResult(result.StdOutput); + + if (!string.IsNullOrWhiteSpace(result.StdError)) + logResult(result.StdError); + + result.ExitCode.Should().Be(0, $"command failed: {executablePath} {arguments}"); + return result; + } + + private ProcessResult RunProcess(string workingDirectory, string executablePath, string arguments) + => RunProcessInternal(workingDirectory, executablePath, arguments, TestOutputHelper.WriteLine); + + private static void RunProcessStatic(string workingDirectory, string executablePath, string arguments) + => RunProcessInternal(workingDirectory, executablePath, arguments, Console.WriteLine); +} \ No newline at end of file diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Calculator.cs b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Calculator.cs new file mode 100644 index 00000000..5795b6f0 --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Calculator.cs @@ -0,0 +1,36 @@ +namespace ReqnrollCalculator; + +public class Calculator +{ + private readonly Stack _numbers = new(); + + public void Enter(int number) + { + _numbers.Push(number); + } + + public void Reset() + { + _numbers.Clear(); + } + + public int GetResult() + { + return _numbers.Peek(); + } + + public string GetResultMessage() + { + return $"The result is {GetResult()}."; + } + + public void Add() + { + _numbers.Push(_numbers.Pop() + _numbers.Pop()); + } + + public void Multiply() + { + _numbers.Push(_numbers.Pop() * _numbers.Pop()); + } +} \ No newline at end of file diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Addition.feature b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Addition.feature new file mode 100644 index 00000000..39a31814 --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Addition.feature @@ -0,0 +1,53 @@ +Feature: Addition + +The calculator functions related to addition, +that is denoted with the '+' operator. + +@core +Rule: Positive numbers can be added + +Scenario: Add two numbers + This is the most common use-case: + people often sum two numbers. + + Given the first number is 50 + And the second number is 70 + When the two numbers are added + Then the result should be 120 + +@commutativity +Scenario: Addition is commutative + Given the first number is 70 + And the second number is 50 + When the two numbers are added + Then the result should be 120 + +Rule: Non-positive numbers can be added + +@edgeCase +Scenario Outline: Add zeros + Given the first number is + And the second number is + When the two numbers are added + Then the result should be +Examples: + | first | second | result | + | 0 | 0 | 0 | + | 0 | 42 | 42 | +@testOnly +Examples: + | first | second | result | + | 42 | 0 | 42 | + +Scenario: Add negatives + Given the entered numbers are + | number | + | -5 | + | -7 | + When the two numbers are added + Then the result should be -12 + And the text message should be + """ + The result is -12. + """ + diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Multiplication.feature b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Multiplication.feature new file mode 100644 index 00000000..c3f7dd01 --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/Features/Multiplication.feature @@ -0,0 +1,20 @@ +Feature: Multiplication + +The calculator functions related to multiplication + +@core +Rule: Numbers can be multiplied + +Scenario: Add two numbers + Given the first number is 5 + And the second number is 7 + When the two numbers are multiplied + Then the result should be 35 + +Rule: Can multiply with zero + +Scenario: Multily with zero (wrong) + Given the first number is 5 + And the second number is 0 + When the two numbers are multiplied + Then the result should be 1 diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/ImplicitUsings.cs b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/ImplicitUsings.cs new file mode 100644 index 00000000..d220047a --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/ImplicitUsings.cs @@ -0,0 +1,2 @@ +global using AwesomeAssertions; +global using Reqnroll; diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/StepDefinitions/CalculatorStepDefinitions.cs b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/StepDefinitions/CalculatorStepDefinitions.cs new file mode 100644 index 00000000..98bbeffd --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/StepDefinitions/CalculatorStepDefinitions.cs @@ -0,0 +1,55 @@ +namespace ReqnrollCalculator.Specs.StepDefinitions; + +[Binding] +public sealed class CalculatorStepDefinitions +{ + private readonly Calculator _calculator = new(); + + [Given("the first number is {int}")] + public void GivenTheFirstNumberIs(int number) + { + _calculator.Reset(); + _calculator.Enter(number); + } + + [Given("the second number is {int}")] + public void GivenTheSecondNumberIs(int number) + { + _calculator.Enter(number); + } + + [Given("the entered numbers are")] + public void GivenTheEnteredNumbersAre(DataTable dataTable) + { + _calculator.Reset(); + foreach (var row in dataTable.Rows) + { + _calculator.Enter(int.Parse(row[0])); + } + } + + [When("the two numbers are added")] + public void WhenTheTwoNumbersAreAdded() + { + _calculator.Add(); + } + + [When("the two numbers are multiplied")] + public void WhenTheTwoNumbersAreMultiplied() + { + _calculator.Multiply(); + } + + [Then("the result should be {int}")] + public void ThenTheResultShouldBe(int result) + { + _calculator.GetResult().Should().Be(result); + } + + [Then("the text message should be")] + public void ThenTheTextMessageShouldBe(string expectedMessage) + { + _calculator.GetResultMessage().Should().Be(expectedMessage); + } + +} diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/VsExtConnectorTestSamples.ReqnrollCalculator.Tests.csproj b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/VsExtConnectorTestSamples.ReqnrollCalculator.Tests.csproj new file mode 100644 index 00000000..7aded29d --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/VsExtConnectorTestSamples.ReqnrollCalculator.Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + true + MSTEST0001 + + + + + + + + + + + + + + + + diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/reqnroll.json b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/reqnroll.json new file mode 100644 index 00000000..5168db9f --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.ReqnrollCalculator.Tests/reqnroll.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json" +} diff --git a/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.slnx b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.slnx new file mode 100644 index 00000000..3c1beb5f --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/VsExtConnectorTestSamples.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/Tests/VsExtConnectorTestSamples/update-versions.ps1 b/Tests/VsExtConnectorTestSamples/update-versions.ps1 new file mode 100644 index 00000000..f9056bd4 --- /dev/null +++ b/Tests/VsExtConnectorTestSamples/update-versions.ps1 @@ -0,0 +1,34 @@ +param( + [string] $version +) + +function Set-Versions($content, $versionToSet) { + + + $newContent = [regex]::replace($content,'', { + param($m) + $m.Value.Replace($m.Groups["version"].Value, $versionToSet) + }) + return $newContent +} + +if (-not $version) { + Write-Error "Error. The version was not specified." + Write-Error "Usage:" + Write-Error " update-versions.ps1 [version]" + Exit +} + +$projectFiles = Get-ChildItem -Path $PSScriptRoot -File -Recurse -Filter '*.*proj' +$changedCount = 0 +foreach ($path in $projectFiles){ + $fileContent = Get-Content -LiteralPath $path.FullName -Raw + $newFileContent = Set-Versions $fileContent $version + if ($newFileContent -ne $fileContent){ + Write-Host "Updating $($path.Name)" + Set-Content -Path $path.FullName -Value $newFileContent -NoNewline -Encoding utf8 + $changedCount++ + } +} + +Write-Host "Updated $changedCount files."