diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/Microsoft.Performance.Toolkit.Plugins.Core.Tests.csproj b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/Microsoft.Performance.Toolkit.Plugins.Core.Tests.csproj index b5bf69a1..01bb259e 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/Microsoft.Performance.Toolkit.Plugins.Core.Tests.csproj +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/Microsoft.Performance.Toolkit.Plugins.Core.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/PluginMetadataSerializationTests.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/PluginMetadataSerializationTests.cs new file mode 100644 index 00000000..644768a2 --- /dev/null +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/PluginMetadataSerializationTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.Toolkit.Plugins.Core.Metadata; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Fixture = AutoFixture.Fixture; + +namespace Microsoft.Performance.Toolkit.Plugins.Core.Tests; + +[TestClass] +public sealed class PluginMetadataSerializationTests +{ + private Fixture? fixture = null; + + [TestInitialize] + public void Setup() + { + this.fixture = new Fixture(); + } + + [TestMethod] + public void PluginMetadata_Serialization_RoundTrip() + { + SerializationTestsHelper.RunSerializationTest(this.fixture!); + } + + [TestMethod] + public void PluginContentsMetadata_Serialization_RoundTrip() + { + SerializationTestsHelper.RunSerializationTest(this.fixture!); + } +} diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/SerializationTestsHelper.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/SerializationTestsHelper.cs new file mode 100644 index 00000000..3013c5e1 --- /dev/null +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core.Tests/SerializationTestsHelper.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AutoFixture; +using Microsoft.Performance.Toolkit.Plugins.Core.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Performance.Toolkit.Plugins.Core.Tests; + +public class SerializationTestsHelper +{ + public static void RunSerializationTest(Fixture fixture) where T : class + { + ISerializer serializer = SerializationUtils.GetJsonSerializerWithDefaultOptions(); + + T original = fixture.Create(); + + using var stream = new MemoryStream(); + serializer.Serialize(stream, original); + + stream.Position = 0; + + T deserialized = serializer.Deserialize(stream); + + Assert.IsNotNull(deserialized); + Assert.IsTrue(original.Equals(deserialized)); + } +} diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/EnumerableExtensions.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/EnumerableExtensions.cs index 94782dbf..6eed906f 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/EnumerableExtensions.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/EnumerableExtensions.cs @@ -11,7 +11,7 @@ namespace Microsoft.Performance.Toolkit.Plugins.Core /// /// Provides extension methods for . /// - internal static class EnumerableExtensions + public static class EnumerableExtensions { /// /// Determines whether two sequences are equal by comparing their elements by using the default equality comparer for their type. diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Metadata/PluginMetadata.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Metadata/PluginMetadata.cs index 2ba15e16..20076b8d 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Metadata/PluginMetadata.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Metadata/PluginMetadata.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using Microsoft.Performance.SDK; -using Microsoft.Performance.Toolkit.Plugins.Core.Serialization; using NuGet.Versioning; namespace Microsoft.Performance.Toolkit.Plugins.Core.Metadata @@ -87,7 +86,6 @@ public PluginMetadata( /// /// Gets the version of the performance SDK which this plugin depends upon. /// - [JsonConverter(typeof(SemanticVersionJsonConverter))] public SemanticVersion SdkVersion { get; } /// diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/PluginIdentity.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/PluginIdentity.cs index 590f8aa2..8dd53b01 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/PluginIdentity.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/PluginIdentity.cs @@ -36,7 +36,7 @@ public PluginIdentity(string id, PluginVersion version) } /// - /// Gets the identifer of this plugin. + /// Gets the identifier of this plugin. /// public string Id { get; } diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/PluginVersionJsonConverter.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/PluginVersionJsonConverter.cs index ee63a5b8..3cc7f68c 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/PluginVersionJsonConverter.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/PluginVersionJsonConverter.cs @@ -7,13 +7,19 @@ namespace Microsoft.Performance.Toolkit.Plugins.Core.Serialization { + /// + /// Converts a to and from JSON. + /// public class PluginVersionJsonConverter : JsonConverter { + /// public override PluginVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var versionString = reader.GetString(); return versionString is null ? null : PluginVersion.Parse(versionString); } + + /// public override void Write(Utf8JsonWriter writer, PluginVersion value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SemanticVersionConverter.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SemanticVersionConverter.cs index 81402f99..7eee49a1 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SemanticVersionConverter.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SemanticVersionConverter.cs @@ -8,14 +8,19 @@ namespace Microsoft.Performance.Toolkit.Plugins.Core.Serialization { + /// + /// Converts a to and from JSON. + /// public class SemanticVersionJsonConverter : JsonConverter { + /// public override SemanticVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var versionString = reader.GetString(); return versionString is null ? null : SemanticVersion.Parse(versionString); } + /// public override void Write(Utf8JsonWriter writer, SemanticVersion value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); diff --git a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SerializationUtils.cs b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SerializationUtils.cs index 81dfe12f..ae9f6d47 100644 --- a/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SerializationUtils.cs +++ b/src/PluginsSystem/Microsoft.Performance.Toolkit.Plugins.Core/Serialization/SerializationUtils.cs @@ -54,28 +54,9 @@ public static ISerializer GetJsonSerializer(JsonSerializerOptions serializ Converters = { new JsonStringEnumConverter(), - new VersionStringConverter(), + new PluginVersionJsonConverter(), + new SemanticVersionJsonConverter(), } }; - - internal class VersionStringConverter - : JsonConverter - { - public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - string stringValue = reader.GetString(); - return new Version(stringValue); - } - - throw new JsonException(); - } - - public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } } } diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/Microsoft.Performance.Toolkit.Plugins.Cli.Tests.csproj b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/Microsoft.Performance.Toolkit.Plugins.Cli.Tests.csproj new file mode 100644 index 00000000..4b292333 --- /dev/null +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/Microsoft.Performance.Toolkit.Plugins.Cli.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/PluginManifestSerializationTests.cs b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/PluginManifestSerializationTests.cs new file mode 100644 index 00000000..d8d21a44 --- /dev/null +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli.Tests/PluginManifestSerializationTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Performance.Toolkit.Plugins.Cli.Manifest; +using Microsoft.Performance.Toolkit.Plugins.Core.Tests; +using Fixture = AutoFixture.Fixture; + +namespace Microsoft.Performance.Toolkit.Plugins.Cli.Tests; + +[TestClass] +public sealed class PluginManifestSerializationTest +{ + private Fixture? fixture = null; + + [TestInitialize] + public void Setup() + { + this.fixture = new Fixture(); + } + + [TestMethod] + public void PluginManifest_Serialization_RoundTrip() + { + SerializationTestsHelper.RunSerializationTest(this.fixture!); + } +} diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginIdentityManifest.cs b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginIdentityManifest.cs index a0e87ae7..7dd0b51b 100644 --- a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginIdentityManifest.cs +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginIdentityManifest.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.Performance.SDK; using Microsoft.Performance.Toolkit.Plugins.Core; -using Microsoft.Performance.Toolkit.Plugins.Core.Serialization; -using System.Text.Json.Serialization; namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest { @@ -11,6 +10,7 @@ namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest /// Represents the identity of a plugin in a manifest. /// public sealed class PluginIdentityManifest + : IEquatable { /// /// Initializes a new instance of the @@ -25,6 +25,9 @@ public PluginIdentityManifest( string id, PluginVersion version) { + Guard.NotNull(id, nameof(id)); + Guard.NotNull(version, nameof(version)); + this.Id = id; this.Version = version; } @@ -37,7 +40,55 @@ public PluginIdentityManifest( /// /// Gets or sets the version of this plugin. /// - [JsonConverter(typeof(PluginVersionJsonConverter))] public PluginVersion Version { get; } + + /// + public override bool Equals(object? obj) + { + return Equals(obj as PluginIdentityManifest); + } + + /// + public bool Equals(PluginIdentityManifest? other) + { + return Equals(this, other); + } + + /// + /// Determines whether the specified instances are considered equal. + /// + /// + /// The first plugin identity to compare. + /// + /// + /// The second plugin identity to compare. + /// + /// + /// true if a and b are considered equal; false otherwise. + /// If a or b is null, the method returns false. + /// + public static bool Equals(PluginIdentityManifest? a, PluginIdentityManifest? b) + { + if (ReferenceEquals(a, b)) + { + return true; + } + + if (a is null || b is null) + { + return false; + } + + return string.Equals(a.Id, b.Id, StringComparison.Ordinal) && + PluginVersion.Equals(a.Version, b.Version); + } + + /// + public override int GetHashCode() + { + return HashCodeUtils.CombineHashCodeValues( + this.Id.GetHashCode(), + this.Version.GetHashCode()); + } } } diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginManifest.cs b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginManifest.cs index 975d3c54..9f8c8777 100644 --- a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginManifest.cs +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginManifest.cs @@ -1,12 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.Performance.Toolkit.Plugins.Core; +using Microsoft.Performance.SDK; +using System.Text.Json.Serialization; + namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest { /// /// Represents the manifest for a plugin. /// public sealed class PluginManifest + : IEquatable { /// /// Initializes a new instance of the s @@ -26,6 +31,7 @@ public sealed class PluginManifest /// /// The URL of the project that owns this plugin. /// + [JsonConstructor] public PluginManifest( PluginIdentityManifest identity, string displayName, @@ -65,5 +71,51 @@ public PluginManifest( /// Gets the URL of the project that owns this plugin. /// public Uri ProjectUrl { get; } + + /// + public override bool Equals(object? obj) + { + return Equals(obj as PluginManifest); + } + + /// + public bool Equals(PluginManifest? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.Identity.Equals(other.Identity) + && this.DisplayName == other.DisplayName + && this.Description == other.Description + && this.Owners.EnumerableEqual(other.Owners) + && this.ProjectUrl == other.ProjectUrl; + } + + /// + public override int GetHashCode() + { + int result = HashCodeUtils.CombineHashCodeValues( + this.Identity?.GetHashCode() ?? 0, + this.DisplayName?.GetHashCode() ?? 0, + this.Description?.GetHashCode() ?? 0, + this.ProjectUrl?.GetHashCode() ?? 0); + + if (this.Owners != null) + { + foreach (PluginOwnerInfoManifest owner in this.Owners) + { + result = HashCodeUtils.CombineHashCodeValues(result, owner?.GetHashCode() ?? 0); + } + } + + return result; + } } } diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginOwnerInfoManifest.cs b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginOwnerInfoManifest.cs index 48b6815e..e7642dca 100644 --- a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginOwnerInfoManifest.cs +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Manifest/PluginOwnerInfoManifest.cs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.Performance.SDK; +using Microsoft.Performance.Toolkit.Plugins.Core; + namespace Microsoft.Performance.Toolkit.Plugins.Cli.Manifest { /// /// Contains information about the plugin owner in the manifest. /// public sealed class PluginOwnerInfoManifest + : IEquatable { /// /// Initializes a new instance of the @@ -54,5 +58,56 @@ public PluginOwnerInfoManifest( /// Gets the phone numbers of the owner, if any. /// public IEnumerable PhoneNumbers { get; } + + /// + public override bool Equals(object? obj) + { + return Equals(obj as PluginOwnerInfoManifest); + } + + /// + public bool Equals(PluginOwnerInfoManifest? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return string.Equals(this.Name, other.Name, StringComparison.Ordinal) + && string.Equals(this.Address, other.Address, StringComparison.Ordinal) + && this.EmailAddresses.EnumerableEqual(other.EmailAddresses, StringComparer.Ordinal) + && this.PhoneNumbers.EnumerableEqual(other.PhoneNumbers, StringComparer.Ordinal); + } + + /// + public override int GetHashCode() + { + int result = HashCodeUtils.CombineHashCodeValues( + this.Name?.GetHashCode() ?? 0, + this.Address?.GetHashCode() ?? 0); + + if (this.PhoneNumbers != null) + { + foreach (string phone in this.PhoneNumbers) + { + HashCodeUtils.CombineHashCodeValues(result, phone?.GetHashCode() ?? 0); + } + } + + if (this.EmailAddresses != null) + { + foreach (string email in this.EmailAddresses) + { + HashCodeUtils.CombineHashCodeValues(result, email?.GetHashCode() ?? 0); + } + } + + return result; + } } } diff --git a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Program.cs b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Program.cs index 40ba0ec2..96a575ba 100644 --- a/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Program.cs +++ b/src/PluginsSystem/Tools/Microsoft.Performance.Toolkit.Plugins.Cli/Program.cs @@ -33,10 +33,20 @@ public sealed class Program /// public static int Main(string[] args) { - return CreatPluginsCli().Run(args); + return CreatePluginsCli().Run(args); } - private static PluginsCli CreatPluginsCli() + public static readonly JsonSerializerOptions PluginManifestSerializerDefaultOptions = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new PluginVersionJsonConverter(), + } + }; + + private static PluginsCli CreatePluginsCli() { ServiceProvider serviceProvider = new ServiceCollection() .AddLogging(x => x.AddConsole()) @@ -47,11 +57,7 @@ private static PluginsCli CreatPluginsCli() .AddSingleton, MetadataGenOptionsValidator>() .AddSingleton() .AddSingleton() - .AddSingleton>(SerializationUtils.GetJsonSerializer(new JsonSerializerOptions - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - })) + .AddSingleton>(SerializationUtils.GetJsonSerializer(PluginManifestSerializerDefaultOptions)) .AddSingleton>(SerializationUtils.GetJsonSerializerWithDefaultOptions()) .AddSingleton>(SerializationUtils.GetJsonSerializerWithDefaultOptions()) .AddSingleton() diff --git a/src/SDK.sln b/src/SDK.sln index d11fb79b..52dfcf69 100644 --- a/src/SDK.sln +++ b/src/SDK.sln @@ -51,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.json = version.json EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Performance.Toolkit.Plugins.Cli.Tests", "PluginsSystem\Tools\Microsoft.Performance.Toolkit.Plugins.Cli.Tests\Microsoft.Performance.Toolkit.Plugins.Cli.Tests.csproj", "{025EB03D-028A-401F-AFA8-731FCF90DD8A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -265,6 +267,18 @@ Global {B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x64.Build.0 = Release|Any CPU {B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x86.ActiveCfg = Release|Any CPU {B010E944-C96D-4152-8CA6-38E5D53070E1}.Release|x86.Build.0 = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|x64.ActiveCfg = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|x64.Build.0 = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|x86.ActiveCfg = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Debug|x86.Build.0 = Debug|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|Any CPU.Build.0 = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|x64.ActiveCfg = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|x64.Build.0 = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|x86.ActiveCfg = Release|Any CPU + {025EB03D-028A-401F-AFA8-731FCF90DD8A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -277,6 +291,7 @@ Global {86E14EA1-084E-4C21-AA14-AAAC83992D86} = {F5125921-DE2A-43BC-89C6-8584E2F23867} {487C7320-8CCF-45BC-9622-1B55B5194259} = {86E14EA1-084E-4C21-AA14-AAAC83992D86} {B010E944-C96D-4152-8CA6-38E5D53070E1} = {AB9CEC9E-61E6-4FDC-912B-6848C3CD4066} + {025EB03D-028A-401F-AFA8-731FCF90DD8A} = {86E14EA1-084E-4C21-AA14-AAAC83992D86} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C9B73E61-5E48-45BD-B144-AFEFF5E44A52}