Add tests of client code generation components (#13444)

- #4914
- test service ref MSBuild tasks
  - add `MockBuildEngine` class to support tests where a task logs
- correct an incredibly minor product issue about logging errors for `@(OpenApiProjectReference)` items
  - set `%(SourceProject)` metadata and use it when logging
- add test project that builds and can be tested in the future
This commit is contained in:
Doug Bunting 2019-08-26 20:42:13 -07:00 committed by GitHub
parent 2d8cd179a1
commit 8ea3edadaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1331 additions and 48 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization;

View File

@ -63,8 +63,7 @@ namespace Microsoft.Extensions.ApiDescription.Client
"OpenApiReference" :
"OpenApiProjectReference";
Log.LogError(
Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiReference", item.ItemSpec));
Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec));
continue;
}

View File

@ -0,0 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.ApiDescription.Client.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -48,7 +48,9 @@
</MSBuild>
<ItemGroup>
<OpenApiReference Include="@(_Temporary)" Exclude="@(OpenApiReference)" RemoveMetadata="FullConfiguration" />
<OpenApiReference Include="@(_Temporary)" Exclude="@(OpenApiReference)" RemoveMetadata="FullConfiguration">
<SourceProject>%(_Temporary.OriginalItemSpec)</SourceProject>
</OpenApiReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>

View File

