From 0a5a00eb3347daf8ec39ec820c04899ee7ae5bfe Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Sun, 6 Jul 2025 22:26:48 -0700 Subject: [PATCH 1/4] Update analyzers and add Meziantou.Analyzer Update analyzers and add Meziantou.Analyzer https://www.nuget.org/packages/Meziantou.Analyzer/ --- src/Directory.Packages.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index a2ac10c..e78f8e2 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -28,8 +28,9 @@ + - + From 29800d0c4265077346770f8de2987857a6448d93 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Mon, 7 Jul 2025 01:19:03 -0700 Subject: [PATCH 2/4] Refactor build process to use dirs.proj for project organization - Updated GitHub Actions workflow to build and test using dirs.proj instead of solution files. - Introduced Directory.Build.props and Directory.Packages.props for centralized package management and build properties. - Added global.json to specify SDK version and MSBuild SDKs. - Removed legacy solution files (.sln) in favor of a more efficient project structure. - Enhanced documentation to reflect the new project organization and provide instructions for generating solution files if needed. - Implemented StringComparer.Ordinal in various dictionary initializations for consistency and performance. - Updated integration tests to align with the new project structure and ensure compatibility across different environments. --- .editorconfig | 154 ++++++++++++++++++ .github/workflows/build.yml | 8 +- ...ctory.Build.props => Directory.Build.props | 27 +-- ...Packages.props => Directory.Packages.props | 75 +++++---- README.md | 22 +++ dirs.proj | 9 + global.json | 9 + src/.editorconfig | 21 --- src/AggregateConfigBuildTask.sln | 38 ----- src/Task/AggregateConfig.cs | 5 +- .../FileHandlers/ArmParametersFileHandler.cs | 8 +- src/Task/FileHandlers/FileHandlerFactory.cs | 4 +- .../{FileTypeEnum.cs => FileType.cs} | 66 ++++---- src/Task/FileHandlers/JsonFileHandler.cs | 2 +- src/Task/FileHandlers/YamlFileHandler.cs | 3 +- src/Task/JsonHelper.cs | 7 +- src/Task/Logger/QuietTaskLogger.cs | 2 +- src/Task/Logger/TaskLogger.cs | 3 +- src/Task/ObjectManager.cs | 19 ++- src/Task/Polyfills/ListExtensions.cs | 7 +- src/UnitTests/PropertyExtensions.cs | 4 +- src/UnitTests/TaskTestBase.cs | 101 +++++++++--- src/UnitTests/VirtualFileSystem.cs | 4 +- src/dirs.proj | 9 + test/Directory.Build.props | 11 ++ test/IntegrationTests.sln | 25 --- .../IntegrationTests/EmbeddedResourceTests.cs | 6 +- test/IntegrationTests/IntegrationTests.csproj | 12 +- test/README.md | 45 +++++ test/dirs.proj | 8 + 30 files changed, 480 insertions(+), 234 deletions(-) create mode 100644 .editorconfig rename src/Directory.Build.props => Directory.Build.props (61%) rename src/Directory.Packages.props => Directory.Packages.props (89%) create mode 100644 dirs.proj create mode 100644 global.json delete mode 100644 src/.editorconfig delete mode 100644 src/AggregateConfigBuildTask.sln rename src/Task/FileHandlers/{FileTypeEnum.cs => FileType.cs} (96%) create mode 100644 src/dirs.proj create mode 100644 test/Directory.Build.props delete mode 100644 test/IntegrationTests.sln create mode 100644 test/README.md create mode 100644 test/dirs.proj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3ed2476 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,154 @@ +root = true + +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{cs,vb}] +# Code style rules +dotnet_diagnostic.IDE1006.severity = error + +# Visual Studio Spell Checker settings +spelling_languages = en-us +spelling_checkable_types = strings,identifiers,comments +spelling_error_severity = warning +spelling_use_default_exclusion_dictionary = true +spelling_exclusion_path = .\exclusion.dic + +[*.cs] +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = warning + +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = true + +# Missing usings should be reported as error (IDE0005) +dotnet_diagnostic.IDE0005.severity = error + +# Additional best practice rules as errors +# IDE0003: Remove qualification +dotnet_diagnostic.IDE0003.severity = error +# IDE0009: Member access should be qualified +dotnet_diagnostic.IDE0009.severity = error +# IDE0017: Simplify object initialization +dotnet_diagnostic.IDE0017.severity = error +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = error +# IDE0032: Use auto property +dotnet_diagnostic.IDE0032.severity = error +# IDE0034: Simplify 'default' expression +dotnet_diagnostic.IDE0034.severity = error +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = error +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = error +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = error +# IDE0049: Simplify names +dotnet_diagnostic.IDE0049.severity = error +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = error +# IDE0052: Remove unread private members +dotnet_diagnostic.IDE0052.severity = error +# IDE0055: Fix formatting +dotnet_diagnostic.IDE0055.severity = error +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = suggestion +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = error +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = error +# IDE0062: Make local function 'static' +dotnet_diagnostic.IDE0062.severity = error +# IDE0063: Use simple 'using' statement +dotnet_diagnostic.IDE0063.severity = error +# IDE0065: Misplaced using directive +dotnet_diagnostic.IDE0065.severity = error +# IDE0066: Convert switch statement to expression +dotnet_diagnostic.IDE0066.severity = suggestion +# IDE0090: Use 'new(...)' +dotnet_diagnostic.IDE0090.severity = error +# IDE0100: Remove redundant equality +dotnet_diagnostic.IDE0100.severity = error +# IDE0110: Remove unnecessary discard +dotnet_diagnostic.IDE0110.severity = error + +# Security rules as errors +# CA2100: Review SQL queries for security vulnerabilities +dotnet_diagnostic.CA2100.severity = error +# CA2109: Review visible event handlers +dotnet_diagnostic.CA2109.severity = error +# CA2119: Seal methods that satisfy private interfaces +dotnet_diagnostic.CA2119.severity = error +# CA3001: Review code for SQL injection vulnerabilities +dotnet_diagnostic.CA3001.severity = error +# CA3002: Review code for XSS vulnerabilities +dotnet_diagnostic.CA3002.severity = error +# CA3003: Review code for file path injection vulnerabilities +dotnet_diagnostic.CA3003.severity = error +# CA3004: Review code for information disclosure vulnerabilities +dotnet_diagnostic.CA3004.severity = error +# CA3005: Review code for LDAP injection vulnerabilities +dotnet_diagnostic.CA3005.severity = error +# CA3006: Review code for process command injection vulnerabilities +dotnet_diagnostic.CA3006.severity = error +# CA3007: Review code for open redirect vulnerabilities +dotnet_diagnostic.CA3007.severity = error +# CA3008: Review code for XPath injection vulnerabilities +dotnet_diagnostic.CA3008.severity = error +# CA3009: Review code for XML injection vulnerabilities +dotnet_diagnostic.CA3009.severity = error +# CA3010: Review code for XAML injection vulnerabilities +dotnet_diagnostic.CA3010.severity = error +# CA3011: Review code for DLL injection vulnerabilities +dotnet_diagnostic.CA3011.severity = error +# CA3012: Review code for regex injection vulnerabilities +dotnet_diagnostic.CA3012.severity = error + +# Performance rules as errors +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = error +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = error +# CA1810: Initialize reference type static fields inline +dotnet_diagnostic.CA1810.severity = error +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = error +# CA1813: Avoid unsealed attributes +dotnet_diagnostic.CA1813.severity = error +# CA1814: Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1814.severity = suggestion +# CA1815: Override equals and operator equals on value types +dotnet_diagnostic.CA1815.severity = error +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = error +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = error +# CA1821: Remove empty finalizers +dotnet_diagnostic.CA1821.severity = error +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = suggestion +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = error +# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +dotnet_diagnostic.CA1824.severity = suggestion +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = error + +# AsyncFixer findings as errors +# AsyncFixer01: Unnecessary async/await usage - Remove async/await when not needed to avoid performance penalty +dotnet_diagnostic.AsyncFixer01.severity = error +# AsyncFixer02: Long-running or blocking operations inside an async method - Replace blocking calls with async equivalents +dotnet_diagnostic.AsyncFixer02.severity = error +# AsyncFixer03: Fire-and-forget async-void methods and delegates - Convert async void to async Task (except event handlers) +dotnet_diagnostic.AsyncFixer03.severity = error +# AsyncFixer04: Fire-and-forget async call inside a using block - Await async operations that use disposable objects +dotnet_diagnostic.AsyncFixer04.severity = error +# AsyncFixer05: Downcasting from a nested task to an outer task - Avoid awaiting Task, use Unwrap() or Task.Run instead +dotnet_diagnostic.AsyncFixer05.severity = error + +# Roslynator findings as errors +dotnet_analyzer_diagnostic.category-roslynator.severity = error diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85d71c..2b5da2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,10 +56,10 @@ jobs: echo "VERSION=$VERSION" >> $env:GITHUB_OUTPUT - name: Build AggregateConfigBuildTask solution in Release mode - run: dotnet build src/AggregateConfigBuildTask.sln --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} + run: dotnet build src/dirs.proj --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} - name: Run tests for AggregateConfigBuildTask solution - run: dotnet test src/AggregateConfigBuildTask.sln --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} -p:CollectCoverage=true + run: dotnet test src/dirs.proj --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} -p:CollectCoverage=true --no-build - name: Upload NuGetPackage artifact uses: actions/upload-artifact@v4 @@ -98,10 +98,10 @@ jobs: run: dotnet nuget add source ${{ github.workspace }}/nuget/local --name AggregateConfigBuildTask - name: Build IntegrationTests in Release mode - run: dotnet build test/IntegrationTests.sln --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} + run: dotnet build test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} - name: Run IntegrationTests - run: dotnet test test/IntegrationTests.sln --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} -p:CollectCoverage=true + run: dotnet test test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} -p:CollectCoverage=true - name: Upload integration results artifact uses: actions/upload-artifact@v4 diff --git a/src/Directory.Build.props b/Directory.Build.props similarity index 61% rename from src/Directory.Build.props rename to Directory.Build.props index 5766305..5a9e983 100644 --- a/src/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,14 @@ - - - - true - true - true - latest - false - false - AllEnabledByDefault - - - + + + + true + true + true + latest + true + true + AllEnabledByDefault + true + + + diff --git a/src/Directory.Packages.props b/Directory.Packages.props similarity index 89% rename from src/Directory.Packages.props rename to Directory.Packages.props index e78f8e2..128cc97 100644 --- a/src/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,36 +1,39 @@ - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index f8c04be..d9f7d1c 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,28 @@ foreach (var name in resourceNames) This will list all embedded resources, allowing you to confirm the correct name to use when loading the resource. +## Development + +This project uses `dirs.proj` files with the Microsoft.Build.Traversal SDK for project organization instead of traditional solution files. This approach provides better performance and more flexibility for build scenarios. + +### Generating Solution Files + +If you need Visual Studio solution files for development, you can generate them using the [Microsoft.VisualStudio.SlnGen](https://github.com/microsoft/slngen) global tool: + +```bash +# Install the slngen global tool +dotnet tool install --global Microsoft.VisualStudio.SlnGen + +# Generate solution files from dirs.proj files +slngen src/dirs.proj --folders true +slngen test/dirs.proj --folders true + +# Or generate a solution for the entire repository +slngen dirs.proj --folders true +``` + +The generated solution files will include all projects referenced by the `dirs.proj` files and maintain the folder structure for easy navigation in Visual Studio. + ## License This project is licensed under the MIT License. See the [LICENSE](https://github.com/richardsondev/AggregateConfigBuildTask/blob/main/LICENSE) file for details. diff --git a/dirs.proj b/dirs.proj new file mode 100644 index 0000000..2515cca --- /dev/null +++ b/dirs.proj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..286d9b8 --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "9.0.301", + "rollForward": "latestFeature" + }, + "msbuild-sdks": { + "Microsoft.Build.Traversal": "4.1.82" + } +} \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig deleted file mode 100644 index 37f9dad..0000000 --- a/src/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -[*.cs] - -# CS1591: Missing XML comment for publicly visible type or member -dotnet_diagnostic.CS1591.severity = suggestion - -# Organize usings -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups = true - -# Missing usings should be reported as error (IDE0005) -dotnet_diagnostic.IDE0005.severity = error - -# AsyncFixer findings as errors -dotnet_diagnostic.AsyncFixer01.severity = error -dotnet_diagnostic.AsyncFixer02.severity = error -dotnet_diagnostic.AsyncFixer03.severity = error -dotnet_diagnostic.AsyncFixer04.severity = error -dotnet_diagnostic.AsyncFixer05.severity = error - -# Roslynator findings as errors -dotnet_analyzer_diagnostic.category-roslynator.severity = error diff --git a/src/AggregateConfigBuildTask.sln b/src/AggregateConfigBuildTask.sln deleted file mode 100644 index da33366..0000000 --- a/src/AggregateConfigBuildTask.sln +++ /dev/null @@ -1,38 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34607.119 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AggregateConfigBuildTask", "Task\AggregateConfigBuildTask.csproj", "{D7E13729-CCC0-48F7-A03A-68C0204EC1D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{FF28495E-5B97-412A-B580-574D27351EB3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29D0AE56-C184-4741-824F-521198552928}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D7E13729-CCC0-48F7-A03A-68C0204EC1D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7E13729-CCC0-48F7-A03A-68C0204EC1D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7E13729-CCC0-48F7-A03A-68C0204EC1D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7E13729-CCC0-48F7-A03A-68C0204EC1D4}.Release|Any CPU.Build.0 = Release|Any CPU - {FF28495E-5B97-412A-B580-574D27351EB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF28495E-5B97-412A-B580-574D27351EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF28495E-5B97-412A-B580-574D27351EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF28495E-5B97-412A-B580-574D27351EB3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {66DD6337-8C9A-49D6-8392-6CCD04E598D9} - EndGlobalSection -EndGlobal diff --git a/src/Task/AggregateConfig.cs b/src/Task/AggregateConfig.cs index 7c133b8..6ca8204 100644 --- a/src/Task/AggregateConfig.cs +++ b/src/Task/AggregateConfig.cs @@ -2,8 +2,11 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; + using AggregateConfigBuildTask.FileHandlers; + using Microsoft.Build.Framework; + using Task = Microsoft.Build.Utilities.Task; [assembly: InternalsVisibleTo("AggregateConfig.Tests.UnitTests")] @@ -25,7 +28,7 @@ public class AggregateConfig : Task public string InputDirectory { get; set; } /// - /// The type of input file type to be processed from . If not specified, the default type is used. + /// The type of input file type to be processed from . If not specified, the default type is used. /// public string InputType { get; set; } diff --git a/src/Task/FileHandlers/ArmParametersFileHandler.cs b/src/Task/FileHandlers/ArmParametersFileHandler.cs index 48b2077..7c2148f 100644 --- a/src/Task/FileHandlers/ArmParametersFileHandler.cs +++ b/src/Task/FileHandlers/ArmParametersFileHandler.cs @@ -55,13 +55,13 @@ public void WriteOutput(JsonElement? mergedData, string outputPath) { if (mergedData.HasValue && mergedData.Value.ValueKind == JsonValueKind.Object) { - var parameters = new Dictionary(); + var parameters = new Dictionary(StringComparer.Ordinal); foreach (var kvp in mergedData.Value.EnumerateObject()) { string type = GetParameterType(kvp.Value); - parameters[kvp.Name] = new Dictionary + parameters[kvp.Name] = new Dictionary(StringComparer.Ordinal) { ["type"] = type, ["value"] = kvp.Value @@ -69,7 +69,7 @@ public void WriteOutput(JsonElement? mergedData, string outputPath) } // ARM template structure - var armTemplate = new Dictionary + var armTemplate = new Dictionary(StringComparer.Ordinal) { ["$schema"] = "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", ["contentVersion"] = "1.0.0.0", @@ -111,7 +111,7 @@ private static string GetParameterType(JsonElement value) return "object"; default: - throw new ArgumentException("Unsupported type for ARM template parameters."); + throw new ArgumentException("Unsupported type for ARM template parameters.", nameof(value)); } } diff --git a/src/Task/FileHandlers/FileHandlerFactory.cs b/src/Task/FileHandlers/FileHandlerFactory.cs index bdc9d4c..e9c8d1e 100644 --- a/src/Task/FileHandlers/FileHandlerFactory.cs +++ b/src/Task/FileHandlers/FileHandlerFactory.cs @@ -26,7 +26,7 @@ internal static IFileHandler GetFileHandlerForType(IFileSystem fileSystem, FileT case FileType.Arm: return new ArmParametersFileHandler(fileSystem); default: - throw new ArgumentException("Unsupported format"); + throw new ArgumentException("Unsupported format", nameof(format)); } } @@ -47,7 +47,7 @@ internal static List GetExpectedFileExtensions(FileType inputType) case FileType.Arm: return new List { ".json" }; default: - throw new ArgumentException("Unsupported input type"); + throw new ArgumentException("Unsupported input type", nameof(inputType)); } } } diff --git a/src/Task/FileHandlers/FileTypeEnum.cs b/src/Task/FileHandlers/FileType.cs similarity index 96% rename from src/Task/FileHandlers/FileTypeEnum.cs rename to src/Task/FileHandlers/FileType.cs index 993eb81..eb8a3c7 100644 --- a/src/Task/FileHandlers/FileTypeEnum.cs +++ b/src/Task/FileHandlers/FileType.cs @@ -1,33 +1,33 @@ -namespace AggregateConfigBuildTask -{ - /// - /// Enum representing different file types supported for merging and processing. - /// - public enum FileType - { - /// - /// Represents a JSON file type. - /// - Json = 0, - - /// - /// Represents an ARM (Azure Resource Manager) template parameter file type. - /// - Arm = 1, - - /// - /// Alias for the file type, specifically for ARM parameter files. - /// - ArmParameter = Arm, - - /// - /// Represents a YAML file type (.yml extension). - /// - Yml = 2, - - /// - /// Alias for the file type, for files with the .yaml extension. - /// - Yaml = Yml, - } -} +namespace AggregateConfigBuildTask +{ + /// + /// Enum representing different file types supported for merging and processing. + /// + public enum FileType + { + /// + /// Represents a JSON file type. + /// + Json = 0, + + /// + /// Represents an ARM (Azure Resource Manager) template parameter file type. + /// + Arm = 1, + + /// + /// Alias for the file type, specifically for ARM parameter files. + /// + ArmParameter = Arm, + + /// + /// Represents a YAML file type (.yml extension). + /// + Yml = 2, + + /// + /// Alias for the file type, for files with the .yaml extension. + /// + Yaml = Yml, + } +} diff --git a/src/Task/FileHandlers/JsonFileHandler.cs b/src/Task/FileHandlers/JsonFileHandler.cs index 6ceb2b3..4d1c6ab 100644 --- a/src/Task/FileHandlers/JsonFileHandler.cs +++ b/src/Task/FileHandlers/JsonFileHandler.cs @@ -6,7 +6,7 @@ namespace AggregateConfigBuildTask.FileHandlers /// public class JsonFileHandler : IFileHandler { - readonly IFileSystem fileSystem; + private readonly IFileSystem fileSystem; private readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions { WriteIndented = true }; diff --git a/src/Task/FileHandlers/YamlFileHandler.cs b/src/Task/FileHandlers/YamlFileHandler.cs index 06bf3aa..3c86db9 100644 --- a/src/Task/FileHandlers/YamlFileHandler.cs +++ b/src/Task/FileHandlers/YamlFileHandler.cs @@ -1,6 +1,7 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; + using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.System.Text.Json; @@ -10,7 +11,7 @@ namespace AggregateConfigBuildTask.FileHandlers /// public class YamlFileHandler : IFileHandler { - readonly IFileSystem fileSystem; + private readonly IFileSystem fileSystem; internal YamlFileHandler(IFileSystem fileSystem) { diff --git a/src/Task/JsonHelper.cs b/src/Task/JsonHelper.cs index c9d0ce8..79e9d27 100644 --- a/src/Task/JsonHelper.cs +++ b/src/Task/JsonHelper.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; + using Microsoft.Build.Framework; namespace AggregateConfigBuildTask @@ -38,7 +39,7 @@ public static object ConvertKeysToString(object data) { if (data is IDictionary dict) { - var convertedDict = new Dictionary(); + var convertedDict = new Dictionary(StringComparer.Ordinal); foreach (var key in dict.Keys) { @@ -72,7 +73,7 @@ public static object ConvertKeysToString(object data) /// A dictionary containing the parsed key-value pairs. public static Dictionary ParseAdditionalProperties(ITaskItem[] properties) { - var additionalPropertiesDict = new Dictionary(); + var additionalPropertiesDict = new Dictionary(StringComparer.Ordinal); const string unicodeEscape = "\u001F"; char[] split = new[] { '=' }; @@ -137,7 +138,7 @@ public static async Task ConvertObjectToJsonElement(object value) /// A dictionary representing the JsonElement. public static Dictionary JsonElementToDictionary(JsonElement jsonElement) { - var dictionary = new Dictionary(); + var dictionary = new Dictionary(StringComparer.Ordinal); foreach (var property in jsonElement.EnumerateObject()) { diff --git a/src/Task/Logger/QuietTaskLogger.cs b/src/Task/Logger/QuietTaskLogger.cs index af4b61b..df639e2 100644 --- a/src/Task/Logger/QuietTaskLogger.cs +++ b/src/Task/Logger/QuietTaskLogger.cs @@ -24,7 +24,7 @@ public QuietTaskLogger(TaskLoggingHelper task) } /// - public void LogError(string message, params object[] messageArgs) + public void LogError(string message = null, params object[] messageArgs) { Log.LogError(message ?? "Unknown Error", messageArgs); } diff --git a/src/Task/Logger/TaskLogger.cs b/src/Task/Logger/TaskLogger.cs index f0b5020..942c688 100644 --- a/src/Task/Logger/TaskLogger.cs +++ b/src/Task/Logger/TaskLogger.cs @@ -1,4 +1,5 @@ using System; + using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -22,7 +23,7 @@ public TaskLogger(TaskLoggingHelper task) } /// - public void LogError(string message, params object[] messageArgs) + public void LogError(string message = null, params object[] messageArgs) { Log.LogError(message ?? "Unknown Error", messageArgs); } diff --git a/src/Task/ObjectManager.cs b/src/Task/ObjectManager.cs index 4ec2ac6..eaa888c 100644 --- a/src/Task/ObjectManager.cs +++ b/src/Task/ObjectManager.cs @@ -1,6 +1,4 @@ -using AggregateConfigBuildTask.FileHandlers; -using Microsoft.Build.Framework; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -8,6 +6,10 @@ using System.Text.Json; using System.Threading.Tasks; +using AggregateConfigBuildTask.FileHandlers; + +using Microsoft.Build.Framework; + namespace AggregateConfigBuildTask { internal static class ObjectManager @@ -39,7 +41,8 @@ internal static class ObjectManager .Chunk(100); await fileGroups.ForEachAsync(Environment.ProcessorCount, - async (files) => { + async (files) => + { JsonElement? intermediateResult = null; foreach (var file in files) { @@ -101,7 +104,7 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, if (sourceObject != null && injectSourceProperty && sourceObject.HasValue && sourceObject.Value.ValueKind == JsonValueKind.Object) { var obj2Dict = sourceObject.Value; - var jsonObject = obj2Dict.EnumerateObject().ToDictionary(p => p.Name, p => p.Value); + var jsonObject = obj2Dict.EnumerateObject().ToDictionary(p => p.Name, p => p.Value, StringComparer.Ordinal); foreach (var kvp in jsonObject.ToList()) { @@ -119,7 +122,7 @@ await fileGroups.ForEachAsync(Environment.ProcessorCount, if (currentObj2Nested.ValueKind == JsonValueKind.Object) { var nestedObj = currentObj2Nested; - var nestedDict = nestedObj.EnumerateObject().ToDictionary(p => p.Name, p => p.Value); + var nestedDict = nestedObj.EnumerateObject().ToDictionary(p => p.Name, p => p.Value, StringComparer.Ordinal); // Inject the "source" property nestedDict["source"] = JsonDocument.Parse($"\"{Path.GetFileNameWithoutExtension(source)}\"").RootElement; @@ -159,8 +162,8 @@ public static async Task MergeAndUpdateObjects(JsonElement? obj1, J // Handle merging of objects if (obj1.Value.ValueKind == JsonValueKind.Object && obj2.Value.ValueKind == JsonValueKind.Object) { - var dict1 = obj1.Value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value); - var dict2 = obj2.Value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value); + var dict1 = obj1.Value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value, StringComparer.Ordinal); + var dict2 = obj2.Value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value, StringComparer.Ordinal); foreach (var key in dict2.Keys) { diff --git a/src/Task/Polyfills/ListExtensions.cs b/src/Task/Polyfills/ListExtensions.cs index 4655523..fd0ce92 100644 --- a/src/Task/Polyfills/ListExtensions.cs +++ b/src/Task/Polyfills/ListExtensions.cs @@ -20,7 +20,7 @@ public static class ListExtensions /// An IEnumerable of string arrays, each containing a chunk of the original list. /// When chunk size is 0 or less /// is null. - public static IEnumerable> Chunk(this List source, int chunkSize) + public static IEnumerable> Chunk(this ICollection source, int chunkSize) { if (source == null) { @@ -36,9 +36,10 @@ public static IEnumerable> Chunk(this List source, i IEnumerable> FetchChunks() { - for (int i = 0; i < source.Count; i += chunkSize) + var list = source as List ?? source.ToList(); + for (int i = 0; i < list.Count; i += chunkSize) { - yield return source.GetRange(i, Math.Min(chunkSize, source.Count - i)); + yield return list.GetRange(i, Math.Min(chunkSize, list.Count - i)); } } } diff --git a/src/UnitTests/PropertyExtensions.cs b/src/UnitTests/PropertyExtensions.cs index 69cc39d..60b165f 100644 --- a/src/UnitTests/PropertyExtensions.cs +++ b/src/UnitTests/PropertyExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; + using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -16,7 +17,8 @@ internal static class PropertyExtensions /// An array of TaskItems. public static ITaskItem[] CreateTaskItems(this IDictionary properties, bool legacyAdditionalProperties) { - return properties.Select((q) => { + return properties.Select((q) => + { if (legacyAdditionalProperties) { // Legacy format: "Key=Value" in ItemSpec diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index 6f42c64..a78d19e 100644 --- a/src/UnitTests/TaskTestBase.cs +++ b/src/UnitTests/TaskTestBase.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; + using Microsoft.Build.Framework; + using Moq; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -205,7 +209,7 @@ public void ShouldIncludeAdditionalPropertiesInJson(bool useLegacyAdditionalProp OutputFile = testPath + @"\output.json", OutputType = nameof(FileType.Json), AddSourceProperty = true, - AdditionalProperties = new Dictionary + AdditionalProperties = new Dictionary(StringComparer.Ordinal) { { "Group", "TestRG" }, { "Environment\\=Key", "Prod\\=West" } @@ -250,7 +254,7 @@ public void ShouldIncludeAdditionalPropertiesInArmParameters(bool useLegacyAddit OutputFile = testPath + @"\output.json", OutputType = nameof(FileType.Arm), AddSourceProperty = true, - AdditionalProperties = new Dictionary + AdditionalProperties = new Dictionary(StringComparer.Ordinal) { { "Group", "TestRG" }, { "Environment", "Prod" } @@ -376,7 +380,7 @@ public void ShouldIncludeAdditionalPropertiesInJsonInput(bool useLegacyAdditiona OutputFile = testPath + @"\output.json", OutputType = nameof(FileType.Arm), AddSourceProperty = true, - AdditionalProperties = new Dictionary + AdditionalProperties = new Dictionary(StringComparer.Ordinal) { { "Group", "TestRG" }, { "Environment", "Prod" } @@ -431,7 +435,7 @@ public void ShouldIncludeAdditionalPropertiesInArmParameterFile(bool useLegacyAd OutputFile = testPath + @"\output.parameters.json", OutputType = nameof(FileType.Arm), AddSourceProperty = true, - AdditionalProperties = new Dictionary + AdditionalProperties = new Dictionary(StringComparer.Ordinal) { { "Group", "TestRG" }, { "Environment", "'Prod'" } @@ -471,8 +475,8 @@ public void StressTest_ShouldAddSourcePropertyManyFiles() for (int optionIndex = 1; optionIndex <= totalOptionsPerFile; optionIndex++) { - sb.AppendLine($" - name: 'Option {optionIndex}'") - .AppendLine($" description: 'Description for Option {optionIndex}'"); + sb.AppendLine(CultureInfo.InvariantCulture, $" - name: 'Option {optionIndex}'") + .AppendLine(CultureInfo.InvariantCulture, $" description: 'Description for Option {optionIndex}'"); } // Write each YAML file to the mock file system @@ -519,14 +523,32 @@ public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss(string inputType, { Assert.IsTrue(steps?.Length > 0); - // Arrange: Prepare paths and sample data based on the input type. + // Setup input file + string inputFilePath = SetupInputFile(inputType); + + // Execute translation chain + var (finalInputPath, finalOutputType) = ExecuteTranslationChain(inputType, steps, inputFilePath); + + // Execute final conversion back to original format + string finalOutputPath = ExecuteFinalConversion(inputType, finalInputPath, finalOutputType); + + // Validate no data loss + AssertNoDataLoss(inputFilePath, finalOutputPath, inputType); + } + + private string SetupInputFile(string inputType) + { var inputDir = $"{testPath}\\input"; virtualFileSystem.CreateDirectory(inputDir); - // Write the initial input file - var inputFilePath = $"{inputDir}\\input.{(inputType == "arm" ? "json" : inputType)}"; + var inputFilePath = $"{inputDir}\\input.{(string.Equals(inputType, "arm", StringComparison.Ordinal) ? "json" : inputType)}"; virtualFileSystem.WriteAllText(inputFilePath, GetSampleDataForType(inputType)); + return inputFilePath; + } + + private (string path, string outputType) ExecuteTranslationChain(string inputType, string[] steps, string inputFilePath) + { string previousInputPath = inputFilePath; string previousOutputType = inputType; @@ -535,7 +557,7 @@ public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss(string inputType, { var outputType = steps[i]; var stepDir = $"{testPath}\\step{i + 1}"; - var stepOutputPath = $"{stepDir}\\output.{(outputType == "arm" ? "json" : outputType)}"; + var stepOutputPath = $"{stepDir}\\output.{(string.Equals(outputType, "arm", StringComparison.Ordinal) ? "json" : outputType)}"; virtualFileSystem.CreateDirectory(stepDir); @@ -547,15 +569,19 @@ public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss(string inputType, previousOutputType = outputType; } + return (previousInputPath, previousOutputType); + } + + private string ExecuteFinalConversion(string inputType, string previousInputPath, string previousOutputType) + { // Final step: Convert the final output back to the original input type var finalDir = $"{testPath}\\final"; - var finalOutputPath = $"{finalDir}\\final_output.{(inputType == "arm" ? "json" : inputType)}"; + var finalOutputPath = $"{finalDir}\\final_output.{(string.Equals(inputType, "arm", StringComparison.Ordinal) ? "json" : inputType)}"; virtualFileSystem.CreateDirectory(finalDir); ExecuteTranslationTask(previousOutputType, inputType, previousInputPath, finalOutputPath); - // Assert: Compare final output with original input to check no data loss - AssertNoDataLoss(inputFilePath, finalOutputPath, inputType); + return finalOutputPath; } private void ExecuteTranslationTask(string inputType, string outputType, string inputFilePath, string outputFilePath) @@ -576,14 +602,32 @@ private void AssertNoDataLoss(string originalFilePath, string finalFilePath, str { string originalInput = virtualFileSystem.ReadAllText(originalFilePath); string finalOutput = virtualFileSystem.ReadAllText(finalFilePath); - Assert.AreEqual(originalInput, finalOutput, $"Data mismatch after full conversion cycle for {inputType}"); + Assert.IsTrue(string.Equals(originalInput, finalOutput, StringComparison.Ordinal), $"Data mismatch after full conversion cycle for {inputType}"); } private static string GetSampleDataForType(string type) { - return type switch + if (string.Equals(type, "json", StringComparison.Ordinal)) + { + return GetJsonSampleData(); + } + else if (string.Equals(type, "arm", StringComparison.Ordinal)) + { + return GetArmSampleData(); + } + else if (string.Equals(type, "yml", StringComparison.Ordinal)) { - "json" => """ + return GetYmlSampleData(); + } + else + { + throw new InvalidOperationException("Unknown type"); + } + } + + private static string GetJsonSampleData() + { + return """ { "options": [ { @@ -606,8 +650,12 @@ private static string GetSampleDataForType(string type) } ] } -""", - "arm" => """ +"""; + } + + private static string GetArmSampleData() + { + return """ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", @@ -635,8 +683,12 @@ private static string GetSampleDataForType(string type) } } } -""", - "yml" => @"options: +"""; + } + + private static string GetYmlSampleData() + { + return @"options: - name: Option 1 description: First option isTrue: true @@ -648,10 +700,7 @@ private static string GetSampleDataForType(string type) number: 1003 - name: Nested option 2 description: Nested second option -", - - _ => throw new InvalidOperationException("Unknown type") - }; +"; } /// @@ -664,9 +713,9 @@ private static bool OptionExistsWithSource(List> opti { return options.Any(option => option.ContainsKey("name") && - (string)option["name"] == optionName && + string.Equals((string)option["name"], optionName, StringComparison.Ordinal) && option.ContainsKey("source") && - (string)option["source"] == expectedSource); + string.Equals((string)option["source"], expectedSource, StringComparison.Ordinal)); } } } diff --git a/src/UnitTests/VirtualFileSystem.cs b/src/UnitTests/VirtualFileSystem.cs index 2e387b8..dd9199d 100644 --- a/src/UnitTests/VirtualFileSystem.cs +++ b/src/UnitTests/VirtualFileSystem.cs @@ -30,8 +30,8 @@ public string[] GetFiles(string path, string searchPattern) // Ensure the file starts with the given path if (file.StartsWith(path, StringComparison)) { - // Perform a regex match using the translated pattern - if (Regex.IsMatch(file, regexPattern, RegexOptions)) + // Perform a regex match using the translated pattern with timeout + if (Regex.IsMatch(file, regexPattern, RegexOptions, TimeSpan.FromMilliseconds(500))) { files.Add(file); } diff --git a/src/dirs.proj b/src/dirs.proj new file mode 100644 index 0000000..7ef75f1 --- /dev/null +++ b/src/dirs.proj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props new file mode 100644 index 0000000..c9eecc3 --- /dev/null +++ b/test/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + + false + + $(NoWarn);CA1515;CA1707 + + diff --git a/test/IntegrationTests.sln b/test/IntegrationTests.sln deleted file mode 100644 index 03198ec..0000000 --- a/test/IntegrationTests.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34607.119 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{C3186E8C-A01C-46A3-BB70-0433374822C2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C3186E8C-A01C-46A3-BB70-0433374822C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3186E8C-A01C-46A3-BB70-0433374822C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3186E8C-A01C-46A3-BB70-0433374822C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3186E8C-A01C-46A3-BB70-0433374822C2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F4C35D56-1D95-4EF7-ACF7-4319177A048E} - EndGlobalSection -EndGlobal diff --git a/test/IntegrationTests/EmbeddedResourceTests.cs b/test/IntegrationTests/EmbeddedResourceTests.cs index 056749b..8758831 100644 --- a/test/IntegrationTests/EmbeddedResourceTests.cs +++ b/test/IntegrationTests/EmbeddedResourceTests.cs @@ -20,10 +20,8 @@ public void ReadEmbeddedResource_DeserializesJsonSuccessfully(string resourceNam { Assert.IsNotNull(stream, $"Embedded resource '{resourceName}' not found. Available: {string.Join(", ", Assembly.GetAssembly(typeof(EmbeddedResourceTests)).GetManifestResourceNames())}"); - using (var reader = new StreamReader(stream)) - { - jsonContent = reader.ReadToEnd(); - } + using var reader = new StreamReader(stream); + jsonContent = reader.ReadToEnd(); } // Deserialize the JSON into an object diff --git a/test/IntegrationTests/IntegrationTests.csproj b/test/IntegrationTests/IntegrationTests.csproj index c40be70..7b77ff4 100644 --- a/test/IntegrationTests/IntegrationTests.csproj +++ b/test/IntegrationTests/IntegrationTests.csproj @@ -12,12 +12,12 @@ - - - - - - + + + + + + diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..2df6c15 --- /dev/null +++ b/test/README.md @@ -0,0 +1,45 @@ +# Integration Tests + +This folder contains integration tests that validate the `AggregateConfigBuildTask` NuGet package functionality in realistic scenarios. + +## About Integration Tests + +These tests differ from the [unit tests](../src/UnitTests/) in that they: + +- Test the complete NuGet package functionality end-to-end +- Use actual file system operations and MSBuild task execution +- Validate the package works correctly when installed as a NuGet dependency +- Run against multiple operating systems (Windows, Linux, macOS) in CI/CD + +## Running Integration Tests + +### Prerequisites + +The integration tests require the NuGet package to be built and available locally. You can either: + +1. **Build and run everything together** (recommended): + ```bash + dotnet build dirs.proj --configuration Release + dotnet test test/dirs.proj --configuration Release --no-build + ``` + +2. **Manually provide the NuGet package**: + First, build the package: + ```bash + dotnet pack src/Task/AggregateConfigBuildTask.csproj --configuration Release + ``` + + Then add it as a local NuGet source and run the tests: + ```bash + dotnet nuget add source ./src/Task/bin/Release --name AggregateConfigBuildTask-Local + dotnet test test/dirs.proj --configuration Release + ``` + +## Test Structure + +- **IntegrationTests/** - Contains the integration test project +- **dirs.proj** - MSBuild traversal project for building and running integration tests + +## Related Tests + +For unit tests that test individual components and methods, see the [unit tests folder](../src/UnitTests/). diff --git a/test/dirs.proj b/test/dirs.proj new file mode 100644 index 0000000..a43ab2f --- /dev/null +++ b/test/dirs.proj @@ -0,0 +1,8 @@ + + + + + + + + From e6fd1e95f79243f102d73fdf7f29f77bd02bf4b8 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Mon, 7 Jul 2025 01:52:44 -0700 Subject: [PATCH 3/4] Add SBOM generation and update integration tests for version management --- .github/workflows/build.yml | 14 ++++++++++++-- src/Task/AggregateConfigBuildTask.csproj | 1 + test/IntegrationTests/IntegrationTests.csproj | 4 +++- test/README.md | 16 ++++++++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b5da2f..71baf2c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,12 +55,22 @@ jobs: echo "VERSION=$VERSION" >> $env:GITHUB_OUTPUT + - name: Generate SBOM for the project + uses: advanced-security/generate-sbom-action@v1 + id: gensbom + - name: Build AggregateConfigBuildTask solution in Release mode run: dotnet build src/dirs.proj --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} - name: Run tests for AggregateConfigBuildTask solution run: dotnet test src/dirs.proj --configuration Release -warnaserror -p:Version=${{ steps.get_version.outputs.VERSION }} -p:CollectCoverage=true --no-build + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: ${{ steps.gensbom.outputs.fileName }} + - name: Upload NuGetPackage artifact uses: actions/upload-artifact@v4 with: @@ -98,10 +108,10 @@ jobs: run: dotnet nuget add source ${{ github.workspace }}/nuget/local --name AggregateConfigBuildTask - name: Build IntegrationTests in Release mode - run: dotnet build test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} + run: dotnet build test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} -p:UseLocalPackageVersion=true - name: Run IntegrationTests - run: dotnet test test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} -p:CollectCoverage=true + run: dotnet test test/dirs.proj --configuration Release -warnaserror -p:Version=${{ needs.build.outputs.VERSION }} -p:CollectCoverage=true -p:UseLocalPackageVersion=true - name: Upload integration results artifact uses: actions/upload-artifact@v4 diff --git a/src/Task/AggregateConfigBuildTask.csproj b/src/Task/AggregateConfigBuildTask.csproj index e5f8924..f27af0b 100644 --- a/src/Task/AggregateConfigBuildTask.csproj +++ b/src/Task/AggregateConfigBuildTask.csproj @@ -65,6 +65,7 @@ + diff --git a/test/IntegrationTests/IntegrationTests.csproj b/test/IntegrationTests/IntegrationTests.csproj index 7b77ff4..1bbeaf3 100644 --- a/test/IntegrationTests/IntegrationTests.csproj +++ b/test/IntegrationTests/IntegrationTests.csproj @@ -12,7 +12,9 @@ - + + + diff --git a/test/README.md b/test/README.md index 2df6c15..61eb577 100644 --- a/test/README.md +++ b/test/README.md @@ -15,7 +15,14 @@ These tests differ from the [unit tests](../src/UnitTests/) in that they: ### Prerequisites -The integration tests require the NuGet package to be built and available locally. You can either: +The integration tests require the NuGet package to be available. By default, they use the version specified in `Directory.Packages.props`, but can be configured to use a different version for CI/CD testing. + +### Version Management + +- **Default behavior**: Uses the `AggregateConfigBuildTask` version from `Directory.Packages.props` +- **CI/CD testing**: Set `UseLocalPackageVersion=true` to use the version specified by the `Version` property + +### Running Tests 1. **Build and run everything together** (recommended): ```bash @@ -23,7 +30,12 @@ The integration tests require the NuGet package to be built and available locall dotnet test test/dirs.proj --configuration Release --no-build ``` -2. **Manually provide the NuGet package**: +2. **Test with a specific package version** (useful for CI/CD): + ```bash + dotnet test test/dirs.proj --configuration Release -p:UseLocalPackageVersion=true -p:Version=1.2.3 + ``` + +3. **Manually provide the NuGet package**: First, build the package: ```bash dotnet pack src/Task/AggregateConfigBuildTask.csproj --configuration Release From 4c117574141afb769e68daed8629ff634858db56 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Mon, 7 Jul 2025 01:56:47 -0700 Subject: [PATCH 4/4] Update sbom file pattern in project file to match new naming convention --- src/Task/AggregateConfigBuildTask.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Task/AggregateConfigBuildTask.csproj b/src/Task/AggregateConfigBuildTask.csproj index f27af0b..e2413bf 100644 --- a/src/Task/AggregateConfigBuildTask.csproj +++ b/src/Task/AggregateConfigBuildTask.csproj @@ -65,7 +65,7 @@ - +