From 27e63853493248a3b06c8761d8bd0f17ff0f4cfb Mon Sep 17 00:00:00 2001 From: Congyong Su Date: Wed, 30 Jul 2014 12:58:58 +0800 Subject: [PATCH] Build OData folder Add build related files etc. to OData folder: build.cmd WebApiOData.msbuild WebApiOData.sln .nuget tools src Common (some depending files, e.g., for Error.ArgumentNull) CommonAssemblyInfo.cs GlobalSuppressions.cs Settings.StyleCop Strict.ruleset test Common (ContextUtil.cs, MediaTypeFormatterTestBase.cs) Microsoft.TestCommon (a .csproj contains xUnit ref, Assert etc.) Settings.StyleCop Change project references to nuget ref: System.Net.Http.Formatting System.Web.Http => src packages.config: test packages.config: + Remove OData src and test projects from Runtime.sln and repositories.config Build succeeds, all test cases pass. --- OData/.nuget/NuGet.Config | 11 + OData/.nuget/packages.config | 6 + OData/Settings.StyleCop | 392 ++++++++++++ OData/WebApiOData.msbuild | 119 ++++ OData/WebApiOData.sln | 74 +++ OData/build.cmd | 39 ++ OData/packages/repositories.config | 8 + OData/src/CodeAnalysisDictionary.xml | 76 +++ OData/src/Common/CollectionExtensions.cs | 271 ++++++++ .../Common/CommonWebApiResources.Designer.cs | 140 ++++ OData/src/Common/CommonWebApiResources.resx | 141 +++++ OData/src/Common/Error.cs | 268 ++++++++ OData/src/Common/ListWrapperCollection.cs | 30 + OData/src/Common/PropertyHelper.cs | 190 ++++++ OData/src/Common/TaskHelpers.cs | 84 +++ OData/src/Common/TaskHelpersExtensions.cs | 54 ++ OData/src/Common/TypeExtensions.cs | 27 + OData/src/CommonAssemblyInfo.cs | 32 + OData/src/GlobalSuppressions.cs | 5 + OData/src/Settings.StyleCop | 5 + OData/src/Strict.ruleset | 11 + .../System.Web.Http.OData.csproj | 22 +- .../src/System.Web.Http.OData/packages.config | 2 + .../System.Web.OData/System.Web.OData.csproj | 22 +- OData/src/System.Web.OData/packages.config | 2 + OData/test/Common/AttributeListTest.cs | 128 ++++ OData/test/Common/CollectionExtensionsTest.cs | 354 +++++++++++ OData/test/Common/DictionaryExtensionsTest.cs | 161 +++++ OData/test/Common/ErrorTest.cs | 22 + OData/test/Common/HttpMethodHelperTest.cs | 65 ++ .../test/Common/ListWrapperCollectionTests.cs | 36 ++ OData/test/Common/PathHelpersTest.cs | 29 + OData/test/Common/PrefixContainerTest.cs | 226 +++++++ .../DefaultInlineConstraintResolverTest.cs | 226 +++++++ .../Common/Routing/DirectRouteBuilderTests.cs | 214 +++++++ .../Routing/InlineRouteTemplateParserTests.cs | 254 ++++++++ .../Common/Routing/RouteConstraintsTests.cs | 362 +++++++++++ .../Routing/RouteFactoryAttributeTests.cs | 598 ++++++++++++++++++ .../Common/Routing/RoutePrecedenceTests.cs | 77 +++ .../Common/Routing/SubRouteCollectionTest.cs | 59 ++ .../test/Common/TaskHelpersExtensionsTest.cs | 197 ++++++ OData/test/Common/TaskHelpersTest.cs | 77 +++ OData/test/Common/TypeExtensionsTest.cs | 23 + OData/test/Common/UriQueryUtilityTest.cs | 124 ++++ .../Microsoft.TestCommon/AppDomainUtils.cs | 73 +++ OData/test/Microsoft.TestCommon/Assert.cs | 30 + .../Microsoft.TestCommon/CultureReplacer.cs | 72 +++ .../Microsoft.TestCommon/DataAttribute.cs | 11 + .../DictionaryEqualityComparer.cs | 49 ++ .../EnumHelperTestBase.cs | 101 +++ .../ExceptionAssertions.cs | 521 +++++++++++++++ .../Microsoft.TestCommon/FactAttribute.cs | 60 ++ .../Microsoft.TestCommon/ForceGCAttribute.cs | 17 + .../InlineDataAttribute.cs | 15 + .../MatrixTheoryDataSet.cs | 25 + .../test/Microsoft.TestCommon/MemberHelper.cs | 383 +++++++++++ .../Microsoft.TestCommon.csproj | 103 +++ .../DataSets/CommonUnitTestDataSets.cs | 40 ++ .../TestCommon/DataSets/RefTypeTestData.cs | 75 +++ .../Microsoft/TestCommon/DataSets/TestData.cs | 463 ++++++++++++++ .../TestCommon/DataSets/TestDataHolder.cs | 49 ++ .../TestCommon/DataSets/TestDataVariations.cs | 115 ++++ .../TestCommon/DataSets/ValueTypeTestData.cs | 39 ++ .../Microsoft/TestCommon/GenericTypeAssert.cs | 490 ++++++++++++++ .../Microsoft/TestCommon/HttpAssert.cs | 256 ++++++++ .../Microsoft/TestCommon/MediaTypeAssert.cs | 54 ++ .../MediaTypeHeaderValueComparer.cs | 90 +++ .../TestCommon/ParsedMediaTypeHeaderValue.cs | 111 ++++ .../Microsoft/TestCommon/RegexReplacement.cs | 40 ++ .../TestCommon/RuntimeEnvironment.cs | 33 + .../Microsoft/TestCommon/SerializerAssert.cs | 152 +++++ .../Microsoft/TestCommon/StreamAssert.cs | 96 +++ .../Microsoft/TestCommon/TaskAssert.cs | 100 +++ .../TestCommon/TestDataSetAttribute.cs | 203 ++++++ .../Microsoft/TestCommon/TimeoutConstant.cs | 22 + .../Microsoft/TestCommon/TypeAssert.cs | 163 +++++ .../Microsoft/TestCommon/Types/ByteEnum.cs | 11 + .../Microsoft/TestCommon/Types/FlagsEnum.cs | 14 + .../TestCommon/Types/INameAndIdContainer.cs | 14 + .../TestCommon/Types/ISerializableType.cs | 75 +++ .../Microsoft/TestCommon/Types/LongEnum.cs | 12 + .../Microsoft/TestCommon/Types/SByteEnum.cs | 11 + .../Microsoft/TestCommon/Types/ShortEnum.cs | 11 + .../Microsoft/TestCommon/Types/SimpleEnum.cs | 12 + .../Microsoft/TestCommon/Types/UIntEnum.cs | 11 + .../Microsoft/TestCommon/Types/UShortEnum.cs | 11 + .../Microsoft/TestCommon/XmlAssert.cs | 78 +++ OData/test/Microsoft.TestCommon/Platform.cs | 36 ++ .../test/Microsoft.TestCommon/PlatformInfo.cs | 33 + .../test/Microsoft.TestCommon/PortReserver.cs | 135 ++++ .../PreAppStartTestHelper.cs | 27 + .../PreserveSyncContextAttribute.cs | 25 + .../PropertyDataAttribute.cs | 15 + .../Microsoft.TestCommon/ReflectionAssert.cs | 221 +++++++ .../ReplaceCultureAttribute.cs | 55 ++ .../RestoreThreadPrincipalAttribute.cs | 30 + .../StringAssertException.cs | 117 ++++ .../Microsoft.TestCommon/StringAssertions.cs | 135 ++++ .../Microsoft.TestCommon/TaskExtensions.cs | 35 + OData/test/Microsoft.TestCommon/TestFile.cs | 77 +++ OData/test/Microsoft.TestCommon/TestHelper.cs | 31 + .../Microsoft.TestCommon/TheoryAttribute.cs | 57 ++ .../Microsoft.TestCommon/TheoryDataSet.cs | 105 +++ .../ThreadPoolSyncContext.cs | 33 + .../Microsoft.TestCommon/TraitAttribute.cs | 15 + .../Microsoft.TestCommon/VersionTestHelper.cs | 20 + OData/test/Microsoft.TestCommon/WebUtils.cs | 89 +++ .../test/Microsoft.TestCommon/packages.config | 5 + OData/test/Settings.StyleCop | 197 ++++++ .../Formatting/MediaTypeFormatterTestBase.cs | 559 ++++++++++++++++ .../System.Web.Http.OData.Test.csproj | 26 +- .../packages.config | 3 + .../System.Web.Http.Test/Util/ContextUtil.cs | 70 ++ .../System.Web.OData.Test.csproj | 26 +- .../System.Web.OData.Test/packages.config | 3 + OData/tools/35MSSharedLib1024.snk | Bin 0 -> 160 bytes OData/tools/SkipStrongNames.xml | 69 ++ OData/tools/WebStack.StyleCop.targets | 80 +++ OData/tools/WebStack.settings.targets | 62 ++ OData/tools/WebStack.targets | 5 + OData/tools/WebStack.tasks.targets | 221 +++++++ OData/tools/WebStack.xunit.targets | 20 + .../DoNotCallProblematicMethodsOnTaskRule.cs | 62 ++ .../DoNotConstructTaskInstancesRule.cs | 39 ++ .../DoNotUseFinalizersRule.cs | 24 + .../DoNotUseProblematicTaskTypesRule.cs | 54 ++ .../Microsoft.Web.FxCop/IntrospectionRule.cs | 14 + .../Microsoft.Web.FxCop.csproj | 74 +++ .../Properties/AssemblyInfo.cs | 8 + OData/tools/src/Microsoft.Web.FxCop/Rules.xml | 59 ++ .../Microsoft.Web.FxCop/TypeNodeExtensions.cs | 102 +++ .../UnusedResourceUsageRule.cs | 119 ++++ Runtime.sln | 38 +- packages/repositories.config | 4 - 134 files changed, 12717 insertions(+), 91 deletions(-) create mode 100644 OData/.nuget/NuGet.Config create mode 100644 OData/.nuget/packages.config create mode 100644 OData/Settings.StyleCop create mode 100644 OData/WebApiOData.msbuild create mode 100644 OData/WebApiOData.sln create mode 100644 OData/build.cmd create mode 100644 OData/packages/repositories.config create mode 100644 OData/src/CodeAnalysisDictionary.xml create mode 100644 OData/src/Common/CollectionExtensions.cs create mode 100644 OData/src/Common/CommonWebApiResources.Designer.cs create mode 100644 OData/src/Common/CommonWebApiResources.resx create mode 100644 OData/src/Common/Error.cs create mode 100644 OData/src/Common/ListWrapperCollection.cs create mode 100644 OData/src/Common/PropertyHelper.cs create mode 100644 OData/src/Common/TaskHelpers.cs create mode 100644 OData/src/Common/TaskHelpersExtensions.cs create mode 100644 OData/src/Common/TypeExtensions.cs create mode 100644 OData/src/CommonAssemblyInfo.cs create mode 100644 OData/src/GlobalSuppressions.cs create mode 100644 OData/src/Settings.StyleCop create mode 100644 OData/src/Strict.ruleset create mode 100644 OData/test/Common/AttributeListTest.cs create mode 100644 OData/test/Common/CollectionExtensionsTest.cs create mode 100644 OData/test/Common/DictionaryExtensionsTest.cs create mode 100644 OData/test/Common/ErrorTest.cs create mode 100644 OData/test/Common/HttpMethodHelperTest.cs create mode 100644 OData/test/Common/ListWrapperCollectionTests.cs create mode 100644 OData/test/Common/PathHelpersTest.cs create mode 100644 OData/test/Common/PrefixContainerTest.cs create mode 100644 OData/test/Common/Routing/DefaultInlineConstraintResolverTest.cs create mode 100644 OData/test/Common/Routing/DirectRouteBuilderTests.cs create mode 100644 OData/test/Common/Routing/InlineRouteTemplateParserTests.cs create mode 100644 OData/test/Common/Routing/RouteConstraintsTests.cs create mode 100644 OData/test/Common/Routing/RouteFactoryAttributeTests.cs create mode 100644 OData/test/Common/Routing/RoutePrecedenceTests.cs create mode 100644 OData/test/Common/Routing/SubRouteCollectionTest.cs create mode 100644 OData/test/Common/TaskHelpersExtensionsTest.cs create mode 100644 OData/test/Common/TaskHelpersTest.cs create mode 100644 OData/test/Common/TypeExtensionsTest.cs create mode 100644 OData/test/Common/UriQueryUtilityTest.cs create mode 100644 OData/test/Microsoft.TestCommon/AppDomainUtils.cs create mode 100644 OData/test/Microsoft.TestCommon/Assert.cs create mode 100644 OData/test/Microsoft.TestCommon/CultureReplacer.cs create mode 100644 OData/test/Microsoft.TestCommon/DataAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs create mode 100644 OData/test/Microsoft.TestCommon/EnumHelperTestBase.cs create mode 100644 OData/test/Microsoft.TestCommon/ExceptionAssertions.cs create mode 100644 OData/test/Microsoft.TestCommon/FactAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/ForceGCAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/InlineDataAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/MatrixTheoryDataSet.cs create mode 100644 OData/test/Microsoft.TestCommon/MemberHelper.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataHolder.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ByteEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SByteEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ShortEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UIntEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UShortEnum.cs create mode 100644 OData/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/Platform.cs create mode 100644 OData/test/Microsoft.TestCommon/PlatformInfo.cs create mode 100644 OData/test/Microsoft.TestCommon/PortReserver.cs create mode 100644 OData/test/Microsoft.TestCommon/PreAppStartTestHelper.cs create mode 100644 OData/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/PropertyDataAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/ReflectionAssert.cs create mode 100644 OData/test/Microsoft.TestCommon/ReplaceCultureAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/RestoreThreadPrincipalAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/StringAssertException.cs create mode 100644 OData/test/Microsoft.TestCommon/StringAssertions.cs create mode 100644 OData/test/Microsoft.TestCommon/TaskExtensions.cs create mode 100644 OData/test/Microsoft.TestCommon/TestFile.cs create mode 100644 OData/test/Microsoft.TestCommon/TestHelper.cs create mode 100644 OData/test/Microsoft.TestCommon/TheoryAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/TheoryDataSet.cs create mode 100644 OData/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs create mode 100644 OData/test/Microsoft.TestCommon/TraitAttribute.cs create mode 100644 OData/test/Microsoft.TestCommon/VersionTestHelper.cs create mode 100644 OData/test/Microsoft.TestCommon/WebUtils.cs create mode 100644 OData/test/Microsoft.TestCommon/packages.config create mode 100644 OData/test/Settings.StyleCop create mode 100644 OData/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs create mode 100644 OData/test/System.Web.Http.Test/Util/ContextUtil.cs create mode 100644 OData/tools/35MSSharedLib1024.snk create mode 100644 OData/tools/SkipStrongNames.xml create mode 100644 OData/tools/WebStack.StyleCop.targets create mode 100644 OData/tools/WebStack.settings.targets create mode 100644 OData/tools/WebStack.targets create mode 100644 OData/tools/WebStack.tasks.targets create mode 100644 OData/tools/WebStack.xunit.targets create mode 100644 OData/tools/src/Microsoft.Web.FxCop/DoNotCallProblematicMethodsOnTaskRule.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/DoNotConstructTaskInstancesRule.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/DoNotUseFinalizersRule.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/DoNotUseProblematicTaskTypesRule.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/IntrospectionRule.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/Microsoft.Web.FxCop.csproj create mode 100644 OData/tools/src/Microsoft.Web.FxCop/Properties/AssemblyInfo.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/Rules.xml create mode 100644 OData/tools/src/Microsoft.Web.FxCop/TypeNodeExtensions.cs create mode 100644 OData/tools/src/Microsoft.Web.FxCop/UnusedResourceUsageRule.cs diff --git a/OData/.nuget/NuGet.Config b/OData/.nuget/NuGet.Config new file mode 100644 index 000000000..6ff25f633 --- /dev/null +++ b/OData/.nuget/NuGet.Config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OData/.nuget/packages.config b/OData/.nuget/packages.config new file mode 100644 index 000000000..268e93fac --- /dev/null +++ b/OData/.nuget/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OData/Settings.StyleCop b/OData/Settings.StyleCop new file mode 100644 index 000000000..5e9551bf6 --- /dev/null +++ b/OData/Settings.StyleCop @@ -0,0 +1,392 @@ + + + NoMerge + + + + + False + + + + + + + + + False + + + + + + as + db + dc + do + ef + id + if + in + is + my + no + on + sl + to + ui + vs + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + + + + \ No newline at end of file diff --git a/OData/WebApiOData.msbuild b/OData/WebApiOData.msbuild new file mode 100644 index 000000000..0200ae7ae --- /dev/null +++ b/OData/WebApiOData.msbuild @@ -0,0 +1,119 @@ + + + + + + + CodeAnalysis + Release + true + true + true + true + false + $(MSBuildThisFileDirectory)bin\$(Configuration)\test\TestResults\ + $(MSBuildThisFileDirectory)packages\Microsoft.Web.SkipStrongNames.1.0.0\tools\SkipStrongNames.exe + $(MSBuildThisFileDirectory)tools\SkipStrongNames.xml + .nuget\NuGet.exe + + + + + $(BuildInParallel) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(MsBuildThisFileDirectory)tools\src\Microsoft.Web.FxCop\ + $(MsBuildThisFileDirectory)packages\CustomFxCopRules + + + + + + + + + + + + + + + + + + + + TestAssembly=%(TestDLLsXunit.FullPath);XmlPath=$(TestResultsDirectory)%(TestDLLsXunit.FileName)-XunitResults.xml + + + + + + + + + + + + + + + + + + + + + diff --git a/OData/WebApiOData.sln b/OData/WebApiOData.sln new file mode 100644 index 000000000..252055830 --- /dev/null +++ b/OData/WebApiOData.sln @@ -0,0 +1,74 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C40883CD-366D-4534-8B58-3EA0D13136DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.TestCommon", "test\Microsoft.TestCommon\Microsoft.TestCommon.csproj", "{FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.OData", "src\System.Web.Http.OData\System.Web.Http.OData.csproj", "{CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.OData.Test", "test\System.Web.Http.OData.Test\System.Web.Http.OData.Test.csproj", "{D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{CB34D534-9A09-4EE4-B350-C1C23AFBF5EE}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData", "src\System.Web.OData\System.Web.OData.csproj", "{D23E28F1-CCD0-43E0-8C0D-36731EC91318}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData.Test", "test\System.Web.OData.Test\System.Web.OData.Test.csproj", "{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0}.Release|Any CPU.Build.0 = Release|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Release|Any CPU.Build.0 = Release|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Release|Any CPU.Build.0 = Release|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.Build.0 = Release|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0} = {C40883CD-366D-4534-8B58-3EA0D13136DF} + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9} = {C40883CD-366D-4534-8B58-3EA0D13136DF} + {D23E28F1-CCD0-43E0-8C0D-36731EC91318} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612} = {C40883CD-366D-4534-8B58-3EA0D13136DF} + EndGlobalSection +EndGlobal diff --git a/OData/build.cmd b/OData/build.cmd new file mode 100644 index 000000000..63f3c6203 --- /dev/null +++ b/OData/build.cmd @@ -0,0 +1,39 @@ +@echo off +pushd %~dp0 +setlocal + +if exist bin goto build +mkdir bin + +:Build + +REM Find the most recent 32bit MSBuild.exe on the system. Require v12.0 (installed with VS2013) or later since .NET 4.0 +REM is not supported. Also handle x86 operating systems, where %ProgramFiles(x86)% is not defined. Always quote the +REM %MSBuild% value when setting the variable and never quote %MSBuild% references. +set MSBuild="%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild.exe" +if not exist %MSBuild% @set MSBuild="%ProgramFiles%\MSBuild\12.0\Bin\MSBuild.exe" + +if "%1" == "" goto BuildDefaults + +%MSBuild% WebApiOData.msbuild /m /nr:false /t:%* /p:Platform="Any CPU" /p:Desktop=true /v:M /fl /flp:LogFile=bin\msbuild.log;Verbosity=Normal +if %ERRORLEVEL% neq 0 goto BuildFail +goto BuildSuccess + +:BuildDefaults +%MSBuild% WebApiOData.msbuild /m /nr:false /p:Platform="Any CPU" /p:Desktop=true /v:M /fl /flp:LogFile=bin\msbuild.log;Verbosity=Normal +if %ERRORLEVEL% neq 0 goto BuildFail +goto BuildSuccess + +:BuildFail +echo. +echo *** BUILD FAILED *** +goto End + +:BuildSuccess +echo. +echo **** BUILD SUCCESSFUL *** +goto end + +:End +popd +endlocal diff --git a/OData/packages/repositories.config b/OData/packages/repositories.config new file mode 100644 index 000000000..e43c93fe0 --- /dev/null +++ b/OData/packages/repositories.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/OData/src/CodeAnalysisDictionary.xml b/OData/src/CodeAnalysisDictionary.xml new file mode 100644 index 000000000..fdd4e0ea7 --- /dev/null +++ b/OData/src/CodeAnalysisDictionary.xml @@ -0,0 +1,76 @@ + + + + + Multi + Bitly + Digg + Facebook + Reddit + Captcha + Facebook + Gravatar + JSON + Lookahead + MVC + Param + Params + Pluralizer + Pragma + Pragmas + Templating + Unvalidated + Validator + Validators + Validatable + WebPage + cshtml + vbhtml + asax + Eval + Src + Charset + Coords + Rel + Dto + Tokenizer + ReDim + OAuth + OpenID + Yadis + fwlink + Edm + Deserializer + Api + ws + enc + dir + Auth + bg + Cors + Owin + Unbuffered + Rfc + Realtime + ModelName + BSON + Untyped + + + WebPage + WebPages + TimeLine + oAuth + userName + modelName + HasId + + + + + ID + Db + Dto + + + \ No newline at end of file diff --git a/OData/src/Common/CollectionExtensions.cs b/OData/src/Common/CollectionExtensions.cs new file mode 100644 index 000000000..9028c1254 --- /dev/null +++ b/OData/src/Common/CollectionExtensions.cs @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace System.Collections.Generic +{ + /// + /// Helper extension methods for fast use of collections. + /// + internal static class CollectionExtensions + { + /// + /// Return a new array with the value added to the end. Slow and best suited to long lived arrays with few writes relative to reads. + /// + public static T[] AppendAndReallocate(this T[] array, T value) + { + Contract.Assert(array != null); + + int originalLength = array.Length; + T[] newArray = new T[originalLength + 1]; + array.CopyTo(newArray, 0); + newArray[originalLength] = value; + return newArray; + } + + /// + /// Return the enumerable as an Array, copying if required. Optimized for common case where it is an Array. + /// Avoid mutating the return value. + /// + public static T[] AsArray(this IEnumerable values) + { + Contract.Assert(values != null); + + T[] array = values as T[]; + if (array == null) + { + array = values.ToArray(); + } + return array; + } + + /// + /// Return the enumerable as a Collection of T, copying if required. Optimized for the common case where it is + /// a Collection of T and avoiding a copy if it implements IList of T. Avoid mutating the return value. + /// + public static Collection AsCollection(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + Collection collection = enumerable as Collection; + if (collection != null) + { + return collection; + } + // Check for IList so that collection can wrap it instead of copying + IList list = enumerable as IList; + if (list == null) + { + list = new List(enumerable); + } + return new Collection(list); + } + + /// + /// Return the enumerable as a IList of T, copying if required. Avoid mutating the return value. + /// + public static IList AsIList(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + IList list = enumerable as IList; + if (list != null) + { + return list; + } + return new List(enumerable); + } + + /// + /// Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of T + /// or a ListWrapperCollection of T. Avoid mutating the return value. + /// + public static List AsList(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + List list = enumerable as List; + if (list != null) + { + return list; + } + ListWrapperCollection listWrapper = enumerable as ListWrapperCollection; + if (listWrapper != null) + { + return listWrapper.ItemsList; + } + return new List(enumerable); + } + + /// + /// Remove values from the list starting at the index start. + /// + public static void RemoveFrom(this List list, int start) + { + Contract.Assert(list != null); + Contract.Assert(start >= 0 && start <= list.Count); + + list.RemoveRange(start, list.Count - start); + } + + /// + /// Return the only value from list, the type's default value if empty, or call the errorAction for 2 or more. + /// + public static T SingleDefaultOrError(this IList list, Action errorAction, TArg1 errorArg1) + { + Contract.Assert(list != null); + Contract.Assert(errorAction != null); + + switch (list.Count) + { + case 0: + return default(T); + + case 1: + T value = list[0]; + return value; + + default: + errorAction(errorArg1); + return default(T); + } + } + + /// + /// Returns a single value in list matching type TMatch if there is only one, null if there are none of type TMatch or calls the + /// errorAction with errorArg1 if there is more than one. + /// + public static TMatch SingleOfTypeDefaultOrError(this IList list, Action errorAction, TArg1 errorArg1) where TMatch : class + { + Contract.Assert(list != null); + Contract.Assert(errorAction != null); + + TMatch result = null; + for (int i = 0; i < list.Count; i++) + { + TMatch typedValue = list[i] as TMatch; + if (typedValue != null) + { + if (result == null) + { + result = typedValue; + } + else + { + errorAction(errorArg1); + return null; + } + } + } + return result; + } + + /// + /// Convert an ICollection to an array, removing null values. Fast path for case where there are no null values. + /// + public static T[] ToArrayWithoutNulls(this ICollection collection) where T : class + { + Contract.Assert(collection != null); + + T[] result = new T[collection.Count]; + int count = 0; + foreach (T value in collection) + { + if (value != null) + { + result[count] = value; + count++; + } + } + if (count == collection.Count) + { + return result; + } + else + { + T[] trimmedResult = new T[count]; + Array.Copy(result, trimmedResult, count); + return trimmedResult; + } + } + + /// + /// Convert the array to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for array input. + /// + public static Dictionary ToDictionaryFast(this TValue[] array, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(array != null); + Contract.Assert(keySelector != null); + + Dictionary dictionary = new Dictionary(array.Length, comparer); + for (int i = 0; i < array.Length; i++) + { + TValue value = array[i]; + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + + /// + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input with fast path for array. + /// + public static Dictionary ToDictionaryFast(this IList list, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(list != null); + Contract.Assert(keySelector != null); + + TValue[] array = list as TValue[]; + if (array != null) + { + return ToDictionaryFast(array, keySelector, comparer); + } + return ToDictionaryFastNoCheck(list, keySelector, comparer); + } + + /// + /// Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified comparer. Fast paths for array and IList of T. + /// + public static Dictionary ToDictionaryFast(this IEnumerable enumerable, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(enumerable != null); + Contract.Assert(keySelector != null); + + TValue[] array = enumerable as TValue[]; + if (array != null) + { + return ToDictionaryFast(array, keySelector, comparer); + } + IList list = enumerable as IList; + if (list != null) + { + return ToDictionaryFastNoCheck(list, keySelector, comparer); + } + Dictionary dictionary = new Dictionary(comparer); + foreach (TValue value in enumerable) + { + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + + /// + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input. No checking for other types. + /// + private static Dictionary ToDictionaryFastNoCheck(IList list, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(list != null); + Contract.Assert(keySelector != null); + + int listCount = list.Count; + Dictionary dictionary = new Dictionary(listCount, comparer); + for (int i = 0; i < listCount; i++) + { + TValue value = list[i]; + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + } +} diff --git a/OData/src/Common/CommonWebApiResources.Designer.cs b/OData/src/Common/CommonWebApiResources.Designer.cs new file mode 100644 index 000000000..0944808f4 --- /dev/null +++ b/OData/src/Common/CommonWebApiResources.Designer.cs @@ -0,0 +1,140 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.17929 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Web.Http.Properties { + using System; + using System.Linq; + using System.Reflection; + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CommonWebApiResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CommonWebApiResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { +#if NETFX_CORE + var assembly = typeof(CommonWebApiResources).GetTypeInfo().Assembly; +#else + var assembly = typeof(CommonWebApiResources).Assembly; +#endif + + // Find the CommonResources.resources file's full resource name in this assembly + string commonResourcesName = assembly.GetManifestResourceNames().Where(s => s.EndsWith("CommonWebApiResources.resources", StringComparison.OrdinalIgnoreCase)).Single(); + + // Trim off the ".resources" + commonResourcesName = commonResourcesName.Substring(0, commonResourcesName.Length - 10); + + // Load the resource manager + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(commonResourcesName, assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Relative URI values are not supported: '{0}'. The URI must be absolute.. + /// + internal static string ArgumentInvalidAbsoluteUri { + get { + return ResourceManager.GetString("ArgumentInvalidAbsoluteUri", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported URI scheme: '{0}'. The URI scheme must be either '{1}' or '{2}'.. + /// + internal static string ArgumentInvalidHttpUriScheme { + get { + return ResourceManager.GetString("ArgumentInvalidHttpUriScheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be greater than or equal to {0}.. + /// + internal static string ArgumentMustBeGreaterThanOrEqualTo { + get { + return ResourceManager.GetString("ArgumentMustBeGreaterThanOrEqualTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be less than or equal to {0}.. + /// + internal static string ArgumentMustBeLessThanOrEqualTo { + get { + return ResourceManager.GetString("ArgumentMustBeLessThanOrEqualTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument '{0}' is null or empty.. + /// + internal static string ArgumentNullOrEmpty { + get { + return ResourceManager.GetString("ArgumentNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to URI must not contain a query component or a fragment identifier.. + /// + internal static string ArgumentUriHasQueryOrFragment { + get { + return ResourceManager.GetString("ArgumentUriHasQueryOrFragment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.. + /// + internal static string InvalidEnumArgument { + get { + return ResourceManager.GetString("InvalidEnumArgument", resourceCulture); + } + } + } +} diff --git a/OData/src/Common/CommonWebApiResources.resx b/OData/src/Common/CommonWebApiResources.resx new file mode 100644 index 000000000..3fa56b192 --- /dev/null +++ b/OData/src/Common/CommonWebApiResources.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Relative URI values are not supported: '{0}'. The URI must be absolute. + + + Unsupported URI scheme: '{0}'. The URI scheme must be either '{1}' or '{2}'. + + + Value must be greater than or equal to {0}. + + + Value must be less than or equal to {0}. + + + The argument '{0}' is null or empty. + + + URI must not contain a query component or a fragment identifier. + + + The value of argument '{0}' ({1}) is invalid for Enum type '{2}'. + + \ No newline at end of file diff --git a/OData/src/Common/Error.cs b/OData/src/Common/Error.cs new file mode 100644 index 000000000..1b23f4dfe --- /dev/null +++ b/OData/src/Common/Error.cs @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Web.Http.Properties; + +namespace System.Web.Http +{ + /// + /// Utility class for creating and unwrapping instances. + /// + internal static class Error + { + private const string HttpScheme = "http"; + private const string HttpsScheme = "https"; + + /// + /// Formats the specified resource string using . + /// + /// A composite format string. + /// An object array that contains zero or more objects to format. + /// The formatted string. + internal static string Format(string format, params object[] args) + { + return String.Format(CultureInfo.CurrentCulture, format, args); + } + + /// + /// Creates an with the provided properties. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentException Argument(string messageFormat, params object[] messageArgs) + { + return new ArgumentException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentException Argument(string parameterName, string messageFormat, params object[] messageArgs) + { + return new ArgumentException(Error.Format(messageFormat, messageArgs), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an "http" or "https" URI. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriNotHttpOrHttpsScheme(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentInvalidHttpUriScheme, actualValue, HttpScheme, HttpsScheme), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an absolute URI. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriNotAbsolute(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentInvalidAbsoluteUri, actualValue), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an absolute URI + /// without a query or fragment identifier and then logs it with . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriHasQueryOrFragment(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentUriHasQueryOrFragment, actualValue), parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The logged . + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The purpose of this API is to return an error for properties")] + internal static ArgumentNullException PropertyNull() + { + return new ArgumentNullException("value"); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// The logged . + internal static ArgumentNullException ArgumentNull(string parameterName) + { + return new ArgumentNullException(parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentNullException ArgumentNull(string parameterName, string messageFormat, params object[] messageArgs) + { + return new ArgumentNullException(parameterName, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with a default message. + /// + /// The name of the parameter that caused the current exception. + /// The logged . + internal static ArgumentException ArgumentNullOrEmpty(string parameterName) + { + return Error.Argument(parameterName, CommonWebApiResources.ArgumentNullOrEmpty, parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentOutOfRange(string parameterName, object actualValue, string messageFormat, params object[] messageArgs) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with a message saying that the argument must be greater than or equal to . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The minimum size of the argument. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentMustBeGreaterThanOrEqualTo(string parameterName, object actualValue, object minValue) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(CommonWebApiResources.ArgumentMustBeGreaterThanOrEqualTo, minValue)); + } + + /// + /// Creates an with a message saying that the argument must be less than or equal to . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The maximum size of the argument. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentMustBeLessThanOrEqualTo(string parameterName, object actualValue, object maxValue) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(CommonWebApiResources.ArgumentMustBeLessThanOrEqualTo, maxValue)); + } + + /// + /// Creates an with a message saying that the key was not found. + /// + /// The logged . + internal static KeyNotFoundException KeyNotFound() + { + return new KeyNotFoundException(); + } + + /// + /// Creates an with a message saying that the key was not found. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static KeyNotFoundException KeyNotFound(string messageFormat, params object[] messageArgs) + { + return new KeyNotFoundException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an initialized according to guidelines. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ObjectDisposedException ObjectDisposed(string messageFormat, params object[] messageArgs) + { + // Pass in null, not disposedObject.GetType().FullName as per the above guideline + return new ObjectDisposedException(null, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an initialized with the provided parameters. + /// + /// The logged . + internal static OperationCanceledException OperationCanceled() + { + return new OperationCanceledException(); + } + + /// + /// Creates an initialized with the provided parameters. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static OperationCanceledException OperationCanceled(string messageFormat, params object[] messageArgs) + { + return new OperationCanceledException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an for an invalid enum argument. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that failed. + /// A that represents the enumeration class with the valid values. + /// The logged . + internal static ArgumentException InvalidEnumArgument(string parameterName, int invalidValue, Type enumClass) + { +#if NETFX_CORE + return new ArgumentException(Error.Format(CommonWebApiResources.InvalidEnumArgument, parameterName, invalidValue, enumClass.Name), parameterName); +#else + return new InvalidEnumArgumentException(parameterName, invalidValue, enumClass); +#endif + } + + /// + /// Creates an . + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an . + /// + /// Inner exception + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(Exception innerException, string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs), innerException); + } + + /// + /// Creates an . + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static NotSupportedException NotSupported(string messageFormat, params object[] messageArgs) + { + return new NotSupportedException(Error.Format(messageFormat, messageArgs)); + } + } +} diff --git a/OData/src/Common/ListWrapperCollection.cs b/OData/src/Common/ListWrapperCollection.cs new file mode 100644 index 000000000..d5bba56d6 --- /dev/null +++ b/OData/src/Common/ListWrapperCollection.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace System.Collections.ObjectModel +{ + /// + /// A class that inherits from Collection of T but also exposes its underlying data as List of T for performance. + /// + internal sealed class ListWrapperCollection : Collection + { + private readonly List _items; + + internal ListWrapperCollection() + : this(new List()) + { + } + + internal ListWrapperCollection(List list) + : base(list) + { + _items = list; + } + + internal List ItemsList + { + get { return _items; } + } + } +} diff --git a/OData/src/Common/PropertyHelper.cs b/OData/src/Common/PropertyHelper.cs new file mode 100644 index 000000000..fd9ea3bee --- /dev/null +++ b/OData/src/Common/PropertyHelper.cs @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; + +#if ASPNETWEBAPI +namespace System.Web.Http.Internal +#else +namespace System.Web.WebPages +#endif +{ + internal class PropertyHelper + { + private static ConcurrentDictionary _reflectionCache = new ConcurrentDictionary(); + + private Func _valueGetter; + + /// + /// Initializes a fast property helper. This constructor does not cache the helper. + /// + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "This is intended the Name is auto set differently per type and the type is internal")] + public PropertyHelper(PropertyInfo property) + { + Contract.Assert(property != null); + + Name = property.Name; + _valueGetter = MakeFastPropertyGetter(property); + } + + /// + /// Creates a single fast property setter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast setter. + /// This method is more memory efficient than a dynamically compiled lambda, and about the same speed. + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + where TDeclaringType : class + { + Contract.Assert(propertyInfo != null); + + MethodInfo setMethod = propertyInfo.GetSetMethod(); + + Contract.Assert(setMethod != null); + Contract.Assert(!setMethod.IsStatic); + Contract.Assert(setMethod.GetParameters().Length == 1); + Contract.Assert(!propertyInfo.ReflectedType.IsValueType); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "this". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + Type typeInput = propertyInfo.ReflectedType; + Type typeValue = setMethod.GetParameters()[0].ParameterType; + + Delegate callPropertySetterDelegate; + + // Create a delegate TValue -> "TDeclaringType.Property" + var propertySetterAsAction = setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, typeValue)); + var callPropertySetterClosedGenericMethod = _callPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, typeValue); + callPropertySetterDelegate = Delegate.CreateDelegate(typeof(Action), propertySetterAsAction, callPropertySetterClosedGenericMethod); + + return (Action)callPropertySetterDelegate; + } + + public virtual string Name { get; protected set; } + + public object GetValue(object instance) + { + Contract.Assert(_valueGetter != null, "Must call Initialize before using this object"); + + return _valueGetter(instance); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the underlying type. + /// + /// the instance to extract property accessors for. + /// a cached array of all public property getters from the underlying type of this instance. + public static PropertyHelper[] GetProperties(object instance) + { + return GetProperties(instance, CreateInstance, _reflectionCache); + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// This method is more memory efficient than a dynamically compiled lambda, and about the same speed. + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Contract.Assert(propertyInfo != null); + + MethodInfo getMethod = propertyInfo.GetGetMethod(); + Contract.Assert(getMethod != null); + Contract.Assert(!getMethod.IsStatic); + Contract.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "this". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + Type typeInput = getMethod.ReflectedType; + Type typeOutput = getMethod.ReturnType; + + Delegate callPropertyGetterDelegate; + if (typeInput.IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(ByRefFunc<,>).MakeGenericType(typeInput, typeOutput)); + MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterByReferenceOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput); + callPropertyGetterDelegate = Delegate.CreateDelegate(typeof(Func), propertyGetterAsFunc, callPropertyGetterClosedGenericMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(typeInput, typeOutput)); + MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput); + callPropertyGetterDelegate = Delegate.CreateDelegate(typeof(Func), propertyGetterAsFunc, callPropertyGetterClosedGenericMethod); + } + + return (Func)callPropertyGetterDelegate; + } + + private static PropertyHelper CreateInstance(PropertyInfo property) + { + return new PropertyHelper(property); + } + + // Implementation of the fast getter. + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo _callPropertyGetterOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertyGetter", BindingFlags.NonPublic | BindingFlags.Static); + private static readonly MethodInfo _callPropertyGetterByReferenceOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertyGetterByReference", BindingFlags.NonPublic | BindingFlags.Static); + + private static object CallPropertyGetter(Func getter, object @this) + { + return getter((TDeclaringType)@this); + } + + private static object CallPropertyGetterByReference(ByRefFunc getter, object @this) + { + TDeclaringType unboxed = (TDeclaringType)@this; + return getter(ref unboxed); + } + + // Implementation of the fast setter. + private static readonly MethodInfo _callPropertySetterOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertySetter", BindingFlags.NonPublic | BindingFlags.Static); + + private static void CallPropertySetter(Action setter, object @this, object value) + { + setter((TDeclaringType)@this, (TValue)value); + } + + protected static PropertyHelper[] GetProperties(object instance, + Func createPropertyHelper, + ConcurrentDictionary cache) + { + // Using an array rather than IEnumerable, as this will be called on the hot path numerous times. + PropertyHelper[] helpers; + + Type type = instance.GetType(); + + if (!cache.TryGetValue(type, out helpers)) + { + // We avoid loading indexed properties using the where statement. + // Indexed properties are not useful (or valid) for grabbing properties off an anonymous object. + IEnumerable properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(prop => prop.GetIndexParameters().Length == 0 && + prop.GetMethod != null); + + var newHelpers = new List(); + + foreach (PropertyInfo property in properties) + { + PropertyHelper propertyHelper = createPropertyHelper(property); + + newHelpers.Add(propertyHelper); + } + + helpers = newHelpers.ToArray(); + cache.TryAdd(type, helpers); + } + + return helpers; + } + } +} diff --git a/OData/src/Common/TaskHelpers.cs b/OData/src/Common/TaskHelpers.cs new file mode 100644 index 000000000..32ead3f5f --- /dev/null +++ b/OData/src/Common/TaskHelpers.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace System.Threading.Tasks +{ + /// + /// Helpers for safely using Task libraries. + /// + internal static class TaskHelpers + { + private static readonly Task _defaultCompleted = Task.FromResult(default(AsyncVoid)); + + private static readonly Task _completedTaskReturningNull = Task.FromResult(null); + + /// + /// Returns a canceled Task. The task is completed, IsCanceled = True, IsFaulted = False. + /// + internal static Task Canceled() + { + return CancelCache.Canceled; + } + + /// + /// Returns a canceled Task of the given type. The task is completed, IsCanceled = True, IsFaulted = False. + /// + internal static Task Canceled() + { + return CancelCache.Canceled; + } + + /// + /// Returns a completed task that has no result. + /// + internal static Task Completed() + { + return _defaultCompleted; + } + + /// + /// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True + /// + internal static Task FromError(Exception exception) + { + return FromError(exception); + } + + /// + /// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True + /// + /// + internal static Task FromError(Exception exception) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetException(exception); + return tcs.Task; + } + + internal static Task NullResult() + { + return _completedTaskReturningNull; + } + + /// + /// Used as the T in a "conversion" of a Task into a Task{T} + /// + private struct AsyncVoid + { + } + + /// + /// This class is a convenient cache for per-type cancelled tasks + /// + private static class CancelCache + { + public static readonly Task Canceled = GetCancelledTask(); + + private static Task GetCancelledTask() + { + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + return tcs.Task; + } + } + } +} diff --git a/OData/src/Common/TaskHelpersExtensions.cs b/OData/src/Common/TaskHelpersExtensions.cs new file mode 100644 index 000000000..22258e61e --- /dev/null +++ b/OData/src/Common/TaskHelpersExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +namespace System.Threading.Tasks +{ + internal static class TaskHelpersExtensions + { + /// + /// Cast Task to Task of object + /// + internal static async Task CastToObject(this Task task) + { + await task; + return null; + } + + /// + /// Cast Task of T to Task of object + /// + internal static async Task CastToObject(this Task task) + { + return (object)await task; + } + + /// + /// Throws the first faulting exception for a task which is faulted. It preserves the original stack trace when + /// throwing the exception. Note: It is the caller's responsibility not to pass incomplete tasks to this + /// method, because it does degenerate into a call to the equivalent of .Wait() on the task when it hasn't yet + /// completed. + /// + internal static void ThrowIfFaulted(this Task task) + { + task.GetAwaiter().GetResult(); + } + + /// + /// Attempts to get the result value for the given task. If the task ran to completion, then + /// it will return true and set the result value; otherwise, it will return false. + /// + [SuppressMessage("Microsoft.Web.FxCop", "MW1201:DoNotCallProblematicMethodsOnTask", Justification = "The usages here are deemed safe, and provide the implementations that this rule relies upon.")] + internal static bool TryGetResult(this Task task, out TResult result) + { + if (task.Status == TaskStatus.RanToCompletion) + { + result = task.Result; + return true; + } + + result = default(TResult); + return false; + } + } +} diff --git a/OData/src/Common/TypeExtensions.cs b/OData/src/Common/TypeExtensions.cs new file mode 100644 index 000000000..0b90c3ee7 --- /dev/null +++ b/OData/src/Common/TypeExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.ComponentModel; + +namespace System +{ + /// + /// Extension methods for . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class TypeExtensions + { + public static bool IsNullable(this Type type) + { + if (type.IsValueType) + { + // value types are only nullable if they are Nullable + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + else + { + // reference types are always nullable + return true; + } + } + } +} diff --git a/OData/src/CommonAssemblyInfo.cs b/OData/src/CommonAssemblyInfo.cs new file mode 100644 index 000000000..2ffa5766d --- /dev/null +++ b/OData/src/CommonAssemblyInfo.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +#if !BUILD_GENERATED_VERSION +[assembly: AssemblyCompany("Microsoft Open Technologies, Inc.")] +[assembly: AssemblyCopyright("© Microsoft Open Technologies, Inc. All rights reserved.")] +#endif +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: ComVisible(false)] +#if !NOT_CLS_COMPLIANT +[assembly: CLSCompliant(true)] +#endif +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: AssemblyMetadata("Serviceable", "True")] + +// =========================================================================== +// DO NOT EDIT OR REMOVE ANYTHING BELOW THIS COMMENT. +// Version numbers are automatically generated based on regular expressions. +// =========================================================================== + +#if ASPNETODATA +#if !BUILD_GENERATED_VERSION +[assembly: AssemblyVersion("5.3.0.0")] // ASPNETODATA +[assembly: AssemblyFileVersion("5.3.0.0")] // ASPNETODATA +#endif +[assembly: AssemblyProduct("Microsoft ASP.NET Web API OData")] +#endif \ No newline at end of file diff --git a/OData/src/GlobalSuppressions.cs b/OData/src/GlobalSuppressions.cs new file mode 100644 index 000000000..4723a3a02 --- /dev/null +++ b/OData/src/GlobalSuppressions.cs @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay-signed")] diff --git a/OData/src/Settings.StyleCop b/OData/src/Settings.StyleCop new file mode 100644 index 000000000..42b4729d9 --- /dev/null +++ b/OData/src/Settings.StyleCop @@ -0,0 +1,5 @@ + + + Parent + + \ No newline at end of file diff --git a/OData/src/Strict.ruleset b/OData/src/Strict.ruleset new file mode 100644 index 000000000..7f48d5f29 --- /dev/null +++ b/OData/src/Strict.ruleset @@ -0,0 +1,11 @@ + + + + ..\packages\CustomFxCopRules + + + + + + + \ No newline at end of file diff --git a/OData/src/System.Web.Http.OData/System.Web.Http.OData.csproj b/OData/src/System.Web.Http.OData/System.Web.Http.OData.csproj index 01eeda27a..73afd5fc7 100644 --- a/OData/src/System.Web.Http.OData/System.Web.Http.OData.csproj +++ b/OData/src/System.Web.Http.OData/System.Web.Http.OData.csproj @@ -1,6 +1,6 @@  - + {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE} Library @@ -9,7 +9,7 @@ $(OutputPath)$(AssemblyName).xml $(CodeAnalysis) ..\Strict.ruleset - $(DefineConstants);ASPNETMVC;ASPNETWEBAPI + $(DefineConstants);ASPNETODATA;ASPNETWEBAPI @@ -29,12 +29,20 @@ + + False + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.0\lib\net45\System.Net.Http.Formatting.dll + False ..\..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.0\lib\net45\System.Web.Http.dll + @@ -359,16 +367,6 @@ - - - {668e9021-ce84-49d9-98fb-df125a9fcdb0} - System.Net.Http.Formatting - - - {ddc1ce0c-486e-4e35-bb3b-eab61f8f9440} - System.Web.Http - - \ No newline at end of file diff --git a/OData/src/System.Web.Http.OData/packages.config b/OData/src/System.Web.Http.OData/packages.config index ff6b54603..797208e12 100644 --- a/OData/src/System.Web.Http.OData/packages.config +++ b/OData/src/System.Web.Http.OData/packages.config @@ -1,5 +1,7 @@  + + diff --git a/OData/src/System.Web.OData/System.Web.OData.csproj b/OData/src/System.Web.OData/System.Web.OData.csproj index 614841fc4..4352fab1f 100644 --- a/OData/src/System.Web.OData/System.Web.OData.csproj +++ b/OData/src/System.Web.OData/System.Web.OData.csproj @@ -1,6 +1,6 @@  - + {D23E28F1-CCD0-43E0-8C0D-36731EC91318} Library @@ -9,7 +9,7 @@ $(OutputPath)$(AssemblyName).xml $(CodeAnalysis) ..\Strict.ruleset - $(DefineConstants);ASPNETMVC;ASPNETWEBAPI + $(DefineConstants);ASPNETODATA;ASPNETWEBAPI @@ -33,8 +33,16 @@ + + False + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.0\lib\net45\System.Net.Http.Formatting.dll + + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.0\lib\net45\System.Web.Http.dll + @@ -424,16 +432,6 @@ - - - {668e9021-ce84-49d9-98fb-df125a9fcdb0} - System.Net.Http.Formatting - - - {ddc1ce0c-486e-4e35-bb3b-eab61f8f9440} - System.Web.Http - - \ No newline at end of file diff --git a/OData/src/System.Web.OData/packages.config b/OData/src/System.Web.OData/packages.config index 313b26545..5d51df21a 100644 --- a/OData/src/System.Web.OData/packages.config +++ b/OData/src/System.Web.OData/packages.config @@ -1,5 +1,7 @@  + + diff --git a/OData/test/Common/AttributeListTest.cs b/OData/test/Common/AttributeListTest.cs new file mode 100644 index 000000000..c9c706da5 --- /dev/null +++ b/OData/test/Common/AttributeListTest.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; +using Microsoft.TestCommon; + +namespace System.ComponentModel +{ + public class AttributeListTest + { + private readonly Attribute[] _testAttributes; + private readonly AttributeCollection _collection; + private readonly AttributeList _list; + + public AttributeListTest() + { + _testAttributes = new Attribute[] { new TestAttribute(), new DerivedAttribute(), new DerivedDerivedAttribute() }; + _collection = new AttributeCollection(_testAttributes); + _list = new AttributeList(_collection); + } + + [Fact] + public void AttributeListCountMatchesWrapped() + { + Assert.Equal(_collection.Count, _list.Count); + } + + [Fact] + public void AttributeListIsReadOnlyTrue() + { + Assert.True(_list.IsReadOnly); + } + + [Fact] + public void AttributeListIndexerMatchesWrapped() + { + Assert.Equal(_collection[1], _list[1]); + } + + [Fact] + public void AttributeListAddThrows() + { + Assert.Throws(() => _list.Add(null)); + } + + [Fact] + public void AttributeListClearThrows() + { + Assert.Throws(() => _list.Clear()); + } + + [Fact] + public void AttributeListContainsWrappedTrue() + { + Attribute presentAttribute = _collection[2]; + Assert.True(_list.Contains(presentAttribute)); + } + + [Fact] + public void AttributeListContainsMissingFalse() + { + Attribute missingAttribute = new MissingAttribute(); + Assert.False(_list.Contains(missingAttribute)); + } + + [Fact] + public void AttributeListCopyToResultsEqual() + { + Attribute[] arrayCopy = new Attribute[3]; + _list.CopyTo(arrayCopy, 0); + Assert.Equal(_list, arrayCopy); + } + + [Fact] + public void AttributeListIndexOfMatchesIndexer() + { + Assert.Equal(1, _list.IndexOf(_list[1])); + } + + [Fact] + public void AttributeListRemoveAtThrows() + { + Assert.Throws(() => _list.RemoveAt(0)); + Assert.Throws(() => ((ICollection)_list).Remove(_list[0])); + } + + [Fact] + public void AttributeListEnumerationMatchesWrapped() + { + int i = 0; + foreach (Attribute attribute in _list) + { + Assert.Equal(_collection[i], attribute); + i++; + } + Assert.Equal(_collection.Count, i); + + i = 0; + IEnumerable asEumerable = _list as IEnumerable; + foreach (Attribute attribute in asEumerable) + { + Assert.Equal(_collection[i], attribute); + i++; + } + Assert.Equal(_collection.Count, i); + } + + private class TestAttribute : Attribute + { + public TestAttribute() { } + } + + private class DerivedAttribute: TestAttribute + { + public DerivedAttribute() { } + } + + private class DerivedDerivedAttribute : DerivedAttribute + { + public DerivedDerivedAttribute() { } + } + + private class MissingAttribute : Attribute + { + public MissingAttribute() { } + } + } +} diff --git a/OData/test/Common/CollectionExtensionsTest.cs b/OData/test/Common/CollectionExtensionsTest.cs new file mode 100644 index 000000000..f838cc5bc --- /dev/null +++ b/OData/test/Common/CollectionExtensionsTest.cs @@ -0,0 +1,354 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.TestCommon; + +namespace System.Collections.Generic +{ + public class CollectionExtensionsTest + { + [Fact] + public void AppendAndReallocateEmpty_ReturnsOne() + { + string[] empty = new string[0]; + + string[] emptyAppended = empty.AppendAndReallocate("AppendedEmpty"); + + Assert.Equal(1, emptyAppended.Length); + Assert.Equal("AppendedEmpty", emptyAppended[0]); + } + + [Fact] + public void AppendAndReallocateOne_ReturnsTwo() + { + string[] one = new string[] { "One" }; + + string[] oneAppended = one.AppendAndReallocate("AppendedOne"); + + Assert.Equal(2, oneAppended.Length); + Assert.Equal("One", oneAppended[0]); + Assert.Equal("AppendedOne", oneAppended[1]); + } + + [Fact] + public void AsArray_Array_ReturnsSameInstance() + { + object[] array = new object[] { new object(), new object() }; + + object[] arrayAsArray = ((IEnumerable)array).AsArray(); + + Assert.Same(array, arrayAsArray); + } + + [Fact] + public void AsArray_Enumerable_Copies() + { + IList list = new List() { new object(), new object() }; + object[] listToArray = list.ToArray(); + + object[] listAsArray = ((IEnumerable)list).AsArray(); + + Assert.Equal(listToArray, listAsArray); + } + + [Fact] + public void AsCollection_Collection_ReturnsSameInstance() + { + Collection collection = new Collection() { new object(), new object() }; + + Collection collectionAsCollection = ((IEnumerable)collection).AsCollection(); + + Assert.Same(collection, collectionAsCollection); + } + + [Fact] + public void AsCollection_Enumerable_Copies() + { + IEnumerable enumerable = new LinkedList(new object[] { new object(), new object() }); + + Collection enumerableAsCollection = ((IEnumerable)enumerable).AsCollection(); + + Assert.Equal(enumerable, ((IEnumerable)enumerableAsCollection)); + } + + [Fact] + public void AsCollection_IList_Wraps() + { + IList list = new List() { new object(), new object() }; + + Collection listAsCollection = list.AsCollection(); + list.Add(new object()); + + Assert.Equal(list, listAsCollection.ToList()); + } + + [Fact] + public void AsIList_IList_ReturnsSameInstance() + { + List list = new List { new object(), new object() }; + + IList listAsIList = ((IEnumerable)list).AsIList(); + + Assert.Same(list, listAsIList); + } + + [Fact] + public void AsIList_Enumerable_Copies() + { + LinkedList enumerable = new LinkedList(); + enumerable.AddLast(new object()); + enumerable.AddLast(new object()); + List expected = enumerable.ToList(); + + IList enumerableAsIList = ((IEnumerable)enumerable).AsIList(); + + Assert.Equal(expected, enumerableAsIList); + Assert.NotSame(expected, enumerableAsIList); + } + + [Fact] + public void AsList_List_ReturnsSameInstance() + { + List list = new List { new object(), new object() }; + + List listAsList = ((IEnumerable)list).AsList(); + + Assert.Same(list, listAsList); + } + + [Fact] + public void AsList_Enumerable_Copies() + { + List list = new List() { new object(), new object() }; + object[] array = list.ToArray(); + + List arrayAsList = ((IEnumerable)array).AsList(); + + Assert.Equal(list, arrayAsList); + Assert.NotSame(list, arrayAsList); + Assert.NotSame(array, arrayAsList); + } + + public void AsList_ListWrapperCollection_ReturnsSameInstance() + { + List list = new List { new object(), new object() }; + ListWrapperCollection listWrapper = new ListWrapperCollection(list); + + List listWrapperAsList = ((IEnumerable)listWrapper).AsList(); + + Assert.Same(list, listWrapperAsList); + } + + [Fact] + void RemoveFromTwoElementsAtEnd_NoChange() + { + List list = new List() { new object(), new object() }; + List listExpected = new List(list); + list.RemoveFrom(2); + Assert.Equal(listExpected, list); + } + + [Fact] + void RemoveFromTwoElementsMiddle_ToOne() + { + List list = new List() { new object(), new object() }; + List listExpected = new List() { list[0] }; + list.RemoveFrom(1); + Assert.Equal(listExpected, list); + } + + [Fact] + void RemoveFromTwoElementsStart_ToEmpty() + { + List list = new List() { new object(), new object() }; + List listExpected = new List(); + list.RemoveFrom(0); + Assert.Equal(listExpected, list); + } + + [Fact] + void SingleDefaultOrErrorIListEmptyReturnsNull() + { + IList empty = new List(); + object errorArgument = new object(); + Action errorAction = (object argument) => + { + throw new InvalidOperationException(); + }; + + Assert.Null(empty.SingleDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void SingleDefaultOrErrorIListSingleReturns() + { + IList single = new List() { new object() }; + object errorArgument = new object(); + Action errorAction = (object argument) => + { + throw new InvalidOperationException(); + }; + + Assert.Equal(single[0], single.SingleDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void SingleDefaultOrErrorIListMultipleThrows() + { + IList multiple = new List() { new object(), new object() }; + object errorArgument = new object(); + Action errorAction = (object argument) => + { + Assert.Equal(errorArgument, argument); + throw new InvalidOperationException(); + }; + + Assert.Throws(() => multiple.SingleDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void SingleOfTypeDefaultOrErrorIListNoMatchReturnsNull() + { + IList noMatch = new List() { new object(), new object() }; + object errorArgument = new object(); + Action errorAction = (object argument) => + { + throw new InvalidOperationException(); + }; + + Assert.Null(noMatch.SingleOfTypeDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void SingleOfTypeDefaultOrErrorIListOneMatchReturns() + { + IList singleMatch = new List() { new object(), "Match", new object() }; + object errorArgument = new object(); + Action errorAction = (object argument) => + { + throw new InvalidOperationException(); + }; + + Assert.Equal("Match", singleMatch.SingleOfTypeDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void SingleOfTypeDefaultOrErrorIListMultipleMatchesThrows() + { + IList multipleMatch = new List() { new object(), "Match1", new object(), "Match2" }; + object errorArgument = new object(); + Action errorAction = (object argument) => + { + Assert.Equal(errorArgument, argument); + throw new InvalidOperationException(); + }; + + Assert.Throws(() => multipleMatch.SingleOfTypeDefaultOrError(errorAction, errorArgument)); + } + + [Fact] + public void ToArrayWithoutNullsICollectionNoNullsCopies() + { + ICollection noNulls = new object[] { new object(), new object() }; + + object[] noNullsresult = noNulls.ToArrayWithoutNulls(); + + Assert.Equal(noNulls, noNullsresult); + } + + [Fact] + public void ToArrayWithoutNullsICollectionHasNullsRemovesNulls() + { + IList hasNulls = new List() { new object(), null, new object() }; + + object[] hasNullsResult = ((ICollection)hasNulls).ToArrayWithoutNulls(); + + Assert.Equal(2, hasNullsResult.Length); + Assert.Equal(hasNulls[0], hasNullsResult[0]); + Assert.Equal(hasNulls[2], hasNullsResult[1]); + } + + [Fact] + public void ToDictionaryFastArray2Element() + { + string[] input = new string[] {"AA", "BB"}; + var expectedOutput = new Dictionary() { { "A", "AA"}, {"B", "BB"}}; + Func keySelector = (string value) => value.Substring(1); + + var result = input.ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, result); + Assert.Equal(StringComparer.OrdinalIgnoreCase, result.Comparer); + } + + [Fact] + public void ToDictionaryFastIListList2Element() + { + string[] input = new string[] {"AA", "BB"}; + var expectedOutput = new Dictionary() { { "A", "AA"}, {"B", "BB"}}; + Func keySelector = (string value) => value.Substring(1); + List listInput = new List(input); + + var listResult = listInput.ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, listResult); + Assert.Equal(StringComparer.OrdinalIgnoreCase, listResult.Comparer); + } + + [Fact] + public void ToDictionaryFastIListArray2Element() + { + string[] input = new string[] { "AA", "BB" }; + var expectedOutput = new Dictionary() { { "A", "AA" }, { "B", "BB" } }; + Func keySelector = (string value) => value.Substring(1); + IList arrayAsList = input; + + var arrayResult = arrayAsList.ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, arrayResult); + Assert.Equal(StringComparer.OrdinalIgnoreCase, arrayResult.Comparer); + } + + [Fact] + public void ToDictionaryFastIEnumerableArray2Element() + { + string[] input = new string[] {"AA", "BB"}; + var expectedOutput = new Dictionary() { { "A", "AA"}, {"B", "BB"}}; + Func keySelector = (string value) => value.Substring(1); + + var arrayResult = ((IEnumerable)input).ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, arrayResult); + Assert.Equal(StringComparer.OrdinalIgnoreCase, arrayResult.Comparer); + } + + [Fact] + public void ToDictionaryFastIEnumerableList2Element() + { + string[] input = new string[] { "AA", "BB" }; + var expectedOutput = new Dictionary() { { "A", "AA" }, { "B", "BB" } }; + Func keySelector = (string value) => value.Substring(1); + List listInput = new List(input); + + var listResult = ((IEnumerable)listInput).ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, listResult); + Assert.Equal(StringComparer.OrdinalIgnoreCase, listResult.Comparer); + } + + [Fact] + public void ToDictionaryFastIEnumerableLinkedList2Element() + { + string[] input = new string[] { "AA", "BB" }; + var expectedOutput = new Dictionary() { { "A", "AA" }, { "B", "BB" } }; + Func keySelector = (string value) => value.Substring(1); + LinkedList linkedListInput = new LinkedList(input); + + var enumerableResult = ((IEnumerable)linkedListInput).ToDictionaryFast(keySelector, StringComparer.OrdinalIgnoreCase); + + Assert.Equal(expectedOutput, enumerableResult); + Assert.Equal(StringComparer.OrdinalIgnoreCase, enumerableResult.Comparer); + } + } +} diff --git a/OData/test/Common/DictionaryExtensionsTest.cs b/OData/test/Common/DictionaryExtensionsTest.cs new file mode 100644 index 000000000..fe654bfb3 --- /dev/null +++ b/OData/test/Common/DictionaryExtensionsTest.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.TestCommon; + +namespace System.Collections.Generic +{ + public class DictionaryExtensionsTest + { + public static TheoryDataSet DictionaryValues + { + get + { + return new TheoryDataSet + { + "test", + new string[] { "A", "B", "C" }, + 8, + new List {1, 2, 3}, + 1D, + (IEnumerable)new List { 1D, 2D, 3D }, + new Uri("http://some.host"), + Guid.NewGuid(), + HttpStatusCode.NotImplemented, + new HttpStatusCode[] { HttpStatusCode.Accepted, HttpStatusCode.Ambiguous, HttpStatusCode.BadGateway } + }; + } + } + + [Fact] + public void IsCorrectType() + { + Assert.Type.HasProperties(typeof(DictionaryExtensions), TypeAssert.TypeProperties.IsStatic | TypeAssert.TypeProperties.IsClass); + } + + [Fact] + public void RemoveFromDictionary_Args0_EvensRemoved() + { + Dictionary dictionary = new Dictionary(); + object object1 = new object(); + object object2 = new object(); + object object3 = new object(); + object object4 = new object(); + dictionary.Add(object1, 1); + dictionary.Add(object2, 2); + dictionary.Add(object3, 3); + dictionary.Add(object4, 4); + + Func, bool> removeAction = (KeyValuePair entry) => + { + // remove even values + return (entry.Value % 2) == 0; + }; + dictionary.RemoveFromDictionary(removeAction); + + Assert.Equal(2, dictionary.Count); + Assert.True(dictionary.ContainsKey(object1)); + Assert.False(dictionary.ContainsKey(object2)); + Assert.True(dictionary.ContainsKey(object3)); + Assert.False(dictionary.ContainsKey(object4)); + } + + [Fact] + public void RemoveFromDictionary_Args1_EvensRemoved() + { + Dictionary dictionary = new Dictionary(); + object object1 = new object(); + object object2 = new object(); + object object3 = new object(); + object object4 = new object(); + dictionary.Add(object1, 1); + dictionary.Add(object2, 2); + dictionary.Add(object3, 3); + dictionary.Add(object4, 4); + object expectedArgument = new object(); + + Func, object, bool> removeAction = (KeyValuePair entry, object arg) => + { + Assert.Equal(expectedArgument, arg); + // remove even values + return (entry.Value % 2) == 0; + }; + dictionary.RemoveFromDictionary(removeAction, expectedArgument); + + Assert.Equal(2, dictionary.Count); + Assert.True(dictionary.ContainsKey(object1)); + Assert.False(dictionary.ContainsKey(object2)); + Assert.True(dictionary.ContainsKey(object3)); + Assert.False(dictionary.ContainsKey(object4)); + } + + [Fact] + public void TryGetValueThrowsOnNullKey() + { + IDictionary dict = new Dictionary(); + string value; + Assert.ThrowsArgumentNull(() => dict.TryGetValue(null, out value), "key"); + } + + [Fact] + public void TryGetValueReturnsFalse() + { + // Arrange + IDictionary dict = new Dictionary(); + + // Act + string resultValue = null; + bool result = dict.TryGetValue("notfound", out resultValue); + + // Assert + Assert.False(result); + Assert.Null(resultValue); + } + + [Theory] + [PropertyData("DictionaryValues")] + public void TryGetValueReturnsTrue(T value) + { + // Arrange + IDictionary dict = new Dictionary() + { + { "key", value } + }; + + + // Act + T resultValue; + bool result = DictionaryExtensions.TryGetValue(dict, "key", out resultValue); + + // Assert + Assert.True(result); + Assert.Equal(typeof(T), resultValue.GetType()); + Assert.Equal(value, resultValue); + } + + [Fact] + public void FindKeysWithPrefixRecognizesRootChilden() + { + // Arrange + IDictionary dict = new Dictionary() + { + { "[0]", 1 }, + { "Name", 2 }, + { "Address.Street", 3 }, + { "", 4 } + }; + + // Act + List results = DictionaryExtensions.FindKeysWithPrefix(dict, "").Select(kvp => kvp.Value).ToList(); + + // Assert + Assert.Equal(4, results.Count); + Assert.Contains(1, results); + Assert.Contains(2, results); + Assert.Contains(3, results); + Assert.Contains(4, results); + } + } +} diff --git a/OData/test/Common/ErrorTest.cs b/OData/test/Common/ErrorTest.cs new file mode 100644 index 000000000..4d0456375 --- /dev/null +++ b/OData/test/Common/ErrorTest.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.TestCommon; + +namespace System.Web.Http +{ + public class ErrorTest + { + [Fact] + public void Format() + { + // Arrange + string expected = "The formatted message"; + + // Act + string actual = Error.Format("The {0} message", "formatted"); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/OData/test/Common/HttpMethodHelperTest.cs b/OData/test/Common/HttpMethodHelperTest.cs new file mode 100644 index 000000000..f03b06df9 --- /dev/null +++ b/OData/test/Common/HttpMethodHelperTest.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Net.Http; +using Microsoft.TestCommon; + +namespace System.Web.Http +{ + public class HttpMethodHelperTest + { + public static TheoryDataSet CommonHttpMethods + { + get + { + return new TheoryDataSet + { + { "Get", HttpMethod.Get }, + { "Post", HttpMethod.Post }, + { "Put", HttpMethod.Put }, + { "Delete", HttpMethod.Delete }, + { "Head", HttpMethod.Head }, + { "Options", HttpMethod.Options }, + { "Trace", HttpMethod.Trace }, + }; + } + } + + public static TheoryDataSet UncommonHttpMethods + { + get + { + return new TheoryDataSet + { + "Debug", + "Patch", + "Connect", + "Random", + "M-Get", + }; + } + } + + [Fact] + public void GetHttpMethod_ReturnsNullOnNullorEmpty() + { + Assert.Null(HttpMethodHelper.GetHttpMethod(null)); + Assert.Null(HttpMethodHelper.GetHttpMethod(String.Empty)); + } + + [Theory] + [PropertyData("CommonHttpMethods")] + public void GetHttpMethod_RetunsStaticResult(string method, HttpMethod expectedMethod) + { + Assert.Same(expectedMethod, HttpMethodHelper.GetHttpMethod(method)); + Assert.Same(expectedMethod, HttpMethodHelper.GetHttpMethod(method.ToLowerInvariant())); + Assert.Same(expectedMethod, HttpMethodHelper.GetHttpMethod(method.ToUpperInvariant())); + } + + [Theory] + [PropertyData("UncommonHttpMethods")] + public void GetHttpMethod_RetunsNonStaticResult(string method) + { + Assert.Equal(method, HttpMethodHelper.GetHttpMethod(method).ToString()); + } + } +} diff --git a/OData/test/Common/ListWrapperCollectionTests.cs b/OData/test/Common/ListWrapperCollectionTests.cs new file mode 100644 index 000000000..c40a9c0c7 --- /dev/null +++ b/OData/test/Common/ListWrapperCollectionTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.TestCommon; + +namespace System.Collections.ObjectModel +{ + public class ListWrapperCollectionTests + { + [Fact] + public void ListWrapperCollection_ItemsList_HasSameContents() + { + // Arrange + ListWrapperCollection listWrapper = new ListWrapperCollection(); + + // Act + listWrapper.Add(new object()); + listWrapper.Add(new object()); + + // Assert + Assert.Equal(listWrapper, listWrapper.ItemsList); + } + + [Fact] + public void ListWrapperCollection_ItemsList_IsPassedInList() + { + // Arrange + List list = new List() { new object(), new object() }; + ListWrapperCollection listWrapper = new ListWrapperCollection(list); + + // Act & Assert + Assert.Same(list, listWrapper.ItemsList); + } + } +} diff --git a/OData/test/Common/PathHelpersTest.cs b/OData/test/Common/PathHelpersTest.cs new file mode 100644 index 000000000..6b5c04763 --- /dev/null +++ b/OData/test/Common/PathHelpersTest.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Web; +using Microsoft.TestCommon; + +namespace System.Web.Test +{ + public class PathHelpersTest + { + [Theory] + [InlineData("foo.Bar", "bar")] + [InlineData("foo.bar", "bar")] + [InlineData(".bar", "bar")] + public void EndsWithExtensionReturnsTrue(string path, string extension) + { + Assert.True(PathHelpers.EndsWithExtension(path, extension)); + } + + [Theory] + [InlineData("foo.Baz", "bar")] + [InlineData("", "bar")] + [InlineData("Bar", "bar")] + [InlineData("fooBar", "bar")] + public void EndsWithExtensionReturnsFalse(string path, string extension) + { + Assert.False(PathHelpers.EndsWithExtension(path, extension)); + } + } +} diff --git a/OData/test/Common/PrefixContainerTest.cs b/OData/test/Common/PrefixContainerTest.cs new file mode 100644 index 000000000..12ea1a51b --- /dev/null +++ b/OData/test/Common/PrefixContainerTest.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.TestCommon; + +namespace System.Web +{ + [CLSCompliant(false)] + public class PrefixContainerTests + { + [Fact] + public void Constructor_GuardClauses() + { + // Act & assert + Assert.ThrowsArgumentNull(() => new PrefixContainer(null), "values"); + } + + [Fact] + public void ContainsPrefix_GuardClauses() + { + // Arrange + var container = new PrefixContainer(new string[0]); + + // Act & assert + Assert.ThrowsArgumentNull(() => container.ContainsPrefix(null), "prefix"); + } + + [Fact] + public void ContainsPrefix_EmptyCollectionReturnsFalse() + { + // Arrange + var container = new PrefixContainer(new string[0]); + + // Act & Assert + Assert.False(container.ContainsPrefix("")); + } + + [Fact] + public void ContainsPrefix_ExactMatch() + { + // Arrange + var container = new PrefixContainer(new[] { "Hello" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("Hello")); + } + + [Fact] + public void ContainsPrefix_MatchIsCaseInsensitive() + { + // Arrange + var container = new PrefixContainer(new[] { "Hello" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("hello")); + } + + [Fact] + public void ContainsPrefix_MatchIsNotSimpleSubstringMatch() + { + // Arrange + var container = new PrefixContainer(new[] { "Hello" }); + + // Act & Assert + Assert.False(container.ContainsPrefix("He")); + } + + [Fact] + public void ContainsPrefix_NonEmptyCollectionReturnsTrueIfPrefixIsEmptyString() + { + // Arrange + var container = new PrefixContainer(new[] { "Hello" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("")); + } + + [Fact] + public void ContainsPrefix_PrefixBoundaries() + { + // Arrange + var container = new PrefixContainer(new[] { "Hello.There[0]" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("hello")); + Assert.True(container.ContainsPrefix("hello.there")); + Assert.True(container.ContainsPrefix("hello.there[0]")); + Assert.False(container.ContainsPrefix("hello.there.0")); + } + + [Theory] + [InlineData("a")] + [InlineData("a[d]")] + [InlineData("c.b")] + [InlineData("c.b.a")] + public void ContainsPrefix_PositiveTests(string testValue) + { + // Arrange + var container = new PrefixContainer(new[] { "a.b", "c.b.a", "a[d]", "a.c" }); + + // Act & Assert + Assert.True(container.ContainsPrefix(testValue)); + } + + [Theory] + [InlineData("a.d")] + [InlineData("b")] + [InlineData("c.a")] + [InlineData("c.b.a.a")] + public void ContainsPrefix_NegativeTests(string testValue) + { + // Arrange + var container = new PrefixContainer(new[] { "a.b", "c.b.a", "a[d]", "a.c" }); + + // Act & Assert + Assert.False(container.ContainsPrefix(testValue)); + } + + [Fact] + public void ContainsPrefix_ShouldIdentifyCollectionWhenNonCollectionPropertyOccursOnBinarySearchBoundary() + { + // Arrange + var container = new PrefixContainer(new[] { "foo.a", "foo.b", "foo.c", "foo.d", "foo.esSomethingElse", "foo.es[0].a", "foo.es[0].b", "foo.es[0].c", "foo.es[0].d", "foo.es[0].e" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("foo.es")); + } + + [Fact] + public void ContainsPrefix_ShouldIdentifyCollectionWhenNonCollectionPropertyDoesNotOccurOnBinarySearchBoundary() + { + // Arrange + var container = new PrefixContainer(new[] { "foo.a", "foo.b", "foo.c", "foo.d", "foo.esSomethingElse", "foo.es[0].a", "foo.es[0].b", "foo.es[0].c" }); + + // Act & Assert + Assert.True(container.ContainsPrefix("foo.es")); + } + + [Fact] + public void GetKeysFromPrefix_DotsNotation() + { + // Arrange + var container = new PrefixContainer(new[] { "foo.bar.baz", "something.other", "foo.baz", "foot.hello", "fo.nothing", "foo" }); + string prefix = "foo"; + + // Act + IDictionary result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Equal(2, result.Count()); + Assert.True(result.ContainsKey("bar")); + Assert.True(result.ContainsKey("baz")); + Assert.Equal("foo.bar", result["bar"]); + Assert.Equal("foo.baz", result["baz"]); + } + + [Fact] + public void GetKeysFromPrefix_BracketsNotation() + { + // Arrange + var container = new PrefixContainer(new[] { "foo[bar]baz", "something[other]", "foo[baz]", "foot[hello]", "fo[nothing]", "foo" }); + string prefix = "foo"; + + // Act + IDictionary result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Equal(2, result.Count()); + Assert.True(result.ContainsKey("bar")); + Assert.True(result.ContainsKey("baz")); + Assert.Equal("foo[bar]", result["bar"]); + Assert.Equal("foo[baz]", result["baz"]); + } + + [Fact] + public void GetKeysFromPrefix_MixedDotsAndBrackets() + { + // Arrange + var container = new PrefixContainer(new[] { "foo[bar]baz", "something[other]", "foo.baz", "foot[hello]", "fo[nothing]", "foo" }); + string prefix = "foo"; + + // Act + IDictionary result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Equal(2, result.Count()); + Assert.True(result.ContainsKey("bar")); + Assert.True(result.ContainsKey("baz")); + Assert.Equal("foo[bar]", result["bar"]); + Assert.Equal("foo.baz", result["baz"]); + } + + [Fact] + public void GetKeysFromPrefix_AllValues() + { + // Arrange + var container = new PrefixContainer(new[] { "foo[bar]baz", "something[other]", "foo.baz", "foot[hello]", "fo[nothing]", "foo" }); + string prefix = ""; + + // Act + IDictionary result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Equal(4, result.Count()); + Assert.Equal("foo", result["foo"]); + Assert.Equal("something", result["something"]); + Assert.Equal("foot", result["foot"]); + Assert.Equal("fo", result["fo"]); + } + + [Fact] + public void GetKeysFromPrefix_PrefixNotFound() + { + // Arrange + var container = new PrefixContainer(new[] { "foo[bar]", "something[other]", "foo.baz", "foot[hello]", "fo[nothing]", "foo" }); + string prefix = "notfound"; + + // Act + IDictionary result = container.GetKeysFromPrefix(prefix); + + // Assert + Assert.Empty(result); + } + } +} \ No newline at end of file diff --git a/OData/test/Common/Routing/DefaultInlineConstraintResolverTest.cs b/OData/test/Common/Routing/DefaultInlineConstraintResolverTest.cs new file mode 100644 index 000000000..508434c1b --- /dev/null +++ b/OData/test/Common/Routing/DefaultInlineConstraintResolverTest.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +#if ASPNETWEBAPI +using System.Web.Http.Routing.Constraints; +#else +using System.Web.Mvc.Routing.Constraints; +#endif +using Microsoft.TestCommon; + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class DefaultInlineConstraintResolverTest + { + [Fact] + public void ResolveConstraint_AlphaConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("alpha"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_BoolConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("bool"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_CompoundConstraintIsNotRegistered() + { + Assert.Null(new DefaultInlineConstraintResolver().ResolveConstraint("compound")); + } + + [Fact] + public void ResolveConstraint_DateTimeConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("datetime"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_DecimalConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("decimal"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_DoubleConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("double"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_EnumNameConstraintIsNotRegistered() + { + Assert.Null(new DefaultInlineConstraintResolver().ResolveConstraint("enumname")); + } + + [Fact] + public void ResolveConstraint_EnumValueConstraintIsNotRegistered() + { + Assert.Null(new DefaultInlineConstraintResolver().ResolveConstraint("enumvalue")); + } + + [Fact] + public void ResolveConstraint_FloatConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("float"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_GuidConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("guid"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_IntConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("int"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_LengthConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("length(5)"); + + Assert.IsType(constraint); + Assert.Equal(5, ((LengthRouteConstraint)constraint).Length); + } + + [Fact] + public void ResolveConstraint_LengthRangeConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("length(5, 10)"); + + Assert.IsType(constraint); + LengthRouteConstraint lengthConstraint = (LengthRouteConstraint)constraint; + Assert.Equal(5, lengthConstraint.MinLength); + Assert.Equal(10, lengthConstraint.MaxLength); + } + + [Fact] + public void ResolveConstraint_LongRangeConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("long"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_MaxConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("max(10)"); + + Assert.IsType(constraint); + Assert.Equal(10, ((MaxRouteConstraint)constraint).Max); + } + + [Fact] + public void ResolveConstraint_MaxLengthConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("maxlength(10)"); + + Assert.IsType(constraint); + Assert.Equal(10, ((MaxLengthRouteConstraint)constraint).MaxLength); + } + + [Fact] + public void ResolveConstraint_MinConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("min(3)"); + + Assert.IsType(constraint); + Assert.Equal(3, ((MinRouteConstraint)constraint).Min); + } + + [Fact] + public void ResolveConstraint_MinLengthConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("minlength(3)"); + + Assert.IsType(constraint); + Assert.Equal(3, ((MinLengthRouteConstraint)constraint).MinLength); + } + + [Fact] + public void ResolveConstraint_OptionalConstraintIsNotRegistered() + { + Assert.Null(new DefaultInlineConstraintResolver().ResolveConstraint("optional")); + } + + [Fact] + public void ResolveConstraint_RangeConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("range(5, 10)"); + + Assert.IsType(constraint); + RangeRouteConstraint rangeConstraint = (RangeRouteConstraint)constraint; + Assert.Equal(5, rangeConstraint.Min); + Assert.Equal(10, rangeConstraint.Max); + } + + [Fact] + public void ResolveConstraint_RegexConstraint() + { + var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("regex(abc,defg)"); + + Assert.IsType(constraint); + RegexRouteConstraint regexConstraint = (RegexRouteConstraint)constraint; + Assert.Equal("abc,defg", regexConstraint.Pattern); + } + + [Fact] + public void ResolveConstraint_IntConstraintWithArgument_Throws() + { + Assert.Throws( + () => new DefaultInlineConstraintResolver().ResolveConstraint("int(5)"), + "Could not find a constructor for constraint type 'IntRouteConstraint' with the following number of parameters: 1."); + } + + [Fact] + public void ResolveConstraint_SupportsCustomConstraints() + { + var resolver = new DefaultInlineConstraintResolver(); + resolver.ConstraintMap.Add("custom", typeof(IntRouteConstraint)); + + var constraint = resolver.ResolveConstraint("custom"); + + Assert.IsType(constraint); + } + + [Fact] + public void ResolveConstraint_CustomConstraintThatDoesNotImplementouteConstraintInterfact_Throws() + { + var resolver = new DefaultInlineConstraintResolver(); + resolver.ConstraintMap.Add("custom", typeof(string)); + + Assert.Throws( + () => resolver.ResolveConstraint("custom"), +#if ASPNETWEBAPI + "The constraint type 'String' which is mapped to constraint key 'custom' must implement the IHttpRouteConstraint interface."); +#else + "The constraint type 'String' which is mapped to constraint key 'custom' must implement the IRouteConstraint interface."); +#endif + } + } +} \ No newline at end of file diff --git a/OData/test/Common/Routing/DirectRouteBuilderTests.cs b/OData/test/Common/Routing/DirectRouteBuilderTests.cs new file mode 100644 index 000000000..d92044c02 --- /dev/null +++ b/OData/test/Common/Routing/DirectRouteBuilderTests.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +#if ASPNETWEBAPI +using System.Net.Http; +using System.Web.Http.Routing.Constraints; +#else +using System.Web.Mvc.Routing.Constraints; +#endif +using Microsoft.TestCommon; +using Moq; + +#if ASPNETWEBAPI +using TActionDescriptor = System.Web.Http.Controllers.HttpActionDescriptor; +using TParsedRoute = System.Web.Http.Routing.HttpParsedRoute; +using TRouteValueDictionary = System.Web.Http.Routing.HttpRouteValueDictionary; +#else +using TActionDescriptor = System.Web.Mvc.ActionDescriptor; +using TParsedRoute = System.Web.Mvc.Routing.ParsedRoute; +using TRouteValueDictionary = System.Web.Routing.RouteValueDictionary; +#endif + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class DirectRouteBuilderTests + { + [Fact] + public void CreateRoute_ValidatesConstraintType_TRouteConstraint() + { + // Arrange + var actions = GetActions(); + var builder = new DirectRouteBuilder(actions, targetIsAction: true); + + var constraint = new AlphaRouteConstraint(); + var constraints = new TRouteValueDictionary(); + constraints.Add("custom", constraint); + builder.Constraints = constraints; + + // Act + var routeEntry = builder.Build(); + + // Assert + Assert.NotNull(routeEntry.Route.Constraints["custom"]); + } + + [Fact] + public void BuildRoute_ValidatesConstraintType_StringRegex() + { + // Arrange + var actions = GetActions(); + var builder = new DirectRouteBuilder(actions, targetIsAction: true); + + var constraint = "product|products"; + var constraints = new TRouteValueDictionary(); + constraints.Add("custom", constraint); + builder.Constraints = constraints; + + // Act + var routeEntry = builder.Build(); + + // Assert + Assert.NotNull(routeEntry.Route.Constraints["custom"]); + } + + [Fact] + public void BuildRoute_ValidatesConstraintType_InvalidType() + { + // Arrange + var actions = GetActions(); + var builder = new DirectRouteBuilder(actions, targetIsAction: true); + + var constraint = new Uri("http://localhost/"); + var constraints = new TRouteValueDictionary(); + constraints.Add("custom", constraint); + + builder.Constraints = constraints; + builder.Template = "c/{id}"; + +#if ASPNETWEBAPI + string expectedMessage = + "The constraint entry 'custom' on the route with route template 'c/{id}' " + + "must have a string value or be of a type which implements 'System.Web.Http.Routing.IHttpRouteConstraint'."; +#else + string expectedMessage = + "The constraint entry 'custom' on the route with route template 'c/{id}' " + + "must have a string value or be of a type which implements 'System.Web.Routing.IRouteConstraint'."; +#endif + + // Act & Assert + Assert.Throws(() => builder.Build(), expectedMessage); + } + + [Fact] + public void BuildRoute_ValidatesAllowedParameters() + { + // Arrange + var actions = GetActions(); + var builder = new MockDirectRouteBuilder(actions, targetIsAction: true); + builder.Template = "{a}/{b}"; + + // Act + RouteEntry entry = builder.Build(); + + // Assert + Assert.NotNull(entry); + Assert.Equal(1, builder.TimesValidateParametersCalled); + } + + [Theory] + [InlineData("{controller}", true)] + [InlineData("{controller}", false)] + [InlineData("{z}-abc-{controller}", true)] + public void BuildRoute_ControllerParameterNotAllowed(string template, bool targetIsAction) + { + // Arrange + var actions = GetActions(); + + var expectedMessage = + "A direct route cannot use the parameter 'controller'. " + + "Specify a literal path in place of this parameter to create a route to a controller."; + + var builder = new MockDirectRouteBuilder(actions, targetIsAction); + builder.Template = template; + + // Act & Assert + Assert.Throws(() => builder.Build(), expectedMessage); + Assert.Equal(1, builder.TimesValidateParametersCalled); + } + + [Fact] + public void BuildRoute_ActionParameterAllowed_OnControllerRoute() + { + // Arrange + var actions = GetActions(); + var builder = new MockDirectRouteBuilder(actions, targetIsAction: false); + builder.Template = "{a}/{action}"; + + // Act + RouteEntry entry = builder.Build(); + + // Assert + Assert.NotNull(entry); + Assert.Equal(1, builder.TimesValidateParametersCalled); + } + + [Theory] + [InlineData("{action}")] + [InlineData("api/yy-{action}")] + public void BuildRoute_ActionNotAllowed_OnActionRoute(string template) + { + // Arrange + var actions = GetActions(); + + var expectedMessage = + "A direct route for an action method cannot use the parameter 'action'. " + + "Specify a literal path in place of this parameter to create a route to the action."; + + var builder = new MockDirectRouteBuilder(actions, targetIsAction: true); + builder.Template = template; + + // Act & Assert + Assert.Throws(() => builder.Build(), expectedMessage); + Assert.Equal(1, builder.TimesValidateParametersCalled); + } + +#if ASPNETWEBAPI + private IReadOnlyCollection GetActions() + { + var actions = new List() + { + new Mock().Object, + }; + + return actions.AsReadOnly(); + } +#else + private IReadOnlyCollection GetActions() + { + var action = new Mock(); + action.SetupGet(a => a.ControllerDescriptor).Returns(new Mock().Object); + var actions = new List() + { + action.Object, + }; + + return actions.AsReadOnly(); + } +#endif + + private class MockDirectRouteBuilder : DirectRouteBuilder + { + public MockDirectRouteBuilder(IReadOnlyCollection actionDescriptors, bool targetIsAction) + : base(actionDescriptors, targetIsAction) + { + } + + public int TimesValidateParametersCalled + { + get; + private set; + } + + internal override void ValidateParameters(TParsedRoute parsedRoute) + { + TimesValidateParametersCalled++; + base.ValidateParameters(parsedRoute); + } + } + } +} diff --git a/OData/test/Common/Routing/InlineRouteTemplateParserTests.cs b/OData/test/Common/Routing/InlineRouteTemplateParserTests.cs new file mode 100644 index 000000000..fe144b3f5 --- /dev/null +++ b/OData/test/Common/Routing/InlineRouteTemplateParserTests.cs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Linq; +#if ASPNETWEBAPI +using System.Web.Http.Routing.Constraints; +#else +using System.Web.Mvc.Routing.Constraints; +using System.Web.Routing; +#endif +using Microsoft.TestCommon; + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class InlineRouteTemplateParserTests + { +#if ASPNETWEBAPI + private static readonly RouteParameter OptionalParameter = RouteParameter.Optional; +#else + private static readonly UrlParameter OptionalParameter = UrlParameter.Optional; +#endif + + [Fact] + public void ParseRouteTemplate_ChainedConstraintAndDefault() + { + var result = Act(@"hello/{param:int=111111}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.Equal("111111", result.Defaults["param"]); + Assert.IsType(result.Constraints["param"]); + } + + [Fact] + public void ParseRouteTemplate_ChainedConstraintWithArgumentsAndDefault() + { + var result = Act(@"hello/{param:regex(\d+)=111111}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.Equal("111111", result.Defaults["param"]); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\d+", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_ChainedConstraintAndOptional() + { + var result = Act(@"hello/{param:int?}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.Equal(OptionalParameter, result.Defaults["param"]); + + Assert.IsType(result.Constraints["param"]); + var constraint = (OptionalRouteConstraint)result.Constraints["param"]; + Assert.IsType(constraint.InnerConstraint); + } + + [Fact] + public void ParseRouteTemplate_ChainedConstraintWithArgumentsAndOptional() + { + var result = Act(@"hello/{param:regex(\d+)?}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.Equal(OptionalParameter, result.Defaults["param"]); + + Assert.IsType(result.Constraints["param"]); + var constraint = (OptionalRouteConstraint)result.Constraints["param"]; + Assert.Equal(@"\d+", ((RegexRouteConstraint)constraint.InnerConstraint).Pattern); + } + + [Fact] + public void ParseRouteTemplate_ChainedConstraints() + { + var result = Act(@"hello/{param:regex(\d+):regex(\w+)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.IsType(result.Constraints["param"]); + CompoundRouteConstraint constraint = (CompoundRouteConstraint)result.Constraints["param"]; + Assert.Equal(@"\d+", ((RegexRouteConstraint)constraint.Constraints.ElementAt(0)).Pattern); + Assert.Equal(@"\w+", ((RegexRouteConstraint)constraint.Constraints.ElementAt(1)).Pattern); + } + + [Fact] + public void ParseRouteTemplate_Constraint() + { + var result = Act(@"hello/{param:regex(\d+)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\d+", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections() + { + var result = Act(@"some/url-{p1:alpha:length(3)=hello}/{p2=abc}/{p3?}"); + + Assert.Equal("some/url-{p1}/{p2}/{p3}", result.RouteUrl); + + Assert.Equal("hello", result.Defaults["p1"]); + Assert.Equal("abc", result.Defaults["p2"]); + Assert.Equal(OptionalParameter, result.Defaults["p3"]); + + Assert.IsType(result.Constraints["p1"]); + CompoundRouteConstraint constraint = (CompoundRouteConstraint)result.Constraints["p1"]; + Assert.IsType(constraint.Constraints.ElementAt(0)); + Assert.IsType(constraint.Constraints.ElementAt(1)); + } + + [Fact] + public void ParseRouteTemplate_NoTokens() + { + var result = Act("hello/world"); + + Assert.Equal("hello/world", result.RouteUrl); + } + + [Fact] + public void ParseRouteTemplate_OptionalParam() + { + var result = Act("hello/{param?}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.Equal(OptionalParameter, result.Defaults["param"]); + } + + [Fact] + public void ParseRouteTemplate_ParamDefault() + { + var result = Act("hello/{param=world}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.Equal("world", result.Defaults["param"]); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithClosingBraceInPattern() + { + var result = Act(@"hello/{param:regex(\})}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\}", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithClosingParenInPattern() + { + var result = Act(@"hello/{param:regex(\))}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\)", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithColonInPattern() + { + var result = Act(@"hello/{param:regex(:)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@":", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithCommaInPattern() + { + var result = Act(@"hello/{param:regex(\w,\w)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\w,\w", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithEqualsSignInPattern() + { + var result = Act(@"hello/{param:regex(=)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + + Assert.DoesNotContain("param", result.Defaults.Keys); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"=", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithOpenBraceInPattern() + { + var result = Act(@"hello/{param:regex(\{)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\{", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithOpenParenInPattern() + { + var result = Act(@"hello/{param:regex(\()}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\(", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + [Fact] + public void ParseRouteTemplate_RegexConstraintWithQuestionMarkInPattern() + { + var result = Act(@"hello/{param:regex(\?)}"); + + Assert.Equal("hello/{param}", result.RouteUrl); + Assert.DoesNotContain("param", result.Defaults.Keys); + Assert.IsType(result.Constraints["param"]); + Assert.Equal(@"\?", ((RegexRouteConstraint)result.Constraints["param"]).Pattern); + } + + + private ParseResult Act(string template) + { + var result = new ParseResult(); + #if ASPNETWEBAPI + result.Constraints = new HttpRouteValueDictionary(); + result.Defaults = new HttpRouteValueDictionary(); +#else + result.Constraints = new RouteValueDictionary(); + result.Defaults = new RouteValueDictionary(); +#endif + result.RouteUrl = InlineRouteTemplateParser.ParseRouteTemplate(template, result.Defaults, result.Constraints, new DefaultInlineConstraintResolver()); + return result; + } + + struct ParseResult + { + public string RouteUrl; + #if ASPNETWEBAPI + public HttpRouteValueDictionary Defaults; + public HttpRouteValueDictionary Constraints; +#else + public RouteValueDictionary Defaults; + public RouteValueDictionary Constraints; +#endif + } + } +} diff --git a/OData/test/Common/Routing/RouteConstraintsTests.cs b/OData/test/Common/Routing/RouteConstraintsTests.cs new file mode 100644 index 000000000..f96eb3fef --- /dev/null +++ b/OData/test/Common/Routing/RouteConstraintsTests.cs @@ -0,0 +1,362 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq.Expressions; +#if ASPNETWEBAPI +using System.Net.Http; +using System.Web.Http.Routing.Constraints; +#else +using System.Web.Mvc.Routing.Constraints; +using System.Web.Routing; +#endif +using Microsoft.TestCommon; +using Moq; + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class RouteConstraintsTests + { + [Theory] + [InlineData(42, true)] + [InlineData("42", true)] + [InlineData(3.14, false)] + [InlineData("43.567", false)] + [InlineData("42a", false)] + public void IntRouteConstraintTests(object parameterValue, bool expected) + { + var constraint = new IntRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(42, true)] + [InlineData("42", true)] + [InlineData("9223372036854775807", true)] + [InlineData(3.14, false)] + [InlineData("43.567", false)] + [InlineData("42a", false)] + public void LongRouteConstraintTests(object parameterValue, bool expected) + { + Console.WriteLine(long.MaxValue); + var constraint = new LongRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(@"^\d{3}-\d{3}-\d{4}$", "406-555-0123", true)] + [InlineData(@"^\d{3}$", "1234", false)] + public void RegexRouteConstraintTests(string pattern, string parameterValue, bool expected) + { + var constraint = new RegexRouteConstraint(pattern); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("alpha", true)] + [InlineData("a1pha", false)] + [InlineData("", true)] + public void AlphaRouteConstraintTests(string parameterValue, bool expected) + { + var constraint = new AlphaRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(long.MinValue, long.MaxValue, 2, true)] + [InlineData(3, 5, 4, true)] + [InlineData(3, 5, 5, true)] + [InlineData(3, 5, 3, true)] + [InlineData(3, 5, 6, false)] + [InlineData(3, 5, 2, false)] + [InlineData(3, 1, 2, false)] + public void RangeRouteConstraintTests(long min, long max, int parameterValue, bool expected) + { + var constraint = new RangeRouteConstraint(min, max); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, 4, true)] + [InlineData(3, 3, true)] + [InlineData(3, 2, false)] + public void MinRouteConstraintTests(long min, int parameterValue, bool expected) + { + var constraint = new MinRouteConstraint(min); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, 2, true)] + [InlineData(3, 3, true)] + [InlineData(3, 4, false)] + public void MaxRouteConstraintTests(long max, int parameterValue, bool expected) + { + var constraint = new MaxRouteConstraint(max); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, "1234", true)] + [InlineData(3, "123", true)] + [InlineData(3, "12", false)] + [InlineData(3, "", false)] + public void MinLengthRouteConstraintTests(int min, string parameterValue, bool expected) + { + var constraint = new MinLengthRouteConstraint(min); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, "", true)] + [InlineData(3, "12", true)] + [InlineData(3, "123", true)] + [InlineData(3, "1234", false)] + public void MaxLengthRouteConstraintTests(int min, string parameterValue, bool expected) + { + var constraint = new MaxLengthRouteConstraint(min); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, "123", true)] + [InlineData(3, "1234", false)] + public void LengthRouteConstraint_ExactLength_Tests(int length, string parameterValue, bool expected) + { + var constraint = new LengthRouteConstraint(length); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(3, 5, "12", false)] + [InlineData(3, 5, "123", true)] + [InlineData(3, 5, "1234", true)] + [InlineData(3, 5, "12345", true)] + [InlineData(3, 5, "123456", false)] + public void LengthRouteConstraint_Range_Tests(int min, int max, string parameterValue, bool expected) + { + var constraint = new LengthRouteConstraint(min, max); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("12345678-1234-1234-1234-123456789012", false, true)] + [InlineData("12345678-1234-1234-1234-123456789012", true, true)] + [InlineData("12345678901234567890123456789012", false, true)] + [InlineData("not-parseable-as-guid", false, false)] + [InlineData(12, false, false)] + public void GuidRouteConstraintTests(object parameterValue, bool parseBeforeTest, bool expected) + { + if (parseBeforeTest) + { + parameterValue = Guid.Parse(parameterValue.ToString()); + } + var constraint = new GuidRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("3.14", true)] + [InlineData(3.14f, true)] + [InlineData("not-parseable-as-float", false)] + [InlineData(false, false)] + [InlineData("1.79769313486232E+300", false)] + public void FloatRouteConstraintTests(object parameterValue, bool expected) + { + var constraint = new FloatRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("3.14", true)] + [InlineData(3.14f, true)] + [InlineData("1.79769313486232E+300", true)] + [InlineData("not-parseable-as-double", false)] + [InlineData(false, false)] + public void DoubleRouteConstraintTests(object parameterValue, bool expected) + { + var constraint = new DoubleRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("3.14", true)] + [InlineData("9223372036854775808.9223372036854775808", true)] + [InlineData("1.79769313486232E+300", false)] + [InlineData("not-parseable-as-decimal", false)] + [InlineData(false, false)] + public void DecimalRouteConstraintTests(object parameterValue, bool expected) + { + var constraint = new DecimalRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("12/25/2009", true)] + [InlineData("12/25/2009 11:45:00 PM", true)] + [InlineData("11:45:00 PM", true)] + [InlineData("2009-05-12T11:45:00Z", true)] + [InlineData("not-parseable-as-date", false)] + [InlineData(false, false)] + public void DateTimeRouteConstraint(object parameterValue, bool expected) + { + var constraint = new DateTimeRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("true", true)] + [InlineData("false", true)] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(1, false)] + [InlineData("not-parseable-as-bool", false)] + public void BoolRouteConstraint(object parameterValue, bool expected) + { + var constraint = new BoolRouteConstraint(); + var actual = TestValue(constraint, parameterValue); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(null, false, true)] + [InlineData("pass", true, true)] + [InlineData("fail", true, false)] + public void OptionalRouteConstraintTests(object parameterValue, bool shouldCallInner, bool expected) + { + // Arrange + var inner = MockConstraintWithResult((string)parameterValue != "fail"); + + // Act + var constraint = new OptionalRouteConstraint(inner.Object); +#if ASPNETWEBAPI + var optionalParameter = RouteParameter.Optional; +#else + var optionalParameter = UrlParameter.Optional; +#endif + var actual = TestValue(constraint, parameterValue ?? optionalParameter, route => + { + route.Defaults.Add("fake", optionalParameter); + }); + + // Assert + Assert.Equal(expected, actual); + + var timeMatchShouldHaveBeenCalled = shouldCallInner + ? Times.Once() + : Times.Never(); + + AssertMatchWasCalled(inner, timeMatchShouldHaveBeenCalled); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, false)] + public void CompoundRouteConstraintTests(bool inner1Result, bool inner2Result, bool expected) + { + // Arrange + var inner1 = MockConstraintWithResult(inner1Result); + + var inner2 = MockConstraintWithResult(inner2Result); + + // Act + var constraint = new CompoundRouteConstraint(new[] { inner1.Object, inner2.Object }); + var actual = TestValue(constraint, null); + + // Assert + Assert.Equal(expected, actual); + } + +#if ASPNETWEBAPI + static Expression> ConstraintMatchMethodExpression = + c => c.Match(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()); + + private static Mock MockConstraintWithResult(bool result) + { + var mock = new Mock(); + mock.Setup(ConstraintMatchMethodExpression) + .Returns(result) + .Verifiable(); + return mock; + } + + private static void AssertMatchWasCalled(Mock mock, Times times) + { + mock.Verify(ConstraintMatchMethodExpression, times); + } + + private static bool TestValue(IHttpRouteConstraint constraint, object value, Action routeConfig = null) + { + HttpRequestMessage httpRequestMessage = new HttpRequestMessage(); + + HttpRoute httpRoute = new HttpRoute(); + if (routeConfig != null) + { + routeConfig(httpRoute); + } + const string parameterName = "fake"; + HttpRouteValueDictionary values = new HttpRouteValueDictionary { { parameterName, value } }; + const HttpRouteDirection httpRouteDirection = HttpRouteDirection.UriResolution; + + return constraint.Match(httpRequestMessage, httpRoute, parameterName, values, httpRouteDirection); + } +#else + static Expression> ConstraintMatchMethodExpression = + c => c.Match(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()); + + private static Mock MockConstraintWithResult(bool result) + { + var mock = new Mock(); + mock.Setup(ConstraintMatchMethodExpression) + .Returns(result) + .Verifiable(); + return mock; + } + + private static void AssertMatchWasCalled(Mock mock, Times times) + { + mock.Verify(ConstraintMatchMethodExpression, times); + } + + private static bool TestValue(IRouteConstraint constraint, object value, Action routeConfig = null) + { + var context = new Mock(); + + Route route = new Route("", null); + route.Defaults = new RouteValueDictionary(); + + if (routeConfig != null) + { + routeConfig(route); + } + const string parameterName = "fake"; + RouteValueDictionary values = new RouteValueDictionary { { parameterName, value } }; + const RouteDirection routeDirection = RouteDirection.IncomingRequest; + + return constraint.Match(context.Object, route, parameterName, values, routeDirection); + } +#endif + + } +} \ No newline at end of file diff --git a/OData/test/Common/Routing/RouteFactoryAttributeTests.cs b/OData/test/Common/Routing/RouteFactoryAttributeTests.cs new file mode 100644 index 000000000..e61d4954a --- /dev/null +++ b/OData/test/Common/Routing/RouteFactoryAttributeTests.cs @@ -0,0 +1,598 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +#if ASPNETWEBAPI +using System.Web.Http.Controllers; +#endif +using Microsoft.TestCommon; +using Moq; + +#if ASPNETWEBAPI +using TActionDescriptor = System.Web.Http.Controllers.HttpActionDescriptor; +using TRoute = System.Web.Http.Routing.IHttpRoute; +using TRouteDictionary = System.Collections.Generic.IDictionary; +using TRouteDictionaryConcrete = System.Web.Http.Routing.HttpRouteValueDictionary; +#else +using TActionDescriptor = System.Web.Mvc.ActionDescriptor; +using TRoute = System.Web.Routing.Route; +using TRouteDictionary = System.Web.Routing.RouteValueDictionary; +using TRouteDictionaryConcrete = System.Web.Routing.RouteValueDictionary; +#endif + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class RouteFactoryAttributeTests + { + [Fact] + public void TemplateGet_ReturnsSpecifiedInstance() + { + // Arrange + string expectedTemplate = "RouteTemplate"; + RouteFactoryAttribute product = CreateProductUnderTest(expectedTemplate); + + // Act + string template = product.Template; + + // Assert + Assert.Same(expectedTemplate, template); + } + + [Fact] + public void NameGet_ReturnsNull() + { + // Arrange + RouteFactoryAttribute product = CreateProductUnderTest(); + + // Act + string name = product.Name; + + // Assert + Assert.Null(name); + } + + [Fact] + public void OrderGet_ReturnsZero() + { + // Arrange + RouteFactoryAttribute product = CreateProductUnderTest(); + + // Act + int order = product.Order; + + // Assert + Assert.Equal(0, order); + } + + [Fact] + public void ConstraintsGet_ReturnsNull() + { + // Arrange + RouteFactoryAttribute product = CreateProductUnderTest(); + + // Act + TRouteDictionary constraints = product.Constraints; + + // Assert + Assert.Null(constraints); + } + + [Fact] + public void CreateRoute_DelegatesToContextCreateBuilderBuild() + { + // Arrange + string expectedTemplate = "RouteTemplate"; + IDirectRouteFactory product = CreateProductUnderTest(expectedTemplate); + + RouteEntry expectedEntry = CreateEntry(); + + IDirectRouteBuilder builder = CreateBuilder(() => expectedEntry); + DirectRouteFactoryContext context = CreateContext((template) => template == expectedTemplate ? builder : + new DirectRouteBuilder(new TActionDescriptor[0], targetIsAction: true)); + + // Act + RouteEntry entry = product.CreateRoute(context); + + // Assert + Assert.Same(expectedEntry, entry); + } + + [Fact] + public void CreateRoute_IfContextIsNull_Throws() + { + // Arrange + DirectRouteFactoryContext context = null; + IDirectRouteFactory product = CreateProductUnderTest(); + + // Act & Assert + Assert.ThrowsArgumentNull(() => product.CreateRoute(context), "context"); + } + + [Fact] + public void CreateRoute_UsesNamePropertyWhenBuilding() + { + // Arrange + string expectedName = "RouteName"; + RouteFactoryAttribute product = CreateProductUnderTest(); + product.Name = expectedName; + + string name = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + name = builder.Name; + return null; + }); + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(expectedName, name); + } + + [Fact] + public void CreateRoute_UsesOrderPropertyWhenBuilding() + { + // Arrange + int expectedOrder = 123; + RouteFactoryAttribute product = CreateProductUnderTest(); + product.Order = expectedOrder; + + int order = 0; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + order = builder.Order; + return null; + }); + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Equal(expectedOrder, order); + } + + [Fact] + public void CreateRoute_IfBuilderDefaultsIsNull_UsesDefaultsPropertyWhenBuilding() + { + // Arrange + TRouteDictionary expectedDefaults = new TRouteDictionaryConcrete(); + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Defaults).Returns(expectedDefaults); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary defaults = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + defaults = builder.Defaults; + return null; + }); + Assert.Null(builder.Defaults); // Guard + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(expectedDefaults, defaults); + } + + [Fact] + public void CreateRoute_IfBuilderDefaultsIsNotNull_UpdatesDefaultsFromPropertyWhenBuilding() + { + // Arrange + TRouteDictionary existingDefaults = new TRouteDictionaryConcrete(); + string existingDefaultKey = "ExistingDefaultKey"; + object existingDefaultValue = "ExistingDefault"; + existingDefaults.Add(existingDefaultKey, existingDefaultValue); + string conflictingDefaultKey = "ConflictingDefaultKey"; + object oldConflictingDefaultValue = "OldConflictingDefault"; + existingDefaults.Add(conflictingDefaultKey, oldConflictingDefaultValue); + + TRouteDictionary additionalDefaults = new TRouteDictionaryConcrete(); + string additionalDefaultKey = "NewDefaultKey"; + string additionalDefaultValue = "NewDefault"; + additionalDefaults.Add(additionalDefaultKey, additionalDefaultValue); + string newConflictingDefaultValue = "NewConflictingDefault"; + additionalDefaults.Add(conflictingDefaultKey, newConflictingDefaultValue); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Defaults).Returns(additionalDefaults); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary defaults = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + defaults = builder.Defaults; + return null; + }); + + builder.Defaults = existingDefaults; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingDefaults, defaults); + Assert.Equal(3, defaults.Count); + Assert.True(defaults.ContainsKey(existingDefaultKey)); + Assert.Same(existingDefaultValue, defaults[existingDefaultKey]); + Assert.True(defaults.ContainsKey(conflictingDefaultKey)); + Assert.Same(newConflictingDefaultValue, defaults[conflictingDefaultKey]); + Assert.True(defaults.ContainsKey(additionalDefaultKey)); + Assert.Same(additionalDefaultValue, defaults[additionalDefaultKey]); + } + + [Fact] + public void CreateRoute_IfBuilderConstraintsIsNotNullAndDefaultsPropertyIsNull_UsesBuilderDefaults() + { + // Arrange + TRouteDictionary existingDefaults = new TRouteDictionaryConcrete(); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Defaults).Returns((TRouteDictionary)null); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary defaults = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + defaults = builder.Defaults; + return null; + }); + + builder.Defaults = existingDefaults; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingDefaults, defaults); + } + + [Fact] + public void CreateRoute_IfBuilderConstraintsIsNull_UsesConstraintsPropertyWhenBuilding() + { + // Arrange + TRouteDictionary expectedConstraints = new TRouteDictionaryConcrete(); + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Constraints).Returns(expectedConstraints); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary constraints = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + constraints = builder.Constraints; + return null; + }); + Assert.Null(builder.Constraints); // Guard + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(expectedConstraints, constraints); + } + + [Fact] + public void CreateRoute_IfBuilderConstraintsIsNotNull_UpdatesConstraintsFromPropertyWhenBuilding() + { + // Arrange + TRouteDictionary existingConstraints = new TRouteDictionaryConcrete(); + string existingConstraintKey = "ExistingConstraintKey"; + object existingConstraintValue = "ExistingConstraint"; + existingConstraints.Add(existingConstraintKey, existingConstraintValue); + string conflictingConstraintKey = "ConflictingConstraintKey"; + object oldConflictingConstraintValue = "OldConflictingConstraint"; + existingConstraints.Add(conflictingConstraintKey, oldConflictingConstraintValue); + + TRouteDictionary additionalConstraints = new TRouteDictionaryConcrete(); + string additionalConstraintKey = "NewConstraintKey"; + string additionalConstraintValue = "NewConstraint"; + additionalConstraints.Add(additionalConstraintKey, additionalConstraintValue); + string newConflictingConstraintValue = "NewConflictingConstraint"; + additionalConstraints.Add(conflictingConstraintKey, newConflictingConstraintValue); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Constraints).Returns(additionalConstraints); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary constraints = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + constraints = builder.Constraints; + return null; + }); + + builder.Constraints = existingConstraints; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingConstraints, constraints); + Assert.Equal(3, constraints.Count); + Assert.True(constraints.ContainsKey(existingConstraintKey)); + Assert.Same(existingConstraintValue, constraints[existingConstraintKey]); + Assert.True(constraints.ContainsKey(conflictingConstraintKey)); + Assert.Same(newConflictingConstraintValue, constraints[conflictingConstraintKey]); + Assert.True(constraints.ContainsKey(additionalConstraintKey)); + Assert.Same(additionalConstraintValue, constraints[additionalConstraintKey]); + } + + [Fact] + public void CreateRoute_IfBuilderConstraintsIsNotNullAndConstraintsPropertyIsNull_UsesBuilderConstraints() + { + // Arrange + TRouteDictionary existingConstraints = new TRouteDictionaryConcrete(); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.Constraints).Returns((TRouteDictionary)null); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary constraints = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + constraints = builder.Constraints; + return null; + }); + + builder.Constraints = existingConstraints; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingConstraints, constraints); + } + + [Fact] + public void CreateRoute_IfBuilderDataTokensIsNull_UsesDataTokensPropertyWhenBuilding() + { + // Arrange + TRouteDictionary expectedDataTokens = new TRouteDictionaryConcrete(); + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.DataTokens).Returns(expectedDataTokens); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary dataTokens = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + dataTokens = builder.DataTokens; + return null; + }); + Assert.Null(builder.DataTokens); // Guard + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(expectedDataTokens, dataTokens); + } + + [Fact] + public void CreateRoute_IfBuilderDataTokensIsNotNull_UpdatesDataTokensFromPropertyWhenBuilding() + { + // Arrange + TRouteDictionary existingDataTokens = new TRouteDictionaryConcrete(); + string existingDataTokenKey = "ExistingDataTokenKey"; + object existingDataTokenValue = "ExistingDataToken"; + existingDataTokens.Add(existingDataTokenKey, existingDataTokenValue); + string conflictingDataTokenKey = "ConflictingDataTokenKey"; + object oldConflictingDataTokenValue = "OldConflictingDataToken"; + existingDataTokens.Add(conflictingDataTokenKey, oldConflictingDataTokenValue); + + TRouteDictionary additionalDataTokens = new TRouteDictionaryConcrete(); + string additionalDataTokenKey = "NewDataTokenKey"; + string additionalDataTokenValue = "NewDataToken"; + additionalDataTokens.Add(additionalDataTokenKey, additionalDataTokenValue); + string newConflictingDataTokenValue = "NewConflictingDataToken"; + additionalDataTokens.Add(conflictingDataTokenKey, newConflictingDataTokenValue); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.DataTokens).Returns(additionalDataTokens); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary dataTokens = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + dataTokens = builder.DataTokens; + return null; + }); + + builder.DataTokens = existingDataTokens; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingDataTokens, dataTokens); + Assert.Equal(3, dataTokens.Count); + Assert.True(dataTokens.ContainsKey(existingDataTokenKey)); + Assert.Same(existingDataTokenValue, dataTokens[existingDataTokenKey]); + Assert.True(dataTokens.ContainsKey(conflictingDataTokenKey)); + Assert.Same(newConflictingDataTokenValue, dataTokens[conflictingDataTokenKey]); + Assert.True(dataTokens.ContainsKey(additionalDataTokenKey)); + Assert.Same(additionalDataTokenValue, dataTokens[additionalDataTokenKey]); + } + + [Fact] + public void CreateRoute_IfBuilderDataTokensIsNotNullAndDataTokensPropertyIsNull_UsesBuilderDataTokens() + { + // Arrange + TRouteDictionary existingDataTokens = new TRouteDictionaryConcrete(); + + Mock productMock = CreateProductUnderTestMock(); + productMock.SetupGet(p => p.DataTokens).Returns((TRouteDictionary)null); + IDirectRouteFactory product = productMock.Object; + + RouteEntry expectedEntry = CreateEntry(); + + TRouteDictionary dataTokens = null; + IDirectRouteBuilder builder = null; + builder = CreateBuilder(() => + { + dataTokens = builder.DataTokens; + return null; + }); + + builder.DataTokens = existingDataTokens; + + DirectRouteFactoryContext context = CreateContext((i) => builder); + + // Act + RouteEntry ignore = product.CreateRoute(context); + + // Assert + Assert.Same(existingDataTokens, dataTokens); + } + + [Fact] + public void AttributeUsage_IsAsSpecified() + { + // Act + AttributeUsageAttribute usage = (AttributeUsageAttribute)Attribute.GetCustomAttribute( + typeof(RouteFactoryAttribute), typeof(AttributeUsageAttribute)); + + // Assert + Assert.NotNull(usage); + Assert.Equal(AttributeTargets.Class | AttributeTargets.Method, usage.ValidOn); + Assert.Equal(false, usage.Inherited); + Assert.Equal(true, usage.AllowMultiple); + } + + private static IDirectRouteBuilder CreateBuilder(Func build) + { + return new LambdaDirectRouteBuilder(build); + } + + private static DirectRouteFactoryContext CreateContext(Func createBuilder) + { + return new LambdaDirectRouteFactoryContext(createBuilder); + } + + private static RouteEntry CreateEntry() + { +#if ASPNETWEBAPI + TRoute route = new Mock(MockBehavior.Strict).Object; +#else + TRoute route = new Mock(MockBehavior.Strict, null, null).Object; +#endif + return new RouteEntry("IgnoreEntry", route); + } + + private static RouteFactoryAttribute CreateProductUnderTest() + { + return CreateProductUnderTest("IgnoreTemplate"); + } + + private static RouteFactoryAttribute CreateProductUnderTest(string template) + { + return CreateProductUnderTestMock(template).Object; + } + + private static Mock CreateProductUnderTestMock() + { + return CreateProductUnderTestMock("IgnoreTemplate"); + } + + private static Mock CreateProductUnderTestMock(string template) + { + Mock mock = new Mock(template); + mock.CallBase = true; + return mock; + } + + private class LambdaDirectRouteFactoryContext : DirectRouteFactoryContext + { + private readonly Func _createBuilder; + + public LambdaDirectRouteFactoryContext(Func createBuilder) +#if ASPNETWEBAPI + : base(null, new TActionDescriptor[] { new Mock().Object }, + new Mock(MockBehavior.Strict).Object, + targetIsAction: true) +#else + : base(null, null, new TActionDescriptor[] { CreateStubActionDescriptor() }, + new Mock(MockBehavior.Strict).Object, targetIsAction: true) +#endif + { + Contract.Assert(createBuilder != null); + _createBuilder = createBuilder; + } + + internal override IDirectRouteBuilder CreateBuilderInternal(string template) + { + return _createBuilder.Invoke(template); + } + +#if !ASPNETWEBAPI + private static ActionDescriptor CreateStubActionDescriptor() + { + Mock mock = new Mock(); + mock.Setup(m => m.ControllerDescriptor).Returns(new Mock().Object); + return mock.Object; + } +#endif + } + + private class LambdaDirectRouteBuilder : DirectRouteBuilder + { + private readonly Func _build; + + public LambdaDirectRouteBuilder(Func build) + : base(new TActionDescriptor[0], targetIsAction: true) + { + Contract.Assert(build != null); + _build = build; + } + + public override RouteEntry Build() + { + return _build.Invoke(); + } + } + } +} diff --git a/OData/test/Common/Routing/RoutePrecedenceTests.cs b/OData/test/Common/Routing/RoutePrecedenceTests.cs new file mode 100644 index 000000000..b3eabdfb9 --- /dev/null +++ b/OData/test/Common/Routing/RoutePrecedenceTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if !ASPNETWEBAPI +using System.Web.Routing; +#endif +using Microsoft.TestCommon; + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class RoutePrecedenceTests + { + [Theory] + [InlineData("Employees/{id}", "Employees/{id}")] + [InlineData("abc", "def")] + [InlineData("{x:alpha}", "{x:int}")] + public void Compute_IsEqual(string xTemplate, string yTemplate) + { + // Arrange & Act + var xPrededence = Compute(xTemplate); + var yPrededence = Compute(yTemplate); + + // Assert + Assert.Equal(xPrededence, yPrededence); + } + + [Theory] + [InlineData("abc", "a{x}")] + [InlineData("abc", "{x}c")] + [InlineData("abc", "{x:int}")] + [InlineData("abc", "{x}")] + [InlineData("abc", "{*x}")] + [InlineData("{x:int}", "{x}")] + [InlineData("{x:int}", "{*x}")] + [InlineData("a{x}", "{x}")] + [InlineData("{x}c", "{x}")] + [InlineData("a{x}", "{*x}")] + [InlineData("{x}c", "{*x}")] + [InlineData("{x}", "{*x}")] + [InlineData("{*x:maxlength(10)}", "{*x}")] + [InlineData("abc/def", "abc/{x:int}")] + [InlineData("abc/def", "abc/{x}")] + [InlineData("abc/def", "abc/{*x}")] + [InlineData("abc/{x:int}", "abc/{x}")] + [InlineData("abc/{x:int}", "abc/{*x}")] + [InlineData("abc/{x}", "abc/{*x}")] + [InlineData("{x}/{y:int}", "{x}/{y}")] + public void Compute_IsLessThan(string xTemplate, string yTemplate) + { + // Arrange & Act + var xPrededence = Compute(xTemplate); + var yPrededence = Compute(yTemplate); + + // Assert + Assert.True(xPrededence < yPrededence); + } + + private static decimal Compute(string template) + { + DefaultInlineConstraintResolver resolver = new DefaultInlineConstraintResolver(); +#if ASPNETWEBAPI + HttpRouteValueDictionary defaults = new HttpRouteValueDictionary(); + HttpRouteValueDictionary constraints = new HttpRouteValueDictionary(); +#else + RouteValueDictionary defaults = new RouteValueDictionary(); + RouteValueDictionary constraints = new RouteValueDictionary(); +#endif + string standardRouteTemplate = InlineRouteTemplateParser.ParseRouteTemplate(template, + defaults, constraints, new DefaultInlineConstraintResolver()); + var parsedRoute = RouteParser.Parse(standardRouteTemplate); + return RoutePrecedence.Compute(parsedRoute, constraints); + } + } +} \ No newline at end of file diff --git a/OData/test/Common/Routing/SubRouteCollectionTest.cs b/OData/test/Common/Routing/SubRouteCollectionTest.cs new file mode 100644 index 000000000..35237eb1d --- /dev/null +++ b/OData/test/Common/Routing/SubRouteCollectionTest.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if !ASPNETWEBAPI +using System.Web.Routing; +#endif +using Microsoft.TestCommon; +using Moq; + +#if ASPNETWEBAPI +namespace System.Web.Http.Routing +#else +namespace System.Web.Mvc.Routing +#endif +{ + public class SubRouteCollectionTest + { +#if ASPNETWEBAPI + [Fact] + public void SubRouteCollection_Throws_OnDuplicateNamedRoute_WebAPI() + { + // Arrange + var collection = new SubRouteCollection(); + var route1 = new HttpRoute("api/Person"); + var route2 = new HttpRoute("api/Car"); + + collection.Add(new RouteEntry("route", route1)); + + var expectedError = + "A route named 'route' is already in the route collection. Route names must be unique.\r\n\r\n" + + "Duplicates:" + Environment.NewLine + + "api/Car" + Environment.NewLine + + "api/Person"; + + // Act & Assert + Assert.Throws(() => collection.Add(new RouteEntry("route", route2)), expectedError); + } +#else + [Fact] + public void SubRouteCollection_Throws_OnDuplicateNamedRoute_MVC() + { + // Arrange + var collection = new SubRouteCollection(); + var route1 = new Route("Home/Index", new Mock().Object); + var route2 = new Route("Person/Index", new Mock().Object); + + collection.Add(new RouteEntry("route", route1)); + + var expectedError = + "A route named 'route' is already in the route collection. Route names must be unique.\r\n\r\n" + + "Duplicates:" + Environment.NewLine + + "Person/Index" + Environment.NewLine + + "Home/Index"; + + // Act & Assert + Assert.Throws(() => collection.Add(new RouteEntry("route", route2)), expectedError); + } +#endif + } +} diff --git a/OData/test/Common/TaskHelpersExtensionsTest.cs b/OData/test/Common/TaskHelpersExtensionsTest.cs new file mode 100644 index 000000000..7fb5d0f50 --- /dev/null +++ b/OData/test/Common/TaskHelpersExtensionsTest.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.TestCommon; +using Moq; + +namespace System.Threading.Tasks +{ + public class TaskHelpersExtensionsTest + { + // ---------------------------------------------------------------- + // Task Task.CastToObject() + + [Fact, ForceGC] + public Task ConvertFromTaskOfStringShouldSucceed() + { + // Arrange + return Task.FromResult("StringResult") + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.RanToCompletion, task.Status); + Assert.Equal("StringResult", (string)task.Result); + }); + } + + [Fact, ForceGC] + public Task ConvertFromTaskOfIntShouldSucceed() + { + // Arrange + return Task.FromResult(123) + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.RanToCompletion, task.Status); + Assert.Equal(123, (int)task.Result); + }); + } + + [Fact, ForceGC] + public Task ConvertFromFaultedTaskOfObjectShouldBeHandled() + { + // Arrange + return TaskHelpers.FromError(new InvalidOperationException()) + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.Faulted, task.Status); + Assert.IsType(task.Exception.GetBaseException()); + }); + } + + [Fact, ForceGC] + public Task ConvertFromCancelledTaskOfStringShouldBeHandled() + { + // Arrange + return TaskHelpers.Canceled() + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.Canceled, task.Status); + }); + } + + // ---------------------------------------------------------------- + // Task Task.CastToObject() + + [Fact, ForceGC] + public Task ConvertFromTaskShouldSucceed() + { + // Arrange + return TaskHelpers.Completed() + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.RanToCompletion, task.Status); + Assert.Equal(null, task.Result); + }); + } + + [Fact, ForceGC] + public Task ConvertFromFaultedTaskShouldBeHandled() + { + // Arrange + return TaskHelpers.FromError(new InvalidOperationException()) + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.Faulted, task.Status); + Assert.IsType(task.Exception.GetBaseException()); + }); + } + + [Fact, ForceGC] + public Task ConvertFromCancelledTaskShouldBeHandled() + { + // Arrange + return TaskHelpers.Canceled() + + // Act + .CastToObject() + + // Assert + .ContinueWith((task) => + { + Assert.Equal(TaskStatus.Canceled, task.Status); + }); + } + + // ----------------------------------------------------------------- + // bool Task.TryGetResult(Task, out TResult) + + [Fact, ForceGC] + public void TryGetResult_CompleteTask_ReturnsTrueAndGivesResult() + { + // Arrange + var task = Task.FromResult(42); + + // Act + int value; + bool result = task.TryGetResult(out value); + + // Assert + Assert.True(result); + Assert.Equal(42, value); + } + + [Fact, ForceGC] + public void TryGetResult_FaultedTask_ReturnsFalse() + { + // Arrange + var task = TaskHelpers.FromError(new Exception()); + + // Act + int value; + bool result = task.TryGetResult(out value); + + // Assert + Assert.False(result); + var ex = task.Exception; // Observe the task exception + } + + [Fact, ForceGC] + public void TryGetResult_CanceledTask_ReturnsFalse() + { + // Arrange + var task = TaskHelpers.Canceled(); + + // Act + int value; + bool result = task.TryGetResult(out value); + + // Assert + Assert.False(result); + } + + [Fact, ForceGC] + public Task TryGetResult_IncompleteTask_ReturnsFalse() + { + // Arrange + var incompleteTask = new Task(() => 42); + + // Act + int value; + bool result = incompleteTask.TryGetResult(out value); + + // Assert + Assert.False(result); + + incompleteTask.Start(); + return incompleteTask; // Make sure the task gets observed + } + } +} diff --git a/OData/test/Common/TaskHelpersTest.cs b/OData/test/Common/TaskHelpersTest.cs new file mode 100644 index 000000000..96768021f --- /dev/null +++ b/OData/test/Common/TaskHelpersTest.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.TestCommon; + +namespace System.Threading.Tasks +{ + public class TaskHelpersTest + { + // ----------------------------------------------------------------- + // TaskHelpers.Canceled + + [Fact] + public void Canceled_ReturnsCanceledTask() + { + Task result = TaskHelpers.Canceled(); + + Assert.NotNull(result); + Assert.True(result.IsCanceled); + } + + // ----------------------------------------------------------------- + // TaskHelpers.Canceled + + [Fact] + public void Canceled_Generic_ReturnsCanceledTask() + { + Task result = TaskHelpers.Canceled(); + + Assert.NotNull(result); + Assert.True(result.IsCanceled); + } + + // ----------------------------------------------------------------- + // TaskHelpers.Completed + + [Fact] + public void Completed_ReturnsCompletedTask() + { + Task result = TaskHelpers.Completed(); + + Assert.NotNull(result); + Assert.Equal(TaskStatus.RanToCompletion, result.Status); + } + + // ----------------------------------------------------------------- + // TaskHelpers.FromError + + [Fact] + public void FromError_ReturnsFaultedTaskWithGivenException() + { + var exception = new Exception(); + + Task result = TaskHelpers.FromError(exception); + + Assert.NotNull(result); + Assert.True(result.IsFaulted); + Assert.Same(exception, result.Exception.InnerException); + } + + // ----------------------------------------------------------------- + // TaskHelpers.FromError + + [Fact] + public void FromError_Generic_ReturnsFaultedTaskWithGivenException() + { + var exception = new Exception(); + + Task result = TaskHelpers.FromError(exception); + + Assert.NotNull(result); + Assert.True(result.IsFaulted); + Assert.Same(exception, result.Exception.InnerException); + } + } +} diff --git a/OData/test/Common/TypeExtensionsTest.cs b/OData/test/Common/TypeExtensionsTest.cs new file mode 100644 index 000000000..6de161329 --- /dev/null +++ b/OData/test/Common/TypeExtensionsTest.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using Microsoft.TestCommon; + +namespace System +{ + public class TypeExtensionsTest + { + [Theory] + [InlineData(typeof(int), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(DateTime), false)] + [InlineData(typeof(int?), true)] + [InlineData(typeof(IEnumerable), true)] + [InlineData(typeof(int[]), true)] + [InlineData(typeof(string[]), true)] + public void IsNullable_Returns_ExpectedValue(Type type, bool expectedResult) + { + Assert.Equal(expectedResult, type.IsNullable()); + } + } +} diff --git a/OData/test/Common/UriQueryUtilityTest.cs b/OData/test/Common/UriQueryUtilityTest.cs new file mode 100644 index 000000000..a33c9732d --- /dev/null +++ b/OData/test/Common/UriQueryUtilityTest.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net.Http.Formatting; +using System.Text; +using System.Web.Http; +using Microsoft.TestCommon; + +namespace System.Net.Http +{ + public class UriQueryUtilityTest + { + public static TheoryDataSet UriQueryData + { + get + { + return UriQueryTestData.UriQueryData; + } + } + + [Fact] + public void TypeIsCorrect() + { + Assert.Type.HasProperties(typeof(UriQueryUtility), TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsStatic); + } + + [Fact] + public void UrlEncode_ReturnsNull() + { + Assert.Null(UriQueryUtility.UrlEncode(null)); + } + + [Fact] + public void UrlDecode_ReturnsNull() + { + Assert.Null(UriQueryUtility.UrlDecode(null)); + } + + [Fact] + public void UrlDecode_ParsesEmptySegmentsCorrectly() + { + int iterations = 16; + List segments = new List(); + + for (int index = 1; index < iterations; index++) + { + segments.Add("&"); + string query = string.Join("", segments); + NameValueCollection result = ParseQueryString(query); + Assert.NotNull(result); + + // Because this is a NameValueCollection, the same name appears only once + Assert.Equal(1, result.Count); + + // Values should be a comma separated list of empty strings + string[] values = result[""].Split(new char[] { ',' }); + + // We expect length+1 segment as the final '&' counts as a segment + Assert.Equal(index + 1, values.Length); + foreach (var value in values) + { + Assert.Equal("", value); + } + } + } + + [Theory] + [InlineData("N", "N", "")] + [InlineData("%26", "&", "")] + [InlineData("foo=%u0026", "foo", "%u0026")] + [PropertyData("UriQueryData")] + public void UrlDecode_ParsesCorrectly(string segment, string resultName, string resultValue) + { + int iterations = 16; + List segments = new List(); + + for (int index = 1; index < iterations; index++) + { + segments.Add(segment); + string query = CreateQuery(segments.ToArray()); + NameValueCollection result = ParseQueryString(query); + Assert.NotNull(result); + + // Because this is a NameValueCollection, the same name appears only once + Assert.Equal(1, result.Count); + + // Values should be a comma separated list of resultValue + string[] values = result[resultName].Split(new char[] { ',' }); + Assert.Equal(index, values.Length); + foreach (var value in values) + { + Assert.Equal(resultValue, value); + } + } + } + + private static string CreateQuery(params string[] segments) + { + StringBuilder buffer = new StringBuilder(); + bool first = true; + foreach (string segment in segments) + { + if (first) + { + first = false; + } + else + { + buffer.Append('&'); + } + + buffer.Append(segment); + } + + return buffer.ToString(); + } + + private static NameValueCollection ParseQueryString(string query) + { + return new FormDataCollection(query).ReadAsNameValueCollection(); + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/AppDomainUtils.cs b/OData/test/Microsoft.TestCommon/AppDomainUtils.cs new file mode 100644 index 000000000..5c8ca854a --- /dev/null +++ b/OData/test/Microsoft.TestCommon/AppDomainUtils.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.IO; +using System.Reflection; +using System.Web.Compilation; +using System.Web.Hosting; + +namespace System.Web.WebPages.TestUtils +{ + public static class AppDomainUtils + { + // Allow a test to modify static fields in an independent appdomain so that + // other tests will not be affected. + public static void RunInSeparateAppDomain(Action action) + { + RunInSeparateAppDomain(new AppDomainSetup(), action); + } + + public static void RunInSeparateAppDomain(AppDomainSetup setup, Action action) + { + var dir = Path.GetDirectoryName(typeof(AppDomainUtils).Assembly.CodeBase).Replace("file:\\", ""); + setup.PrivateBinPath = dir; + setup.ApplicationBase = dir; + setup.ApplicationName = Guid.NewGuid().ToString(); + setup.ShadowCopyFiles = "true"; + setup.ShadowCopyDirectories = setup.ApplicationBase; + setup.CachePath = Path.Combine(Path.GetTempPath(), setup.ApplicationName); + + AppDomain appDomain = null; + try + { + appDomain = AppDomain.CreateDomain(setup.ApplicationName, null, setup); + AppDomainHelper helper = appDomain.CreateInstanceAndUnwrap(typeof(AppDomainUtils).Assembly.FullName, typeof(AppDomainHelper).FullName) as AppDomainHelper; + helper.Run(action); + } + finally + { + if (appDomain != null) + { + AppDomain.Unload(appDomain); + } + } + } + + public class AppDomainHelper : MarshalByRefObject + { + public void Run(Action action) + { + action(); + } + } + + public static void SetPreAppStartStage() + { + var stage = typeof(BuildManager).GetProperty("PreStartInitStage", BindingFlags.Static | BindingFlags.NonPublic); + var value = ((FieldInfo)typeof(BuildManager).Assembly.GetType("System.Web.Compilation.PreStartInitStage").GetMember("DuringPreStartInit")[0]).GetValue(null); + stage.SetValue(null, value, new object[] { }); + SetAppData(); + var env = new HostingEnvironment(); + } + + public static void SetAppData() + { + var appdomain = AppDomain.CurrentDomain; + // Set some dummy values to make the appdomain seem more like a ASP.NET hosted one + appdomain.SetData(".appDomain", "*"); + appdomain.SetData(".appId", "appId"); + appdomain.SetData(".appPath", "appPath"); + appdomain.SetData(".appVPath", "/WebSite1"); + appdomain.SetData(".domainId", "1"); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Assert.cs b/OData/test/Microsoft.TestCommon/Assert.cs new file mode 100644 index 000000000..a33f4c539 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Assert.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon +{ + // This extends xUnit.net's Assert class, and makes it partial so that we can + // organize the extension points by logical functionality (rather than dumping them + // all into this single file). + // + // See files named XxxAssertions for root extensions to Assert. + public partial class Assert : Xunit.Assert + { + public static readonly ReflectionAssert Reflection = new ReflectionAssert(); + + public static readonly TypeAssert Type = new TypeAssert(); + + public static readonly HttpAssert Http = new HttpAssert(); + + public static readonly MediaTypeAssert MediaType = new MediaTypeAssert(); + + public static readonly GenericTypeAssert GenericType = new GenericTypeAssert(); + + public static readonly SerializerAssert Serializer = new SerializerAssert(); + + public static readonly StreamAssert Stream = new StreamAssert(); + + public static readonly TaskAssert Task = new TaskAssert(); + + public static readonly XmlAssert Xml = new XmlAssert(); + } +} diff --git a/OData/test/Microsoft.TestCommon/CultureReplacer.cs b/OData/test/Microsoft.TestCommon/CultureReplacer.cs new file mode 100644 index 000000000..fb0e59853 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/CultureReplacer.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Threading; + +namespace Microsoft.TestCommon +{ + public class CultureReplacer : IDisposable + { + private const string _defaultCultureName = "en-GB"; + private const string _defaultUICultureName = "en-US"; + private static readonly CultureInfo _defaultCulture = CultureInfo.GetCultureInfo(_defaultCultureName); + private readonly CultureInfo _originalCulture; + private readonly CultureInfo _originalUICulture; + private readonly long _threadId; + + // Culture => Formatting of dates/times/money/etc, defaults to en-GB because en-US is the same as InvariantCulture + // We want to be able to find issues where the InvariantCulture is used, but a specific culture should be. + // + // UICulture => Language + public CultureReplacer(string culture = _defaultCultureName, string uiCulture = _defaultUICultureName) + { + _originalCulture = Thread.CurrentThread.CurrentCulture; + _originalUICulture = Thread.CurrentThread.CurrentUICulture; + _threadId = Thread.CurrentThread.ManagedThreadId; + + Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture); + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(uiCulture); + } + + /// + /// The name of the culture that is used as the default value for Thread.CurrentCulture when CultureReplacer is used. + /// + public static string DefaultCultureName + { + get { return _defaultCultureName; } + } + + /// + /// The name of the culture that is used as the default value for Thread.UICurrentCulture when CultureReplacer is used. + /// + public static string DefaultUICultureName + { + get { return _defaultUICultureName; } + } + + /// + /// The culture that is used as the default value for Thread.CurrentCulture when CultureReplacer is used. + /// + public static CultureInfo DefaultCulture + { + get { return _defaultCulture; } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + Assert.True(Thread.CurrentThread.ManagedThreadId == _threadId, "The current thread is not the same as the thread invoking the constructor. This should never happen."); + Thread.CurrentThread.CurrentCulture = _originalCulture; + Thread.CurrentThread.CurrentUICulture = _originalUICulture; + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/DataAttribute.cs b/OData/test/Microsoft.TestCommon/DataAttribute.cs new file mode 100644 index 000000000..87494de3d --- /dev/null +++ b/OData/test/Microsoft.TestCommon/DataAttribute.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class DataAttribute : Xunit.Extensions.DataAttribute + { + } +} diff --git a/OData/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs b/OData/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs new file mode 100644 index 000000000..76b2e8574 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/DictionaryEqualityComparer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.TestCommon +{ + public class DictionaryEqualityComparer : IEqualityComparer> + { + public bool Equals(IDictionary x, IDictionary y) + { + if (x.Count != y.Count) + { + return false; + } + + foreach (string key in x.Keys) + { + object xVal = x[key]; + object yVal; + if (!y.TryGetValue(key, out yVal)) + { + return false; + } + + if (xVal == null) + { + if (yVal == null) + { + continue; + } + + return false; + } + + if (!xVal.Equals(yVal)) + { + return false; + } + } + + return true; + } + + public int GetHashCode(IDictionary obj) + { + return 1; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/EnumHelperTestBase.cs b/OData/test/Microsoft.TestCommon/EnumHelperTestBase.cs new file mode 100644 index 000000000..d066cf309 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/EnumHelperTestBase.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + public abstract class EnumHelperTestBase where TEnum : IComparable, IFormattable, IConvertible + { + private Func _isDefined; + private Action _validate; + private TEnum _undefined; + + /// + /// Helper to verify that we validate enums correctly when passed as arguments etc. + /// + /// A Func used to validate that a value is defined. + /// A Func used to validate that a value is definded of throw an exception. + /// An undefined value. + protected EnumHelperTestBase(Func isDefined, Action validate, TEnum undefined) + { + _isDefined = isDefined; + _validate = validate; + _undefined = undefined; + } + + [Fact] + public void IsDefined_ReturnsTrueForDefinedValues() + { + Array values = Enum.GetValues(typeof(TEnum)); + foreach (object value in values) + { + if (ValueExistsForFramework((TEnum)value)) + { + Assert.True(_isDefined((TEnum)value)); + } + else + { + Assert.False(_isDefined((TEnum)value)); + } + } + } + + [Fact] + public void IsDefined_ReturnsFalseForUndefinedValues() + { + Assert.False(_isDefined(_undefined)); + } + + [Fact] + public void Validate_DoesNotThrowForDefinedValues() + { + Array values = Enum.GetValues(typeof(TEnum)); + foreach (object value in values) + { + if (ValueExistsForFramework((TEnum)value)) + { + _validate((TEnum)value, "parameter"); + } + } + } + + [Fact] + public void Validate_ThrowsForUndefinedValues() + { + AssertForUndefinedValue( + () => _validate(_undefined, "parameter"), + "parameter", + (int)Convert.ChangeType(_undefined, typeof(int)), + typeof(TEnum), + allowDerivedExceptions: false); + } + + /// + /// Override this if InvalidEnumArgument is not supported in the targetted platform + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The expected invalid value that should appear in the message + /// The type of the enumeration + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + protected virtual void AssertForUndefinedValue(Action testCode, string parameterName, int invalidValue, Type enumType, bool allowDerivedExceptions = false) + { + Assert.ThrowsInvalidEnumArgument( + testCode, + parameterName, + invalidValue, + enumType, + allowDerivedExceptions); + } + + /// + /// Override this to determine if a given enum value for an enum exists in a given framework + /// + /// + /// Wheter the value exists + protected virtual bool ValueExistsForFramework(TEnum value) + { + return true; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/ExceptionAssertions.cs b/OData/test/Microsoft.TestCommon/ExceptionAssertions.cs new file mode 100644 index 000000000..777ff0180 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/ExceptionAssertions.cs @@ -0,0 +1,521 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Reflection; +using System.Threading.Tasks; +using System.Web; + +namespace Microsoft.TestCommon +{ + public partial class Assert + { + /// + /// Verifies that the exact exception is thrown (and not a derived exception type). + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static T Throws(Action testCode) + where T : Exception + { + return (T)Throws(typeof(T), testCode); + } + + /// + /// Verifies that the exact exception is thrown (and not a derived exception type). + /// Generally used to test property accessors. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static T Throws(Func testCode) + where T : Exception + { + return (T)Throws(typeof(T), testCode); + } + + /// + /// Verifies that the exact exception is thrown (and not a derived exception type). + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static Exception Throws(Type exceptionType, Action testCode) + { + Exception exception = RecordException(testCode); + return VerifyException(exceptionType, exception); + } + + /// + /// Verifies that the exact exception is thrown (and not a derived exception type). + /// Generally used to test property accessors. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static Exception Throws(Type exceptionType, Func testCode) + { + return Throws(exceptionType, () => { testCode(); }); + } + + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static TException Throws(Action testCode, bool allowDerivedExceptions) + where TException : Exception + { + Type exceptionType = typeof(TException); + Exception exception = RecordException(testCode); + + TargetInvocationException tie = exception as TargetInvocationException; + if (tie != null) + { + exception = tie.InnerException; + } + + if (exception == null) + { + throw new ThrowsException(exceptionType); + } + + var typedException = exception as TException; + if (typedException == null || (!allowDerivedExceptions && typedException.GetType() != typeof(TException))) + { + throw new ThrowsException(exceptionType, exception); + } + + return typedException; + } + + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// Generally used to test property accessors. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static TException Throws(Func testCode, bool allowDerivedExceptions) + where TException : Exception + { + return Throws(() => { testCode(); }, allowDerivedExceptions); + } + + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// Also verifies that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static TException Throws(Action testCode, string exceptionMessage, bool allowDerivedExceptions = false) + where TException : Exception + { + var ex = Throws(testCode, allowDerivedExceptions); + VerifyExceptionMessage(ex, exceptionMessage); + return ex; + } + + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// Also verified that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static TException Throws(Func testCode, string exceptionMessage, bool allowDerivedExceptions = false) + where TException : Exception + { + return Throws(() => { testCode(); }, exceptionMessage, allowDerivedExceptions); + } + + /// + /// Verifies that the code throws an (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentException ThrowsArgument(Action testCode, string paramName, bool allowDerivedExceptions = false) + { + var ex = Throws(testCode, allowDerivedExceptions); + + if (paramName != null) + { + Equal(paramName, ex.ParamName); + } + + return ex; + } + + /// + /// Verifies that the code throws an (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false) + { + var ex = Throws(testCode, allowDerivedExceptions); + + if (paramName != null) + { + Equal(paramName, ex.ParamName); + } + + VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); + + return ex; + } + + /// + /// Verifies that the code throws an ArgumentException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentException ThrowsArgument(Func testCode, string paramName, bool allowDerivedExceptions = false) + { + var ex = Throws(testCode, allowDerivedExceptions); + + if (paramName != null) + { + Equal(paramName, ex.ParamName); + } + + return ex; + } + + /// + /// Verifies that the code throws an ArgumentNullException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName) + { + var ex = Throws(testCode, allowDerivedExceptions: false); + + if (paramName != null) + { + Equal(paramName, ex.ParamName); + } + + return ex; + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName) + { + return Throws(testCode, "Value cannot be null or empty.\r\nParameter name: " + paramName, allowDerivedExceptions: false); + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty string. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName) + { + return ThrowsArgument(testCode, paramName, "Value cannot be null or an empty string.", allowDerivedExceptions: true); + } + + /// + /// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The actual value provided + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false, object actualValue = null) + { + if (exceptionMessage != null) + { + exceptionMessage = exceptionMessage + "\r\nParameter name: " + paramName; + if (actualValue != null) + { + exceptionMessage += String.Format(CultureReplacer.DefaultCulture, "\r\nActual value was {0}.", actualValue); + } + } + + var ex = Throws(testCode, exceptionMessage, allowDerivedExceptions); + + if (paramName != null) + { + Equal(paramName, ex.ParamName); + } + + return ex; + } + + /// + /// Verifies that the code throws an with the expected message that indicates that + /// the value must be greater than the given . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The actual value provided. + /// The expected limit value. + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentOutOfRangeException ThrowsArgumentGreaterThan(Action testCode, string paramName, string value, object actualValue = null) + { + return ThrowsArgumentOutOfRange( + testCode, + paramName, + String.Format(CultureReplacer.DefaultCulture, "Value must be greater than {0}.", value), false, actualValue); + } + + /// + /// Verifies that the code throws an with the expected message that indicates that + /// the value must be greater than or equal to the given value. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The expected limit value. + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentOutOfRangeException ThrowsArgumentGreaterThanOrEqualTo(Action testCode, string paramName, string value, object actualValue = null) + { + return ThrowsArgumentOutOfRange( + testCode, + paramName, + String.Format(CultureReplacer.DefaultCulture, "Value must be greater than or equal to {0}.", value), false, actualValue); + } + + /// + /// Verifies that the code throws an with the expected message that indicates that + /// the value must be less than the given . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The actual value provided. + /// The expected limit value. + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentOutOfRangeException ThrowsArgumentLessThan(Action testCode, string paramName, string maxValue, object actualValue = null) + { + return ThrowsArgumentOutOfRange( + testCode, + paramName, + String.Format(CultureReplacer.DefaultCulture, "Value must be less than {0}.", maxValue), false, actualValue); + } + + /// + /// Verifies that the code throws an with the expected message that indicates that + /// the value must be less than or equal to the given . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The actual value provided. + /// The expected limit value. + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ArgumentOutOfRangeException ThrowsArgumentLessThanOrEqualTo(Action testCode, string paramName, string maxValue, object actualValue = null) + { + return ThrowsArgumentOutOfRange( + testCode, + paramName, + String.Format(CultureReplacer.DefaultCulture, "Value must be less than or equal to {0}.", maxValue), false, actualValue); + } + + /// + /// Verifies that the code throws an HttpException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The exception message to verify + /// The expected HTTP status code of the exception + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static HttpException ThrowsHttpException(Action testCode, string exceptionMessage, int httpCode, bool allowDerivedExceptions = false) + { + var ex = Throws(testCode, exceptionMessage, allowDerivedExceptions); + Equal(httpCode, ex.GetHttpCode()); + return ex; + } + + /// + /// Verifies that the code throws an InvalidEnumArgumentException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The expected invalid value that should appear in the message + /// The type of the enumeration + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static InvalidEnumArgumentException ThrowsInvalidEnumArgument(Action testCode, string paramName, int invalidValue, Type enumType, bool allowDerivedExceptions = false) + { + string message = String.Format(CultureReplacer.DefaultCulture, + "The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.{3}Parameter name: {0}", + paramName, invalidValue, enumType.Name, Environment.NewLine); + return Throws(testCode, message, allowDerivedExceptions); + } + + /// + /// Verifies that the code throws an HttpException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the object that was dispose + /// Pass true to allow exceptions which derive from TException; pass false, otherwise + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + public static ObjectDisposedException ThrowsObjectDisposed(Action testCode, string objectName, bool allowDerivedExceptions = false) + { + var ex = Throws(testCode, allowDerivedExceptions); + + if (objectName != null) + { + Equal(objectName, ex.ObjectName); + } + + return ex; + } + + /// + /// Verifies that an exception of the given type is thrown. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown + /// + /// Unlike other Throws* methods, this method does not enforce running the exception delegate with a known Thread Culture. + /// + public static async Task ThrowsAsync(Func testCode) + where TException : Exception + { + Exception exception = null; + try + { + // The 'testCode' Task might execute asynchronously in a different thread making it hard to enforce the thread culture. + // The correct way to verify exception messages in such a scenario would be to run the task synchronously inside of a + // culture enforced block. + await testCode(); + } + catch (Exception ex) + { + exception = ex; + } + VerifyException(typeof(TException), exception); + return (TException)exception; + } + + // We've re-implemented all the xUnit.net Throws code so that we can get this + // updated implementation of RecordException which silently unwraps any instances + // of AggregateException. In addition to unwrapping exceptions, this method ensures + // that tests are executed in with a known set of Culture and UICulture. This prevents + // tests from failing when executed on a non-English machine. + private static Exception RecordException(Action testCode) + { + try + { + using (new CultureReplacer()) + { + testCode(); + } + return null; + } + catch (Exception exception) + { + return UnwrapException(exception); + } + } + + private static Exception UnwrapException(Exception exception) + { + AggregateException aggEx = exception as AggregateException; + if (aggEx != null) + { + return aggEx.GetBaseException(); + } + return exception; + } + + private static Exception VerifyException(Type exceptionType, Exception exception) + { + if (exception == null) + { + throw new ThrowsException(exceptionType); + } + else if (exceptionType != exception.GetType()) + { + throw new ThrowsException(exceptionType, exception); + } + + return exception; + } + + private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false) + { + if (expectedMessage != null) + { + if (!partialMatch) + { + Equal(expectedMessage, exception.Message); + } + else + { + Contains(expectedMessage, exception.Message); + } + } + } + + // Custom ThrowsException so we can filter the stack trace. + [Serializable] + private class ThrowsException : Xunit.Sdk.ThrowsException + { + public ThrowsException(Type type) : base(type) { } + + public ThrowsException(Type type, Exception ex) : base(type, ex) { } + + protected override bool ExcludeStackFrame(string stackFrame) + { + if (stackFrame.StartsWith("at Microsoft.TestCommon.Assert.", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return base.ExcludeStackFrame(stackFrame); + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/FactAttribute.cs b/OData/test/Microsoft.TestCommon/FactAttribute.cs new file mode 100644 index 000000000..449d68269 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/FactAttribute.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Xunit.Sdk; + +namespace Microsoft.TestCommon +{ + /// + /// An override of that provides extended capabilities. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class FactAttribute : Xunit.FactAttribute + { + public FactAttribute() + { + Timeout = Debugger.IsAttached ? Int32.MaxValue : TimeoutConstant.DefaultTimeout; + Platforms = Platform.All; + PlatformJustification = "Unsupported platform (test runs on {0}, current platform is {1})"; + } + + /// + /// Gets the platform that the unit test is currently running on. + /// + protected Platform Platform + { + get { return PlatformInfo.Platform; } + } + + /// + /// Gets or set the platforms that the unit test is compatible with. Defaults to + /// . + /// + public Platform Platforms { get; set; } + + /// + /// Gets or sets the platform skipping justification. This message can receive + /// the supported platforms as {0}, and the current platform as {1}. + /// + public string PlatformJustification { get; set; } + + /// + protected override IEnumerable EnumerateTestCommands(IMethodInfo method) + { + if ((Platforms & Platform) == 0) + { + return new[] { + new SkipCommand( + method, + DisplayName, + String.Format(PlatformJustification, Platforms.ToString().Replace(", ", " | "), Platform) + ) + }; + } + + return base.EnumerateTestCommands(method); + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/ForceGCAttribute.cs b/OData/test/Microsoft.TestCommon/ForceGCAttribute.cs new file mode 100644 index 000000000..03bc46c85 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/ForceGCAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.TestCommon +{ + public class ForceGCAttribute : Xunit.BeforeAfterTestAttribute + { + public override void After(MethodInfo methodUnderTest) + { + GC.Collect(99); + GC.Collect(99); + GC.Collect(99); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/InlineDataAttribute.cs b/OData/test/Microsoft.TestCommon/InlineDataAttribute.cs new file mode 100644 index 000000000..cd0443991 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/InlineDataAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class InlineDataAttribute : Xunit.Extensions.InlineDataAttribute + { + public InlineDataAttribute(params object[] dataValues) + : base(dataValues) + { + } + } +} diff --git a/OData/test/Microsoft.TestCommon/MatrixTheoryDataSet.cs b/OData/test/Microsoft.TestCommon/MatrixTheoryDataSet.cs new file mode 100644 index 000000000..8b304eee5 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/MatrixTheoryDataSet.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Microsoft.TestCommon +{ + public class MatrixTheoryDataSet : TheoryDataSet + { + public MatrixTheoryDataSet(IEnumerable data1, IEnumerable data2) + { + Contract.Assert(data1 != null && data1.Any()); + Contract.Assert(data2 != null && data2.Any()); + + foreach (T1 t1 in data1) + { + foreach (T2 t2 in data2) + { + Add(t1, t2); + } + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/MemberHelper.cs b/OData/test/Microsoft.TestCommon/MemberHelper.cs new file mode 100644 index 000000000..56dadb16c --- /dev/null +++ b/OData/test/Microsoft.TestCommon/MemberHelper.cs @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using Microsoft.TestCommon; + +namespace System.Web.TestUtil +{ + public static class MemberHelper + { + private static ConstructorInfo GetConstructorInfo(object instance, Type[] parameterTypes) + { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + ConstructorInfo constructorInfo = instance.GetType().GetConstructor(parameterTypes); + if (constructorInfo == null) + { + throw new ArgumentException(String.Format( + "A matching constructor on type '{0}' could not be found.", + instance.GetType().FullName)); + } + return constructorInfo; + } + + private static EventInfo GetEventInfo(object instance, string eventName) + { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + if (String.IsNullOrEmpty(eventName)) + { + throw new ArgumentException("An event must be specified.", "eventName"); + } + EventInfo eventInfo = instance.GetType().GetEvent(eventName); + if (eventInfo == null) + { + throw new ArgumentException(String.Format( + "An event named '{0}' on type '{1}' could not be found.", + eventName, instance.GetType().FullName)); + } + return eventInfo; + } + + private static MethodInfo GetMethodInfo(object instance, string methodName, Type[] types = null, MethodAttributes attrs = MethodAttributes.Public) + { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + if (String.IsNullOrEmpty(methodName)) + { + throw new ArgumentException("A method must be specified.", "methodName"); + } + + MethodInfo methodInfo; + if (types != null) + { + methodInfo = instance.GetType().GetMethod(methodName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + types, + null); + } + else + { + methodInfo = instance.GetType().GetMethod(methodName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + } + + if (methodInfo == null) + { + throw new ArgumentException(String.Format( + "A method named '{0}' on type '{1}' could not be found.", + methodName, instance.GetType().FullName)); + } + + if ((methodInfo.Attributes & attrs) != attrs) + { + throw new ArgumentException(String.Format( + "Method '{0}' on type '{1}' with attributes '{2}' does not match the attributes '{3}'.", + methodName, instance.GetType().FullName, methodInfo.Attributes, attrs)); + } + + return methodInfo; + } + + private static PropertyInfo GetPropertyInfo(object instance, string propertyName) + { + if (instance == null) + { + throw new ArgumentNullException("instance"); + } + if (String.IsNullOrEmpty(propertyName)) + { + throw new ArgumentException("A property must be specified.", "propertyName"); + } + PropertyInfo propInfo = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (propInfo == null) + { + throw new ArgumentException(String.Format( + "A property named '{0}' on type '{1}' could not be found.", + propertyName, instance.GetType().FullName)); + } + return propInfo; + } + + private static void TestAttribute(MemberInfo memberInfo, TAttribute attributeValue) + where TAttribute : Attribute + { + object[] attrs = memberInfo.GetCustomAttributes(typeof(TAttribute), true); + + if (attributeValue == null) + { + Assert.True(attrs.Length == 0, "Member should not have an attribute of type " + typeof(TAttribute)); + } + else + { + Assert.True(attrs != null && attrs.Length > 0, + "Member does not have an attribute of type " + typeof(TAttribute)); + Assert.Equal(attributeValue, attrs[0]); + } + } + + public static void TestBooleanProperty(object instance, string propertyName, bool initialValue, bool testDefaultValue) + { + // Assert initial value + TestGetPropertyValue(instance, propertyName, initialValue); + + if (testDefaultValue) + { + // Assert DefaultValue attribute matches inital value + TestDefaultValue(instance, propertyName); + } + + TestPropertyValue(instance, propertyName, true); + TestPropertyValue(instance, propertyName, false); + } + + public static void TestDefaultValue(object instance, string propertyName) + { + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + + object initialValue = propInfo.GetValue(instance, null); + TestAttribute(propInfo, new DefaultValueAttribute(initialValue)); + } + + public static void TestEvent(object instance, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs + { + EventInfo eventInfo = GetEventInfo(instance, eventName); + + // Assert category "Action" + TestAttribute(eventInfo, new CategoryAttribute("Action")); + + // Call protected method with no event handlers, assert no error + MethodInfo methodInfo = GetMethodInfo(instance, "On" + eventName, attrs: MethodAttributes.Family | MethodAttributes.Virtual); + methodInfo.Invoke(instance, new object[] { eventArgs }); + + // Attach handler, call method, assert fires once + List eventHandlerArgs = new List(); + EventHandler handler = new EventHandler(delegate(object sender, TEventArgs t) + { + eventHandlerArgs.Add(sender); + eventHandlerArgs.Add(t); + }); + eventInfo.AddEventHandler(instance, handler); + methodInfo.Invoke(instance, new object[] { eventArgs }); + Assert.Equal(new[] { instance, eventArgs }, eventHandlerArgs.ToArray()); + + // Detach handler, call method, assert not fired + eventHandlerArgs = new List(); + eventInfo.RemoveEventHandler(instance, handler); + methodInfo.Invoke(instance, new object[] { eventArgs }); + Assert.Empty(eventHandlerArgs); + } + + public static void TestGetPropertyValue(object instance, string propertyName, object valueToCheck) + { + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + object value = propInfo.GetValue(instance, null); + Assert.Equal(valueToCheck, value); + } + + public static void TestEnumProperty(object instance, string propertyName, TEnumValue initialValue, bool testDefaultValue) + { + // Assert initial value + TestGetPropertyValue(instance, propertyName, initialValue); + + if (testDefaultValue) + { + // Assert DefaultValue attribute matches inital value + TestDefaultValue(instance, propertyName); + } + + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + + // Values are sorted numerically + TEnumValue[] values = (TEnumValue[])Enum.GetValues(propInfo.PropertyType); + + // Assert get/set works for all valid enum values + foreach (TEnumValue value in values) + { + TestPropertyValue(instance, propertyName, value); + } + + // Assert ArgumentOutOfRangeException is thrown for value one less than smallest + // enum value, and one more than largest enum value + var targetException = Assert.Throws(() => propInfo.SetValue(instance, Convert.ToInt32(values[0]) - 1, null)); + Assert.IsType(targetException.InnerException); + + targetException = Assert.Throws(() => propInfo.SetValue(instance, Convert.ToInt32(values[values.Length - 1]) + 1, null)); + Assert.IsType(targetException.InnerException); + } + + public static void TestInt32Property(object instance, string propertyName, int value1, int value2) + { + TestPropertyValue(instance, propertyName, value1); + TestPropertyValue(instance, propertyName, value2); + } + + public static void TestPropertyWithDefaultInstance(object instance, string propertyName, object valueToSet) + { + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + + // Set to explicit property + propInfo.SetValue(instance, valueToSet, null); + object value = propInfo.GetValue(instance, null); + Assert.Equal(valueToSet, value); + + // Set to null + propInfo.SetValue(instance, null, null); + value = propInfo.GetValue(instance, null); + Assert.IsAssignableFrom(propInfo.PropertyType, value); + } + + public static void TestPropertyWithDefaultInstance(object instance, string propertyName, object valueToSet, object defaultValue) + { + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + + // Set to explicit property + propInfo.SetValue(instance, valueToSet, null); + object value = propInfo.GetValue(instance, null); + Assert.Same(valueToSet, value); + + // Set to null + propInfo.SetValue(instance, null, null); + value = propInfo.GetValue(instance, null); + Assert.Equal(defaultValue, value); + } + + public static void TestPropertyValue(object instance, string propertyName, object value) + { + TestPropertyValue(instance, propertyName, value, value); + } + + public static void TestPropertyValue(object instance, string propertyName, object valueToSet, object valueToCheck) + { + PropertyInfo propInfo = GetPropertyInfo(instance, propertyName); + propInfo.SetValue(instance, valueToSet, null); + object value = propInfo.GetValue(instance, null); + Assert.Equal(valueToCheck, value); + } + + public static void TestStringParams(object instance, Type[] constructorParameterTypes, object[] parameters) + { + ConstructorInfo ctor = GetConstructorInfo(instance, constructorParameterTypes); + TestStringParams(ctor, instance, parameters); + } + + public static void TestStringParams(object instance, string methodName, object[] parameters) + { + TestStringParams(instance, methodName, null, parameters); + } + + public static void TestStringParams(object instance, string methodName, Type[] types, object[] parameters) + { + MethodInfo method = GetMethodInfo(instance, methodName, types); + TestStringParams(method, instance, parameters); + } + + private static void TestStringParams(MethodBase method, object instance, object[] parameters) + { + ParameterInfo[] parameterInfos = method.GetParameters(); + foreach (ParameterInfo parameterInfo in parameterInfos) + { + if (parameterInfo.ParameterType == typeof(String)) + { + object originalParameter = parameters[parameterInfo.Position]; + + parameters[parameterInfo.Position] = null; + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + method.Invoke(instance, parameters); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + parameterInfo.Name); + + parameters[parameterInfo.Position] = String.Empty; + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + method.Invoke(instance, parameters); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + parameterInfo.Name); + + parameters[parameterInfo.Position] = originalParameter; + } + } + } + + public static void TestStringProperty(object instance, string propertyName, string initialValue, + bool testDefaultValueAttribute = false, bool allowNullAndEmpty = true, + string nullAndEmptyReturnValue = "") + { + // Assert initial value + TestGetPropertyValue(instance, propertyName, initialValue); + + if (testDefaultValueAttribute) + { + // Assert DefaultValue attribute matches inital value + TestDefaultValue(instance, propertyName); + } + + if (allowNullAndEmpty) + { + // Assert get/set works for null + TestPropertyValue(instance, propertyName, null, nullAndEmptyReturnValue); + + // Assert get/set works for String.Empty + TestPropertyValue(instance, propertyName, String.Empty, nullAndEmptyReturnValue); + } + else + { + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + TestPropertyValue(instance, propertyName, null); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + "value"); + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + TestPropertyValue(instance, propertyName, String.Empty); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + "value"); + } + + // Assert get/set works for arbitrary value + TestPropertyValue(instance, propertyName, "TestValue"); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj b/OData/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj new file mode 100644 index 000000000..3bcad063c --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft.TestCommon.csproj @@ -0,0 +1,103 @@ + + + + + {FCCC4CB7-BAF7-4A57-9F89-E5766FE536C0} + Library + Properties + Microsoft.TestCommon + Microsoft.TestCommon + ..\..\bin\$(Configuration)\Test\ + + + + + + + + + + + + + False + ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll + + + False + ..\..\packages\xunit.extensions.1.9.1\lib\net20\xunit.extensions.dll + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs new file mode 100644 index 000000000..d56a61962 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/CommonUnitTestDataSets.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.ObjectModel; +using Microsoft.TestCommon.Types; + +namespace Microsoft.TestCommon +{ + public class CommonUnitTestDataSets + { + public static ValueTypeTestData Chars { get { return TestData.CharTestData; } } + public static ValueTypeTestData Ints { get { return TestData.IntTestData; } } + public static ValueTypeTestData Uints { get { return TestData.UintTestData; } } + public static ValueTypeTestData Shorts { get { return TestData.ShortTestData; } } + public static ValueTypeTestData Ushorts { get { return TestData.UshortTestData; } } + public static ValueTypeTestData Longs { get { return TestData.LongTestData; } } + public static ValueTypeTestData Ulongs { get { return TestData.UlongTestData; } } + public static ValueTypeTestData Bytes { get { return TestData.ByteTestData; } } + public static ValueTypeTestData SBytes { get { return TestData.SByteTestData; } } + public static ValueTypeTestData Bools { get { return TestData.BoolTestData; } } + public static ValueTypeTestData Doubles { get { return TestData.DoubleTestData; } } + public static ValueTypeTestData Floats { get { return TestData.FloatTestData; } } + public static ValueTypeTestData DateTimes { get { return TestData.DateTimeTestData; } } + public static ValueTypeTestData Decimals { get { return TestData.DecimalTestData; } } + public static ValueTypeTestData TimeSpans { get { return TestData.TimeSpanTestData; } } + public static ValueTypeTestData Guids { get { return TestData.GuidTestData; } } + public static ValueTypeTestData DateTimeOffsets { get { return TestData.DateTimeOffsetTestData; } } + public static ValueTypeTestData SimpleEnums { get { return TestData.SimpleEnumTestData; } } + public static ValueTypeTestData LongEnums { get { return TestData.LongEnumTestData; } } + public static ValueTypeTestData FlagsEnums { get { return TestData.FlagsEnumTestData; } } + public static TestData EmptyStrings { get { return TestData.EmptyStrings; } } + public static RefTypeTestData Strings { get { return TestData.StringTestData; } } + public static TestData NonNullEmptyStrings { get { return TestData.NonNullEmptyStrings; } } + public static RefTypeTestData ISerializableTypes { get { return TestData.ISerializableTypeTestData; } } + public static ReadOnlyCollection ValueTypeTestDataCollection { get { return TestData.ValueTypeTestDataCollection; } } + public static ReadOnlyCollection RefTypeTestDataCollection { get { return TestData.RefTypeTestDataCollection; } } + public static ReadOnlyCollection ValueAndRefTypeTestDataCollection { get { return TestData.ValueAndRefTypeTestDataCollection; } } + public static ReadOnlyCollection RepresentativeValueAndRefTypeTestDataCollection { get { return TestData.RepresentativeValueAndRefTypeTestDataCollection; } } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs new file mode 100644 index 000000000..49fdd7a26 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/RefTypeTestData.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.TestCommon +{ + public class RefTypeTestData : TestData where T : class + { + private Func> testDataProvider; + private Func> derivedTypeTestDataProvider; + private Func> knownTypeTestDataProvider; + + public RefTypeTestData(Func> testDataProvider) + { + if (testDataProvider == null) + { + throw new ArgumentNullException("testDataProvider"); + } + + this.testDataProvider = testDataProvider; + this.RegisterTestDataVariation(TestDataVariations.WithNull, this.Type, GetNullTestData); + } + + public RefTypeTestData( + Func> testDataProvider, + Func> derivedTypeTestDataProvider, + Func> knownTypeTestDataProvider) + : this(testDataProvider) + { + this.derivedTypeTestDataProvider = derivedTypeTestDataProvider; + if (this.derivedTypeTestDataProvider != null) + { + this.RegisterTestDataVariation(TestDataVariations.AsDerivedType, this.Type, this.GetTestDataAsDerivedType); + } + + this.knownTypeTestDataProvider = knownTypeTestDataProvider; + if (this.knownTypeTestDataProvider != null) + { + this.RegisterTestDataVariation(TestDataVariations.AsKnownType, this.Type, this.GetTestDataAsDerivedKnownType); + } + } + + public T GetNullTestData() + { + return null; + } + + public IEnumerable GetTestDataAsDerivedType() + { + if (this.derivedTypeTestDataProvider != null) + { + return this.derivedTypeTestDataProvider(); + } + + return Enumerable.Empty(); + } + + public IEnumerable GetTestDataAsDerivedKnownType() + { + if (this.knownTypeTestDataProvider != null) + { + return this.knownTypeTestDataProvider(); + } + + return Enumerable.Empty(); + } + + protected override IEnumerable GetTypedTestData() + { + return this.testDataProvider(); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs new file mode 100644 index 000000000..141a75508 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestData.cs @@ -0,0 +1,463 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.TestCommon.Types; + +namespace Microsoft.TestCommon +{ + /// + /// A base class for test data. A instance is associated with a given type, and the instance can + /// provide instances of the given type to use as data in tests. The same instance can also provide instances + /// of types related to the given type, such as a of the type. See the enum for all the + /// variations of test data that a instance can provide. + /// + public abstract class TestData + { + /// + /// Common for a . + /// + public static readonly ValueTypeTestData CharTestData = new ValueTypeTestData('a', Char.MinValue, Char.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData IntTestData = new ValueTypeTestData(-1, 0, 1, Int32.MinValue, Int32.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData UintTestData = new ValueTypeTestData(0, 1, UInt32.MinValue, UInt32.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData ShortTestData = new ValueTypeTestData(-1, 0, 1, Int16.MinValue, Int16.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData UshortTestData = new ValueTypeTestData(0, 1, UInt16.MinValue, UInt16.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData LongTestData = new ValueTypeTestData(-1, 0, 1, Int64.MinValue, Int64.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData UlongTestData = new ValueTypeTestData(0, 1, UInt64.MinValue, UInt64.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData ByteTestData = new ValueTypeTestData(0, 1, Byte.MinValue, Byte.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData SByteTestData = new ValueTypeTestData(-1, 0, 1, SByte.MinValue, SByte.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData BoolTestData = new ValueTypeTestData(true, false); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData DoubleTestData = new ValueTypeTestData( + -1.0, + 0.0, + 1.0, + double.MinValue, + double.MaxValue, + double.PositiveInfinity, + double.NegativeInfinity); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData FloatTestData = new ValueTypeTestData( + -1.0f, + 0.0f, + 1.0f, + float.MinValue, + float.MaxValue, + float.PositiveInfinity, + float.NegativeInfinity); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData DecimalTestData = new ValueTypeTestData( + -1M, + 0M, + 1M, + decimal.MinValue, + decimal.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData DateTimeTestData = new ValueTypeTestData( + DateTime.Now, + DateTime.UtcNow, + DateTime.MaxValue, + DateTime.MinValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData TimeSpanTestData = new ValueTypeTestData( + TimeSpan.MinValue, + TimeSpan.MaxValue); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData GuidTestData = new ValueTypeTestData( + Guid.NewGuid(), + Guid.Empty); + + /// + /// Common for a . + /// + public static readonly ValueTypeTestData DateTimeOffsetTestData = new ValueTypeTestData( + DateTimeOffset.MaxValue, + DateTimeOffset.MinValue, + new DateTimeOffset(DateTime.Now)); + + /// + /// Common for an enum. + /// + public static readonly ValueTypeTestData SimpleEnumTestData = new ValueTypeTestData( + SimpleEnum.First, + SimpleEnum.Second, + SimpleEnum.Third); + + /// + /// Common for an enum implemented with a . + /// + public static readonly ValueTypeTestData LongEnumTestData = new ValueTypeTestData( + LongEnum.FirstLong, + LongEnum.SecondLong, + LongEnum.ThirdLong); + + /// + /// Common for an enum decorated with a . + /// + public static readonly ValueTypeTestData FlagsEnumTestData = new ValueTypeTestData( + FlagsEnum.One, + FlagsEnum.Two, + FlagsEnum.Four); + + /// + /// Expected permutations of non supported file paths. + /// + public static readonly TestData NotSupportedFilePaths = new RefTypeTestData(() => new List() { + "cc:\\a\\b", + }); + + /// + /// Expected permutations of invalid file paths. + /// + public static readonly TestData InvalidNonNullFilePaths = new RefTypeTestData(() => new List() { + String.Empty, + "", + " ", + " ", + "\t\t \n ", + "c:\\ab", + "c:\\a\"b", + "c:\\a\tb", + "c:\\a|b", + "c:\\a\bb", + "c:\\a\0b", + }); + + /// + /// All expected permutations of an empty string. + /// + public static readonly TestData NonNullEmptyStrings = new RefTypeTestData(() => new List() { String.Empty, "", " ", "\t\r\n" }); + + /// + /// All expected permutations of an empty string. + /// + public static readonly TestData EmptyStrings = new RefTypeTestData(() => new List() { null, String.Empty, "", " ", "\t\r\n" }); + + /// + /// Common for a . + /// + public static readonly RefTypeTestData StringTestData = new RefTypeTestData(() => new List() { + "", + " ", // one space + " ", // multiple spaces + " data ", // leading and trailing whitespace + "\t\t \n ", + "Some String!"}); + + /// + /// Common for a class that implements . + /// + public static readonly RefTypeTestData ISerializableTypeTestData = new RefTypeTestData( + ISerializableType.GetTestData); + + /// + /// A read-only collection of value type test data. + /// + public static readonly ReadOnlyCollection ValueTypeTestDataCollection = new ReadOnlyCollection(new TestData[] { + CharTestData, + IntTestData, + UintTestData, + ShortTestData, + UshortTestData, + LongTestData, + UlongTestData, + ByteTestData, + SByteTestData, + BoolTestData, + DoubleTestData, + FloatTestData, + DecimalTestData, + TimeSpanTestData, + GuidTestData, + DateTimeOffsetTestData, + SimpleEnumTestData, + LongEnumTestData, + FlagsEnumTestData}); + + /// + /// A read-only collection of reference type test data. + /// + public static readonly ReadOnlyCollection RefTypeTestDataCollection = new ReadOnlyCollection(new TestData[] { + StringTestData, + ISerializableTypeTestData}); + + /// + /// A read-only collection of value and reference type test data. + /// + public static readonly ReadOnlyCollection ValueAndRefTypeTestDataCollection = new ReadOnlyCollection( + ValueTypeTestDataCollection.Concat(RefTypeTestDataCollection).ToList()); + + /// + /// A read-only collection of representative values and reference type test data. + /// Uses where exhaustive coverage is not required. + /// + public static readonly ReadOnlyCollection RepresentativeValueAndRefTypeTestDataCollection = new ReadOnlyCollection(new TestData[] { + IntTestData, + BoolTestData, + SimpleEnumTestData, + StringTestData, + }); + + private Dictionary registeredTestDataVariations; + + + /// + /// Initializes a new instance of the class. + /// + /// The type associated with the instance. + protected TestData(Type type) + { + if (type.ContainsGenericParameters) + { + throw new InvalidOperationException("Only closed generic types are supported."); + } + + this.Type = type; + this.registeredTestDataVariations = new Dictionary(); + } + + /// + /// Gets the type associated with the instance. + /// + public Type Type { get; private set; } + + + /// + /// Gets the supported test data variations. + /// + /// + public IEnumerable GetSupportedTestDataVariations() + { + return this.registeredTestDataVariations.Keys; + } + + /// + /// Gets the related type for the given test data variation or returns null if the instance + /// doesn't support the given variation. + /// + /// The test data variation with which to create the related . + /// The related for the as given by the test data variation. + /// + /// For example, if the given was created for test data and the varation parameter + /// was then the returned type would be . + /// + public Type GetAsTypeOrNull(TestDataVariations variation) + { + TestDataVariationProvider testDataVariation = null; + if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation)) + { + return testDataVariation.Type; + } + + return null; + } + + /// + /// Gets test data for the given test data variation or returns null if the instance + /// doesn't support the given variation. + /// + /// The test data variation with which to create the related test data. + /// Test data of the type specified by the method. + public object GetAsTestDataOrNull(TestDataVariations variation) + { + TestDataVariationProvider testDataVariation = null; + if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation)) + { + return testDataVariation.TestDataProvider(); + } + + return null; + } + + + /// + /// Allows derived classes to register a that will + /// provide test data for a given variation. + /// + /// The variation with which to register the r. + /// The type of the test data created by the + /// A that will provide test data. + protected void RegisterTestDataVariation(TestDataVariations variation, Type type, Func testDataProvider) + { + this.registeredTestDataVariations.Add(variation, new TestDataVariationProvider(type, testDataProvider)); + } + + private class TestDataVariationProvider + { + public TestDataVariationProvider(Type type, Func testDataProvider) + { + this.Type = type; + this.TestDataProvider = testDataProvider; + } + + + public Func TestDataProvider { get; private set; } + + public Type Type { get; private set; } + } + } + + + /// + /// A generic base class for test data. + /// + /// The type associated with the test data. + public abstract class TestData : TestData, IEnumerable + { + private static readonly Type OpenIEnumerableType = typeof(IEnumerable<>); + private static readonly Type OpenListType = typeof(List<>); + private static readonly Type OpenIQueryableType = typeof(IQueryable<>); + private static readonly Type OpenDictionaryType = typeof(Dictionary<,>); + private static readonly Type OpenTestDataHolderType = typeof(TestDataHolder<>); + private int dictionaryKey; + + /// + /// Initializes a new instance of the class. + /// + protected TestData() + : base(typeof(T)) + { + Type[] typeParams = new Type[] { this.Type }; + Type[] dictionaryTypeParams = new Type[] { typeof(string), this.Type }; + + Type arrayType = this.Type.MakeArrayType(); + Type listType = OpenListType.MakeGenericType(typeParams); + Type iEnumerableType = OpenIEnumerableType.MakeGenericType(typeParams); + Type iQueryableType = OpenIQueryableType.MakeGenericType(typeParams); + Type dictionaryType = OpenDictionaryType.MakeGenericType(dictionaryTypeParams); + Type testDataHolderType = OpenTestDataHolderType.MakeGenericType(typeParams); + + this.RegisterTestDataVariation(TestDataVariations.AsInstance, this.Type, () => GetTypedTestData()); + this.RegisterTestDataVariation(TestDataVariations.AsArray, arrayType, GetTestDataAsArray); + this.RegisterTestDataVariation(TestDataVariations.AsIEnumerable, iEnumerableType, GetTestDataAsIEnumerable); + this.RegisterTestDataVariation(TestDataVariations.AsIQueryable, iQueryableType, GetTestDataAsIQueryable); + this.RegisterTestDataVariation(TestDataVariations.AsList, listType, GetTestDataAsList); + this.RegisterTestDataVariation(TestDataVariations.AsDictionary, dictionaryType, GetTestDataAsDictionary); + this.RegisterTestDataVariation(TestDataVariations.AsClassMember, testDataHolderType, GetTestDataInHolder); + } + + public IEnumerator GetEnumerator() + { + return (IEnumerator)this.GetTypedTestData().ToList().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)this.GetTypedTestData().ToList().GetEnumerator(); + } + + /// + /// Gets the test data as an array. + /// + /// An array of test data of the given type. + public T[] GetTestDataAsArray() + { + return this.GetTypedTestData().ToArray(); + } + + /// + /// Gets the test data as a . + /// + /// A of test data of the given type. + public List GetTestDataAsList() + { + return this.GetTypedTestData().ToList(); + } + + /// + /// Gets the test data as an . + /// + /// An of test data of the given type. + public IEnumerable GetTestDataAsIEnumerable() + { + return this.GetTypedTestData().AsEnumerable(); + } + + /// + /// Gets the test data as an . + /// + /// An of test data of the given type. + public IQueryable GetTestDataAsIQueryable() + { + return this.GetTypedTestData().AsQueryable(); + } + + public Dictionary GetTestDataAsDictionary() + { + // Some TestData collections contain duplicates e.g. UintTestData contains both 0 and UInt32.MinValue. + // Therefore use dictionaryKey, not _unused.ToString(). Reset key to keep dictionaries consistent if used + // multiple times. + dictionaryKey = 0; + return this.GetTypedTestData().ToDictionary(_unused => (dictionaryKey++).ToString()); + } + + public IEnumerable> GetTestDataInHolder() + { + return this.GetTypedTestData().Select(value => new TestDataHolder { V1 = value, }); + } + + /// + /// Must be implemented by derived types to return test data of the given type. + /// + /// Test data of the given type. + protected abstract IEnumerable GetTypedTestData(); + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataHolder.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataHolder.cs new file mode 100644 index 000000000..90e98f60e --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataHolder.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; + +namespace Microsoft.TestCommon +{ + /// + /// Equatable class wrapping a single instance of type . Equatable to ease test assertions. + /// + /// The to wrap. + public class TestDataHolder : IEquatable> + { + public T V1 { get; set; } + + bool IEquatable>.Equals(TestDataHolder other) + { + if (other == null) + { + return false; + } + + return Object.Equals(V1, other.V1); + } + + public override bool Equals(object obj) + { + TestDataHolder that = obj as TestDataHolder; + return ((IEquatable>)this).Equals(that); + } + + public override int GetHashCode() + { + if (typeof(ValueType).IsAssignableFrom(typeof(T)) || V1 != null) + { + return V1.GetHashCode(); + } + else + { + return 0; + } + } + + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "{{ V1: '{0}' }}", V1); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs new file mode 100644 index 000000000..40a538a1a --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/TestDataVariations.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + /// + /// An flags enum that can be used to indicate different variations of a given + /// instance. + /// + [Flags] + public enum TestDataVariations + { + /// + /// An individual instance of a given type. + /// + AsInstance = 0x1, + + /// + /// An individual instance of a type that derives from a given type. + /// + AsDerivedType = 0x2, + + /// + /// An individual instance of a given type that has a property value + /// that is a known type of the declared property type. + /// + AsKnownType = 0x4, + + /// + /// A instance of a given type. Only applies to + /// instances of . + /// + AsNullable = 0x8, + + /// + /// An instance of a of a given type. + /// + AsList = 0x10, + + /// + /// An instance of a array of the type. + /// + AsArray = 0x20, + + /// + /// An instance of an of a given type. + /// + AsIEnumerable = 0x40, + + /// + /// An instance of an of a given type. + /// + AsIQueryable = 0x80, + + /// + /// An instance of a DataContract type in which a given type is a member. + /// + AsDataMember = 0x100, + + /// + /// An instance of a type in which a given type is decorated with a + /// . + /// + AsXmlElementProperty = 0x200, + + /// + /// An instance of a of a given + /// type. + /// + AsDictionary = 0x400, + + /// + /// Add a null instance of the given type to the data set. This variation is + /// not included in or other variation masks. + /// + WithNull = 0x800, + + /// + /// Individual instances of containing the given . This + /// variation is not included in or other variation masks. + /// + AsClassMember = 0x1000, + + /// + /// All of the flags for single instance variations of a given type. + /// + AllSingleInstances = AsInstance | AsDerivedType | AsKnownType | AsNullable, + + /// + /// All of the flags for collection variations of a given type. + /// + AllCollections = AsList | AsArray | AsIEnumerable | AsIQueryable | AsDictionary, + + /// + /// All of the flags for variations in which a given type is a property on another type. + /// + AllProperties = AsDataMember | AsXmlElementProperty, + + /// + /// All of the flags for interface collection variations of a given type. + /// + AllInterfaces = AsIEnumerable | AsIQueryable, + + /// + /// All of the flags except for the interface collection variations of a given type. + /// + AllNonInterfaces = All & ~AllInterfaces, + + /// + /// All of the flags for all of the variations of a given type. + /// + All = AllSingleInstances | AllCollections | AllProperties + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs new file mode 100644 index 000000000..f4cd3e5ad --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/DataSets/ValueTypeTestData.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.TestCommon +{ + public class ValueTypeTestData : TestData where T : struct + { + private static readonly Type OpenNullableType = typeof(Nullable<>); + private T[] testData; + + public ValueTypeTestData(params T[] testData) + : base() + { + this.testData = testData; + + Type[] typeParams = new Type[] { this.Type }; + this.RegisterTestDataVariation(TestDataVariations.WithNull, OpenNullableType.MakeGenericType(typeParams), GetNullTestData); + this.RegisterTestDataVariation(TestDataVariations.AsNullable, OpenNullableType.MakeGenericType(typeParams), GetTestDataAsNullable); + } + + public object GetNullTestData() + { + return null; + } + + public IEnumerable> GetTestDataAsNullable() + { + return this.GetTypedTestData().Select(d => new Nullable(d)); + } + + protected override IEnumerable GetTypedTestData() + { + return this.testData; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs new file mode 100644 index 000000000..10c723278 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/GenericTypeAssert.cs @@ -0,0 +1,490 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.TestCommon +{ + /// + /// MSTest assertion class to provide convenience and assert methods for generic types + /// whose type parameters are not known at compile time. + /// + public class GenericTypeAssert + { + private static readonly GenericTypeAssert singleton = new GenericTypeAssert(); + + public static GenericTypeAssert Singleton { get { return singleton; } } + + /// + /// Asserts the given is a generic type and creates a new + /// bound generic type using . It then asserts there + /// is a constructor that will accept and returns it. + /// + /// The unbound generic base type. + /// The type of the single generic parameter to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The of that constructor which may be invoked to create that new generic type. + public ConstructorInfo GetConstructor(Type genericBaseType, Type genericParameterType, params Type[] parameterTypes) + { + Assert.NotNull(genericBaseType); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterType); + Assert.NotNull(parameterTypes); + + Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType }); + ConstructorInfo ctor = genericType.GetConstructor(parameterTypes); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name)); + return ctor; + } + + /// + /// Asserts the given is a generic type and creates a new + /// bound generic type using . It then asserts there + /// is a constructor that will accept and returns it. + /// + /// The unbound generic base type. + /// The types of the generic parameters to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The of that constructor which may be invoked to create that new generic type. + public ConstructorInfo GetConstructor(Type genericBaseType, Type[] genericParameterTypes, params Type[] parameterTypes) + { + Assert.NotNull(genericBaseType); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterTypes); + Assert.NotNull(parameterTypes); + + Type genericType = genericBaseType.MakeGenericType(genericParameterTypes); + ConstructorInfo ctor = genericType.GetConstructor(parameterTypes); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name)); + return ctor; + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The unbound generic base type. + /// The type of the single generic parameter to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The list of values to supply to the constructor + /// The instance created by calling that constructor. + public object InvokeConstructor(Type genericBaseType, Type genericParameterType, Type[] parameterTypes, object[] parameterValues) + { + ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterType, parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The unbound generic base type. + /// The types of the generic parameters to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The list of values to supply to the constructor + /// The instance created by calling that constructor. + public object InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, Type[] parameterTypes, object[] parameterValues) + { + ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterTypes, parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from the types of . + /// + /// The unbound generic base type. + /// The type of the single generic parameter to apply to create a bound generic type. + /// The list of values to supply to the constructor. It must be possible to determine the + /// The instance created by calling that constructor. + public object InvokeConstructor(Type genericBaseType, Type genericParameterType, params object[] parameterValues) + { + Assert.NotNull(genericBaseType); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterType); + + Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType }); + + ConstructorInfo ctor = FindConstructor(genericType, parameterValues); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name)); + return ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from the types of . + /// + /// The unbound generic base type. + /// The types of the generic parameters to apply to create a bound generic type. + /// The list of values to supply to the constructor. It must be possible to determine the + /// The instance created by calling that constructor. + public object InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, params object[] parameterValues) + { + Assert.NotNull(genericBaseType); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterTypes); + + Type genericType = genericBaseType.MakeGenericType(genericParameterTypes); + + ConstructorInfo ctor = FindConstructor(genericType, parameterValues); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name)); + return ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The type of object the constuctor is expected to yield. + /// The unbound generic base type. + /// The type of the single generic parameter to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The list of values to supply to the constructor + /// An instance of type . + public T InvokeConstructor(Type genericBaseType, Type genericParameterType, Type[] parameterTypes, object[] parameterValues) + { + ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterType, parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return (T)ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The type of object the constuctor is expected to yield. + /// The unbound generic base type. + /// The types of the generic parameters to apply to create a bound generic type. + /// The list of parameter types for a constructor that must exist. + /// The list of values to supply to the constructor + /// An instance of type . + public T InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, Type[] parameterTypes, object[] parameterValues) + { + ConstructorInfo ctor = GetConstructor(genericBaseType, genericParameterTypes, parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return (T)ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The type of object the constuctor is expected to yield. + /// The unbound generic base type. + /// The type of the single generic parameter to apply to create a bound generic type. + /// The list of values to supply to the constructor. It must be possible to determine the + /// The instance created by calling that constructor. + /// An instance of type . + public T InvokeConstructor(Type genericBaseType, Type genericParameterType, params object[] parameterValues) + { + Assert.NotNull(genericBaseType == null); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterType); + + Type genericType = genericBaseType.MakeGenericType(new Type[] { genericParameterType }); + + ConstructorInfo ctor = FindConstructor(genericType, parameterValues); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<{1}>',", genericBaseType.Name, genericParameterType.Name)); + return (T)ctor.Invoke(parameterValues); + } + + /// + /// Creates a new bound generic type and invokes the constructor matched from . + /// + /// The type of object the constuctor is expected to yield. + /// The unbound generic base type. + /// The types of the generic parameters to apply to create a bound generic type. + /// The list of values to supply to the constructor. It must be possible to determine the + /// The instance created by calling that constructor. + /// An instance of type . + public T InvokeConstructor(Type genericBaseType, Type[] genericParameterTypes, params object[] parameterValues) + { + Assert.NotNull(genericBaseType); + Assert.True(genericBaseType.IsGenericTypeDefinition); + Assert.NotNull(genericParameterTypes); + + Type genericType = genericBaseType.MakeGenericType(genericParameterTypes); + + ConstructorInfo ctor = FindConstructor(genericType, parameterValues); + Assert.True(ctor != null, String.Format("Test error: failed to locate generic ctor for type '{0}<>',", genericBaseType.Name)); + return (T)ctor.Invoke(parameterValues); + } + + /// + /// Asserts the given instance is one from a generic type of the specified parameter type. + /// + /// The type of instance. + /// The instance to test. + /// The type of the generic parameter to which the instance's generic type should have been bound. + public void IsCorrectGenericType(T instance, Type genericTypeParameter) + { + Assert.NotNull(instance); + Assert.NotNull(genericTypeParameter); + Assert.True(instance.GetType().IsGenericType); + Type[] genericArguments = instance.GetType().GetGenericArguments(); + Assert.Equal(1, genericArguments.Length); + Assert.Equal(genericTypeParameter, genericArguments[0]); + } + + /// + /// Invokes via Reflection the method on the given instance. + /// + /// The instance to use. + /// The name of the method to call. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public object InvokeMethod(object instance, string methodName, Type[] parameterTypes, object[] parameterValues) + { + Assert.NotNull(instance); + Assert.NotNull(parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + MethodInfo methodInfo = instance.GetType().GetMethod(methodName, parameterTypes); + Assert.NotNull(methodInfo); + return methodInfo.Invoke(instance, parameterValues); + } + + /// + /// Invokes via Reflection the static method on the given type. + /// + /// The type containing the method. + /// The name of the method to call. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public object InvokeMethod(Type type, string methodName, Type[] parameterTypes, object[] parameterValues) + { + Assert.NotNull(type); + Assert.NotNull(parameterTypes); + Assert.NotNull(parameterValues); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + MethodInfo methodInfo = type.GetMethod(methodName, parameterTypes); + Assert.NotNull(methodInfo); + return methodInfo.Invoke(null, parameterValues); + } + + /// + /// Invokes via Reflection the static method on the given type. + /// + /// The type containing the method. + /// The name of the method to call. + /// The generic parameter type of the method. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public MethodInfo CreateGenericMethod(Type type, string methodName, Type genericParameterType, Type[] parameterTypes) + { + Assert.NotNull(type); + Assert.NotNull(parameterTypes); + Assert.NotNull(genericParameterType); + //MethodInfo methodInfo = type.GetMethod(methodName, parameterTypes); + MethodInfo methodInfo = type.GetMethods().Where((m) => m.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase) && m.IsGenericMethod && AreAssignableFrom(m.GetParameters(), parameterTypes)).FirstOrDefault(); + Assert.NotNull(methodInfo); + Assert.True(methodInfo.IsGenericMethod); + MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType); + Assert.NotNull(genericMethod); + return genericMethod; + } + + /// + /// Invokes via Reflection the static generic method on the given type. + /// + /// The type containing the method. + /// The name of the method to call. + /// The generic parameter type of the method. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public object InvokeGenericMethod(Type type, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues) + { + MethodInfo methodInfo = CreateGenericMethod(type, methodName, genericParameterType, parameterTypes); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return methodInfo.Invoke(null, parameterValues); + } + + /// + /// Invokes via Reflection the generic method on the given instance. + /// + /// The instance on which to invoke the method. + /// The name of the method to call. + /// The generic parameter type of the method. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public object InvokeGenericMethod(object instance, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues) + { + Assert.NotNull(instance); + MethodInfo methodInfo = CreateGenericMethod(instance.GetType(), methodName, genericParameterType, parameterTypes); + Assert.Equal(parameterTypes.Length, parameterValues.Length); + return methodInfo.Invoke(instance, parameterValues); + } + + /// + /// Invokes via Reflection the generic method on the given instance. + /// + /// The type of the return value from the method. + /// The instance on which to invoke the method. + /// The name of the method to call. + /// The generic parameter type of the method. + /// The types of the parameters to the method. + /// The values to supply to the method. + /// The results of the method. + public T InvokeGenericMethod(object instance, string methodName, Type genericParameterType, Type[] parameterTypes, object[] parameterValues) + { + return (T)InvokeGenericMethod(instance, methodName, genericParameterType, parameterTypes, parameterValues); + } + + /// + /// Invokes via Reflection the method on the given instance. + /// + /// The instance to use. + /// The name of the method to call. + /// The values to supply to the method. + /// The results of the method. + public object InvokeMethod(object instance, string methodName, params object[] parameterValues) + { + Assert.NotNull(instance); + MethodInfo methodInfo = FindMethod(instance.GetType(), methodName, parameterValues); + Assert.NotNull(methodInfo); + return methodInfo.Invoke(instance, parameterValues); + } + + /// + /// Invokes via Reflection the static method on the given type. + /// + /// The instance to use. + /// The name of the method to call. + /// The values to supply to the method. + /// The results of the method. + public object InvokeMethod(Type type, string methodName, params object[] parameterValues) + { + Assert.NotNull(type); + MethodInfo methodInfo = FindMethod(type, methodName, parameterValues); + Assert.NotNull(methodInfo); + return methodInfo.Invoke(null, parameterValues); + } + + /// + /// Invokes via Reflection the method on the given instance. + /// + /// The instance to use. + /// The name of the method to call. + /// The type of the generic parameter. + /// The values to supply to the method. + /// The results of the method. + public object InvokeGenericMethod(object instance, string methodName, Type genericParameterType, params object[] parameterValues) + { + Assert.NotNull(instance); + Assert.NotNull(genericParameterType); + MethodInfo methodInfo = FindMethod(instance.GetType(), methodName, parameterValues); + Assert.NotNull(methodInfo); + Assert.True(methodInfo.IsGenericMethod); + MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType); + return genericMethod.Invoke(instance, parameterValues); + } + + /// + /// Invokes via Reflection the method on the given instance. + /// + /// The instance to use. + /// The name of the method to call. + /// The type of the generic parameter. + /// The values to supply to the method. + /// The results of the method. + public object InvokeGenericMethod(Type type, string methodName, Type genericParameterType, params object[] parameterValues) + { + Assert.NotNull(type); + Assert.NotNull(genericParameterType); + MethodInfo methodInfo = FindMethod(type, methodName, parameterValues); + Assert.NotNull(methodInfo); + Assert.True(methodInfo.IsGenericMethod); + MethodInfo genericMethod = methodInfo.MakeGenericMethod(genericParameterType); + return genericMethod.Invoke(null, parameterValues); + } + + /// + /// Retrieves the value from the specified property. + /// + /// The instance containing the property value. + /// The name of the property. + /// The error message to prefix any test assertions. + /// The value returned from the property. + public object GetProperty(object instance, string propertyName, string failureMessage) + { + PropertyInfo propertyInfo = instance.GetType().GetProperty(propertyName); + Assert.NotNull(propertyInfo); + return propertyInfo.GetValue(instance, null); + } + + private static bool AreAssignableFrom(Type[] parameterTypes, params object[] parameterValues) + { + Assert.NotNull(parameterTypes); + Assert.NotNull(parameterValues); + if (parameterTypes.Length != parameterValues.Length) + { + return false; + } + + for (int i = 0; i < parameterTypes.Length; ++i) + { + if (!parameterTypes[i].IsInstanceOfType(parameterValues[i])) + { + return false; + } + } + + return true; + } + + private static bool AreAssignableFrom(ParameterInfo[] parameterInfos, params Type[] parameterTypes) + { + Assert.NotNull(parameterInfos); + Assert.NotNull(parameterTypes); + Type[] parameterInfoTypes = parameterInfos.Select((info) => info.ParameterType).ToArray(); + if (parameterInfoTypes.Length != parameterTypes.Length) + { + return false; + } + + for (int i = 0; i < parameterInfoTypes.Length; ++i) + { + // Generic parameters are assumed to be assignable + if (parameterInfoTypes[i].IsGenericParameter) + { + continue; + } + + if (!parameterInfoTypes[i].IsAssignableFrom(parameterTypes[i])) + { + return false; + } + } + + return true; + } + + private static bool AreAssignableFrom(ParameterInfo[] parameterInfos, params object[] parameterValues) + { + Assert.NotNull(parameterInfos); + Assert.NotNull(parameterValues); + Type[] parameterTypes = parameterInfos.Select((info) => info.ParameterType).ToArray(); + return AreAssignableFrom(parameterTypes, parameterValues); + } + + private static ConstructorInfo FindConstructor(Type type, params object[] parameterValues) + { + Assert.NotNull(type); + Assert.NotNull(parameterValues); + return type.GetConstructors().FirstOrDefault((c) => AreAssignableFrom(c.GetParameters(), parameterValues)); + } + + private static MethodInfo FindMethod(Type type, string methodName, params object[] parameterValues) + { + Assert.NotNull(type); + Assert.False(String.IsNullOrWhiteSpace(methodName)); + Assert.NotNull(parameterValues); + return type.GetMethods().FirstOrDefault((m) => String.Equals(m.Name, methodName, StringComparison.Ordinal) && AreAssignableFrom(m.GetParameters(), parameterValues)); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs new file mode 100644 index 000000000..1054ea538 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/HttpAssert.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.RegularExpressions; + +namespace Microsoft.TestCommon +{ + /// + /// Unit test utility for testing instances. + /// + public class HttpAssert + { + private const string CommaSeperator = ", "; + private static readonly HttpAssert singleton = new HttpAssert(); + + public static HttpAssert Singleton { get { return singleton; } } + + /// + /// Asserts that the expected is equal to the actual . + /// + /// The expected . Should not be null. + /// The actual . Should not be null. + public void Equal(HttpRequestMessage expected, HttpRequestMessage actual) + { + Assert.NotNull(expected); + Assert.NotNull(actual); + + Assert.Equal(expected.Version, actual.Version); + Equal(expected.Headers, actual.Headers); + + if (expected.Content == null) + { + Assert.Null(actual.Content); + } + else + { + string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result); + string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result); + Assert.Equal(expectedContent, actualContent); + Equal(expected.Content.Headers, actual.Content.Headers); + } + } + + /// + /// Asserts that the expected is equal to the actual . + /// + /// The expected . Should not be null. + /// The actual . Should not be null. + public void Equal(HttpResponseMessage expected, HttpResponseMessage actual) + { + Equal(expected, actual, null); + } + + /// + /// Asserts that the expected is equal to the actual . + /// + /// The expected . Should not be null. + /// The actual . Should not be null. + /// The callback to verify the Content string. If it is null, Assert.Equal will be used. + public void Equal(HttpResponseMessage expected, HttpResponseMessage actual, Action verifyContentStringCallback) + { + Assert.NotNull(expected); + Assert.NotNull(actual); + + Assert.Equal(expected.StatusCode, actual.StatusCode); + Assert.Equal(expected.ReasonPhrase, actual.ReasonPhrase); + Assert.Equal(expected.Version, actual.Version); + Equal(expected.Headers, actual.Headers); + + if (expected.Content == null) + { + Assert.Null(actual.Content); + } + else + { + string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result); + string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result); + if (verifyContentStringCallback != null) + { + verifyContentStringCallback(expectedContent, actualContent); + } + else + { + Assert.Equal(expectedContent, actualContent); + } + Equal(expected.Content.Headers, actual.Content.Headers); + } + } + + /// + /// Asserts that the expected instance is equal to the actual instance. + /// + /// The expected instance. Should not be null. + /// The actual instance. Should not be null. + public void Equal(HttpHeaders expectedHeaders, HttpHeaders actualHeaders) + { + Assert.NotNull(expectedHeaders); + Assert.NotNull(actualHeaders); + + Assert.Equal(expectedHeaders.Count(), actualHeaders.Count()); + + foreach (KeyValuePair> expectedHeader in expectedHeaders) + { + KeyValuePair> actualHeader = actualHeaders.FirstOrDefault(h => h.Key == expectedHeader.Key); + Assert.NotNull(actualHeader); + + if (expectedHeader.Key == "Date") + { + HandleDateHeader(expectedHeader.Value.ToArray(), actualHeader.Value.ToArray()); + } + else + { + string expectedHeaderStr = string.Join(CommaSeperator, expectedHeader.Value); + string actualHeaderStr = string.Join(CommaSeperator, actualHeader.Value); + Assert.Equal(expectedHeaderStr, actualHeaderStr); + } + } + } + + /// + /// Asserts the given contain the given + /// for the given . + /// + /// The to examine. It cannot be null. + /// The name of the header. It cannot be empty. + /// The values that must all be present. It cannot be null. + public void Contains(HttpHeaders headers, string name, params string[] values) + { + Assert.NotNull(headers); + Assert.False(String.IsNullOrWhiteSpace(name), "Test error: name cannot be empty."); + Assert.NotNull(values); + + IEnumerable headerValues = null; + bool foundIt = headers.TryGetValues(name, out headerValues); + Assert.True(foundIt); + + foreach (string value in values) + { + Assert.Contains(value, headerValues); + } + } + + public bool IsKnownUnserializableType(Type type, Func isTypeUnserializableCallback) + { + if (isTypeUnserializableCallback != null && isTypeUnserializableCallback(type)) + { + return true; + } + + if (type.IsGenericType) + { + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + if (type.GetMethod("Add") == null) + { + return true; + } + } + + // Generic type -- recursively analyze generic arguments + return IsKnownUnserializableType(type.GetGenericArguments()[0], isTypeUnserializableCallback); + } + + if (type.HasElementType && IsKnownUnserializableType(type.GetElementType(), isTypeUnserializableCallback)) + { + return true; + } + + return false; + } + + public bool IsKnownUnserializable(Type type, object obj, Func isTypeUnserializableCallback) + { + if (IsKnownUnserializableType(type, isTypeUnserializableCallback)) + { + return true; + } + + return obj != null && IsKnownUnserializableType(obj.GetType(), isTypeUnserializableCallback); + } + + public bool IsKnownUnserializable(Type type, object obj) + { + return IsKnownUnserializable(type, obj, null); + } + + public bool CanRoundTrip(Type type) + { + if (typeof(DateTime).IsAssignableFrom(type)) + { + return false; + } + + if (typeof(DateTimeOffset).IsAssignableFrom(type)) + { + return false; + } + + if (type.IsGenericType) + { + foreach (Type genericParameterType in type.GetGenericArguments()) + { + if (!CanRoundTrip(genericParameterType)) + { + return false; + } + } + } + + if (type.HasElementType) + { + return CanRoundTrip(type.GetElementType()); + } + + return true; + } + + private static void HandleDateHeader(string[] expectedDateHeaderValues, string[] actualDateHeaderValues) + { + Assert.Equal(expectedDateHeaderValues.Length, actualDateHeaderValues.Length); + + for (int i = 0; i < expectedDateHeaderValues.Length; i++) + { + DateTime expectedDateTime = DateTime.Parse(expectedDateHeaderValues[i]); + DateTime actualDateTime = DateTime.Parse(actualDateHeaderValues[i]); + + Assert.Equal(expectedDateTime.Year, actualDateTime.Year); + Assert.Equal(expectedDateTime.Month, actualDateTime.Month); + Assert.Equal(expectedDateTime.Day, actualDateTime.Day); + + int hourDifference = Math.Abs(actualDateTime.Hour - expectedDateTime.Hour); + Assert.True(hourDifference <= 1); + + int minuteDifference = Math.Abs(actualDateTime.Minute - expectedDateTime.Minute); + Assert.True(minuteDifference <= 1); + } + } + + private static string CleanContentString(string content) + { + Assert.Null(content); + + string cleanedContent = null; + + // remove any port numbers from Uri's + cleanedContent = Regex.Replace(content, ":\\d+", ""); + + return cleanedContent; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs new file mode 100644 index 000000000..0bca8e37f --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeAssert.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Net.Http.Headers; + +namespace Microsoft.TestCommon +{ + public class MediaTypeAssert + { + private static readonly MediaTypeAssert singleton = new MediaTypeAssert(); + + public static MediaTypeAssert Singleton { get { return singleton; } } + + public void AreEqual(MediaTypeHeaderValue expected, MediaTypeHeaderValue actual, string errorMessage) + { + if (expected != null || actual != null) + { + Assert.NotNull(expected); + Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actual)); + } + } + + public void AreEqual(MediaTypeHeaderValue expected, string actual, string errorMessage) + { + if (expected != null || !String.IsNullOrEmpty(actual)) + { + MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual); + Assert.NotNull(expected); + Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actualMediaType)); + } + } + + public void AreEqual(string expected, string actual, string errorMessage) + { + if (!String.IsNullOrEmpty(expected) || !String.IsNullOrEmpty(actual)) + { + Assert.NotNull(expected); + MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected); + MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual); + Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actualMediaType)); + } + } + + public void AreEqual(string expected, MediaTypeHeaderValue actual, string errorMessage) + { + if (!String.IsNullOrEmpty(expected) || actual != null) + { + Assert.NotNull(expected); + MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected); + Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actual)); + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs new file mode 100644 index 000000000..d454146a5 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/MediaTypeHeaderValueComparer.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http.Headers; + +namespace Microsoft.TestCommon +{ + public class MediaTypeHeaderValueComparer : IComparer + { + private static readonly MediaTypeHeaderValueComparer mediaTypeComparer = new MediaTypeHeaderValueComparer(); + + public MediaTypeHeaderValueComparer() + { + } + + public static MediaTypeHeaderValueComparer Comparer + { + get + { + return mediaTypeComparer; + } + } + + public int Compare(MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2) + { + ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1); + ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2); + + int returnValue = CompareBasedOnQualityFactor(parsedMediaType1, parsedMediaType2); + + if (returnValue == 0) + { + if (!String.Equals(parsedMediaType1.Type, parsedMediaType2.Type, StringComparison.OrdinalIgnoreCase)) + { + if (parsedMediaType1.IsAllMediaRange) + { + return 1; + } + else if (parsedMediaType2.IsAllMediaRange) + { + return -1; + } + } + else if (!String.Equals(parsedMediaType1.SubType, parsedMediaType2.SubType, StringComparison.OrdinalIgnoreCase)) + { + if (parsedMediaType1.IsSubTypeMediaRange) + { + return 1; + } + else if (parsedMediaType2.IsSubTypeMediaRange) + { + return -1; + } + } + else + { + if (!parsedMediaType1.HasNonQualityFactorParameter) + { + if (parsedMediaType2.HasNonQualityFactorParameter) + { + return 1; + } + } + else if (!parsedMediaType2.HasNonQualityFactorParameter) + { + return -1; + } + } + } + + return returnValue; + } + + private static int CompareBasedOnQualityFactor(ParsedMediaTypeHeaderValue parsedMediaType1, ParsedMediaTypeHeaderValue parsedMediaType2) + { + double qualityDifference = parsedMediaType1.QualityFactor - parsedMediaType2.QualityFactor; + if (qualityDifference < 0) + { + return 1; + } + else if (qualityDifference > 0) + { + return -1; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs new file mode 100644 index 000000000..a71a21825 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/ParsedMediaTypeHeaderValue.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Net.Http.Headers; + +namespace Microsoft.TestCommon +{ + internal class ParsedMediaTypeHeaderValue + { + private const string MediaRangeAsterisk = "*"; + private const char MediaTypeSubTypeDelimiter = '/'; + private const string QualityFactorParameterName = "q"; + private const double DefaultQualityFactor = 1.0; + + private MediaTypeHeaderValue mediaType; + private string type; + private string subType; + private bool? hasNonQualityFactorParameter; + private double? qualityFactor; + + public ParsedMediaTypeHeaderValue(MediaTypeHeaderValue mediaType) + { + this.mediaType = mediaType; + string[] splitMediaType = mediaType.MediaType.Split(MediaTypeSubTypeDelimiter); + this.type = splitMediaType[0]; + this.subType = splitMediaType[1]; + } + + public string Type + { + get + { + return this.type; + } + } + + public string SubType + { + get + { + return this.subType; + } + } + + public bool IsAllMediaRange + { + get + { + return this.IsSubTypeMediaRange && String.Equals(MediaRangeAsterisk, this.Type, StringComparison.Ordinal); + } + } + + public bool IsSubTypeMediaRange + { + get + { + return String.Equals(MediaRangeAsterisk, this.SubType, StringComparison.Ordinal); + } + } + + public bool HasNonQualityFactorParameter + { + get + { + if (!this.hasNonQualityFactorParameter.HasValue) + { + this.hasNonQualityFactorParameter = false; + foreach (NameValueHeaderValue param in this.mediaType.Parameters) + { + if (!String.Equals(QualityFactorParameterName, param.Name, StringComparison.Ordinal)) + { + this.hasNonQualityFactorParameter = true; + } + } + } + + return this.hasNonQualityFactorParameter.Value; + } + } + + public string CharSet + { + get + { + return this.mediaType.CharSet; + } + } + + public double QualityFactor + { + get + { + if (!this.qualityFactor.HasValue) + { + MediaTypeWithQualityHeaderValue mediaTypeWithQuality = this.mediaType as MediaTypeWithQualityHeaderValue; + if (mediaTypeWithQuality != null) + { + this.qualityFactor = mediaTypeWithQuality.Quality; + } + + if (!this.qualityFactor.HasValue) + { + this.qualityFactor = DefaultQualityFactor; + } + } + + return this.qualityFactor.Value; + } + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs new file mode 100644 index 000000000..36cf366c0 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RegexReplacement.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Text.RegularExpressions; + +namespace Microsoft.TestCommon +{ + public class RegexReplacement + { + Regex regex; + string replacement; + + public RegexReplacement(Regex regex, string replacement) + { + this.regex = regex; + this.replacement = replacement; + } + + public RegexReplacement(string regex, string replacement) + { + this.regex = new Regex(regex); + this.replacement = replacement; + } + + public Regex Regex + { + get + { + return this.regex; + } + } + + public string Replacement + { + get + { + return this.replacement; + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs new file mode 100644 index 000000000..b0cd0eccf --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/RuntimeEnvironment.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.Win32; + +namespace Microsoft.TestCommon +{ + public static class RuntimeEnvironment + { + private const string NetFx40FullSubKey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"; + private const string Version = "Version"; + + static RuntimeEnvironment() + { + object runtimeVersion = Registry.LocalMachine.OpenSubKey(RuntimeEnvironment.NetFx40FullSubKey).GetValue(RuntimeEnvironment.Version); + string versionFor40String = runtimeVersion as string; + if (versionFor40String != null) + { + VersionFor40 = new Version(versionFor40String); + } + } + + private static Version VersionFor40; + + public static bool IsVersion45Installed + { + get + { + return VersionFor40.Major > 4 || (VersionFor40.Major == 4 && VersionFor40.Minor >= 5); + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs new file mode 100644 index 000000000..feaca48ac --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/SerializerAssert.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Xml.Serialization; + +namespace Microsoft.TestCommon +{ + /// + /// MSTest utility for testing code operating against a stream. + /// + public class SerializerAssert + { + private static SerializerAssert singleton = new SerializerAssert(); + + public static SerializerAssert Singleton { get { return singleton; } } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. It cannot be null. + /// The value to serialize. + /// Code to check the contents of the stream. + public void UsingXmlSerializer(Type type, object objectInstance, Action codeThatChecks) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (codeThatChecks == null) + { + throw new ArgumentNullException("codeThatChecks"); + } + + XmlSerializer serializer = new XmlSerializer(type); + + using (MemoryStream stream = new MemoryStream()) + { + serializer.Serialize(stream, objectInstance); + + stream.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + + codeThatChecks(stream); + } + } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. + /// The value to serialize. + /// Code to check the contents of the stream. + public void UsingXmlSerializer(T objectInstance, Action codeThatChecks) + { + UsingXmlSerializer(typeof(T), objectInstance, codeThatChecks); + } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. It cannot be null. + /// The value to serialize. + /// Code to check the contents of the stream. + public void UsingDataContractSerializer(Type type, object objectInstance, Action codeThatChecks) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (codeThatChecks == null) + { + throw new ArgumentNullException("codeThatChecks"); + } + + DataContractSerializer serializer = new DataContractSerializer(type); + + using (MemoryStream stream = new MemoryStream()) + { + serializer.WriteObject(stream, objectInstance); + + stream.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + + codeThatChecks(stream); + } + } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. + /// The value to serialize. + /// Code to check the contents of the stream. + public void UsingDataContractSerializer(T objectInstance, Action codeThatChecks) + { + UsingDataContractSerializer(typeof(T), objectInstance, codeThatChecks); + } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. It cannot be null. + /// The value to serialize. + /// Code to check the contents of the stream. + public static void UsingDataContractJsonSerializer(Type type, object objectInstance, Action codeThatChecks) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (codeThatChecks == null) + { + throw new ArgumentNullException("codeThatChecks"); + } + + DataContractJsonSerializer serializer = new DataContractJsonSerializer(type); + + using (MemoryStream stream = new MemoryStream()) + { + serializer.WriteObject(stream, objectInstance); + + stream.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + + codeThatChecks(stream); + } + } + + /// + /// Creates a , serializes to it using + /// , rewinds the stream and calls . + /// + /// The type to serialize. + /// The value to serialize. + /// Code to check the contents of the stream. + public void UsingDataContractJsonSerializer(T objectInstance, Action codeThatChecks) + { + UsingDataContractJsonSerializer(typeof(T), objectInstance, codeThatChecks); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs new file mode 100644 index 000000000..c0ab0a5aa --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/StreamAssert.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace Microsoft.TestCommon +{ + //// TODO RONCAIN using System.Runtime.Serialization.Json; + + /// + /// MSTest utility for testing code operating against a stream. + /// + public class StreamAssert + { + private static StreamAssert singleton = new StreamAssert(); + + public static StreamAssert Singleton { get { return singleton; } } + + /// + /// Creates a , invokes to write to it, + /// rewinds the stream to the beginning and invokes . + /// + /// Code to write to the stream. It cannot be null. + /// Code that reads from the stream. It cannot be null. + public void WriteAndRead(Action codeThatWrites, Action codeThatReads) + { + if (codeThatWrites == null) + { + throw new ArgumentNullException("codeThatWrites"); + } + + if (codeThatReads == null) + { + throw new ArgumentNullException("codeThatReads"); + } + + using (MemoryStream stream = new MemoryStream()) + { + codeThatWrites(stream); + + stream.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + + codeThatReads(stream); + } + } + + /// + /// Creates a , invokes to write to it, + /// rewinds the stream to the beginning and invokes to obtain + /// the result to return from this method. + /// + /// Code to write to the stream. It cannot be null. + /// Code that reads from the stream and returns the result. It cannot be null. + /// The value returned from . + public static object WriteAndReadResult(Action codeThatWrites, Func codeThatReads) + { + if (codeThatWrites == null) + { + throw new ArgumentNullException("codeThatWrites"); + } + + if (codeThatReads == null) + { + throw new ArgumentNullException("codeThatReads"); + } + + object result = null; + using (MemoryStream stream = new MemoryStream()) + { + codeThatWrites(stream); + + stream.Flush(); + stream.Seek(0L, SeekOrigin.Begin); + + result = codeThatReads(stream); + } + + return result; + } + + /// + /// Creates a , invokes to write to it, + /// rewinds the stream to the beginning and invokes to obtain + /// the result to return from this method. + /// + /// The type of the result expected. + /// Code to write to the stream. It cannot be null. + /// Code that reads from the stream and returns the result. It cannot be null. + /// The value returned from . + public T WriteAndReadResult(Action codeThatWrites, Func codeThatReads) + { + return (T)WriteAndReadResult(codeThatWrites, codeThatReads); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs new file mode 100644 index 000000000..b49ae0ddb --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TaskAssert.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace Microsoft.TestCommon +{ + /// + /// MSTest assert class to make assertions about tests using . + /// + public class TaskAssert + { + private static int timeOutMs = System.Diagnostics.Debugger.IsAttached ? TimeoutConstant.DefaultTimeout : TimeoutConstant.DefaultTimeout * 10; + private static TaskAssert singleton = new TaskAssert(); + + public static TaskAssert Singleton { get { return singleton; } } + + /// + /// Asserts the given task has been started. TAP guidelines are that all + /// objects returned from public API's have been started. + /// + /// The to test. + public void IsStarted(Task task) + { + Assert.NotNull(task); + Assert.True(task.Status != TaskStatus.Created); + } + + /// + /// Asserts the given task completes successfully. This method will block the + /// current thread waiting for the task, but will timeout if it does not complete. + /// + /// The to test. + public void Succeeds(Task task) + { + IsStarted(task); + task.Wait(timeOutMs); + AggregateException aggregateException = task.Exception; + Exception innerException = aggregateException == null ? null : aggregateException.InnerException; + Assert.Null(innerException); + } + + /// + /// Asserts the given task completes successfully and returns a result. + /// Use this overload for a generic whose generic parameter is not known at compile time. + /// This method will block the current thread waiting for the task, but will timeout if it does not complete. + /// + /// The to test. + /// The result from that task. + public object SucceedsWithResult(Task task) + { + Succeeds(task); + Assert.True(task.GetType().IsGenericType); + Type[] genericArguments = task.GetType().GetGenericArguments(); + Assert.Equal(1, genericArguments.Length); + PropertyInfo resultProperty = task.GetType().GetProperty("Result"); + Assert.NotNull(resultProperty); + return resultProperty.GetValue(task, null); + } + + /// + /// Asserts the given task completes successfully and returns a result. + /// This method will block the current thread waiting for the task, but will timeout if it does not complete. + /// + /// The result of the . + /// The to test. + /// The result from that task. + public T SucceedsWithResult(Task task) + { + Succeeds(task); + return task.Result; + } + + /// + /// Asserts the given completes successfully and yields + /// the expected result. + /// + /// The to test. + /// The expected result. + public void ResultEquals(Task task, object expectedObj) + { + object actualObj = SucceedsWithResult(task); + Assert.Equal(expectedObj, actualObj); + } + + /// + /// Asserts the given completes successfully and yields + /// the expected result. + /// + /// The type the task will return. + /// The task to test. + /// The expected result. + public void ResultEquals(Task task, T expectedObj) + { + T actualObj = SucceedsWithResult(task); + Assert.Equal(expectedObj, actualObj); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs new file mode 100644 index 000000000..1fa8b8c74 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TestDataSetAttribute.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.TestCommon +{ + public class TestDataSetAttribute : DataAttribute + { + public Type DeclaringType { get; set; } + + public string PropertyName { get; set; } + + public TestDataVariations TestDataVariations { get; set; } + + private IEnumerable> ExtraDataSets { get; set; } + + public TestDataSetAttribute(Type declaringType, string propertyName, TestDataVariations testDataVariations = TestCommon.TestDataVariations.All) + { + DeclaringType = declaringType; + PropertyName = propertyName; + TestDataVariations = testDataVariations; + ExtraDataSets = new List>(); + } + + public TestDataSetAttribute(Type declaringType, string propertyName, + Type declaringType1, string propertyName1, + TestDataVariations testDataVariations = TestCommon.TestDataVariations.All) + : this(declaringType, propertyName, testDataVariations) + { + ExtraDataSets = new List> { Tuple.Create(declaringType1, propertyName1) }; + } + + public TestDataSetAttribute(Type declaringType, string propertyName, + Type declaringType1, string propertyName1, + Type declaringType2, string propertyName2, + TestDataVariations testDataVariations = TestCommon.TestDataVariations.All) + : this(declaringType, propertyName, testDataVariations) + { + ExtraDataSets = new List> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2) }; + } + + public TestDataSetAttribute(Type declaringType, string propertyName, + Type declaringType1, string propertyName1, + Type declaringType2, string propertyName2, + Type declaringType3, string propertyName3, + TestDataVariations testDataVariations = TestCommon.TestDataVariations.All) + : this(declaringType, propertyName, testDataVariations) + { + ExtraDataSets = new List> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2), Tuple.Create(declaringType3, propertyName3) }; + } + + public TestDataSetAttribute(Type declaringType, string propertyName, + Type declaringType1, string propertyName1, + Type declaringType2, string propertyName2, + Type declaringType3, string propertyName3, + Type declaringType4, string propertyName4, + TestDataVariations testDataVariations = TestCommon.TestDataVariations.All) + : this(declaringType, propertyName, testDataVariations) + { + ExtraDataSets = new List> + { + Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2), + Tuple.Create(declaringType3, propertyName3), Tuple.Create(declaringType4, propertyName4) + }; + } + + public override IEnumerable GetData(MethodInfo methodUnderTest, Type[] parameterTypes) + { + IEnumerable baseDataSet = GetBaseDataSet(DeclaringType, PropertyName, TestDataVariations); + IEnumerable> extraDataSets = GetExtraDataSets(); + + IEnumerable> finalDataSets = (new[] { baseDataSet }).Concat(extraDataSets); + + var datasets = CrossProduct(finalDataSets); + + return datasets; + } + + private static IEnumerable CrossProduct(IEnumerable> datasets) + { + if (datasets.Count() == 1) + { + foreach (var dataset in datasets.First()) + { + yield return dataset; + } + } + else + { + IEnumerable datasetLeft = datasets.First(); + IEnumerable datasetRight = CrossProduct(datasets.Skip(1)); + + foreach (var dataLeft in datasetLeft) + { + foreach (var dataRight in datasetRight) + { + yield return dataLeft.Concat(dataRight).ToArray(); + } + } + } + } + + // The base data set(first one) can either be a TestDataSet or a TestDataSetCollection + private static IEnumerable GetBaseDataSet(Type declaringType, string propertyName, TestDataVariations variations) + { + return TryGetDataSetFromTestDataCollection(declaringType, propertyName, variations) ?? GetDataSet(declaringType, propertyName); + } + + private IEnumerable> GetExtraDataSets() + { + foreach (var tuple in ExtraDataSets) + { + yield return GetDataSet(tuple.Item1, tuple.Item2); + } + } + + private static object GetTestDataPropertyValue(Type declaringType, string propertyName) + { + PropertyInfo property = declaringType.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public); + + if (property == null) + { + throw new ArgumentException(String.Format("Could not find public static property {0} on {1}", propertyName, declaringType.FullName)); + } + else + { + return property.GetValue(null, null); + } + } + + private static IEnumerable GetDataSet(Type declaringType, string propertyName) + { + object propertyValue = GetTestDataPropertyValue(declaringType, propertyName); + + // box the dataset items if the property is not a RefTypeTestData + IEnumerable value = (propertyValue as IEnumerable) ?? (propertyValue as IEnumerable).Cast(); + if (value == null) + { + throw new InvalidOperationException(String.Format("{0}.{1} is either null or does not implement IEnumerable", declaringType.FullName, propertyName)); + } + + IEnumerable dataset = value as IEnumerable; + if (dataset != null) + { + return dataset; + } + else + { + return value.Select((data) => new object[] { data }); + } + } + + private static IEnumerable TryGetDataSetFromTestDataCollection(Type declaringType, string propertyName, TestDataVariations variations) + { + object propertyValue = GetTestDataPropertyValue(declaringType, propertyName); + + IEnumerable testDataCollection = propertyValue as IEnumerable; + + return testDataCollection == null ? null : GetDataSetFromTestDataCollection(testDataCollection, variations); + } + + private static IEnumerable GetDataSetFromTestDataCollection(IEnumerable testDataCollection, TestDataVariations variations) + { + foreach (TestData testdataInstance in testDataCollection) + { + foreach (TestDataVariations variation in testdataInstance.GetSupportedTestDataVariations()) + { + if ((variation & variations) == variation) + { + Type variationType = testdataInstance.GetAsTypeOrNull(variation); + object testData = testdataInstance.GetAsTestDataOrNull(variation); + if (AsSingleInstances(variation)) + { + foreach (object obj in (IEnumerable)testData) + { + yield return new object[] { variationType, obj }; + } + } + else + { + yield return new object[] { variationType, testData }; + } + } + } + } + } + + private static bool AsSingleInstances(TestDataVariations variation) + { + return variation == TestDataVariations.AsInstance || + variation == TestDataVariations.AsNullable || + variation == TestDataVariations.AsDerivedType || + variation == TestDataVariations.AsKnownType || + variation == TestDataVariations.AsDataMember || + variation == TestDataVariations.AsClassMember || + variation == TestDataVariations.AsXmlElementProperty; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs new file mode 100644 index 000000000..3064827cf --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TimeoutConstant.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon +{ + /// + /// MSTest timeout constants for use with the . + /// + public class TimeoutConstant + { + private const int seconds = 1000; + + /// + /// The default timeout for test methods. + /// + public const int DefaultTimeout = 30 * seconds; + + /// + /// An extended timeout for longer running test methods. + /// + public const int ExtendedTimeout = 240 * seconds; + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs new file mode 100644 index 000000000..9eae1d3d9 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/TypeAssert.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + /// + /// MSTest utility for testing that a given type has the expected properties such as being public, sealed, etc. + /// + public class TypeAssert + { + /// + /// Specifies a set of type properties to test for using the method. + /// This enumeration has a attribute that allows a bitwise combination of its member values. + /// + [Flags] + public enum TypeProperties + { + /// + /// Indicates that the type must be abstract. + /// + IsAbstract = 0x1, + + /// + /// Indicates that the type must be a class. + /// + IsClass = 0x2, + + /// + /// Indicates that the type must be a COM object. + /// + IsComObject = 0x4, + + /// + /// Indicates that the type must be disposable. + /// + IsDisposable = 0x8, + + /// + /// Indicates that the type must be an enum. + /// + IsEnum = 0x10, + + /// + /// Indicates that the type must be a generic type. + /// + IsGenericType = 0x20, + + /// + /// Indicates that the type must be a generic type definition. + /// + IsGenericTypeDefinition = 0x40, + + /// + /// Indicates that the type must be an interface. + /// + IsInterface = 0x80, + + /// + /// Indicates that the type must be nested and declared private. + /// + IsNestedPrivate = 0x100, + + /// + /// Indicates that the type must be nested and declared public. + /// + IsNestedPublic = 0x200, + + /// + /// Indicates that the type must be public. + /// + IsPublic = 0x400, + + /// + /// Indicates that the type must be sealed. + /// + IsSealed = 0x800, + + /// + /// Indicates that the type must be visible outside the assembly. + /// + IsVisible = 0x1000, + + /// + /// Indicates that the type must be static. + /// + IsStatic = TypeAssert.TypeProperties.IsAbstract | TypeAssert.TypeProperties.IsSealed, + + /// + /// Indicates that the type must be a public, visible class. + /// + IsPublicVisibleClass = TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsPublic | TypeAssert.TypeProperties.IsVisible + } + + private static void CheckProperty(Type type, bool expected, bool actual, string property) + { + Assert.NotNull(type); + Assert.True(expected == actual, String.Format("Type '{0}' should{1} be {2}.", type.FullName, expected ? "" : " NOT", property)); + } + + /// + /// Determines whether the specified type has a given set of properties such as being public, sealed, etc. + /// The method asserts if one or more of the properties are not satisfied. + /// + /// The type to test for properties. + /// The set of type properties to test for. + public void HasProperties(TypeProperties typeProperties) + { + HasProperties(typeof(T), typeProperties); + } + + /// + /// Determines whether the specified type has a given set of properties such as being public, sealed, etc. + /// The method asserts if one or more of the properties are not satisfied. + /// + /// The type to test for properties. + /// Verify that the type to test is assignable from this type. + /// The set of type properties to test for. + public void HasProperties(TypeProperties typeProperties) + { + HasProperties(typeof(T), typeProperties, typeof(TIsAssignableFrom)); + } + + /// + /// Determines whether the specified type has a given set of properties such as being public, sealed, etc. + /// The method asserts if one or more of the properties are not satisfied. + /// + /// The type to test for properties. + /// The set of type properties to test for. + public void HasProperties(Type type, TypeProperties typeProperties) + { + HasProperties(type, typeProperties, null); + } + + /// + /// Determines whether the specified type has a given set of properties such as being public, sealed, etc. + /// The method asserts if one or more of the properties are not satisfied. + /// + /// The type to test for properties. + /// The set of type properties to test for. + /// Verify that the type to test is assignable from this type. + public void HasProperties(Type type, TypeProperties typeProperties, Type isAssignableFrom) + { + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsAbstract) > 0, type.IsAbstract, "abstract"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsClass) > 0, type.IsClass, "a class"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsComObject) > 0, type.IsCOMObject, "a COM object"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsDisposable) > 0, typeof(IDisposable).IsAssignableFrom(type), "disposable"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsEnum) > 0, type.IsEnum, "an enum"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericType) > 0, type.IsGenericType, "a generic type"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericTypeDefinition) > 0, type.IsGenericTypeDefinition, "a generic type definition"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsInterface) > 0, type.IsInterface, "an interface"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPrivate) > 0, type.IsNestedPrivate, "nested private"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPublic) > 0, type.IsNestedPublic, "nested public"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsPublic) > 0, type.IsPublic, "public"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsSealed) > 0, type.IsSealed, "sealed"); + TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsVisible) > 0, type.IsVisible, "visible"); + if (isAssignableFrom != null) + { + TypeAssert.CheckProperty(type, true, isAssignableFrom.IsAssignableFrom(type), String.Format("assignable from {0}", isAssignableFrom.FullName)); + } + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ByteEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ByteEnum.cs new file mode 100644 index 000000000..c1d4c7b89 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ByteEnum.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum ByteEnum : byte + { + FirstByte, + SecondByte, + ThirdByte + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs new file mode 100644 index 000000000..d1b48a2fd --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/FlagsEnum.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon.Types +{ + [Flags] + public enum FlagsEnum + { + One = 0x1, + Two = 0x2, + Four = 0x4 + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs new file mode 100644 index 000000000..53d58b677 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/INameAndIdContainer.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + /// + /// Tagging interface to assist comparing instances of these types. + /// + public interface INameAndIdContainer + { + string Name { get; set; } + + int Id { get; set; } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs new file mode 100644 index 000000000..7fd7956b1 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ISerializableType.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.TestCommon.Types +{ + [Serializable] + public class ISerializableType : ISerializable, INameAndIdContainer + { + private int id; + private string name; + + public ISerializableType() + { + } + + public ISerializableType(int id, string name) + { + this.id = id; + this.name = name; + } + + public ISerializableType(SerializationInfo information, StreamingContext context) + { + this.id = information.GetInt32("Id"); + this.name = information.GetString("Name"); + } + + public int Id + { + get + { + return this.id; + } + + set + { + this.IdSet = true; + this.id = value; + } + } + + public string Name + { + get + { + return this.name; + } + + set + { + this.NameSet = true; + this.name = value; + } + + } + + public bool IdSet { get; private set; } + + public bool NameSet { get; private set; } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Id", this.Id); + info.AddValue("Name", this.Name); + } + + public static IEnumerable GetTestData() + { + return new ISerializableType[] { new ISerializableType(), new ISerializableType(1, "SomeName") }; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs new file mode 100644 index 000000000..d27b40ac9 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/LongEnum.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum LongEnum : long + { + FirstLong, + SecondLong, + ThirdLong, + FourthLong + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SByteEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SByteEnum.cs new file mode 100644 index 000000000..1afb32c1c --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SByteEnum.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum SByteEnum : sbyte + { + FirstSByte, + SecondSByte, + ThirdSByte + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ShortEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ShortEnum.cs new file mode 100644 index 000000000..d74576a73 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/ShortEnum.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum ShortEnum : short + { + FirstShort, + SecondShort, + ThirdShort + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs new file mode 100644 index 000000000..b740c8f20 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/SimpleEnum.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum SimpleEnum + { + First, + Second, + Third, + Fourth + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UIntEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UIntEnum.cs new file mode 100644 index 000000000..8804f2309 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UIntEnum.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum UIntEnum : uint + { + FirstUInt, + SecondUInt, + ThirdUInt + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UShortEnum.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UShortEnum.cs new file mode 100644 index 000000000..53be0b9e2 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/Types/UShortEnum.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.TestCommon.Types +{ + public enum UShortEnum : ushort + { + FirstUShort, + SecondUShort, + ThirdUShort + } +} diff --git a/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs new file mode 100644 index 000000000..4608150b7 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Microsoft/TestCommon/XmlAssert.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Xml.Linq; + +namespace Microsoft.TestCommon +{ + /// + /// Assert class that compares two XML strings for equality. Namespaces are ignored during comparison + /// + public class XmlAssert + { + public void Equal(string expected, string actual, params RegexReplacement[] regexReplacements) + { + if (regexReplacements != null) + { + for (int i = 0; i < regexReplacements.Length; i++) + { + actual = regexReplacements[i].Regex.Replace(actual, regexReplacements[i].Replacement); + } + } + + Equal(XElement.Parse(expected), XElement.Parse(actual)); + } + + public void Equal(XElement expected, XElement actual) + { + Assert.Equal(Normalize(expected).ToString(), Normalize(actual).ToString()); + } + + private static XElement Normalize(XElement element) + { + if (element.HasElements) + { + return new XElement( + Encode(element.Name), + Normalize(element.Attributes()), + Normalize(element.Elements())); + } + + if (element.IsEmpty) + { + return new XElement( + Encode(element.Name), + Normalize(element.Attributes())); + } + else + { + return new XElement( + Encode(element.Name), + Normalize(element.Attributes()), + element.Value); + } + } + + private static IEnumerable Normalize(IEnumerable attributes) + { + return attributes + .Where((attrib) => !attrib.IsNamespaceDeclaration) + .Select((attrib) => new XAttribute(Encode(attrib.Name), attrib.Value)) + .OrderBy(a => a.Name.ToString()); + } + + private static IEnumerable Normalize(IEnumerable elements) + { + return elements + .Select(e => Normalize(e)) + .OrderBy(a => a.ToString()); + } + + private static string Encode(XName name) + { + return string.Format("{0}_{1}", HttpUtility.UrlEncode(name.NamespaceName).Replace('%', '_'), name.LocalName); + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/Platform.cs b/OData/test/Microsoft.TestCommon/Platform.cs new file mode 100644 index 000000000..8ec9d6694 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/Platform.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + /// + /// An enumeration of known platforms that the unit test might be running under. + /// + [Flags] + public enum Platform + { + /// + /// A special value used to indicate that the test is valid on all known platforms. + /// + All = 0xFFFFFF, + + /// + /// Indicates that the test wants to run on .NET 4 (when used with + /// and/or ), + /// or that the current platform that the test is running on is .NET 4 (when used with the + /// , , and/or + /// ). + /// + Net40 = 0x01, + + /// + /// Indicates that the test wants to run on .NET 4.5 (when used with + /// and/or ), + /// or that the current platform that the test is running on is .NET 4.5 (when used with the + /// , , and/or + /// ). + /// + Net45 = 0x02, + } +} diff --git a/OData/test/Microsoft.TestCommon/PlatformInfo.cs b/OData/test/Microsoft.TestCommon/PlatformInfo.cs new file mode 100644 index 000000000..f65809d16 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/PlatformInfo.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + /// + /// Used to retrieve the currently running platform. + /// + public static class PlatformInfo + { + private const string _net45TypeName = "System.IWellKnownStringEqualityComparer, mscorlib, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089"; + private static Lazy _platform = new Lazy(GetPlatform, isThreadSafe: true); + + /// + /// Gets the platform that the unit test is currently running on. + /// + public static Platform Platform + { + get { return _platform.Value; } + } + + private static Platform GetPlatform() + { + if (Type.GetType(_net45TypeName, throwOnError: false) != null) + { + return Platform.Net45; + } + + return Platform.Net40; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/PortReserver.cs b/OData/test/Microsoft.TestCommon/PortReserver.cs new file mode 100644 index 000000000..3cc9968af --- /dev/null +++ b/OData/test/Microsoft.TestCommon/PortReserver.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Threading; + +namespace Microsoft.TestCommon +{ + /// + /// This class allocates ports while ensuring that: + /// 1. Ports that are permanentaly taken (or taken for the duration of the test) are not being attempted to be used. + /// 2. Ports are not shared across different tests (but you can allocate two different ports in the same test). + /// + /// Gotcha: If another application grabs a port during the test, we have a race condition. + /// + [DebuggerDisplay("Port: {PortNumber}, Port count for this app domain: {_appDomainOwnedPorts.Count}")] + public class PortReserver : IDisposable + { + private Mutex _portMutex; + + // We use this list to hold on to all the ports used because the Mutex will be blown through on the same thread. + // Theoretically we can do a thread local hashset, but that makes dispose thread dependant, or requires more complicated concurrency checks. + // Since practically there is no perf issue or concern here, this keeps the code the simplest possible. + private static HashSet _appDomainOwnedPorts = new HashSet(); + + public int PortNumber + { + get; + private set; + } + + public PortReserver(int basePort = 50231) + { + if (basePort <= 0) + { + throw new InvalidOperationException(); + } + + // Grab a cross appdomain/cross process/cross thread lock, to ensure only one port is reserved at a time. + using (Mutex mutex = GetGlobalMutex()) + { + try + { + int port = basePort - 1; + + while (true) + { + port++; + + if (port > 65535) + { + throw new InvalidOperationException("Exceeded port range"); + } + + // AppDomainOwnedPorts check enables reserving two ports from the same thread in sequence. + // ListUsedTCPPort prevents port contention with other apps. + if (_appDomainOwnedPorts.Contains(port) || + ListUsedTCPPort().Any(endPoint => endPoint.Port == port)) + { + continue; + } + + string mutexName = "WebStack-Port-" + port.ToString(CultureInfo.InvariantCulture); // Create a well known mutex + _portMutex = new Mutex(initiallyOwned: false, name: mutexName); + + // If no one else is using this port grab it. + if (_portMutex.WaitOne(millisecondsTimeout: 0)) + { + break; + } + + // dispose this mutex since the port it represents is not available. + _portMutex.Dispose(); + _portMutex = null; + } + + PortNumber = port; + _appDomainOwnedPorts.Add(port); + } + finally + { + mutex.ReleaseMutex(); + } + } + } + + public string BaseUri + { + get + { + return String.Format(CultureInfo.InvariantCulture, "http://localhost:{0}/", PortNumber); + } + } + + public void Dispose() + { + if (PortNumber == -1) + { + // Object already disposed + return; + } + + using (Mutex mutex = GetGlobalMutex()) + { + _portMutex.Dispose(); + _appDomainOwnedPorts.Remove(PortNumber); + PortNumber = -1; + } + } + + private static Mutex GetGlobalMutex() + { + Mutex mutex = new Mutex(initiallyOwned: false, name: "WebStack-RandomPortAcquisition"); + if (!mutex.WaitOne(30000)) + { + throw new InvalidOperationException(); + } + + return mutex; + } + + private static IPEndPoint[] ListUsedTCPPort() + { + var usedPort = new HashSet(); + IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + return ipGlobalProperties.GetActiveTcpListeners(); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/PreAppStartTestHelper.cs b/OData/test/Microsoft.TestCommon/PreAppStartTestHelper.cs new file mode 100644 index 000000000..47638d36f --- /dev/null +++ b/OData/test/Microsoft.TestCommon/PreAppStartTestHelper.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.ComponentModel; +using System.Reflection; +using Microsoft.TestCommon; + +namespace System.Web.WebPages.TestUtils +{ + public static class PreAppStartTestHelper + { + public static void TestPreAppStartClass(Type preAppStartType) + { + string typeMessage = String.Format("The type '{0}' must be static, public, and named 'PreApplicationStartCode'.", preAppStartType.FullName); + Assert.True(preAppStartType.IsSealed && preAppStartType.IsAbstract && preAppStartType.IsPublic && preAppStartType.Name == "PreApplicationStartCode", typeMessage); + + string editorBrowsableMessage = String.Format("The only attribute on type '{0}' must be [EditorBrowsable(EditorBrowsableState.Never)].", preAppStartType.FullName); + object[] attrs = preAppStartType.GetCustomAttributes(typeof(EditorBrowsableAttribute), true); + Assert.True(attrs.Length == 1 && ((EditorBrowsableAttribute)attrs[0]).State == EditorBrowsableState.Never, editorBrowsableMessage); + + string startMethodMessage = String.Format("The only public member on type '{0}' must be a method called Start().", preAppStartType.FullName); + MemberInfo[] publicMembers = preAppStartType.GetMembers(BindingFlags.Public | BindingFlags.Static); + Assert.True(publicMembers.Length == 1, startMethodMessage); + Assert.True(publicMembers[0].MemberType == MemberTypes.Method, startMethodMessage); + Assert.True(publicMembers[0].Name == "Start", startMethodMessage); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs b/OData/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs new file mode 100644 index 000000000..1c1dd1d85 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/PreserveSyncContextAttribute.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading; + +namespace Microsoft.TestCommon +{ + /// + /// Preserves the current . Use this attribute on + /// tests which modify the current . + /// + public class PreserveSyncContextAttribute : Xunit.BeforeAfterTestAttribute + { + private SynchronizationContext _syncContext; + + public override void Before(System.Reflection.MethodInfo methodUnderTest) + { + _syncContext = SynchronizationContext.Current; + } + + public override void After(System.Reflection.MethodInfo methodUnderTest) + { + SynchronizationContext.SetSynchronizationContext(_syncContext); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/PropertyDataAttribute.cs b/OData/test/Microsoft.TestCommon/PropertyDataAttribute.cs new file mode 100644 index 000000000..af1b5aa6d --- /dev/null +++ b/OData/test/Microsoft.TestCommon/PropertyDataAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class PropertyDataAttribute : Xunit.Extensions.PropertyDataAttribute + { + public PropertyDataAttribute(string propertyName) + : base(propertyName) + { + } + } +} diff --git a/OData/test/Microsoft.TestCommon/ReflectionAssert.cs b/OData/test/Microsoft.TestCommon/ReflectionAssert.cs new file mode 100644 index 000000000..74d0766a4 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/ReflectionAssert.cs @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.TestCommon +{ + public class ReflectionAssert + { + private static PropertyInfo GetPropertyInfo(Expression> property) + { + if (property.Body is MemberExpression) + { + return (PropertyInfo)((MemberExpression)property.Body).Member; + } + else if (property.Body is UnaryExpression && property.Body.NodeType == ExpressionType.Convert) + { + return (PropertyInfo)((MemberExpression)((UnaryExpression)property.Body).Operand).Member; + } + else + { + throw new InvalidOperationException("Could not determine property from lambda expression."); + } + } + + private static void TestPropertyValue(TInstance instance, Func getFunc, Action setFunc, TValue valueToSet, TValue valueToCheck) + { + setFunc(instance, valueToSet); + TValue newValue = getFunc(instance); + Assert.Equal(valueToCheck, newValue); + } + + private static void TestPropertyValue(TInstance instance, Func getFunc, Action setFunc, TValue value) + { + TestPropertyValue(instance, getFunc, setFunc, value, value); + } + + public void Property(T instance, Expression> propertyGetter, TResult expectedDefaultValue, bool allowNull = false, TResult roundTripTestValue = null) where TResult : class + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (TResult)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + if (allowNull) + { + TestPropertyValue(instance, getFunc, setFunc, null); + } + else + { + Assert.ThrowsArgumentNull(() => + { + setFunc(instance, null); + }, "value"); + } + + if (roundTripTestValue != null) + { + TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); + } + } + + public void IntegerProperty(T instance, Expression> propertyGetter, TResult expectedDefaultValue, + TResult? minLegalValue, TResult? illegalLowerValue, + TResult? maxLegalValue, TResult? illegalUpperValue, + TResult roundTripTestValue) where TResult : struct + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (TResult)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + if (minLegalValue.HasValue) + { + TestPropertyValue(instance, getFunc, setFunc, minLegalValue.Value); + } + + if (maxLegalValue.HasValue) + { + TestPropertyValue(instance, getFunc, setFunc, maxLegalValue.Value); + } + + if (illegalLowerValue.HasValue) + { + Assert.ThrowsArgumentGreaterThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", minLegalValue.Value.ToString(), illegalLowerValue.Value); + } + + if (illegalUpperValue.HasValue) + { + Assert.ThrowsArgumentLessThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", maxLegalValue.Value.ToString(), illegalUpperValue.Value); + } + + TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); + } + + public void NullableIntegerProperty(T instance, Expression> propertyGetter, TResult? expectedDefaultValue, + TResult? minLegalValue, TResult? illegalLowerValue, + TResult? maxLegalValue, TResult? illegalUpperValue, + TResult roundTripTestValue) where TResult : struct + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (TResult?)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + TestPropertyValue(instance, getFunc, setFunc, null); + + if (minLegalValue.HasValue) + { + TestPropertyValue(instance, getFunc, setFunc, minLegalValue.Value); + } + + if (maxLegalValue.HasValue) + { + TestPropertyValue(instance, getFunc, setFunc, maxLegalValue.Value); + } + + if (illegalLowerValue.HasValue) + { + Assert.ThrowsArgumentGreaterThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", minLegalValue.Value.ToString(), illegalLowerValue.Value); + } + + if (illegalUpperValue.HasValue) + { + Assert.ThrowsArgumentLessThanOrEqualTo(() => { setFunc(instance, illegalLowerValue.Value); }, "value", maxLegalValue.Value.ToString(), illegalUpperValue.Value); + } + + TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); + } + + public void BooleanProperty(T instance, Expression> propertyGetter, bool expectedDefaultValue) + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (bool)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + TestPropertyValue(instance, getFunc, setFunc, !expectedDefaultValue); + } + + public void EnumProperty(T instance, Expression> propertyGetter, TResult expectedDefaultValue, TResult illegalValue, TResult roundTripTestValue) where TResult : struct + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (TResult)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + Assert.ThrowsInvalidEnumArgument(() => { setFunc(instance, illegalValue); }, "value", Convert.ToInt32(illegalValue), typeof(TResult)); + + TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); + } + + public void EnumPropertyWithoutIllegalValueCheck(T instance, Expression> propertyGetter, TResult expectedDefaultValue, TResult roundTripTestValue) where TResult : struct + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (TResult)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + TestPropertyValue(instance, getFunc, setFunc, roundTripTestValue); + } + + public void StringProperty(T instance, Expression> propertyGetter, string expectedDefaultValue, + bool allowNullAndEmpty = true, bool treatNullAsEmpty = true) + { + PropertyInfo property = GetPropertyInfo(propertyGetter); + Func getFunc = (obj) => (string)property.GetValue(obj, index: null); + Action setFunc = (obj, value) => property.SetValue(obj, value, index: null); + + Assert.Equal(expectedDefaultValue, getFunc(instance)); + + if (allowNullAndEmpty) + { + // Assert get/set works for null + TestPropertyValue(instance, getFunc, setFunc, null, treatNullAsEmpty ? String.Empty : null); + + // Assert get/set works for String.Empty + TestPropertyValue(instance, getFunc, setFunc, String.Empty, String.Empty); + } + else + { + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + TestPropertyValue(instance, getFunc, setFunc, null); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + "value"); + Assert.ThrowsArgumentNullOrEmpty( + delegate() + { + try + { + TestPropertyValue(instance, getFunc, setFunc, String.Empty); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }, + "value"); + } + + // Assert get/set works for arbitrary value + TestPropertyValue(instance, getFunc, setFunc, "TestValue"); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/ReplaceCultureAttribute.cs b/OData/test/Microsoft.TestCommon/ReplaceCultureAttribute.cs new file mode 100644 index 000000000..c14976360 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/ReplaceCultureAttribute.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Reflection; +using System.Threading; + +namespace Microsoft.TestCommon +{ + /// + /// Replaces the current culture and UI culture for the test. + /// + [AttributeUsage(AttributeTargets.Method)] + public class ReplaceCultureAttribute : Xunit.BeforeAfterTestAttribute + { + private CultureInfo _originalCulture; + private CultureInfo _originalUICulture; + + public ReplaceCultureAttribute() + { + Culture = CultureReplacer.DefaultCultureName; + UICulture = CultureReplacer.DefaultUICultureName; + } + + /// + /// Sets for the test. Defaults to en-GB. + /// + /// + /// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We + /// want to be able to find bugs where we're accidentally relying on the Invariant instead of the + /// user's culture. + /// + public string Culture { get; set; } + + /// + /// Sets for the test. Defaults to en-US. + /// + public string UICulture { get; set; } + + public override void Before(MethodInfo methodUnderTest) + { + _originalCulture = Thread.CurrentThread.CurrentCulture; + _originalUICulture = Thread.CurrentThread.CurrentUICulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(Culture); + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(UICulture); + } + + public override void After(MethodInfo methodUnderTest) + { + Thread.CurrentThread.CurrentCulture = _originalCulture; + Thread.CurrentThread.CurrentUICulture = _originalUICulture; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/RestoreThreadPrincipalAttribute.cs b/OData/test/Microsoft.TestCommon/RestoreThreadPrincipalAttribute.cs new file mode 100644 index 000000000..1f08dc6fb --- /dev/null +++ b/OData/test/Microsoft.TestCommon/RestoreThreadPrincipalAttribute.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Security.Principal; +using System.Threading; + +namespace Microsoft.TestCommon +{ + public class RestoreThreadPrincipalAttribute : Xunit.BeforeAfterTestAttribute + { + private IPrincipal _originalPrincipal; + + public override void Before(MethodInfo methodUnderTest) + { + _originalPrincipal = Thread.CurrentPrincipal; + + AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.NoPrincipal); + + Thread.CurrentPrincipal = null; + } + + public override void After(MethodInfo methodUnderTest) + { + Thread.CurrentPrincipal = _originalPrincipal; + + AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.UnauthenticatedPrincipal); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/StringAssertException.cs b/OData/test/Microsoft.TestCommon/StringAssertException.cs new file mode 100644 index 000000000..441fc6fc6 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/StringAssertException.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Xunit.Sdk; + +namespace Microsoft.TestCommon +{ + // An early copy of the new string assert exception from xUnit.net 2.0 (temporarily, until it RTMs) + + [Serializable] + internal class StringEqualException : AssertException + { + private static Dictionary _encodings = new Dictionary { { '\r', "\\r" }, { '\n', "\\n" }, { '\t', "\\t" }, { '\0', "\\0" } }; + private string _message; + + public StringEqualException(string expected, string actual, int expectedIndex, int actualIndex) + : base("Assert.Equal() failure") + { + Actual = actual; + ActualIndex = actualIndex; + Expected = expected; + ExpectedIndex = expectedIndex; + } + + public string Actual { get; private set; } + + public int ActualIndex { get; private set; } + + public string Expected { get; private set; } + + public int ExpectedIndex { get; private set; } + + public override string Message + { + get + { + if (_message == null) + _message = CreateMessage(); + + return _message; + } + } + + private string CreateMessage() + { + Tuple printedExpected = ShortenAndEncode(Expected, ExpectedIndex, '↓'); + Tuple printedActual = ShortenAndEncode(Actual, ActualIndex, '↑'); + + return String.Format( + CultureInfo.CurrentCulture, + "{1}{0} {2}{0}Expected: {3}{0}Actual: {4}{0} {5}", + Environment.NewLine, + base.Message, + printedExpected.Item2, + printedExpected.Item1, + printedActual.Item1, + printedActual.Item2 + ); + } + + private Tuple ShortenAndEncode(string value, int position, char pointer) + { + int start = Math.Max(position - 20, 0); + int end = Math.Min(position + 41, value.Length); + StringBuilder printedValue = new StringBuilder(100); + StringBuilder printedPointer = new StringBuilder(100); + + if (start > 0) + { + printedValue.Append("···"); + printedPointer.Append(" "); + } + + for (int idx = start; idx < end; ++idx) + { + char c = value[idx]; + string encoding; + int paddingLength = 1; + + if (_encodings.TryGetValue(c, out encoding)) + { + printedValue.Append(encoding); + paddingLength = encoding.Length; + } + else + { + printedValue.Append(c); + } + + if (idx < position) + { + printedPointer.Append(' ', paddingLength); + } + else if (idx == position) + { + printedPointer.AppendFormat("{0} (pos {1})", pointer, position); + } + } + + if (end < value.Length) + { + printedValue.Append("···"); + } + + return new Tuple(printedValue.ToString(), printedPointer.ToString()); + } + + protected override bool ExcludeStackFrame(string stackFrame) + { + return base.ExcludeStackFrame(stackFrame) + || stackFrame.StartsWith("at Microsoft.TestCommon.Assert.", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/StringAssertions.cs b/OData/test/Microsoft.TestCommon/StringAssertions.cs new file mode 100644 index 000000000..f84f7f647 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/StringAssertions.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + // An early copy of the new string assert from xUnit.net 2.0 (temporarily, until it RTMs) + + public partial class Assert + { + private const string NullDisplayValue = "(null)"; + + /// + /// Verifies that two strings are equivalent. + /// + /// The expected string value. + /// The actual string value. + /// If set to true, ignores cases differences. The invariant culture is used. + /// If set to true, treats \r\n, \r, and \n as equivalent. + /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent. + /// Thrown when the strings are not equivalent. + public static void Equal(string expected, string actual, bool ignoreCase = false, bool ignoreLineEndingDifferences = false, bool ignoreWhiteSpaceDifferences = false) + { + // Start out assuming the one of the values is null + int expectedIndex = -1; + int actualIndex = -1; + int expectedLength = 0; + int actualLength = 0; + + if (expected == null) + { + if (actual == null) + { + return; + } + + expected = NullDisplayValue; + } + else if (actual == null) + { + actual = NullDisplayValue; + } + else + { + // Walk the string, keeping separate indices since we can skip variable amounts of + // data based on ignoreLineEndingDifferences and ignoreWhiteSpaceDifferences. + expectedIndex = 0; + actualIndex = 0; + expectedLength = expected.Length; + actualLength = actual.Length; + + while (expectedIndex < expectedLength && actualIndex < actualLength) + { + char expectedChar = expected[expectedIndex]; + char actualChar = actual[actualIndex]; + + if (ignoreLineEndingDifferences && IsLineEnding(expectedChar) && IsLineEnding(actualChar)) + { + expectedIndex = SkipLineEnding(expected, expectedIndex); + actualIndex = SkipLineEnding(actual, actualIndex); + } + else if (ignoreWhiteSpaceDifferences && IsWhiteSpace(expectedChar) && IsWhiteSpace(actualChar)) + { + expectedIndex = SkipWhitespace(expected, expectedIndex); + actualIndex = SkipWhitespace(actual, actualIndex); + } + else + { + if (ignoreCase) + { + expectedChar = Char.ToUpperInvariant(expectedChar); + actualChar = Char.ToUpperInvariant(actualChar); + } + + if (expectedChar != actualChar) + { + break; + } + + expectedIndex++; + actualIndex++; + } + } + } + + if (expectedIndex < expectedLength || actualIndex < actualLength) + { + throw new StringEqualException(expected, actual, expectedIndex, actualIndex); + } + } + + private static bool IsLineEnding(char c) + { + return c == '\r' || c == '\n'; + } + + private static bool IsWhiteSpace(char c) + { + return c == ' ' || c == '\t'; + } + + private static int SkipLineEnding(string value, int index) + { + if (value[index] == '\r') + { + ++index; + } + if (index < value.Length && value[index] == '\n') + { + ++index; + } + + return index; + } + + private static int SkipWhitespace(string value, int index) + { + while (index < value.Length) + { + switch (value[index]) + { + case ' ': + case '\t': + index++; + break; + + default: + return index; + } + } + + return index; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/TaskExtensions.cs b/OData/test/Microsoft.TestCommon/TaskExtensions.cs new file mode 100644 index 000000000..eeb441970 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TaskExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.TestCommon; + +// No namespace so that these extensions are available for all test classes + +public static class TaskExtensions +{ + /// + /// Waits until the given task finishes executing and completes in any of the 3 states. + /// + /// A task + public static void WaitUntilCompleted(this Task task) + { + if (task == null) throw new ArgumentNullException("task"); + task.ContinueWith(prev => + { + if (prev.IsFaulted) + { + // Observe the exception in the faulted case to avoid an unobserved exception leaking and + // killing the thread finalizer. + var e = prev.Exception; + } + }, TaskContinuationOptions.ExecuteSynchronously).Wait(); + } + + public static void RethrowFaultedTaskException(this Task task) + { + task.WaitUntilCompleted(); + Assert.Equal(TaskStatus.Faulted, task.Status); + throw task.Exception.GetBaseException(); + } +} diff --git a/OData/test/Microsoft.TestCommon/TestFile.cs b/OData/test/Microsoft.TestCommon/TestFile.cs new file mode 100644 index 000000000..a4b0a5fe9 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TestFile.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.IO; +using System.Reflection; +using Microsoft.TestCommon; + +namespace System.Web.WebPages.TestUtils +{ + public class TestFile + { + public const string ResourceNameFormat = "{0}.TestFiles.{1}"; + + public string ResourceName { get; set; } + public Assembly Assembly { get; set; } + + public TestFile(string resName, Assembly asm) + { + ResourceName = resName; + Assembly = asm; + } + + public static TestFile Create(string localResourceName) + { + return new TestFile(String.Format(ResourceNameFormat, Assembly.GetCallingAssembly().GetName().Name, localResourceName), Assembly.GetCallingAssembly()); + } + + public Stream OpenRead() + { + Stream strm = Assembly.GetManifestResourceStream(ResourceName); + if (strm == null) + { + Assert.True(false, String.Format("Manifest resource: {0} not found", ResourceName)); + } + return strm; + } + + public byte[] ReadAllBytes() + { + using (Stream stream = OpenRead()) + { + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, buffer.Length); + return buffer; + } + } + + public string ReadAllText() + { + using (StreamReader reader = new StreamReader(OpenRead())) + { + // The .Replace() calls normalize line endings, in case you get \n instead of \r\n + // since all the unit tests rely on the assumption that the files will have \r\n endings. + return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n"); + } + } + + /// + /// Saves the file to the specified path. + /// + public void Save(string filePath) + { + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + using (Stream outStream = File.Create(filePath)) + { + using (Stream inStream = OpenRead()) + { + inStream.CopyTo(outStream); + } + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/TestHelper.cs b/OData/test/Microsoft.TestCommon/TestHelper.cs new file mode 100644 index 000000000..afe7c535e --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TestHelper.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Globalization; +using System.Linq; +using Microsoft.TestCommon; + +namespace System.Web.TestUtil +{ + public static class UnitTestHelper + { + public static bool EnglishBuildAndOS + { + get + { + bool englishBuild = String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en", + StringComparison.OrdinalIgnoreCase); + bool englishOS = String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en", + StringComparison.OrdinalIgnoreCase); + return englishBuild && englishOS; + } + } + + public static void AssertEqualsIgnoreWhitespace(string expected, string actual) + { + expected = new String(expected.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + actual = new String(actual.Where(c => !Char.IsWhiteSpace(c)).ToArray()); + + Assert.Equal(expected, actual, StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/TheoryAttribute.cs b/OData/test/Microsoft.TestCommon/TheoryAttribute.cs new file mode 100644 index 000000000..34558a3df --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TheoryAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Xunit.Sdk; + +namespace Microsoft.TestCommon +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class TheoryAttribute : Xunit.Extensions.TheoryAttribute + { + public TheoryAttribute() + { + Timeout = Debugger.IsAttached ? Int32.MaxValue : TimeoutConstant.DefaultTimeout; + Platforms = Platform.All; + PlatformJustification = "Unsupported platform (test runs on {0}, current platform is {1})"; + } + + /// + /// Gets the platform that the unit test is currently running on. + /// + protected Platform Platform + { + get { return PlatformInfo.Platform; } + } + + /// + /// Gets or set the platforms that the unit test is compatible with. Defaults to + /// . + /// + public Platform Platforms { get; set; } + + /// + /// Gets or sets the platform skipping justification. This message can receive + /// the supported platforms as {0}, and the current platform as {1}. + /// + public string PlatformJustification { get; set; } + + /// + protected override IEnumerable EnumerateTestCommands(IMethodInfo method) + { + if ((Platforms & Platform) == 0) + { + return new[] { + new SkipCommand( + method, + DisplayName, + String.Format(PlatformJustification, Platforms.ToString().Replace(", ", " | "), Platform) + ) + }; + } + + return base.EnumerateTestCommands(method); + } + } +} \ No newline at end of file diff --git a/OData/test/Microsoft.TestCommon/TheoryDataSet.cs b/OData/test/Microsoft.TestCommon/TheoryDataSet.cs new file mode 100644 index 000000000..66c3ae2a3 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TheoryDataSet.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.TestCommon +{ + /// + /// Helper class for generating test data for XUnit's -based tests. + /// Should be used in combination with . + /// + /// First parameter type + public class TheoryDataSet : TheoryDataSet + { + public void Add(TParam p) + { + AddItem(p); + } + } + + /// + /// Helper class for generating test data for XUnit's -based tests. + /// Should be used in combination with . + /// + /// First parameter type + /// Second parameter type + public class TheoryDataSet : TheoryDataSet + { + public void Add(TParam1 p1, TParam2 p2) + { + AddItem(p1, p2); + } + } + + /// + /// Helper class for generating test data for XUnit's -based tests. + /// Should be used in combination with . + /// + /// First parameter type + /// Second parameter type + /// Third parameter type + public class TheoryDataSet : TheoryDataSet + { + public void Add(TParam1 p1, TParam2 p2, TParam3 p3) + { + AddItem(p1, p2, p3); + } + } + + /// + /// Helper class for generating test data for XUnit's -based tests. + /// Should be used in combination with . + /// + /// First parameter type + /// Second parameter type + /// Third parameter type + /// Fourth parameter type + public class TheoryDataSet : TheoryDataSet + { + public void Add(TParam1 p1, TParam2 p2, TParam3 p3, TParam4 p4) + { + AddItem(p1, p2, p3, p4); + } + } + + /// + /// Helper class for generating test data for XUnit's -based tests. + /// Should be used in combination with . + /// + /// First parameter type + /// Second parameter type + /// Third parameter type + /// Fourth parameter type + /// Fifth parameter type + public class TheoryDataSet : TheoryDataSet + { + public void Add(TParam1 p1, TParam2 p2, TParam3 p3, TParam4 p4, TParam5 p5) + { + AddItem(p1, p2, p3, p4, p5); + } + } + + /// + /// Base class for TheoryDataSet classes. + /// + public abstract class TheoryDataSet : IEnumerable + { + private readonly List data = new List(); + + protected void AddItem(params object[] values) + { + data.Add(values); + } + + public IEnumerator GetEnumerator() + { + return data.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs b/OData/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs new file mode 100644 index 000000000..900526e4e --- /dev/null +++ b/OData/test/Microsoft.TestCommon/ThreadPoolSyncContext.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading; + +namespace Microsoft.TestCommon +{ + /// + /// This is an implementation of SynchronizationContext that not only queues things on the thread pool for + /// later work, but also ensures that it sets itself back as the synchronization context (something that the + /// default implementatation of SynchronizationContext does not do). + /// + public class ThreadPoolSyncContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object state) + { + ThreadPool.QueueUserWorkItem(_ => + { + SynchronizationContext oldContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(this); + d.Invoke(state); + SynchronizationContext.SetSynchronizationContext(oldContext); + }, null); + } + + public override void Send(SendOrPostCallback d, object state) + { + SynchronizationContext oldContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(this); + d.Invoke(state); + SynchronizationContext.SetSynchronizationContext(oldContext); + } + } +} diff --git a/OData/test/Microsoft.TestCommon/TraitAttribute.cs b/OData/test/Microsoft.TestCommon/TraitAttribute.cs new file mode 100644 index 000000000..19965e62f --- /dev/null +++ b/OData/test/Microsoft.TestCommon/TraitAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.TestCommon +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] + public class TraitAttribute : Xunit.TraitAttribute + { + public TraitAttribute(string name, string value) + : base(name, value) + { + } + } +} diff --git a/OData/test/Microsoft.TestCommon/VersionTestHelper.cs b/OData/test/Microsoft.TestCommon/VersionTestHelper.cs new file mode 100644 index 000000000..2b4257b5b --- /dev/null +++ b/OData/test/Microsoft.TestCommon/VersionTestHelper.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.TestCommon +{ + public static class VersionTestHelper + { + // returns a version for an assembly by using a type from the assembly + // also verifies that type wasn't moved to another assembly. + public static Version GetVersionFromAssembly(string assemblyName, Type typeFromAssembly) + { + Assembly assembly = typeFromAssembly.Assembly; + + Assert.Equal(assemblyName, assembly.GetName().Name); + return assembly.GetName().Version; + } + } +} diff --git a/OData/test/Microsoft.TestCommon/WebUtils.cs b/OData/test/Microsoft.TestCommon/WebUtils.cs new file mode 100644 index 000000000..640dcaa95 --- /dev/null +++ b/OData/test/Microsoft.TestCommon/WebUtils.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.IO; +using System.Reflection; +using System.Web.UI; + +namespace System.Web.WebPages.TestUtils +{ + public static class WebUtils + { + /// + /// Creates an instance of HttpRuntime and assigns it (using magic) to the singleton instance of HttpRuntime. + /// Ensure that the returned value is disposed at the end of the test. + /// + /// Returns an IDisposable that restores the original HttpRuntime. + public static IDisposable CreateHttpRuntime(string appVPath, string appPath = null) + { + var runtime = new HttpRuntime(); + var appDomainAppVPathField = typeof(HttpRuntime).GetField("_appDomainAppVPath", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); + appDomainAppVPathField.SetValue(runtime, CreateVirtualPath(appVPath)); + + if (appPath != null) + { + var appDomainAppPathField = typeof(HttpRuntime).GetField("_appDomainAppPath", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); + appDomainAppPathField.SetValue(runtime, Path.GetFullPath(appPath)); + } + + GetTheRuntime().SetValue(null, runtime); + var appDomainIdField = typeof(HttpRuntime).GetField("_appDomainId", BindingFlags.NonPublic | BindingFlags.Instance); + appDomainIdField.SetValue(runtime, "test"); + + return new DisposableAction(RestoreHttpRuntime); + } + + internal static FieldInfo GetTheRuntime() + { + return typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.NonPublic | BindingFlags.Static); + } + + internal static void RestoreHttpRuntime() + { + GetTheRuntime().SetValue(null, null); + } + + internal static object CreateVirtualPath(string path) + { + var vPath = typeof(Page).Assembly.GetType("System.Web.VirtualPath"); + var method = vPath.GetMethod("CreateNonRelativeTrailingSlash", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + return method.Invoke(null, new object[] { path }); + } + + private class DisposableAction : IDisposable + { + private Action _action; + private bool _hasDisposed; + + public DisposableAction(Action action) + { + if (action == null) + { + throw new ArgumentNullException("action"); + } + _action = action; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + // If we were disposed by the finalizer it's because the user didn't use a "using" block, so don't do anything! + if (disposing) + { + lock (this) + { + if (!_hasDisposed) + { + _hasDisposed = true; + _action(); + } + } + } + } + } + } +} diff --git a/OData/test/Microsoft.TestCommon/packages.config b/OData/test/Microsoft.TestCommon/packages.config new file mode 100644 index 000000000..9d80a7fba --- /dev/null +++ b/OData/test/Microsoft.TestCommon/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/OData/test/Settings.StyleCop b/OData/test/Settings.StyleCop new file mode 100644 index 000000000..8b3833e48 --- /dev/null +++ b/OData/test/Settings.StyleCop @@ -0,0 +1,197 @@ + + + Parent + + + + + + + False + + + + + + + + False + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + \ No newline at end of file diff --git a/OData/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs b/OData/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs new file mode 100644 index 000000000..12e338f52 --- /dev/null +++ b/OData/test/System.Net.Http.Formatting.Test/Formatting/MediaTypeFormatterTestBase.cs @@ -0,0 +1,559 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.TestCommon; +using Moq; + +namespace System.Net.Http.Formatting +{ + /// + /// A test class for common functionality across multiple implementations. + /// + /// The type of formatter under test. + public abstract class MediaTypeFormatterTestBase where TFormatter : MediaTypeFormatter + { + protected MediaTypeFormatterTestBase() + { + } + + // Test data variations of interest in round-trip tests. + public const TestDataVariations RoundTripDataVariations = + TestDataVariations.All | TestDataVariations.WithNull | TestDataVariations.AsClassMember; + + public abstract IEnumerable ExpectedSupportedMediaTypes { get; } + + public abstract IEnumerable ExpectedSupportedEncodings { get; } + + /// + /// Byte representation of an with value 42 using the default encoding + /// for this media type formatter. + /// + public abstract byte[] ExpectedSampleTypeByteRepresentation { get; } + + [Fact] + public void TypeIsCorrect() + { + Assert.Type.HasProperties(TypeAssert.TypeProperties.IsPublicVisibleClass); + } + + [Fact] + public void SupportedMediaTypes_HeaderValuesAreNotSharedBetweenInstances() + { + var formatter1 = CreateFormatter(); + var formatter2 = CreateFormatter(); + + foreach (MediaTypeHeaderValue mediaType1 in formatter1.SupportedMediaTypes) + { + MediaTypeHeaderValue mediaType2 = formatter2.SupportedMediaTypes.Single(m => m.Equals(mediaType1)); + Assert.NotSame(mediaType1, mediaType2); + } + } + + [Fact] + public void SupportEncodings_ValuesAreNotSharedBetweenInstances() + { + var formatter1 = CreateFormatter(); + var formatter2 = CreateFormatter(); + + foreach (Encoding mediaType1 in formatter1.SupportedEncodings) + { + Encoding mediaType2 = formatter2.SupportedEncodings.Single(m => m.Equals(mediaType1)); + Assert.NotSame(mediaType1, mediaType2); + } + } + + [Fact] + public void SupportMediaTypes_DefaultSupportedMediaTypes() + { + TFormatter formatter = CreateFormatter(); + Assert.True(ExpectedSupportedMediaTypes.SequenceEqual(formatter.SupportedMediaTypes)); + } + + [Fact] + public void SupportEncoding_DefaultSupportedEncodings() + { + TFormatter formatter = CreateFormatter(); + Assert.True(ExpectedSupportedEncodings.SequenceEqual(formatter.SupportedEncodings)); + } + + [Fact] + public void ReadFromStreamAsync_ThrowsOnNull() + { + TFormatter formatter = CreateFormatter(); + Assert.ThrowsArgumentNull(() => { formatter.ReadFromStreamAsync(null, Stream.Null, null, null); }, "type"); + Assert.ThrowsArgumentNull(() => { formatter.ReadFromStreamAsync(typeof(object), null, null, null); }, "readStream"); + } + + [Fact] + public Task ReadFromStreamAsync_WhenContentLengthIsZero_DoesNotReadStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + Mock mockStream = new Mock(); + IFormatterLogger mockFormatterLogger = new Mock().Object; + HttpContent content = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = content.Headers; + contentHeaders.ContentLength = 0; + + // Act + return formatter.ReadFromStreamAsync(typeof(SampleType), mockStream.Object, content, mockFormatterLogger) + .ContinueWith( + readTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + mockStream.Verify(s => s.Read(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + mockStream.Verify(s => s.ReadByte(), Times.Never()); + mockStream.Verify(s => s.BeginRead(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + }); + } + + [Fact] + public Task ReadFromStreamAsync_WhenContentLengthIsZero_DoesNotCloseStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + Mock mockStream = new Mock(); + IFormatterLogger mockFormatterLogger = new Mock().Object; + HttpContent content = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = content.Headers; + contentHeaders.ContentLength = 0; + + // Act + return formatter.ReadFromStreamAsync(typeof(SampleType), mockStream.Object, content, mockFormatterLogger) + .ContinueWith( + readTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + mockStream.Verify(s => s.Close(), Times.Never()); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(0)] + [InlineData("")] + public void ReadFromStreamAsync_WhenContentLengthIsZero_ReturnsDefaultTypeValue(T value) + { + // Arrange + TFormatter formatter = CreateFormatter(); + HttpContent content = new StringContent(""); + + // Act + var result = formatter.ReadFromStreamAsync(typeof(T), content.ReadAsStreamAsync().Result, + content, null); + result.WaitUntilCompleted(); + + // Assert + Assert.Equal(default(T), (T)result.Result); + } + + [Fact] + public Task ReadFromStreamAsync_ReadsDataButDoesNotCloseStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + MemoryStream memStream = new MemoryStream(ExpectedSampleTypeByteRepresentation); + HttpContent content = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = content.Headers; + contentHeaders.ContentLength = memStream.Length; + contentHeaders.ContentType = CreateSupportedMediaType(); + + // Act + return formatter.ReadFromStreamAsync(typeof(SampleType), memStream, content, null).ContinueWith( + readTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + Assert.True(memStream.CanRead); + + var value = Assert.IsType(readTask.Result); + Assert.Equal(42, value.Number); + }); + } + + [Fact] + public Task ReadFromStreamAsync_WhenContentLengthIsNull_ReadsDataButDoesNotCloseStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + MemoryStream memStream = new MemoryStream(ExpectedSampleTypeByteRepresentation); + HttpContent content = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = content.Headers; + contentHeaders.ContentLength = null; + contentHeaders.ContentType = CreateSupportedMediaType(); + + // Act + return formatter.ReadFromStreamAsync(typeof(SampleType), memStream, content, null).ContinueWith( + readTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + Assert.True(memStream.CanRead); + + var value = Assert.IsType(readTask.Result); + Assert.Equal(42, value.Number); + }); + } + + [Fact] + public void WriteToStreamAsync_ThrowsOnNull() + { + TFormatter formatter = CreateFormatter(); + Assert.ThrowsArgumentNull(() => { formatter.WriteToStreamAsync(null, new object(), Stream.Null, null, null); }, "type"); + Assert.ThrowsArgumentNull(() => { formatter.WriteToStreamAsync(typeof(object), new object(), null, null, null); }, "writeStream"); + } + + [Fact] + public virtual Task WriteToStreamAsync_WhenObjectIsNull_WritesDataButDoesNotCloseStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + Mock mockStream = new Mock(); + mockStream.Setup(s => s.CanWrite).Returns(true); + HttpContent content = new StringContent(String.Empty); + + // Act + return formatter.WriteToStreamAsync(typeof(SampleType), null, mockStream.Object, content, null).ContinueWith( + writeTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + mockStream.Verify(s => s.Close(), Times.Never()); + mockStream.Verify(s => s.BeginWrite(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + }); + } + + [Fact] + public Task WriteToStreamAsync_WritesDataButDoesNotCloseStream() + { + // Arrange + TFormatter formatter = CreateFormatter(); + SampleType sampleType = new SampleType { Number = 42 }; + MemoryStream memStream = new MemoryStream(); + HttpContent content = new StringContent(String.Empty); + content.Headers.ContentType = CreateSupportedMediaType(); + + // Act + return formatter.WriteToStreamAsync(typeof(SampleType), sampleType, memStream, content, null).ContinueWith( + writeTask => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + Assert.True(memStream.CanRead); + + byte[] actualSampleTypeByteRepresentation = memStream.ToArray(); + Assert.NotEmpty(actualSampleTypeByteRepresentation); + }); + } + + [Fact] + public virtual async Task Overridden_WriteToStreamAsyncWithoutCancellationToken_GetsCalled() + { + // Arrange + Stream stream = new MemoryStream(); + Mock formatter = CreateMockFormatter(); + ObjectContent content = new ObjectContent(42, formatter.Object); + + formatter + .Setup(f => f.WriteToStreamAsync(typeof(int), 42, stream, content, null /* transportContext */)) + .Returns(TaskHelpers.Completed()) + .Verifiable(); + + // Act + await content.CopyToAsync(stream); + + // Assert + formatter.Verify(); + } + + [Fact] + public virtual async Task Overridden_WriteToStreamAsyncWithCancellationToken_GetsCalled() + { + // Arrange + Stream stream = new MemoryStream(); + Mock formatter = CreateMockFormatter(); + ObjectContent content = new ObjectContent(42, formatter.Object); + + formatter + .Setup(f => f.WriteToStreamAsync(typeof(int), 42, stream, content, null /* transportContext */, CancellationToken.None)) + .Returns(TaskHelpers.Completed()) + .Verifiable(); + + // Act + await content.CopyToAsync(stream); + + // Assert + formatter.Verify(); + } + + [Fact] + public virtual async Task Overridden_ReadFromStreamAsyncWithoutCancellationToken_GetsCalled() + { + // Arrange + Stream stream = new MemoryStream(); + Mock formatter = CreateMockFormatter(); + formatter.Object.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/test")); + StringContent content = new StringContent(" ", Encoding.Default, "application/test"); + CancellationTokenSource cts = new CancellationTokenSource(); + + formatter + .Setup(f => f.ReadFromStreamAsync(typeof(string), It.IsAny(), content, null /*formatterLogger */)) + .Returns(Task.FromResult(null)) + .Verifiable(); + + // Act + await content.ReadAsAsync(new[] { formatter.Object }, cts.Token); + + // Assert + formatter.Verify(); + } + + [Fact] + public virtual async Task Overridden_ReadFromStreamAsyncWithCancellationToken_GetsCalled() + { + // Arrange + Stream stream = new MemoryStream(); + Mock formatter = CreateMockFormatter(); + formatter.Object.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/test")); + StringContent content = new StringContent(" ", Encoding.Default, "application/test"); + CancellationTokenSource cts = new CancellationTokenSource(); + + formatter + .Setup(f => f.ReadFromStreamAsync(typeof(string), It.IsAny(), content, null /*formatterLogger */, cts.Token)) + .Returns(Task.FromResult(null)) + .Verifiable(); + + // Act + await content.ReadAsAsync(new[] { formatter.Object }, cts.Token); + + // Assert + formatter.Verify(); + } + + public abstract Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding); + + public abstract Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding); + + protected virtual TFormatter CreateFormatter() + { + ConstructorInfo constructor = typeof(TFormatter).GetConstructor(Type.EmptyTypes); + return (TFormatter)constructor.Invoke(null); + } + + protected virtual Mock CreateMockFormatter() + { + return new Mock() { CallBase = true }; + } + + protected virtual MediaTypeHeaderValue CreateSupportedMediaType() + { + return ExpectedSupportedMediaTypes.First(); + } + + public object ReadFromStreamAsync_RoundTripsWriteToStreamAsync_Helper(MediaTypeFormatter formatter, Type variationType, object testData) + { + // Arrange + HttpContent content = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = content.Headers; + object readObj = null; + + // Act & Assert + Assert.Stream.WriteAndRead( + stream => + { + Assert.Task.Succeeds(formatter.WriteToStreamAsync(variationType, testData, stream, content, transportContext: null)); + contentHeaders.ContentLength = stream.Length; + }, + stream => readObj = Assert.Task.SucceedsWithResult(formatter.ReadFromStreamAsync(variationType, stream, content, formatterLogger: null))); + + return readObj; + } + + public Task ReadFromStreamAsync_UsesCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, string formattedContent, string mediaType, string encoding, bool isDefaultEncoding) + { + // Arrange + Encoding enc = null; + if (isDefaultEncoding) + { + enc = formatter.SupportedEncodings.First((e) => e.WebName.Equals(encoding, StringComparison.OrdinalIgnoreCase)); + } + else + { + enc = Encoding.GetEncoding(encoding); + formatter.SupportedEncodings.Add(enc); + } + + byte[] data = enc.GetBytes(formattedContent); + MemoryStream memStream = new MemoryStream(data); + + StringContent dummyContent = new StringContent(string.Empty); + HttpContentHeaders headers = dummyContent.Headers; + headers.Clear(); + headers.ContentType = MediaTypeHeaderValue.Parse(mediaType); + headers.ContentLength = data.Length; + + IFormatterLogger mockFormatterLogger = new Mock().Object; + + // Act + return formatter.ReadFromStreamAsync(typeof(string), memStream, dummyContent, mockFormatterLogger).ContinueWith( + (readTask) => + { + string result = readTask.Result as string; + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + Assert.Equal(content, result); + }); + } + + public Task WriteToStreamAsync_UsesCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, string formattedContent, string mediaType, string encoding, bool isDefaultEncoding) + { + // Arrange + Encoding enc = null; + if (isDefaultEncoding) + { + enc = formatter.SupportedEncodings.First((e) => e.WebName.Equals(encoding, StringComparison.OrdinalIgnoreCase)); + } + else + { + enc = Encoding.GetEncoding(encoding); + formatter.SupportedEncodings.Add(enc); + } + + byte[] preamble = enc.GetPreamble(); + byte[] data = enc.GetBytes(formattedContent); + byte[] expectedData = new byte[preamble.Length + data.Length]; + Buffer.BlockCopy(preamble, 0, expectedData, 0, preamble.Length); + Buffer.BlockCopy(data, 0, expectedData, preamble.Length, data.Length); + + MemoryStream memStream = new MemoryStream(); + + HttpContent httpContent = new StringContent(String.Empty); + HttpContentHeaders contentHeaders = httpContent.Headers; + contentHeaders.Clear(); + contentHeaders.ContentType = MediaTypeHeaderValue.Parse(mediaType); + contentHeaders.ContentLength = expectedData.Length; + + IFormatterLogger mockFormatterLogger = new Mock().Object; + + // Act + return formatter.WriteToStreamAsync(typeof(string), content, memStream, httpContent, null).ContinueWith( + (writeTask) => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + byte[] actualData = memStream.ToArray(); + Assert.Equal(expectedData, actualData); + }); + } + + public static Encoding CreateOrGetSupportedEncoding(MediaTypeFormatter formatter, string encoding, bool isDefaultEncoding) + { + Encoding enc = null; + if (isDefaultEncoding) + { + enc = formatter.SupportedEncodings.First((e) => e.WebName.Equals(encoding, StringComparison.OrdinalIgnoreCase)); + } + else + { + enc = Encoding.GetEncoding(encoding); + formatter.SupportedEncodings.Add(enc); + } + + return enc; + } + + public static Task ReadContentUsingCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, string formattedContent, string mediaType, string encoding, bool isDefaultEncoding) + { + // Arrange + Encoding enc = CreateOrGetSupportedEncoding(formatter, encoding, isDefaultEncoding); + byte[] sourceData = enc.GetBytes(formattedContent); + + // Further Arrange, Act & Assert + return ReadContentUsingCorrectCharacterEncodingHelper(formatter, content, sourceData, mediaType); + } + + public static Task ReadContentUsingCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, byte[] sourceData, string mediaType) + { + // Arrange + MemoryStream memStream = new MemoryStream(sourceData); + + StringContent dummyContent = new StringContent(string.Empty); + HttpContentHeaders headers = dummyContent.Headers; + headers.Clear(); + headers.ContentType = MediaTypeHeaderValue.Parse(mediaType); + headers.ContentLength = sourceData.Length; + + IFormatterLogger mockFormatterLogger = new Mock().Object; + + // Act & Assert + return formatter.ReadFromStreamAsync(typeof(string), memStream, dummyContent, mockFormatterLogger).ContinueWith( + (readTask) => + { + string result = readTask.Result as string; + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, readTask.Status); + Assert.Equal(content, result); + }); + } + + public static Task WriteContentUsingCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, string formattedContent, string mediaType, string encoding, bool isDefaultEncoding) + { + // Arrange + Encoding enc = CreateOrGetSupportedEncoding(formatter, encoding, isDefaultEncoding); + + byte[] preamble = enc.GetPreamble(); + byte[] data = enc.GetBytes(formattedContent); + byte[] expectedData = new byte[preamble.Length + data.Length]; + Buffer.BlockCopy(preamble, 0, expectedData, 0, preamble.Length); + Buffer.BlockCopy(data, 0, expectedData, preamble.Length, data.Length); + + // Further Arrange, Act & Assert + return WriteContentUsingCorrectCharacterEncodingHelper(formatter, content, expectedData, mediaType); + } + + + public static Task WriteContentUsingCorrectCharacterEncodingHelper(MediaTypeFormatter formatter, string content, byte[] expectedData, string mediaType) + { + // Arrange + MemoryStream memStream = new MemoryStream(); + + StringContent dummyContent = new StringContent(string.Empty); + HttpContentHeaders headers = dummyContent.Headers; + headers.Clear(); + headers.ContentType = MediaTypeHeaderValue.Parse(mediaType); + headers.ContentLength = expectedData.Length; + + IFormatterLogger mockFormatterLogger = new Mock().Object; + + // Act & Assert + return formatter.WriteToStreamAsync(typeof(string), content, memStream, dummyContent, null).ContinueWith( + (writeTask) => + { + // Assert + Assert.Equal(TaskStatus.RanToCompletion, writeTask.Status); + byte[] actualData = memStream.ToArray(); + + Assert.Equal(expectedData, actualData); + }); + } + } + + [DataContract(Name = "DataContractSampleType")] + public class SampleType + { + [DataMember] + public int Number { get; set; } + } +} diff --git a/OData/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj b/OData/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj index ca08a44ad..97f39249d 100644 --- a/OData/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj +++ b/OData/test/System.Web.Http.OData.Test/System.Web.Http.OData.Test.csproj @@ -1,6 +1,6 @@  - + {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9} Library @@ -32,6 +32,10 @@ + + False + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.0\lib\net45\System.Net.Http.Formatting.dll + @@ -39,6 +43,14 @@ ..\..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.0\lib\net45\System.Web.Http.dll + + + False + ..\..\packages\Microsoft.AspNet.WebApi.SelfHost.5.2.0\lib\net45\System.Web.Http.SelfHost.dll + @@ -352,22 +364,10 @@ - - {668e9021-ce84-49d9-98fb-df125a9fcdb0} - System.Net.Http.Formatting - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE} System.Web.Http.OData - - {66492e69-ce4c-4fb1-9b1f-88dee09d06f1} - System.Web.Http.SelfHost - - - {ddc1ce0c-486e-4e35-bb3b-eab61f8f9440} - System.Web.Http - {fccc4cb7-baf7-4a57-9f89-e5766fe536c0} Microsoft.TestCommon diff --git a/OData/test/System.Web.Http.OData.Test/packages.config b/OData/test/System.Web.Http.OData.Test/packages.config index fca4fad1e..57f71a3c2 100644 --- a/OData/test/System.Web.Http.OData.Test/packages.config +++ b/OData/test/System.Web.Http.OData.Test/packages.config @@ -1,5 +1,8 @@  + + + diff --git a/OData/test/System.Web.Http.Test/Util/ContextUtil.cs b/OData/test/System.Web.Http.Test/Util/ContextUtil.cs new file mode 100644 index 000000000..e133b7a93 --- /dev/null +++ b/OData/test/System.Web.Http.Test/Util/ContextUtil.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using System.Web.Http.Routing; +using Moq; + +namespace System.Web.Http +{ + internal static class ContextUtil + { + public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration = null, IHttpController instance = null, IHttpRouteData routeData = null, HttpRequestMessage request = null) + { + HttpConfiguration config = configuration ?? new HttpConfiguration(); + IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute()); + HttpRequestMessage req = request ?? new HttpRequestMessage(); + req.SetConfiguration(config); + req.SetRouteData(route); + + HttpControllerContext context = new HttpControllerContext(config, route, req); + if (instance != null) + { + context.Controller = instance; + } + context.ControllerDescriptor = CreateControllerDescriptor(config); + + return context; + } + + public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null) + { + HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext(); + HttpActionDescriptor descriptor = actionDescriptor ?? CreateActionDescriptor(); + descriptor.ControllerDescriptor = context.ControllerDescriptor; + return new HttpActionContext(context, descriptor); + } + + public static HttpActionContext GetHttpActionContext(HttpRequestMessage request) + { + HttpActionContext actionContext = CreateActionContext(); + actionContext.ControllerContext.Request = request; + return actionContext; + } + + public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response) + { + HttpActionContext actionContext = CreateActionContext(); + actionContext.ControllerContext.Request = request; + HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response }; + return actionExecutedContext; + } + + public static HttpControllerDescriptor CreateControllerDescriptor(HttpConfiguration config = null) + { + if (config == null) + { + config = new HttpConfiguration(); + } + return new HttpControllerDescriptor() { Configuration = config, ControllerName = "FooController" }; + } + + public static HttpActionDescriptor CreateActionDescriptor() + { + var mock = new Mock() { CallBase = true }; + mock.SetupGet(d => d.ActionName).Returns("Bar"); + return mock.Object; + } + } +} diff --git a/OData/test/System.Web.OData.Test/System.Web.OData.Test.csproj b/OData/test/System.Web.OData.Test/System.Web.OData.Test.csproj index 794dd9b58..7340a480f 100644 --- a/OData/test/System.Web.OData.Test/System.Web.OData.Test.csproj +++ b/OData/test/System.Web.OData.Test/System.Web.OData.Test.csproj @@ -1,6 +1,6 @@  - + {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612} Library @@ -36,9 +36,21 @@ + + False + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.0\lib\net45\System.Net.Http.Formatting.dll + + + False + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.0\lib\net45\System.Web.Http.dll + + + False + ..\..\packages\Microsoft.AspNet.WebApi.SelfHost.5.2.0\lib\net45\System.Web.Http.SelfHost.dll + @@ -429,18 +441,6 @@ - - {668e9021-ce84-49d9-98fb-df125a9fcdb0} - System.Net.Http.Formatting - - - {66492e69-ce4c-4fb1-9b1f-88dee09d06f1} - System.Web.Http.SelfHost - - - {ddc1ce0c-486e-4e35-bb3b-eab61f8f9440} - System.Web.Http - {d23e28f1-ccd0-43e0-8c0d-36731ec91318} System.Web.OData diff --git a/OData/test/System.Web.OData.Test/packages.config b/OData/test/System.Web.OData.Test/packages.config index ef2dfd4a4..82a0c2a84 100644 --- a/OData/test/System.Web.OData.Test/packages.config +++ b/OData/test/System.Web.OData.Test/packages.config @@ -1,5 +1,8 @@  + + + diff --git a/OData/tools/35MSSharedLib1024.snk b/OData/tools/35MSSharedLib1024.snk new file mode 100644 index 0000000000000000000000000000000000000000..695f1b38774e839e5b90059bfb7f32df1dff4223 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OData/tools/WebStack.StyleCop.targets b/OData/tools/WebStack.StyleCop.targets new file mode 100644 index 000000000..0b73b551f --- /dev/null +++ b/OData/tools/WebStack.StyleCop.targets @@ -0,0 +1,80 @@ + + + + + + + + + + + $(PrepareForRunDependsOn);StyleCop + + + + $(StyleCopEnabled) + false + true + true + + $(IntermediateOutputPath)StyleCopViolations.xml + $(IntermediateOutputPath)StyleCop.success + + 0 + + + + + + @(StyleCopMsBuildRunner) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OData/tools/WebStack.settings.targets b/OData/tools/WebStack.settings.targets new file mode 100644 index 000000000..b8728ff12 --- /dev/null +++ b/OData/tools/WebStack.settings.targets @@ -0,0 +1,62 @@ + + + + + + + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\')) + $(MSBuildThisFileDirectory) + + + $(WebStackToolsPath)WebStack.targets + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + Debug + $(WebStackRootPath)bin\$(Configuration)\ + AnyCPU + 512 + prompt + 4 + true + + + ENU + + + + false + false + + + v4.5 + + + true + true + $(WebStackRootPath)\tools\35MSSharedLib1024.snk + + + + + true + full + false + TRACE;DEBUG;CODE_ANALYSIS + + + pdbonly + true + TRACE + + + pdbonly + true + TRACE;CODE_ANALYSIS + + \ No newline at end of file diff --git a/OData/tools/WebStack.targets b/OData/tools/WebStack.targets new file mode 100644 index 000000000..46c77bc27 --- /dev/null +++ b/OData/tools/WebStack.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/OData/tools/WebStack.tasks.targets b/OData/tools/WebStack.tasks.targets new file mode 100644 index 000000000..6303444aa --- /dev/null +++ b/OData/tools/WebStack.tasks.targets @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + testFailures = new List(); + + foreach (string testResultFile in testResultFiles) + { + XElement xml; + using (FileStream fileStream = File.OpenRead(testResultFile)) + { + xml = XElement.Load(fileStream); + } + + var assemblies = xml.Elements(XName.Get("assembly")); + foreach (XElement assembly in assemblies) + { + int failures = Int32.Parse(assembly.Attribute(XName.Get("failed")).Value); + + testsPassed += Int32.Parse(assembly.Attribute(XName.Get("passed")).Value); + testsFailed += failures; + testsSkipped += Int32.Parse(assembly.Attribute(XName.Get("skipped")).Value); + timeSpent += Decimal.Parse(assembly.Attribute(XName.Get("time")).Value); + + if (failures > 0) + { + foreach (XElement classWithFailure in assembly.Elements(XName.Get("class")) + .Where(c => Int32.Parse(c.Attribute(XName.Get("failed")).Value) > 0)) + { + foreach (XElement failure in classWithFailure.Elements(XName.Get("test")) + .Where(test => test.Attribute(XName.Get("result")).Value == "Fail")) + { + testFailures.Add(failure.Attribute("name").Value); + } + } + } + } + } + + if (testFailures.Count > 0) + { + Console.WriteLine(); + Console.WriteLine(" Test Failures:"); + ConsoleColor originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + foreach (string testFailure in testFailures) + { + Console.WriteLine(" " + testFailure); + } + Console.ForegroundColor = originalColor; + } + + Console.WriteLine(); + Console.WriteLine(" Tests passed: {0}, Tests failed: {1}, Tests skipped: {2}", testsPassed, testsFailed, testsSkipped); + Console.WriteLine(" Time spent running tests: {0} seconds", timeSpent); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + return false; + } + ]]> + + + + \ No newline at end of file diff --git a/OData/tools/WebStack.xunit.targets b/OData/tools/WebStack.xunit.targets new file mode 100644 index 000000000..e232f1730 --- /dev/null +++ b/OData/tools/WebStack.xunit.targets @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OData/tools/src/Microsoft.Web.FxCop/DoNotCallProblematicMethodsOnTaskRule.cs b/OData/tools/src/Microsoft.Web.FxCop/DoNotCallProblematicMethodsOnTaskRule.cs new file mode 100644 index 000000000..52b976e8d --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/DoNotCallProblematicMethodsOnTaskRule.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public class DoNotCallProblematicMethodsOnTaskRule : IntrospectionRule + { + private readonly Dictionary _problematicMethods = GetProblematicMethods(); + + public DoNotCallProblematicMethodsOnTaskRule() + : base("DoNotCallProblematicMethodsOnTask") + { + } + + public override ProblemCollection Check(Member member) + { + var method = member as Method; + if (method != null) + { + VisitStatements(method.Body.Statements); + } + + return Problems; + } + + public override void VisitMemberBinding(MemberBinding memberBinding) + { + var method = memberBinding.BoundMember as Method; + if (method != null) + { + string message; + if (_problematicMethods.TryGetValue(method.Name.Name, out message) && + method.DeclaringType.IsTask()) + { + Problems.Add(new Problem(GetResolution(method.Name.Name, message), memberBinding.UniqueKey.ToString())); + } + } + + base.VisitMemberBinding(memberBinding); + } + + private static Dictionary GetProblematicMethods() + { + return new Dictionary + { + { "get_Result", "Calls to this method are difficult to get correct or do not have good performance characteristics. Use the .Then(), .Catch(), or .Finally() extension method instead." }, + { "get_Factory", "If you need to create a Task, use the TaskHelpers class instead." }, + { "ContinueWith", "Calls to this method are difficult to get correct or do not have good performance characteristics. Use the .Then(), .Catch(), or .Finally() extension method instead." }, + { "Dispose", "Tasks should never be disposed of." }, + { "Run", "If you need to create a Task, use the TaskHelpers class instead." }, + { "RunSynchronously", "If you need to create a Task, use the TaskHelpers class instead." }, + { "Start", "If you need to create a Task, use the TaskHelpers class instead." }, + { "Wait", "This is a blocking call. Switch to an asynchronous call like .Then() instead." }, + { "WaitAll", "This is a blocking call. Switch to an asynchronous call like .Then() instead." }, + { "WaitAny", "This is a blocking call. Switch to an asynchronous call like .Then() instead." }, + { "Yield", "This call forces a thread transition which can hurt server performance. Do not use." }, + }; + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/DoNotConstructTaskInstancesRule.cs b/OData/tools/src/Microsoft.Web.FxCop/DoNotConstructTaskInstancesRule.cs new file mode 100644 index 000000000..b994e297b --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/DoNotConstructTaskInstancesRule.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public class DoNotConstructTaskInstancesRule : IntrospectionRule + { + public DoNotConstructTaskInstancesRule() + : base("DoNotConstructTaskInstances") + { + } + + public override ProblemCollection Check(Member member) + { + var method = member as Method; + if (method != null) + { + VisitStatements(method.Body.Statements); + } + + return Problems; + } + + public override void VisitConstruct(Construct construct) + { + var memberBinding = construct.Constructor as MemberBinding; + + if (memberBinding != null + && memberBinding.BoundMember.Name.Name == ".ctor" + && memberBinding.BoundMember.DeclaringType.IsTask()) + { + Problems.Add(new Problem(GetResolution(), construct.UniqueKey.ToString())); + } + + base.VisitConstruct(construct); + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/DoNotUseFinalizersRule.cs b/OData/tools/src/Microsoft.Web.FxCop/DoNotUseFinalizersRule.cs new file mode 100644 index 000000000..d885f3be5 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/DoNotUseFinalizersRule.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public class DoNotUseFinalizersRule : IntrospectionRule + { + public DoNotUseFinalizersRule() + : base("DoNotUseFinalizers") + { + } + + public override ProblemCollection Check(Member member) + { + if (member.NodeType == NodeType.Method && member.Name.Name == "Finalize") + { + Problems.Add(new Problem(GetResolution(member.DeclaringType.FullName), member)); + } + + return Problems; + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/DoNotUseProblematicTaskTypesRule.cs b/OData/tools/src/Microsoft.Web.FxCop/DoNotUseProblematicTaskTypesRule.cs new file mode 100644 index 000000000..69e957347 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/DoNotUseProblematicTaskTypesRule.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public class DoNotUseProblematicTaskTypesRule : IntrospectionRule + { + private readonly Dictionary _problematicTypes = GetProblematicTypes(); + + public DoNotUseProblematicTaskTypesRule() + : base("DoNotUseProblematicTaskTypes") + { + } + + public override ProblemCollection Check(Member member) + { + var method = member as Method; + if (method != null) + { + VisitStatements(method.Body.Statements); + } + + return Problems; + } + + public override void VisitMemberBinding(MemberBinding memberBinding) + { + var method = memberBinding.BoundMember as Method; + if (method != null) + { + string message; + if (_problematicTypes.TryGetValue(method.DeclaringType.FullName, out message)) + { + Problems.Add(new Problem(GetResolution(method.DeclaringType.FullName, message), memberBinding.UniqueKey.ToString())); + } + } + + base.VisitMemberBinding(memberBinding); + } + + private static Dictionary GetProblematicTypes() + { + return new Dictionary + { + { "System.Threading.Tasks.Parallel", "The methods on this type are blocking operations." }, + { "System.Threading.Tasks.TaskExtensions", "The .Unwrap() method does not have good performance characteristics. Use the .FastUnwrap() extension method instead." }, + { "System.Threading.Tasks.TaskFactory", "If you need to create a Task, use the TaskHelpers class instead." }, + { "System.Threading.Tasks.TaskScheduler", "If you need to create a Task, use the TaskHelpers class instead." } + }; + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/IntrospectionRule.cs b/OData/tools/src/Microsoft.Web.FxCop/IntrospectionRule.cs new file mode 100644 index 000000000..6c339d2d0 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/IntrospectionRule.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public abstract class IntrospectionRule : BaseIntrospectionRule + { + protected IntrospectionRule(string name) + : base(name, "Microsoft.Web.FxCop.Rules", typeof(IntrospectionRule).Assembly) + { + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/Microsoft.Web.FxCop.csproj b/OData/tools/src/Microsoft.Web.FxCop/Microsoft.Web.FxCop.csproj new file mode 100644 index 000000000..13afb5d79 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/Microsoft.Web.FxCop.csproj @@ -0,0 +1,74 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {F439D4E6-3FAC-4C30-9585-6D258133A2BF} + Library + Properties + Microsoft.Web.FxCop + Microsoft.Web.FxCop + v4.0 + bin\$(Configuration) + + + x86 + true + full + false + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + TRACE + prompt + 4 + + + $(MSBuildProgramFiles32)\Microsoft Visual Studio $(VisualStudioVersion)\Team Tools\Static Analysis Tools\FxCop\ + + + + $(FxCopLocation)FxCopSdk.dll + False + + + $(FxCopLocation)Microsoft.Cci.dll + False + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + \ No newline at end of file diff --git a/OData/tools/src/Microsoft.Web.FxCop/Properties/AssemblyInfo.cs b/OData/tools/src/Microsoft.Web.FxCop/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5d52e02f8 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Reflection; + +[assembly: AssemblyProduct("Microsoft.Web.FxCop")] +[assembly: AssemblyDescription("FxCop rules used by the Microsoft ASP.NET WebStack projects")] +[assembly: AssemblyCompany("Microsoft Open Technologies, Inc.")] +[assembly: AssemblyCopyright("© Microsoft Open Technologies, Inc. All rights reserved.")] diff --git a/OData/tools/src/Microsoft.Web.FxCop/Rules.xml b/OData/tools/src/Microsoft.Web.FxCop/Rules.xml new file mode 100644 index 000000000..f137dcca8 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/Rules.xml @@ -0,0 +1,59 @@ + + + + + + UnusedResourceUsageRule + This rule fires when a resource is unused in the assembly. + + Remove the unused resource '{0}' from '{1}'. + + Error + Breaking + + + + + + Do not use finalizers + For performance reasons, managed types should not implement finalizers. + + Type '{0}' has a finalizer. Use SafeHandle to contain unmanaged data instead. + + Error + Breaking + + + + + + Do not construct instances of the Task-related types + For performance reasons, all tasks should be created using the TaskHelpers library. + + Do not construct instances of Task-related types. If you need to create a Task, use the TaskHelpers class instead. + + Error + NonBreaking + + + + Do not call problematic methods on Task + For performance reasons, use the TaskHelpers and TaskHelpersExtensions library rather than methods on Task. + + Do not call the method 'Task.{0}'. {1} + + Error + NonBreaking + + + + Do not call use problematic Task types + For performance reasons, use the TaskHelpers and TaskHelpersExtensions library rather than Task-related types. + + Do not use the type '{0}'. {1} + + Error + NonBreaking + + + diff --git a/OData/tools/src/Microsoft.Web.FxCop/TypeNodeExtensions.cs b/OData/tools/src/Microsoft.Web.FxCop/TypeNodeExtensions.cs new file mode 100644 index 000000000..cfbc5d3d2 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/TypeNodeExtensions.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public static class TypeNodeExtensions + { + private const string TaskPortableAssembly = "System.Threading.Tasks"; + private const string TaskNamespace = "System.Threading.Tasks"; + private const string TaskType = "Task"; + private const string TaskGenericType = "Task`1"; + private static readonly object _taskLock = new object(); + + private static TypeNode _task; + private static TypeNode _taskGeneric; + + public static bool IsTask(this TypeNode type) + { + EnsureTaskTypesInitialized(type); + return IsTaskCore(type); + } + + private static bool IsTaskCore(TypeNode type) + { + Contract.Assert(_task != null); + Contract.Assert(_taskGeneric != null); + + if (type.Name.UniqueIdKey == _task.Name.UniqueIdKey) + { + return true; + } + + if (!type.IsGeneric) + { + return false; + } + + TypeNode targetType = _taskGeneric.GetGenericTemplateInstance(_taskGeneric.DeclaringModule, type.ConsolidatedTemplateArguments); + return targetType.Name.UniqueIdKey == type.Name.UniqueIdKey; + } + + private static void EnsureTaskTypesInitialized(TypeNode type) + { + lock (_taskLock) + { + if (_task == null) + { + Contract.Assert(_task == null); + Contract.Assert(_taskGeneric == null); + + AssemblyNode taskAssembly = GetTaskAssembly(type); + Contract.Assert(taskAssembly != null); + + _task = GetTypeNode(taskAssembly, TaskNamespace, TaskType); + _taskGeneric = GetTypeNode(taskAssembly, TaskNamespace, TaskGenericType); + + Contract.Assert(_task != null); + Contract.Assert(_taskGeneric != null); + } + } + } + + private static TypeNode GetTypeNode(AssemblyNode assembly, string ns, string name) + { + Contract.Assert(assembly != null); + + return assembly.GetType(Identifier.For(ns), Identifier.For(name)); + } + + private static AssemblyNode GetTaskAssembly(TypeNode type) + { + AssemblyNode taskAssembly = null; + + // Check if the type's located in mscorlib + TypeNode taskTypeNode = GetTypeNode(FrameworkAssemblies.Mscorlib, TaskNamespace, TaskGenericType); + if (taskTypeNode != null) + { + taskAssembly = FrameworkAssemblies.Mscorlib; + } + else if (type.DeclaringModule.Name.Equals(TaskPortableAssembly)) + { + // If the type is a type in the portable assembly, no need to loop through the assembly references + taskAssembly = type.DeclaringModule.ContainingAssembly; + } + else + { + AssemblyReference assemblyReference = type.DeclaringModule + .AssemblyReferences + .FirstOrDefault(reference => reference.Name.Equals(TaskPortableAssembly, StringComparison.Ordinal)); + Contract.Assert(assemblyReference != null); + taskAssembly = assemblyReference.Assembly; + } + + Contract.Assert(taskAssembly != null); + return taskAssembly; + } + } +} diff --git a/OData/tools/src/Microsoft.Web.FxCop/UnusedResourceUsageRule.cs b/OData/tools/src/Microsoft.Web.FxCop/UnusedResourceUsageRule.cs new file mode 100644 index 000000000..5115d9ef4 --- /dev/null +++ b/OData/tools/src/Microsoft.Web.FxCop/UnusedResourceUsageRule.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Microsoft.FxCop.Sdk; + +namespace Microsoft.Web.FxCop +{ + public class UnusedResourceUsageRule : IntrospectionRule + { + private static readonly object _lock = new object(); + private static readonly IDictionary> _availableResources = new ConcurrentDictionary>(); + private static readonly IDictionary> _usedResources = new ConcurrentDictionary>(); + + public UnusedResourceUsageRule() + : base("UnusedResourceUsageRule") + { + } + + public override TargetVisibilities TargetVisibility + { + get { return TargetVisibilities.All; } + } + + public override ProblemCollection Check(ModuleNode node) + { + var assemblyNode = node as AssemblyNode; + if (assemblyNode != null) + { + var assemblyAvailableResources = new HashSet(); + var assemblyUsedResources = new HashSet(); + + _availableResources[assemblyNode] = assemblyAvailableResources; + _usedResources[assemblyNode] = assemblyUsedResources; + + VisitAssembly(assemblyNode); + + IEnumerable unusedResources = from res in assemblyAvailableResources.Except(assemblyUsedResources) + where !IsCommonResource(res) + select res; + + foreach (PropertyNode item in unusedResources) + { + Problems.Add(new Problem(this.GetResolution(item.Name.Name, item.DeclaringType.FullName), item.UniqueKey.ToString())); + } + } + + return Problems; + } + + public override void VisitProperty(PropertyNode property) + { + if (IsResourceType(property.DeclaringType) && IsResource(property)) + { + AddItemWithLock(_availableResources[(AssemblyNode)property.DeclaringType.DeclaringModule], property); + } + + base.VisitProperty(property); + } + + public override void VisitMethodCall(MethodCall call) + { + MemberBinding mb = call.Callee as MemberBinding; + if (mb != null) + { + Method methodBeingCalled = mb.BoundMember as Method; + if (methodBeingCalled != null + && IsResourceType(methodBeingCalled.DeclaringType) + && IsResource(methodBeingCalled.DeclaringMember as PropertyNode)) + { + + var property = methodBeingCalled.DeclaringMember as PropertyNode; + + ISet properties = null; + // Look up the assembly from the dictionary. If the assembly could not be looked up, the resource must have been declared outside the current assembly + if (_usedResources.TryGetValue((AssemblyNode)property.DeclaringType.DeclaringModule, out properties)) + { + AddItemWithLock(properties, property); + } + } + } + + base.VisitMethodCall(call); + } + + private static void AddItemWithLock(ISet item, T value) + { + lock (_lock) + { + item.Add(value); + } + } + + private static bool IsResource(PropertyNode property) + { + return property != null && property.Type.FullName.Equals(typeof(System.String).FullName); + } + + private static bool IsResourceType(TypeNode typeNode) + { + var classNode = typeNode as ClassNode; + return (classNode != null + && classNode.Attributes.Any(c => c.Type.FullName.Equals(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).FullName)) + && classNode.Name.Name.Contains("Resource")); + } + + /// + /// Hack to determine if the file is "Common*Resources.resx" that is shared amongst multiple WebStack projects. + /// Strings in this resx are ignored since they may have dependencies outside the current assembly. + /// + private static bool IsCommonResource(PropertyNode property) + { + string name = property.DeclaringType.Name.Name; + return name.StartsWith("Common") && name.EndsWith("Resources"); + } + } +} + diff --git a/Runtime.sln b/Runtime.sln index 746bebe80..53af9bb27 100644 --- a/Runtime.sln +++ b/Runtime.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30414.0 +VisualStudioVersion = 12.0.30501.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}" EndProject @@ -83,10 +83,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.Tracing", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.Tracing.Test", "test\System.Web.Http.Tracing.Test\System.Web.Http.Tracing.Test.csproj", "{F87FD911-4A97-4057-8EAE-1CB96B9A1937}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.OData", "src\System.Web.Http.OData\System.Web.Http.OData.csproj", "{CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.OData.Test", "test\System.Web.Http.OData.Test\System.Web.Http.OData.Test.csproj", "{D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.SignalR", "src\System.Web.Http.SignalR\System.Web.Http.SignalR.csproj", "{8A607AC9-E7DD-4B74-A0B1-47FC95B9838B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.SignalR.Test", "test\System.Web.Http.SignalR.Test\System.Web.Http.SignalR.Test.csproj", "{E22245AF-D5E1-46F6-B443-C886983EC50C}" @@ -113,10 +109,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Cors.Test", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.Http.Cors.Test", "test\System.Web.Http.Cors.Test\System.Web.Http.Cors.Test.csproj", "{1E89A3E9-0A7F-418F-B4BE-6E38A6315373}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData", "src\System.Web.OData\System.Web.OData.csproj", "{D23E28F1-CCD0-43E0-8C0D-36731EC91318}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Web.OData.Test", "test\System.Web.OData.Test\System.Web.OData.Test.csproj", "{66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Facebook", "src\Microsoft.AspNet.Facebook\Microsoft.AspNet.Facebook.csproj", "{821A136C-7C6F-44C6-A9E6-C39B5BFB1483}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Facebook.Test", "test\Microsoft.AspNet.Facebook.Test\Microsoft.AspNet.Facebook.Test.csproj", "{C3BEF382-C7C4-454D-B017-1EAC03E9A82C}" @@ -356,18 +348,6 @@ Global {F87FD911-4A97-4057-8EAE-1CB96B9A1937}.Debug|Any CPU.Build.0 = Debug|Any CPU {F87FD911-4A97-4057-8EAE-1CB96B9A1937}.Release|Any CPU.ActiveCfg = Release|Any CPU {F87FD911-4A97-4057-8EAE-1CB96B9A1937}.Release|Any CPU.Build.0 = Release|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE}.Release|Any CPU.Build.0 = Release|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9}.Release|Any CPU.Build.0 = Release|Any CPU {8A607AC9-E7DD-4B74-A0B1-47FC95B9838B}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {8A607AC9-E7DD-4B74-A0B1-47FC95B9838B}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {8A607AC9-E7DD-4B74-A0B1-47FC95B9838B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -428,18 +408,6 @@ Global {1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E89A3E9-0A7F-418F-B4BE-6E38A6315373}.Release|Any CPU.Build.0 = Release|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D23E28F1-CCD0-43E0-8C0D-36731EC91318}.Release|Any CPU.Build.0 = Release|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Debug|Any CPU.Build.0 = Debug|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.ActiveCfg = Release|Any CPU - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612}.Release|Any CPU.Build.0 = Release|Any CPU {821A136C-7C6F-44C6-A9E6-C39B5BFB1483}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {821A136C-7C6F-44C6-A9E6-C39B5BFB1483}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {821A136C-7C6F-44C6-A9E6-C39B5BFB1483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -495,8 +463,6 @@ Global {291EF478-BF24-4935-BC78-E0DCCD0C9A1B} = {C40883CD-366D-4534-8B58-3EA0D13136DF} {6E81EF98-8F5C-4EED-8B37-456991CA56FD} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} {F87FD911-4A97-4057-8EAE-1CB96B9A1937} = {C40883CD-366D-4534-8B58-3EA0D13136DF} - {CF73AAA7-ACE9-4C91-AFA5-5EC1DF18FEEE} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} - {D344485F-F8CA-4B02-AF3D-D9C6FD556CA9} = {C40883CD-366D-4534-8B58-3EA0D13136DF} {8A607AC9-E7DD-4B74-A0B1-47FC95B9838B} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} {E22245AF-D5E1-46F6-B443-C886983EC50C} = {C40883CD-366D-4534-8B58-3EA0D13136DF} {22075D5A-E9A1-4E2D-ABA7-8E7CBF2A049E} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} @@ -507,8 +473,6 @@ Global {25DEF6F6-7F99-4EB7-91ED-5181A346AFE1} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} {BF07E947-120D-4E93-93DA-A4BF121753EA} = {C40883CD-366D-4534-8B58-3EA0D13136DF} {1E89A3E9-0A7F-418F-B4BE-6E38A6315373} = {C40883CD-366D-4534-8B58-3EA0D13136DF} - {D23E28F1-CCD0-43E0-8C0D-36731EC91318} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} - {66EFD03D-95B7-4C7E-83AC-1A8BD6C12612} = {C40883CD-366D-4534-8B58-3EA0D13136DF} {821A136C-7C6F-44C6-A9E6-C39B5BFB1483} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} {C3BEF382-C7C4-454D-B017-1EAC03E9A82C} = {C40883CD-366D-4534-8B58-3EA0D13136DF} EndGlobalSection diff --git a/packages/repositories.config b/packages/repositories.config index acac51584..8bede8eeb 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -5,14 +5,12 @@ - - @@ -29,7 +27,6 @@ - @@ -37,7 +34,6 @@ -