@ -0,0 +1,107 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.Extensions.ApiDescription.Client
{
public class CSharpIdentifierTest
{
[Theory]
[InlineData('a')]
[InlineData('Q')]
[InlineData('\u2164')] // UnicodeCategory.LetterNumber (roman numeral five)
[InlineData('_')]
[InlineData('9')]
[InlineData('\u0303')] // UnicodeCategory.NonSpacingMark (combining tilde)
[InlineData('\u09CB')] // UnicodeCategory.SpacingCombiningMark (Bengali vowel sign O)
[InlineData('\uFE4F')] // UnicodeCategory.ConnectorPunctuation (wavy low line)
[InlineData('\u2062')] // UnicodeCategory.Format (invisible times)
public void IsIdentifierPart_ReturnsTrue_WhenItShould(char character)
{
// Arrange and Act
var result = CSharpIdentifier.IsIdentifierPart(character);
// Assert
Assert.True(result);
}
[Theory]
[InlineData('/')]
[InlineData('-')]
[InlineData('\u20DF')] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
[InlineData('\u2005')] // UnicodeCategory.SpaceSeparator (four-per-em space)
[InlineData('\u0096')] // UnicodeCategory.Control (start of guarded area)
[InlineData('\uFF1C')] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
public void IsIdentifierPart_ReturnsFalse_WhenItShould(char character)
{
// Arrange and Act
var result = CSharpIdentifier.IsIdentifierPart(character);
// Assert
Assert.False(result);
}
// Output length is one longer than input in these cases.
[Theory]
[InlineData("9", "_9")]
[InlineData("\u0303", "_\u0303")] // UnicodeCategory.NonSpacingMark (combining tilde)
[InlineData("\u09CB", "_\u09CB")] // UnicodeCategory.SpacingCombiningMark (Bengali vowel sign O)
[InlineData("\uFE4F", "_\uFE4F")] // UnicodeCategory.ConnectorPunctuation (wavy low line)
[InlineData("\u2062", "_\u2062")] // UnicodeCategory.Format (invisible times)
public void SanitizeIdentifier_AddsUnderscore_WhenItShould(string input, string expectdOutput)
{
// Arrange and Act
var output = CSharpIdentifier.SanitizeIdentifier(input);
// Assert
Assert.Equal(expectdOutput, output);
}
[Theory]
[InlineData("a", "a")]
[InlineData("Q", "Q")]
[InlineData("\u2164", "\u2164")]
[InlineData("_", "_")]
public void SanitizeIdentifier_DoesNotAddUnderscore_WhenValidStartCharacter(string input, string expectdOutput)
{
// Arrange and Act
var output = CSharpIdentifier.SanitizeIdentifier(input);
// Assert
Assert.Equal(expectdOutput, output);
}
[Theory]
[InlineData("/", "_")]
[InlineData("-", "_")]
[InlineData("\u20DF", "_")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
[InlineData("\u2005", "_")] // UnicodeCategory.SpaceSeparator (four-per-em space)
[InlineData("\u0096", "_")] // UnicodeCategory.Control (start of guarded area)
[InlineData("\uFF1C", "_")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
public void SanitizeIdentifier_DoesNotAddUnderscore_WhenInvalidCharacter(string input, string expectdOutput)
{
// Arrange and Act
var output = CSharpIdentifier.SanitizeIdentifier(input);
// Assert
Assert.Equal(expectdOutput, output);
}
[Theory]
[InlineData("a/", "a_")]
[InlineData("aa-bb", "aa_bb")]
[InlineData("aa\u20DF\u20DF", "aa__")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
[InlineData("aa\u2005bb\u2005cc", "aa_bb_cc")] // UnicodeCategory.SpaceSeparator (four-per-em space)
[InlineData("aa\u0096\u0096bb", "aa__bb")] // UnicodeCategory.Control (start of guarded area)
[InlineData("aa\uFF1C\uFF1C\uFF1Cbb", "aa___bb")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
public void SanitizeIdentifier_ReplacesInvalidCharacters_WhenNotFirst(string input, string expectdOutput)
{
// Arrange and Act
var output = CSharpIdentifier.SanitizeIdentifier(input);
// Assert
Assert.Equal(expectdOutput, output);
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.Extensions.ApiDescription.Client
{
public class GetCurrentOpenApiReferenceTest
{
[Fact]
public void Execute_ReturnsExpectedItem()
{
// Arrange
string input = "Identity=../files/azureMonitor.json|ClassName=azureMonitorClient|" +
"CodeGenerator=NSwagCSharp|Namespace=ConsoleClient|Options=|OutputPath=" +
"C:\\dd\\dnx\\AspNetCore\\artifacts\\obj\\ConsoleClient\\azureMonitorClient.cs|" +
"OriginalItemSpec=../files/azureMonitor.json|FirstForGenerator=true";
var task = new GetCurrentOpenApiReference
{
Input = input,
};
string expectedIdentity = "../files/azureMonitor.json";
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", "azureMonitorClient" },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", "ConsoleClient" },
{ "Options", "" },
{ "OriginalItemSpec", expectedIdentity },
{ "OutputPath", "C:\\dd\\dnx\\AspNetCore\\artifacts\\obj\\ConsoleClient\\azureMonitorClient.cs" },
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(expectedIdentity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
}
}

View File

@ -0,0 +1,576 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Utilities;
using Xunit;
namespace Microsoft.Extensions.ApiDescription.Client
{
public class GetOpenApiReferenceMetadataTest
{
[Fact]
public void Execute_AddsExpectedMetadata()
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var @namespace = "Console.Client";
var outputPath = Path.Combine("obj", "NSwagClient.cs");
var inputMetadata = new Dictionary<string, string> { { "CodeGenerator", "NSwagCSharp" } };
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = @namespace,
OutputDirectory = "obj",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", "NSwagClient" },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", outputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"OutputPath={outputPath}|ClassName=NSwagClient|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Fact]
public void Execute_DoesNotOverrideClassName()
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var className = "ThisIsClassy";
var @namespace = "Console.Client";
var outputPath = Path.Combine("obj", $"NSwagClient.cs");
var inputMetadata = new Dictionary<string, string>
{
{ "CodeGenerator", "NSwagCSharp" },
{ "ClassName", className },
};
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = @namespace,
OutputDirectory = "obj",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", outputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"ClassName={className}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"OutputPath={outputPath}|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Fact]
public void Execute_DoesNotOverrideNamespace()
{
// Arrange
var defaultNamespace = "Console.Client";
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var @namespace = "NotConsole.NotClient";
var outputPath = Path.Combine("obj", "NSwagClient.cs");
var inputMetadata = new Dictionary<string, string>
{
{ "CodeGenerator", "NSwagCSharp" },
{ "Namespace", @namespace },
};
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = defaultNamespace,
OutputDirectory = "obj",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", "NSwagClient" },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", outputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"Namespace={@namespace}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"OutputPath={outputPath}|ClassName=NSwagClient"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Fact]
public void Execute_DoesNotOverrideOutputPath_IfRooted()
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var className = "ThisIsClassy";
var @namespace = "Console.Client";
var outputPath = Path.Combine(Path.GetTempPath(), $"{className}.cs");
var inputMetadata = new Dictionary<string, string>
{
{ "CodeGenerator", "NSwagCSharp" },
{ "OutputPath", outputPath }
};
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = @namespace,
OutputDirectory = "bin",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", outputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"OutputPath={outputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"ClassName={className}|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Fact]
public void Execute_LogsError_IfCodeGeneratorMissing()
{
// Arrange
var identity1 = Path.Combine("TestProjects", "files", "NSwag.json");
var identity2 = Path.Combine("TestProjects", "files", "swashbuckle.json");
var error1 = Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiReference", identity1);
var error2 = Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiProjectReference", identity2);
var @namespace = "Console.Client";
var inputMetadata1 = new Dictionary<string, string>
{
{ "ExtraMetadata", "this is extra" },
};
var inputMetadata2 = new Dictionary<string, string>
{
{ "Options", "-quiet" },
{ "SourceProject", "ConsoleProject.csproj" },
};
var buildEngine = new MockBuildEngine();
var task = new GetOpenApiReferenceMetadata
{
BuildEngine = buildEngine,
Extension = ".cs",
Inputs = new[]
{
new TaskItem(identity1, inputMetadata1),
new TaskItem(identity2, inputMetadata2),
},
Namespace = @namespace,
OutputDirectory = "obj",
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
Assert.True(task.Log.HasLoggedErrors);
Assert.Equal(2, buildEngine.Errors);
Assert.Equal(0, buildEngine.Messages);
Assert.Equal(0, buildEngine.Warnings);
Assert.Contains(error1, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
Assert.Contains(error2, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Execute_LogsError_IfOutputPathDuplicated()
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var codeGenerator = "NSwagCSharp";
var error = Resources.FormatDuplicateFileOutputPaths(Path.Combine("obj", "NSwagClient.cs"));
var @namespace = "Console.Client";
var inputMetadata1 = new Dictionary<string, string>
{
{ "CodeGenerator", codeGenerator },
{ "ExtraMetadata", "this is extra" },
};
var inputMetadata2 = new Dictionary<string, string>
{
{ "CodeGenerator", codeGenerator },
{ "Options", "-quiet" },
};
var buildEngine = new MockBuildEngine();
var task = new GetOpenApiReferenceMetadata
{
BuildEngine = buildEngine,
Extension = ".cs",
Inputs = new[]
{
new TaskItem(identity, inputMetadata1),
new TaskItem(identity, inputMetadata2),
},
Namespace = @namespace,
OutputDirectory = "obj",
};
// Act
var result = task.Execute();
// Assert
Assert.False(result);
Assert.True(task.Log.HasLoggedErrors);
Assert.Equal(1, buildEngine.Errors);
Assert.Equal(0, buildEngine.Messages);
Assert.Equal(0, buildEngine.Warnings);
Assert.Contains(error, buildEngine.Log, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Execute_SetsClassName_BasedOnOutputPath()
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var className = "ThisIsClassy";
var @namespace = "Console.Client";
var outputPath = $"{className}.cs";
var expectedOutputPath = Path.Combine("bin", outputPath);
var inputMetadata = new Dictionary<string, string>
{
{ "CodeGenerator", "NSwagCSharp" },
{ "OutputPath", outputPath }
};
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = @namespace,
OutputDirectory = "bin",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", expectedOutputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"OutputPath={expectedOutputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"ClassName={className}|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Theory]
[InlineData("aa-bb.cs", "aa_bb")]
[InlineData("aa.bb.cc.ts", "aa_bb_cc")]
[InlineData("aa\u20DF\u20DF.tsx", "aa__")] // UnicodeCategory.EnclosingMark (combining enclosing diamond)
[InlineData("aa\u2005bb\u2005cc.cs", "aa_bb_cc")] // UnicodeCategory.SpaceSeparator (four-per-em space)
[InlineData("aa\u0096\u0096bb.cs", "aa__bb")] // UnicodeCategory.Control (start of guarded area)
[InlineData("aa\uFF1C\uFF1C\uFF1Cbb.cs", "aa___bb")] // UnicodeCategory.MathSymbol (fullwidth less-than sign)
public void Execute_SetsClassName_BasedOnSanitizedOutputPath(string outputPath, string className)
{
// Arrange
var identity = Path.Combine("TestProjects", "files", "NSwag.json");
var @namespace = "Console.Client";
var expectedOutputPath = Path.Combine("bin", outputPath);
var inputMetadata = new Dictionary<string, string>
{
{ "CodeGenerator", "NSwagCSharp" },
{ "OutputPath", outputPath }
};
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[] { new TaskItem(identity, inputMetadata) },
Namespace = @namespace,
OutputDirectory = "bin",
};
IDictionary<string, string> expectedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className },
{ "CodeGenerator", "NSwagCSharp" },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity },
{ "OutputPath", expectedOutputPath },
{
"SerializedMetadata",
$"Identity={identity}|CodeGenerator=NSwagCSharp|" +
$"OutputPath={expectedOutputPath}|OriginalItemSpec={identity}|FirstForGenerator=true|" +
$"ClassName={className}|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
var output = Assert.Single(task.Outputs);
Assert.Equal(identity, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
// The dictionary CloneCustomMetadata returns doesn't provide a useful KeyValuePair enumerator.
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata, orderedMetadata);
}
[Fact]
public void Execute_SetsFirstForGenerator_UsesCorrectExtension()
{
// Arrange
var identity12 = Path.Combine("TestProjects", "files", "NSwag.json");
var identity3 = Path.Combine("TestProjects", "files", "swashbuckle.json");
var className12 = "NSwagClient";
var className3 = "swashbuckleClient";
var codeGenerator13 = "NSwagCSharp";
var codeGenerator2 = "NSwagTypeScript";
var inputMetadata1 = new Dictionary<string, string> { { "CodeGenerator", codeGenerator13 } };
var inputMetadata2 = new Dictionary<string, string> { { "CodeGenerator", codeGenerator2 } };
var inputMetadata3 = new Dictionary<string, string> { { "CodeGenerator", codeGenerator13 } };
var @namespace = "Console.Client";
var outputPath1 = Path.Combine("obj", $"{className12}.cs");
var outputPath2 = Path.Combine("obj", $"{className12}.ts");
var outputPath3 = Path.Combine("obj", $"{className3}.cs");
var task = new GetOpenApiReferenceMetadata
{
Extension = ".cs",
Inputs = new[]
{
new TaskItem(identity12, inputMetadata1),
new TaskItem(identity12, inputMetadata2),
new TaskItem(identity3, inputMetadata3),
},
Namespace = @namespace,
OutputDirectory = "obj",
};
IDictionary<string, string> expectedMetadata1 = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className12 },
{ "CodeGenerator", codeGenerator13 },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity12 },
{ "OutputPath", outputPath1 },
{
"SerializedMetadata",
$"Identity={identity12}|CodeGenerator={codeGenerator13}|" +
$"OriginalItemSpec={identity12}|FirstForGenerator=true|" +
$"OutputPath={outputPath1}|ClassName={className12}|Namespace={@namespace}"
},
};
IDictionary<string, string> expectedMetadata2 = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className12 },
{ "CodeGenerator", codeGenerator2 },
{ "FirstForGenerator", "true" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity12 },
{ "OutputPath", outputPath2 },
{
"SerializedMetadata",
$"Identity={identity12}|CodeGenerator={codeGenerator2}|" +
$"OriginalItemSpec={identity12}|FirstForGenerator=true|" +
$"OutputPath={outputPath2}|ClassName={className12}|Namespace={@namespace}"
},
};
IDictionary<string, string> expectedMetadata3 = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
{ "ClassName", className3 },
{ "CodeGenerator", codeGenerator13 },
{ "FirstForGenerator", "false" },
{ "Namespace", @namespace },
{ "OriginalItemSpec", identity3 },
{ "OutputPath", outputPath3 },
{
"SerializedMetadata",
$"Identity={identity3}|CodeGenerator={codeGenerator13}|" +
$"OriginalItemSpec={identity3}|FirstForGenerator=false|" +
$"OutputPath={outputPath3}|ClassName={className3}|Namespace={@namespace}"
},
};
// Act
var result = task.Execute();
// Assert
Assert.True(result);
Assert.False(task.Log.HasLoggedErrors);
Assert.Collection(
task.Outputs,
output =>
{
Assert.Equal(identity12, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata1, orderedMetadata);
},
output =>
{
Assert.Equal(identity12, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata2, orderedMetadata);
},
output =>
{
Assert.Equal(identity3, output.ItemSpec);
var metadata = Assert.IsAssignableFrom<IDictionary<string, string>>(output.CloneCustomMetadata());
var orderedMetadata = new SortedDictionary<string, string>(StringComparer.Ordinal);
foreach (var key in metadata.Keys)
{
orderedMetadata.Add(key, metadata[key]);
}
Assert.Equal(expectedMetadata3, orderedMetadata);
});
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<DefaultItemExcludes>$(DefaultItemExcludes);TestProjects\**\*</DefaultItemExcludes>
<TestGroupName>ApiDescriptionClientTests</TestGroupName>
</PropertyGroup>
<ItemGroup>
<Content Include="$(MSBuildProjectDirectory)\..\src\build\**\*" CopyToOutputDirectory="PreserveNewest" LinkBase="build" />
<Content Include="$(MSBuildProjectDirectory)\..\src\buildMultiTargeting\**\*" CopyToOutputDirectory="PreserveNewest" LinkBase="buildMultiTargeting" />
<Content Include="TestProjects\**\*" CopyToOutputDirectory="PreserveNewest" />
<ProjectReference Include="..\src\Microsoft.Extensions.ApiDescription.Client.csproj" />
</ItemGroup>
<Target Name="CleanTestProjects" BeforeTargets="CoreCompile">
<RemoveDir Directories="$(TargetDir)TestProjects" />
</Target>
</Project>

View File

@ -0,0 +1,102 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Framework;
// Inspired by https://github.com/microsoft/msbuild/blob/master/src/Utilities.UnitTests/MockEngine.cs
namespace Microsoft.Extensions.ApiDescription.Client
{
internal sealed class MockBuildEngine : IBuildEngine3
{
private readonly StringBuilder _log = new StringBuilder();
public bool IsRunningMultipleNodes => false;
public bool ContinueOnError => false;
public string ProjectFileOfTaskNode => string.Empty;
public int LineNumberOfTaskNode => 0;
public int ColumnNumberOfTaskNode => 0;
internal MessageImportance MinimumMessageImportance { get; set; } = MessageImportance.Low;
internal int Messages { set; get; }
internal int Warnings { set; get; }
internal int Errors { set; get; }
internal string Log => _log.ToString();
public bool BuildProjectFile(
string projectFileName,
string[] targetNames,
IDictionary globalProperties,
IDictionary targetOutputs) => false;
public bool BuildProjectFile(
string projectFileName,
string[] targetNames,
IDictionary globalProperties,
IDictionary targetOutputs,
string toolsVersion) => false;
public bool BuildProjectFilesInParallel(
string[] projectFileNames,
string[] targetNames,
IDictionary[] globalProperties,
IDictionary[] targetOutputsPerProject,
string[] toolsVersion,
bool useResultsCache,
bool unloadProjectsOnCompletion) => false;
public BuildEngineResult BuildProjectFilesInParallel(
string[] projectFileNames,
string[] targetNames,
IDictionary[] globalProperties,
IList<string>[] undefineProperties,
string[] toolsVersion,
bool includeTargetOutputs) => new BuildEngineResult(false, null);
public void LogErrorEvent(BuildErrorEventArgs eventArgs)
{
_log.AppendLine(eventArgs.Message);
Errors++;
}
public void LogWarningEvent(BuildWarningEventArgs eventArgs)
{
_log.AppendLine(eventArgs.Message);
Warnings++;
}
public void LogCustomEvent(CustomBuildEventArgs eventArgs)
{
_log.AppendLine(eventArgs.Message);
}
public void LogMessageEvent(BuildMessageEventArgs eventArgs)
{
// Only record the message if it is above the minimum importance. MessageImportance enum has higher values
// for lower importance.
if (eventArgs.Importance <= MinimumMessageImportance)
{
_log.AppendLine(eventArgs.Message);
Messages++;
}
}
public void Reacquire()
{
}
public void Yield()
{
}
}
}

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/Microsoft.Extensions.ApiDescription.Client.props"
Condition="Exists('../../build/Microsoft.Extensions.ApiDescription.Client.props')" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsTestAssetProject>true</IsTestAssetProject>
</PropertyGroup>
<ItemGroup>
<OpenApiReference Include="../files/azureMonitor.json" />
<OpenApiReference Include="../files/NSwag.json" />
<OpenApiReference Include="../files/swashbuckle.json" />
</ItemGroup>
<!-- Position tasks assembly where Microsoft.Extensions.ApiDescription.Client.props expects it. -->
<Target Name="CopyTargets" BeforeTargets="BeforeBuild">
<ItemGroup>
<_Files Include="../../Microsoft.Extensions.ApiDescription.Client.*"
Exclude="../../Microsoft.Extensions.ApiDescription.Client.Tests.*" />
</ItemGroup>
<Copy
SourceFiles="@(_Files)"
DestinationFolder="../../tasks/netstandard2.0/"
SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" />
</Target>
<Import Project="../../build/Microsoft.Extensions.ApiDescription.Client.targets"
Condition="Exists('../../build/Microsoft.Extensions.ApiDescription.Client.targets')" />
<Import Project="../build/Fakes.targets" Condition="EXISTS('../build/Fakes.targets')" />
</Project>

View File

@ -0,0 +1,12 @@
using System;
namespace ConsoleClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<!-- NSwag's OpenApiReference support for C# (default target). -->
<Target Name="GenerateNSwagCSharp">
<PropertyGroup>
<_Metadata>'%(CurrentOpenApiReference.FullPath)'</_Metadata>
<_Metadata>$(_Metadata) Class: '%(CurrentOpenApiReference.Namespace).%(CurrentOpenApiReference.ClassName)'</_Metadata>
<_Metadata>$(_Metadata) FirstForGenerator: '%(CurrentOpenApiReference.FirstForGenerator)'</_Metadata>
<_Metadata>$(_Metadata) Options: '%(CurrentOpenApiReference.Options)'</_Metadata>
<_Metadata>$(_Metadata) OutputPath: '%(CurrentOpenApiReference.OutputPath)'</_Metadata>
</PropertyGroup>
<Message Importance="high" Text="GenerateNSwagCSharp $(_Metadata)" />
</Target>
<!-- NSwag's OpenApiReference support for TypeScript. -->
<Target Name="GenerateNSwagTypeScript">
<PropertyGroup>
<_Metadata>'%(FullPath)' Class: '%(Namespace).%(ClassName)'</_Metadata>
<_Metadata>$(_Metadata) FirstForGenerator: '%(FirstForGenerator)' Options: '%(Options)'</_Metadata>
<_Metadata>$(_Metadata) OutputPath: '%(OutputPath)'</_Metadata>
</PropertyGroup>
<Message Importance="high" Text="GenerateNSwagTypeScript $(_Metadata)" />
</Target>
<!-- Custom OpenApiReference support for C#. -->
<Target Name="GenerateCustomCSharp">
<PropertyGroup>
<_Metadata>'%(FullPath)' Class: '%(Namespace).%(ClassName)'</_Metadata>
<_Metadata>$(_Metadata) FirstForGenerator: '%(FirstForGenerator)' Options: '%(Options)'</_Metadata>
<_Metadata>$(_Metadata) OutputPath: '%(OutputPath)'</_Metadata>
</PropertyGroup>
<Message Importance="high" Text="GenerateCustomCSharp $(_Metadata)" />
</Target>
</Project>

View File

@ -0,0 +1,183 @@
{
"x-generator": "NSwag v11.18.6.0 (NJsonSchema v9.10.67.0 (Newtonsoft.Json v11.0.0.0))",
"swagger": "2.0",
"info": {
"title": "My Title",
"version": "1.0.0"
},
"host": "localhost:5000",
"schemes": [
"http"
],
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json",
"application/xml",
"text/xml",
"application/*+xml"
],
"produces": [
"text/plain",
"application/json",
"text/json",
"application/xml",
"text/xml"
],
"paths": {
"/api/Values/matches": {
"post": {
"tags": [
"Values"
],
"operationId": "Values_Matches",
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json",
"application/xml",
"text/xml",
"application/*+xml"
],
"parameters": [
{
"name": "possibilities",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"x-nullable": true
},
{
"type": "integer",
"name": "comparisonType",
"in": "query",
"x-schema": {
"$ref": "#/definitions/StringComparison"
},
"x-nullable": false,
"enum": [
0,
1,
2,
3,
4,
5
]
}
],
"responses": {
"200": {
"x-nullable": true,
"description": "",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/api/Values": {
"get": {
"tags": [
"Values"
],
"operationId": "Values_Get",
"responses": {
"200": {
"x-nullable": true,
"description": "",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"post": {
"tags": [
"Values"
],
"operationId": "Values_Post",
"consumes": [
"application/xml"
],
"parameters": [
{
"name": "value",
"in": "body",
"required": true,
"schema": {
"type": "string"
},
"x-nullable": true
}
],
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/Values/too": {
"get": {
"tags": [
"Values"
],
"operationId": "Values_GetToo",
"responses": {
"200": {
"x-nullable": true,
"description": "",
"schema": {
"$ref": "#/definitions/Model"
}
}
}
}
}
},
"definitions": {
"StringComparison": {
"type": "integer",
"description": "",
"x-enumNames": [
"CurrentCulture",
"CurrentCultureIgnoreCase",
"InvariantCulture",
"InvariantCultureIgnoreCase",
"Ordinal",
"OrdinalIgnoreCase"
],
"enum": [
0,
1,
2,
3,
4,
5
]
},
"Model": {
"type": "object",
"additionalProperties": false,
"properties": {
"userName": {
"type": "string"
}
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
{
"swagger": "2.0",
"info": {
"version": "and so is version",
"title": "title is required"
},
"paths": {
"/api/Values/matches": {
"post": {
"tags": [
"Values"
],
"operationId": "ApiValuesMatchesPost",
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json",
"application/xml",
"text/xml",
"application/*+xml"
],
"produces": [
"text/plain",
"application/json",
"text/json",
"application/xml",
"text/xml"
],
"parameters": [
{
"name": "possibilities",
"in": "body",
"required": false,
"schema": {
"uniqueItems": false,
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "comparisonType",
"in": "query",
"required": false,
"type": "integer",
"format": "int32",
"enum": [
0,
1,
2,
3,
4,
5
]
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"uniqueItems": false,
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/api/Values": {
"get": {
"tags": [
"Values"
],
"operationId": "ApiValuesGet",
"consumes": [],
"produces": [
"application/json"
],
"parameters": [],
"responses": {
"200": {
"description": "Success",
"schema": {
"uniqueItems": false,
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"post": {
"tags": [
"Values"
],
"operationId": "ApiValuesPost",
"consumes": [
"application/xml"
],
"produces": [],
"parameters": [
{
"name": "value",
"in": "body",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/Values/too": {
"get": {
"tags": [
"Values"
],
"operationId": "ApiValuesTooGet",
"consumes": [],
"produces": [
"application/xml"
],
"parameters": [],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/Model"
}
}
}
}
}
},
"definitions": {
"Model": {
"type": "object",
"properties": {
"userName": {
"type": "string"
}
}
}
}
}

View File

@ -23,22 +23,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.dotnet-openapi",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-microsoft.openapi.Tests", "Microsoft.dotnet-openapi\test\dotnet-microsoft.openapi.Tests.csproj", "{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client", "{78610083-1FCE-47F5-AB4D-AF0E1313B351}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0D46647-EF66-456E-9F79-134985E7445E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client\src\Microsoft.Extensions.ApiDescription.Client.csproj", "{B29B2627-3604-4FDB-A976-EF1E077F5316}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server", "{003EA860-5DFC-40AE-87C0-9D21BB2C68D7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4110117E-3C28-4064-A7A3-B112BD6F8CB9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "dotnet-getdocument\src\dotnet-getdocument.csproj", "{160A445F-7E1F-430D-9403-41F7F6F4A16E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Server", "Extensions.ApiDescription.Server\src\Microsoft.Extensions.ApiDescription.Server.csproj", "{233119FC-E4C1-421C-89AE-1A445C5A947F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "GetDocumentInsider\src\GetDocumentInsider.csproj", "{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client.Tests", "Extensions.ApiDescription.Client\test\Microsoft.Extensions.ApiDescription.Client.Tests.csproj", "{2C62584B-EC31-40C8-819B-E46334645AE5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -53,38 +49,6 @@ Global
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.Build.0 = Release|Any CPU
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.Build.0 = Release|Any CPU
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.Build.0 = Release|Any CPU
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.Build.0 = Release|Any CPU
{15FB0E39-1A28-4325-AD3C-76352516C80D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15FB0E39-1A28-4325-AD3C-76352516C80D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15FB0E39-1A28-4325-AD3C-76352516C80D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15FB0E39-1A28-4325-AD3C-76352516C80D}.Release|Any CPU.Build.0 = Release|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.Build.0 = Release|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.Build.0 = Release|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.Build.0 = Release|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.Build.0 = Release|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98550159-E04E-44EB-A969-E5BF12444B94}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -109,17 +73,31 @@ Global
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F}.Release|Any CPU.Build.0 = Release|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B29B2627-3604-4FDB-A976-EF1E077F5316}.Release|Any CPU.Build.0 = Release|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{160A445F-7E1F-430D-9403-41F7F6F4A16E}.Release|Any CPU.Build.0 = Release|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{233119FC-E4C1-421C-89AE-1A445C5A947F}.Release|Any CPU.Build.0 = Release|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6}.Release|Any CPU.Build.0 = Release|Any CPU
{2C62584B-EC31-40C8-819B-E46334645AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C62584B-EC31-40C8-819B-E46334645AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C62584B-EC31-40C8-819B-E46334645AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C62584B-EC31-40C8-819B-E46334645AE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A0D46647-EF66-456E-9F79-134985E7445E} = {78610083-1FCE-47F5-AB4D-AF0E1313B351}
{B29B2627-3604-4FDB-A976-EF1E077F5316} = {A0D46647-EF66-456E-9F79-134985E7445E}
{4110117E-3C28-4064-A7A3-B112BD6F8CB9} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
{160A445F-7E1F-430D-9403-41F7F6F4A16E} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
{233119FC-E4C1-421C-89AE-1A445C5A947F} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6} = {4110117E-3C28-4064-A7A3-B112BD6F8CB9}
{E16F10C8-5FC3-420B-AB33-D6E5CBE89B75} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
{98550159-E04E-44EB-A969-E5BF12444B94} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
@ -128,6 +106,12 @@ Global
{25F8DCC4-4571-42F7-BA0F-5C2D5A802297} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
{C806041C-30F2-4B27-918A-5FF3576B833B} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{26BBA8A7-0F69-4C5F-B1C2-16B3320FFE3F} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
{B29B2627-3604-4FDB-A976-EF1E077F5316} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{003EA860-5DFC-40AE-87C0-9D21BB2C68D7} = {E01EE27B-6CF9-4707-9849-5BA2ABA825F2}
{160A445F-7E1F-430D-9403-41F7F6F4A16E} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
{233119FC-E4C1-421C-89AE-1A445C5A947F} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
{EB63AECB-B898-475D-90F7-FE61F9C1CCC6} = {003EA860-5DFC-40AE-87C0-9D21BB2C68D7}
{2C62584B-EC31-40C8-819B-E46334645AE5} = {2C485EAF-E4DE-4D14-8AE1-330641E17D44}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC668D8E-97B9-4758-9E5C-2E5DD6B9137B}