diff --git a/Directory.Packages.props b/Directory.Packages.props index b120fe3..68a66aa 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,5 +12,7 @@ SPDX-License-Identifier: MIT + + \ No newline at end of file diff --git a/FuzzingTests/FuzzingTests.csproj b/FuzzingTests/FuzzingTests.csproj new file mode 100644 index 0000000..dd14e74 --- /dev/null +++ b/FuzzingTests/FuzzingTests.csproj @@ -0,0 +1,23 @@ + + + + + + $(MainTargetFramework) + True + + + + + + + + + + + + diff --git a/FuzzingTests/GitVersionVariables_Tests.cs b/FuzzingTests/GitVersionVariables_Tests.cs new file mode 100644 index 0000000..6a4a9c8 --- /dev/null +++ b/FuzzingTests/GitVersionVariables_Tests.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +using System.Text.Json.Nodes; + +namespace FuzzingTests; + +[TestClass] +sealed class GitVersionVariables_Tests +{ + [TestMethod] + public void JsonIsParsedCorrectly() + { + var jsonGenerator = JsonGenerator.GenerateExactlyTwoPairs(); + + Prop.ForAll(Arb.From(jsonGenerator), (JsonObject json) => + { + var result = GitVersionVariables.FromJson(json.ToJsonString()); + return result.Count == 2 && json.All(pair => result.TryGetValue(pair.Key, out var value) && value == pair.Value!.ToString()); + }).QuickCheckThrowOnFailure(); + } +} diff --git a/FuzzingTests/GlobalSuppressions.cs b/FuzzingTests/GlobalSuppressions.cs new file mode 100644 index 0000000..f9a7d2a --- /dev/null +++ b/FuzzingTests/GlobalSuppressions.cs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1812:Internal class is never instantiated", Justification = "We use internal test classes")] diff --git a/FuzzingTests/JsonGenerator.cs b/FuzzingTests/JsonGenerator.cs new file mode 100644 index 0000000..258e6f8 --- /dev/null +++ b/FuzzingTests/JsonGenerator.cs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +using System.Text.Json.Nodes; + +namespace FuzzingTests; + +static class JsonGenerator +{ + static readonly char[] AllowedKeyChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray(); + + public static Gen GenerateExactlyTwoPairs() + { + // GitVersion keys are always strings of length 1 to 20, consisting of uppercase and lowercase letters. + var keyGen = Gen.Choose(1, 20).SelectMany( + length => Gen.ArrayOf(Gen.Elements(AllowedKeyChars), length).Select(chars => new string(chars))); + + // GitVersion values can be either strings or integers, so we create a generator that can produce both types. + var valueGen = Gen.OneOf( + ArbMap.Default.GeneratorFor().Where(s => s is not null).Select(s => JsonValue.Create(s)), + Gen.Choose(0, int.MaxValue).Select(i => JsonValue.Create(i))); + + var firstPairGen = Gen.Zip(keyGen, valueGen); + + // Chain the generation to enforce uniqueness on the second key + return firstPairGen.SelectMany(firstPair => + { + var key1 = firstPair.Item1; + var val1 = firstPair.Item2; + + // CRITICAL: Key 2 must be different from Key 1 to avoid a duplicate key error + var uniqueSecondKeyGen = keyGen.Where(key2 => key2 != key1); + + return Gen.Zip(uniqueSecondKeyGen, valueGen).Select(secondPair => + { + var key2 = secondPair.Item1; + var val2 = secondPair.Item2; + + return new JsonObject + { + { key1, val1 }, + { key2, val2 } + }; + }); + }); + } +} diff --git a/FuzzingTests/MSTestSettings.cs b/FuzzingTests/MSTestSettings.cs new file mode 100644 index 0000000..399ee0b --- /dev/null +++ b/FuzzingTests/MSTestSettings.cs @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +[assembly: DiscoverInternals] diff --git a/FuzzingTests/Usings.cs b/FuzzingTests/Usings.cs new file mode 100644 index 0000000..4efd69e --- /dev/null +++ b/FuzzingTests/Usings.cs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +global using Dorssel.GitVersion.MsBuild; + +global using FsCheck; +global using FsCheck.Fluent; diff --git a/FuzzingTests/packages.lock.json b/FuzzingTests/packages.lock.json new file mode 100644 index 0000000..6925520 --- /dev/null +++ b/FuzzingTests/packages.lock.json @@ -0,0 +1,146 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "FsCheck": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "Gintz/p4eTVb3EW3P8tg5wZB66sHbUBO42kHwuDZJ9BRHurOuC3+JF5YPq4rfeURMxuGs5YgzW/DDneIUsTNqA==", + "dependencies": { + "FSharp.Core": "5.0.2" + } + }, + "GitVersion.MsBuild": { + "type": "Direct", + "requested": "[6.8.2, )", + "resolved": "6.8.2", + "contentHash": "eTd8jUiogUXpVssFyDWWdMrPr5Cdc57tWaQ3Cv/eMIhnumOHZhDkc/wcy8hYU8aH7NS4VT2w/pHjVITDSTXMGw==" + }, + "JunitXml.TestLogger": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "rO2ugKcbVy8SHNKeqfX/DshUcTtolnEUwMKehVB6gh5/YoL+U07zoACCyGRvgMtzq7fr9o6GLkXOmVj9WMgx+w==" + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[10.0.13, )", + "resolved": "10.0.13", + "contentHash": "40yrBMsLEcjixBR4G+FiUaBPvxXIPSKJ2OgSrbOZcmSDBda4Sb0k7BWrfDpIvn3IvvDzwOL/tEPa6gDQfh6aRw==" + }, + "Microsoft.Testing.Extensions.CodeCoverage": { + "type": "Direct", + "requested": "[18.12.0, )", + "resolved": "18.12.0", + "contentHash": "BST/sWYpKk9OXO7Yxktcg6FWORRsHU0EdCbMwZ7d3OqJsMKlrNHIZ30g0Ib4k5T5acJYXRBmg9mNrTU/N6cy1g==", + "dependencies": { + "Microsoft.DiaSymReader": "2.2.10", + "Microsoft.Extensions.DependencyModel": "10.0.10", + "Microsoft.Testing.Platform": "2.5.0", + "Mono.Cecil": "0.11.6" + } + }, + "Microsoft.Testing.Extensions.TrxReport": { + "type": "Direct", + "requested": "[2.5.1, )", + "resolved": "2.5.1", + "contentHash": "NHqpG2UAVsXoNnNZtyIjxgsfPe9N/E6eTHjHCs7uaSbdOCOsbX5I7N0d1BHyjM7vv3Q5R2nlvngKSHWT+ZCRXQ==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.5.1", + "Microsoft.Testing.Platform": "[2.5.1, 3.0.0)" + } + }, + "MSTest.TestAdapter": { + "type": "Direct", + "requested": "[4.5.1, )", + "resolved": "4.5.1", + "contentHash": "0q9TkkpOF3YjXGMfjcQG5bftpu/NPhVqKFL4DLUDJK6CGO4si5z+PVxVL1fM6TXEFSHnCM09E1691nhn8GUTYg==", + "dependencies": { + "MSTest.TestFramework": "4.5.1", + "Microsoft.TestPlatform.ObjectModel": "18.10.1", + "Microsoft.Testing.Extensions.Telemetry": "2.5.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.5.1", + "Microsoft.Testing.Platform.MSBuild": "2.5.1" + } + }, + "MSTest.TestFramework": { + "type": "Direct", + "requested": "[4.5.1, )", + "resolved": "4.5.1", + "contentHash": "mdYgQYAtDGq/IixJq7oD6VvIYAz+mRniGMxyhlZ2wQOpIeFe6oFQ/RniT3i7c3ZfCTKykhI6AB0wqRYVEWZDgQ==", + "dependencies": { + "MSTest.Analyzers": "4.5.1" + } + }, + "FSharp.Core": { + "type": "Transitive", + "resolved": "5.0.2", + "contentHash": "DpcajqENgb3VXaHw1FKfVS+TWwEzck1PCajqLwBAVrtd1lfxd7T8JISVZBdV1mX9+2g48+tIgpNaVJWObdMrBw==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" + }, + "Microsoft.DiaSymReader": { + "type": "Transitive", + "resolved": "2.2.10", + "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "2.5.1", + "contentHash": "mu2iyoHovwl0q1mGeeRU0h8+LALXC7HqbxniLa1shTKYdJGl+KPoCEgjWIQ8MLzk1tlTGPOfg1JsxdH3RxhMCA==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "[2.5.1, 3.0.0)" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "2.5.1", + "contentHash": "DYTZAguaHz0wNJdHDTTGYOXtnlDoLoIbTMDKSApNNcytm55DJcnwpwELfQGLk5DB5QlgaEjNhnGo1Pu5QvL5VA==", + "dependencies": { + "Microsoft.Testing.Platform": "[2.5.1, 3.0.0)" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "2.5.1", + "contentHash": "FAMaFFWnULW+hDHMb40Qzhcgz9cAU2/EtgRCBNX/DdQoKo0sPG2EiY+w7bQdl/BgHnf/DbpXLbf5Xy0o15M8NA==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "2.5.1", + "contentHash": "lYawcAwY+h76rQmpma2n0HaL8zkOFbbKapso0Eb+uOAUQn7333pIByLftEuiO67csAoZufHNMwi6PqhNemWQXA==", + "dependencies": { + "Microsoft.Testing.Platform": "[2.5.1, 3.0.0)" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.10.1", + "contentHash": "o+PuLDRVp1iwiVAPBADZItXgm9Ck23XtPhE4kZtZVi0uYnMiiz1Dx9Cjh75kJ+ijfqnORL3VOTOaQVKi0f/ixg==" + }, + "Mono.Cecil": { + "type": "Transitive", + "resolved": "0.11.6", + "contentHash": "f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA==" + }, + "MSTest.Analyzers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "oa0w2QC/T3U+ulUn38AmbWE/wnEMtqSFRb2z277z5SpswqNc/ySWcAVlG1qEWUxzm+NztCjkoeLAn/83gIx3ug==" + }, + "Dorssel.GitVersion.MsBuild": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/FuzzingTests/packages.lock.json.license b/FuzzingTests/packages.lock.json.license new file mode 100644 index 0000000..8e46c87 --- /dev/null +++ b/FuzzingTests/packages.lock.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Frans van Dorsselaer + +SPDX-License-Identifier: MIT diff --git a/GitVersion.MsBuild/AssemblySettings.cs b/GitVersion.MsBuild/AssemblySettings.cs new file mode 100644 index 0000000..a38b04d --- /dev/null +++ b/GitVersion.MsBuild/AssemblySettings.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2026 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("FuzzingTests, PublicKey = " + + "002400000480000094010000060200000024000052534131000c000001000100f1c9097124f926" // DevSkim: ignore DS173237 + + "60242335d835f2f0bc70ff592f63bd0fb41d3a753441884f35c53253a0eb123a01d960dfba3903" // DevSkim: ignore DS173237 + + "7470b7272d51ade1af6fca95d260075ec71cae6559cd9e20558a6f281f53ec76108cbc4a55c5dd" // DevSkim: ignore DS173237 + + "05ceac08699305fdbdb3a7bce263b9b920a1ea077d3ad05736a47ade8a2489c33b62a37115dc64" // DevSkim: ignore DS173237 + + "6758f0f5d804e01d9729514e8ac719b9b861bf6ae820c34cd60b3bb71f4042f637829ac42eb105" // DevSkim: ignore DS173237 + + "f3d55cbb10dae14ff7c84198b8a74328eca4c5815cac4734d3c2581496cbbe253cff89bdb33f8f" // DevSkim: ignore DS173237 + + "44e447f33ef3e227eb6a4e2c9e197c2dd90fc24c054be4f90e38dc95a47878854556debd0b8540" // DevSkim: ignore DS173237 + + "221cd2f782e7281191af6a5463ce34c1caf03a40d1b2c26ddf717db931b74633b9102b855699db" // DevSkim: ignore DS173237 + + "299b2c8e4c1710eee22a79d360fc953e872d5722dd3851a5cccd5697c102e455491890c0ff1e40" // DevSkim: ignore DS173237 + + "0d050a4d6efafb8e62e59df7239fda4b06856e3abb17d8b32530c43f2afffc626a94046c6d58ea" // DevSkim: ignore DS173237 + + "6af4915f06baca85b66d157ea2655dde145054dac3a5543dd7e5" // DevSkim: ignore DS173237 + )] diff --git a/GitVersion.MsBuild/GetVersion.cs b/GitVersion.MsBuild/GetVersion.cs index 4b6a4a7..d56d36b 100644 --- a/GitVersion.MsBuild/GetVersion.cs +++ b/GitVersion.MsBuild/GetVersion.cs @@ -96,7 +96,7 @@ public class GetVersion : GitVersionTaskBase public override bool Execute() { - foreach (var keyValue in GitVersionVariables.FromFile(VersionFile!)) + foreach (var keyValue in GitVersionVariables.FromJson(File.ReadAllText(VersionFile))) { GetType().GetProperty(keyValue.Key).SetValue(this, keyValue.Value); } diff --git a/GitVersion.MsBuild/GitVersionVariables.cs b/GitVersion.MsBuild/GitVersionVariables.cs index 1315163..d8832d4 100644 --- a/GitVersion.MsBuild/GitVersionVariables.cs +++ b/GitVersion.MsBuild/GitVersionVariables.cs @@ -3,26 +3,23 @@ // SPDX-License-Identifier: MIT using System.Collections.ObjectModel; +using System.Runtime.Serialization.Json; +using System.Text; namespace Dorssel.GitVersion.MsBuild; static class GitVersionVariables { - public static ReadOnlyDictionary FromFile(string versionFile) + public static ReadOnlyDictionary FromJson(string json) { - var dictionary = new Dictionary(); - - foreach (var line in File.ReadAllLines(versionFile)) - { - var keyValue = line.Split(':'); - if (keyValue.Length != 2) + var serializer = new DataContractJsonSerializer(typeof(Dictionary), + new DataContractJsonSerializerSettings() { - // opening or closing brace - continue; - } - dictionary.Add(keyValue[0].Trim(' ', '"'), keyValue[1].Trim(' ', '"', ',')); - } + UseSimpleDictionaryFormat = true + }); - return new(dictionary); + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + var result = (Dictionary)serializer.ReadObject(stream); + return new(result); } } diff --git a/gitversion-msbuild.slnx b/gitversion-msbuild.slnx index 9a69cf3..af48f09 100644 --- a/gitversion-msbuild.slnx +++ b/gitversion-msbuild.slnx @@ -22,6 +22,7 @@ SPDX-License-Identifier: MIT +