From cdea205f4222e56c1c82e4443c36210ae3928126 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 11:35:05 +0100 Subject: [PATCH 01/52] Initial rewrite for version 2 The commit marks to start of version 2.0.0 of the SQL LocalDB API wrapper. It is effectively a rewrite, and is a breaking change to upgrade from 1.x.x. The main changes involve support for .NET Standard 2.0, as well as redesign to better support principles such as dependency injection and extensibility. * All user-facing static types (except for extensions) have been removed. * The NuGet package and the namespaces have been renamed to not be in System. * Configuration is now via classes, not System.Configuration support. * The solution is now compiled using the .NET Core SDK. * Tests are now written in xunit instead of MSTest. * All extensions to parse connection strings for EntityFramework are removed. * Updates to source code to use C# 7.2 features where appropriate. * Various method overloads of old types are now extensions for new types. --- .editorconfig | 2 +- .github/CONTRIBUTING.md | 10 + .github/ISSUE_TEMPLATE.md | 11 + .github/PULL_REQUEST_TEMPLATE.md | 3 + .github/stale.yml | 12 + .travis.yml | 15 +- .vscode/launch.json | 19 + .vscode/tasks.json | 15 + AssemblyVersion.cs | 17 - Build.ps1 | 179 +- changelog.md => CHANGELOG | 5 +- CODE_OF_CONDUCT.md | 74 + CommonAssemblyInfo.cs | 17 +- Directory.Build.props | 44 + Directory.Build.targets | 50 + SqlLocalDb.sln | 61 +- appveyor.yml | 19 +- build.sh | 51 +- contributing.md | 10 - global.json | 5 + .../SqlLocalDbConfigurationSection.cs | 176 -- src/SqlLocalDb/ErrorHelper.cs | 56 - src/SqlLocalDb/EventIds.cs | 295 +++ src/SqlLocalDb/Extensions.cs | 678 ------- src/SqlLocalDb/ILogger.cs | 71 - src/SqlLocalDb/ILoggerExtensions.cs | 710 +++++++ src/SqlLocalDb/ISqlLocalDbApi.cs | 60 +- src/SqlLocalDb/ISqlLocalDbApiExtensions.cs | 204 ++ src/SqlLocalDb/ISqlLocalDbInstance.cs | 102 - src/SqlLocalDb/ISqlLocalDbInstanceInfo.cs | 72 +- .../ISqlLocalDbInstanceInfoExtensions.cs | 69 + src/SqlLocalDb/ISqlLocalDbInstanceManager.cs | 52 + .../ISqlLocalDbInstanceManagerExtensions.cs | 54 + src/SqlLocalDb/ISqlLocalDbProvider.cs | 66 - src/SqlLocalDb/ISqlLocalDbVersionInfo.cs | 32 +- src/SqlLocalDb/Interop/IRegistry.cs | 20 + src/SqlLocalDb/Interop/IRegistryKey.cs | 30 + .../LocalDbInstanceApi.cs} | 434 ++--- .../{ => Interop}/LocalDbInstanceInfo.cs | 36 +- .../{ => Interop}/LocalDbVersionInfo.cs | 25 +- src/SqlLocalDb/Interop/NativeMethods.cs | 74 + .../{ => Interop}/SafeLibraryHandle.cs | 18 +- src/SqlLocalDb/Interop/WindowsRegistry.cs | 20 + src/SqlLocalDb/Interop/WindowsRegistryKey.cs | 46 + src/SqlLocalDb/Logger.cs | 321 ---- .../MartinCostello.SqlLocalDb.csproj | 26 + src/SqlLocalDb/Properties/AssemblyInfo.cs | 18 +- src/SqlLocalDb/SR.Designer.cs | 383 ++-- src/SqlLocalDb/SR.en-GB.resx | 8 +- src/SqlLocalDb/SR.resx | 143 +- src/SqlLocalDb/SRHelper.cs | 36 +- src/SqlLocalDb/SqlLocalDbApi.cs | 784 ++++---- src/SqlLocalDb/SqlLocalDbApiWrapper.cs | 272 --- src/SqlLocalDb/SqlLocalDbConfig.cs | 117 -- src/SqlLocalDb/SqlLocalDbErrors.cs | 133 +- src/SqlLocalDb/SqlLocalDbException.cs | 30 +- src/SqlLocalDb/SqlLocalDbInstance.cs | 369 ---- src/SqlLocalDb/SqlLocalDbInstanceInfo.cs | 82 + src/SqlLocalDb/SqlLocalDbInstanceManager.cs | 165 ++ src/SqlLocalDb/SqlLocalDbOptions.cs | 73 + src/SqlLocalDb/SqlLocalDbProvider.cs | 224 --- src/SqlLocalDb/SqlLocalDbVersionInfo.cs | 47 + src/SqlLocalDb/StopInstanceOptions.cs | 17 +- src/SqlLocalDb/System.Data.SqlLocalDb.csproj | 23 - src/SqlLocalDb/TemporarySqlLocalDbInstance.cs | 313 +-- src/SqlLocalDb/TraceSourceLogger.cs | 265 --- src/TestApp/Log4NetLogger.cs | 44 - .../MartinCostello.SqlLocalDb.TestApp.csproj | 18 + src/TestApp/Program.cs | 66 +- src/TestApp/Properties/AssemblyInfo.cs | 16 +- src/TestApp/Strings.Designer.cs | 17 +- src/TestApp/Strings.resx | 11 +- .../System.Data.SqlLocalDb.TestApp.csproj | 21 - src/TestApp/app.config | 22 - src/TestApp/app.ico | Bin 10134 -> 0 bytes src/TestApp/app.manifest | 26 - stylecop.json | 8 +- tests/SqlLocalDb.Tests/EventIdsTests.cs | 82 + .../ILoggerFactoryExtensions.cs | 45 + .../ISqlLocalDbApiExtensionsTests.cs | 491 +++++ .../ISqlLocalDbInstanceInfoExtensionsTests.cs | 70 + ...qlLocalDbInstanceManagerExtensionsTests.cs | 91 + .../ITestOutputHelperExtensions.cs | 26 + .../MartinCostello.SqlLocalDb.Tests.csproj | 27 + .../RunAsAdminFactAttribute.cs | 58 + tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 61 + .../SqlLocalDbExceptionTests.cs | 153 ++ .../SqlLocalDbInstanceInfoTests.cs | 111 ++ .../SqlLocalDbInstanceManagerTests.cs | 295 +++ .../SqlLocalDbOptionsTests.cs | 40 + .../SqlLocalDbVersionInfoTests.cs | 81 + tests/SqlLocalDb.Tests/XunitLogger.cs | 36 + tests/SqlLocalDb.Tests/XunitLoggerProvider.cs | 30 + tests/SqlLocalDb.Tests/xunit.runner.json | 3 + ...ionSectionTests.DefinedAndSpecified.config | 14 - ...SectionTests.DefinedButNotSpecified.config | 7 - .../SqlLocalDbConfigurationSectionTests.cs | 180 -- tests/SqlLocalDb.UnitTests/Empty.config | 3 - tests/SqlLocalDb.UnitTests/EmptyLogger.cs | 47 - tests/SqlLocalDb.UnitTests/ErrorAssert.cs | 156 -- ...ionsTests.MultipleConnectionStrings.config | 8 - ...ExtensionsTests.NoConnectionStrings.config | 6 - tests/SqlLocalDb.UnitTests/ExtensionsTests.cs | 1154 ----------- .../GetInitialCatalogNameTestCases.xml | 27 - .../GetPhysicalFileNameTestCases.xml | 27 - tests/SqlLocalDb.UnitTests/Helpers.cs | 178 -- .../SqlLocalDb.UnitTests/IntegrationTests.cs | 323 ---- .../LoggerTests.CustomLoggerType.config | 7 - tests/SqlLocalDb.UnitTests/LoggerTests.cs | 177 -- .../NativeMethodsTests.cs | 332 ---- .../Properties/AssemblyInfo.cs | 15 - tests/SqlLocalDb.UnitTests/SRHelperTests.cs | 93 - .../SetInitialCatalogNameTestCases.xml | 35 - .../SetPhysicalFileNameTestCases.xml | 35 - ...ts.AutomaticallyDeleteInstanceFiles.config | 6 - ...DbApiTests.DefaultInstanceName.2012.config | 6 - ...DbApiTests.DefaultInstanceName.2014.config | 6 - ...DbApiTests.DefaultInstanceName.2016.config | 6 - ...alDbApiTests.InvalidOverrideVersion.config | 6 - ...ocalDbApiTests.PropertiesOverridden.config | 13 - .../SqlLocalDbApiTests.cs | 1712 ----------------- ...alDbConfigTests.DefinedAndSpecified.config | 13 - ...finedAndSpecifiedWithLegacySettings.config | 17 - ...bConfigTests.DefinedButNotSpecified.config | 7 - ...edButNotSpecifiedWithLegacySettings.config | 11 - .../SqlLocalDbConfigTests.cs | 120 -- .../SqlLocalDbExceptionTests.cs | 183 -- .../SqlLocalDbInstanceTests.cs | 805 -------- .../SqlLocalDbProviderTests.cs | 543 ------ .../System.Data.SqlLocalDb.UnitTests.csproj | 28 - .../TemporarySqlLocalDbInstanceTests.cs | 680 ------- tests/SqlLocalDb.UnitTests/TestCategories.cs | 50 - tests/SqlLocalDb.UnitTests/TestSetup.cs | 93 - .../TraceSourceLoggerTests.AllDisabled.config | 19 - .../TraceSourceLoggerTests.AllEnabled.config | 19 - ...TraceSourceLoggerTests.SomeDisabled.config | 19 - .../TraceSourceLoggerTests.cs | 206 -- tests/SqlLocalDb.UnitTests/app.config | 29 - 138 files changed, 5193 insertions(+), 12045 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/stale.yml create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json delete mode 100644 AssemblyVersion.cs rename changelog.md => CHANGELOG (99%) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets delete mode 100644 contributing.md create mode 100644 global.json delete mode 100644 src/SqlLocalDb/Configuration/SqlLocalDbConfigurationSection.cs delete mode 100644 src/SqlLocalDb/ErrorHelper.cs create mode 100644 src/SqlLocalDb/EventIds.cs delete mode 100644 src/SqlLocalDb/Extensions.cs delete mode 100644 src/SqlLocalDb/ILogger.cs create mode 100644 src/SqlLocalDb/ILoggerExtensions.cs create mode 100644 src/SqlLocalDb/ISqlLocalDbApiExtensions.cs delete mode 100644 src/SqlLocalDb/ISqlLocalDbInstance.cs create mode 100644 src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs create mode 100644 src/SqlLocalDb/ISqlLocalDbInstanceManager.cs create mode 100644 src/SqlLocalDb/ISqlLocalDbInstanceManagerExtensions.cs delete mode 100644 src/SqlLocalDb/ISqlLocalDbProvider.cs create mode 100644 src/SqlLocalDb/Interop/IRegistry.cs create mode 100644 src/SqlLocalDb/Interop/IRegistryKey.cs rename src/SqlLocalDb/{NativeMethods.cs => Interop/LocalDbInstanceApi.cs} (69%) rename src/SqlLocalDb/{ => Interop}/LocalDbInstanceInfo.cs (82%) rename src/SqlLocalDb/{ => Interop}/LocalDbVersionInfo.cs (75%) create mode 100644 src/SqlLocalDb/Interop/NativeMethods.cs rename src/SqlLocalDb/{ => Interop}/SafeLibraryHandle.cs (60%) create mode 100644 src/SqlLocalDb/Interop/WindowsRegistry.cs create mode 100644 src/SqlLocalDb/Interop/WindowsRegistryKey.cs delete mode 100644 src/SqlLocalDb/Logger.cs create mode 100644 src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj delete mode 100644 src/SqlLocalDb/SqlLocalDbApiWrapper.cs delete mode 100644 src/SqlLocalDb/SqlLocalDbConfig.cs delete mode 100644 src/SqlLocalDb/SqlLocalDbInstance.cs create mode 100644 src/SqlLocalDb/SqlLocalDbInstanceInfo.cs create mode 100644 src/SqlLocalDb/SqlLocalDbInstanceManager.cs create mode 100644 src/SqlLocalDb/SqlLocalDbOptions.cs delete mode 100644 src/SqlLocalDb/SqlLocalDbProvider.cs create mode 100644 src/SqlLocalDb/SqlLocalDbVersionInfo.cs delete mode 100644 src/SqlLocalDb/System.Data.SqlLocalDb.csproj delete mode 100644 src/SqlLocalDb/TraceSourceLogger.cs delete mode 100644 src/TestApp/Log4NetLogger.cs create mode 100644 src/TestApp/MartinCostello.SqlLocalDb.TestApp.csproj delete mode 100644 src/TestApp/System.Data.SqlLocalDb.TestApp.csproj delete mode 100644 src/TestApp/app.config delete mode 100644 src/TestApp/app.ico delete mode 100644 src/TestApp/app.manifest create mode 100644 tests/SqlLocalDb.Tests/EventIdsTests.cs create mode 100644 tests/SqlLocalDb.Tests/ILoggerFactoryExtensions.cs create mode 100644 tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs create mode 100644 tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs create mode 100644 tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs create mode 100644 tests/SqlLocalDb.Tests/ITestOutputHelperExtensions.cs create mode 100644 tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj create mode 100644 tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbExceptionTests.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbOptionsTests.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs create mode 100644 tests/SqlLocalDb.Tests/XunitLogger.cs create mode 100644 tests/SqlLocalDb.Tests/XunitLoggerProvider.cs create mode 100644 tests/SqlLocalDb.Tests/xunit.runner.json delete mode 100644 tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedAndSpecified.config delete mode 100644 tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedButNotSpecified.config delete mode 100644 tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/Empty.config delete mode 100644 tests/SqlLocalDb.UnitTests/EmptyLogger.cs delete mode 100644 tests/SqlLocalDb.UnitTests/ErrorAssert.cs delete mode 100644 tests/SqlLocalDb.UnitTests/ExtensionsTests.MultipleConnectionStrings.config delete mode 100644 tests/SqlLocalDb.UnitTests/ExtensionsTests.NoConnectionStrings.config delete mode 100644 tests/SqlLocalDb.UnitTests/ExtensionsTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/GetInitialCatalogNameTestCases.xml delete mode 100644 tests/SqlLocalDb.UnitTests/GetPhysicalFileNameTestCases.xml delete mode 100644 tests/SqlLocalDb.UnitTests/Helpers.cs delete mode 100644 tests/SqlLocalDb.UnitTests/IntegrationTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/LoggerTests.CustomLoggerType.config delete mode 100644 tests/SqlLocalDb.UnitTests/LoggerTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/NativeMethodsTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/Properties/AssemblyInfo.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SRHelperTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SetInitialCatalogNameTestCases.xml delete mode 100644 tests/SqlLocalDb.UnitTests/SetPhysicalFileNameTestCases.xml delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.AutomaticallyDeleteInstanceFiles.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2012.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2014.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2016.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.InvalidOverrideVersion.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.PropertiesOverridden.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecified.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecifiedWithLegacySettings.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecified.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecifiedWithLegacySettings.config delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbExceptionTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbInstanceTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/SqlLocalDbProviderTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/System.Data.SqlLocalDb.UnitTests.csproj delete mode 100644 tests/SqlLocalDb.UnitTests/TemporarySqlLocalDbInstanceTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/TestCategories.cs delete mode 100644 tests/SqlLocalDb.UnitTests/TestSetup.cs delete mode 100644 tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllDisabled.config delete mode 100644 tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllEnabled.config delete mode 100644 tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.SomeDisabled.config delete mode 100644 tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.cs delete mode 100644 tests/SqlLocalDb.UnitTests/app.config diff --git a/.editorconfig b/.editorconfig index 7028a45a..2a098e24 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,5 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.{csproj,json,props}] +[*.{csproj,json,props,ruleset,targets}] indent_size = 2 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..da8449c6 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,10 @@ +To contribute changes (source code, scripts, configuration) to this repository please follow the steps below. These steps are a guideline for contributing and do not necessarily need to be followed for all changes. + + 1. If you intend to fix a bug please create an issue before forking the repository. + 1. Fork the `master` branch of this repository from the latest commit. + 1. Create a branch from your fork's `master` branch to help isolate your changes from any further work on `master`. If fixing an issue try to reference its name in your branch name (e.g. `issue-123`) to make changes easier to track the changes. + 1. Work on your proposed changes on your fork. If you are fixing an issue include at least one unit test that reproduces it if the code changes to fix it have not been applied; if you are adding new functionality please include unit tests appropriate to the changes you are making. The [code coverage figure](https://codecov.io/gh/martincostello/sqllocaldb) should be maintained where possible. + 1. When you think your changes are complete, test that the code builds cleanly using `Build.ps1`/`build.sh`. There should be no compiler warnings and all tests should pass. + 1. Once your changes build cleanly locally submit a Pull Request back to the `master` branch from your fork's branch. Ideally commits to your branch should be squashed before creating the Pull Request. If the Pull Request fixes an issue please reference it in the title and/or description. Please keep changes focused around a specific topic rather than include multiple types of changes in a single Pull Request. + 1. After your Pull Request is created it will build against the repository's continuous integrations. + 1. Once the Pull Request has been reviewed by the project's [contributors](https://github.com/martincostello/sqllocaldb/graphs/contributors) and the status checks pass your Pull Request will be merged back to the `master` branch, assuming that the changes are deemed appropriate. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..e5de7c07 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +### Expected behaviour + +_Explain what you expected to happen._ + +### Actual behaviour + +_Explain what actually happened. If an exception occurred, please include a stack trace if available._ + +### Steps to reproduce + +_A concise, repeatable, example of how to illustrate the issue._ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..aaabe099 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +_Summarise the changes this Pull Request makes._ + +_Please include a reference to a GitHub issue if appropriate._ diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..4003cebe --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,12 @@ +daysUntilStale: 56 +daysUntilClose: 7 +exemptLabels: + - blocked + - pinned + - security +staleLabel: wontfix +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +closeComment: Closed automatically due to inactivity. diff --git a/.travis.yml b/.travis.yml index a2a89596..e19a4b81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ sudo: required dist: trusty +os: + - linux + - osx + env: global: - - CLI_VERSION=1.0.1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true - NUGET_XMLDOC_MODE=skip @@ -11,6 +14,10 @@ branches: only: - master +cache: + directories: + - /home/travis/.nuget/packages + addons: apt: packages: @@ -20,11 +27,5 @@ addons: - libssl-dev - libunwind8 -install: - - export DOTNET_INSTALL_DIR="$PWD/.dotnetcli" - - curl -sSL https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" - - export PATH="$DOTNET_INSTALL_DIR:$PATH" - - dotnet --info - script: - ./build.sh diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..41a4bfed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "dotnet", + "args": [ + "test" + ], + "cwd": "${workspaceRoot}/tests/MartinCostello.SqlLocalDb.Tests", + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..f30b87c6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet build", + "type": "shell", + "group": "build", + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} diff --git a/AssemblyVersion.cs b/AssemblyVersion.cs deleted file mode 100644 index 6ba90076..00000000 --- a/AssemblyVersion.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// AssemblyVersion.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Reflection; - -[assembly: AssemblyVersion("2.0.0.0")] -[assembly: AssemblyFileVersion("2.0.0.0")] -[assembly: AssemblyInformationalVersion("2.0.0.0")] diff --git a/Build.ps1 b/Build.ps1 index 7f661730..a9967646 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,56 +1,42 @@ param( - [Parameter(Mandatory=$false)][bool] $RestorePackages = $false, - [Parameter(Mandatory=$false)][string] $Configuration = "Release", - [Parameter(Mandatory=$false)][string] $VersionSuffix = "", - [Parameter(Mandatory=$false)][string] $OutputPath = "", - [Parameter(Mandatory=$false)][bool] $PatchVersion = $false, - [Parameter(Mandatory=$false)][bool] $RunTests = $true, - [Parameter(Mandatory=$false)][bool] $CreatePackages = $true + [Parameter(Mandatory = $false)][string] $Configuration = "Release", + [Parameter(Mandatory = $false)][string] $VersionSuffix = "", + [Parameter(Mandatory = $false)][string] $OutputPath = "", + [Parameter(Mandatory = $false)][switch] $SkipTests, + [Parameter(Mandatory = $false)][switch] $DisableCodeCoverage ) $ErrorActionPreference = "Stop" -$solutionPath = Split-Path $MyInvocation.MyCommand.Definition -$solutionFile = Join-Path $solutionPath "SqlLocalDb.sln" -$dotnetVersion = "1.0.1" +$solutionPath = Split-Path $MyInvocation.MyCommand.Definition +$solutionFile = Join-Path $solutionPath "SqlLocalDb.sln" +$sdkFile = Join-Path $solutionPath "global.json" -if ($OutputPath -eq "") { - $OutputPath = Join-Path "$(Convert-Path "$PSScriptRoot")" "artifacts" -} - -$env:DOTNET_INSTALL_DIR = "$(Convert-Path "$PSScriptRoot")\.dotnetcli" - -if ($env:CI -ne $null) { - - $RestorePackages = $true - $PatchVersion = $true - - if (($VersionSuffix -eq "" -and $env:APPVEYOR_REPO_TAG -eq "false" -and $env:APPVEYOR_BUILD_NUMBER -ne "") -eq $true) { +$libraryProject = Join-Path $solutionPath "src\SqlLocalDb\MartinCostello.SqlLocalDb.csproj" +$testProject = Join-Path $solutionPath "tests\SqlLocalDb.Tests\MartinCostello.SqlLocalDb.Tests.csproj" - $LastVersionBuild = (Get-Content ".\releases.txt" | Select-Object -Last 1) - $LastVersion = New-Object -TypeName System.Version -ArgumentList $LastVersionBuild - $ThisVersion = $env:APPVEYOR_BUILD_NUMBER -as [int] +$dotnetVersion = (Get-Content $sdkFile | Out-String | ConvertFrom-Json).sdk.version - if ($LastVersion.Revision -eq 0) { - $ThisBuildNumber = $ThisVersion - $LastVersion.Build - } else { - $ThisBuildNumber = $ThisVersion - $LastVersion.Revision - } - - $VersionSuffix = "beta" + $ThisBuildNumber.ToString("0000") - } +if ($OutputPath -eq "") { + $OutputPath = Join-Path "$(Convert-Path "$PSScriptRoot")" "artifacts" } $installDotNetSdk = $false; -if (((Get-Command "dotnet.exe" -ErrorAction SilentlyContinue) -eq $null) -and ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -eq $null)) { +if (($null -eq (Get-Command "dotnet.exe" -ErrorAction SilentlyContinue)) -and ($null -eq (Get-Command "dotnet" -ErrorAction SilentlyContinue))) { Write-Host "The .NET Core SDK is not installed." $installDotNetSdk = $true } else { - $installedDotNetVersion = (dotnet --version | Out-String).Trim() + Try { + $installedDotNetVersion = (dotnet --version 2>&1 | Out-String).Trim() + } + Catch { + $installedDotNetVersion = "?" + } + if ($installedDotNetVersion -ne $dotnetVersion) { - Write-Host "The required version of the .NET Core SDK is not installed. Expected $dotnetVersion but $installedDotNetVersion was found." + Write-Host "The required version of the .NET Core SDK is not installed. Expected $dotnetVersion." $installDotNetSdk = $true } } @@ -61,29 +47,24 @@ if ($installDotNetSdk -eq $true) { if (!(Test-Path $env:DOTNET_INSTALL_DIR)) { mkdir $env:DOTNET_INSTALL_DIR | Out-Null $installScript = Join-Path $env:DOTNET_INSTALL_DIR "install.ps1" - Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.ps1" -OutFile $installScript + Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/v$dotnetVersion/scripts/obtain/dotnet-install.ps1" -OutFile $installScript -UseBasicParsing & $installScript -Version "$dotnetVersion" -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath } $env:PATH = "$env:DOTNET_INSTALL_DIR;$env:PATH" - $dotnet = Join-Path "$env:DOTNET_INSTALL_DIR" "dotnet" -} else { - $dotnet = "dotnet" + $dotnet = Join-Path "$env:DOTNET_INSTALL_DIR" "dotnet.exe" } - -function DotNetRestore { - param([string]$Project) - & $dotnet restore $Project --verbosity minimal - if ($LASTEXITCODE -ne 0) { - throw "dotnet restore failed with exit code $LASTEXITCODE" - } +else { + $dotnet = "dotnet" } function DotNetBuild { - param([string]$Project, [string]$Configuration, [string]$VersionSuffix) + param([string]$Project) + if ($VersionSuffix) { & $dotnet build $Project --output $OutputPath --configuration $Configuration --version-suffix "$VersionSuffix" - } else { + } + else { & $dotnet build $Project --output $OutputPath --configuration $Configuration } if ($LASTEXITCODE -ne 0) { @@ -91,68 +72,74 @@ function DotNetBuild { } } -function DotNetTest { +function DotNetPack { param([string]$Project) - & $dotnet test $Project - if ($LASTEXITCODE -ne 0) { - throw "dotnet test failed with exit code $LASTEXITCODE" - } -} -function DotNetPack { - param([string]$Project, [string]$Configuration, [string]$VersionSuffix) if ($VersionSuffix) { - & $dotnet pack $Project --output $OutputPath --configuration $Configuration --version-suffix "$VersionSuffix" --include-symbols --include-source - } else { - & $dotnet pack $Project --output $OutputPath --configuration $Configuration --include-symbols --include-source + & $dotnet pack $Project --output $OutputPath --configuration $Configuration --version-suffix "$VersionSuffix" --include-symbols --include-source --no-build + } + else { + & $dotnet pack $Project --output $OutputPath --configuration $Configuration --include-symbols --include-source --no-build } if ($LASTEXITCODE -ne 0) { throw "dotnet pack failed with exit code $LASTEXITCODE" } } -if ($PatchVersion -eq $true) { - - $gitRevision = (git rev-parse HEAD | Out-String).Trim() - $gitBranch = (git rev-parse --abbrev-ref HEAD | Out-String).Trim() - $timestamp = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssK") - - $assemblyVersion = Get-Content ".\AssemblyVersion.cs" -Raw - $assemblyVersionWithMetadata = "{0}[assembly: AssemblyMetadata(""CommitHash"", ""{1}"")]`r`n[assembly: AssemblyMetadata(""CommitBranch"", ""{2}"")]`r`n[assembly: AssemblyMetadata(""BuildTimestamp"", ""{3}"")]" -f $assemblyVersion, $gitRevision, $gitBranch, $timestamp +function DotNetTest { + param([string]$Project) - Set-Content ".\AssemblyVersion.cs" $assemblyVersionWithMetadata -Encoding utf8 -} + if ($DisableCodeCoverage -eq $true) { + & $dotnet test $Project --output $OutputPath --framework $framework --no-build + } + else { -$testProjects = @( - (Join-Path $solutionPath "tests\SqlLocalDb.UnitTests\System.Data.SqlLocalDb.UnitTests.csproj") -) + if ($installDotNetSdk -eq $true) { + $dotnetPath = $dotnet + } + else { + $dotnetPath = (Get-Command "dotnet.exe").Source + } -$packageProjects = @( - (Join-Path $solutionPath "src\SqlLocalDb\System.Data.SqlLocalDb.csproj") -) + $nugetPath = Join-Path $env:USERPROFILE ".nuget\packages" + + $openCoverVersion = "4.6.519" + $openCoverPath = Join-Path $nugetPath "OpenCover\$openCoverVersion\tools\OpenCover.Console.exe" + + $reportGeneratorVersion = "3.1.2" + $reportGeneratorPath = Join-Path $nugetPath "ReportGenerator\$reportGeneratorVersion\tools\ReportGenerator.exe" + + $coverageOutput = Join-Path $OutputPath "code-coverage.xml" + $reportOutput = Join-Path $OutputPath "coverage" + + & $openCoverPath ` + `"-target:$dotnetPath`" ` + `"-targetargs:test $Project --output $OutputPath --no-build`" ` + -output:$coverageOutput ` + -hideskipped:All ` + -mergebyhash ` + -mergeoutput ` + -oldstyle ` + -register:user ` + -skipautoprops ` + `"-filter:+[MartinCostello.SqlLocalDb]* -[MartinCostello.SqlLocalDb.Tests]*`" + + & $reportGeneratorPath ` + `"-reports:$coverageOutput`" ` + `"-targetdir:$reportOutput`" ` + -verbosity:Warning + } -if ($RestorePackages -eq $true) { - Write-Host "Restoring NuGet packages for solution..." -ForegroundColor Green - DotNetRestore $solutionFile + if ($LASTEXITCODE -ne 0) { + throw "dotnet test failed with exit code $LASTEXITCODE" + } } Write-Host "Building solution..." -ForegroundColor Green -DotNetBuild $solutionFile $Configuration $VersionSuffix +DotNetBuild $solutionFile -if ($RunTests -eq $true) { - Write-Host "Testing $($testProjects.Count) project(s)..." -ForegroundColor Green - ForEach ($project in $testProjects) { - DotNetTest $project - } -} - -if ($CreatePackages -eq $true) { - Write-Host "Creating $($packageProjects.Count) package(s)..." -ForegroundColor Green - ForEach ($project in $packageProjects) { - DotNetPack $project $Configuration $VersionSuffix - } -} +Write-Host "Packaging library..." -ForegroundColor Green +DotNetPack $libraryProject -if ($PatchVersion -eq $true) { - Set-Content ".\AssemblyVersion.cs" $assemblyVersion.Trim() -Encoding utf8 -} +Write-Host "Running tests..." -ForegroundColor Green +DotNetTest $testProject diff --git a/changelog.md b/CHANGELOG similarity index 99% rename from changelog.md rename to CHANGELOG index 101ea514..bea97fe8 100644 --- a/changelog.md +++ b/CHANGELOG @@ -139,4 +139,7 @@ Added overloads to support specifying the name of the Initial Catalog using the * Fixed ```TemporarySqlLocalDbInstance``` so it no longer throws an exception when disposed if the temporary SQL LocalDB instance could not be stopped or deleted. * The internals now use the Windows ```LoadLibraryEx``` function instead of ```LoadLibrary```. -# SqlLocalDb v1.16.0.0 +# SqlLocalDb v2.0.0.0 + +## New Features +* TODO diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..c82dee64 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team [through a GitHub issue](https://github.com/justeat/httpclient-interception/issues). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CommonAssemblyInfo.cs b/CommonAssemblyInfo.cs index fd4e7725..f584d28a 100644 --- a/CommonAssemblyInfo.cs +++ b/CommonAssemblyInfo.cs @@ -1,20 +1,11 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// CommonAssemblyInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] -[assembly: AssemblyProduct("System.Data.SqlLocalDb")] [assembly: AssemblyTrademark("")] - +[assembly: CLSCompliant(false)] [assembly: ComVisible(false)] diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..1294b211 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + $(MSBuildThisFileDirectory)SqlLocalDb.snk + Martin Costello + $(MSBuildThisFileDirectory)SqlLocalDb.ruleset + https://github.com/martincostello/sqllocaldb + Martin Costello (c) 2015-$([System.DateTime]::Now.ToString(yyyy)) + true + false + true + latest + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + en-US + $(NoWarn);CA1054;CA2234 + + https://github.com/martincostello/sqllocaldb + $(PackageProjectUrl)/blob/master/LICENSE + See $(PackageProjectUrl)/releases for details. + false + sql;localdb + true + true + git + $(PackageProjectUrl).git + true + true + 2.0.0 + beta$([System.Convert]::ToInt32(`$(APPVEYOR_BUILD_NUMBER)`).ToString(`0000`)) + $(APPVEYOR_REPO_TAG_NAME.Substring($(APPVEYOR_REPO_TAG_NAME.IndexOf(`-`))).Substring(1)) + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 00000000..186acc7a --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,50 @@ + + + $(APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) + $(APPVEYOR_REPO_BRANCH) + $(BUILD_SOURCEBRANCHNAME) + $(TRAVIS_PULL_REQUEST_BRANCH) + $(TRAVIS_BRANCH) + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + + + + + <_Parameter1>BuildTimestamp + <_Parameter2>$([System.DateTime]::UtcNow.ToString(yyyy-MM-ddTHH:mm:ssK)) + + + <_Parameter1>CommitHash + <_Parameter2>$(CommitHash) + + + <_Parameter1>CommitBranch + <_Parameter2>$(CommitBranch) + + + + + + + <_Parameter1>$(CommitHash);$(CommitBranch) + + + + + + + + + diff --git a/SqlLocalDb.sln b/SqlLocalDb.sln index 6f65befa..b7eb63e7 100644 --- a/SqlLocalDb.sln +++ b/SqlLocalDb.sln @@ -10,12 +10,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore .travis.yml = .travis.yml appveyor.yml = appveyor.yml - AssemblyVersion.cs = AssemblyVersion.cs Build.ps1 = Build.ps1 build.sh = build.sh - changelog.md = changelog.md + CHANGELOG = CHANGELOG CommonAssemblyInfo.cs = CommonAssemblyInfo.cs - contributing.md = contributing.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets LICENSE = LICENSE readme.md = readme.md releases.txt = releases.txt @@ -29,11 +29,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2684B19D-7D4 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{278BCCB1-39B2-46DB-9395-7F85995A6132}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Data.SqlLocalDb", "src\SqlLocalDb\System.Data.SqlLocalDb.csproj", "{566977D9-834A-4486-A505-3858A0BDB240}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.SqlLocalDb", "src\SqlLocalDb\MartinCostello.SqlLocalDb.csproj", "{A0521A38-5703-449C-A633-295C3A21373E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Data.SqlLocalDb.TestApp", "src\TestApp\System.Data.SqlLocalDb.TestApp.csproj", "{D4571C51-39F3-4519-9079-040CF8420863}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.SqlLocalDb.TestApp", "src\TestApp\MartinCostello.SqlLocalDb.TestApp.csproj", "{9643FA0A-56FA-481E-94E3-262A3A0EE12F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Data.SqlLocalDb.UnitTests", "tests\SqlLocalDb.UnitTests\System.Data.SqlLocalDb.UnitTests.csproj", "{01B28914-8623-41E1-B8BA-429E476466F8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.SqlLocalDb.Tests", "tests\SqlLocalDb.Tests\MartinCostello.SqlLocalDb.Tests.csproj", "{C80D956D-9878-4955-B89F-C29F9996DD1F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA}" + ProjectSection(SolutionItems) = preProject + .github\CONTRIBUTING.md = .github\CONTRIBUTING.md + .github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md + .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md + .github\stale.yml = .github\stale.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{701E574A-6366-4AF9-9319-237968FA1089}" + ProjectSection(SolutionItems) = preProject + .vscode\launch.json = .vscode\launch.json + .vscode\tasks.json = .vscode\tasks.json + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -41,25 +55,30 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {566977D9-834A-4486-A505-3858A0BDB240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {566977D9-834A-4486-A505-3858A0BDB240}.Debug|Any CPU.Build.0 = Debug|Any CPU - {566977D9-834A-4486-A505-3858A0BDB240}.Release|Any CPU.ActiveCfg = Release|Any CPU - {566977D9-834A-4486-A505-3858A0BDB240}.Release|Any CPU.Build.0 = Release|Any CPU - {D4571C51-39F3-4519-9079-040CF8420863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4571C51-39F3-4519-9079-040CF8420863}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4571C51-39F3-4519-9079-040CF8420863}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4571C51-39F3-4519-9079-040CF8420863}.Release|Any CPU.Build.0 = Release|Any CPU - {01B28914-8623-41E1-B8BA-429E476466F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01B28914-8623-41E1-B8BA-429E476466F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01B28914-8623-41E1-B8BA-429E476466F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01B28914-8623-41E1-B8BA-429E476466F8}.Release|Any CPU.Build.0 = Release|Any CPU + {A0521A38-5703-449C-A633-295C3A21373E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0521A38-5703-449C-A633-295C3A21373E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0521A38-5703-449C-A633-295C3A21373E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0521A38-5703-449C-A633-295C3A21373E}.Release|Any CPU.Build.0 = Release|Any CPU + {9643FA0A-56FA-481E-94E3-262A3A0EE12F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9643FA0A-56FA-481E-94E3-262A3A0EE12F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9643FA0A-56FA-481E-94E3-262A3A0EE12F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9643FA0A-56FA-481E-94E3-262A3A0EE12F}.Release|Any CPU.Build.0 = Release|Any CPU + {C80D956D-9878-4955-B89F-C29F9996DD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C80D956D-9878-4955-B89F-C29F9996DD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C80D956D-9878-4955-B89F-C29F9996DD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C80D956D-9878-4955-B89F-C29F9996DD1F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {566977D9-834A-4486-A505-3858A0BDB240} = {2684B19D-7D49-4099-8BD2-4D281455EB29} - {D4571C51-39F3-4519-9079-040CF8420863} = {2684B19D-7D49-4099-8BD2-4D281455EB29} - {01B28914-8623-41E1-B8BA-429E476466F8} = {278BCCB1-39B2-46DB-9395-7F85995A6132} + {A0521A38-5703-449C-A633-295C3A21373E} = {2684B19D-7D49-4099-8BD2-4D281455EB29} + {9643FA0A-56FA-481E-94E3-262A3A0EE12F} = {2684B19D-7D49-4099-8BD2-4D281455EB29} + {C80D956D-9878-4955-B89F-C29F9996DD1F} = {278BCCB1-39B2-46DB-9395-7F85995A6132} + {D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA} = {E207A447-68A1-4D72-B24F-89FB7890AE12} + {701E574A-6366-4AF9-9319-237968FA1089} = {E207A447-68A1-4D72-B24F-89FB7890AE12} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3B9E157C-5E92-4357-B233-281B4530EABD} EndGlobalSection EndGlobal diff --git a/appveyor.yml b/appveyor.yml index d8c916e0..5b2296d0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,7 @@ -os: Visual Studio 2017 +os: Visual Studio 2017 version: 2.0.0.{build} environment: - CLI_VERSION: 1.0.1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true NUGET_XMLDOC_MODE: skip @@ -10,19 +9,17 @@ branches: only: - master -assembly_info: - patch: true - file: AssemblyVersion.cs - assembly_version: "2.0.0.0" - assembly_file_version: "{version}" - assembly_informational_version: "{version}" - build_script: - ps: .\Build.ps1 +after_build: + - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" + - pip install codecov + - codecov -f "artifacts\code-coverage.xml" + artifacts: - path: artifacts\*.nupkg - - path: artifacts\System.Data.SqlLocalDb.TestApp.* + - path: artifacts\MartinCostello.SqlLocalDb.TestApp.* nuget: disable_publish_on_pr: true @@ -34,7 +31,7 @@ deploy: - provider: NuGet api_key: - secure: OKbODD04RIoauopeXve4FsbtYb6wreSRms0k3SnH7b/Gj/6cQhVQHhu2xMjoSlQc + secure: AUZFfhSn20XPUPIzbbt2x3xoa/5vdAVcYFVz7JkQYkYJYWbrIyz3chgTjEhI9Gs2 artifact: /.*\.nupkg/ skip_symbols: false on: diff --git a/build.sh b/build.sh index 9c9626de..0b17e834 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,46 @@ -#!/bin/sh -export artifacts=$(dirname "$(readlink -f "$0")")/artifacts -export configuration=Release +#!/usr/bin/env bash -dotnet restore SqlLocalDb.sln --verbosity minimal || exit 1 -dotnet build src/SqlLocalDb/System.Data.SqlLocalDb.csproj --output $artifacts --configuration $configuration --framework "netstandard1.6" || exit 1 -dotnet test tests/SqlLocalDb.Tests/System.Data.SqlLocalDb.UnitTests.csproj --output $artifacts --configuration $configuration --framework "netcoreapp1.1" || exit 1 +root=$(cd "$(dirname "$0")"; pwd -P) +artifacts=$root/artifacts +configuration=Release +skipTests=0 + +while :; do + if [ $# -le 0 ]; then + break + fi + + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + -\?|-h|--help) + echo "./build.sh [--skip-tests]" + exit 1 + ;; + + --skip-tests) + skipTests=1 + ;; + + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" + ;; + esac + + shift +done + +export CLI_VERSION=`cat ./global.json | grep -E '[0-9]\.[0-9]\.[a-zA-Z0-9\-]*' -o` +export DOTNET_INSTALL_DIR="$root/.dotnetcli" +export PATH="$DOTNET_INSTALL_DIR:$PATH" + +dotnet_version=$(dotnet --version) + +if [ "$dotnet_version" != "$CLI_VERSION" ]; then + curl -sSL https://raw.githubusercontent.com/dotnet/cli/v$CLI_VERSION/scripts/obtain/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" +fi + +dotnet publish ./src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj --output $artifacts/publish --configuration $configuration || exit 1 + +if [ $skipTests == 0 ]; then + dotnet test ./tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj --output $artifacts --configuration $configuration || exit 1 +fi diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 0ecdc50a..00000000 --- a/contributing.md +++ /dev/null @@ -1,10 +0,0 @@ -To contribute changes (source code, scripts, configuration) to this repository please follow the steps below. These steps are a guideline for contributing and do not necessarily need to be followed for all changes. - - 1. If you intend to fix a bug please create an issue before forking the repository. - 1. Fork the ```master``` branch of this repository from the latest commit. - 1. Create a branch from your fork's ```master``` branch to help isolate your changes from any further work on ```master```. If fixing an issue try to reference its name in your branch name (e.g. ```issue-123```) to make changes easier to track the changes. - 1. Work on your proposed changes on your fork. If you are fixing an issue include at least one unit test that reproduces it if the code changes to fix it have not been applied; if you are adding new functionality please include unit tests appropriate to the changes you are making. The [code coverage figure](https://coveralls.io/r/martincostello/sqllocaldb) should be maintained where possible. - 1. When you think your changes are complete, test that the code builds cleanly using ```Build.cmd```. There should be no compiler, StyleCop or FxCop warnings and all tests should pass. - 1. Once your changes build cleanly locally submit a Pull Request back to the ```master``` branch from your fork's branch. Ideally commits to your branch should be squashed before creating the Pull Request. If the Pull Request fixes an issue please reference it in the title and/or description. Please keep changes focused around a specific topic rather than include multiple types of changes in a single Pull Request. - 1. After your Pull Request is created it will build against the repository's [continuous integration](https://ci.appveyor.com/project/martincostello/sqllocaldb). - 1. Once the Pull Request has been reviewed by the project's [contributors](https://github.com/martincostello/sqllocaldb/graphs/contributors) and the CI build is successful your Pull Request will be merged back to the ```master``` branch, assuming that the changes are deemed appropriate. diff --git a/global.json b/global.json new file mode 100644 index 00000000..1208561d --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "2.1.302" + } +} diff --git a/src/SqlLocalDb/Configuration/SqlLocalDbConfigurationSection.cs b/src/SqlLocalDb/Configuration/SqlLocalDbConfigurationSection.cs deleted file mode 100644 index bbc7f632..00000000 --- a/src/SqlLocalDb/Configuration/SqlLocalDbConfigurationSection.cs +++ /dev/null @@ -1,176 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbConfigurationSection.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.ComponentModel; -using System.Configuration; -using System.Globalization; - -namespace System.Data.SqlLocalDb.Configuration -{ - /// - /// A class representing the configuration section for the System.Data.SqlLocalDb assembly. - /// This class cannot be inherited. - /// - public sealed class SqlLocalDbConfigurationSection : ConfigurationSection - { - /// - /// The name of the SQL LocalDB configuration section. - /// - public const string SectionName = "system.data.sqlLocalDb"; - - /// - /// The name of the configuration attribute. - /// - private const string AutomaticallyDeleteInstanceFilesAttributeName = "automaticallyDeleteInstanceFiles"; - - /// - /// The name of the configuration attribute. - /// - private const string LanguageAttributeName = "language"; - - /// - /// The name of the configuration attribute. - /// - private const string LoggerTypeAttributeName = "loggerType"; - - /// - /// The name of the configuration attribute. - /// - private const string NativeApiOverrideVersionAttributeName = "overrideVersion"; - - /// - /// The name of the configuration attribute. - /// - private const string StopOptionsAttributeName = "stopOptions"; - - /// - /// The name of the configuration attribute. - /// - private const string StopTimeoutAttributeName = "stopTimeout"; - - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbConfigurationSection() - : base() - { - } - - /// - /// Gets or sets a value indicating whether to automatically delete the - /// files associated with SQL LocalDB instances when they are deleted. - /// - [ConfigurationProperty(AutomaticallyDeleteInstanceFilesAttributeName, IsRequired = false, DefaultValue = false)] - public bool AutomaticallyDeleteInstanceFiles - { - get { return (bool)base[AutomaticallyDeleteInstanceFilesAttributeName]; } - set { base[AutomaticallyDeleteInstanceFilesAttributeName] = value; } - } - - /// - /// Gets or sets the override language to use to format error messages. - /// - [ConfigurationProperty(LanguageAttributeName, IsRequired = false, DefaultValue = null)] - [TypeConverter(typeof(CultureInfoConverter))] - public CultureInfo Language - { - get { return base[LanguageAttributeName] as CultureInfo; } - set { base[LanguageAttributeName] = value; } - } - - /// - /// Gets or sets the type of the implementation to use, if any. - /// - [ConfigurationProperty(LoggerTypeAttributeName, IsRequired = false, DefaultValue = null)] - [SubclassTypeValidator(typeof(ILogger))] - [TypeConverter(typeof(TypeNameConverter))] - public Type LoggerType - { - get { return base[LoggerTypeAttributeName] as Type; } - set { base[LoggerTypeAttributeName] = value; } - } - - /// - /// Gets or sets the override version string of the native SQL LocalDB API to load, if any. - /// - [ConfigurationProperty(NativeApiOverrideVersionAttributeName, IsRequired = false, DefaultValue = "")] - public string NativeApiOverrideVersion - { - get { return base[NativeApiOverrideVersionAttributeName] as string; } - set { base[NativeApiOverrideVersionAttributeName] = value; } - } - - /// - /// Gets or sets the options to use when stopping instances of SQL LocalDB. - /// - [ConfigurationProperty(StopOptionsAttributeName, IsRequired = false, DefaultValue = StopInstanceOptions.None)] - public StopInstanceOptions StopOptions - { - get { return (StopInstanceOptions)base[StopOptionsAttributeName]; } - set { base[StopOptionsAttributeName] = value; } - } - - /// - /// Gets or sets the default timeout to use when stopping instances of SQL LocalDB. - /// - [ConfigurationProperty(StopTimeoutAttributeName, IsRequired = false, DefaultValue = "00:01:00")] - [TimeSpanValidator(MinValueString = "00:00:00")] - public TimeSpan StopTimeout - { - get { return (TimeSpan)base[StopTimeoutAttributeName]; } - set { base[StopTimeoutAttributeName] = value; } - } - - /// - /// Gets a value indicating whether is set by the application configuration file. - /// - internal bool IsAutomaticallyDeleteInstanceFilesSpecified => IsPropertySetInConfigurationFile(AutomaticallyDeleteInstanceFilesAttributeName); - - /// - /// Gets a value indicating whether is set by the application configuration file. - /// - internal bool IsNativeApiOverrideVersionSpecified => IsPropertySetInConfigurationFile(NativeApiOverrideVersionAttributeName); - - /// - /// Returns the from the current application configuration file. - /// - /// - /// An instance of read from the current application configuration file. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires loading the entire configuration file, which may be non-trivial.")] - public static SqlLocalDbConfigurationSection GetSection() - { - SqlLocalDbConfigurationSection section = ConfigurationManager.GetSection(SectionName) as SqlLocalDbConfigurationSection; - - if (section == null) - { - section = new SqlLocalDbConfigurationSection(); - } - - return section; - } - - /// - /// Returns whether the configuration property with the specified name gets its - /// value from it being explicitly set by the user in the application configuration file. - /// - /// The name of the configuration attribute. - /// - /// if the configuration property value is defined in the - /// application configuration file; otherwise . - /// - private bool IsPropertySetInConfigurationFile(string propertyName) => ElementInformation.Properties[propertyName].ValueOrigin == PropertyValueOrigin.SetHere; - } -} diff --git a/src/SqlLocalDb/ErrorHelper.cs b/src/SqlLocalDb/ErrorHelper.cs deleted file mode 100644 index 537b3cad..00000000 --- a/src/SqlLocalDb/ErrorHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ErrorHelper.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing helper methods for error handling. This class cannot be inherited. - /// - internal static class ErrorHelper - { - /// - /// Determines whether the specified exception is fatal. - /// - /// The exception to test. - /// - /// if the specified exception is fatal; otherwise, . - /// - internal static bool IsFatal(Exception exception) - { - while (exception != null) - { - if (((exception is OutOfMemoryException) && !(exception is InsufficientMemoryException)) || - (((exception is ThreadAbortException) || - (exception is AccessViolationException)) || - (exception is StackOverflowException) || - (exception is SEHException))) - { - return true; - } - - if (!(exception is TypeInitializationException) && !(exception is TargetInvocationException)) - { - break; - } - - exception = exception.InnerException; - } - - return false; - } - } -} diff --git a/src/SqlLocalDb/EventIds.cs b/src/SqlLocalDb/EventIds.cs new file mode 100644 index 00000000..ae1e214b --- /dev/null +++ b/src/SqlLocalDb/EventIds.cs @@ -0,0 +1,295 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Logging; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing trace event Ids. This class cannot be inherited. + /// + internal static class EventIds + { + //// Add new events to the end of the class to preserve the Ids. + + /// + /// The for when the SQL LocalDB Instance API is loaded. This field is read-only. + /// + internal static readonly EventId NativeApiLoaded = new EventId(++Id, nameof(NativeApiLoaded)); + + /// + /// The for when the SQL LocalDB Instance API fails to load. This field is read-only. + /// + internal static readonly EventId NativeApiLoadFailed = new EventId(++Id, nameof(NativeApiLoadFailed)); + + /// + /// The for when the SQL LocalDB Instance API is not loaded. This field is read-only. + /// + internal static readonly EventId NativeApiNotLoaded = new EventId(++Id, nameof(NativeApiNotLoaded)); + + /// + /// The for when the SQL LocalDB Instance API version is overridden. This field is read-only. + /// + internal static readonly EventId NativeApiVersionOverriddenByUser = new EventId(++Id, nameof(NativeApiVersionOverriddenByUser)); + + /// + /// The for when the SQL LocalDB Instance API version specified as an override cannot be found. This field is read-only. + /// + internal static readonly EventId NativeApiVersionOverrideNotFound = new EventId(++Id, nameof(NativeApiVersionOverrideNotFound)); + + /// + /// The for when the SQL LocalDB Instance API cannot be found. This field is read-only. + /// + internal static readonly EventId NoNativeApiFound = new EventId(++Id, nameof(NoNativeApiFound)); + + /// + /// The for when the SQL LocalDB Instance API path configured in the registry cannot be found. This field is read-only. + /// + internal static readonly EventId NativeApiPathNotFound = new EventId(++Id, nameof(NativeApiPathNotFound)); + + /// + /// The for when a native function export from the SQL LocalDB Instance API cannot be found. This field is read-only. + /// + internal static readonly EventId NativeFunctionNotFound = new EventId(++Id, nameof(NativeFunctionNotFound)); + + /// + /// The for when the SQL LocalDB Instance API is not installed. This field is read-only. + /// + internal static readonly EventId NotInstalled = new EventId(++Id, nameof(NotInstalled)); + + /// + /// The for when a SQL LocalDB instance is being created. This field is read-only. + /// + internal static readonly EventId CreatingInstance = new EventId(++Id, nameof(CreatingInstance)); + + /// + /// The for when creating a SQL LocalDB instance fails. This field is read-only. + /// + internal static readonly EventId CreatingInstanceFailed = new EventId(++Id, nameof(CreatingInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance has been created. This field is read-only. + /// + internal static readonly EventId CreatedInstance = new EventId(++Id, nameof(CreatedInstance)); + + /// + /// The for when a SQL LocalDB instance is being deleted. This field is read-only. + /// + internal static readonly EventId DeletingInstance = new EventId(++Id, nameof(DeletingInstance)); + + /// + /// The for when deleting a SQL LocalDB instance fails. This field is read-only. + /// + internal static readonly EventId DeletingInstanceFailed = new EventId(++Id, nameof(DeletingInstanceFailed)); + + /// + /// The for when deleting a SQL LocalDB instance fails because it cannot be found. This field is read-only. + /// + internal static readonly EventId DeletingInstanceFailedAsCannotBeNotFound = new EventId(++Id, nameof(DeletingInstanceFailedAsCannotBeNotFound)); + + /// + /// The for when deleting a SQL LocalDB instance fails because it is in use. This field is read-only. + /// + internal static readonly EventId DeletingInstanceFailedAsInUse = new EventId(++Id, nameof(DeletingInstanceFailedAsInUse)); + + /// + /// The for when a SQL LocalDB instance has been deleted. This field is read-only. + /// + internal static readonly EventId DeletedInstance = new EventId(++Id, nameof(DeletedInstance)); + + /// + /// The for when the files for a SQL LocalDB instance are being deleted. This field is read-only. + /// + internal static readonly EventId DeletingInstanceFiles = new EventId(++Id, nameof(DeletingInstanceFiles)); + + /// + /// The for when the files for a SQL LocalDB instance fail to be deleted. This field is read-only. + /// + internal static readonly EventId DeletingInstanceFilesFailed = new EventId(++Id, nameof(DeletingInstanceFilesFailed)); + + /// + /// The for when the files for a SQL LocalDB instance have been deleted. This field is read-only. + /// + internal static readonly EventId DeletedInstanceFiles = new EventId(++Id, nameof(DeletedInstanceFiles)); + + /// + /// The for when getting information about a SQL LocalDB instance. This field is read-only. + /// + internal static readonly EventId GettingInstanceInfo = new EventId(++Id, nameof(GettingInstanceInfo)); + + /// + /// The for when getting information about a SQL LocalDB instance fails. This field is read-only. + /// + internal static readonly EventId GettingInstanceInfoFailed = new EventId(++Id, nameof(GettingInstanceInfoFailed)); + + /// + /// The for when information about a SQL LocalDB instance was retrieved. This field is read-only. + /// + internal static readonly EventId GotInstanceInfo = new EventId(++Id, nameof(GotInstanceInfo)); + + /// + /// The for when getting SQL LocalDB instance names. This field is read-only. + /// + internal static readonly EventId GettingInstanceNames = new EventId(++Id, nameof(GettingInstanceNames)); + + /// + /// The for when getting SQL LocalDB instance names fails. This field is read-only. + /// + internal static readonly EventId GettingInstanceNamesFailed = new EventId(++Id, nameof(GettingInstanceNamesFailed)); + + /// + /// The for when SQL LocalDB instance names are retrieved. This field is read-only. + /// + internal static readonly EventId GotInstanceNames = new EventId(++Id, nameof(GotInstanceNames)); + + /// + /// The for when getting information about a SQL LocalDB version. This field is read-only. + /// + internal static readonly EventId GettingVersionInfo = new EventId(++Id, nameof(GettingVersionInfo)); + + /// + /// The for when getting information about a SQL LocalDB version fails. This field is read-only. + /// + internal static readonly EventId GettingVersionInfoFailed = new EventId(++Id, nameof(GettingVersionInfoFailed)); + + /// + /// The for when information about a SQL LocalDB version was retrieved. This field is read-only. + /// + internal static readonly EventId GotVersionInfo = new EventId(++Id, nameof(GotVersionInfo)); + + /// + /// The for when getting SQL LocalDB versions. This field is read-only. + /// + internal static readonly EventId GettingVersions = new EventId(++Id, nameof(GettingVersions)); + + /// + /// The for when getting SQL LocalDB versions fails. This field is read-only. + /// + internal static readonly EventId GettingVersionsFailed = new EventId(++Id, nameof(GettingVersionsFailed)); + + /// + /// The for when SQL LocalDB instance versions are retrieved. This field is read-only. + /// + internal static readonly EventId GotVersions = new EventId(++Id, nameof(GotVersions)); + + /// + /// The for when a specified Language Id is invalid. This field is read-only. + /// + internal static readonly EventId InvalidLanguageId = new EventId(++Id, nameof(InvalidLanguageId)); + + /// + /// The for when a specified registry key name is invalid. This field is read-only. + /// + internal static readonly EventId InvalidRegistryKey = new EventId(++Id, nameof(InvalidRegistryKey)); + + /// + /// The for when a registry key cannot be found. This field is read-only. + /// + internal static readonly EventId RegistryKeyNotFound = new EventId(++Id, nameof(RegistryKeyNotFound)); + + /// + /// The for when a SQL LocalDB instance is starting. This field is read-only. + /// + internal static readonly EventId StartingInstance = new EventId(++Id, nameof(StartingInstance)); + + /// + /// The for when a SQL LocalDB instance fails to start. This field is read-only. + /// + internal static readonly EventId StartingInstanceFailed = new EventId(++Id, nameof(StartingInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance has started. This field is read-only. + /// + internal static readonly EventId StartedInstance = new EventId(++Id, nameof(StartedInstance)); + + /// + /// The for when a SQL LocalDB instance is stopping. This field is read-only. + /// + internal static readonly EventId StoppingInstance = new EventId(++Id, nameof(StoppingInstance)); + + /// + /// The for when a SQL LocalDB instance fails to stop. This field is read-only. + /// + internal static readonly EventId StoppingInstanceFailed = new EventId(++Id, nameof(StoppingInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance has stopped. This field is read-only. + /// + internal static readonly EventId StoppedInstance = new EventId(++Id, nameof(StoppedInstance)); + + /// + /// The for when SQL LocalDB API tracing is starting. This field is read-only. + /// + internal static readonly EventId StartingTracing = new EventId(++Id, nameof(StartingTracing)); + + /// + /// The for when tracing for SQL LocalDB API fails to start. This field is read-only. + /// + internal static readonly EventId StartingTracingFailed = new EventId(++Id, nameof(StartingTracingFailed)); + + /// + /// The for when SQL LocalDB API tracing is started. This field is read-only. + /// + internal static readonly EventId StartedTracing = new EventId(++Id, nameof(StartedTracing)); + + /// + /// The for when SQL LocalDB API tracing is stopping. This field is read-only. + /// + internal static readonly EventId StoppedTracing = new EventId(++Id, nameof(StoppedTracing)); + + /// + /// The for when tracing for SQL LocalDB API fails to stop. This field is read-only. + /// + internal static readonly EventId StoppingTracingFailed = new EventId(++Id, nameof(StoppingTracingFailed)); + + /// + /// The for when SQL LocalDB API tracing is stopped. This field is read-only. + /// + internal static readonly EventId StoppingTracing = new EventId(++Id, nameof(StoppingTracing)); + + /// + /// The for when a temporary SQL LocalDB API instance fails to stop. This field is read-only. + /// + internal static readonly EventId StopTemporaryInstanceFailed = new EventId(++Id, nameof(StopTemporaryInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance is being shared. This field is read-only. + /// + internal static readonly EventId SharingInstance = new EventId(++Id, nameof(SharingInstance)); + + /// + /// The for when sharing a SQL LocalDB instance fails. This field is read-only. + /// + internal static readonly EventId SharingInstanceFailed = new EventId(++Id, nameof(SharingInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance has been shared. This field is read-only. + /// + internal static readonly EventId SharedInstance = new EventId(++Id, nameof(SharedInstance)); + + /// + /// The for when a SQL LocalDB instance is being unshared. This field is read-only. + /// + internal static readonly EventId UnsharingInstance = new EventId(++Id, nameof(UnsharingInstance)); + + /// + /// The for when unsharing a SQL LocalDB instance fails. This field is read-only. + /// + internal static readonly EventId UnsharingInstanceFailed = new EventId(++Id, nameof(UnsharingInstanceFailed)); + + /// + /// The for when a SQL LocalDB instance has been unshared. This field is read-only. + /// + internal static readonly EventId UnsharedInstance = new EventId(++Id, nameof(UnsharedInstance)); + + /// + /// The base Id for the event Ids. + /// + private const int BaseId = 0; + + /// + /// Gets or sets the current Id for assigning event Ids. + /// + private static int Id { get; set; } = BaseId; + } +} diff --git a/src/SqlLocalDb/Extensions.cs b/src/SqlLocalDb/Extensions.cs deleted file mode 100644 index 387adb73..00000000 --- a/src/SqlLocalDb/Extensions.cs +++ /dev/null @@ -1,678 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// Extensions.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; -using System.Data.Common; -using System.Data.SqlClient; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing extension methods for use with SQL LocalDB instances. This class cannot be inherited. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class Extensions - { - /// - /// The connection string keyword for the attached database file name. - /// - private const string AttachDBFilenameKeywordName = "AttachDBFilename"; - - /// - /// The assembly-qualified name of the type to use for entity connection string builders. - /// - private const string EntityConnectionStringBuilderTypeName = "System.Data.EntityClient.EntityConnectionStringBuilder, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; - - /// - /// The assembly-qualified name of the type to use for entity connections. - /// - private const string EntityConnectionTypeName = "System.Data.EntityClient.EntityConnection, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; - - /// - /// The connection string keyword for the Initial Catalog. - /// - private const string InitialCatalogKeywordName = "Initial Catalog"; - - /// - /// The connection string keyword for the Provider Connection String. - /// - private const string ProviderConnectionStringKeywordName = "Provider Connection String"; - - /// - /// The for the type with the name specified by . - /// - private static Type _entityConnectionStringBuilderType; - - /// - /// The for the type with the name specified by . - /// - private static Type _entityConnectionType; - - /// - /// The for the ProviderConnectionString property of the type specified by . - /// - private static PropertyInfo _providerConnectionStringProperty; - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance - /// using the only connection string configured in the current application configuration file. - /// - /// The to get the connection string builder for. - /// - /// The created instance of . - /// - /// - /// is . - /// - /// - /// No connection strings are configured in the application configuration file or more than one - /// connection string is configured in the application configuration file. - /// - /// - /// Connection strings may be inherited from outside the current application's configuration file, such as from - /// machine.config. To use this overload it is recommended that, unless otherwise needed, a <clear/> - /// element is added to the <connectionStrings> section of your application configuration file to - /// prevent the default connection strings from being inherited. - /// - public static DbConnection GetConnectionForDefaultModel(this ISqlLocalDbInstance instance) - { - DbConnectionStringBuilder builder = instance.GetConnectionStringForDefaultModel(); - return CreateConnection(builder.ConnectionString); - } - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance - /// using the only connection string configured in the current application configuration file. - /// - /// The to get the connection string builder for. - /// The optional name to use for the Initial Catalog in the provider connection string to override the default, if present. - /// - /// The created instance of . - /// - /// - /// is . - /// - /// - /// No connection strings are configured in the application configuration file or more than one - /// connection string is configured in the application configuration file. - /// - /// - /// Connection strings may be inherited from outside the current application's configuration file, such as from - /// machine.config. To use this overload it is recommended that, unless otherwise needed, a <clear/> - /// element is added to the <connectionStrings> section of your application configuration file to - /// prevent the default connection strings from being inherited. - /// - public static DbConnection GetConnectionForDefaultModel(this ISqlLocalDbInstance instance, string initialCatalog) - { - DbConnectionStringBuilder builder = instance.GetConnectionStringForDefaultModel(initialCatalog); - return CreateConnection(builder.ConnectionString); - } - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance. - /// - /// The to get the connection string builder for. - /// The name of the connection string for the model in the application configuration file. - /// - /// The created instance of . - /// - /// - /// No connection string with the name specified by can be found in the application configuration file. - /// - /// - /// is . - /// - public static DbConnection GetConnectionForModel(this ISqlLocalDbInstance instance, string modelConnectionStringName) - { - DbConnectionStringBuilder builder = instance.GetConnectionStringForModel(modelConnectionStringName); - return CreateConnection(builder.ConnectionString); - } - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance. - /// - /// The to get the connection string builder for. - /// The name of the connection string for the model in the application configuration file. - /// The optional name to use for the Initial Catalog in the provider connection string to override the default, if present. - /// - /// The created instance of . - /// - /// - /// No connection string with the name specified by can be found in the application configuration file. - /// - /// - /// is . - /// - public static DbConnection GetConnectionForModel(this ISqlLocalDbInstance instance, string modelConnectionStringName, string initialCatalog) - { - DbConnectionStringBuilder builder = instance.GetConnectionStringForModel(modelConnectionStringName, initialCatalog); - return CreateConnection(builder.ConnectionString); - } - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance - /// using the only connection string configured in the current application configuration file. - /// - /// The to get the connection string builder for. - /// - /// The created instance of . - /// - /// - /// is . - /// - /// - /// No connection strings are configured in the application configuration file or more than one - /// connection string is configured in the application configuration file. - /// - /// - /// Connection strings may be inherited from outside the current application's configuration file, such as from - /// machine.config. To use this overload it is recommended that, unless otherwise needed, a <clear/> - /// element is added to the <connectionStrings> section of your application configuration file to - /// prevent the default connection strings from being inherited. - /// - public static DbConnectionStringBuilder GetConnectionStringForDefaultModel(this ISqlLocalDbInstance instance) => instance.GetConnectionStringForDefaultModel(null); - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance - /// using the only connection string configured in the current application configuration file. - /// - /// The to get the connection string builder for. - /// The optional name to use for the Initial Catalog in the provider connection string to override the default, if present. - /// - /// The created instance of . - /// - /// - /// is . - /// - /// - /// No connection strings are configured in the application configuration file, more than one - /// connection string is configured in the application configuration file or the value of the - /// property of is - /// or the empty string. - /// - /// - /// Connection strings may be inherited from outside the current application's configuration file, such as from - /// machine.config. To use this overload it is recommended that, unless otherwise needed, a <clear/> - /// element is added to the <connectionStrings> section of your application configuration file to - /// prevent the default connection strings from being inherited. - /// - public static DbConnectionStringBuilder GetConnectionStringForDefaultModel(this ISqlLocalDbInstance instance, string initialCatalog) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - var connectionStrings = ConfigurationManager.ConnectionStrings - .OfType() - .ToList(); - - if (connectionStrings.Count < 1) - { - throw new InvalidOperationException(SR.Extensions_NoConnectionStrings); - } - else if (connectionStrings.Count > 1) - { - throw new InvalidOperationException(SR.Extensions_NoSingleConnectionString); - } - - return CreateBuilder(connectionStrings[0], instance.NamedPipe, initialCatalog); - } - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance. - /// - /// The to get the connection string builder for. - /// The name of the connection string for the model in the application configuration file. - /// - /// The created instance of . - /// - /// - /// No connection string with the name specified by can be found in the application configuration file. - /// - /// - /// is . - /// - public static DbConnectionStringBuilder GetConnectionStringForModel(this ISqlLocalDbInstance instance, string modelConnectionStringName) => instance.GetConnectionStringForModel(modelConnectionStringName, null); - - /// - /// Creates an instance of that can be used to connect to the SQL LocalDB instance. - /// - /// The to get the connection string builder for. - /// The name of the connection string for the model in the application configuration file. - /// The optional name to use for the Initial Catalog in the provider connection string to override the default, if present. - /// - /// The created instance of . - /// - /// - /// No connection string with the name specified by can be found in the application configuration file. - /// - /// - /// is . - /// - /// - /// The value of the property of is or the empty string. - /// - public static DbConnectionStringBuilder GetConnectionStringForModel(this ISqlLocalDbInstance instance, string modelConnectionStringName, string initialCatalog) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - var connectionStringSettings = ConfigurationManager.ConnectionStrings - .OfType() - .Where((p) => string.Equals(p.Name, modelConnectionStringName, StringComparison.Ordinal)) - .FirstOrDefault(); - - if (connectionStringSettings == null) - { - throw new ArgumentException(SRHelper.Format(SR.Extensions_NoConnectionStringFormat, modelConnectionStringName), nameof(modelConnectionStringName)); - } - - return CreateBuilder(connectionStringSettings, instance.NamedPipe, initialCatalog); - } - - /// - /// Gets the default SQL Local DB instance. - /// - /// The to use to get the default instance. - /// - /// The default SQL Local DB instance. - /// - /// - /// is . - /// - public static ISqlLocalDbInstance GetDefaultInstance(this ISqlLocalDbProvider value) => value.GetOrCreateInstance(SqlLocalDbApi.DefaultInstanceName); - - /// - /// Gets the Initial Catalog name from the specified , if present. - /// - /// The to extract the Initial Catalog name from. - /// - /// The name of the Initial Catalog present in , if any; otherwise . - /// - /// - /// is . - /// - public static string GetInitialCatalogName(this DbConnectionStringBuilder value) => value.ExtractStringValueFromConnectionString(InitialCatalogKeywordName); - - /// - /// Gets the physical file name from the specified , if present. - /// - /// The to extract the physical file name from. - /// - /// The name of the physical file name present in , if any; otherwise . - /// - /// - /// is . - /// - public static string GetPhysicalFileName(this DbConnectionStringBuilder value) => value.ExtractStringValueFromConnectionString(AttachDBFilenameKeywordName); - - /// - /// Gets an SQL Local DB instance with the specified name if it exists, otherwise a new instance with the specified name is created. - /// - /// The to use to get or create the instance. - /// The name of the SQL Server LocalDB instance to get or create. - /// - /// An SQL Local DB instance with the name specified by . - /// - /// - /// or is . - /// - /// - /// returns when queried for instances. - /// - public static ISqlLocalDbInstance GetOrCreateInstance(this ISqlLocalDbProvider value, string instanceName) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (instanceName == null) - { - throw new ArgumentNullException(nameof(instanceName)); - } - - bool instanceExists = false; - - if (SqlLocalDbApi.IsDefaultInstanceName(instanceName)) - { - // The default instance is always listed, even if it does not exist, - // so need to query that separately to verify whether to get or create. - instanceExists = SqlLocalDbApi.GetInstanceInfo(instanceName).Exists; - } - else - { - // This approach is used otherwise for testability - IList instances = value.GetInstances(); - - if (instances != null) - { - // Instance names in SQL Local DB are case-insensitive - instanceExists = instances - .Where((p) => p != null) - .Where((p) => string.Equals(p.Name, instanceName, StringComparison.OrdinalIgnoreCase)) - .Any(); - } - } - - if (instanceExists) - { - return value.GetInstance(instanceName); - } - else - { - return value.CreateInstance(instanceName); - } - } - - /// - /// Restarts the specified instance. - /// - /// The instance to restart. - /// - /// is . - /// - public static void Restart(this ISqlLocalDbInstance instance) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - instance.Stop(); - instance.Start(); - } - - /// - /// Sets the Initial Catalog name in the specified . - /// - /// The to set the Initial Catalog name for. - /// The name of the Initial Catalog to set. - /// - /// is . - /// - public static void SetInitialCatalogName(this DbConnectionStringBuilder value, string initialCatalog) - { - value.SetKeywordValueAsString(InitialCatalogKeywordName, initialCatalog); - } - - /// - /// Sets the physical database file name in the specified . - /// - /// The to set the physical database file name for. - /// The physical file name to set. - /// - /// is or invalid. - /// - /// - /// references the Data Directory for the current but - /// the Data Directory for the current has no value set. - /// - public static void SetPhysicalFileName(this DbConnectionStringBuilder value, string fileName) - { - string fullPath = null; - - if (fileName != null) - { - fullPath = ExpandDataDirectoryIfPresent(fileName); - - try - { - // Only full file paths are supported, so ensure that the path is fully qualified before it is set - fullPath = Path.GetFullPath(fullPath); - } - catch (ArgumentException ex) - { - throw new ArgumentException(SRHelper.Format(SR.Extensions_InvalidPathFormat, ex.Message), nameof(fileName), ex); - } - catch (NotSupportedException ex) - { - throw new ArgumentException(SRHelper.Format(SR.Extensions_InvalidPathFormat, ex.Message), nameof(fileName), ex); - } - } - - value.SetKeywordValueAsString(AttachDBFilenameKeywordName, fullPath); - } - - /// - /// Creates an instance of using the specified base - /// connection string, SQL Local DB named pipe and optional Initial Catalog name. - /// - /// The connection string settings for the base connection string. - /// The SQL Local DB named pipe to use as the data source. - /// The optional name to use for the Initial Catalog in the provider connection string to override the default, if present. - /// - /// The created instance of . - /// - private static DbConnectionStringBuilder CreateBuilder( - ConnectionStringSettings connectionStringSettings, - string namedPipe, - string initialCatalog) - { - Debug.Assert(connectionStringSettings != null, "connectionStringSettings cannot be null."); - - if (string.IsNullOrEmpty(namedPipe)) - { - throw new InvalidOperationException(SR.Extensions_NoNamedPipe); - } - - if (_entityConnectionStringBuilderType == null) - { - _entityConnectionStringBuilderType = Type.GetType(EntityConnectionStringBuilderTypeName); - } - - Debug.Assert(_entityConnectionStringBuilderType != null, "Could not find the EntityConnectionStringBuilder type."); - - DbConnectionStringBuilder entityBuilder = Activator.CreateInstance( - _entityConnectionStringBuilderType, - new object[] { connectionStringSettings.ConnectionString }) as DbConnectionStringBuilder; - - if (_providerConnectionStringProperty == null) - { - _providerConnectionStringProperty = _entityConnectionStringBuilderType.GetProperty("ProviderConnectionString"); - } - - Debug.Assert(_providerConnectionStringProperty != null, "Could not find the ProviderConnectionString property of the EntityConnectionStringBuilder type."); - string providerConnectionString = _providerConnectionStringProperty.GetValue(entityBuilder, null) as string; - - SqlConnectionStringBuilder providerBuilder = new SqlConnectionStringBuilder(providerConnectionString) - { - DataSource = namedPipe, - }; - - if (!string.IsNullOrEmpty(initialCatalog)) - { - providerBuilder.InitialCatalog = initialCatalog; - } - - _providerConnectionStringProperty.SetValue(entityBuilder, providerBuilder.ConnectionString, null); - - return entityBuilder; - } - - /// - /// Creates a for an entity connection. - /// - /// The connection string to use to create the connection. - /// - /// The created instance of . - /// - private static DbConnection CreateConnection(string connectionString) - { - if (_entityConnectionType == null) - { - _entityConnectionType = Type.GetType(EntityConnectionTypeName); - } - - Debug.Assert(_entityConnectionType != null, "The EntityConnection type could not be found."); - return Activator.CreateInstance(_entityConnectionType, new object[] { connectionString }) as DbConnection; - } - - /// - /// Expands any reference to the Data Directory for the current . - /// - /// The file name to expand. - /// - /// The expanded representation of if the Data Directory for - /// the current is referenced and set; otherwise . - /// - /// - /// The Data Directory is not set for the current . - /// - private static string ExpandDataDirectoryIfPresent(string fileName) - { - const string DataDirectorySubstitution = "|DataDirectory|"; - - if (fileName.Contains(DataDirectorySubstitution)) - { - string dataDirectoryPath = AppDomain.CurrentDomain.GetData("DataDirectory") as string; - - if (dataDirectoryPath == null) - { - throw new NotSupportedException(SR.Extensions_NoAppDomainDataDirectory); - } - - fileName = fileName.Replace(DataDirectorySubstitution, dataDirectoryPath); - } - - return fileName; - } - - /// - /// Extracts the value of the specified keyword from the specified . - /// - /// The to extract the value from. - /// The keyword to extract the value of. - /// - /// The value of the keyword specified by present in , if any; otherwise . - /// - /// - /// is . - /// - private static string ExtractStringValueFromConnectionString(this DbConnectionStringBuilder value, string keyword) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - // N.B. Keywords are used here rather than the strongly-typed derived classes - // of DbConnectionStringBuilder. This is so that custom derived classes can be - // used and also so that both of the following entity connection string builders - // can be used without using reflection and hard-coded type names: - // 1) System.Data.EntityClient.EntityClientConnectionStringBuilder (System.Data.Entity.dll) - // 2) System.Data.Entity.Core.EntityClient.EntityClientConnectionStringBuilder (EntityFramework.dll) - - // First assume it's an SQL connection string - string result = null; - - if (value.TryGetValue(keyword, out object resultAsObject)) - { - result = resultAsObject as string; - } - - if (!string.IsNullOrEmpty(result)) - { - return result; - } - - string providerConnectionString = null; - - // If it wasn't SQL, see if it's an entity connection string - // by trying to extract the provider connection string - if (value.TryGetValue(ProviderConnectionStringKeywordName, out object providerConnectionStringAsObject)) - { - // It wasn't an entity connection string, nothing further to try - providerConnectionString = providerConnectionStringAsObject as string; - } - - if (!string.IsNullOrEmpty(providerConnectionString)) - { - // Build a connection string from the provider connection string - DbConnectionStringBuilder builder = new DbConnectionStringBuilder() - { - ConnectionString = providerConnectionString, - }; - - // Try and extract the initial catalog from the provider's connection string - if (builder.TryGetValue(keyword, out object resultFromProviderAsObject)) - { - result = resultFromProviderAsObject as string; - } - } - - // Derived types of DbConnectionStringBuilder may return the empty string instead of null - // if they key is missing/not set, so in those cases explicitly return null instead. - if (string.IsNullOrEmpty(result)) - { - return null; - } - - return result; - } - - /// - /// Sets the specified keyword's value to the specified value in the specified . - /// - /// The to set the keyword value for. - /// The keyword to set the value for. - /// The value to set the keyword to. - /// - /// is . - /// - private static void SetKeywordValueAsString(this DbConnectionStringBuilder value, string keyword, string keywordValue) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - // N.B. Keywords are used here rather than the strongly-typed derived classes - // of DbConnectionStringBuilder. This is so that custom derived classes can be - // used and also so that both of the following entity connection string builders - // can be used without using reflection and hard-coded type names: - // 1) System.Data.EntityClient.EntityClientConnectionStringBuilder (System.Data.Entity.dll) - // 2) System.Data.Entity.Core.EntityClient.EntityClientConnectionStringBuilder (EntityFramework.dll) - if (value.TryGetValue(ProviderConnectionStringKeywordName, out object providerConnectionStringAsObject)) - { - string providerConnectionString = providerConnectionStringAsObject as string; - - if (!string.IsNullOrEmpty(providerConnectionString)) - { - // Build a connection string from the provider connection string - DbConnectionStringBuilder builder = new DbConnectionStringBuilder() - { - ConnectionString = providerConnectionString, - }; - - // Set the keyword value in the Provider Connection String and replace - // the initial Provider Connection String with the updated one - builder[keyword] = keywordValue; - value[ProviderConnectionStringKeywordName] = builder.ConnectionString; - } - } - else - { - value[keyword] = keywordValue; - } - } - } -} diff --git a/src/SqlLocalDb/ILogger.cs b/src/SqlLocalDb/ILogger.cs deleted file mode 100644 index 713f6958..00000000 --- a/src/SqlLocalDb/ILogger.cs +++ /dev/null @@ -1,71 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ILogger.cs -// -// -------------------------------------------------------------------------------------------------------------------- -namespace System.Data.SqlLocalDb -{ - /// - /// Defines a logger. - /// - public interface ILogger - { - /// - /// Writes an error trace event. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - void WriteError(int id, string format, params object[] args); - - /// - /// Writes an informational trace event. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - void WriteInformation(int id, string format, params object[] args); - - /// - /// Writes a verbose trace event. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - void WriteVerbose(int id, string format, params object[] args); - - /// - /// Writes a warning trace event. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - void WriteWarning(int id, string format, params object[] args); - } -} diff --git a/src/SqlLocalDb/ILoggerExtensions.cs b/src/SqlLocalDb/ILoggerExtensions.cs new file mode 100644 index 00000000..13e1ecc1 --- /dev/null +++ b/src/SqlLocalDb/ILoggerExtensions.cs @@ -0,0 +1,710 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Logging; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + internal static class ILoggerExtensions + { + /// + /// Logging delegate for when a SQL LocalDB instance has been created. + /// + private static readonly Action _createdInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CreatedInstance, + SR.ILoggerExtensions_CreatedInstanceFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is being created. + /// + private static readonly Action _creatingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CreatingInstance, + SR.ILoggerExtensions_CreatingInstanceFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance has been deleted. + /// + private static readonly Action _deletedInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.DeletedInstance, + SR.ILoggerExtensions_DeletedInstanceFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is being deleting. + /// + private static readonly Action _deletingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.DeletingInstance, + SR.ILoggerExtensions_DeletingFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance that could not be deleted. + /// + private static readonly Action _deletingInstanceFailed = LoggerMessage.Define( + LogLevel.Error, + EventIds.DeletingInstanceFailed, + SR.ILoggerExtensions_DeleteFailedFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance that could not be deleted as it is still in use. + /// + private static readonly Action _deletingInstanceFailedAsInUse = LoggerMessage.Define( + LogLevel.Warning, + EventIds.DeletingInstanceFailedAsInUse, + SR.ILoggerExtensions_DeleteFailedAsInUseFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance that could not be deleted because it could not be found. + /// + private static readonly Action _deletingInstanceFailedAsInstanceNotFound = LoggerMessage.Define( + LogLevel.Debug, + EventIds.DeletingInstanceFailedAsCannotBeNotFound, + SR.ILoggerExtensions_InstanceDoesNotExistFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance files have been deleted. + /// + private static readonly Action _deletedInstanceFiles = LoggerMessage.Define( + LogLevel.Debug, + EventIds.DeletedInstanceFiles, + SR.ILoggerExtensions_DeletedInstanceFilesFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance files are being deleted. + /// + private static readonly Action _deletingInstanceFiles = LoggerMessage.Define( + LogLevel.Debug, + EventIds.DeletingInstanceFiles, + SR.ILoggerExtensions_DeletingInstanceFilesFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance files could not be deleted. + /// + private static readonly Action _deletingInstanceFilesFailed = LoggerMessage.Define( + LogLevel.Error, + EventIds.DeletingInstanceFilesFailed, + SR.ILoggerExtensions_DeletingInstanceFilesFailedFormat); + + /// + /// Logging delegate for when getting information about a SQL LocalDB instance. + /// + private static readonly Action _gettingInstanceInfo = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GettingInstanceInfo, + SR.ILoggerExtensions_GettingInfoFormat); + + /// + /// Logging delegate for when information about a SQL LocalDB instance was got. + /// + private static readonly Action _gotInstanceInfo = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GotInstanceInfo, + SR.ILoggerExtensions_GotInfoFormat); + + /// + /// Logging delegate for when getting SQL LocalDB instance names. + /// + private static readonly Action _gettingInstanceNames = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GettingInstanceNames, + SR.ILoggerExtensions_GetInstances); + + /// + /// Logging delegate for when SQL LocalDB instance names were got. + /// + private static readonly Action _gotInstanceNames = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GotInstanceNames, + SR.ILoggerExtensions_GotInstancesFormat); + + /// + /// Logging delegate for when getting information about a SQL LocalDB version. + /// + private static readonly Action _gettingVersionInfo = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GettingVersionInfo, + SR.ILoggerExtensions_GetVersionInfoFormat); + + /// + /// Logging delegate for when information about a SQL LocalDB version was got. + /// + private static readonly Action _gotVersionInfo = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GotVersionInfo, + SR.ILoggerExtensions_GotVersionInfoFormat); + + /// + /// Logging delegate for when getting SQL LocalDB versions. + /// + private static readonly Action _gettingVersions = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GettingVersions, + SR.ILoggerExtensions_GetVersions); + + /// + /// Logging delegate for when SQL LocalDB versions were got. + /// + private static readonly Action _gotVersions = LoggerMessage.Define( + LogLevel.Debug, + EventIds.GotVersions, + SR.ILoggerExtensions_GotVersionsFormat); + + /// + /// Logging delegate for when a invalid Language Id is used. + /// + private static readonly Action _invalidLanguageId = LoggerMessage.Define( + LogLevel.Warning, + EventIds.InvalidLanguageId, + SR.ILoggerExtensions_InvalidLanguageIdFormat); + + /// + /// Logging delegate for when a invalid SQL LocalDB Instance API registry key was found. + /// + private static readonly Action _invalidRegistryKey = LoggerMessage.Define( + LogLevel.Warning, + EventIds.InvalidRegistryKey, + SR.ILoggerExtensions_InvalidRegistryKeyNameFormat); + + /// + /// Logging delegate for when a SQL LocalDB Instance API function could not be found. + /// + private static readonly Action _nativeApiFunctionNotFound = LoggerMessage.Define( + LogLevel.Error, + EventIds.NativeFunctionNotFound, + SR.ILoggerExtensions_FunctionNotFoundFormat); + + /// + /// Logging delegate for when the SQL LocalDB Instance API DLL was loaded. + /// + private static readonly Action _nativeApiLoaded = LoggerMessage.Define( + LogLevel.Debug, + EventIds.NativeApiLoaded, + SR.ILoggerExtensions_NativeApiLoadedFormat); + + /// + /// Logging delegate for when the SQL LocalDB Instance API DLL could not loaded. + /// + private static readonly Action _nativeApiLoadFailed = LoggerMessage.Define( + LogLevel.Error, + EventIds.NativeApiLoadFailed, + SR.ILoggerExtensions_NativeApiLoadFailedFormat); + + /// + /// Logging delegate for when the SQL LocalDB Instance API could not be found. + /// + private static readonly Action _nativeApiNotFound = LoggerMessage.Define( + LogLevel.Warning, + EventIds.NoNativeApiFound, + SR.ILoggerExtensions_NoNativeApiFound); + + /// + /// Logging delegate for when the SQL LocalDB Instance API DLL was not loaded. + /// + private static readonly Action _nativeApiNotLoaded = LoggerMessage.Define( + LogLevel.Warning, + EventIds.NativeApiNotLoaded, + SR.ILoggerExtensions_NativeApiNotLoaded); + + /// + /// Logging delegate for when the SQL LocalDB Instance API DLL could not be found at the configured path. + /// + private static readonly Action _nativeApiPathNotFound = LoggerMessage.Define( + LogLevel.Error, + EventIds.NativeApiPathNotFound, + SR.ILoggerExtensions_NativeApiNotFoundFormat); + + /// + /// Logging delegate for when the version of the SQL LocalDB Instance API to use was overridden by the user. + /// + private static readonly Action _nativeApiVersionOverriddenByUser = LoggerMessage.Define( + LogLevel.Debug, + EventIds.NativeApiVersionOverriddenByUser, + SR.ILoggerExtensions_ApiVersionOverriddenByUserFormat); + + /// + /// Logging delegate for when the version of the SQL LocalDB Instance API specified by the user could not be found. + /// + private static readonly Action _nativeApiVersionOverrideNotFound = LoggerMessage.Define( + LogLevel.Warning, + EventIds.NativeApiVersionOverrideNotFound, + SR.ILoggerExtensions_OverrideVersionNotFoundFormat); + + /// + /// Logging delegate for when SQL LocalDB is not installed. + /// + private static readonly Action _notInstalled = LoggerMessage.Define( + LogLevel.Warning, + EventIds.NotInstalled, + SR.ILoggerExtensions_NotInstalled); + + /// + /// Logging delegate for when the SQL LocalDB Instance API registry key cannot be found. + /// + private static readonly Action _registryKeyNotFound = LoggerMessage.Define( + LogLevel.Warning, + EventIds.RegistryKeyNotFound, + SR.ILoggerExtensions_RegistryKeyNotFoundFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance was shared. + /// + private static readonly Action _sharedInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.SharedInstance, + SR.ILoggerExtensions_SharedInstanceFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is shared. + /// + private static readonly Action _sharingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.SharingInstance, + SR.ILoggerExtensions_SharingInstanceFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is starting. + /// + private static readonly Action _startedInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StartedInstance, + SR.ILoggerExtensions_StartedFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is starting. + /// + private static readonly Action _startingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StartingInstance, + SR.ILoggerExtensions_StartingFormat); + + /// + /// Logging delegate for when SQL LocalDB tracing has started. + /// + private static readonly Action _startedTracing = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StartedTracing, + SR.ILoggerExtensions_StartedTracing); + + /// + /// Logging delegate for when SQL LocalDB tracing is starting. + /// + private static readonly Action _startingTracing = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StartingTracing, + SR.ILoggerExtensions_StartTracing); + + /// + /// Logging delegate for when a SQL LocalDB instance was stopped. + /// + private static readonly Action _stoppedInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StoppedInstance, + SR.ILoggerExtensions_StoppedFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is stopping. + /// + private static readonly Action _stoppingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StoppingInstance, + SR.ILoggerExtensions_StoppingFormat); + + /// + /// Logging delegate for when a temporary SQL LocalDB instance failed to stop. + /// + private static readonly Action _stoppingTemporaryInstanceFailed = LoggerMessage.Define( + LogLevel.Error, + EventIds.StopTemporaryInstanceFailed, + SR.ILoggerExtensions_StopFailedFormat); + + /// + /// Logging delegate for when SQL LocalDB instance is stopped. + /// + private static readonly Action _stoppedTracing = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StoppedTracing, + SR.ILoggerExtensions_StoppedTracing); + + /// + /// Logging delegate for when SQL LocalDB instance is stopping. + /// + private static readonly Action _stoppingTracing = LoggerMessage.Define( + LogLevel.Debug, + EventIds.StoppingTracing, + SR.ILoggerExtensions_StoppingTracing); + + /// + /// Logging delegate for when a SQL LocalDB instance was unshared. + /// + private static readonly Action _unsharedInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.UnsharedInstance, + SR.ILoggerExtensions_StoppedSharingFormat); + + /// + /// Logging delegate for when a SQL LocalDB instance is unshared. + /// + private static readonly Action _unsharingInstance = LoggerMessage.Define( + LogLevel.Debug, + EventIds.UnsharingInstance, + SR.ILoggerExtensions_StoppingSharingFormat); + + /// + /// Logs that a SQL LocalDB instance has been created. + /// + /// The logger to use. + /// The name of the instance that was created. + /// The LocalDB version the instance that was created with. + internal static void CreatedInstance(this ILogger logger, string instanceName, string version) + => _createdInstance(logger, instanceName, version, null); + + /// + /// Logs that a SQL LocalDB instance is being created. + /// + /// The logger to use. + /// The name of the instance that is being created. + /// The LocalDB version to create the instance with. + internal static void CreatingInstance(this ILogger logger, string instanceName, string version) + => _creatingInstance(logger, instanceName, version, null); + + /// + /// Logs that a SQL LocalDB instance has been deleted. + /// + /// The logger to use. + /// The name of the instance that was created. + internal static void DeletedInstance(this ILogger logger, string instanceName) + => _deletedInstance(logger, instanceName, null); + + /// + /// Logs that a SQL LocalDB instance is being deleted. + /// + /// The logger to use. + /// The name of the instance that is being deleted. + internal static void DeletingInstance(this ILogger logger, string instanceName) + => _deletingInstance(logger, instanceName, null); + + /// + /// Logs that a SQL LocalDB instance could not be deleted. + /// + /// The logger to use. + /// The name of the instance that could not be deleted. + /// The error code. + internal static void DeletingInstanceFailed(this ILogger logger, string instanceName, int error) + => _deletingInstanceFailed(logger, instanceName, error, null); + + /// + /// Logs that a SQL LocalDB instance could not be deleted because it is still in use. + /// + /// The logger to use. + /// The exception that was thrown. + /// The name of the instance that could not be deleted. + internal static void DeletingInstanceFailedAsInUse(this ILogger logger, Exception exception, string instanceName) + => _deletingInstanceFailedAsInUse(logger, instanceName, exception); + + /// + /// Logs that a SQL LocalDB instance could not be deleted because it could not be found. + /// + /// The logger to use. + /// The name of the instance that could not be deleted. + internal static void DeletingInstanceFailedAsNotFound(this ILogger logger, string instanceName) + => _deletingInstanceFailedAsInstanceNotFound(logger, instanceName, null); + + /// + /// Logs that a SQL LocalDB instance's files have been deleted. + /// + /// The logger to use. + /// The name of the instance whose files were deleted. + /// The path of the files that were deleted. + internal static void DeletedInstanceFiles(this ILogger logger, string instanceName, string instancePath) + => _deletedInstanceFiles(logger, instanceName, instancePath, null); + + /// + /// Logs that a SQL LocalDB instance's files are being deleted. + /// + /// The logger to use. + /// The name of the instance whose files are being deleted. + /// The path of the files to be deleted. + internal static void DeletingInstanceFiles(this ILogger logger, string instanceName, string instancePath) + => _deletingInstanceFiles(logger, instanceName, instancePath, null); + + /// + /// Logs that a SQL LocalDB instance's files could not be deleted. + /// + /// The logger to use. + /// The name of the instance whose files could not be deleted. + /// The path of the files that failed to be deleted. + internal static void DeletingInstanceFilesFailed(this ILogger logger, string instanceName, string instancePath) + => _deletingInstanceFilesFailed(logger, instanceName, instancePath, null); + + /// + /// Logs that information about a SQL LocalDB instance is being retrieved. + /// + /// The logger to use. + /// The name of the instance information is being retrieved for. + internal static void GettingInstanceInfo(this ILogger logger, string instanceName) + => _gettingInstanceInfo(logger, instanceName, null); + + /// + /// Logs that information about a SQL LocalDB instance was retrieved. + /// + /// The logger to use. + /// The name of the instance information was retrieved for. + internal static void GotInstanceInfo(this ILogger logger, string instanceName) + => _gotInstanceInfo(logger, instanceName, null); + + /// + /// Logs that SQL LocalDB instance names are being retrieved. + /// + /// The logger to use. + internal static void GettingInstanceNames(this ILogger logger) + => _gettingInstanceNames(logger, null); + + /// + /// Logs that SQL LocalDB instance names were retrieved. + /// + /// The logger to use. + /// The number of SQL LocalDB instances retrieved. + internal static void GotInstanceNames(this ILogger logger, int count) + => _gotInstanceNames(logger, count, null); + + /// + /// Logs that information about a SQL LocalDB version is being retrieved. + /// + /// The logger to use. + /// The version information is being retrieved for. + internal static void GettingVersionInfo(this ILogger logger, string version) + => _gettingVersionInfo(logger, version, null); + + /// + /// Logs that information about a SQL LocalDB version was retrieved. + /// + /// The logger to use. + /// The version information was retrieved for. + internal static void GotVersionInfo(this ILogger logger, string version) + => _gotVersionInfo(logger, version, null); + + /// + /// Logs that SQL LocalDB versions are being retrieved. + /// + /// The logger to use. + internal static void GettingVersions(this ILogger logger) + => _gettingVersions(logger, null); + + /// + /// Logs that SQL LocalDB versions were retrieved. + /// + /// The logger to use. + /// The number of SQL LocalDB versions retrieved. + internal static void GotVersions(this ILogger logger, int count) + => _gotVersions(logger, count, null); + + /// + /// Logs that an invalid Language Id is configured. + /// + /// The logger to use. + /// The version used. + internal static void InvalidLanguageId(this ILogger logger, int languageId) + => _invalidLanguageId(logger, languageId, null); + + /// + /// Logs that an invalid SQL LocalDB Instance API registry key was found. + /// + /// The logger to use. + /// The version used. + internal static void InvalidRegistryKey(this ILogger logger, string version) + => _invalidRegistryKey(logger, version, null); + + /// + /// Logs that a SQL LocalDB Instance API function could not be found. + /// + /// The logger to use. + /// The name of the function that could not be found. + internal static void NativeApiFunctionNotFound(this ILogger logger, string functionName) + => _nativeApiFunctionNotFound(logger, functionName, null); + + /// + /// Logs that the SQL LocalDB Instance API DLL could not be found at the configured path. + /// + /// The logger to use. + /// The path to the file that could not be found. + internal static void NativeApiLibraryNotFound(this ILogger logger, string path) + => _nativeApiPathNotFound(logger, path, null); + + /// + /// Logs that the SQL LocalDB Instance API DLL was loaded. + /// + /// The logger to use. + /// The full path to the DLL that was loaded. + internal static void NativeApiLoaded(this ILogger logger, string fileName) + => _nativeApiLoaded(logger, fileName, null); + + /// + /// Logs that the SQL LocalDB Instance API DLL could not loaded. + /// + /// The logger to use. + /// The full path to the DLL that could not be loaded. + /// The error code. + internal static void NativeApiLoadFailed(this ILogger logger, string fileName, int error) + => _nativeApiLoadFailed(logger, fileName, error, null); + + /// + /// Logs that the SQL LocalDB Instance API could not be found. + /// + /// The logger to use. + internal static void NativeApiNotFound(this ILogger logger) + => _nativeApiNotFound(logger, null); + + /// + /// Logs that the SQL LocalDB Instance API DLL was not loaded. + /// + /// The logger to use. + internal static void NativeApiNotLoaded(this ILogger logger) + => _nativeApiNotLoaded(logger, null); + + /// + /// Logs that the version of the SQL LocalDB Instance API to use was overridden by the user. + /// + /// The logger to use. + /// The version used. + internal static void NativeApiVersionOverriddenByUser(this ILogger logger, Version version) + => _nativeApiVersionOverriddenByUser(logger, version, null); + + /// + /// Logs that the version of the SQL LocalDB Instance API specified by the user could not be found. + /// + /// The logger to use. + /// The version specified to be used. + internal static void NativeApiVersionOverrideNotFound(this ILogger logger, string version) + => _nativeApiVersionOverrideNotFound(logger, version, Environment.MachineName, null); + + /// + /// Logs that SQL LocalDB is not installed. + /// + /// The logger to use. + internal static void NotInstalled(this ILogger logger) + => _notInstalled(logger, null); + + /// + /// Logs that the SQL LocalDB Instance API registry key cannot be found. + /// + /// The logger to use. + /// The name of the registry key that was not found. + internal static void RegistryKeyNotFound(this ILogger logger, string keyName) + => _registryKeyNotFound(logger, keyName, null); + + /// + /// Logs that a SQL LocalDB instance is being shared. + /// + /// The logger to use. + /// The name of the instance that is being shared. + /// The SID of the instance owner. + /// The shared name for the LocalDB instance to share as. + internal static void SharingInstance(this ILogger logger, string instanceName, string ownerSid, string sharedInstanceName) + => _sharingInstance(logger, instanceName, ownerSid, sharedInstanceName, null); + + /// + /// Logs that a SQL LocalDB instance was shared. + /// + /// The logger to use. + /// The name of the instance that was shared. + /// The SID of the instance owner. + /// The shared name for the LocalDB instance to share as. + internal static void SharedInstance(this ILogger logger, string instanceName, string ownerSid, string sharedInstanceName) + => _sharedInstance(logger, instanceName, ownerSid, sharedInstanceName, null); + + /// + /// Logs that a SQL LocalDB instance was started. + /// + /// The logger to use. + /// The name of the instance that was started. + /// The named pipe the instance is listening on. + internal static void StartedInstance(this ILogger logger, string instanceName, string namedPipe) + => _startedInstance(logger, instanceName, namedPipe, null); + + /// + /// Logs that a SQL LocalDB instance is starting. + /// + /// The logger to use. + /// The name of the instance that is starting. + internal static void StartingInstance(this ILogger logger, string instanceName) + => _startingInstance(logger, instanceName, null); + + /// + /// Logs that SQL LocalDB tracing was started. + /// + /// The logger to use. + internal static void StartedTracing(this ILogger logger) + => _startedTracing(logger, null); + + /// + /// Logs that SQL LocalDB tracing is starting. + /// + /// The logger to use. + internal static void StartingTracing(this ILogger logger) + => _startingTracing(logger, null); + + /// + /// Logs that a SQL LocalDB instance was stopped. + /// + /// The logger to use. + /// The name of the instance that was stopped. + /// The time taken for the instance to stop. + internal static void StoppedInstance(this ILogger logger, string instanceName, TimeSpan elapsed) + => _stoppedInstance(logger, instanceName, elapsed, null); + + /// + /// Logs that a SQL LocalDB instance is stopping. + /// + /// The logger to use. + /// The name of the instance that is stopping. + /// The timeout used. + /// The stop options used. + internal static void StoppingInstance(this ILogger logger, string instanceName, TimeSpan timeout, StopInstanceOptions options) + => _stoppingInstance(logger, instanceName, timeout, options, null); + + /// + /// Logs that a temporary SQL LocalDB instance failed to stop. + /// + /// The logger to use. + /// The name of the instance that failed to stop. + /// The error code. + internal static void StoppingTemporaryInstanceFailed(this ILogger logger, string instanceName, int error) + => _stoppingTemporaryInstanceFailed(logger, instanceName, error, null); + + /// + /// Logs that SQL LocalDB tracing was stopped. + /// + /// The logger to use. + internal static void StoppedTracing(this ILogger logger) + => _stoppedTracing(logger, null); + + /// + /// Logs that SQL LocalDB tracing is stopping. + /// + /// The logger to use. + internal static void StoppingTracing(this ILogger logger) + => _stoppingTracing(logger, null); + + /// + /// Logs that a SQL LocalDB instance is being unshared. + /// + /// The logger to use. + /// The name of the instance that is being shared. + internal static void UnsharingInstance(this ILogger logger, string instanceName) + => _unsharingInstance(logger, instanceName, null); + + /// + /// Logs that a SQL LocalDB instance was shared. + /// + /// The logger to use. + /// The name of the instance that was shared. + internal static void UnsharedInstance(this ILogger logger, string instanceName) + => _unsharedInstance(logger, instanceName, null); + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbApi.cs b/src/SqlLocalDb/ISqlLocalDbApi.cs index 8863e17e..19e76cc5 100644 --- a/src/SqlLocalDb/ISqlLocalDbApi.cs +++ b/src/SqlLocalDb/ISqlLocalDbApi.cs @@ -1,46 +1,40 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ISqlLocalDbApi.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb { /// - /// Defines the interface to the SQL LocalDB API. + /// Defines the interface to the SQL LocalDB Instance API. /// public interface ISqlLocalDbApi { + /// + /// Gets the name of the default SQL LocalDB instance. + /// + string DefaultInstanceName { get; } + /// /// Gets the version string for the latest installed version of SQL Server LocalDB. /// - string LatestVersion - { - get; - } + string LatestVersion { get; } /// - /// Gets an of containing the available version(s) of SQL LocalDB. + /// Gets an of containing the available version(s) of SQL LocalDB. /// - IList Versions - { - get; - } + IReadOnlyList Versions { get; } /// /// Creates a new instance of SQL Server LocalDB. /// /// The name of the LocalDB instance. /// The version of SQL Server LocalDB to use. - void CreateInstance(string instanceName, string version); + /// + /// An containing information about the instance that was created. + /// + ISqlLocalDbInstanceInfo CreateInstance(string instanceName, string version); /// /// Deletes the specified SQL Server LocalDB instance. @@ -66,11 +60,7 @@ IList Versions /// /// The names of the the SQL Server LocalDB instances for the current user. /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires enumerating the API which is non-trivial.")] - IList GetInstanceNames(); + IReadOnlyList GetInstanceNames(); /// /// Returns information about the specified LocalDB version. @@ -122,19 +112,14 @@ IList Versions /// The name of the LocalDB instance to stop. /// /// - /// The amount of time to give the LocalDB instance to stop. - /// If the value is , the method will - /// return immediately and not wait for the instance to stop. + /// The optional amount of time to give the LocalDB instance to stop. /// - void StopInstance(string instanceName, TimeSpan timeout); + void StopInstance(string instanceName, TimeSpan? timeout); /// /// Disables tracing of native API calls for all the SQL Server /// LocalDB instances owned by the current Windows user. /// - /// - /// Tracing could not be disabled. - /// void StopTracing(); /// @@ -143,11 +128,6 @@ IList Versions /// /// The private name for the LocalDB instance to stop sharing. /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the native LocalDB API function.")] void UnshareInstance(string instanceName); } } diff --git a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs new file mode 100644 index 00000000..5ba2202d --- /dev/null +++ b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs @@ -0,0 +1,204 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Principal; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ISqlLocalDbApiExtensions + { + /// + /// Creates a new instance of with a randomly assigned name. + /// + /// The to use to create the temporary instance. + /// An optional value indicating whether to delete the file(s) associated with the SQL LocalDB instance when it is deleted. + /// + /// The created instance of . + /// + /// + /// is . + /// + public static TemporarySqlLocalDbInstance CreateTemporaryInstance(this ISqlLocalDbApi api, bool deleteFiles = false) => new TemporarySqlLocalDbInstance(api, deleteFiles); + + /// + /// Gets the default SQL Local DB instance. + /// + /// The to use to get the default instance. + /// + /// The default SQL Local DB instance. + /// + /// + /// is . + /// + public static ISqlLocalDbInstanceInfo GetDefaultInstance(this ISqlLocalDbApi api) + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + return api.GetOrCreateInstance(api.DefaultInstanceName); + } + + /// + /// Returns information about the available SQL Server LocalDB instances. + /// + /// The to use to enumerate the instances. + /// + /// An containing information + /// about the available SQL Server LocalDB instances on the current machine. + /// + /// + /// is . + /// + public static IReadOnlyList GetInstances(this ISqlLocalDbApi api) + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + IReadOnlyList instanceNames = api.GetInstanceNames(); + + var instances = new List(instanceNames?.Count ?? 0); + + if (instanceNames != null) + { + foreach (string name in instanceNames) + { + ISqlLocalDbInstanceInfo info = api.GetInstanceInfo(name); + instances.Add(info); + } + } + + return instances; + } + + /// + /// Returns information about the installed SQL Server LocalDB version(s). + /// + /// The to use to enumerate the installed versions. + /// + /// An containing information + /// about the SQL Server LocalDB version(s) installed on the current machine. + /// + /// + /// is . + /// + public static IReadOnlyList GetVersions(this ISqlLocalDbApi api) + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + IReadOnlyList versionNames = api.Versions; + var versions = new List(versionNames?.Count ?? 0); + + if (versionNames != null) + { + foreach (string version in versionNames) + { + ISqlLocalDbVersionInfo info = api.GetVersionInfo(version); + versions.Add(info); + } + } + + return versions; + } + + /// + /// Gets a SQL Local DB instance with the specified name if it exists, otherwise a new instance with the specified name is created. + /// + /// The to use to get or create the instance. + /// The name of the SQL Server LocalDB instance to get or create. + /// + /// A SQL Local DB instance with the name specified by . + /// + /// + /// or is . + /// + public static ISqlLocalDbInstanceInfo GetOrCreateInstance(this ISqlLocalDbApi api, string instanceName) + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + if (instanceName == null) + { + throw new ArgumentNullException(nameof(instanceName)); + } + + bool instanceExists = false; + + if (string.Equals(api.DefaultInstanceName, instanceName, StringComparison.Ordinal) || SqlLocalDbApi.IsDefaultInstanceName(instanceName)) + { + // The default instance is always listed, even if it does not exist, + // so need to query that separately to verify whether to get or create. + ISqlLocalDbInstanceInfo info = api.GetInstanceInfo(instanceName); + + if (info?.Exists == true) + { + return info; + } + } + else + { + IReadOnlyList instanceNames = api.GetInstanceNames(); + + if (instanceNames != null) + { + // Instance names in SQL Local DB are case-insensitive + instanceExists = instanceNames + .Where((p) => string.Equals(p, instanceName, StringComparison.OrdinalIgnoreCase)) + .Any(); + } + } + + if (instanceExists) + { + return api.GetInstanceInfo(instanceName); + } + else + { + return api.CreateInstance(instanceName, api.LatestVersion); + } + } + + /// + /// Shares the specified SQL Server LocalDB instance with other users of the computer, + /// using the specified shared name for the current Windows user. + /// + /// The to use to share the instance. + /// The private name for the LocalDB instance to share. + /// The shared name for the LocalDB instance to share. + /// + /// , or is . + /// + public static void ShareInstance(this ISqlLocalDbApi api, string instanceName, string sharedInstanceName) + { + if (api == null) + { + throw new ArgumentNullException(nameof(api)); + } + + string ownerSid; + + using (var identity = WindowsIdentity.GetCurrent()) + { + ownerSid = identity.User.Value; + } + + api.ShareInstance(ownerSid, instanceName, sharedInstanceName); + } + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbInstance.cs b/src/SqlLocalDb/ISqlLocalDbInstance.cs deleted file mode 100644 index 94d3865f..00000000 --- a/src/SqlLocalDb/ISqlLocalDbInstance.cs +++ /dev/null @@ -1,102 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ISqlLocalDbInstance.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Data.SqlClient; - -namespace System.Data.SqlLocalDb -{ - /// - /// Defines an SQL Server LocalDB instance. - /// - public interface ISqlLocalDbInstance - { - /// - /// Gets the name of the LocalDB instance. - /// - string Name - { - get; - } - - /// - /// Gets the named pipe that should be used - /// to connect to the LocalDB instance. - /// - string NamedPipe - { - get; - } - - /// - /// Creates a connection to the LocalDB instance. - /// - /// - /// An instance of that - /// can be used to connect to the LocalDB instance. - /// - SqlConnection CreateConnection(); - - /// - /// Creates an instance of containing - /// the default SQL connection string to connect to the LocalDB instance. - /// - /// - /// An instance of containing - /// the default SQL connection string to connect to the LocalDB instance. - /// - SqlConnectionStringBuilder CreateConnectionStringBuilder(); - - /// - /// Returns information about the LocalDB instance. - /// - /// - /// An instance of containing - /// information about the LocalDB instance. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires querying the LocalDB native API.")] - ISqlLocalDbInstanceInfo GetInstanceInfo(); - - /// - /// Shares the LocalDB instance using the specified name. - /// - /// The name to use to share the instance. - void Share(string sharedName); - - /// - /// Starts the LocalDB instance. - /// - void Start(); - - /// - /// Stops the LocalDB instance. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1716:IdentifiersShouldNotMatchKeywords", - MessageId = "Stop", - Justification = "Matches the name of the LocalDB native API function.")] - void Stop(); - - /// - /// Stops sharing the LocalDB instance. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the LocalDB native API function.")] - void Unshare(); - } -} diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceInfo.cs b/src/SqlLocalDb/ISqlLocalDbInstanceInfo.cs index 04e65f32..b96cfd61 100644 --- a/src/SqlLocalDb/ISqlLocalDbInstanceInfo.cs +++ b/src/SqlLocalDb/ISqlLocalDbInstanceInfo.cs @@ -1,16 +1,9 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ISqlLocalDbInstanceInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -namespace System.Data.SqlLocalDb +using System; + +namespace MartinCostello.SqlLocalDb { /// /// Defines information about a SQL Server LocalDB instance. @@ -20,89 +13,56 @@ public interface ISqlLocalDbInstanceInfo /// /// Gets a value indicating whether the Registry configuration is corrupt. /// - bool ConfigurationCorrupt - { - get; - } + bool ConfigurationCorrupt { get; } /// /// Gets a value indicating whether the instance's files exist on disk. /// - bool Exists - { - get; - } + bool Exists { get; } /// /// Gets a value indicating whether the instance is automatic. /// - bool IsAutomatic - { - get; - } + bool IsAutomatic { get; } /// /// Gets a value indicating whether the instance is currently running. /// - bool IsRunning - { - get; - } + bool IsRunning { get; } /// /// Gets a value indicating whether the instance is shared. /// - bool IsShared - { - get; - } + bool IsShared { get; } /// /// Gets the UTC date and time the instance was last started. /// - DateTime LastStartTimeUtc - { - get; - } + DateTime LastStartTimeUtc { get; } /// /// Gets the LocalDB version for the instance. /// - Version LocalDbVersion - { - get; - } + Version LocalDbVersion { get; } /// /// Gets the name of the instance. /// - string Name - { - get; - } + string Name { get; } /// /// Gets the named pipe that should be used to communicate with the instance. /// - string NamedPipe - { - get; - } + string NamedPipe { get; } /// /// Gets the SID of the LocalDB instance owner if the instance is shared. /// - string OwnerSid - { - get; - } + string OwnerSid { get; } /// /// Gets the shared name of the LocalDB instance if the instance is shared. /// - string SharedName - { - get; - } + string SharedName { get; } } } diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs new file mode 100644 index 00000000..e0aa6e32 --- /dev/null +++ b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.ComponentModel; +using System.Data.SqlClient; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ISqlLocalDbInstanceInfoExtensions + { + /// + /// Creates a connection to the LocalDB instance. + /// + /// The SQL LocalDB instance to create a connection to. + /// + /// An instance of that can be used to connect to the LocalDB instance. + /// + /// + /// is . + /// + /// + /// The SQL LocalDB instance specified by is not running. + /// + public static SqlConnection CreateConnection(this ISqlLocalDbInstanceInfo instance) + { + SqlConnectionStringBuilder builder = instance.CreateConnectionStringBuilder(); + return new SqlConnection(builder.ConnectionString); + } + + /// + /// Creates an instance of containing + /// the default SQL connection string to connect to the LocalDB instance. + /// + /// The SQL LocalDB instance to create a connection string builder for. + /// + /// An instance of containing + /// the default SQL connection string to connect to the LocalDB instance. + /// + /// + /// is . + /// + /// + /// The SQL LocalDB instance specified by is not running. + /// + public static SqlConnectionStringBuilder CreateConnectionStringBuilder(this ISqlLocalDbInstanceInfo instance) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + if (!instance.IsRunning) + { + string message = SRHelper.Format(SR.ISqlLocalDbInstanceInfoExtensions_NotRunningFormat, instance.Name); + throw new InvalidOperationException(message); + } + + return new SqlConnectionStringBuilder() + { + DataSource = instance.NamedPipe, + }; + } + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceManager.cs b/src/SqlLocalDb/ISqlLocalDbInstanceManager.cs new file mode 100644 index 00000000..7923851f --- /dev/null +++ b/src/SqlLocalDb/ISqlLocalDbInstanceManager.cs @@ -0,0 +1,52 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Defines an interface for managing instances of SQL LocalDB. + /// + public interface ISqlLocalDbInstanceManager + { + /// + /// Gets the name of the LocalDB instance. + /// + string Name { get; } + + /// + /// Gets the named pipe that should be used to connect to the LocalDB instance. + /// + string NamedPipe { get; } + + /// + /// Gets the current state of the instance. + /// + /// + /// An representing the current state of the instance being managed. + /// + ISqlLocalDbInstanceInfo GetInstanceInfo(); + + /// + /// Shares the LocalDB instance using the specified name. + /// + /// The name to use to share the instance. + void Share(string sharedName); + + /// + /// Starts the LocalDB instance. + /// + void Start(); + + /// + /// Stops the LocalDB instance. + /// +#pragma warning disable CA1716 // Identifiers should not match keywords + void Stop(); +#pragma warning restore CA1716 // Identifiers should not match keywords + + /// + /// Stops sharing the LocalDB instance. + /// + void Unshare(); + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceManagerExtensions.cs b/src/SqlLocalDb/ISqlLocalDbInstanceManagerExtensions.cs new file mode 100644 index 00000000..da3beee8 --- /dev/null +++ b/src/SqlLocalDb/ISqlLocalDbInstanceManagerExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.ComponentModel; +using System.Data.SqlClient; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ISqlLocalDbInstanceManagerExtensions + { + /// + /// Creates a connection to the LocalDB instance. + /// + /// The associated with the instance to create a connection to. + /// + /// An instance of that can be used to connect to the LocalDB instance. + /// + /// + /// is . + /// + public static SqlConnection CreateConnection(this ISqlLocalDbInstanceManager manager) + { + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + return manager.GetInstanceInfo().CreateConnection(); + } + + /// + /// Restarts the specified instance. + /// + /// The associated with the instance to restart. + /// + /// is . + /// + public static void Restart(this ISqlLocalDbInstanceManager manager) + { + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + manager.Stop(); + manager.Start(); + } + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbProvider.cs b/src/SqlLocalDb/ISqlLocalDbProvider.cs deleted file mode 100644 index 4eb7ed18..00000000 --- a/src/SqlLocalDb/ISqlLocalDbProvider.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ISqlLocalDbProvider.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; - -namespace System.Data.SqlLocalDb -{ - /// - /// Defines methods for obtaining instances of . - /// - public interface ISqlLocalDbProvider - { - /// - /// Creates a new instance of . - /// - /// The name of the SQL Server LocalDB instance to create. - /// - /// The created instance of . - /// - ISqlLocalDbInstance CreateInstance(string instanceName); - - /// - /// Returns an existing instance of . - /// - /// The name of the SQL Server LocalDB instance to return. - /// - /// The existing instance of . - /// - ISqlLocalDbInstance GetInstance(string instanceName); - - /// - /// Returns information about the available SQL Server LocalDB instances. - /// - /// - /// An containing information - /// about the available SQL Server LocalDB instances on the current machine. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires querying the native LocalDB API.")] - IList GetInstances(); - - /// - /// Returns information about the installed SQL Server LocalDB version(s). - /// - /// - /// An containing information - /// about the SQL Server LocalDB version(s) installed on the current machine. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires querying the native LocalDB API.")] - IList GetVersions(); - } -} diff --git a/src/SqlLocalDb/ISqlLocalDbVersionInfo.cs b/src/SqlLocalDb/ISqlLocalDbVersionInfo.cs index 207c22e0..970f0049 100644 --- a/src/SqlLocalDb/ISqlLocalDbVersionInfo.cs +++ b/src/SqlLocalDb/ISqlLocalDbVersionInfo.cs @@ -1,16 +1,9 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ISqlLocalDbVersionInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -namespace System.Data.SqlLocalDb +using System; + +namespace MartinCostello.SqlLocalDb { /// /// Defines information about a version of SQL Server LocalDB. @@ -20,25 +13,16 @@ public interface ISqlLocalDbVersionInfo /// /// Gets a value indicating whether the instance files exist on disk. /// - bool Exists - { - get; - } + bool Exists { get; } /// /// Gets the version name. /// - string Name - { - get; - } + string Name { get; } /// /// Gets the version. /// - Version Version - { - get; - } + Version Version { get; } } } diff --git a/src/SqlLocalDb/Interop/IRegistry.cs b/src/SqlLocalDb/Interop/IRegistry.cs new file mode 100644 index 00000000..06ad7c49 --- /dev/null +++ b/src/SqlLocalDb/Interop/IRegistry.cs @@ -0,0 +1,20 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +namespace MartinCostello.SqlLocalDb.Interop +{ + /// + /// Defines a method for opening a registry sub-key. + /// + internal interface IRegistry + { + /// + /// Retrieves a sub-key as read-only. + /// + /// The name or path of the sub-key to open as read-only. + /// + /// The sub-key requested, or if the operation failed. + /// + IRegistryKey OpenSubKey(string keyName); + } +} diff --git a/src/SqlLocalDb/Interop/IRegistryKey.cs b/src/SqlLocalDb/Interop/IRegistryKey.cs new file mode 100644 index 00000000..9040b3ec --- /dev/null +++ b/src/SqlLocalDb/Interop/IRegistryKey.cs @@ -0,0 +1,30 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; + +namespace MartinCostello.SqlLocalDb.Interop +{ + /// + /// Defines a registry sub-key. + /// + internal interface IRegistryKey : IRegistry, IDisposable + { + /// + /// Retrieves an array of strings that contains all the sub-key names. + /// + /// + /// An array of strings that contains the names of the sub-keys for the current key. + /// + string[] GetSubKeyNames(); + + /// + /// Retrieves the value associated with the specified name. + /// + /// The name of the value to retrieve. This string is not case-sensitive. + /// + /// The value associated with , or if is not found. + /// + string GetValue(string name); + } +} diff --git a/src/SqlLocalDb/NativeMethods.cs b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs similarity index 69% rename from src/SqlLocalDb/NativeMethods.cs rename to src/SqlLocalDb/Interop/LocalDbInstanceApi.cs index 75c6186c..9a17839a 100644 --- a/src/SqlLocalDb/NativeMethods.cs +++ b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs @@ -1,212 +1,187 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// NativeMethods.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.InteropServices; -using System.Security; using System.Text; -using Microsoft.Win32; +using Microsoft.Extensions.Logging; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb.Interop { /// - /// A class containing native P/Invoke methods. This class cannot be inherited. + /// A class containing methods for interop with the SQL LocalDB Instance API. This class cannot be inherited. /// - [SecurityCritical] - internal static class NativeMethods + internal sealed class LocalDbInstanceApi : IDisposable { /// /// The maximum size of SQL Server LocalDB connection string. /// - internal const int LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE = 260; + internal const int MaximumSqlConnectionStringBufferLength = 260; /// /// The maximum size of SQL Server LocalDB instance names. /// - internal const int MAX_LOCALDB_INSTANCE_NAME_LENGTH = 128; + internal const int MaximumInstanceNameLength = 128; /// - /// The maximum size of an SQL Server LocalDB version string. + /// The maximum size of a SQL Server LocalDB version string. /// - internal const int MAX_LOCALDB_VERSION_LENGTH = 43; + internal const int MaximumInstanceVersionLength = 43; /// /// The maximum length of a SID string. /// - internal const int MAX_STRING_SID_LENGTH = 186; + internal const int MaximumSidStringLength = 186; /// /// Specifies that error messages that are too long should be truncated. /// - private const int LOCALDB_TRUNCATE_ERR_MESSAGE = 1; + private const int LocalDbTruncateErrorMessage = 1; /// - /// This value represents the recommended maximum number of directories an application should include in its DLL search path. + /// An array containing the null character. This field is read-only. /// - /// - /// Only supported on Windows Vista, 7, Server 2008 and Server 2008 R2 with KB2533623. - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179%28v=vs.85%29.aspx. - /// - private const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000; - - /// - /// The name of the Windows Kernel library. - /// - private const string KernelLibName = "kernel32.dll"; + private static readonly char[] _nullArray = new char[] { '\0' }; /// - /// An array containing the '\0' character. This field is read-only. + /// Synchronization object to protect loading the native library and its functions. This field is read-only. /// - private static readonly char[] _nullArray = new char[] { '\0' }; + private readonly object _syncRoot = new object(); /// - /// Synchronization object to protect loading the native library and its functions. + /// Whether the instance has been disposed of. /// - private static readonly object _syncRoot = new object(); + private bool _disposed; /// /// The handle to the native SQL LocalDB API. /// - private static SafeLibraryHandle _localDB; + private SafeLibraryHandle _handle; /// /// The delegate to the LocalDBCreateInstance LocalDB API function. /// - private static Functions.LocalDBCreateInstance _localDBCreateInstance; + private Functions.LocalDBCreateInstance _localDBCreateInstance; /// /// The delegate to the LocalDBDeleteInstance LocalDB API function. /// - private static Functions.LocalDBDeleteInstance _localDBDeleteInstance; + private Functions.LocalDBDeleteInstance _localDBDeleteInstance; /// /// The delegate to the LocalDBFormatMessage LocalDB API function. /// - private static Functions.LocalDBFormatMessage _localDBFormatMessage; + private Functions.LocalDBFormatMessage _localDBFormatMessage; /// /// The delegate to the LocalDBGetInstanceInfo LocalDB API function. /// - private static Functions.LocalDBGetInstanceInfo _localDBGetInstanceInfo; + private Functions.LocalDBGetInstanceInfo _localDBGetInstanceInfo; /// /// The delegate to the LocalDBGetInstances LocalDB API function. /// - private static Functions.LocalDBGetInstances _localDBGetInstances; + private Functions.LocalDBGetInstances _localDBGetInstances; /// /// The delegate to the LocalDBGetVersionInfo LocalDB API function. /// - private static Functions.LocalDBGetVersionInfo _localDBGetVersionInfo; + private Functions.LocalDBGetVersionInfo _localDBGetVersionInfo; /// /// The delegate to the LocalDBGetVersions LocalDB API function. /// - private static Functions.LocalDBGetVersions _localDBGetVersions; + private Functions.LocalDBGetVersions _localDBGetVersions; /// /// The delegate to the LocalDBShareInstance LocalDB API function. /// - private static Functions.LocalDBShareInstance _localDBShareInstance; + private Functions.LocalDBShareInstance _localDBShareInstance; /// /// The delegate to the LocalDBStartInstance LocalDB API function. /// - private static Functions.LocalDBStartInstance _localDBStartInstance; + private Functions.LocalDBStartInstance _localDBStartInstance; /// /// The delegate to the LocalDBStartTracing LocalDB API function. /// - private static Functions.LocalDBStartTracing _localDBStartTracing; + private Functions.LocalDBStartTracing _localDBStartTracing; /// /// The delegate to the LocalDBStopInstance LocalDB API function. /// - private static Functions.LocalDBStopInstance _localDBStopInstance; + private Functions.LocalDBStopInstance _localDBStopInstance; /// /// The delegate to the LocalDBStopTracing LocalDB API function. /// - private static Functions.LocalDBStopTracing _localDBStopTracing; + private Functions.LocalDBStopTracing _localDBStopTracing; /// /// The delegate to the LocalDBUnshareInstance LocalDB API function. /// - private static Functions.LocalDBUnshareInstance _localDBUnshareInstance; + private Functions.LocalDBUnshareInstance _localDBUnshareInstance; /// - /// The to use. + /// Initializes a new instance of the class. /// - private static IRegistry _registry; + /// The version of the SQL LocalDB Instance API to load. + /// The to use. + /// The logger to use. + internal LocalDbInstanceApi(string apiVersion, IRegistry registry, ILogger logger) + { + ApiVersion = apiVersion; + Registry = registry; + Logger = logger; + } /// - /// Defines a method for opening a registry sub-key. + /// Finalizes an instance of the class. /// - internal interface IRegistry - { - /// - /// Retrieves a sub-key as read-only. - /// - /// The name or path of the sub-key to open as read-only. - /// - /// The sub-key requested, or if the operation failed. - /// - IRegistryKey OpenSubKey(string keyName); - } + ~LocalDbInstanceApi() => Dispose(false); /// - /// Defines a registry sub-key. + /// Gets the version of the SQL LocalDB native API loaded, if any. /// - internal interface IRegistryKey : IRegistry, IDisposable - { - /// - /// Retrieves an array of strings that contains all the sub-key names. - /// - /// - /// An array of strings that contains the names of the sub-keys for the current key. - /// - string[] GetSubKeyNames(); + internal Version NativeApiVersion { get; private set; } - /// - /// Retrieves the value associated with the specified name. - /// - /// The name of the value to retrieve. This string is not case-sensitive. - /// - /// The value associated with , or if is not found. - /// - string GetValue(string name); - } + /// + /// Gets the API version to use. + /// + private string ApiVersion { get; } /// - /// Gets the version of the SQL LocalDB native API loaded, if any. + /// Gets the to use. + /// + private ILogger Logger { get; } + + /// + /// Gets the to use. /// - internal static Version NativeApiVersion + private IRegistry Registry { get; } + + /// + public void Dispose() { - get; - private set; + Dispose(true); + GC.SuppressFinalize(this); } /// - /// Gets or sets the to use. + /// Marshals the specified of to a . /// - /// - /// Used for unit testing. - /// - internal static IRegistry Registry + /// The array to marshal as a . + /// + /// A representation of . + /// + internal static string MarshalString(byte[] bytes) { - get { return _registry ?? WindowsRegistry.Instance; } - set { _registry = value; } + Debug.Assert(bytes != null, "bytes cannot be null."); + return Encoding.Unicode.GetString(bytes).TrimEnd(_nullArray); } /// @@ -216,7 +191,7 @@ internal static IRegistry Registry /// The name for the LocalDB instance to create. /// Reserved for future use. Currently should be set to 0. /// The HRESULT returned by the LocalDB API. - internal static int CreateInstance(string wszVersion, string pInstanceName, int dwFlags) + internal int CreateInstance(string wszVersion, string pInstanceName, int dwFlags) { return EnsureFunctionAndInvoke( "LocalDBCreateInstance", @@ -230,7 +205,7 @@ internal static int CreateInstance(string wszVersion, string pInstanceName, int /// The name of the LocalDB instance to delete. /// Reserved for future use. Currently should be set to 0. /// The HRESULT returned by the LocalDB API. - internal static int DeleteInstance(string pInstanceName, int dwFlags) + internal int DeleteInstance(string pInstanceName, int dwFlags) { return EnsureFunctionAndInvoke( "LocalDBDeleteInstance", @@ -238,16 +213,6 @@ internal static int DeleteInstance(string pInstanceName, int dwFlags) (function) => function(pInstanceName, dwFlags)); } - /// - /// Frees a specified library. - /// - /// The handle to the module to free. - /// Whether the library was successfully unloaded. - [DllImport(KernelLibName)] - [return: MarshalAs(UnmanagedType.Bool)] - [SecurityCritical] - internal static extern bool FreeLibrary(IntPtr handle); - /// /// Returns information for the specified SQL Server Express LocalDB instance, /// such as whether it exists, the LocalDB version it uses, whether it is running, @@ -257,7 +222,7 @@ internal static int DeleteInstance(string pInstanceName, int dwFlags) /// The buffer to store the information about the LocalDB instance. /// Holds the size of the InstanceInfo buffer. /// The HRESULT returned by the LocalDB API. - internal static int GetInstanceInfo(string wszInstanceName, IntPtr pInstanceInfo, int dwInstanceInfoSize) + internal int GetInstanceInfo(string wszInstanceName, IntPtr pInstanceInfo, int dwInstanceInfoSize) { return EnsureFunctionAndInvoke( "LocalDBGetInstanceInfo", @@ -278,7 +243,7 @@ internal static int GetInstanceInfo(string wszInstanceName, IntPtr pInstanceInfo /// of LocalDB instances found on the user’s workstation. /// /// The HRESULT returned by the LocalDB API. - internal static int GetInstanceNames(IntPtr pInstanceNames, ref int lpdwNumberOfInstances) + internal int GetInstanceNames(IntPtr pInstanceNames, ref int lpdwNumberOfInstances) { var function = EnsureFunction("LocalDBGetInstances", ref _localDBGetInstances); @@ -303,7 +268,7 @@ internal static int GetInstanceNames(IntPtr pInstanceNames, ref int lpdwNumberOf /// excluding any trailing nulls. /// /// The HRESULT returned by the LocalDB API. - internal static int GetLocalDbError(int hrLocalDB, int dwLanguageId, StringBuilder wszMessage, ref int lpcchMessage) + internal int GetLocalDbError(int hrLocalDB, int dwLanguageId, StringBuilder wszMessage, ref int lpcchMessage) { var function = EnsureFunction("LocalDBFormatMessage", ref _localDBFormatMessage); @@ -312,7 +277,7 @@ internal static int GetLocalDbError(int hrLocalDB, int dwLanguageId, StringBuild return SqlLocalDbErrors.NotInstalled; } - return function(hrLocalDB, LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId, wszMessage, ref lpcchMessage); + return function(hrLocalDB, LocalDbTruncateErrorMessage, dwLanguageId, wszMessage, ref lpcchMessage); } /// @@ -324,7 +289,7 @@ internal static int GetLocalDbError(int hrLocalDB, int dwLanguageId, StringBuild /// The buffer to store the information about the LocalDB version. /// Holds the size of the VersionInfo buffer. /// The HRESULT returned by the LocalDB API. - internal static int GetVersionInfo(string wszVersionName, IntPtr pVersionInfo, int dwVersionInfoSize) + internal int GetVersionInfo(string wszVersionName, IntPtr pVersionInfo, int dwVersionInfoSize) { return EnsureFunctionAndInvoke( "LocalDBGetVersionInfo", @@ -341,7 +306,7 @@ internal static int GetVersionInfo(string wszVersionName, IntPtr pVersionInfo, i /// buffer. On output, holds the number of existing LocalDB versions. /// /// The HRESULT returned by the LocalDB API. - internal static int GetVersions(IntPtr pVersion, ref int lpdwNumberOfVersions) + internal int GetVersions(IntPtr pVersion, ref int lpdwNumberOfVersions) { var function = EnsureFunction("LocalDBGetVersions", ref _localDBGetVersions); @@ -362,7 +327,7 @@ internal static int GetVersions(IntPtr pVersion, ref int lpdwNumberOfVersions) /// The shared name for the LocalDB instance to share. /// Reserved for future use. Currently should be set to 0. /// The HRESULT returned by the LocalDB API. - internal static int ShareInstance(IntPtr pOwnerSID, string pInstancePrivateName, string pInstanceSharedName, int dwFlags) + internal int ShareInstance(IntPtr pOwnerSID, string pInstancePrivateName, string pInstanceSharedName, int dwFlags) { return EnsureFunctionAndInvoke( "LocalDBShareInstance", @@ -382,7 +347,7 @@ internal static int ShareInstance(IntPtr pOwnerSID, string pInstancePrivateName, /// too small, contains the required buffer size in characters, including any trailing nulls. /// /// The HRESULT returned by the LocalDB API. - internal static int StartInstance(string pInstanceName, int dwFlags, StringBuilder wszSqlConnection, ref int lpcchSqlConnection) + internal int StartInstance(string pInstanceName, int dwFlags, StringBuilder wszSqlConnection, ref int lpcchSqlConnection) { var function = EnsureFunction("LocalDBStartInstance", ref _localDBStartInstance); @@ -399,7 +364,7 @@ internal static int StartInstance(string pInstanceName, int dwFlags, StringBuild /// LocalDB instances owned by the current Windows user. /// /// The HRESULT returned by the LocalDB API. - internal static int StartTracing() + internal int StartTracing() { return EnsureFunctionAndInvoke( "LocalDBStartTracing", @@ -417,7 +382,7 @@ internal static int StartTracing() /// value is 0, this function will return immediately without waiting for the LocalDB instance to stop. /// /// The HRESULT returned by the LocalDB API. - internal static int StopInstance(string pInstanceName, StopInstanceOptions options, int ulTimeout) + internal int StopInstance(string pInstanceName, StopInstanceOptions options, int ulTimeout) { return EnsureFunctionAndInvoke( "LocalDBStopInstance", @@ -430,7 +395,7 @@ internal static int StopInstance(string pInstanceName, StopInstanceOptions optio /// instances owned by the current Windows user. /// /// The HRESULT returned by the LocalDB API. - internal static int StopTracing() + internal int StopTracing() { return EnsureFunctionAndInvoke( "LocalDBStopTracing", @@ -448,7 +413,7 @@ internal static int StopTracing() /// Reserved for future use. Currently should be set to 0. /// /// The HRESULT returned by the LocalDB API. - internal static int UnshareInstance(string pInstanceName, int dwFlags) + internal int UnshareInstance(string pInstanceName, int dwFlags) { return EnsureFunctionAndInvoke( "LocalDBUnshareInstance", @@ -456,19 +421,6 @@ internal static int UnshareInstance(string pInstanceName, int dwFlags) (function) => function(pInstanceName, dwFlags)); } - /// - /// Marshals the specified of to a . - /// - /// The array to marshal as a . - /// - /// A representation of . - /// - internal static string MarshalString(byte[] bytes) - { - Debug.Assert(bytes != null, "bytes cannot be null."); - return Encoding.Unicode.GetString(bytes).TrimEnd(_nullArray); - } - /// /// Tries to obtaining the path to the latest version of the SQL LocalDB /// native API DLL for the currently executing process. @@ -481,7 +433,7 @@ internal static string MarshalString(byte[] bytes) /// if the native API path was successfully found; /// otherwise . /// - internal static bool TryGetLocalDbApiPath(out string fileName) + internal bool TryGetLocalDbApiPath(out string fileName) { fileName = null; @@ -497,7 +449,7 @@ internal static bool TryGetLocalDbApiPath(out string fileName) if (key == null) { - Logger.Warning(Logger.TraceEvent.RegistryKeyNotFound, SR.NativeMethods_RegistryKeyNotFoundFormat, keyName); + Logger.RegistryKeyNotFound(keyName); return false; } @@ -508,7 +460,7 @@ internal static bool TryGetLocalDbApiPath(out string fileName) try { // Is there a setting overriding the version to load? - string overrideVersionString = SqlLocalDbConfig.NativeApiOverrideVersionString; + string overrideVersionString = ApiVersion; foreach (string versionString in key.GetSubKeyNames()) { @@ -518,19 +470,9 @@ internal static bool TryGetLocalDbApiPath(out string fileName) { version = new Version(versionString); } - catch (ArgumentException) - { - Logger.Warning(Logger.TraceEvent.InvalidRegistryKey, SR.NativeMethods_InvalidRegistryKeyNameFormat, versionString); - continue; - } - catch (FormatException) + catch (Exception ex) when (ex is ArgumentException || ex is FormatException || ex is OverflowException) { - Logger.Warning(Logger.TraceEvent.InvalidRegistryKey, SR.NativeMethods_InvalidRegistryKeyNameFormat, versionString); - continue; - } - catch (OverflowException) - { - Logger.Warning(Logger.TraceEvent.InvalidRegistryKey, SR.NativeMethods_InvalidRegistryKeyNameFormat, versionString); + Logger.InvalidRegistryKey(versionString); continue; } @@ -538,7 +480,7 @@ internal static bool TryGetLocalDbApiPath(out string fileName) overrideVersion == null && string.Equals(versionString, overrideVersionString, StringComparison.OrdinalIgnoreCase)) { - Logger.Verbose(Logger.TraceEvent.NativeApiVersionOverriddenByUser, SR.NativeMethods_ApiVersionOverriddenByUserFormat, version); + Logger.NativeApiVersionOverriddenByUser(version); overrideVersion = version; } @@ -551,19 +493,14 @@ internal static bool TryGetLocalDbApiPath(out string fileName) if (!string.IsNullOrEmpty(overrideVersionString) && overrideVersion == null) { - Logger.Warning( - Logger.TraceEvent.NativeApiVersionOverrideNotFound, - SR.NativeMethods_OverrideVersionNotFoundFormat, - overrideVersionString, - Environment.MachineName, - latestVersion); + Logger.NativeApiVersionOverrideNotFound(overrideVersionString); } Version versionToUse = overrideVersion ?? latestVersion; if (versionToUse != null) { - using (var subkey = key.OpenSubKey(versionToUse.ToString())) + using (IRegistryKey subkey = key.OpenSubKey(versionToUse.ToString())) { path = subkey.GetValue("InstanceAPIPath"); } @@ -578,13 +515,13 @@ internal static bool TryGetLocalDbApiPath(out string fileName) if (string.IsNullOrEmpty(path)) { - Logger.Warning(Logger.TraceEvent.NoNativeApiFound, SR.NativeMethods_NoNativeApiFound); + Logger.NativeApiNotFound(); return false; } if (!File.Exists(path)) { - Logger.Error(Logger.TraceEvent.NativeApiPathNotFound, SR.NativeMethods_NativeApiNotFoundFormat, path); + Logger.NativeApiLibraryNotFound(path); return false; } @@ -592,45 +529,6 @@ internal static bool TryGetLocalDbApiPath(out string fileName) return true; } - /// - /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). - /// - /// A handle to the DLL module that contains the function or variable. - /// The function or variable name, or the function's ordinal value. - /// - /// If the function succeeds, the return value is the address of the exported function or variable. - /// If the function fails, the return value is . - /// - /// - /// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683212%28v=vs.85%29.aspx. - /// - [DllImport(KernelLibName, BestFitMapping = false, CharSet = CharSet.Ansi, ThrowOnUnmappableChar = true)] - private static extern IntPtr GetProcAddress( - SafeLibraryHandle hModule, - [MarshalAs(UnmanagedType.LPStr)] - string lpProcName); - - /// - /// Loads the specified module into the address space of the calling process. - /// The specified module may cause other modules to be loaded. - /// - /// The name of the module. - /// This parameter is reserved for future use. It must be . - /// The action to be taken when loading the module. - /// - /// If the function succeeds, the return value is a handle to the module. - /// If the function fails, the return value is . - /// - /// - /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179%28v=vs.85%29.aspx. - /// - [DllImport(KernelLibName, BestFitMapping = false, CharSet = CharSet.Ansi, SetLastError = true, ThrowOnUnmappableChar = true)] - private static extern SafeLibraryHandle LoadLibraryEx( - [MarshalAs(UnmanagedType.LPStr)] - string lpFileName, - IntPtr hFile, - int dwFlags); - /// /// Ensures that the specified delegate to an unmanaged function is initialized. /// @@ -641,8 +539,8 @@ private static extern SafeLibraryHandle LoadLibraryEx( /// An instance of that points to the specified unmanaged /// function, if found; otherwise . /// - private static T EnsureFunction(string functionName, ref T function) - where T : class + private T EnsureFunction(string functionName, ref T function) + where T : Delegate { Debug.Assert(functionName != null, "functionName cannot be null."); @@ -671,8 +569,8 @@ private static T EnsureFunction(string functionName, ref T function) /// The result of invoking , if the function was /// initialized; otherwise the value of is returned. /// - private static int EnsureFunctionAndInvoke(string functionName, ref T function, Func callback) - where T : class + private int EnsureFunctionAndInvoke(string functionName, ref T function, Func callback) + where T : Delegate { Debug.Assert(callback != null, "callback cannot be null."); @@ -688,13 +586,13 @@ private static int EnsureFunctionAndInvoke(string functionName, ref T functio /// A pointing to the loaded /// SQL LocalDB API, if successful; otherwise . /// - private static SafeLibraryHandle EnsureLocalDBLoaded() + private SafeLibraryHandle EnsureLocalDBLoaded() { - if (_localDB == null) + if (_handle == null) { lock (_syncRoot) { - if (_localDB == null) + if (_handle == null) { if (!TryGetLocalDbApiPath(out string fileName)) { @@ -707,36 +605,35 @@ private static SafeLibraryHandle EnsureLocalDBLoaded() // to use the more secure flags when calling LoadLibraryEx bool hasKB2533623; - using (var hModule = LoadLibraryEx(KernelLibName, IntPtr.Zero, 0)) + using (var hModule = NativeMethods.LoadLibraryEx(NativeMethods.KernelLibName, IntPtr.Zero, 0)) { // If the AddDllDirectory function is found then the flags are supported - hasKB2533623 = GetProcAddress(hModule, "AddDllDirectory") != IntPtr.Zero; + hasKB2533623 = NativeMethods.GetProcAddress(hModule, "AddDllDirectory") != IntPtr.Zero; } if (hasKB2533623) { // If KB2533623 is installed then specify the more secure LOAD_LIBRARY_SEARCH_DEFAULT_DIRS in dwFlags - dwFlags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; + dwFlags = NativeMethods.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; } - _localDB = LoadLibraryEx(fileName, IntPtr.Zero, dwFlags); + _handle = NativeMethods.LoadLibraryEx(fileName, IntPtr.Zero, dwFlags); - if (_localDB == null || - _localDB.IsInvalid) + if (_handle == null || _handle.IsInvalid) { int error = Marshal.GetLastWin32Error(); - Logger.Error(Logger.TraceEvent.NativeApiLoadFailed, SR.NativeMethods_NativeApiLoadFailedFormat, fileName, error); - _localDB = null; + Logger.NativeApiLoadFailed(fileName, error); + _handle = null; } else { - Logger.Verbose(Logger.TraceEvent.NativeApiLoaded, SR.NativeMethods_NativeApiLoadedFormat, fileName); + Logger.NativeApiLoaded(fileName); } } } } - return _localDB; + return _handle; } /// @@ -748,8 +645,8 @@ private static SafeLibraryHandle EnsureLocalDBLoaded() /// An instance of that points to the specified unmanaged /// function, if found; otherwise . /// - private static T GetDelegate(string functionName) - where T : class + private T GetDelegate(string functionName) + where T : Delegate { Debug.Assert(functionName != null, "functionName cannot be null."); @@ -757,21 +654,43 @@ private static T GetDelegate(string functionName) if (handle == null) { - Logger.Warning(Logger.TraceEvent.NativeApiNotLoaded, SR.NativeMethods_NativeApiNotLoaded); + Logger.NativeApiNotLoaded(); return null; } - IntPtr ptr = GetProcAddress(handle, functionName); + IntPtr ptr = NativeMethods.GetProcAddress(handle, functionName); if (ptr == IntPtr.Zero) { - Logger.Error(Logger.TraceEvent.FunctionNotFound, SR.NativeMethods_FunctionNotFoundFormat, functionName); + Logger.NativeApiFunctionNotFound(functionName); return null; } return Marshal.GetDelegateForFunctionPointer(ptr); } + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // Dispose of managed resources + } + + // Dispose of unmanaged resources + _handle?.Dispose(); + _disposed = true; + } + } + /// /// A class containing delegates to functions in the SQL LocalDB native API. /// @@ -846,8 +765,7 @@ internal delegate int LocalDBFormatMessage( /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int LocalDBGetInstanceInfo( - [MarshalAs(UnmanagedType.LPWStr)] - string wszInstanceName, + [MarshalAs(UnmanagedType.LPWStr)] string wszInstanceName, IntPtr pInstanceInfo, int dwInstanceInfoSize); @@ -884,8 +802,7 @@ internal delegate int LocalDBGetInstanceInfo( /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int LocalDBGetVersionInfo( - [MarshalAs(UnmanagedType.LPWStr)] - string wszVersionName, + [MarshalAs(UnmanagedType.LPWStr)] string wszVersionName, IntPtr pVersionInfo, int dwVersionInfoSize); @@ -971,8 +888,7 @@ internal delegate int LocalDBStartInstance( /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int LocalDBStopInstance( - [MarshalAs(UnmanagedType.LPWStr)] - string pInstanceName, + [MarshalAs(UnmanagedType.LPWStr)] string pInstanceName, int dwFlags, int ulTimeout); @@ -997,74 +913,8 @@ internal delegate int LocalDBStopInstance( /// See http://technet.microsoft.com/en-us/library/hh215383.aspx. /// internal delegate int LocalDBUnshareInstance( - [MarshalAs(UnmanagedType.LPWStr)] - string pInstanceName, + [MarshalAs(UnmanagedType.LPWStr)] string pInstanceName, int dwFlags); } - - /// - /// A class representing an implementation of for the Windows registry. This class cannot be inherited. - /// - private sealed class WindowsRegistry : IRegistry - { - /// - /// The singleton instance of . This field is read-only. - /// - internal static readonly WindowsRegistry Instance = new WindowsRegistry(); - - /// - /// Prevents a default instance of the class from being created. - /// - private WindowsRegistry() - { - } - - /// - public IRegistryKey OpenSubKey(string keyName) - { - var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyName, writable: false); - return key == null ? null : new WindowsRegistryKey(key); - } - } - - /// - /// A class representing an implementation of for a Windows registry key. This class cannot be inherited. - /// - private sealed class WindowsRegistryKey : IRegistryKey - { - /// - /// The wrapped by the instance. This field is read-only. - /// - private readonly RegistryKey _key; - - /// - /// Initializes a new instance of the class. - /// - /// The to wrap. - internal WindowsRegistryKey(RegistryKey key) - { - Debug.Assert(key != null, "key cannot be null."); - _key = key; - } - - /// - void IDisposable.Dispose() - { - _key.Dispose(); - } - - /// - public string[] GetSubKeyNames() => _key.GetSubKeyNames(); - - /// - public string GetValue(string name) => _key.GetValue(name, null, RegistryValueOptions.None) as string; - - /// - public IRegistryKey OpenSubKey(string keyName) - { - var key = _key.OpenSubKey(keyName); - return key == null ? null : new WindowsRegistryKey(key); - } - } } } diff --git a/src/SqlLocalDb/LocalDbInstanceInfo.cs b/src/SqlLocalDb/Interop/LocalDbInstanceInfo.cs similarity index 82% rename from src/SqlLocalDb/LocalDbInstanceInfo.cs rename to src/SqlLocalDb/Interop/LocalDbInstanceInfo.cs index 4b19cdfb..935ce411 100644 --- a/src/SqlLocalDb/LocalDbInstanceInfo.cs +++ b/src/SqlLocalDb/Interop/LocalDbInstanceInfo.cs @@ -1,19 +1,11 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// LocalDBInstanceInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Diagnostics; using System.Runtime.InteropServices; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb.Interop { /// /// A structure representing information about a SQL Server LocalDB instance. @@ -46,7 +38,7 @@ internal struct LocalDbInstanceInfo : ISqlLocalDbInstanceInfo /// [MarshalAs( UnmanagedType.ByValArray, - SizeConst = (NativeMethods.MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1) * sizeof(char))] + SizeConst = (LocalDbInstanceApi.MaximumInstanceNameLength + 1) * sizeof(char))] internal byte[] InstanceName; /// @@ -111,7 +103,7 @@ internal struct LocalDbInstanceInfo : ISqlLocalDbInstanceInfo /// /// Maps to the ftLastStartUTC member. /// - internal Runtime.InteropServices.ComTypes.FILETIME LastStartUtc; + internal System.Runtime.InteropServices.ComTypes.FILETIME LastStartUtc; /// /// The named pipe that should be used to communicate with the instance. @@ -121,7 +113,7 @@ internal struct LocalDbInstanceInfo : ISqlLocalDbInstanceInfo /// [MarshalAs( UnmanagedType.ByValArray, - SizeConst = NativeMethods.LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE * sizeof(char))] + SizeConst = LocalDbInstanceApi.MaximumSqlConnectionStringBufferLength * sizeof(char))] internal byte[] Connection; /// @@ -140,7 +132,7 @@ internal struct LocalDbInstanceInfo : ISqlLocalDbInstanceInfo /// [MarshalAs( UnmanagedType.ByValArray, - SizeConst = (NativeMethods.MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1) * sizeof(char))] + SizeConst = (LocalDbInstanceApi.MaximumInstanceNameLength + 1) * sizeof(char))] internal byte[] SharedInstanceName; /// @@ -151,7 +143,7 @@ internal struct LocalDbInstanceInfo : ISqlLocalDbInstanceInfo /// [MarshalAs( UnmanagedType.ByValArray, - SizeConst = (NativeMethods.MAX_STRING_SID_LENGTH + 1) * sizeof(char))] + SizeConst = (LocalDbInstanceApi.MaximumSidStringLength + 1) * sizeof(char))] internal byte[] OwnerSID; /// @@ -213,28 +205,28 @@ DateTime ISqlLocalDbInstanceInfo.LastStartTimeUtc /// /// Gets the name of the instance. /// - string ISqlLocalDbInstanceInfo.Name => NativeMethods.MarshalString(InstanceName); + string ISqlLocalDbInstanceInfo.Name => LocalDbInstanceApi.MarshalString(InstanceName); /// /// Gets the named pipe that should be used to communicate with the instance. /// - string ISqlLocalDbInstanceInfo.NamedPipe => NativeMethods.MarshalString(Connection); + string ISqlLocalDbInstanceInfo.NamedPipe => LocalDbInstanceApi.MarshalString(Connection); /// /// Gets the SID of the LocalDB instance owner if the instance is shared. /// - string ISqlLocalDbInstanceInfo.OwnerSid => NativeMethods.MarshalString(OwnerSID); + string ISqlLocalDbInstanceInfo.OwnerSid => LocalDbInstanceApi.MarshalString(OwnerSID); /// /// Gets the shared name of the LocalDB instance if the instance is shared. /// - string ISqlLocalDbInstanceInfo.SharedName => NativeMethods.MarshalString(SharedInstanceName); + string ISqlLocalDbInstanceInfo.SharedName => LocalDbInstanceApi.MarshalString(SharedInstanceName); /// /// Gets the name to display in the debugger /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] private string DebuggerDisplayName => ((ISqlLocalDbInstanceInfo)this).Name; } } diff --git a/src/SqlLocalDb/LocalDbVersionInfo.cs b/src/SqlLocalDb/Interop/LocalDbVersionInfo.cs similarity index 75% rename from src/SqlLocalDb/LocalDbVersionInfo.cs rename to src/SqlLocalDb/Interop/LocalDbVersionInfo.cs index a13ef98e..53919467 100644 --- a/src/SqlLocalDb/LocalDbVersionInfo.cs +++ b/src/SqlLocalDb/Interop/LocalDbVersionInfo.cs @@ -1,28 +1,19 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// LocalDBVersionInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Diagnostics; using System.Runtime.InteropServices; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb.Interop { /// - /// A structure representing version about an SQL Server LocalDB version. + /// A structure representing version about a SQL Server LocalDB version. /// /// /// See http://msdn.microsoft.com/en-us/library/hh234365.aspx. /// [DebuggerDisplay("{DebuggerDisplayName}")] - [Serializable] [StructLayout(LayoutKind.Sequential)] internal struct LocalDbVersionInfo : ISqlLocalDbVersionInfo { @@ -45,7 +36,7 @@ internal struct LocalDbVersionInfo : ISqlLocalDbVersionInfo /// /// Maps to the wszVersion member. /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = (NativeMethods.MAX_LOCALDB_VERSION_LENGTH + 1) * sizeof(char))] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = (LocalDbInstanceApi.MaximumInstanceVersionLength + 1) * sizeof(char))] internal byte[] Name; /// @@ -96,7 +87,7 @@ internal struct LocalDbVersionInfo : ISqlLocalDbVersionInfo /// /// Gets the version name. /// - string ISqlLocalDbVersionInfo.Name => NativeMethods.MarshalString(Name); + string ISqlLocalDbVersionInfo.Name => LocalDbInstanceApi.MarshalString(Name); /// /// Gets the version. @@ -107,7 +98,7 @@ internal struct LocalDbVersionInfo : ISqlLocalDbVersionInfo /// Gets the name to display in the debugger /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] - [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] private string DebuggerDisplayName => ((ISqlLocalDbVersionInfo)this).Name; } } diff --git a/src/SqlLocalDb/Interop/NativeMethods.cs b/src/SqlLocalDb/Interop/NativeMethods.cs new file mode 100644 index 00000000..31a3b69c --- /dev/null +++ b/src/SqlLocalDb/Interop/NativeMethods.cs @@ -0,0 +1,74 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +namespace MartinCostello.SqlLocalDb.Interop +{ + /// + /// A class containing native P/Invoke methods. This class cannot be inherited. + /// + internal static class NativeMethods + { + /// + /// This value represents the recommended maximum number of directories an application should include in its DLL search path. + /// + /// + /// Only supported on Windows Vista, 7, Server 2008 and Server 2008 R2 with KB2533623. + /// See https://docs.microsoft.com/en-gb/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibraryexa. + /// + internal const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000; + + /// + /// The name of the Windows Kernel library. + /// + internal const string KernelLibName = "kernel32.dll"; + + /// + /// Frees a specified library. + /// + /// The handle to the module to free. + /// Whether the library was successfully unloaded. + [DllImport(KernelLibName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FreeLibrary(IntPtr handle); + + /// + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// + /// A handle to the DLL module that contains the function or variable. + /// The function or variable name, or the function's ordinal value. + /// + /// If the function succeeds, the return value is the address of the exported function or variable. + /// If the function fails, the return value is . + /// + /// + /// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683212%28v=vs.85%29.aspx. + /// + [DllImport(KernelLibName, BestFitMapping = false, CharSet = CharSet.Ansi, ThrowOnUnmappableChar = true)] + internal static extern IntPtr GetProcAddress( + SafeLibraryHandle hModule, + [MarshalAs(UnmanagedType.LPStr)] string lpProcName); + + /// + /// Loads the specified module into the address space of the calling process. + /// The specified module may cause other modules to be loaded. + /// + /// The name of the module. + /// This parameter is reserved for future use. It must be . + /// The action to be taken when loading the module. + /// + /// If the function succeeds, the return value is a handle to the module. + /// If the function fails, the return value is . + /// + /// + /// See https://docs.microsoft.com/en-gb/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibraryexa. + /// + [DllImport(KernelLibName, BestFitMapping = false, CharSet = CharSet.Ansi, SetLastError = true, ThrowOnUnmappableChar = true)] + internal static extern SafeLibraryHandle LoadLibraryEx( + [MarshalAs(UnmanagedType.LPStr)] string lpFileName, + IntPtr hFile, + int dwFlags); + } +} diff --git a/src/SqlLocalDb/SafeLibraryHandle.cs b/src/SqlLocalDb/Interop/SafeLibraryHandle.cs similarity index 60% rename from src/SqlLocalDb/SafeLibraryHandle.cs rename to src/SqlLocalDb/Interop/SafeLibraryHandle.cs index c662f384..3a926061 100644 --- a/src/SqlLocalDb/SafeLibraryHandle.cs +++ b/src/SqlLocalDb/Interop/SafeLibraryHandle.cs @@ -1,24 +1,13 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SafeLibraryHandle.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -using System.Security; using Microsoft.Win32.SafeHandles; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb.Interop { /// /// A class that represents a handle to a library. This class cannot be inherited. /// - [SecurityCritical] internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid { /// @@ -38,7 +27,6 @@ private SafeLibraryHandle() /// . In this case, it generates a ReleaseHandleFailed /// Managed Debugging Assistant. /// - [SecurityCritical] protected override bool ReleaseHandle() => NativeMethods.FreeLibrary(handle); } } diff --git a/src/SqlLocalDb/Interop/WindowsRegistry.cs b/src/SqlLocalDb/Interop/WindowsRegistry.cs new file mode 100644 index 00000000..94c1a74d --- /dev/null +++ b/src/SqlLocalDb/Interop/WindowsRegistry.cs @@ -0,0 +1,20 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.Win32; + +namespace MartinCostello.SqlLocalDb.Interop +{ + /// + /// A class representing an implementation of for the Windows registry. This class cannot be inherited. + /// + internal sealed class WindowsRegistry : IRegistry + { + /// + public IRegistryKey OpenSubKey(string keyName) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName, writable: false); + return key == null ? null : new WindowsRegistryKey(key); + } + } +} diff --git a/src/SqlLocalDb/Interop/WindowsRegistryKey.cs b/src/SqlLocalDb/Interop/WindowsRegistryKey.cs new file mode 100644 index 00000000..605bc4f5 --- /dev/null +++ b/src/SqlLocalDb/Interop/WindowsRegistryKey.cs @@ -0,0 +1,46 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using Microsoft.Win32; + +namespace MartinCostello.SqlLocalDb.Interop +{ + /// + /// A class representing an implementation of for a Windows registry key. This class cannot be inherited. + /// + internal sealed class WindowsRegistryKey : IRegistryKey + { + /// + /// The wrapped by the instance. This field is read-only. + /// + private readonly RegistryKey _key; + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + internal WindowsRegistryKey(RegistryKey key) + { + Debug.Assert(key != null, "key cannot be null."); + _key = key; + } + + /// + void IDisposable.Dispose() => _key.Dispose(); + + /// + public string[] GetSubKeyNames() => _key.GetSubKeyNames(); + + /// + public string GetValue(string name) => _key.GetValue(name, null, RegistryValueOptions.None) as string; + + /// + public IRegistryKey OpenSubKey(string keyName) + { + RegistryKey key = _key.OpenSubKey(keyName); + return key == null ? null : new WindowsRegistryKey(key); + } + } +} diff --git a/src/SqlLocalDb/Logger.cs b/src/SqlLocalDb/Logger.cs deleted file mode 100644 index b1d323d8..00000000 --- a/src/SqlLocalDb/Logger.cs +++ /dev/null @@ -1,321 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// Logger.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Diagnostics; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class that performs logging for the System.Data.SqlLocalDb assembly. - /// - [DebuggerStepThrough] - [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public static class Logger - { - /// - /// The lazily-initialized default to use. This field is read-only. - /// - internal static readonly Lazy DefaultLogger = new Lazy(CreateDefaultLogger); - - /// - /// The Trace condition string. - /// - private const string TraceCondition = "TRACE"; - - /// - /// The to use. - /// - private static ILogger _logger; - - /// - /// Gets the current . - /// - private static ILogger Current - { - get { return _logger ?? DefaultLogger.Value; } - } - - /// - /// Sets the implementation in use by the assembly. - /// - /// The to use, or to use the default implementation. - [Conditional(TraceCondition)] - public static void SetLogger(ILogger logger) - { - _logger = logger ?? DefaultLogger.Value; - } - - /// - /// Writes an error trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - [Conditional(TraceCondition)] - public static void Error(int id, string format, params object[] args) - { - Current.WriteError(id, format, args); - } - - /// - /// Writes an informational trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - [Conditional(TraceCondition)] - public static void Information(int id, string format, params object[] args) - { - Current.WriteInformation(id, format, args); - } - - /// - /// Writes a verbose trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - [Conditional(TraceCondition)] - public static void Verbose(int id, string format, params object[] args) - { - Current.WriteVerbose(id, format, args); - } - - /// - /// Writes a warning trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - [Conditional(TraceCondition)] - public static void Warning(int id, string format, params object[] args) - { - Current.WriteWarning(id, format, args); - } - - /// - /// Creates the default implementation to use. - /// - /// - /// The default implementation of . - /// - private static ILogger CreateDefaultLogger() - { - Type loggerType = SqlLocalDbConfig.LoggerType; - - ILogger logger; - - if (loggerType == null) - { - logger = TraceSourceLogger.Instance; - } - else - { - try - { - // This cast is safe as the configuration section validates that the type implements ILogger - logger = (ILogger)Activator.CreateInstance(loggerType, nonPublic: true); - } - catch (Reflection.TargetInvocationException ex) - { - // Log directly to Trace if we cannot create the custom ILogger - Trace.TraceError(SR.Logger_FailedToCreateCustomLoggerFormat, loggerType.AssemblyQualifiedName, (ex.InnerException ?? ex).Message); - throw; - } - catch (Exception ex) - { - // Log directly to Trace if we cannot create the custom ILogger - Trace.TraceError(SR.Logger_FailedToCreateCustomLoggerFormat, loggerType.AssemblyQualifiedName, ex.Message); - throw; - } - } - - return logger; - } - - /// - /// A class containing trace event Ids. This class cannot be inherited. - /// - internal static class TraceEvent - { - /// - /// General usage. - /// - internal static readonly int General = 0; - - /// - /// Creating a SQL LocalDB instance. - /// - internal static readonly int CreateInstance = 1; - - /// - /// Deleting a SQL LocalDB instance. - /// - internal static readonly int DeleteInstance = 2; - - /// - /// Getting information about a SQL LocalDB instance. - /// - internal static readonly int GetInstanceInfo = 3; - - /// - /// Getting instance names for SQL LocalDB. - /// - internal static readonly int GetInstanceNames = 4; - - /// - /// Getting version information for SQL LocalDB. - /// - internal static readonly int GetVersionInfo = 5; - - /// - /// Getting installed versions of SQL LocalDB. - /// - internal static readonly int GetVersions = 6; - - /// - /// Sharing a SQL LocalDB instance. - /// - internal static readonly int ShareInstance = 7; - - /// - /// Starting a SQL LocalDB instance. - /// - internal static readonly int StartInstance = 8; - - /// - /// Starting tracing for a SQL LocalDB. - /// - internal static readonly int StartTracing = 9; - - /// - /// Stopping a SQL LocalDB instance. - /// - internal static readonly int StopInstance = 10; - - /// - /// Stopping tracing for SQL LocalDB. - /// - internal static readonly int StopTracing = 11; - - /// - /// Stopping sharing of an instance of SQL LocalDB. - /// - internal static readonly int UnshareInstance = 12; - - /// - /// The SQL LocalDB registry key could not be found or opened. - /// - internal static readonly int RegistryKeyNotFound = 13; - - /// - /// An invalid registry key was processed. - /// - internal static readonly int InvalidRegistryKey = 14; - - /// - /// An invalid registry key was processed. - /// - internal static readonly int NoNativeApiFound = 15; - - /// - /// The native SQL LocalDB API DLL could not be found. - /// - internal static readonly int NativeApiPathNotFound = 16; - - /// - /// The native SQL LocalDB API DLL failed to load. - /// - internal static readonly int NativeApiLoadFailed = 17; - - /// - /// The native SQL LocalDB API DLL was not loaded. - /// - internal static readonly int NativeApiNotLoaded = 18; - - /// - /// The native SQL LocalDB API function could not be found. - /// - internal static readonly int FunctionNotFound = 19; - - /// - /// The native SQL LocalDB API was loaded. - /// - internal static readonly int NativeApiLoaded = 20; - - /// - /// The version of the native SQL LocalDB API loaded was overridden by the user. - /// - internal static readonly int NativeApiVersionOverriddenByUser = 21; - - /// - /// A user instance of SQL LocalDB could not be deleted as it is in use. - /// - internal static readonly int DeleteFailedAsInstanceInUse = 22; - - /// - /// The files(s) for a user instance of SQL LocalDB are being deleted. - /// - internal static readonly int DeletingInstanceFiles = 23; - - /// - /// The files(s) for a user instance of SQL LocalDB were deleted. - /// - internal static readonly int DeletedInstanceFiles = 24; - - /// - /// The files(s) for a user instance of SQL LocalDB could not be deleted. - /// - internal static readonly int DeletingInstanceFilesFailed = 25; - - /// - /// The value of the property is invalid. - /// - internal static readonly int InvalidLanguageId = 26; - - /// - /// The version of the native SQL LocalDB API specified as the override by the user cannot be found. - /// - internal static readonly int NativeApiVersionOverrideNotFound = 27; - - /// - /// A user instance of SQL LocalDB could not be stopped. - /// - internal static readonly int StopFailed = 28; - - /// - /// A user instance of SQL LocalDB could not be deleted. - /// - internal static readonly int DeleteFailed = 29; - } - } -} diff --git a/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj b/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj new file mode 100644 index 00000000..d0a61fe1 --- /dev/null +++ b/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj @@ -0,0 +1,26 @@ + + + SQL LocalDB Wrapper + full + A .NET assembly providing interop with the SQL LocalDB native API from managed code using .NET APIs. + true + $(NoWarn);CA2235 + Library + MartinCostello.SqlLocalDb + MartinCostello.SqlLocalDb + $(Description) + netstandard2.0 + + + True + + + + + + + + + + + diff --git a/src/SqlLocalDb/Properties/AssemblyInfo.cs b/src/SqlLocalDb/Properties/AssemblyInfo.cs index cb60e6a4..919cbb67 100644 --- a/src/SqlLocalDb/Properties/AssemblyInfo.cs +++ b/src/SqlLocalDb/Properties/AssemblyInfo.cs @@ -1,22 +1,12 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// AssemblyInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: CLSCompliant(true)] [assembly: Guid("963628d3-b23b-4b98-9cec-b0a13b00ddef")] -[assembly: InternalsVisibleTo("System.Data.SqlLocalDb.TestApp, PublicKey=00240000048000009400000006020000002400005253413100040000010001004b0b2efbada897147aa03d2076278890aefe2f8023562336d206ec8a719b06e89461c31b43abec615918d509158629f93385930c030494509e418bf396d69ce7dbe0b5b2db1a81543ab42777cb98210677fed69dbeb3237492a7ad69e87a1911ed20eb2d7c300238dc6f6403e3d04a1351c5cb369de4e022b18fbec70f7d21ed")] -[assembly: InternalsVisibleTo("System.Data.SqlLocalDb.UnitTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001004b0b2efbada897147aa03d2076278890aefe2f8023562336d206ec8a719b06e89461c31b43abec615918d509158629f93385930c030494509e418bf396d69ce7dbe0b5b2db1a81543ab42777cb98210677fed69dbeb3237492a7ad69e87a1911ed20eb2d7c300238dc6f6403e3d04a1351c5cb369de4e022b18fbec70f7d21ed")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("MartinCostello.SqlLocalDb.TestApp, PublicKey=00240000048000009400000006020000002400005253413100040000010001004b0b2efbada897147aa03d2076278890aefe2f8023562336d206ec8a719b06e89461c31b43abec615918d509158629f93385930c030494509e418bf396d69ce7dbe0b5b2db1a81543ab42777cb98210677fed69dbeb3237492a7ad69e87a1911ed20eb2d7c300238dc6f6403e3d04a1351c5cb369de4e022b18fbec70f7d21ed")] +[assembly: InternalsVisibleTo("MartinCostello.SqlLocalDb.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001004b0b2efbada897147aa03d2076278890aefe2f8023562336d206ec8a719b06e89461c31b43abec615918d509158629f93385930c030494509e418bf396d69ce7dbe0b5b2db1a81543ab42777cb98210677fed69dbeb3237492a7ad69e87a1911ed20eb2d7c300238dc6f6403e3d04a1351c5cb369de4e022b18fbec70f7d21ed")] diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index e64e5140..d9214232 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace System.Data.SqlLocalDb { +namespace MartinCostello.SqlLocalDb { using System; @@ -19,7 +19,7 @@ namespace System.Data.SqlLocalDb { // 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class SR { @@ -39,7 +39,7 @@ internal SR() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Data.SqlLocalDb.SR", typeof(SR).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MartinCostello.SqlLocalDb.SR", typeof(SR).Assembly); resourceMan = temp; } return resourceMan; @@ -61,434 +61,434 @@ internal SR() { } /// - /// Looks up a localized string similar to The specified database file name is invalid: {0}. + /// Looks up a localized string similar to The SQL LocalDB native API version to load was overridden by the user to {0}.. /// - internal static string Extensions_InvalidPathFormat { + internal static string ILoggerExtensions_ApiVersionOverriddenByUserFormat { get { - return ResourceManager.GetString("Extensions_InvalidPathFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_ApiVersionOverriddenByUserFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The current AppDomain has no value set for the Data Directory.. + /// Looks up a localized string similar to Created named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. /// - internal static string Extensions_NoAppDomainDataDirectory { + internal static string ILoggerExtensions_CreatedInstanceFormat { get { - return ResourceManager.GetString("Extensions_NoAppDomainDataDirectory", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_CreatedInstanceFormat", resourceCulture); } } /// - /// Looks up a localized string similar to No connection string named '{0}' can be found in the application configuration file.. + /// Looks up a localized string similar to Creating named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. /// - internal static string Extensions_NoConnectionStringFormat { + internal static string ILoggerExtensions_CreatingInstanceFormat { get { - return ResourceManager.GetString("Extensions_NoConnectionStringFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_CreatingInstanceFormat", resourceCulture); } } /// - /// Looks up a localized string similar to No connection strings are configured in the application configuration file.. + /// Looks up a localized string similar to Deleted file(s) for SQL LocalDB instance '{0}' from '{1}'.. /// - internal static string Extensions_NoConnectionStrings { + internal static string ILoggerExtensions_DeletedInstanceFilesFormat { get { - return ResourceManager.GetString("Extensions_NoConnectionStrings", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeletedInstanceFilesFormat", resourceCulture); } } /// - /// Looks up a localized string similar to No named pipe is associated with the specified SQL LocalDB instance.. + /// Looks up a localized string similar to Deleted named instance of SQL LocalDB '{0}'.. /// - internal static string Extensions_NoNamedPipe { + internal static string ILoggerExtensions_DeletedInstanceFormat { get { - return ResourceManager.GetString("Extensions_NoNamedPipe", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeletedInstanceFormat", resourceCulture); } } /// - /// Looks up a localized string similar to More than one connection string is configured in the application configuration file. The connection string name must be explicitly specified. Other connection strings may be inherited from higher-level configuration files such as machine.config.. + /// Looks up a localized string similar to The SQL LocalDB instance '{0}' could not be deleted as it is currently in use.. /// - internal static string Extensions_NoSingleConnectionString { + internal static string ILoggerExtensions_DeleteFailedAsInUseFormat { get { - return ResourceManager.GetString("Extensions_NoSingleConnectionString", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeleteFailedAsInUseFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to create an instance of type '{0}': {1}. + /// Looks up a localized string similar to Failed to delete SQL LocalDB instance '{0}'. HRESULT = {1:X}.. /// - internal static string Logger_FailedToCreateCustomLoggerFormat { + internal static string ILoggerExtensions_DeleteFailedFormat { get { - return ResourceManager.GetString("Logger_FailedToCreateCustomLoggerFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeleteFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL LocalDB native API version to load was overridden by the user to {0}.. + /// Looks up a localized string similar to Deleting named instance of SQL LocalDB '{0}'.. /// - internal static string NativeMethods_ApiVersionOverriddenByUserFormat { + internal static string ILoggerExtensions_DeletingFormat { get { - return ResourceManager.GetString("NativeMethods_ApiVersionOverriddenByUserFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeletingFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL LocalDB function {0} could not be found.. + /// Looks up a localized string similar to Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}'.. /// - internal static string NativeMethods_FunctionNotFoundFormat { + internal static string ILoggerExtensions_DeletingInstanceFilesFailedFormat { get { - return ResourceManager.GetString("NativeMethods_FunctionNotFoundFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeletingInstanceFilesFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Ignoring invalid registry key name '{0}'.. + /// Looks up a localized string similar to Deleting file(s) for SQL LocalDB instance '{0}' from '{1}'.. /// - internal static string NativeMethods_InvalidRegistryKeyNameFormat { + internal static string ILoggerExtensions_DeletingInstanceFilesFormat { get { - return ResourceManager.GetString("NativeMethods_InvalidRegistryKeyNameFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_DeletingInstanceFilesFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Loaded SQL LocalDB API from '{0}'.. + /// Looks up a localized string similar to The SQL LocalDB function {0} could not be found.. /// - internal static string NativeMethods_NativeApiLoadedFormat { + internal static string ILoggerExtensions_FunctionNotFoundFormat { get { - return ResourceManager.GetString("NativeMethods_NativeApiLoadedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_FunctionNotFoundFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to load SQL LocalDB API from '{0}'. Error: {1}.. + /// Looks up a localized string similar to Obtaining instance names for SQL LocalDB.. /// - internal static string NativeMethods_NativeApiLoadFailedFormat { + internal static string ILoggerExtensions_GetInstances { get { - return ResourceManager.GetString("NativeMethods_NativeApiLoadFailedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GetInstances", resourceCulture); } } /// - /// Looks up a localized string similar to Could not find SQL LocalDB API DLL '{0}'.. + /// Looks up a localized string similar to Obtaining information for SQL LocalDB instance '{0}'.. /// - internal static string NativeMethods_NativeApiNotFoundFormat { + internal static string ILoggerExtensions_GettingInfoFormat { get { - return ResourceManager.GetString("NativeMethods_NativeApiNotFoundFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GettingInfoFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL LocalDB API was not loaded.. + /// Looks up a localized string similar to Obtaining version information for SQL LocalDB version '{0}'.. /// - internal static string NativeMethods_NativeApiNotLoaded { + internal static string ILoggerExtensions_GetVersionInfoFormat { get { - return ResourceManager.GetString("NativeMethods_NativeApiNotLoaded", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GetVersionInfoFormat", resourceCulture); } } /// - /// Looks up a localized string similar to No SQL LocalDB API DLL path could be found.. + /// Looks up a localized string similar to Obtaining versions for SQL LocalDB.. /// - internal static string NativeMethods_NoNativeApiFound { + internal static string ILoggerExtensions_GetVersions { get { - return ResourceManager.GetString("NativeMethods_NoNativeApiFound", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GetVersions", resourceCulture); } } /// - /// Looks up a localized string similar to The configured SQL LocalDB Instance API override version '{0}' cannot be found on {1}.. + /// Looks up a localized string similar to Obtained information for SQL LocalDB instance '{0}'.. /// - internal static string NativeMethods_OverrideVersionNotFoundFormat { + internal static string ILoggerExtensions_GotInfoFormat { get { - return ResourceManager.GetString("NativeMethods_OverrideVersionNotFoundFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GotInfoFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Could not open registry key '{0}'.. + /// Looks up a localized string similar to Obtained {0} instance names for SQL LocalDB.. /// - internal static string NativeMethods_RegistryKeyNotFoundFormat { + internal static string ILoggerExtensions_GotInstancesFormat { get { - return ResourceManager.GetString("NativeMethods_RegistryKeyNotFoundFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GotInstancesFormat", resourceCulture); } } /// - /// Looks up a localized string similar to An error occurred with SQL Server LocalDB. HRESULT = {0:X}. + /// Looks up a localized string similar to Obtained version information for SQL LocalDB version '{0}'.. /// - internal static string SqlLocalDbApi_GenericFailureFormat { + internal static string ILoggerExtensions_GotVersionInfoFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_GenericFailureFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GotVersionInfoFormat", resourceCulture); } } /// - /// Looks up a localized string similar to SQL LocalDB instance '{0}' cannot be found so was not deleted.. + /// Looks up a localized string similar to Obtained {0} versions for SQL LocalDB.. /// - internal static string SqlLocalDbApi_InstanceDoesNotExistFormat { + internal static string ILoggerExtensions_GotVersionsFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_InstanceDoesNotExistFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_GotVersionsFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The current value ({0}) of the {1}.LanguageId property is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults.. + /// Looks up a localized string similar to SQL LocalDB instance '{0}' cannot be found so was not deleted.. /// - internal static string SqlLocalDbApi_InvalidLanguageIdFormat { + internal static string ILoggerExtensions_InstanceDoesNotExistFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_InvalidLanguageIdFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_InstanceDoesNotExistFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Created named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. + /// Looks up a localized string similar to The current Language Id {0} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults.. /// - internal static string SqlLocalDbApi_LogCreatedFormat { + internal static string ILoggerExtensions_InvalidLanguageIdFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogCreatedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_InvalidLanguageIdFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Creating named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. + /// Looks up a localized string similar to Ignoring invalid registry key name '{0}'.. /// - internal static string SqlLocalDbApi_LogCreatingFormat { + internal static string ILoggerExtensions_InvalidRegistryKeyNameFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogCreatingFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_InvalidRegistryKeyNameFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Deleted named instance of SQL LocalDB '{0}'.. + /// Looks up a localized string similar to Loaded SQL LocalDB API from '{0}'.. /// - internal static string SqlLocalDbApi_LogDeletedFormat { + internal static string ILoggerExtensions_NativeApiLoadedFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeletedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeApiLoadedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Deleted file(s) for SQL LocalDB instance '{0}' from '{1}'.. + /// Looks up a localized string similar to Failed to load SQL LocalDB API from '{0}'. Error: {1}.. /// - internal static string SqlLocalDbApi_LogDeletedInstanceFilesFormat { + internal static string ILoggerExtensions_NativeApiLoadFailedFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeletedInstanceFilesFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeApiLoadFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL LocalDB instance '{0}' could not be deleted as it is currently in use.. + /// Looks up a localized string similar to Could not find SQL LocalDB API DLL '{0}'.. /// - internal static string SqlLocalDbApi_LogDeleteFailedAsInUseFormat { + internal static string ILoggerExtensions_NativeApiNotFoundFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeleteFailedAsInUseFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeApiNotFoundFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Deleting named instance of SQL LocalDB '{0}'.. + /// Looks up a localized string similar to The SQL LocalDB API was not loaded.. /// - internal static string SqlLocalDbApi_LogDeletingFormat { + internal static string ILoggerExtensions_NativeApiNotLoaded { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeletingFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeApiNotLoaded", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}': {2}. + /// Looks up a localized string similar to SQL LocalDB returned HRESULT {0:X}.. /// - internal static string SqlLocalDbApi_LogDeletingInstanceFilesFailedFormat { + internal static string ILoggerExtensions_NativeResultFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeletingInstanceFilesFailedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeResultFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Deleting file(s) for SQL LocalDB instance '{0}' from '{1}'.. + /// Looks up a localized string similar to No SQL LocalDB API DLL path could be found.. /// - internal static string SqlLocalDbApi_LogDeletingInstanceFilesFormat { + internal static string ILoggerExtensions_NoNativeApiFound { get { - return ResourceManager.GetString("SqlLocalDbApi_LogDeletingInstanceFilesFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NoNativeApiFound", resourceCulture); } } /// - /// Looks up a localized string similar to Obtaining instance names for SQL LocalDB.. + /// Looks up a localized string similar to SQL Server LocalDB is not installed.. /// - internal static string SqlLocalDbApi_LogGetInstances { + internal static string ILoggerExtensions_NotInstalled { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGetInstances", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NotInstalled", resourceCulture); } } /// - /// Looks up a localized string similar to Obtaining information for SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to The configured SQL LocalDB Instance API override version '{0}' cannot be found on {1}.. /// - internal static string SqlLocalDbApi_LogGettingInfoFormat { + internal static string ILoggerExtensions_OverrideVersionNotFoundFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGettingInfoFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_OverrideVersionNotFoundFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Obtaining version information for SQL LocalDB version '{0}'.. + /// Looks up a localized string similar to Could not open registry key '{0}'.. /// - internal static string SqlLocalDbApi_LogGetVersionInfoFormat { + internal static string ILoggerExtensions_RegistryKeyNotFoundFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGetVersionInfoFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_RegistryKeyNotFoundFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Obtained information for SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Shared SQL LocalDB instance '{0}' for owner SID '{1}' as '{2}'.. /// - internal static string SqlLocalDbApi_LogGotInfoFormat { + internal static string ILoggerExtensions_SharedInstanceFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGotInfoFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_SharedInstanceFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Obtained {0} instance names for SQL LocalDB.. + /// Looks up a localized string similar to Sharing SQL LocalDB instance '{0}' for owner SID '{1}'. Shared instance name: '{2}'.. /// - internal static string SqlLocalDbApi_LogGotInstancesFormat { + internal static string ILoggerExtensions_SharingInstanceFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGotInstancesFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_SharingInstanceFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Obtained version information for SQL LocalDB version '{0}'.. + /// Looks up a localized string similar to Started SQL LocalDB instance '{0}' using named pipe '{1}'.. /// - internal static string SqlLocalDbApi_LogGotVersionInfoFormat { + internal static string ILoggerExtensions_StartedFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogGotVersionInfoFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StartedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to SQL LocalDB returned HRESULT {0:X}.. + /// Looks up a localized string similar to Tracing started for SQL LocalDB.. /// - internal static string SqlLocalDbApi_LogNativeResultFormat { + internal static string ILoggerExtensions_StartedTracing { get { - return ResourceManager.GetString("SqlLocalDbApi_LogNativeResultFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StartedTracing", resourceCulture); } } /// - /// Looks up a localized string similar to Shared SQL LocalDB instance '{0}' for owner SID '{1}' as '{2}'.. + /// Looks up a localized string similar to Starting SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbApi_LogSharedInstanceFormat { + internal static string ILoggerExtensions_StartingFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogSharedInstanceFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StartingFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Sharing SQL LocalDB instance '{0}' for owner SID '{1}'. Shared instance name: '{2}'.. + /// Looks up a localized string similar to Starting tracing for SQL LocalDB.. /// - internal static string SqlLocalDbApi_LogSharingInstanceFormat { + internal static string ILoggerExtensions_StartTracing { get { - return ResourceManager.GetString("SqlLocalDbApi_LogSharingInstanceFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StartTracing", resourceCulture); } } /// - /// Looks up a localized string similar to Started SQL LocalDB instance '{0}' using named pipe '{1}'.. + /// Looks up a localized string similar to Failed to stop SQL LocalDB instance '{0}'. HRESULT = {1:X}.. /// - internal static string SqlLocalDbApi_LogStartedFormat { + internal static string ILoggerExtensions_StopFailedFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStartedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StopFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Tracing started for SQL LocalDB.. + /// Looks up a localized string similar to Stopped SQL LocalDB instance '{0}' after {1}.. /// - internal static string SqlLocalDbApi_LogStartedTracing { + internal static string ILoggerExtensions_StoppedFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStartedTracing", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Starting SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Stopped sharing SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbApi_LogStartingFormat { + internal static string ILoggerExtensions_StoppedSharingFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStartingFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppedSharingFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Starting tracing for SQL LocalDB.. + /// Looks up a localized string similar to Tracing stopped for SQL LocalDB.. /// - internal static string SqlLocalDbApi_LogStartTracing { + internal static string ILoggerExtensions_StoppedTracing { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStartTracing", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppedTracing", resourceCulture); } } /// - /// Looks up a localized string similar to Stopped SQL LocalDB instance '{0}' after {1}.. + /// Looks up a localized string similar to Stopping SQL LocalDB instance '{0}'. Timeout: {1}; Option(s): {2}.. /// - internal static string SqlLocalDbApi_LogStoppedFormat { + internal static string ILoggerExtensions_StoppingFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppedFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppingFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Stopped sharing SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Stopping sharing SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbApi_LogStoppedSharingFormat { + internal static string ILoggerExtensions_StoppingSharingFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppedSharingFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppingSharingFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Tracing stopped for SQL LocalDB.. + /// Looks up a localized string similar to Stopping tracing for SQL LocalDB.. /// - internal static string SqlLocalDbApi_LogStoppedTracing { + internal static string ILoggerExtensions_StoppingTracing { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppedTracing", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_StoppingTracing", resourceCulture); } } /// - /// Looks up a localized string similar to Stopping SQL LocalDB instance '{0}'. Timeout: {1}; Option(s): {2}.. + /// Looks up a localized string similar to The SQL LocalDB instance '{0}' is not running.. /// - internal static string SqlLocalDbApi_LogStoppingFormat { + internal static string ISqlLocalDbInstanceInfoExtensions_NotRunningFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppingFormat", resourceCulture); + return ResourceManager.GetString("ISqlLocalDbInstanceInfoExtensions_NotRunningFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Stopping sharing SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to An error occurred with SQL Server LocalDB. HRESULT = {0:X}. /// - internal static string SqlLocalDbApi_LogStoppingSharingFormat { + internal static string SqlLocalDbApi_GenericFailureFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppingSharingFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbApi_GenericFailureFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Stopping tracing for SQL LocalDB.. + /// Looks up a localized string similar to No SQL LocalDB instance name specified.. /// - internal static string SqlLocalDbApi_LogStoppingTracing { + internal static string SqlLocalDbApi_NoInstanceName { get { - return ResourceManager.GetString("SqlLocalDbApi_LogStoppingTracing", resourceCulture); + return ResourceManager.GetString("SqlLocalDbApi_NoInstanceName", resourceCulture); } } /// - /// Looks up a localized string similar to No SQL LocalDB instance name specified.. + /// Looks up a localized string similar to No logger was provided for the {0} instance.. /// - internal static string SqlLocalDbApi_NoInstanceName { + internal static string SqlLocalDbApi_NoLoggerFormat { get { - return ResourceManager.GetString("SqlLocalDbApi_NoInstanceName", resourceCulture); + return ResourceManager.GetString("SqlLocalDbApi_NoLoggerFormat", resourceCulture); } } @@ -519,120 +519,57 @@ internal static string SqlLocalDbApi_TimeoutTooSmallFormat { } } - /// - /// Looks up a localized string similar to An error occurred with a SQL Server LocalDB instance.. - /// - internal static string SqlLocalDbException_DefaultMessage { - get { - return ResourceManager.GetString("SqlLocalDbException_DefaultMessage", resourceCulture); - } - } - /// /// Looks up a localized string similar to Failed to enumerate the installed versions of SQL LocalDB.. /// - internal static string SqlLocalDbException_VersionEnumerationFailed { - get { - return ResourceManager.GetString("SqlLocalDbException_VersionEnumerationFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SQL LocalDB instance '{0}' already exists.. - /// - internal static string SqlLocalDbFactory_InstanceExistsFormat { - get { - return ResourceManager.GetString("SqlLocalDbFactory_InstanceExistsFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to delete SQL LocalDB instance '{0}'.. - /// - internal static string SqlLocalDbInstance_DeleteFailedFormat { - get { - return ResourceManager.GetString("SqlLocalDbInstance_DeleteFailedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SQL LocalDB instance '{0}' does not exist.. - /// - internal static string SqlLocalDbInstance_InstanceNotFoundFormat { + internal static string SqlLocalDbApi_VersionEnumerationFailed { get { - return ResourceManager.GetString("SqlLocalDbInstance_InstanceNotFoundFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbApi_VersionEnumerationFailed", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL LocalDB instance '{0}' is not running.. + /// Looks up a localized string similar to An error occurred with a SQL Server LocalDB instance.. /// - internal static string SqlLocalDbInstance_NotRunningFormat { + internal static string SqlLocalDbException_DefaultMessage { get { - return ResourceManager.GetString("SqlLocalDbInstance_NotRunningFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbException_DefaultMessage", resourceCulture); } } /// /// Looks up a localized string similar to Failed to share SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbInstance_ShareFailedFormat { + internal static string SqlLocalDbInstanceManager_ShareFailedFormat { get { - return ResourceManager.GetString("SqlLocalDbInstance_ShareFailedFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbInstanceManager_ShareFailedFormat", resourceCulture); } } /// /// Looks up a localized string similar to Failed to start SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbInstance_StartFailedFormat { + internal static string SqlLocalDbInstanceManager_StartFailedFormat { get { - return ResourceManager.GetString("SqlLocalDbInstance_StartFailedFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbInstanceManager_StartFailedFormat", resourceCulture); } } /// /// Looks up a localized string similar to Failed to stop SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbInstance_StopFailedFormat { + internal static string SqlLocalDbInstanceManager_StopFailedFormat { get { - return ResourceManager.GetString("SqlLocalDbInstance_StopFailedFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbInstanceManager_StopFailedFormat", resourceCulture); } } /// /// Looks up a localized string similar to Failed to stop sharing SQL LocalDB instance '{0}'.. /// - internal static string SqlLocalDbInstance_UnshareFailedFormat { - get { - return ResourceManager.GetString("SqlLocalDbInstance_UnshareFailedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No SQL LocalDB instance returned by underlying SQL LocalDB API.. - /// - internal static string SqlLocalDbProvider_NoInstance { - get { - return ResourceManager.GetString("SqlLocalDbProvider_NoInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to delete SQL LocalDB instance '{0}'. HRESULT = {1:X}.. - /// - internal static string TemporarySqlLocalDbInstance_DeleteFailedFormat { - get { - return ResourceManager.GetString("TemporarySqlLocalDbInstance_DeleteFailedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to stop SQL LocalDB instance '{0}'. HRESULT = {1:X}.. - /// - internal static string TemporarySqlLocalDbInstance_StopFailedFormat { + internal static string SqlLocalDbInstanceManager_UnshareFailedFormat { get { - return ResourceManager.GetString("TemporarySqlLocalDbInstance_StopFailedFormat", resourceCulture); + return ResourceManager.GetString("SqlLocalDbInstanceManager_UnshareFailedFormat", resourceCulture); } } } diff --git a/src/SqlLocalDb/SR.en-GB.resx b/src/SqlLocalDb/SR.en-GB.resx index a98ff2da..cad06d4a 100644 --- a/src/SqlLocalDb/SR.en-GB.resx +++ b/src/SqlLocalDb/SR.en-GB.resx @@ -112,12 +112,12 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The current value ({0}) of the {1}.LanguageId property is not recognised by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. + + The current Language Id {0} is not recognised by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. \ No newline at end of file diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index a529bd1f..ae403b83 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -132,181 +132,160 @@ An error occurred with a SQL Server LocalDB instance. - + Failed to enumerate the installed versions of SQL LocalDB. - - The SQL LocalDB instance '{0}' does not exist. - - + The SQL LocalDB instance '{0}' is not running. - + Failed to start SQL LocalDB instance '{0}'. - + Failed to stop SQL LocalDB instance '{0}'. - - Failed to delete SQL LocalDB instance '{0}'. - - + Failed to share SQL LocalDB instance '{0}'. - + Failed to stop sharing SQL LocalDB instance '{0}'. - - SQL LocalDB instance '{0}' already exists. - No SQL LocalDB instance name specified. - + Created named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'. - + Creating named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'. - + Deleted named instance of SQL LocalDB '{0}'. - + Deleting named instance of SQL LocalDB '{0}'. - + Obtaining instance names for SQL LocalDB. - + Obtaining information for SQL LocalDB instance '{0}'. - + Obtaining version information for SQL LocalDB version '{0}'. - + Obtained information for SQL LocalDB instance '{0}'. - + Obtained {0} instance names for SQL LocalDB. - + Obtained version information for SQL LocalDB version '{0}'. - + SQL LocalDB returned HRESULT {0:X}. - + Shared SQL LocalDB instance '{0}' for owner SID '{1}' as '{2}'. - + Sharing SQL LocalDB instance '{0}' for owner SID '{1}'. Shared instance name: '{2}'. - + Started SQL LocalDB instance '{0}' using named pipe '{1}'. - + Tracing started for SQL LocalDB. - + Starting SQL LocalDB instance '{0}'. - + Starting tracing for SQL LocalDB. - + Stopped SQL LocalDB instance '{0}' after {1}. - + Stopped sharing SQL LocalDB instance '{0}'. - + Tracing stopped for SQL LocalDB. - + Stopping SQL LocalDB instance '{0}'. Timeout: {1}; Option(s): {2}. - + Stopping sharing SQL LocalDB instance '{0}'. - + Stopping tracing for SQL LocalDB. - - No connection string named '{0}' can be found in the application configuration file. - - - No SQL LocalDB instance returned by underlying SQL LocalDB API. - - - No connection strings are configured in the application configuration file. - - - More than one connection string is configured in the application configuration file. The connection string name must be explicitly specified. Other connection strings may be inherited from higher-level configuration files such as machine.config. - - - The specified database file name is invalid: {0} - - - The current AppDomain has no value set for the Data Directory. - - + The SQL LocalDB function {0} could not be found. - + Ignoring invalid registry key name '{0}'. - + Failed to load SQL LocalDB API from '{0}'. Error: {1}. - + Could not find SQL LocalDB API DLL '{0}'. - + The SQL LocalDB API was not loaded. - + No SQL LocalDB API DLL path could be found. - + Could not open registry key '{0}'. - + Loaded SQL LocalDB API from '{0}'. - + SQL LocalDB instance '{0}' cannot be found so was not deleted. - + The SQL LocalDB native API version to load was overridden by the user to {0}. - + The SQL LocalDB instance '{0}' could not be deleted as it is currently in use. - + Deleted file(s) for SQL LocalDB instance '{0}' from '{1}'. - - Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}': {2} + + Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}'. - + Deleting file(s) for SQL LocalDB instance '{0}' from '{1}'. - - The current value ({0}) of the {1}.LanguageId property is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. - - - No named pipe is associated with the specified SQL LocalDB instance. + + The current Language Id {0} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. - + The configured SQL LocalDB Instance API override version '{0}' cannot be found on {1}. - - Failed to create an instance of type '{0}': {1} - - + Failed to delete SQL LocalDB instance '{0}'. HRESULT = {1:X}. - + Failed to stop SQL LocalDB instance '{0}'. HRESULT = {1:X}. + + No logger was provided for the {0} instance. + + + Obtaining versions for SQL LocalDB. + + + Obtained {0} versions for SQL LocalDB. + + + SQL Server LocalDB is not installed. + \ No newline at end of file diff --git a/src/SqlLocalDb/SRHelper.cs b/src/SqlLocalDb/SRHelper.cs index e941f56d..1970798a 100644 --- a/src/SqlLocalDb/SRHelper.cs +++ b/src/SqlLocalDb/SRHelper.cs @@ -1,16 +1,9 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SRHelper.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -namespace System.Data.SqlLocalDb +using System; + +namespace MartinCostello.SqlLocalDb { /// /// A static class containing helper methods for use with the class. @@ -34,22 +27,7 @@ internal static class SRHelper /// is invalid or the index of a format item is less than zero, /// or greater than or equal to the length of the array. /// - public static string Format(string format, params object[] args) - { - if (format == null) - { - throw new ArgumentNullException(nameof(format)); - } - - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - return string.Format( - SR.Culture, - format, - args); - } + internal static string Format(string format, params object[] args) + => string.Format(SR.Culture, format, args); } } diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index f889dd85..d6e3d0d8 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -1,15 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbApi.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -17,14 +9,15 @@ using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; +using MartinCostello.SqlLocalDb.Interop; +using Microsoft.Extensions.Logging; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb { /// - /// A class representing a wrapper to the SQL Server LocalDB native API. - /// This class cannot be inherited. + /// A class representing a wrapper to the SQL Server LocalDB Instance API. This class cannot be inherited. /// - public static class SqlLocalDbApi + public sealed class SqlLocalDbApi : ISqlLocalDbApi, IDisposable { /// /// The name of the default instance in SQL LocalDB 2012. @@ -37,44 +30,106 @@ public static class SqlLocalDbApi private const string DefaultInstanceName2014AndLater = "MSSQLLocalDB"; /// - /// The maximum length of an SQL LocalDB instance name, in bytes. + /// The maximum length of a SQL LocalDB instance name, in bytes. /// - private const int MaxInstanceNameLength = (NativeMethods.MAX_LOCALDB_INSTANCE_NAME_LENGTH + 1) * sizeof(char); + private const int MaxInstanceNameLength = (LocalDbInstanceApi.MaximumInstanceNameLength + 1) * sizeof(char); /// - /// The maximum length of an SQL LocalDB version string, in bytes. + /// The maximum length of a SQL LocalDB version string, in bytes. /// - private const int MaxVersionLength = (NativeMethods.MAX_LOCALDB_VERSION_LENGTH + 1) * sizeof(char); + private const int MaxVersionLength = (LocalDbInstanceApi.MaximumInstanceVersionLength + 1) * sizeof(char); /// /// The value to pass to functions which have a reserved parameter for future use. /// private const int ReservedValue = 0; + /// + /// The native API. This field is read-only. + /// + private readonly LocalDbInstanceApi _api; + + /// + /// Whether the instance has been disposed of. + /// + private bool _disposed; + /// /// The available versions of SQL Server LocalDB installed on the local machine. /// - private static string[] _versions; + private string[] _versions; /// - /// Whether to automatically delete the files associated with SQL LocalDB instances when they are deleted. + /// The timeout for stopping an instance of LocalDB. /// - private static bool _automaticallyDeleteInstanceFiles = SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles; + private TimeSpan _stopTimeout; /// - /// The locale ID (LCID) to use for formatting error messages. + /// Initializes a new instance of the class. /// - private static int _languageId = SqlLocalDbConfig.LanguageId; + /// The to use. + /// + /// is . + /// + /// + /// did create the required loggers. + /// + public SqlLocalDbApi(ILoggerFactory loggerFactory) + : this(new SqlLocalDbOptions(), loggerFactory) + { + } /// - /// The options to use when stopping instances of SQL LocalDB. + /// Initializes a new instance of the class. /// - private static StopInstanceOptions _stopOptions = SqlLocalDbConfig.StopOptions; + /// The to use. + /// The to use. + /// + /// or is . + /// + /// + /// did create the required loggers. + /// + public SqlLocalDbApi(SqlLocalDbOptions options, ILoggerFactory loggerFactory) + : this(options, new WindowsRegistry(), loggerFactory) + { + } /// - /// The timeout for stopping an instance of LocalDB. + /// Initializes a new instance of the class. /// - private static TimeSpan _stopTimeout = SqlLocalDbConfig.StopTimeout; + /// The to use. + /// The to use. + /// The to use. + /// + /// , or is . + /// + /// + /// did not create the required loggers. + /// + internal SqlLocalDbApi(SqlLocalDbOptions options, IRegistry registry, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (registry == null) + { + throw new ArgumentNullException(nameof(registry)); + } + + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + Logger = loggerFactory.CreateLogger() ?? throw new InvalidOperationException(SRHelper.Format(SR.SqlLocalDbApi_NoLoggerFormat, nameof(SqlLocalDbApi))); + var apiLogger = loggerFactory.CreateLogger() ?? throw new InvalidOperationException(SRHelper.Format(SR.SqlLocalDbApi_NoLoggerFormat, nameof(LocalDbInstanceApi))); + + AutomaticallyDeleteInstanceFiles = options.AutomaticallyDeleteInstanceFiles; + LanguageId = options.LanguageId; + StopOptions = options.StopOptions; + StopTimeout = options.StopTimeout; + + _api = new LocalDbInstanceApi(options.NativeApiOverrideVersion, registry, apiLogger); + } /// /// Gets or sets a value indicating whether to automatically delete the @@ -87,16 +142,12 @@ public static class SqlLocalDbApi /// used. The default value is , unless overridden /// by the SQLLocalDB:AutomaticallyDeleteInstanceFiles application configuration setting. /// - public static bool AutomaticallyDeleteInstanceFiles - { - get { return _automaticallyDeleteInstanceFiles; } - set { _automaticallyDeleteInstanceFiles = value; } - } + public bool AutomaticallyDeleteInstanceFiles { get; set; } /// /// Gets the name of the default SQL LocalDB instance. /// - public static string DefaultInstanceName + public string DefaultInstanceName { get { @@ -107,7 +158,7 @@ public static string DefaultInstanceName return string.Empty; } - if (NativeMethods.NativeApiVersion.Major == 11) + if (_api.NativeApiVersion.Major == 11) { return DefaultInstanceName2012; } @@ -125,11 +176,7 @@ public static string DefaultInstanceName /// order is used. This property is provided for integrators to specifically override the language used from /// the defaults used by the local installed operating system. /// - public static int LanguageId - { - get { return _languageId; } - set { _languageId = value; } - } + public int LanguageId { get; set; } /// /// Gets the version string for the latest installed version of SQL Server LocalDB. @@ -137,19 +184,16 @@ public static int LanguageId /// /// No versions of SQL Server LocalDB are installed on the local machine. /// - public static string LatestVersion + public string LatestVersion { get { // Access through property to ensure initialized - IList versions = Versions; + IReadOnlyList versions = Versions; if (versions.Count < 1) { - string message = SRHelper.Format( - SR.SqlLocalDbApi_NoVersionsFormat, - Environment.MachineName); - + string message = SRHelper.Format(SR.SqlLocalDbApi_NoVersionsFormat, Environment.MachineName); throw new InvalidOperationException(message); } @@ -165,11 +209,7 @@ public static string LatestVersion /// /// Gets or sets the options to use when stopping instances of SQL LocalDB. /// - public static StopInstanceOptions StopOptions - { - get { return _stopOptions; } - set { _stopOptions = value; } - } + public StopInstanceOptions StopOptions { get; set; } /// /// Gets or sets the default timeout to use when @@ -178,7 +218,7 @@ public static StopInstanceOptions StopOptions /// /// is less than . /// - public static TimeSpan StopTimeout + public TimeSpan StopTimeout { get { @@ -189,10 +229,7 @@ public static TimeSpan StopTimeout { if (value < TimeSpan.Zero) { - string message = SRHelper.Format( - SR.SqlLocalDbApi_TimeoutTooSmallFormat, - nameof(value)); - + string message = SRHelper.Format(SR.SqlLocalDbApi_TimeoutTooSmallFormat, nameof(value)); throw new ArgumentOutOfRangeException(nameof(value), value, message); } @@ -210,7 +247,7 @@ public static TimeSpan StopTimeout /// /// The installed versions of SQL LocalDB could not be determined. /// - public static IList Versions + public IReadOnlyList Versions { get { @@ -225,10 +262,48 @@ public static IList Versions } } + /// + /// Gets the to use. + /// + internal ILoggerFactory LoggerFactory { get; } + + /// + /// Gets the to use. + /// + private ILogger Logger { get; } + + /// + /// Gets the full path of the directory containing the SQL LocalDB instance files for the current user. + /// + /// + /// The full path of the directory containing the SQL LocalDB instance files for the current user. + /// + /// + /// The folder usually used to store SQL LocalDB instance files is %LOCALAPPDATA%\Microsoft\Microsoft SQL Server Local DB\Instances. + /// + public static string GetInstancesFolderPath() + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Microsoft", + "Microsoft SQL Server Local DB", + "Instances"); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + /// /// Creates a new instance of SQL Server LocalDB. /// /// The name of the LocalDB instance. + /// + /// An containing information about the instance that was created. + /// /// /// is . /// @@ -239,17 +314,16 @@ public static IList Versions /// The SQL Server LocalDB instance specified by could /// not be stopped or the installed versions of SQL LocalDB could not be determined. /// - public static void CreateInstance(string instanceName) - { - // Use the latest version - CreateInstance(instanceName, LatestVersion); - } + public ISqlLocalDbInstanceInfo CreateInstance(string instanceName) => CreateInstance(instanceName, LatestVersion); /// /// Creates a new instance of SQL Server LocalDB. /// /// The name of the LocalDB instance. /// The version of SQL Server LocalDB to use. + /// + /// An containing information about the instance that was created. + /// /// /// or is . /// @@ -259,7 +333,7 @@ public static void CreateInstance(string instanceName) /// /// The SQL Server LocalDB instance specified by and could not be created. /// - public static void CreateInstance(string instanceName, string version) + public ISqlLocalDbInstanceInfo CreateInstance(string instanceName, string version) { if (instanceName == null) { @@ -271,14 +345,16 @@ public static void CreateInstance(string instanceName, string version) throw new ArgumentNullException(nameof(version)); } - Logger.Verbose(Logger.TraceEvent.CreateInstance, SR.SqlLocalDbApi_LogCreatingFormat, instanceName, version); + Logger.CreatingInstance(instanceName, version); InvokeThrowOnError( - () => NativeMethods.CreateInstance(version, instanceName, ReservedValue), - Logger.TraceEvent.CreateInstance, + () => _api.CreateInstance(version, instanceName, ReservedValue), + EventIds.CreatingInstanceFailed, instanceName); - Logger.Verbose(Logger.TraceEvent.CreateInstance, SR.SqlLocalDbApi_LogCreatedFormat, instanceName, version); + Logger.CreatedInstance(instanceName, version); + + return GetInstanceInfo(instanceName); } /// @@ -296,10 +372,7 @@ public static void CreateInstance(string instanceName, string version) /// /// The SQL Server LocalDB instance specified by could not be deleted. /// - public static void DeleteInstance(string instanceName) - { - DeleteInstance(instanceName, deleteFiles: AutomaticallyDeleteInstanceFiles); - } + public void DeleteInstance(string instanceName) => DeleteInstance(instanceName, deleteFiles: AutomaticallyDeleteInstanceFiles); /// /// Deletes the specified SQL Server LocalDB instance. @@ -319,7 +392,7 @@ public static void DeleteInstance(string instanceName) /// /// The SQL Server LocalDB instance specified by could not be deleted. /// - public static void DeleteInstance(string instanceName, bool deleteFiles) + public void DeleteInstance(string instanceName, bool deleteFiles) { DeleteInstanceInternal(instanceName, throwIfNotFound: true, deleteFiles: deleteFiles); } @@ -334,7 +407,7 @@ public static void DeleteInstance(string instanceName, bool deleteFiles) /// The default instance(s) of any version(s) of SQL LocalDB that are /// installed on the local machine are not deleted. /// - public static int DeleteUserInstances() => DeleteUserInstances(deleteFiles: AutomaticallyDeleteInstanceFiles); + public int DeleteUserInstances() => DeleteUserInstances(deleteFiles: AutomaticallyDeleteInstanceFiles); /// /// Deletes all user instances of SQL LocalDB on the current machine, @@ -351,53 +424,46 @@ public static void DeleteInstance(string instanceName, bool deleteFiles) /// The default instance(s) of any version(s) of SQL LocalDB that are /// installed on the local machine are not deleted. /// - public static int DeleteUserInstances(bool deleteFiles) + public int DeleteUserInstances(bool deleteFiles) { int instancesDeleted = 0; - IList instanceNames = GetInstanceNames(); + IReadOnlyList instanceNames = GetInstanceNames(); - if (instanceNames != null) + foreach (string instanceName in instanceNames) { - foreach (string instanceName in instanceNames) + ISqlLocalDbInstanceInfo info = GetInstanceInfo(instanceName); + + // Do not try to delete automatic instances. + // These are the default instances created for each version. + // As of SQL LocalDB 2014, the default instance is named 'MSSQLLocalDB'. + if (!info.Exists || + info.IsAutomatic || + string.Equals(info.Name, DefaultInstanceName2014AndLater, StringComparison.Ordinal)) { - ISqlLocalDbInstanceInfo info = GetInstanceInfo(instanceName); - - // Do not try to delete automatic instances. - // These are the default instances created for each version. - // As of SQL LocalDB 2014, the default instance is named 'MSSQLLocalDB'. - if (!info.Exists || - info.IsAutomatic || - string.Equals(info.Name, DefaultInstanceName2014AndLater, StringComparison.Ordinal)) - { - continue; - } + continue; + } - // In some cases, SQL LocalDB may report instance names in calls - // to enumerate the instances that do not actually exist. Presumably - // this can occur if the installation/instances become corrupted. - // Such failures to delete an instance should be ignored - try + // In some cases, SQL LocalDB may report instance names in calls + // to enumerate the instances that do not actually exist. Presumably + // this can occur if the installation/instances become corrupted. + // Such failures to delete an instance should be ignored + try + { + if (DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: deleteFiles)) { - if (DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: deleteFiles)) - { - instancesDeleted++; - } + instancesDeleted++; } - catch (SqlLocalDbException ex) + } + catch (SqlLocalDbException ex) + { + if (ex.ErrorCode == SqlLocalDbErrors.InstanceBusy) { - if (ex.ErrorCode == SqlLocalDbErrors.InstanceBusy) - { - Logger.Warning( - Logger.TraceEvent.DeleteFailedAsInstanceInUse, - SR.SqlLocalDbApi_LogDeleteFailedAsInUseFormat, - ex.InstanceName); - - continue; - } - - throw; + Logger.DeletingInstanceFailedAsInUse(ex, ex.InstanceName); + continue; } + + throw; } } @@ -421,14 +487,16 @@ public static int DeleteUserInstances(bool deleteFiles) /// /// The information for the SQL Server LocalDB instance specified by could not obtained. /// - public static ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) + public ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) { if (instanceName == null) { throw new ArgumentNullException(nameof(instanceName)); } - Logger.Verbose(Logger.TraceEvent.GetInstanceInfo, SR.SqlLocalDbApi_LogGettingInfoFormat, instanceName); + Logger.GettingInstanceInfo(instanceName); + + LocalDbInstanceInfo info; int size = LocalDbInstanceInfo.MarshalSize; IntPtr ptrInfo = Marshal.AllocHGlobal(size); @@ -436,20 +504,24 @@ public static ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) try { InvokeThrowOnError( - () => NativeMethods.GetInstanceInfo(instanceName, ptrInfo, size), - Logger.TraceEvent.GetInstanceInfo, + () => _api.GetInstanceInfo(instanceName, ptrInfo, size), + EventIds.GettingInstanceInfoFailed, instanceName); - LocalDbInstanceInfo info = MarshalStruct(ptrInfo); - - Logger.Verbose(Logger.TraceEvent.GetInstanceInfo, SR.SqlLocalDbApi_LogGotInfoFormat, instanceName); - - return info; + info = MarshalStruct(ptrInfo); } finally { Marshal.FreeHGlobal(ptrInfo); } + + Logger.GotInstanceInfo(instanceName); + + var result = new SqlLocalDbInstanceInfo(); + + result.Update(info); + + return result; } /// @@ -464,24 +536,22 @@ public static ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) /// /// The SQL Server LocalDB instance names could not be determined. /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Requires enumeration of native API and allocating unmanaged memory.")] - public static IList GetInstanceNames() + public IReadOnlyList GetInstanceNames() { - Logger.Verbose(Logger.TraceEvent.GetInstanceNames, SR.SqlLocalDbApi_LogGetInstances); + Logger.GettingInstanceNames(); // Query the LocalDB API to get the number of instances int count = 0; - int hr = NativeMethods.GetInstanceNames(IntPtr.Zero, ref count); + int hr = _api.GetInstanceNames(IntPtr.Zero, ref count); if (hr != 0 && hr != SqlLocalDbErrors.InsufficientBuffer) { - throw GetLocalDbError(hr, Logger.TraceEvent.GetInstanceNames); + throw GetLocalDbError(hr, EventIds.GettingInstanceNamesFailed); } + string[] names; + // Allocate enough memory to receive the instance name array int nameLength = MaxInstanceNameLength; IntPtr ptrNames = Marshal.AllocHGlobal(nameLength * count); @@ -489,42 +559,20 @@ public static IList GetInstanceNames() try { InvokeThrowOnError( - () => NativeMethods.GetInstanceNames(ptrNames, ref count), - Logger.TraceEvent.GetInstanceNames); + () => _api.GetInstanceNames(ptrNames, ref count), + EventIds.GettingInstanceNamesFailed); // Read the instance names back from unmanaged memory - string[] names = MarshalStringArray(ptrNames, nameLength, count); - - Logger.Verbose(Logger.TraceEvent.GetInstanceNames, SR.SqlLocalDbApi_LogGotInstancesFormat, names.Length); - - return names; + names = MarshalStringArray(ptrNames, nameLength, count); } finally { Marshal.FreeHGlobal(ptrNames); } - } - /// - /// Gets the full path of the directory containing the SQL LocalDB instance files for the current user. - /// - /// - /// The full path of the directory containing the SQL LocalDB instance files for the current user. - /// - /// - /// The folder usually used to store SQL LocalDB instance files is %LOCALAPPDATA%\Microsoft\Microsoft SQL Server Local DB\Instances. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Design", - "CA1024:UsePropertiesWhereAppropriate", - Justification = "Calls a number of methods so is more appropriate as a method.")] - public static string GetInstancesFolderPath() - { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Microsoft", - "Microsoft SQL Server Local DB", - "Instances"); + Logger.GotInstanceNames(names.Length); + + return names; } /// @@ -544,14 +592,16 @@ public static string GetInstancesFolderPath() /// /// The information for the SQL Server LocalDB version specified by could not be obtained. /// - public static ISqlLocalDbVersionInfo GetVersionInfo(string version) + public ISqlLocalDbVersionInfo GetVersionInfo(string version) { if (version == null) { throw new ArgumentNullException(nameof(version)); } - Logger.Verbose(Logger.TraceEvent.GetVersionInfo, SR.SqlLocalDbApi_LogGetVersionInfoFormat, version); + Logger.GettingVersionInfo(version); + + LocalDbVersionInfo info; int size = LocalDbVersionInfo.MarshalSize; IntPtr ptrInfo = Marshal.AllocHGlobal(size); @@ -559,19 +609,23 @@ public static ISqlLocalDbVersionInfo GetVersionInfo(string version) try { InvokeThrowOnError( - () => NativeMethods.GetVersionInfo(version, ptrInfo, size), - Logger.TraceEvent.GetVersionInfo); - - LocalDbVersionInfo info = MarshalStruct(ptrInfo); - - Logger.Verbose(Logger.TraceEvent.GetVersionInfo, SR.SqlLocalDbApi_LogGotVersionInfoFormat, version); + () => _api.GetVersionInfo(version, ptrInfo, size), + EventIds.GettingVersionInfoFailed); - return info; + info = MarshalStruct(ptrInfo); } finally { Marshal.FreeHGlobal(ptrInfo); } + + Logger.GotVersionInfo(version); + + var result = new SqlLocalDbVersionInfo(); + + result.Update(info); + + return result; } /// @@ -581,52 +635,23 @@ public static ISqlLocalDbVersionInfo GetVersionInfo(string version) /// if SQL Server LocalDB is installed on the /// current machine; otherwise . /// - public static bool IsLocalDBInstalled() + public bool IsLocalDBInstalled() { // Call one of the "get info" functions with a zero buffer. // If LocalDB is installed, it will return a "buffer too small" HRESULT, // otherwise it will return the "not installed" HRESULT. - int dummy = 0; - int hr = NativeMethods.GetVersions(IntPtr.Zero, ref dummy); + int notUsed = 0; + int hr = _api.GetVersions(IntPtr.Zero, ref notUsed); return hr != SqlLocalDbErrors.NotInstalled; } - /// - /// Shares the specified SQL Server LocalDB instance with other users of the computer, - /// using the specified shared name for the current Windows user. - /// - /// The private name for the LocalDB instance to share. - /// The shared name for the LocalDB instance to share. - /// - /// or is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by could not be shared. - /// - public static void ShareInstance( - string instanceName, - string sharedInstanceName) - { - string ownerSid; - - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - ownerSid = identity.User.Value; - } - - ShareInstance(ownerSid, instanceName, sharedInstanceName); - } - /// /// Shares the specified SQL Server LocalDB instance with other /// users of the computer, using the specified shared name. /// /// The SID of the instance owner. /// The private name for the LocalDB instance to share. - /// The shared name for the LocalDB instance to share. + /// The shared name for the LocalDB instance to share as. /// /// , or is . /// @@ -636,7 +661,7 @@ public static void ShareInstance( /// /// The SQL Server LocalDB instance specified by could not be shared. /// - public static void ShareInstance( + public void ShareInstance( string ownerSid, string instanceName, string sharedInstanceName) @@ -666,12 +691,10 @@ public static void ShareInstance( throw new ArgumentException(SR.SqlLocalDbApi_NoInstanceName, nameof(instanceName)); } - Logger.Verbose(Logger.TraceEvent.ShareInstance, SR.SqlLocalDbApi_LogSharingInstanceFormat, instanceName, ownerSid, sharedInstanceName); + Logger.SharingInstance(instanceName, ownerSid, sharedInstanceName); // Get the binary version of the SID from its string - SecurityIdentifier sid = new SecurityIdentifier(ownerSid); - byte[] binaryForm = new byte[SecurityIdentifier.MaxBinaryLength]; - sid.GetBinaryForm(binaryForm, 0); + byte[] binaryForm = GetOwnerSidAsByteArray(ownerSid); IntPtr ptrSid = Marshal.AllocHGlobal(binaryForm.Length); @@ -681,11 +704,11 @@ public static void ShareInstance( Marshal.Copy(binaryForm, 0, ptrSid, binaryForm.Length); InvokeThrowOnError( - () => NativeMethods.ShareInstance(ptrSid, instanceName, sharedInstanceName, ReservedValue), - Logger.TraceEvent.ShareInstance, + () => _api.ShareInstance(ptrSid, instanceName, sharedInstanceName, ReservedValue), + EventIds.SharingInstanceFailed, instanceName); - Logger.Verbose(Logger.TraceEvent.ShareInstance, SR.SqlLocalDbApi_LogSharedInstanceFormat, instanceName, ownerSid, sharedInstanceName); + Logger.SharedInstance(instanceName, ownerSid, sharedInstanceName); } finally { @@ -709,26 +732,26 @@ public static void ShareInstance( /// /// The SQL Server LocalDB instance specified by could not be started. /// - public static string StartInstance(string instanceName) + public string StartInstance(string instanceName) { if (instanceName == null) { throw new ArgumentNullException(nameof(instanceName)); } - Logger.Verbose(Logger.TraceEvent.StartInstance, SR.SqlLocalDbApi_LogStartingFormat, instanceName); + Logger.StartingInstance(instanceName); - StringBuilder buffer = new StringBuilder(NativeMethods.LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE + 1); - int size = buffer.Capacity; + int size = LocalDbInstanceApi.MaximumSqlConnectionStringBufferLength + 1; + var buffer = new StringBuilder(size); InvokeThrowOnError( - () => NativeMethods.StartInstance(instanceName, ReservedValue, buffer, ref size), - Logger.TraceEvent.StartInstance, + () => _api.StartInstance(instanceName, ReservedValue, buffer, ref size), + EventIds.StartingInstanceFailed, instanceName); string namedPipe = buffer.ToString(); - Logger.Verbose(Logger.TraceEvent.StartInstance, SR.SqlLocalDbApi_LogStartedFormat, instanceName, namedPipe); + Logger.StartedInstance(instanceName, namedPipe); return namedPipe; } @@ -740,13 +763,13 @@ public static string StartInstance(string instanceName) /// /// Tracing could not be initialized. /// - public static void StartTracing() + public void StartTracing() { - Logger.Verbose(Logger.TraceEvent.StartTracing, SR.SqlLocalDbApi_LogStartTracing); + Logger.StartingTracing(); - InvokeThrowOnError(NativeMethods.StartTracing, Logger.TraceEvent.StartTracing); + InvokeThrowOnError(_api.StartTracing, EventIds.StartingTracingFailed); - Logger.Verbose(Logger.TraceEvent.StartTracing, SR.SqlLocalDbApi_LogStartedTracing); + Logger.StartedTracing(); } /// @@ -765,7 +788,7 @@ public static void StartTracing() /// /// The SQL Server LocalDB instance specified by could not be stopped. /// - public static void StopInstance(string instanceName) + public void StopInstance(string instanceName) { StopInstance(instanceName, _stopTimeout); } @@ -777,8 +800,9 @@ public static void StopInstance(string instanceName) /// The name of the LocalDB instance to stop. /// /// - /// The amount of time to give the LocalDB instance to stop. - /// If the value is , the method will + /// The optional amount of time to give the LocalDB instance to stop. + /// If no value is specified, the value of will + /// be used. If the value is , the method will /// return immediately and not wait for the instance to stop. /// /// @@ -796,7 +820,7 @@ public static void StopInstance(string instanceName) /// /// The parameter is rounded to the nearest second. /// - public static void StopInstance(string instanceName, TimeSpan timeout) + public void StopInstance(string instanceName, TimeSpan? timeout) { StopInstance(instanceName, StopOptions, timeout); } @@ -812,7 +836,8 @@ public static void StopInstance(string instanceName, TimeSpan timeout) /// /// /// The amount of time to give the LocalDB instance to stop. - /// If the value is , the method will + /// If no value is specified, the value of will + /// be used. If the value is , the method will /// return immediately and not wait for the instance to stop. /// /// @@ -830,33 +855,32 @@ public static void StopInstance(string instanceName, TimeSpan timeout) /// /// The parameter is rounded to the nearest second. /// - public static void StopInstance(string instanceName, StopInstanceOptions options, TimeSpan timeout) + public void StopInstance(string instanceName, StopInstanceOptions options, TimeSpan? timeout) { if (instanceName == null) { throw new ArgumentNullException(nameof(instanceName)); } - if (timeout < TimeSpan.Zero) - { - string message = SRHelper.Format( - SR.SqlLocalDbApi_TimeoutTooSmallFormat, - nameof(timeout)); + TimeSpan theTimeout = timeout ?? StopTimeout; + if (theTimeout < TimeSpan.Zero) + { + string message = SRHelper.Format(SR.SqlLocalDbApi_TimeoutTooSmallFormat, nameof(timeout)); throw new ArgumentOutOfRangeException(nameof(timeout), timeout, message); } - Logger.Verbose(Logger.TraceEvent.StopInstance, SR.SqlLocalDbApi_LogStoppingFormat, instanceName, timeout, options); + Logger.StoppingInstance(instanceName, theTimeout, options); Stopwatch stopwatch = Stopwatch.StartNew(); InvokeThrowOnError( - () => NativeMethods.StopInstance(instanceName, options, (int)timeout.TotalSeconds), - Logger.TraceEvent.StopInstance, + () => _api.StopInstance(instanceName, options, (int)theTimeout.TotalSeconds), + EventIds.StoppingInstanceFailed, instanceName); stopwatch.Stop(); - Logger.Verbose(Logger.TraceEvent.StopInstance, SR.SqlLocalDbApi_LogStoppedFormat, instanceName, stopwatch.Elapsed); + Logger.StoppedInstance(instanceName, stopwatch.Elapsed); } /// @@ -866,13 +890,13 @@ public static void StopInstance(string instanceName, StopInstanceOptions options /// /// Tracing could not be disabled. /// - public static void StopTracing() + public void StopTracing() { - Logger.Verbose(Logger.TraceEvent.StopTracing, SR.SqlLocalDbApi_LogStoppingTracing); + Logger.StoppingTracing(); - InvokeThrowOnError(NativeMethods.StopTracing, Logger.TraceEvent.StopTracing); + InvokeThrowOnError(_api.StopTracing, EventIds.StoppingTracingFailed); - Logger.Verbose(Logger.TraceEvent.StartTracing, SR.SqlLocalDbApi_LogStoppedTracing); + Logger.StoppedTracing(); } /// @@ -890,26 +914,36 @@ public static void StopTracing() /// /// The SQL Server LocalDB instance specified by could not be unshared. /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the native LocalDB API function.")] - public static void UnshareInstance(string instanceName) + public void UnshareInstance(string instanceName) { if (instanceName == null) { throw new ArgumentNullException(nameof(instanceName)); } - Logger.Verbose(Logger.TraceEvent.UnshareInstance, SR.SqlLocalDbApi_LogStoppingSharingFormat, instanceName); + Logger.UnsharingInstance(instanceName); InvokeThrowOnError( - () => NativeMethods.UnshareInstance(instanceName, ReservedValue), - Logger.TraceEvent.UnshareInstance, + () => _api.UnshareInstance(instanceName, ReservedValue), + EventIds.UnsharingInstanceFailed, instanceName); - Logger.Verbose(Logger.TraceEvent.UnshareInstance, SR.SqlLocalDbApi_LogStoppedSharingFormat, instanceName); + Logger.UnsharedInstance(instanceName); + } + + /// + /// Returns whether the specified SQL LocalDB instance name is one of the default instance names. + /// + /// The instance name to test. + /// + /// if is one of the default SQL LocalDB + /// instance names; otherwise . + /// + internal static bool IsDefaultInstanceName(string instanceName) + { + return + string.Equals(instanceName, DefaultInstanceName2014AndLater, StringComparison.OrdinalIgnoreCase) || + string.Equals(instanceName, DefaultInstanceName2012, StringComparison.OrdinalIgnoreCase); } /// @@ -939,26 +973,26 @@ public static void UnshareInstance(string instanceName) /// /// The SQL Server LocalDB instance specified by could not be deleted. /// - internal static bool DeleteInstanceInternal(string instanceName, bool throwIfNotFound, bool deleteFiles = false) + internal bool DeleteInstanceInternal(string instanceName, bool throwIfNotFound, bool deleteFiles = false) { if (instanceName == null) { throw new ArgumentNullException(nameof(instanceName)); } - Logger.Verbose(Logger.TraceEvent.DeleteInstance, SR.SqlLocalDbApi_LogDeletingFormat, instanceName); + Logger.DeletingInstance(instanceName); - int hr = NativeMethods.DeleteInstance(instanceName, ReservedValue); + int hr = _api.DeleteInstance(instanceName, ReservedValue); if (hr != 0) { if (!throwIfNotFound && hr == SqlLocalDbErrors.UnknownInstance) { - Logger.Verbose(Logger.TraceEvent.DeleteInstance, SR.SqlLocalDbApi_InstanceDoesNotExistFormat, instanceName); + Logger.DeletingInstanceFailedAsNotFound(instanceName); return false; } - throw GetLocalDbError(hr, Logger.TraceEvent.DeleteInstance, instanceName); + throw GetLocalDbError(hr, EventIds.DeletingInstanceFailed, instanceName); } if (deleteFiles) @@ -966,7 +1000,7 @@ internal static bool DeleteInstanceInternal(string instanceName, bool throwIfNot DeleteInstanceFiles(instanceName); } - Logger.Verbose(Logger.TraceEvent.DeleteInstance, SR.SqlLocalDbApi_LogDeletedFormat, instanceName); + Logger.DeletedInstance(instanceName); return true; } @@ -974,7 +1008,7 @@ internal static bool DeleteInstanceInternal(string instanceName, bool throwIfNot /// Deletes the file(s) from disk that are associated with the specified SQL LocalDB instance. /// /// The name of the SQL LocalDB instance to delete the file(s) for. - internal static void DeleteInstanceFiles(string instanceName) + internal void DeleteInstanceFiles(string instanceName) { Debug.Assert(instanceName != null, "instanceName cannot be null."); @@ -988,97 +1022,47 @@ internal static void DeleteInstanceFiles(string instanceName) { if (Directory.Exists(instancePath)) { - Logger.Verbose( - Logger.TraceEvent.DeletingInstanceFiles, - SR.SqlLocalDbApi_LogDeletingInstanceFilesFormat, - instanceName, - instancePath); + Logger.DeletingInstanceFiles(instanceName, instancePath); Directory.Delete(instancePath, recursive: true); - Logger.Verbose( - Logger.TraceEvent.DeletedInstanceFiles, - SR.SqlLocalDbApi_LogDeletedInstanceFilesFormat, - instanceName, - instancePath); + Logger.DeletedInstanceFiles(instanceName, instancePath); } } - catch (ArgumentException ex) - { - Logger.Error( - Logger.TraceEvent.DeletingInstanceFilesFailed, - SR.SqlLocalDbApi_LogDeletingInstanceFilesFailedFormat, - instanceName, - instancePath, - ex.Message); - } - catch (IOException ex) + catch (Exception ex) when (ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException) { - Logger.Error( - Logger.TraceEvent.DeletingInstanceFilesFailed, - SR.SqlLocalDbApi_LogDeletingInstanceFilesFailedFormat, - instanceName, - instancePath, - ex.Message); + Logger.DeletingInstanceFilesFailed(instanceName, instancePath); } - catch (UnauthorizedAccessException ex) - { - Logger.Error( - Logger.TraceEvent.DeletingInstanceFilesFailed, - SR.SqlLocalDbApi_LogDeletingInstanceFilesFailedFormat, - instanceName, - instancePath, - ex.Message); - } - } - - /// - /// Returns whether the specified SQL LocalDB instance name is one of the default instance names. - /// - /// The instance name to test. - /// - /// if is one of the default SQL LocalDB - /// instance names; otherwise . - /// - internal static bool IsDefaultInstanceName(string instanceName) - { - return - string.Equals(instanceName, DefaultInstanceName2014AndLater, StringComparison.OrdinalIgnoreCase) || - string.Equals(instanceName, DefaultInstanceName2012, StringComparison.OrdinalIgnoreCase); } /// /// Returns an representing the specified LocalDB HRESULT. /// /// The HRESULT returned by the LocalDB API. - /// The trace event Id associated with the error. + /// The trace event Id associated with the error. /// The name of the instance that caused the error, if any. /// /// An representing . /// - internal static Exception GetLocalDbError(int hr, int traceEventId, string instanceName = "") + internal Exception GetLocalDbError(int hr, EventId eventId, string instanceName = "") { string message; - Logger.Error(traceEventId, SR.SqlLocalDbApi_LogNativeResultFormat, hr); + Logger.LogError(eventId, SR.ILoggerExtensions_NativeResultFormat, hr); if (hr == SqlLocalDbErrors.NotInstalled) { - message = SRHelper.Format( - SR.SqlLocalDbApi_NotInstalledFormat, - Environment.MachineName); - - Logger.Error(traceEventId, message); - throw new InvalidOperationException(message); + Logger.NotInstalled(); + throw new InvalidOperationException(SRHelper.Format(SR.SqlLocalDbApi_NotInstalledFormat, Environment.MachineName)); } - StringBuilder buffer = new StringBuilder(NativeMethods.LOCALDB_MAX_SQLCONNECTION_BUFFER_SIZE + 1); - int size = buffer.Capacity; + int size = LocalDbInstanceApi.MaximumSqlConnectionStringBufferLength + 1; + var buffer = new StringBuilder(size); // Get the description of the error from the LocalDB API. - int hr2 = NativeMethods.GetLocalDbError( + int hr2 = _api.GetLocalDbError( hr, - _languageId, + LanguageId, buffer, ref size); @@ -1088,42 +1072,85 @@ internal static Exception GetLocalDbError(int hr, int traceEventId, string insta } else if (hr2 == SqlLocalDbErrors.UnknownLanguageId) { - // If the value of DefaultLanaguageId was not understood by the API, + // If the value of DefaultLanguageId was not understood by the API, // then log an error informing the user. Do not throw an exception in // this case as otherwise we will mask the original exception from the user. - Logger.Error( - Logger.TraceEvent.InvalidLanguageId, - SR.SqlLocalDbApi_InvalidLanguageIdFormat, - LanguageId, - typeof(SqlLocalDbApi).Name); + Logger.InvalidLanguageId(LanguageId); // Use a generic message if getting the message from the API failed - message = SRHelper.Format( - SR.SqlLocalDbApi_GenericFailureFormat, - hr); + message = SRHelper.Format(SR.SqlLocalDbApi_GenericFailureFormat, hr); } else { // Use a generic message if getting the message from the API failed. // N.B. That if this occurs, then the original error is masked (although it is logged). - message = SRHelper.Format( - SR.SqlLocalDbApi_GenericFailureFormat, - hr2); + message = SRHelper.Format(SR.SqlLocalDbApi_GenericFailureFormat, hr2); - Logger.Error(traceEventId, message); + Logger.LogError(eventId, message); - return new SqlLocalDbException( - message, - hr2, - instanceName); + return new SqlLocalDbException(message, hr2, instanceName); } - Logger.Error(traceEventId, message); + Logger.LogError(eventId, message); - return new SqlLocalDbException( - message, - hr, - instanceName); + return new SqlLocalDbException(message, hr, instanceName); + } + + /// + /// Converts a representation of a SID to an array of bytes. + /// + /// The SID to convert to a byte array. + /// + /// An of containing a representation of . + /// + private static byte[] GetOwnerSidAsByteArray(string ownerSid) + { + // Get the binary version of the SID from its string + SecurityIdentifier sid = new SecurityIdentifier(ownerSid); + byte[] binaryForm = new byte[SecurityIdentifier.MaxBinaryLength]; + sid.GetBinaryForm(binaryForm, 0); + return binaryForm; + } + + /// + /// Convenience method to marshal string arrays from unmanaged memory. + /// + /// A pointer to an unmanaged block of memory. + /// The length of each string in the array. + /// The number of elements in the array. + /// + /// An of strings read from . + /// + private static string[] MarshalStringArray(IntPtr ptr, int length, int count) + { + Debug.Assert(ptr != IntPtr.Zero, "The unmanaged memory pointer is invalid."); + Debug.Assert(length > 0, "The length of the elements cannot be less than one."); + Debug.Assert(count > -1, "The number of elements in the array cannot be negative."); + + string[] result = new string[count]; + + for (int i = 0; i < result.Length; i++) + { + // Determine the offset of the element, and get the string from the array + IntPtr offset = new IntPtr(ptr.ToInt64() + (length * i)); + result[i] = Marshal.PtrToStringAuto(offset); + } + + return result; + } + + /// + /// Convenience method to marshal structures from unmanaged memory. + /// + /// The type of structure to marshal from unmanaged memory. + /// A pointer to an unmanaged block of memory. + /// + /// The instance of read from . + /// + private static T MarshalStruct(IntPtr ptr) + where T : struct + { + return (T)Marshal.PtrToStructure(ptr, typeof(T)); } /// @@ -1138,18 +1165,22 @@ internal static Exception GetLocalDbError(int hr, int traceEventId, string insta /// /// The installed LocalDB versions could not be enumerated. /// - private static string[] GetLocalDbVersions() + private string[] GetLocalDbVersions() { + string[] versions; + try { + Logger.GettingVersions(); + // Query the LocalDB API to get the number of instances int count = 0; - int hr = NativeMethods.GetVersions(IntPtr.Zero, ref count); + int hr = _api.GetVersions(IntPtr.Zero, ref count); if (hr != 0 && hr != SqlLocalDbErrors.InsufficientBuffer) { - throw GetLocalDbError(hr, Logger.TraceEvent.GetVersions); + throw GetLocalDbError(hr, EventIds.GettingVersionsFailed); } // Allocate enough memory to receive the version name array @@ -1158,88 +1189,69 @@ private static string[] GetLocalDbVersions() try { - hr = NativeMethods.GetVersions(ptrVersions, ref count); + hr = _api.GetVersions(ptrVersions, ref count); if (hr != 0) { - throw GetLocalDbError(hr, Logger.TraceEvent.GetVersions); + throw GetLocalDbError(hr, EventIds.GettingVersionsFailed); } // Read the version strings back from unmanaged memory - string[] versions = MarshalStringArray(ptrVersions, versionLength, count); - - return versions; + versions = MarshalStringArray(ptrVersions, versionLength, count); } finally { Marshal.FreeHGlobal(ptrVersions); } } - catch (SqlLocalDbException e) + catch (SqlLocalDbException ex) { throw new SqlLocalDbException( - SR.SqlLocalDbException_VersionEnumerationFailed, - e.ErrorCode, - e.InstanceName, - e); + SR.SqlLocalDbApi_VersionEnumerationFailed, + ex.ErrorCode, + ex.InstanceName, + ex); } + + Logger.GotVersions(versions.Length); + + return versions; } /// /// Invokes the specified delegate, throwing an exception if the delegate does not return zero. /// /// A delegate to a method to invoke. - /// The trace event Id if a non-zero value is returned. + /// The trace event Id if a non-zero value is returned. /// The name of the instance that caused the error, if any. - private static void InvokeThrowOnError(Func func, int traceEventId, string instanceName = "") + private void InvokeThrowOnError(Func func, EventId eventId, string instanceName = "") { int hr = func(); if (hr != 0) { - throw GetLocalDbError(hr, traceEventId, instanceName); + throw GetLocalDbError(hr, eventId, instanceName); } } /// - /// Convenience method to marshal string arrays from unmanaged memory. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - /// A pointer to an unmanaged block of memory. - /// The length of each string in the array. - /// The number of elements in the array. - /// - /// An of strings read from . - /// - private static string[] MarshalStringArray(IntPtr ptr, int length, int count) + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + private void Dispose(bool disposing) { - Debug.Assert(ptr != IntPtr.Zero, "The unmanaged memory pointer is invalid."); - Debug.Assert(length > 0, "The length of the elements cannot be less than one."); - Debug.Assert(count > -1, "The number of elements in the array cannot be negative."); - - string[] result = new string[count]; - - for (int i = 0; i < result.Length; i++) + if (!_disposed) { - // Determine the offset of the element, and get the string from the array - IntPtr offset = new IntPtr(ptr.ToInt64() + (length * i)); - result[i] = Marshal.PtrToStringAuto(offset); - } - - return result; - } + if (disposing) + { + _api.Dispose(); + } - /// - /// Convenience method to marshal structures from unmanaged memory. - /// - /// The type of structure to marshal from unmanaged memory. - /// A pointer to an unmanaged block of memory. - /// - /// The instance of read from . - /// - private static T MarshalStruct(IntPtr ptr) - where T : struct - { - return (T)Marshal.PtrToStructure(ptr, typeof(T)); + _disposed = true; + } } } } diff --git a/src/SqlLocalDb/SqlLocalDbApiWrapper.cs b/src/SqlLocalDb/SqlLocalDbApiWrapper.cs deleted file mode 100644 index 8c56f60b..00000000 --- a/src/SqlLocalDb/SqlLocalDbApiWrapper.cs +++ /dev/null @@ -1,272 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbApiWrapper.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing an implementation of that wraps access to . - /// - [Serializable] - public class SqlLocalDbApiWrapper : ISqlLocalDbApi - { - /// - /// The shared singleton instance of . This field is read-only. - /// - internal static readonly SqlLocalDbApiWrapper Instance = new SqlLocalDbApiWrapper(); - - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbApiWrapper() - { - } - - /// - /// Gets the version string for the latest installed version of SQL Server LocalDB. - /// - /// - /// No versions of SQL Server LocalDB are installed on the local machine. - /// - public virtual string LatestVersion => SqlLocalDbApi.LatestVersion; - - /// - /// Gets an of containing the available version(s) of SQL LocalDB. - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The installed versions of SQL LocalDB could not be determined. - /// - public virtual IList Versions => SqlLocalDbApi.Versions; - - /// - /// Creates a new instance of SQL Server LocalDB. - /// - /// The name of the LocalDB instance. - /// The version of SQL Server LocalDB to use. - /// - /// or is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by and could not be created. - /// - public virtual void CreateInstance(string instanceName, string version) - { - SqlLocalDbApi.CreateInstance(instanceName, version); - } - - /// - /// Deletes the specified SQL Server LocalDB instance. - /// - /// - /// The name of the LocalDB instance to delete. - /// - /// - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by could not be deleted. - /// - public virtual void DeleteInstance(string instanceName) - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - - /// - /// Returns information about the specified LocalDB instance. - /// - /// The name of the LocalDB instance to get the information for. - /// - /// An instance of containing information - /// about the LocalDB instance specified by . - /// - /// - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The information for the SQL Server LocalDB instance specified by - /// could not obtained. - /// - public virtual ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) => SqlLocalDbApi.GetInstanceInfo(instanceName); - - /// - /// Returns the names of all the SQL Server LocalDB instances for the current user. - /// - /// - /// The names of the the SQL Server LocalDB instances for the current user. - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance names could not be determined. - /// - public virtual IList GetInstanceNames() => SqlLocalDbApi.GetInstanceNames(); - - /// - /// Returns information about the specified LocalDB version. - /// - /// The name of the LocalDB version to get the information for. - /// - /// An instance of containing information - /// about the LocalDB version specified by . - /// - /// - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The information for the SQL Server LocalDB version specified by - /// could not be obtained. - /// - public virtual ISqlLocalDbVersionInfo GetVersionInfo(string version) => SqlLocalDbApi.GetVersionInfo(version); - - /// - /// Returns whether SQL LocalDB is installed on the current machine. - /// - /// - /// if SQL Server LocalDB is installed on the - /// current machine; otherwise . - /// - public virtual bool IsLocalDBInstalled() => SqlLocalDbApi.IsLocalDBInstalled(); - - /// - /// Shares the specified SQL Server LocalDB instance with other - /// users of the computer, using the specified shared name. - /// - /// The SID of the instance owner. - /// The private name for the LocalDB instance to share. - /// The shared name for the LocalDB instance to share. - /// - /// , or - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by could not be shared. - /// - public virtual void ShareInstance(string ownerSid, string instanceName, string sharedInstanceName) - { - SqlLocalDbApi.ShareInstance(ownerSid, instanceName, sharedInstanceName); - } - - /// - /// Starts the specified instance of SQL Server LocalDB. - /// - /// The name of the LocalDB instance to start. - /// - /// The named pipe to use to connect to the LocalDB instance. - /// - /// - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by could not be started. - /// - public virtual string StartInstance(string instanceName) => SqlLocalDbApi.StartInstance(instanceName); - - /// - /// Enables tracing of native API calls for all the SQL Server - /// LocalDB instances owned by the current Windows user. - /// - /// - /// Tracing could not be initialized. - /// - public virtual void StartTracing() - { - SqlLocalDbApi.StartTracing(); - } - - /// - /// Stops the specified instance of SQL Server LocalDB. - /// - /// - /// The name of the LocalDB instance to stop. - /// - /// - /// The amount of time to give the LocalDB instance to stop. - /// If the value is , the method will - /// return immediately and not wait for the instance to stop. - /// - /// - /// is . - /// - /// - /// is less than . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by could not be stopped. - /// - /// - /// The parameter is rounded to the nearest second. - /// - public virtual void StopInstance(string instanceName, TimeSpan timeout) - { - SqlLocalDbApi.StopInstance(instanceName, timeout); - } - - /// - /// Disables tracing of native API calls for all the SQL Server - /// LocalDB instances owned by the current Windows user. - /// - /// - /// Tracing could not be disabled. - /// - public virtual void StopTracing() - { - SqlLocalDbApi.StopTracing(); - } - - /// - /// Stops the sharing of the specified SQL Server LocalDB instance. - /// - /// - /// The private name for the LocalDB instance to share. - /// - /// - /// is . - /// - /// - /// SQL Server LocalDB is not installed on the local machine. - /// - /// - /// The SQL Server LocalDB instance specified by - /// could not be unshared. - /// - public virtual void UnshareInstance(string instanceName) - { - SqlLocalDbApi.UnshareInstance(instanceName); - } - } -} diff --git a/src/SqlLocalDb/SqlLocalDbConfig.cs b/src/SqlLocalDb/SqlLocalDbConfig.cs deleted file mode 100644 index facd3991..00000000 --- a/src/SqlLocalDb/SqlLocalDbConfig.cs +++ /dev/null @@ -1,117 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbApi.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Configuration; -using System.Data.SqlLocalDb.Configuration; -using System.Globalization; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing the configuration for the assembly. This class cannot be inherited. - /// - internal static class SqlLocalDbConfig - { - /// - /// The name of the legacy application configuration setting for specifying whether to automatically delete instance files. - /// - private const string LegacyDeleteInstanceFilesSettingName = "SQLLocalDB:AutomaticallyDeleteInstanceFiles"; - - /// - /// The name of the legacy application configuration setting for overriding the native SQL LocalDB API version to use. - /// - private const string LegacyOverrideVersionSettingName = "SQLLocalDB:OverrideVersion"; - - /// - /// The current loaded from the application configuration file. - /// - private static readonly SqlLocalDbConfigurationSection ConfigSection = SqlLocalDbConfigurationSection.GetSection(); - - /// - /// Gets a value indicating whether to automatically delete the files associated with SQL LocalDB instances when they are deleted. - /// - internal static bool AutomaticallyDeleteInstanceFiles - { - // Use the value fron app.config first, then if not specified try the legacy appSettings setting value - get { return ConfigSection.IsAutomaticallyDeleteInstanceFilesSpecified ? ConfigSection.AutomaticallyDeleteInstanceFiles : LoadAutomaticDeletionValueFromConfig(); } - } - - /// - /// Gets the locale ID (LCID) to use for formatting error messages. - /// - internal static int LanguageId - { - get - { - CultureInfo culture = ConfigSection.Language; - - if (culture == null) - { - // Zero is used by SQL LocalDB to mean to defer to the OS configuration - return 0; - } - - // N.B. No checks as to the support of the configured culture's LCID for use - // by SQL LocalDB are made here, it is left to the user to ensure that the - // culture code they configure is supported. From experimentation, SQL LocalDB - // supports the "main" language/region for cultures and not the neutral culture. - // For example: - // Supported: de-DE, en-US, es-ES, fr-FR; - // Not supported: de, en, en-GB, es, es-MX, fr, fr-CA. - return culture.LCID; - } - } - - /// - /// Gets the type of the implementation to use, if any. - /// - internal static Type LoggerType => ConfigSection.LoggerType; - - /// - /// Gets the version string of the native SQL LocalDB API to load, if any. - /// - internal static string NativeApiOverrideVersionString - { - // Use the value fron app.config first, then if not specified try the legacy appSettings setting value - get { return ConfigSection.IsNativeApiOverrideVersionSpecified ? ConfigSection.NativeApiOverrideVersion : (ConfigurationManager.AppSettings["SQLLocalDB:OverrideVersion"] ?? string.Empty); } - } - - /// - /// Gets the options to use when stopping instances of SQL LocalDB. - /// - internal static StopInstanceOptions StopOptions => ConfigSection.StopOptions; - - /// - /// Gets the default timeout to use when stopping instances of SQL LocalDB. - /// - internal static TimeSpan StopTimeout => ConfigSection.StopTimeout; - - /// - /// Loads the default value of the property from the application setting section of the configuration file. - /// - /// - /// The default value to use for the property. - /// - private static bool LoadAutomaticDeletionValueFromConfig() - { - string value = ConfigurationManager.AppSettings[LegacyDeleteInstanceFilesSettingName]; - - if (string.IsNullOrEmpty(value) || - !bool.TryParse(value, out bool automaticallyDeleteInstanceFiles)) - { - automaticallyDeleteInstanceFiles = false; - } - - return automaticallyDeleteInstanceFiles; - } - } -} diff --git a/src/SqlLocalDb/SqlLocalDbErrors.cs b/src/SqlLocalDb/SqlLocalDbErrors.cs index 5675f554..781dc837 100644 --- a/src/SqlLocalDb/SqlLocalDbErrors.cs +++ b/src/SqlLocalDb/SqlLocalDbErrors.cs @@ -1,16 +1,9 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbErrors.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace System.Data.SqlLocalDb +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; + +namespace MartinCostello.SqlLocalDb { /// /// A class containing SQL Server LocalDB errors. This class cannot be inherited. @@ -23,7 +16,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_CREATE_INSTANCE_FOLDER error. /// - public static readonly int CannotCreateInstanceFolder = unchecked((int)0x89c50100); + public const int CannotCreateInstanceFolder = unchecked((int)0x89c50100); /// /// The parameter for the LocalDB Instance API method is incorrect. @@ -31,7 +24,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INVALID_PARAMETER error. /// - public static readonly int InvalidParameter = unchecked((int)0x89c50101); + public const int InvalidParameter = unchecked((int)0x89c50101); /// /// Unable to create the LocalDB instance with specified version. @@ -41,7 +34,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_EXISTS_WITH_LOWER_VERSION error. /// - public static readonly int InstanceExistsWithLowerVersion = unchecked((int)0x89c50102); + public const int InstanceExistsWithLowerVersion = unchecked((int)0x89c50102); /// /// Cannot access the user profile folder for local application data (%LOCALAPPDATA%). @@ -49,7 +42,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_GET_USER_PROFILE_FOLDER error. /// - public static readonly int CannotGetUserProfileFolder = unchecked((int)0x89c50103); + public const int CannotGetUserProfileFolder = unchecked((int)0x89c50103); /// /// The full path length of the LocalDB instance folder is longer than @@ -58,8 +51,8 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_FOLDER_PATH_TOO_LONG error. /// - [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] - public static readonly int InstanceFolderPathTooLong = unchecked((int)0x89c50104); + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] + public const int InstanceFolderPathTooLong = unchecked((int)0x89c50104); /// /// Cannot access LocalDB instance folder: %LOCALAPPDATA%\Microsoft\Microsoft SQL Server Local DB\Instances\<instance name>. @@ -67,7 +60,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_FOLDER error. /// - public static readonly int CannotAccessInstanceFolder = unchecked((int)0x89c50105); + public const int CannotAccessInstanceFolder = unchecked((int)0x89c50105); /// /// Unexpected error occurred while trying to access the LocalDB instance @@ -76,7 +69,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_ACCESS_INSTANCE_REGISTRY error. /// - public static readonly int CannotAccessInstanceRegistry = unchecked((int)0x89c50106); + public const int CannotAccessInstanceRegistry = unchecked((int)0x89c50106); /// /// The specified LocalDB instance does not exist. @@ -84,7 +77,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_UNKNOWN_INSTANCE error. /// - public static readonly int UnknownInstance = unchecked((int)0x89c50107); + public const int UnknownInstance = unchecked((int)0x89c50107); /// /// Unexpected error occurred inside a LocalDB instance API method call. @@ -93,7 +86,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INTERNAL_ERROR error. /// - public static readonly int InternalError = unchecked((int)0x89c50108); + public const int InternalError = unchecked((int)0x89c50108); /// /// Unexpected error occurred while trying to modify the registry @@ -103,7 +96,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_MODIFY_INSTANCE_REGISTRY error. /// - public static readonly int CannotModifyInstanceRegistry = unchecked((int)0x89c50109); + public const int CannotModifyInstanceRegistry = unchecked((int)0x89c50109); /// /// Error occurred during LocalDB instance startup: SQL Server process failed to start. @@ -111,7 +104,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_SQL_SERVER_STARTUP_FAILED error. /// - public static readonly int ServerStartupFailed = unchecked((int)0x89c5010a); + public const int ServerStartupFailed = unchecked((int)0x89c5010a); /// /// LocalDB instance is corrupted. See the Windows Application event log for error details. @@ -119,7 +112,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_CONFIGURATION_CORRUPT error. /// - public static readonly int InstanceConfigurationCorrupt = unchecked((int)0x89c5010b); + public const int InstanceConfigurationCorrupt = unchecked((int)0x89c5010b); /// /// Error occurred during LocalDB instance startup: unable to create the SQL Server process. @@ -127,7 +120,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_CREATE_SQL_PROCESS error. /// - public static readonly int CannotCreateSqlProcess = unchecked((int)0x89c5010c); + public const int CannotCreateSqlProcess = unchecked((int)0x89c5010c); /// /// The specified LocalDB version is not available on this computer. @@ -135,7 +128,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_UNKNOWN_VERSION error. /// - public static readonly int UnknownVersion = unchecked((int)0x89c5010d); + public const int UnknownVersion = unchecked((int)0x89c5010d); /// /// Error getting the localized error message. @@ -143,7 +136,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_UNKNOWN_LANGUAGE_ID error. /// - public static readonly int UnknownLanguageId = unchecked((int)0x89c5010e); + public const int UnknownLanguageId = unchecked((int)0x89c5010e); /// /// Stop operation for LocalDB instance failed to complete within the specified time. @@ -151,7 +144,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_STOP_FAILED error. /// - public static readonly int InstanceStopFailed = unchecked((int)0x89c5010f); + public const int InstanceStopFailed = unchecked((int)0x89c5010f); /// /// Error getting the localized error message. The specified error code is unknown. @@ -159,7 +152,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_UNKNOWN_ERROR_CODE error. /// - public static readonly int UnknownErrorCode = unchecked((int)0x89c50110); + public const int UnknownErrorCode = unchecked((int)0x89c50110); /// /// The LocalDB version available on this workstation is lower than @@ -168,7 +161,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_VERSION_REQUESTED_NOT_INSTALLED error. /// - public static readonly int VersionNotInstalled = unchecked((int)0x89c50111); + public const int VersionNotInstalled = unchecked((int)0x89c50111); /// /// Requested operation on LocalDB instance cannot be performed because @@ -177,7 +170,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_BUSY error. /// - public static readonly int InstanceBusy = unchecked((int)0x89c50112); + public const int InstanceBusy = unchecked((int)0x89c50112); /// /// Default LocalDB instances cannot be created, stopped or deleted manually. @@ -185,7 +178,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INVALID_OPERATION error. /// - public static readonly int InvalidOperation = unchecked((int)0x89c50113); + public const int InvalidOperation = unchecked((int)0x89c50113); /// /// The buffer passed to the LocalDB instance API method has insufficient size. @@ -193,7 +186,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSUFFICIENT_BUFFER error. /// - public static readonly int InsufficientBuffer = unchecked((int)0x89c50114); + public const int InsufficientBuffer = unchecked((int)0x89c50114); /// /// Timeout occurred inside the LocalDB instance API method. @@ -201,7 +194,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_WAIT_TIMEOUT error. /// - public static readonly int WaitTimeout = unchecked((int)0x89c50115); + public const int WaitTimeout = unchecked((int)0x89c50115); /// /// SQL Server LocalDB is not installed. @@ -209,7 +202,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_NOT_INSTALLED error. /// - public static readonly int NotInstalled = unchecked((int)0x89c50116); + public const int NotInstalled = unchecked((int)0x89c50116); /// /// Failed to start XEvent engine within the LocalDB Instance API. @@ -217,7 +210,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_XEVENT_FAILED error. /// - public static readonly int XEventFailed = unchecked((int)0x89c50117); + public const int XEventFailed = unchecked((int)0x89c50117); /// /// Cannot create an automatic instance. See the Windows Application event log for error details. @@ -225,7 +218,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_AUTO_INSTANCE_CREATE_FAILED error. /// - public static readonly int AutoInstanceCreateFailed = unchecked((int)0x89c50118); + public const int AutoInstanceCreateFailed = unchecked((int)0x89c50118); /// /// Cannot create a shared instance. The specified shared instance name is already in use. @@ -233,7 +226,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_SHARED_NAME_TAKEN error. /// - public static readonly int SharedNameTaken = unchecked((int)0x89c50119); + public const int SharedNameTaken = unchecked((int)0x89c50119); /// /// API caller is not LocalDB instance owner. @@ -241,7 +234,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CALLER_IS_NOT_OWNER error. /// - public static readonly int CallerIsNotOwner = unchecked((int)0x89c5011a); + public const int CallerIsNotOwner = unchecked((int)0x89c5011a); /// /// Specified LocalDB instance name is invalid. @@ -249,7 +242,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INVALID_INSTANCE_NAME error. /// - public static readonly int InvalidInstanceName = unchecked((int)0x89c5011b); + public const int InvalidInstanceName = unchecked((int)0x89c5011b); /// /// The specified LocalDB instance is already shared with different shared name. @@ -257,7 +250,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_ALREADY_SHARED error. /// - public static readonly int InstanceAlreadyShared = unchecked((int)0x89c5011c); + public const int InstanceAlreadyShared = unchecked((int)0x89c5011c); /// /// The specified LocalDB instance is not shared. @@ -265,7 +258,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_INSTANCE_NOT_SHARED error. /// - public static readonly int InstanceNotShared = unchecked((int)0x89c5011d); + public const int InstanceNotShared = unchecked((int)0x89c5011d); /// /// Administrator privileges are required in order to execute this operation. @@ -273,7 +266,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_ADMIN_RIGHTS_REQUIRED error. /// - public static readonly int AdminRightsRequired = unchecked((int)0x89c5011e); + public const int AdminRightsRequired = unchecked((int)0x89c5011e); /// /// Unable to share a LocalDB instance - maximum number of shared LocalDB instances reached. @@ -281,7 +274,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_TOO_MANY_SHARED_INSTANCES error. /// - public static readonly int TooManySharedInstances = unchecked((int)0x89c5011f); + public const int TooManySharedInstances = unchecked((int)0x89c5011f); /// /// The "Parent Instance" registry value is missing in the LocalDB instance registry key. @@ -289,7 +282,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_GET_LOCAL_APP_DATA_PATH error. /// - public static readonly int CannotGetLocalAppDataPath = unchecked((int)0x89c50120); + public const int CannotGetLocalAppDataPath = unchecked((int)0x89c50120); /// /// Cannot load resources for this DLL. Resources for this DLL should @@ -299,7 +292,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_ERROR_CANNOT_LOAD_RESOURCES error. /// - public static readonly int CannotLoadResources = unchecked((int)0x89c50121); + public const int CannotLoadResources = unchecked((int)0x89c50121); /// /// The "DataDirectory" registry value is missing in the LocalDB instance registry key. @@ -307,7 +300,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_DATADIRECTORY_IS_MISSING error. /// - public static readonly int DataDirectoryMissing = unchecked((int)0x89c50200); + public const int DataDirectoryMissing = unchecked((int)0x89c50200); /// /// Cannot access LocalDB instance folder. @@ -315,7 +308,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_CANNOT_ACCESS_INSTANCE_FOLDER error. /// - public static readonly int CannotAccessInstanceFolderDetail = unchecked((int)0x89c50201); + public const int CannotAccessInstanceFolderDetail = unchecked((int)0x89c50201); /// /// The "DataDirectory" registry value is too long in the LocalDB instance registry key. @@ -323,8 +316,8 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_DATADIRECTORY_IS_TOO_LONG error. /// - [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] - public static readonly int DataDirectoryIsTooLong = unchecked((int)0x89c50202); + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] + public const int DataDirectoryIsTooLong = unchecked((int)0x89c50202); /// /// The "Parent Instance" registry value is missing in the LocalDB instance registry key. @@ -332,7 +325,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_PARENT_INSTANCE_IS_MISSING error. /// - public static readonly int ParentInstanceIsMissing = unchecked((int)0x89c50203); + public const int ParentInstanceIsMissing = unchecked((int)0x89c50203); /// /// The "Parent Instance" registry value is too long in the LocalDB instance registry key. @@ -340,8 +333,8 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_PARENT_INSTANCE_IS_TOO_LONG error. /// - [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] - public static readonly int ParentInstanceIsTooLong = unchecked((int)0x89c50204); + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] + public const int ParentInstanceIsTooLong = unchecked((int)0x89c50204); /// /// Data directory for LocalDB instance is invalid. @@ -349,7 +342,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_DATA_DIRECTORY_INVALID error. /// - public static readonly int DataDirectoryInvalid = unchecked((int)0x89c50205); + public const int DataDirectoryInvalid = unchecked((int)0x89c50205); /// /// LocalDB instance API: XEvent engine assert. @@ -357,7 +350,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_ASSERT error. /// - public static readonly int XEventAssert = unchecked((int)0x89c50206); + public const int XEventAssert = unchecked((int)0x89c50206); /// /// LocalDB instance API: XEvent error. @@ -365,7 +358,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_ERROR error. /// - public static readonly int XEventError = unchecked((int)0x89c50207); + public const int XEventError = unchecked((int)0x89c50207); /// /// LocalDB installation is corrupted. Reinstall the LocalDB. @@ -373,7 +366,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_INSTALLATION_CORRUPTED error. /// - public static readonly int InstallationCorrupted = unchecked((int)0x89c50208); + public const int InstallationCorrupted = unchecked((int)0x89c50208); /// /// LocalDB XEvent error: cannot determine %ProgramFiles% folder location. @@ -381,7 +374,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_CANNOT_GET_PROGRAM_FILES_LOCATION error. /// - public static readonly int CannotGetProgramFilesLocation = unchecked((int)0x89c50209); + public const int CannotGetProgramFilesLocation = unchecked((int)0x89c50209); /// /// LocalDB XEvent error: Cannot initialize XEvent engine. @@ -389,7 +382,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_CANNOT_INITIALIZE error. /// - public static readonly int CannotInitializeXEvent = unchecked((int)0x89c5020a); + public const int CannotInitializeXEvent = unchecked((int)0x89c5020a); /// /// LocalDB XEvent error: Cannot find XEvents configuration file. @@ -397,7 +390,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_CANNOT_FIND_CONF_FILE error. /// - public static readonly int CannotFindXEventConfigFile = unchecked((int)0x89c5020b); + public const int CannotFindXEventConfigFile = unchecked((int)0x89c5020b); /// /// LocalDB XEvent error: Cannot configure XEvents engine with the configuration file. @@ -405,7 +398,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_CANNOT_CONFIGURE error. /// - public static readonly int CannotConfigureXEvent = unchecked((int)0x89c5020c); + public const int CannotConfigureXEvent = unchecked((int)0x89c5020c); /// /// LocalDB XEvent error: XEvents engine configuration file too long. @@ -413,8 +406,8 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_XEVENT_CONF_FILE_NAME_TOO_LONG error. /// - [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] - public static readonly int XEventConfigFileTooLong = unchecked((int)0x89c5020d); + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Justification = "Usage is safe.")] + public const int XEventConfigFileTooLong = unchecked((int)0x89c5020d); /// /// CoInitializeEx API failed. @@ -422,8 +415,8 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_COINITIALIZEEX_FAILED error. /// - [Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co", Justification = "Is part of a function name.")] - public static readonly int CoInitializeExFailed = unchecked((int)0x89c5020e); + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co", Justification = "Is part of a function name.")] + public const int CoInitializeExFailed = unchecked((int)0x89c5020e); /// /// LocalDB parent instance version is invalid. @@ -431,7 +424,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_PARENT_INSTANCE_VERSION_INVALID error. /// - public static readonly int ParentInstanceVersionInvalid = unchecked((int)0x89c5020f); + public const int ParentInstanceVersionInvalid = unchecked((int)0x89c5020f); /// /// A Windows API call returned an error. @@ -439,7 +432,7 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_WINAPI_ERROR error. /// - public static readonly int WindowsApiError = unchecked((int)0x89c50210); + public const int WindowsApiError = unchecked((int)0x89c50210); /// /// Unexpected result. @@ -447,6 +440,6 @@ public static class SqlLocalDbErrors /// /// Maps to the LOCALDB_EDETAIL_UNEXPECTED_RESULT error. /// - public static readonly int UnexpectedResult = unchecked((int)0x89c50211); + public const int UnexpectedResult = unchecked((int)0x89c50211); } } diff --git a/src/SqlLocalDb/SqlLocalDbException.cs b/src/SqlLocalDb/SqlLocalDbException.cs index 3a2027f9..2bedf4bd 100644 --- a/src/SqlLocalDb/SqlLocalDbException.cs +++ b/src/SqlLocalDb/SqlLocalDbException.cs @@ -1,19 +1,11 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbException.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Data.Common; using System.Runtime.Serialization; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb { /// /// The exception that is thrown when SQL Server LocalDB returns an error. @@ -111,13 +103,16 @@ public SqlLocalDbException(string message, int errorCode, string instanceName, E /// the specified serialization information and context. /// /// - /// The that holds + /// The that holds /// the serialized object data about the exception being thrown. /// /// - /// The that contains + /// The that contains /// contextual information about the source or destination. /// + /// + /// is . + /// protected SqlLocalDbException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -134,11 +129,7 @@ protected SqlLocalDbException(SerializationInfo info, StreamingContext context) /// Gets or sets the name of the SQL Server LocalDB /// instance that caused the exception, if any. /// - public string InstanceName - { - get; - protected set; - } + public string InstanceName { get; protected set; } /// /// Sets the with information about the exception. @@ -148,7 +139,6 @@ public string InstanceName /// /// The parameter is . /// - [Security.SecurityCritical] public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) diff --git a/src/SqlLocalDb/SqlLocalDbInstance.cs b/src/SqlLocalDb/SqlLocalDbInstance.cs deleted file mode 100644 index e0fc379b..00000000 --- a/src/SqlLocalDb/SqlLocalDbInstance.cs +++ /dev/null @@ -1,369 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbInstance.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Data.SqlClient; -using System.Diagnostics; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing an instance of SQL Server LocalDB. - /// - [DebuggerDisplay("{Name}")] - [Serializable] - public class SqlLocalDbInstance : ISqlLocalDbInstance - { - /// - /// The SQL Server LocalDB instance name. - /// - private readonly string _instanceName; - - /// - /// The named pipe to the SQL Server LocalDB instance. - /// - private string _namedPipe; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the SQL Server LocalDB instance. - /// - /// is . - /// - /// - /// The LocalDB instance specified by does not exist. - /// - /// - /// The LocalDB instance specified by could not be obtained. - /// - public SqlLocalDbInstance(string instanceName) - : this(instanceName, SqlLocalDbApiWrapper.Instance) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the SQL Server LocalDB instance. - /// The instance to use. - /// - /// is . - /// - /// - /// The LocalDB instance specified by does not exist. - /// - /// - /// The LocalDB instance specified by could not be obtained. - /// - internal SqlLocalDbInstance(string instanceName, ISqlLocalDbApi localDB) - { - if (instanceName == null) - { - throw new ArgumentNullException(nameof(instanceName)); - } - - Debug.Assert(localDB != null, "localDB cannot be null."); - - ISqlLocalDbInstanceInfo info = localDB.GetInstanceInfo(instanceName); - - if (info == null || !info.Exists) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_InstanceNotFoundFormat, - instanceName); - - Logger.Error(Logger.TraceEvent.General, message); - - throw new InvalidOperationException(message); - } - - _instanceName = instanceName; - _namedPipe = info.NamedPipe; - } - - /// - /// Gets a value indicating whether the LocalDB instance is running. - /// - public bool IsRunning => !string.IsNullOrEmpty(_namedPipe); - - /// - /// Gets the name of the LocalDB instance. - /// - public string Name => _instanceName; - - /// - /// Gets the named pipe that should be used - /// to connect to the LocalDB instance. - /// - public string NamedPipe => _namedPipe; - - /// - /// Deletes the specified instance. - /// - /// The LocalDB instance to delete. - /// - /// is . - /// - /// - /// The SQL Server LocalDB instance specified by could not be deleted. - /// - public static void Delete(ISqlLocalDbInstance instance) - { - Delete(instance, throwIfNotFound: true); - } - - /// - /// Creates a instance to communicate - /// with the SQL Server LocalDB instance. - /// - /// - /// An instance of that can be used - /// to communicate with the SQL Server Local DB instance. - /// - /// - /// The value of is . - /// - public virtual SqlConnection CreateConnection() - { - SqlConnectionStringBuilder builder = CreateConnectionStringBuilder(); - - if (builder == null) - { - return new SqlConnection(); - } - - return new SqlConnection(builder.ConnectionString); - } - - /// - /// Creates an instance of containing - /// the default SQL connection string to connect to the LocalDB instance. - /// - /// - /// An instance of containing - /// the default SQL connection string to connect to the LocalDB instance. - /// - /// - /// The value of is . - /// - public virtual SqlConnectionStringBuilder CreateConnectionStringBuilder() - { - if (!this.IsRunning) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_NotRunningFormat, - _instanceName); - - throw new InvalidOperationException(message); - } - - return new SqlConnectionStringBuilder() - { - DataSource = _namedPipe - }; - } - - /// - /// Returns information about the LocalDB instance. - /// - /// - /// An instance of containing information about the LocalDB instance. - /// - public virtual ISqlLocalDbInstanceInfo GetInstanceInfo() => SqlLocalDbApi.GetInstanceInfo(_instanceName); - - /// - /// Shares the LocalDB instance using the specified name. - /// - /// The name to use to share the instance. - /// - /// is . - /// - /// - /// The LocalDB instance could not be shared. - /// - public virtual void Share(string sharedName) - { - if (sharedName == null) - { - throw new ArgumentNullException(nameof(sharedName)); - } - - try - { - SqlLocalDbApi.ShareInstance(_instanceName, sharedName); - } - catch (SqlLocalDbException e) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_ShareFailedFormat, - _instanceName); - - Logger.Error(Logger.TraceEvent.ShareInstance, message); - - throw new SqlLocalDbException( - message, - e.ErrorCode, - e.InstanceName, - e); - } - } - - /// - /// Starts the SQL Server LocalDB instance. - /// - /// - /// The LocalDB instance could not be started. - /// - public void Start() - { - try - { - // The pipe name changes per instance lifetime - _namedPipe = SqlLocalDbApi.StartInstance(_instanceName); - } - catch (SqlLocalDbException e) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_StartFailedFormat, - _instanceName); - - Logger.Error(Logger.TraceEvent.StartInstance, message); - - throw new SqlLocalDbException( - message, - e.ErrorCode, - e.InstanceName, - e); - } - } - - /// - /// Stops the SQL Server LocalDB instance. - /// - /// - /// The LocalDB instance could not be stopped. - /// - public void Stop() - { - try - { - SqlLocalDbApi.StopInstance(_instanceName); - _namedPipe = string.Empty; - } - catch (SqlLocalDbException e) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_StopFailedFormat, - _instanceName); - - Logger.Error(Logger.TraceEvent.StopInstance, message); - - throw new SqlLocalDbException( - message, - e.ErrorCode, - e.InstanceName, - e); - } - } - - /// - /// Stops sharing the LocalDB instance. - /// - /// - /// The LocalDB instance could not be unshared. - /// - public virtual void Unshare() - { - try - { - SqlLocalDbApi.UnshareInstance(_instanceName); - } - catch (SqlLocalDbException e) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_UnshareFailedFormat, - _instanceName); - - Logger.Error(Logger.TraceEvent.UnshareInstance, message); - - throw new SqlLocalDbException( - message, - e.ErrorCode, - e.InstanceName, - e); - } - } - - /// - /// Deletes the specified instance. - /// - /// The LocalDB instance to delete. - /// - /// Whether to throw an exception if the SQL LocalDB instance - /// associated with cannot be found. - /// - /// - /// is . - /// - /// - /// The SQL Server LocalDB instance specified by could not be deleted. - /// - internal static void Delete(ISqlLocalDbInstance instance, bool throwIfNotFound) - { - Delete(instance, throwIfNotFound, SqlLocalDbApi.AutomaticallyDeleteInstanceFiles); - } - - /// - /// Deletes the specified instance. - /// - /// The LocalDB instance to delete. - /// - /// Whether to throw an exception if the SQL LocalDB instance - /// associated with cannot be found. - /// - /// - /// Whether to delete the file(s) associated with the SQL LocalDB instance. - /// - /// - /// is . - /// - /// - /// The SQL Server LocalDB instance specified by could not be deleted. - /// - internal static void Delete(ISqlLocalDbInstance instance, bool throwIfNotFound, bool deleteFiles) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - try - { - SqlLocalDbApi.DeleteInstanceInternal(instance.Name, throwIfNotFound, deleteFiles); - } - catch (SqlLocalDbException ex) - { - string message = SRHelper.Format( - SR.SqlLocalDbInstance_DeleteFailedFormat, - instance.Name); - - Logger.Error(Logger.TraceEvent.DeleteFailed, message); - - throw new SqlLocalDbException( - message, - ex.ErrorCode, - ex.InstanceName, - ex); - } - } - } -} diff --git a/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs new file mode 100644 index 00000000..86b8aea7 --- /dev/null +++ b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs @@ -0,0 +1,82 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class representing information about an instance of SQL LocalDB. This class cannot be inherited. + /// + [DebuggerDisplay("{Name}")] + internal sealed class SqlLocalDbInstanceInfo : ISqlLocalDbInstanceInfo + { + /// + /// Initializes a new instance of the class. + /// + internal SqlLocalDbInstanceInfo() + { + // Internally created type used to provide "pit-of-success" semantics on + // instances of ISqlLocalDbInstanceInfo so that the state can be updated + // during mutations of the API without returning marshalled types directly. + } + + /// + public bool ConfigurationCorrupt { get; internal set; } + + /// + public bool Exists { get; internal set; } + + /// + public bool IsAutomatic { get; internal set; } + + /// + public bool IsRunning { get; internal set; } + + /// + public bool IsShared { get; internal set; } + + /// + public DateTime LastStartTimeUtc { get; internal set; } + + /// + public Version LocalDbVersion { get; internal set; } + + /// + public string Name { get; internal set; } + + /// + public string NamedPipe { get; internal set; } + + /// + public string OwnerSid { get; internal set; } + + /// + public string SharedName { get; internal set; } + + /// + /// Updates the state of the instance from the specified value. + /// + /// The other value to use to update the instance's state. + internal void Update(ISqlLocalDbInstanceInfo other) + { + if (other == null || ReferenceEquals(other, this)) + { + return; + } + + ConfigurationCorrupt = other.ConfigurationCorrupt; + Exists = other.Exists; + IsAutomatic = other.IsAutomatic; + IsRunning = other.IsRunning; + IsShared = other.IsShared; + LastStartTimeUtc = other.LastStartTimeUtc; + LocalDbVersion = other.LocalDbVersion; + Name = other.Name; + NamedPipe = other.NamedPipe; + OwnerSid = other.OwnerSid; + SharedName = other.SharedName; + } + } +} diff --git a/src/SqlLocalDb/SqlLocalDbInstanceManager.cs b/src/SqlLocalDb/SqlLocalDbInstanceManager.cs new file mode 100644 index 00000000..b5178c17 --- /dev/null +++ b/src/SqlLocalDb/SqlLocalDbInstanceManager.cs @@ -0,0 +1,165 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class that can be used to manage instances of SQL LocalDB. This class cannot be inherited. + /// + [DebuggerDisplay("{Name}")] + public sealed class SqlLocalDbInstanceManager : ISqlLocalDbInstanceManager + { + /// + /// Initializes a new instance of the class. + /// + /// The SQL Server LocalDB instance to manage. + /// The instance to use. + /// + /// or is . + /// + public SqlLocalDbInstanceManager(ISqlLocalDbInstanceInfo instance, ISqlLocalDbApi api) + { + Instance = instance ?? throw new ArgumentNullException(nameof(instance)); + Api = api ?? throw new ArgumentNullException(nameof(api)); + } + + /// + public string Name => Instance.Name; + + /// + public string NamedPipe => Instance.NamedPipe; + + /// + /// Gets the to use. + /// + private ISqlLocalDbApi Api { get; } + + /// + /// Gets the in use. + /// + private ISqlLocalDbInstanceInfo Instance { get; } + + /// + /// Gets the current state of the instance. + /// + /// + /// An representing the current state of the instance being managed. + /// + public ISqlLocalDbInstanceInfo GetInstanceInfo() => Api.GetInstanceInfo(Name); + + /// + /// Shares the LocalDB instance using the specified name. + /// + /// The name to use to share the instance. + /// + /// is . + /// + /// + /// The LocalDB instance could not be shared. + /// + public void Share(string sharedName) + { + if (sharedName == null) + { + throw new ArgumentNullException(nameof(sharedName)); + } + + try + { + Api.ShareInstance(Name, sharedName); + UpdateState(); + } + catch (SqlLocalDbException ex) + { + throw new SqlLocalDbException( + SRHelper.Format(SR.SqlLocalDbInstanceManager_ShareFailedFormat, Name), + ex.ErrorCode, + ex.InstanceName, + ex); + } + } + + /// + /// Starts the SQL Server LocalDB instance. + /// + /// + /// The LocalDB instance could not be started. + /// + public void Start() + { + try + { + Api.StartInstance(Name); + UpdateState(); + } + catch (SqlLocalDbException ex) + { + throw new SqlLocalDbException( + SRHelper.Format(SR.SqlLocalDbInstanceManager_StartFailedFormat, Name), + ex.ErrorCode, + ex.InstanceName, + ex); + } + } + + /// + /// Stops the SQL Server LocalDB instance. + /// + /// + /// The LocalDB instance could not be stopped. + /// + public void Stop() + { + try + { + Api.StopInstance(Name, null); + UpdateState(); + } + catch (SqlLocalDbException ex) + { + throw new SqlLocalDbException( + SRHelper.Format(SR.SqlLocalDbInstanceManager_StopFailedFormat, Name), + ex.ErrorCode, + ex.InstanceName, + ex); + } + } + + /// + /// Stops sharing the LocalDB instance. + /// + /// + /// The LocalDB instance could not be unshared. + /// + public void Unshare() + { + try + { + Api.UnshareInstance(Name); + UpdateState(); + } + catch (SqlLocalDbException ex) + { + throw new SqlLocalDbException( + SRHelper.Format(SR.SqlLocalDbInstanceManager_UnshareFailedFormat, Name), + ex.ErrorCode, + ex.InstanceName, + ex); + } + } + + /// + /// Updates the state of , if possible. + /// + private void UpdateState() + { + if (Instance is SqlLocalDbInstanceInfo info) + { + info.Update(Api.GetInstanceInfo(info.Name)); + } + } + } +} diff --git a/src/SqlLocalDb/SqlLocalDbOptions.cs b/src/SqlLocalDb/SqlLocalDbOptions.cs new file mode 100644 index 00000000..7b6e7daa --- /dev/null +++ b/src/SqlLocalDb/SqlLocalDbOptions.cs @@ -0,0 +1,73 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Globalization; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class representing options for using the SQL LocalDB API. + /// + public class SqlLocalDbOptions + { + /// + /// Initializes a new instance of the class. + /// + public SqlLocalDbOptions() + { + } + + /// + /// Gets or sets a value indicating whether to automatically delete the + /// files associated with SQL LocalDB instances when they are deleted. + /// + public bool AutomaticallyDeleteInstanceFiles { get; set; } + + /// + /// Gets or sets the override language to use to format error messages. + /// + public CultureInfo Language { get; set; } + + /// + /// Gets or sets the override version string of the native SQL LocalDB API to load, if any. + /// + public string NativeApiOverrideVersion { get; set; } = string.Empty; + + /// + /// Gets or sets the options to use when stopping instances of SQL LocalDB. + /// + public StopInstanceOptions StopOptions { get; set; } = StopInstanceOptions.None; + + /// + /// Gets or sets the default timeout to use when stopping instances of SQL LocalDB. + /// + public TimeSpan StopTimeout { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Gets the locale ID (LCID) to use for formatting error messages. + /// + internal int LanguageId + { + get + { + CultureInfo culture = Language; + + if (culture == null) + { + // Zero is used by SQL LocalDB to mean to defer to the OS configuration + return 0; + } + + // N.B. No checks as to the support of the configured culture's LCID for use + // by SQL LocalDB are made here, it is left to the user to ensure that the + // culture code they configure is supported. From experimentation, SQL LocalDB + // supports the "main" language/region for cultures and not the neutral culture. + // For example: + // Supported: de-DE, en-US, es-ES, fr-FR; + // Not supported: de, en, en-GB, es, es-MX, fr, fr-CA. + return culture.LCID; + } + } + } +} diff --git a/src/SqlLocalDb/SqlLocalDbProvider.cs b/src/SqlLocalDb/SqlLocalDbProvider.cs deleted file mode 100644 index 731ed857..00000000 --- a/src/SqlLocalDb/SqlLocalDbProvider.cs +++ /dev/null @@ -1,224 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbProvider.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing a provider for obtaining instances of - /// - [Serializable] - public class SqlLocalDbProvider : ISqlLocalDbProvider - { - /// - /// The instance of in use by the provider. - /// - private readonly ISqlLocalDbApi _localDB; - - /// - /// The version of SQL LocalDB to use to create instances, if specified. - /// - /// - /// If the value is , the latest version reported by is used. - /// - private string _version; - - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbProvider() - { - _localDB = SqlLocalDbApiWrapper.Instance; - } - - /// - /// Initializes a new instance of the class. - /// - /// The instance of to use. - /// - /// is . - /// - public SqlLocalDbProvider(ISqlLocalDbApi localDB) - { - _localDB = localDB ?? throw new ArgumentNullException(nameof(localDB)); - } - - /// - /// Gets or sets the version of SQL LocalDB to use to create instances. - /// - /// - /// If no value is set, the latest version of SQL LocalDB installed on the local machine is used. - /// - public virtual string Version - { - get { return _version ?? _localDB.LatestVersion; } - set { _version = value; } - } - - /// - /// Gets the in use by the instance. - /// - internal ISqlLocalDbApi LocalDB => _localDB; - - /// - /// Creates a new instance of with a unique random name. - /// - /// - /// The created instance of . - /// - /// - /// A new LocalDB instance could not be created. - /// - public virtual SqlLocalDbInstance CreateInstance() => CreateInstance(Guid.NewGuid().ToString()); - - /// - /// Creates a new instance of . - /// - /// The name of the SQL Server LocalDB instance to create. - /// - /// The created instance of . - /// - /// - /// is . - /// - /// - /// The LocalDB instance specified by already exists or - /// no LocalDB instance information was returned by the underlying SQL LocalDB API. - /// - /// - /// The LocalDB instance specified by could not be created. - /// - public virtual SqlLocalDbInstance CreateInstance(string instanceName) - { - ISqlLocalDbInstanceInfo info = _localDB.GetInstanceInfo(instanceName); - - if (info == null) - { - throw new InvalidOperationException(SR.SqlLocalDbProvider_NoInstance); - } - - if (info.Exists) - { - string message = SRHelper.Format( - SR.SqlLocalDbFactory_InstanceExistsFormat, - instanceName); - - Logger.Error(Logger.TraceEvent.CreateInstance, message); - - throw new InvalidOperationException(message); - } - - string version; - - // If creating the default instance, the version number must not be specified - if (SqlLocalDbApi.IsDefaultInstanceName(instanceName)) - { - version = string.Empty; - } - else - { - version = _version ?? _localDB.LatestVersion; - } - - _localDB.CreateInstance(instanceName, version); - - return GetInstance(instanceName); - } - - /// - /// Returns an existing instance of . - /// - /// The name of the SQL Server LocalDB instance to return. - /// - /// The existing instance of . - /// - /// - /// is . - /// - /// - /// The LocalDB instance specified by does not exist. - /// - /// - /// The LocalDB instance specified by could not be obtained. - /// - public virtual SqlLocalDbInstance GetInstance(string instanceName) => new SqlLocalDbInstance(instanceName, _localDB); - - /// - /// Returns information about the available SQL Server LocalDB instances. - /// - /// - /// An containing information - /// about the available SQL Server LocalDB instances on the current machine. - /// - public virtual IList GetInstances() - { - IList instanceNames = _localDB.GetInstanceNames(); - - List instances = new List(); - - if (instanceNames != null) - { - foreach (string name in instanceNames) - { - ISqlLocalDbInstanceInfo info = _localDB.GetInstanceInfo(name); - instances.Add(info); - } - } - - return instances; - } - - /// - /// Returns information about the installed SQL Server LocalDB version(s). - /// - /// - /// An containing information - /// about the SQL Server LocalDB version(s) installed on the current machine. - /// - public virtual IList GetVersions() - { - IList versionNames = _localDB.Versions; - - List versions = new List(); - - if (versionNames != null) - { - foreach (string version in versionNames) - { - ISqlLocalDbVersionInfo info = _localDB.GetVersionInfo(version); - versions.Add(info); - } - } - - return versions; - } - - /// - /// Creates a new instance of . - /// - /// The name of the SQL Server LocalDB instance to create. - /// - /// The created instance of . - /// - ISqlLocalDbInstance ISqlLocalDbProvider.CreateInstance(string instanceName) => CreateInstance(instanceName); - - /// - /// Returns an existing instance of . - /// - /// The name of the SQL Server LocalDB instance to return. - /// - /// The existing instance of . - /// - ISqlLocalDbInstance ISqlLocalDbProvider.GetInstance(string instanceName) => GetInstance(instanceName); - } -} diff --git a/src/SqlLocalDb/SqlLocalDbVersionInfo.cs b/src/SqlLocalDb/SqlLocalDbVersionInfo.cs new file mode 100644 index 00000000..156ded3f --- /dev/null +++ b/src/SqlLocalDb/SqlLocalDbVersionInfo.cs @@ -0,0 +1,47 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class representing information about a version of SQL Server LocalDB. This class cannot be inherited. + /// + [DebuggerDisplay("{Name}")] + internal sealed class SqlLocalDbVersionInfo : ISqlLocalDbVersionInfo + { + /// + /// Initializes a new instance of the class. + /// + internal SqlLocalDbVersionInfo() + { + } + + /// + public bool Exists { get; internal set; } + + /// + public string Name { get; internal set; } + + /// + public Version Version { get; internal set; } + + /// + /// Updates the state of the instance from the specified value. + /// + /// The other value to use to update the instance's state. + internal void Update(ISqlLocalDbVersionInfo other) + { + if (other == null || ReferenceEquals(other, this)) + { + return; + } + + Exists = other.Exists; + Name = other.Name; + Version = other.Version; + } + } +} diff --git a/src/SqlLocalDb/StopInstanceOptions.cs b/src/SqlLocalDb/StopInstanceOptions.cs index 414da492..c0754df9 100644 --- a/src/SqlLocalDb/StopInstanceOptions.cs +++ b/src/SqlLocalDb/StopInstanceOptions.cs @@ -1,16 +1,9 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// StopInstanceOptions.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -namespace System.Data.SqlLocalDb +using System; + +namespace MartinCostello.SqlLocalDb { /// /// An enumeration of options to control the behavior when stopping a SQL LocalDB instance. diff --git a/src/SqlLocalDb/System.Data.SqlLocalDb.csproj b/src/SqlLocalDb/System.Data.SqlLocalDb.csproj deleted file mode 100644 index 85ce1e7e..00000000 --- a/src/SqlLocalDb/System.Data.SqlLocalDb.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - SQL Server LocalDB API - A .NET assembly providing interop with the SQL LocalDB native API from managed code using .NET APIs. - true - Library - true - System.Data.SqlLocalDb - net451 - - - - - - - - - - - - - diff --git a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs index 539a098e..298c1561 100644 --- a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs +++ b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs @@ -1,42 +1,24 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// TemporarySqlLocalDbInstance.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. -using System.Data.SqlClient; +using System; +using Microsoft.Extensions.Logging; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb { /// - /// A class representing a temporary SQL LocalDB instance. + /// A class representing a temporary SQL LocalDB instance. This class cannot be inherited. /// /// /// The temporary SQL LocalDB instances that are created by instances of this class are automatically /// started when they are instantiated, and are then subsequently deleted when they are disposed of. /// - public class TemporarySqlLocalDbInstance : ISqlLocalDbInstance, IDisposable + public sealed class TemporarySqlLocalDbInstance : IDisposable { /// - /// The default instance to use to create temporary instances. + /// The lazily initialized name of the temporary SQL LocalDB instance. This field is read-only. /// - private static readonly ISqlLocalDbProvider DefaultProvider = new SqlLocalDbProvider(); - - /// - /// Whether to delete the files associated with the instance when disposed. - /// - private readonly bool _deleteFiles; - - /// - /// The temporary SQL LocalDB instance. - /// - private readonly ISqlLocalDbInstance _instance; + private readonly Lazy _instanceName; /// /// Whether the instance has been disposed. @@ -44,250 +26,104 @@ public class TemporarySqlLocalDbInstance : ISqlLocalDbInstance, IDisposable private bool _disposed; /// - /// Initializes a new instance of the class. + /// The to use, if any. /// - /// The name of the temporary SQL LocalDB instance. - /// - /// is . - /// - public TemporarySqlLocalDbInstance(string instanceName) - : this(instanceName, DefaultProvider) - { - } + private ILogger _logger; /// /// Initializes a new instance of the class. /// - /// The name of the temporary SQL LocalDB instance. - /// The to use to create the temporary instance. - /// - /// or is . - /// - public TemporarySqlLocalDbInstance(string instanceName, ISqlLocalDbProvider provider) - : this(instanceName, provider, SqlLocalDbApi.AutomaticallyDeleteInstanceFiles) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the temporary SQL LocalDB instance. - /// The to use to create the temporary instance. + /// The to use to create the temporary instance. /// Whether to delete the file(s) associated with the SQL LocalDB instance when deleted. /// - /// or is . + /// is . /// - public TemporarySqlLocalDbInstance(string instanceName, ISqlLocalDbProvider provider, bool deleteFiles) + internal TemporarySqlLocalDbInstance(ISqlLocalDbApi api, bool deleteFiles) { - if (instanceName == null) - { - throw new ArgumentNullException(nameof(instanceName)); - } - - if (provider == null) - { - throw new ArgumentNullException(nameof(provider)); - } - - _instance = provider.CreateInstance(instanceName); - _deleteFiles = deleteFiles; - - try - { - _instance.Start(); - } - catch (Exception) - { - SqlLocalDbInstance.Delete(_instance, throwIfNotFound: true, deleteFiles: _deleteFiles); - throw; - } + Api = api ?? throw new ArgumentNullException(nameof(api)); + DeleteFiles = deleteFiles; + _instanceName = new Lazy(EnsureInitialized); } /// - /// Initializes a new instance of the class. + /// Finalizes an instance of the class. /// - /// The instance to use. - /// - /// Used for unit testing. - /// - internal TemporarySqlLocalDbInstance(ISqlLocalDbInstance instance) - { - _instance = instance; - } + ~TemporarySqlLocalDbInstance() => DisposeInternal(); /// - /// Finalizes an instance of the class. + /// Gets the name of the temporary SQL LocalDB instance. /// - ~TemporarySqlLocalDbInstance() + /// + /// Thrown if the instance has been disposed of. + /// + public string Name { - Dispose(false); + get + { + EnsureNotDisposed(); + return _instanceName.Value; + } } /// - /// Gets the name of the LocalDB instance. - /// - public string Name => _instance.Name; - - /// - /// Gets the named pipe that should be used - /// to connect to the LocalDB instance. + /// Gets the to use. /// - public string NamedPipe => _instance.NamedPipe; + private ISqlLocalDbApi Api { get; } /// /// Gets a value indicating whether to delete the instance file(s) when the instance is disposed of. /// - internal bool DeleteFiles => _deleteFiles; - - /// - /// Gets the temporary SQL LocalDB instance associated with this instance. - /// - internal ISqlLocalDbInstance Instance => _instance; - - /// - /// Creates a new instance of with a randomly assigned name. - /// - /// - /// The created instance of . - /// - public static TemporarySqlLocalDbInstance Create() => Create(SqlLocalDbApi.AutomaticallyDeleteInstanceFiles); + private bool DeleteFiles { get; } /// - /// Creates a new instance of with a randomly assigned name. - /// - /// Whether to delete the file(s) associated with the SQL LocalDB instance when deleted. - /// - /// The created instance of . - /// - public static TemporarySqlLocalDbInstance Create(bool deleteFiles) - { - string instanceName = Guid.NewGuid().ToString(); - return new TemporarySqlLocalDbInstance(instanceName, DefaultProvider, deleteFiles); - } - - /// - /// Creates a connection to the LocalDB instance. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - /// - /// An instance of that - /// can be used to connect to the LocalDB instance. - /// - /// - /// The instance has been disposed. - /// - public SqlConnection CreateConnection() + public void Dispose() { - EnsureNotDisposed(); - return _instance.CreateConnection(); + DisposeInternal(); + GC.SuppressFinalize(this); } /// - /// Creates an instance of containing - /// the default SQL connection string to connect to the LocalDB instance. + /// Gets the temporary SQL LocalDB instance. /// /// - /// An instance of containing - /// the default SQL connection string to connect to the LocalDB instance. + /// An representing the temporary SQL LocalDB instance. /// /// - /// The instance has been disposed. + /// Thrown if the instance has been disposed of. /// - public SqlConnectionStringBuilder CreateConnectionStringBuilder() - { - EnsureNotDisposed(); - return _instance.CreateConnectionStringBuilder(); - } + public ISqlLocalDbInstanceInfo GetInstanceInfo() => Api.GetInstanceInfo(Name); /// - /// Returns information about the LocalDB instance. + /// Returns an that can be used to manage the instance. /// /// - /// An instance of containing - /// information about the LocalDB instance. + /// An that can be used to manage the temporary SQL LocalDB instance. /// /// - /// The instance has been disposed. - /// - public ISqlLocalDbInstanceInfo GetInstanceInfo() - { - EnsureNotDisposed(); - return _instance.GetInstanceInfo(); - } - - /// - /// Shares the LocalDB instance using the specified name. - /// - /// The name to use to share the instance. - /// - /// The instance has been disposed. - /// - public void Share(string sharedName) - { - EnsureNotDisposed(); - _instance.Share(sharedName); - } - - /// - /// Starts the LocalDB instance. - /// - /// - /// The instance has been disposed. - /// - public void Start() - { - EnsureNotDisposed(); - _instance.Start(); - } - - /// - /// Stops the LocalDB instance. - /// - /// - /// The instance has been disposed. - /// - public void Stop() - { - EnsureNotDisposed(); - _instance.Stop(); - } - - /// - /// Stops sharing the LocalDB instance. - /// - /// - /// The instance has been disposed. + /// Thrown if the instance has been disposed of. /// - public void Unshare() + public ISqlLocalDbInstanceManager Manage() { - EnsureNotDisposed(); - _instance.Unshare(); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + ISqlLocalDbInstanceInfo instance = GetInstanceInfo(); + return new SqlLocalDbInstanceManager(instance, Api); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) + private void DisposeInternal() { if (!_disposed) { - if (_instance != null) + if (_instanceName.IsValueCreated) { + string instanceName = _instanceName.Value; + try { - _instance.Stop(); + Api.StopInstance(instanceName, timeout: null); } catch (SqlLocalDbException ex) { @@ -295,20 +131,27 @@ protected virtual void Dispose(bool disposing) // because it does not exist, otherwise log the error. if (ex.ErrorCode != SqlLocalDbErrors.UnknownInstance) { - Logger.Error(Logger.TraceEvent.StopFailed, SR.TemporarySqlLocalDbInstance_StopFailedFormat, _instance.Name, ex.ErrorCode); + _logger?.StoppingTemporaryInstanceFailed(instanceName, ex.ErrorCode); } } try { - SqlLocalDbInstance.Delete(_instance, throwIfNotFound: false, deleteFiles: _deleteFiles); + if (Api is SqlLocalDbApi localDB) + { + localDB.DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: DeleteFiles); + } + else + { + Api.DeleteInstance(instanceName); + } } catch (SqlLocalDbException ex) { - // Ignore the exception if we could not delete the instance because it was in use + // Ignore the exception if we could not delete the instance because it was still in use if (ex.ErrorCode != SqlLocalDbErrors.InstanceBusy) { - Logger.Error(Logger.TraceEvent.DeleteFailed, SR.TemporarySqlLocalDbInstance_DeleteFailedFormat, _instance.Name, ex.ErrorCode); + _logger?.DeletingInstanceFailed(instanceName, ex.ErrorCode); } } } @@ -317,6 +160,38 @@ protected virtual void Dispose(bool disposing) } } + /// + /// Ensures that the instance has been initialized. + /// + /// + /// The name of the SQL LocalDB instance that was created. + /// + private string EnsureInitialized() + { + ILoggerFactory loggerFactory = null; + + if (Api is SqlLocalDbApi localDB) + { + loggerFactory = localDB.LoggerFactory; + } + + _logger = loggerFactory?.CreateLogger(); + + string instanceName = Guid.NewGuid().ToString(); + Api.CreateInstance(instanceName, Api.LatestVersion); + + try + { + Api.StartInstance(instanceName); + return instanceName; + } + catch (Exception) + { + Api.DeleteInstance(instanceName); + throw; + } + } + /// /// Ensures that the instance has not been disposed of. /// diff --git a/src/SqlLocalDb/TraceSourceLogger.cs b/src/SqlLocalDb/TraceSourceLogger.cs deleted file mode 100644 index 5f5f6de7..00000000 --- a/src/SqlLocalDb/TraceSourceLogger.cs +++ /dev/null @@ -1,265 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// TraceSourceLogger.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Diagnostics; -using System.Security; -using System.Threading; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing an implementation of that logs to a . This class cannot be inherited. - /// - internal sealed class TraceSourceLogger : MarshalByRefObject, ILogger - { - /// - /// The singleton instance of . This field is read-only. - /// - internal static readonly TraceSourceLogger Instance = new TraceSourceLogger(); - - /// - /// The name of the System.Data.SqlLocalDb . - /// - private const string TraceSourceName = "System.Data.SqlLocalDb"; - - /// - /// Whether the application domain has shutdown. - /// - private static bool _appDomainShutdown; - - /// - /// Whether logging is enabled. - /// - private static bool _enabled; - - /// - /// Whether logging has been initialized. - /// - private static bool _initialized; - - /// - /// The synchronization object for the logging class. - /// - private static object _syncRoot; - - /// - /// The trace source for System.Data.SqlLocalDb. - /// - private static TraceSource _traceSource; - - /// - /// Prevents a default instance of the class from being created. - /// - private TraceSourceLogger() - : base() - { - } - - /// - /// Gets the trace source used for the System.Data.SqlLocalDb assembly - /// if logging is enabled; otherwise . - /// - private static TraceSource Source => EnsureInitialized() ? _traceSource : null; - - /// - /// Gets the synchronization object used by the class. - /// - private static object SyncRoot - { - get - { - if (_syncRoot == null) - { - object syncRoot = new object(); - Interlocked.CompareExchange(ref _syncRoot, syncRoot, null); - } - - return _syncRoot; - } - } - - /// - /// Writes an error trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - public void WriteError(int id, string format, params object[] args) - { - WriteEvent(TraceEventType.Error, id, format, args); - } - - /// - /// Writes an informational trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - public void WriteInformation(int id, string format, params object[] args) - { - WriteEvent(TraceEventType.Information, id, format, args); - } - - /// - /// Writes a verbose trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - public void WriteVerbose(int id, string format, params object[] args) - { - WriteEvent(TraceEventType.Verbose, id, format, args); - } - - /// - /// Writes a warning trace event to the trace listeners for the assembly's trace source. - /// - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - public void WriteWarning(int id, string format, params object[] args) - { - WriteEvent(TraceEventType.Warning, id, format, args); - } - - /// - /// Closes the trace sources. - /// - [SecurityCritical] - private static void Close() - { - if (_traceSource != null) - { - _traceSource.Close(); - } - } - - /// - /// Event handler for when the current application domain unloads or the current process exits. - /// - /// The sender. - /// The instance containing the event data. - [SecurityCritical] - private static void DomainUnloadOrProcessExit(object sender, EventArgs e) - { - Close(); - _appDomainShutdown = true; - - // Remove the event handlers to prevent memory leaks - AppDomain current = AppDomain.CurrentDomain; - current.DomainUnload -= DomainUnloadOrProcessExit; - current.ProcessExit -= DomainUnloadOrProcessExit; - } - - /// - /// Initializes the class. - /// - /// - /// if logging is enabled; otherwise . - /// - [SecurityCritical] - private static bool EnsureInitialized() - { - lock (SyncRoot) - { - if (!_initialized) - { - _traceSource = new TraceSource(TraceSourceName); - - bool loggingEnabled; - - try - { - loggingEnabled = _traceSource.Switch.ShouldTrace(TraceEventType.Critical); - } - catch (SecurityException) - { - Close(); - loggingEnabled = false; - } - - if (loggingEnabled) - { - AppDomain currentDomain = AppDomain.CurrentDomain; - currentDomain.DomainUnload += DomainUnloadOrProcessExit; - currentDomain.ProcessExit += DomainUnloadOrProcessExit; - } - - _enabled = loggingEnabled; - _initialized = true; - } - - return _enabled; - } - } - - /// - /// Validates the settings. - /// - /// The trace source to write to. - /// The trace level of the message. - /// - /// if the settings are valid; - /// otherwise . - /// - private static bool ValidateSettings(TraceSource traceSource, TraceEventType traceLevel) - { - if ((traceSource == null) || !traceSource.Switch.ShouldTrace(traceLevel)) - { - return false; - } - - return !_appDomainShutdown; - } - - /// - /// Writes a trace event to the trace listeners for the assembly's trace source. - /// - /// The trace level to log at. - /// A numeric identifier for the event. - /// - /// A composite format string that contains text intermixed with zero or more - /// format items, which correspond to objects in the args array. - /// - /// - /// An object array containing zero or more objects to format. - /// - [Conditional("TRACE")] - private static void WriteEvent(TraceEventType traceLevel, int id, string format, params object[] args) - { - if (ValidateSettings(Source, traceLevel)) - { - Source.TraceEvent(traceLevel, id, format, args); - } - } - } -} diff --git a/src/TestApp/Log4NetLogger.cs b/src/TestApp/Log4NetLogger.cs deleted file mode 100644 index 1fd4d304..00000000 --- a/src/TestApp/Log4NetLogger.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// Log4NetLogger.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Globalization; -using log4net; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing an implementation of that uses log4net. This class cannot be inherited. - /// - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Performance", - "CA1812:AvoidUninstantiatedInternalClasses", - Justification = "Instantiated via reflection.")] - internal sealed class Log4NetLogger : ILogger - { - /// - /// The log4net logger being wrapped by the interface. This field is read-only. - /// - private static readonly ILog Logger = LogManager.GetLogger("System.Data.SqlLocalDb"); - - /// - public void WriteError(int id, string format, params object[] args) => Logger.ErrorFormat(CultureInfo.InvariantCulture, format, args); - - /// - public void WriteInformation(int id, string format, params object[] args) => Logger.InfoFormat(CultureInfo.InvariantCulture, format, args); - - /// - public void WriteVerbose(int id, string format, params object[] args) => Logger.DebugFormat(CultureInfo.InvariantCulture, format, args); - - /// - public void WriteWarning(int id, string format, params object[] args) => Logger.WarnFormat(CultureInfo.InvariantCulture, format, args); - } -} diff --git a/src/TestApp/MartinCostello.SqlLocalDb.TestApp.csproj b/src/TestApp/MartinCostello.SqlLocalDb.TestApp.csproj new file mode 100644 index 00000000..e25cc820 --- /dev/null +++ b/src/TestApp/MartinCostello.SqlLocalDb.TestApp.csproj @@ -0,0 +1,18 @@ + + + Test application for MartinCostello.SqlLocalDb. + Exe + MartinCostello.SqlLocalDb + netcoreapp2.1 + + + + + + + + + + + + diff --git a/src/TestApp/Program.cs b/src/TestApp/Program.cs index 35fda0be..bb449793 100644 --- a/src/TestApp/Program.cs +++ b/src/TestApp/Program.cs @@ -1,24 +1,18 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// Program.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Reflection; +using System.Runtime.InteropServices; using System.Security.Principal; +using Microsoft.Extensions.Logging; -namespace System.Data.SqlLocalDb +namespace MartinCostello.SqlLocalDb { /// - /// An application that acts as a test harness for the System.Data.SqlLocalDb assembly. This class cannot be inherited. + /// An application that acts as a test harness for the MartinCostello.SqlLocalDb assembly. This class cannot be inherited. /// internal static class Program { @@ -26,15 +20,20 @@ internal static class Program /// The main entry point to the application. /// /// The command-line arguments passed to the application. - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Usage", - "CA2202:Do not dispose objects multiple times", - Justification = "It isn't.")] internal static void Main(string[] args) { PrintBanner(); - ISqlLocalDbApi localDB = new SqlLocalDbApiWrapper(); + var options = new SqlLocalDbOptions() + { + AutomaticallyDeleteInstanceFiles = true, + StopOptions = StopInstanceOptions.NoWait, + }; + + var loggerFactory = new LoggerFactory() + .AddConsole(LogLevel.Debug); + + var localDB = new SqlLocalDbApi(options, loggerFactory); if (!localDB.IsLocalDBInstalled()) { @@ -42,14 +41,14 @@ internal static void Main(string[] args) return; } - if (args?.Length == 1 && string.Equals(args[0], "/deleteuserinstances", StringComparison.OrdinalIgnoreCase)) + if (args?.Length == 1 && + (string.Equals(args[0], "/deleteuserinstances", StringComparison.OrdinalIgnoreCase) || + string.Equals(args[0], "--delete-user-instances", StringComparison.OrdinalIgnoreCase))) { - SqlLocalDbApi.DeleteUserInstances(deleteFiles: true); + localDB.DeleteUserInstances(deleteFiles: true); } - ISqlLocalDbProvider provider = new SqlLocalDbProvider(); - - IList versions = provider.GetVersions(); + IReadOnlyList versions = localDB.GetVersions(); Console.WriteLine(Strings.Program_VersionsListHeader); Console.WriteLine(); @@ -61,7 +60,7 @@ internal static void Main(string[] args) Console.WriteLine(); - IList instances = provider.GetInstances(); + IReadOnlyList instances = localDB.GetInstances(); Console.WriteLine(Strings.Program_InstancesListHeader); Console.WriteLine(); @@ -75,20 +74,21 @@ internal static void Main(string[] args) string instanceName = Guid.NewGuid().ToString(); - ISqlLocalDbInstance instance = provider.CreateInstance(instanceName); + ISqlLocalDbInstanceInfo instance = localDB.CreateInstance(instanceName); - instance.Start(); + var manager = new SqlLocalDbInstanceManager(instance, localDB); + manager.Start(); try { if (IsCurrentUserAdmin()) { - instance.Share(Guid.NewGuid().ToString()); + manager.Share(Guid.NewGuid().ToString()); } try { - using (SqlConnection connection = instance.CreateConnection()) + using (SqlConnection connection = manager.CreateConnection()) { connection.Open(); @@ -114,7 +114,7 @@ internal static void Main(string[] args) { if (IsCurrentUserAdmin()) { - instance.Unshare(); + manager.Unshare(); } } } @@ -124,7 +124,7 @@ internal static void Main(string[] args) } finally { - instance.Stop(); + manager.Stop(); localDB.DeleteInstance(instance.Name); } @@ -142,6 +142,11 @@ internal static void Main(string[] args) /// private static bool IsCurrentUserAdmin() { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; + } + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) { WindowsPrincipal principal = new WindowsPrincipal(identity); @@ -165,7 +170,6 @@ private static void PrintBanner() assembly.GetCustomAttribute().Version, assembly.GetCustomAttribute().InformationalVersion, assembly.GetCustomAttribute().Configuration, - assembly.ImageRuntimeVersion, Environment.UserDomainName, Environment.UserName, IsCurrentUserAdmin(), diff --git a/src/TestApp/Properties/AssemblyInfo.cs b/src/TestApp/Properties/AssemblyInfo.cs index 2b099887..c0377fe0 100644 --- a/src/TestApp/Properties/AssemblyInfo.cs +++ b/src/TestApp/Properties/AssemblyInfo.cs @@ -1,19 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// AssemblyInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; using System.Runtime.InteropServices; -[assembly: CLSCompliant(true)] [assembly: Guid("b6649785-36c9-46d4-8f86-85bb6ee60c7e")] - -[assembly: log4net.Config.XmlConfigurator] diff --git a/src/TestApp/Strings.Designer.cs b/src/TestApp/Strings.Designer.cs index b531a728..1e383336 100644 --- a/src/TestApp/Strings.Designer.cs +++ b/src/TestApp/Strings.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace System.Data.SqlLocalDb { +namespace MartinCostello.SqlLocalDb { using System; @@ -19,7 +19,7 @@ namespace System.Data.SqlLocalDb { // 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -39,7 +39,7 @@ internal Strings() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Data.SqlLocalDb.Strings", typeof(Strings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MartinCostello.SqlLocalDb.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; @@ -68,12 +68,11 @@ internal Strings() { ///Assembly File Version: {3} ///Assembly Informational Version: {4} ///Assembly Configuration: {5} - ///Image Runtime Version: {6} - ///Windows User: {7}\{8} - ///Does User Have Admin Token? {9} - ///Operating System: {10} - ///Is 64-bit Operating System? {11} - ///Is 64-bit Process? {12} + ///User: {6}\{7} + ///Does User Have Admin Token? {8} + ///Operating System: {9} + ///Is 64-bit Operating System? {10} + ///Is 64-bit Process? {11} ///. /// internal static string Program_BannerFormat { diff --git a/src/TestApp/Strings.resx b/src/TestApp/Strings.resx index 7a8bef70..0bcc9e51 100644 --- a/src/TestApp/Strings.resx +++ b/src/TestApp/Strings.resx @@ -125,12 +125,11 @@ Assembly Version: {2} Assembly File Version: {3} Assembly Informational Version: {4} Assembly Configuration: {5} -Image Runtime Version: {6} -Windows User: {7}\{8} -Does User Have Admin Token? {9} -Operating System: {10} -Is 64-bit Operating System? {11} -Is 64-bit Process? {12} +User: {6}\{7} +Does User Have Admin Token? {8} +Operating System: {9} +Is 64-bit Operating System? {10} +Is 64-bit Process? {11} diff --git a/src/TestApp/System.Data.SqlLocalDb.TestApp.csproj b/src/TestApp/System.Data.SqlLocalDb.TestApp.csproj deleted file mode 100644 index e0cf25bd..00000000 --- a/src/TestApp/System.Data.SqlLocalDb.TestApp.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - app.ico - app.manifest - Test application for System.Data.SqlLocalDb. - Exe - System.Data.SqlLocalDb - net451 - - - - - - - - - - - - diff --git a/src/TestApp/app.config b/src/TestApp/app.config deleted file mode 100644 index 58dc1111..00000000 --- a/src/TestApp/app.config +++ /dev/null @@ -1,22 +0,0 @@ - - - -
-
- - - - - - - - - - - - - - - - - diff --git a/src/TestApp/app.ico b/src/TestApp/app.ico deleted file mode 100644 index 42d625aa427c036e4c8bc25cd8e68c08b0aebbd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10134 zcmeHNy>i<|5MJ=#5=lUL02?!IT;&-&>XmV;7s$9%s(?}>RcnhoguiE%AL1bwfVaM1B+z z&kOMr#n0bjuH$lH#{&kgcJ{CQL4VNdzKyaoN>JLUWG9J1d(7I^}jFdJ zf;&?*2=;-S1PuZ@sqo;C5j{j8!gy$WBmAY}^N^p%1La$J##`+%-udwWAv0cA#xpzK zW>h!-itNut9*srXgpo+S44`cSebFaXp3t<&B)2oXGp%-plFfsr>%ogM9ky zZ#h3dmwzTxxxBoT`5Yrul_~;N1pZeM2p@3T<&6!jNo?8JP+D9oE2(1^8ld(cwCYY+ zYW3EGK1eD7yX%dJ7gvpS&x_o6UNa`A8qLVUHEhR;1UxULlxcPV1nx7xuk^3kk7mfS`H`Pr zPHuj2o+a~ATRQvVx_Exyuk~ECPKnkfw(AA!lMUdm$KrncGXYTyYRbKHU5Z!#;L4Y<6Ol*8v%_=S#ejp>L=^H z%72ZE#;L4Y{Z#SKMnL0IR@~LD`pJ4vk-%GkU}7jGL)r``b^iW z`sZ2_=P?WYUDpq#vp=*!TiS<3{<&UNIh^|x-lN*SNlja7_a6JttUt?$|C05mU4L^7 z@@wF`$UoXl?MJSxzd0W;Hpe0LNbOo0jysHR$;= zo6VkHTwE|$<9#~XCV~lhl*#K&CKC_5cldT}=NP^#$Im5wK9R<`LK(_ZN9_>jzFuXQ z)s&@<>a%~#bn<-aRQh2A?o0gmZs0o}=MY!kk))BQ@7(nG@%c;oqBVrRS1H3>xJ!M_ i-&Omc&)+YdKbptO<}dE~*Zlk)w{P~lnDp;xsQVv<_v6d} diff --git a/src/TestApp/app.manifest b/src/TestApp/app.manifest deleted file mode 100644 index 0ee6674c..00000000 --- a/src/TestApp/app.manifest +++ /dev/null @@ -1,26 +0,0 @@ - - - - Test Application For System.Data.SqlLocalDb.dll. - - - - - - - - - - - - - - - - - - - - - - diff --git a/stylecop.json b/stylecop.json index 1e7edef7..61421b17 100644 --- a/stylecop.json +++ b/stylecop.json @@ -3,17 +3,19 @@ "settings": { "documentationRules": { "companyName": "https://github.com/martincostello/sqllocaldb", - "copyrightText": "{ownerName} (c) 2012-{year}", + "copyrightText": "Copyright (c) {ownerName}, {year}. All rights reserved.\nLicensed under the {licenseName} license. See the {licenseFile} file in the project root for full license information.", "documentExposedElements": true, "documentInterfaces": true, "documentInternalElements": true, "documentPrivateElements": true, "documentPrivateFields": true, "fileNamingConvention": "metadata", - "xmlHeader": true, + "xmlHeader": false, "variables": { + "licenseFile": "LICENSE", + "licenseName": "Apache 2.0", "ownerName": "Martin Costello", - "year": "2015" + "year": "2012-2018" } }, "layoutRules": { diff --git a/tests/SqlLocalDb.Tests/EventIdsTests.cs b/tests/SqlLocalDb.Tests/EventIdsTests.cs new file mode 100644 index 00000000..305e4803 --- /dev/null +++ b/tests/SqlLocalDb.Tests/EventIdsTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Reflection; +using Microsoft.Extensions.Logging; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class EventIdsTests + { + [Theory] + [InlineData("NativeApiLoaded", 1)] + [InlineData("NativeApiLoadFailed", 2)] + [InlineData("NativeApiNotLoaded", 3)] + [InlineData("NativeApiVersionOverriddenByUser", 4)] + [InlineData("NativeApiVersionOverrideNotFound", 5)] + [InlineData("NoNativeApiFound", 6)] + [InlineData("NativeApiPathNotFound", 7)] + [InlineData("NativeFunctionNotFound", 8)] + [InlineData("NotInstalled", 9)] + [InlineData("CreatingInstance", 10)] + [InlineData("CreatingInstanceFailed", 11)] + [InlineData("CreatedInstance", 12)] + [InlineData("DeletingInstance", 13)] + [InlineData("DeletingInstanceFailed", 14)] + [InlineData("DeletingInstanceFailedAsCannotBeNotFound", 15)] + [InlineData("DeletingInstanceFailedAsInUse", 16)] + [InlineData("DeletedInstance", 17)] + [InlineData("DeletingInstanceFiles", 18)] + [InlineData("DeletingInstanceFilesFailed", 19)] + [InlineData("DeletedInstanceFiles", 20)] + [InlineData("GettingInstanceInfo", 21)] + [InlineData("GettingInstanceInfoFailed", 22)] + [InlineData("GotInstanceInfo", 23)] + [InlineData("GettingInstanceNames", 24)] + [InlineData("GettingInstanceNamesFailed", 25)] + [InlineData("GotInstanceNames", 26)] + [InlineData("GettingVersionInfo", 27)] + [InlineData("GettingVersionInfoFailed", 28)] + [InlineData("GotVersionInfo", 29)] + [InlineData("GettingVersions", 30)] + [InlineData("GettingVersionsFailed", 31)] + [InlineData("GotVersions", 32)] + [InlineData("InvalidLanguageId", 33)] + [InlineData("InvalidRegistryKey", 34)] + [InlineData("RegistryKeyNotFound", 35)] + [InlineData("StartingInstance", 36)] + [InlineData("StartingInstanceFailed", 37)] + [InlineData("StartedInstance", 38)] + [InlineData("StoppingInstance", 39)] + [InlineData("StoppingInstanceFailed", 40)] + [InlineData("StoppedInstance", 41)] + [InlineData("StartingTracing", 42)] + [InlineData("StartingTracingFailed", 43)] + [InlineData("StartedTracing", 44)] + [InlineData("StoppedTracing", 45)] + [InlineData("StoppingTracingFailed", 46)] + [InlineData("StoppingTracing", 47)] + [InlineData("StopTemporaryInstanceFailed", 48)] + [InlineData("SharingInstance", 49)] + [InlineData("SharingInstanceFailed", 50)] + [InlineData("SharedInstance", 51)] + [InlineData("UnsharingInstance", 52)] + [InlineData("UnsharingInstanceFailed", 53)] + [InlineData("UnsharedInstance", 54)] + public static void EventId_Name_And_Value_Is_Correct(string name, int expected) + { + // Arrange + FieldInfo field = typeof(EventIds).GetField(name, BindingFlags.NonPublic | BindingFlags.Static); + + // Act + EventId eventId = (EventId)field.GetValue(null); + + // Asset + eventId.ShouldNotBe(default); + eventId.Name.ShouldBe(name); + eventId.Id.ShouldBe(expected); + } + } +} diff --git a/tests/SqlLocalDb.Tests/ILoggerFactoryExtensions.cs b/tests/SqlLocalDb.Tests/ILoggerFactoryExtensions.cs new file mode 100644 index 00000000..b1fa3bd5 --- /dev/null +++ b/tests/SqlLocalDb.Tests/ILoggerFactoryExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + internal static class ILoggerFactoryExtensions + { + /// + /// Adds an xunit logger to the factory. + /// + /// The to use. + /// The to use. + /// + /// The instance of specified by . + /// + /// + /// or is . + /// + internal static ILoggerFactory AddXunit(this ILoggerFactory factory, ITestOutputHelper outputHelper) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + if (outputHelper == null) + { + throw new ArgumentNullException(nameof(outputHelper)); + } + + var provider = new XunitLoggerProvider(outputHelper); + + factory.AddProvider(provider); + + return factory; + } + } +} diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs new file mode 100644 index 00000000..c04042d5 --- /dev/null +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -0,0 +1,491 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Moq; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + public class ISqlLocalDbApiExtensionsTests + { + private readonly ILoggerFactory _loggerFactory; + + public ISqlLocalDbApiExtensionsTests(ITestOutputHelper outputHelper) + { + _loggerFactory = outputHelper.AsLoggerFactory(); + } + + [Fact] + public void CreateTemporaryInstance_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + + // Act and Assert + Assert.Throws("api", () => api.CreateTemporaryInstance()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CreateTemporaryInstance_Creates_Starts_And_Deletes_An_Instance(bool deleteFiles) + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + ISqlLocalDbInstanceInfo info; + string name; + + // Act + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance(deleteFiles)) + { + // Assert + target.ShouldNotBeNull(); + target.Name.ShouldNotBeNull(); + target.Name.ShouldNotBeEmpty(); + + Guid.TryParse(target.Name, out Guid nameAsGuid).ShouldBeTrue(); + nameAsGuid.ShouldNotBe(Guid.Empty); + + // Act + info = target.GetInstanceInfo(); + + // Assert + info.ShouldNotBeNull(); + info.Exists.ShouldBeTrue(); + info.IsRunning.ShouldBeTrue(); + + name = target.Name; + } + + // Act + info = api.GetInstanceInfo(name); + + // Assert + info.ShouldNotBeNull(); + info.Exists.ShouldBeFalse(); + } + } + + [Fact] + public void TemporaryInstance_Throws_If_Used_After_Disposal() + { + // Arrange + var api = Mock.Of(); + + TemporarySqlLocalDbInstance instance = api.CreateTemporaryInstance(); + + // Act + instance.Dispose(); + + // Assert + Assert.Throws(() => instance.Name); + Assert.Throws(() => instance.GetInstanceInfo()); + } + + [Fact] + public void TemporaryInstance_Disposes_Cleanly_If_Not_Used() + { + // Arrange + var api = Mock.Of(); + + // Act and Assert + using (TemporarySqlLocalDbInstance instance = api.CreateTemporaryInstance()) + { + instance.Dispose(); + } + } + + [Fact] + public void TemporaryInstance_Deletes_Instance_If_Start_Fails() + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.LatestVersion) + .Returns("v99.9"); + + mock.Setup((p) => p.CreateInstance(It.IsAny(), "v99.9")) + .Verifiable(); + + mock.Setup((p) => p.StartInstance(It.IsAny())) + .Throws(); + + mock.Setup((p) => p.DeleteInstance(It.IsAny())) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance()) + { + // Act and Assert + Assert.Throws(() => target.GetInstanceInfo()); + } + + mock.Verify(); + } + + [Theory] + [InlineData(SqlLocalDbErrors.InternalError)] + [InlineData(SqlLocalDbErrors.UnknownInstance)] + public void TemporaryInstance_Ignores_Exception_If_Stop_Fails(int errorCode) + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.LatestVersion) + .Returns("v99.9"); + + mock.Setup((p) => p.CreateInstance(It.IsAny(), "v99.9")) + .Verifiable(); + + mock.Setup((p) => p.StartInstance(It.IsAny())) + .Verifiable(); + + mock.Setup((p) => p.StopInstance(It.IsAny(), null)) + .Throws(new SqlLocalDbException("Error", errorCode)) + .Verifiable(); + + mock.Setup((p) => p.DeleteInstance(It.IsAny())) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + // Act + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance()) + { + target.GetInstanceInfo(); + } + + // Assert + mock.Verify(); + } + + [Theory] + [InlineData(SqlLocalDbErrors.InstanceBusy)] + [InlineData(SqlLocalDbErrors.InternalError)] + public void TemporaryInstance_Ignores_Exception_If_Delete_Fails(int errorCode) + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.LatestVersion) + .Returns("v99.9"); + + mock.Setup((p) => p.CreateInstance(It.IsAny(), "v99.9")) + .Verifiable(); + + mock.Setup((p) => p.StartInstance(It.IsAny())) + .Verifiable(); + + mock.Setup((p) => p.StopInstance(It.IsAny(), null)) + .Verifiable(); + + mock.Setup((p) => p.DeleteInstance(It.IsAny())) + .Throws(new SqlLocalDbException("Error", errorCode)) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + // Act + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance()) + { + target.GetInstanceInfo(); + } + + // Assert + mock.Verify(); + } + + [Fact] + public void GetDefaultInstance_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + + // Act and Assert + Assert.Throws("api", () => api.GetDefaultInstance()); + } + + [Fact] + public void GetDefaultInstance_Returns_The_Default_Instance() + { + // Arrange + using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + { + // Act + ISqlLocalDbInstanceInfo actual = api.GetDefaultInstance(); + + // Assert + actual.ShouldNotBeNull(); + actual.Exists.ShouldBeTrue(); + actual.IsAutomatic.ShouldBeTrue(); + actual.Name.ShouldBe(api.DefaultInstanceName); + } + } + + [Fact] + public void GetInstances_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + + // Act and Assert + Assert.Throws("api", () => api.GetInstances()); + } + + [Fact] + public void GetInstances_Returns_All_The_Named_Instances() + { + // Arrange + using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + { + // Act + IReadOnlyList actual = api.GetInstances(); + + // Assert + actual.ShouldNotBeNull(); + actual.Count.ShouldBeGreaterThanOrEqualTo(1); + actual.ShouldBeUnique(); + actual.ShouldAllBe((p) => p != null); + actual.ShouldContain((p) => p.Name == api.DefaultInstanceName); + actual.ShouldContain((p) => p.IsAutomatic); + } + } + + [Fact] + public void GetInstances_Returns_Empty_If_No_Instances() + { + // Arrange + var api = Mock.Of(); + + // Act + IReadOnlyList actual = api.GetInstances(); + + // Assert + actual.ShouldNotBeNull(); + actual.ShouldBeEmpty(); + } + + [Fact] + public void GetVersions_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + + // Act and Assert + Assert.Throws("api", () => api.GetVersions()); + } + + [Fact] + public void GetVersions_Returns_All_The_Installed_Versions() + { + // Arrange + using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + { + // Act + IReadOnlyList actual = api.GetVersions(); + + // Assert + actual.ShouldNotBeNull(); + actual.Count.ShouldBeGreaterThanOrEqualTo(1); + actual.ShouldBeUnique(); + actual.ShouldAllBe((p) => p != null); + actual.ShouldContain((p) => p.Exists); + } + } + + [Fact] + public void GetVersions_Returns_Empty_If_No_Versions() + { + // Arrange + var api = Mock.Of(); + + // Act + IReadOnlyList actual = api.GetVersions(); + + // Assert + actual.ShouldNotBeNull(); + actual.ShouldBeEmpty(); + } + + [Fact] + public void GetOrCreateInstance_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + string instanceName = "SomeName"; + + // Act and Assert + Assert.Throws("api", () => api.GetOrCreateInstance(instanceName)); + } + + [Fact] + public void GetOrCreateInstance_Throws_If_InstanceName_Is_Null() + { + // Arrange + ISqlLocalDbApi api = Mock.Of(); + string instanceName = null; + + // Act and Assert + Assert.Throws("instanceName", () => api.GetOrCreateInstance(instanceName)); + } + + [Fact] + public void GetOrCreateInstance_Returns_The_Default_Instance_If_It_Exists_With_The_Default_Name() + { + // Arrange + string instanceName = "Default"; + + var mock = new Mock(); + + mock.Setup((p) => p.DefaultInstanceName) + .Returns(instanceName); + + mock.Setup((p) => p.GetInstanceInfo(instanceName)) + .Returns(new SqlLocalDbInstanceInfo() { Exists = true }); + + ISqlLocalDbApi api = mock.Object; + + // Act + ISqlLocalDbInstanceInfo actual = api.GetOrCreateInstance(instanceName); + + // Assert + actual.ShouldNotBeNull(); + actual.Exists.ShouldBeTrue(); + } + + [Theory] + [InlineData("v11.0")] + [InlineData("MSSQLLocalDB")] + public void GetOrCreateInstance_Returns_The_Default_Instance_If_It_Exists(string instanceName) + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.DefaultInstanceName) + .Returns("Blah"); + + mock.Setup((p) => p.GetInstanceInfo(instanceName)) + .Returns(new SqlLocalDbInstanceInfo() { Exists = true }); + + ISqlLocalDbApi api = mock.Object; + + // Act + ISqlLocalDbInstanceInfo actual = api.GetOrCreateInstance(instanceName); + + // Assert + actual.ShouldNotBeNull(); + actual.Exists.ShouldBeTrue(); + } + + [Fact] + public void GetOrCreateInstance_Returns_An_Instance_If_It_Exists() + { + // Arrange + string instanceName = "MyInstance"; + + var mock = new Mock(); + + mock.Setup((p) => p.DefaultInstanceName) + .Returns("Blah"); + + mock.Setup((p) => p.GetInstanceNames()) + .Returns(new[] { "a", "b", instanceName }); + + mock.Setup((p) => p.GetInstanceInfo(instanceName)) + .Returns(new SqlLocalDbInstanceInfo()); + + ISqlLocalDbApi api = mock.Object; + + // Act + ISqlLocalDbInstanceInfo actual = api.GetOrCreateInstance(instanceName); + + // Assert + actual.ShouldNotBeNull(); + } + + [Fact] + public void GetOrCreateInstance_Returns_An_Instance_If_It_Does_Not_Exist() + { + // Arrange + string instanceName = "MyInstance"; + + var mock = new Mock(); + + mock.Setup((p) => p.DefaultInstanceName) + .Returns("Blah"); + + mock.Setup((p) => p.LatestVersion) + .Returns("v99.0"); + + mock.Setup((p) => p.CreateInstance(instanceName, "v99.0")) + .Returns(new SqlLocalDbInstanceInfo()); + + ISqlLocalDbApi api = mock.Object; + + // Act + ISqlLocalDbInstanceInfo actual = api.GetOrCreateInstance(instanceName); + + // Assert + actual.ShouldNotBeNull(); + } + + [Fact] + public void ShareInstance_Throws_If_Api_Is_Null() + { + // Arrange + ISqlLocalDbApi api = null; + string instanceName = "SomeName"; + string sharedInstanceName = "SomeSharedName"; + + // Act and Assert + Assert.Throws("api", () => api.ShareInstance(instanceName, sharedInstanceName)); + } + + [Fact] + public void ShareInstance_Uses_SID_For_Current_User() + { + // Arrange + string instanceName = "SomeName"; + string sharedInstanceName = "SomeSharedName"; + + var mock = new Mock(); + + mock.Setup((p) => p.ShareInstance(It.IsNotNull(), instanceName, sharedInstanceName)) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + // Act + api.ShareInstance(instanceName, sharedInstanceName); + + // Assert + mock.Verify(); + } + + [RunAsAdminFact] + public void ShareInstance_Shares_Instance_For_Current_User() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance(deleteFiles: true)) + { + target.GetInstanceInfo().IsShared.ShouldBeFalse(); + + // Act + api.ShareInstance(target.Name, Guid.NewGuid().ToString()); + + // Assert + target.GetInstanceInfo().IsShared.ShouldBeTrue(); + } + } + } + } +} diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs new file mode 100644 index 00000000..51a40bf2 --- /dev/null +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Data.SqlClient; +using Moq; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class ISqlLocalDbInstanceInfoExtensionsTests + { + [Fact] + public static void CreateConnection_Throws_If_Instance_Is_Null() + { + // Arrange + ISqlLocalDbInstanceInfo instance = null; + + // Act and Assert + Assert.Throws("instance", () => instance.CreateConnection()); + } + + [Fact] + public static void CreateConnectionStringBuilder_Throws_If_Instance_Is_Null() + { + // Arrange + ISqlLocalDbInstanceInfo instance = null; + + // Act and Assert + Assert.Throws("instance", () => instance.CreateConnectionStringBuilder()); + } + + [Fact] + public static void CreateConnectionStringBuilder_Throws_If_The_Instance_Is_Not_Running() + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.IsRunning).Returns(false); + mock.Setup((p) => p.Name).Returns("MyInstance"); + mock.Setup((p) => p.NamedPipe).Returns("MyNamedPipe"); + + ISqlLocalDbInstanceInfo instance = mock.Object; + + // Act and Assert + var exception = Assert.Throws(() => instance.CreateConnectionStringBuilder()); + exception.Message.ShouldBe("The SQL LocalDB instance 'MyInstance' is not running."); + } + + [Fact] + public static void CreateConnectionStringBuilder_Initializes_Connection_String_Builder() + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.IsRunning).Returns(true); + mock.Setup((p) => p.NamedPipe).Returns("MyNamedPipe"); + + ISqlLocalDbInstanceInfo instance = mock.Object; + + // Act + SqlConnectionStringBuilder actual = instance.CreateConnectionStringBuilder(); + + // Assert + actual.ShouldNotBeNull(); + actual.DataSource.ShouldBe("MyNamedPipe"); + } + } +} diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs new file mode 100644 index 00000000..49ed542a --- /dev/null +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Data; +using System.Data.SqlClient; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + public class ISqlLocalDbInstanceManagerExtensionsTests + { + private readonly ILoggerFactory _loggerFactory; + + public ISqlLocalDbInstanceManagerExtensionsTests(ITestOutputHelper outputHelper) + { + _loggerFactory = outputHelper.AsLoggerFactory(); + } + + [Fact] + public void CreateConnection_Throws_If_Manager_Is_Null() + { + // Arrange + ISqlLocalDbInstanceManager manager = null; + + // Act and Assert + Assert.Throws("manager", () => manager.CreateConnection()); + } + + [Fact] + public async Task CreateConnection_Creates_A_Sql_Connection() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + using (TemporarySqlLocalDbInstance temporary = api.CreateTemporaryInstance(deleteFiles: true)) + { + ISqlLocalDbInstanceManager manager = temporary.Manage(); + + manager.ShouldNotBeNull(); + manager.Name.ShouldBe(temporary.Name); + + // Act + using (SqlConnection actual = manager.CreateConnection()) + { + // Assert + actual.ShouldNotBeNull(); + actual.ConnectionString.ShouldNotBeNull(); + actual.State.ShouldBe(ConnectionState.Closed); + + await actual.OpenAsync(); + actual.Close(); + } + } + } + } + + [Fact] + public void Restart_Throws_If_Manager_Is_Null() + { + // Arrange + ISqlLocalDbInstanceManager manager = null; + + // Act and Assert + Assert.Throws("manager", () => manager.Restart()); + } + + [Fact] + public void Restart_Stops_And_Starts_Instance() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + using (TemporarySqlLocalDbInstance temporary = api.CreateTemporaryInstance(deleteFiles: true)) + { + ISqlLocalDbInstanceManager manager = temporary.Manage(); + + // Act + manager.Restart(); + + // Assert + temporary.GetInstanceInfo().IsRunning.ShouldBeTrue(); + } + } + } + } +} diff --git a/tests/SqlLocalDb.Tests/ITestOutputHelperExtensions.cs b/tests/SqlLocalDb.Tests/ITestOutputHelperExtensions.cs new file mode 100644 index 00000000..5c5872ad --- /dev/null +++ b/tests/SqlLocalDb.Tests/ITestOutputHelperExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + internal static class ITestOutputHelperExtensions + { + /// + /// Returns an that logs to the output helper. + /// + /// The to create the logger factory from. + /// + /// An that writes messages to the test output helper. + /// + public static ILoggerFactory AsLoggerFactory(this ITestOutputHelper outputHelper) + { + return new LoggerFactory().AddXunit(outputHelper); + } + } +} diff --git a/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj b/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj new file mode 100644 index 00000000..0633d79f --- /dev/null +++ b/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj @@ -0,0 +1,27 @@ + + + Tests for MartinCostello.SqlLocalDb. + $(NoWarn);CA1707;CA2007;CA2234 + true + MartinCostello.SqlLocalDb + $(Description) + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + diff --git a/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs b/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs new file mode 100644 index 00000000..3f91c13c --- /dev/null +++ b/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs @@ -0,0 +1,58 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Attribute that is applied to a method to indicate that it is a fact that should be run by the test runner + /// if the user account running the test has administrative privileges. This class cannot be inherited. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class RunAsAdminFactAttribute : FactAttribute + { + public RunAsAdminFactAttribute() + : base() + { + Skip = IsCurrentUserAdmin(out string name) ? string.Empty : $"The current user '{name}' does not have administrative privileges."; + } + + /// + /// Returns whether the current user has Administrative privileges. + /// + /// + /// if the current user has Administrative + /// privileges; otherwise . + /// + internal static bool IsCurrentUserAdmin() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; + } + + return IsCurrentUserAdmin(out string name); + } + + /// + /// Returns whether the current user has Administrative privileges. + /// + /// When the method returns, contains the name of the current user. + /// + /// if the current user has Administrative + /// privileges; otherwise . + /// + private static bool IsCurrentUserAdmin(out string name) + { + using (var identity = WindowsIdentity.GetCurrent()) + { + name = identity.Name; + return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); + } + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs new file mode 100644 index 00000000..7b2a3680 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Globalization; +using Microsoft.Extensions.Logging; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + public class SqlLocalDbApiTests + { + private readonly ILoggerFactory _loggerFactory; + + public SqlLocalDbApiTests(ITestOutputHelper outputHelper) + { + _loggerFactory = outputHelper.AsLoggerFactory(); + } + + [Fact] + public void Constructor_Validates_Parameters() + { + // Arrange + var options = new SqlLocalDbOptions(); + + Assert.Throws("options", () => new SqlLocalDbApi(null, _loggerFactory)); + Assert.Throws("loggerFactory", () => new SqlLocalDbApi(null)); + Assert.Throws("loggerFactory", () => new SqlLocalDbApi(options, null)); + } + + [Fact] + public void Constructor_Initializes_Instance() + { + // Arrange + var options = new SqlLocalDbOptions() + { + AutomaticallyDeleteInstanceFiles = true, + Language = CultureInfo.GetCultureInfo("de-DE"), + NativeApiOverrideVersion = "11.0", + StopOptions = StopInstanceOptions.NoWait, + StopTimeout = TimeSpan.FromSeconds(30), + }; + + using (var actual = new SqlLocalDbApi(options, _loggerFactory)) + { + actual.AutomaticallyDeleteInstanceFiles.ShouldBe(options.AutomaticallyDeleteInstanceFiles); + actual.DefaultInstanceName.ShouldNotBeNull(); + actual.LanguageId.ShouldBeGreaterThan(0); + actual.LatestVersion.ShouldNotBeNull(); + actual.LoggerFactory.ShouldNotBeNull(); + actual.StopOptions.ShouldBe(options.StopOptions); + actual.StopTimeout.ShouldBe(options.StopTimeout); + actual.Versions.ShouldNotBeNull(); + actual.Versions.ShouldNotBeEmpty(); + actual.Versions.ShouldBeUnique(); + } + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbExceptionTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbExceptionTests.cs new file mode 100644 index 00000000..663e4901 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbExceptionTests.cs @@ -0,0 +1,153 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class SqlLocalDbExceptionTests + { + [Fact] + public static void SqlLocalDbException_Constructor_Default_Sets_Properties() + { + // Act + var target = new SqlLocalDbException(); + + // Assert + target.ErrorCode.ShouldBe(-2147467259); + target.InstanceName.ShouldBeNull(); + target.Message.ShouldBe("An error occurred with a SQL Server LocalDB instance."); + } + + [Fact] + public static void SqlLocalDbException_Constructor_With_Message_Sets_Properties() + { + // Arrange + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message); + + // Assert + target.ErrorCode.ShouldBe(-2147467259); + target.InstanceName.ShouldBeNull(); + target.Message.ShouldBe(message); + } + + [Fact] + public static void SqlLocalDbException_Constructor_With_Message_And_InnerException_Sets_Properties() + { + // Arrange + var innerException = new InvalidOperationException(); + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message, innerException); + + // Assert + target.ErrorCode.ShouldBe(-2147467259); + target.InnerException.ShouldBeSameAs(innerException); + target.InstanceName.ShouldBeNull(); + target.Message.ShouldBe(message); + } + + [Fact] + public static void SqlLocalDbException_Constructor_With_Message_And_ErrorCode_Sets_Properties() + { + // Arrange + const int ErrorCode = 337519; + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message, ErrorCode); + + // Assert + target.ErrorCode.ShouldBe(ErrorCode); + target.InstanceName.ShouldBeNull(); + target.Message.ShouldBe(message); + } + + [Fact] + public static void SqlLocalDbException_Constructor_With_Message_ErrorCode_And_InstanceName_Sets_Properties() + { + // Arrange + const int ErrorCode = 337519; + string instanceName = Guid.NewGuid().ToString(); + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message, ErrorCode, instanceName); + + // Assert + target.ErrorCode.ShouldBe(ErrorCode); + target.InstanceName.ShouldBe(instanceName); + target.Message.ShouldBe(message); + } + + [Fact] + public static void SqlLocalDbException_Constructor_With_Message_ErrorCode_InstanceName_And_InnerException_Sets_Properties() + { + // Arrange + var innerException = new InvalidOperationException(); + const int ErrorCode = 337519; + string instanceName = Guid.NewGuid().ToString(); + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message, ErrorCode, instanceName, innerException); + + // Assert + target.ErrorCode.ShouldBe(ErrorCode); + target.InnerException.ShouldBeSameAs(innerException); + target.InstanceName.ShouldBe(instanceName); + target.Message.ShouldBe(message); + } + + [Fact] + public static void SqlLocalDbException_GetObjectData_Throws_If_Info_Is_Null() + { + // Arrange + var target = new SqlLocalDbException(); + + SerializationInfo info = null; + var context = new StreamingContext(); + + // Act and Assert + Assert.Throws("info", () => target.GetObjectData(info, context)); + } + + [Fact] + public static void SqlLocalDbException_Constructor_For_Serialization_Can_Be_Serialized() + { + // Arrange + InvalidOperationException innerException = new InvalidOperationException(); + const int ErrorCode = 337519; + string instanceName = Guid.NewGuid().ToString(); + string message = Guid.NewGuid().ToString(); + + // Act + var target = new SqlLocalDbException(message, ErrorCode, instanceName, innerException); + var formatter = new BinaryFormatter(); + + SqlLocalDbException deserialized; + + using (var stream = new MemoryStream()) + { + formatter.Serialize(stream, target); + stream.Seek(0L, SeekOrigin.Begin); + deserialized = formatter.Deserialize(stream) as SqlLocalDbException; + } + + // Assert + deserialized.ShouldNotBeNull(); + deserialized.ErrorCode.ShouldBe(target.ErrorCode); + deserialized.InstanceName.ShouldBe(target.InstanceName); + deserialized.Message.ShouldBe(target.Message); + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs new file mode 100644 index 00000000..786e1be7 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class SqlLocalDbInstanceInfoTests + { + [Fact] + public static void Update_Copies_State_From_Other_Instance() + { + // Arrange + var other = new SqlLocalDbInstanceInfo() + { + ConfigurationCorrupt = true, + Exists = true, + IsAutomatic = true, + IsRunning = true, + IsShared = true, + LastStartTimeUtc = DateTime.UtcNow, + LocalDbVersion = new Version(2, 1), + Name = "OtherName", + NamedPipe = "OtherPipe", + OwnerSid = "Sidney Poitier", + SharedName = "OtherSharedName", + }; + + var actual = new SqlLocalDbInstanceInfo() + { + ConfigurationCorrupt = false, + Exists = false, + IsAutomatic = false, + IsRunning = false, + IsShared = false, + LastStartTimeUtc = DateTime.MinValue, + LocalDbVersion = new Version(2, 0), + Name = "Name", + NamedPipe = "OtherPipe", + OwnerSid = "Sid James", + SharedName = "SharedName", + }; + + // Act + actual.Update(other); + + // Assert + actual.ConfigurationCorrupt.ShouldBe(other.ConfigurationCorrupt); + actual.Exists.ShouldBe(other.Exists); + actual.IsAutomatic.ShouldBe(other.IsAutomatic); + actual.IsRunning.ShouldBe(other.IsRunning); + actual.IsShared.ShouldBe(other.IsShared); + actual.LastStartTimeUtc.ShouldBe(other.LastStartTimeUtc); + actual.LocalDbVersion.ShouldBe(other.LocalDbVersion); + actual.Name.ShouldBe(other.Name); + actual.NamedPipe.ShouldBe(other.NamedPipe); + actual.OwnerSid.ShouldBe(other.OwnerSid); + actual.SharedName.ShouldBe(other.SharedName); + } + + [Fact] + public static void Update_Does_Not_Copy_State_If_Other_Is_Null() + { + // Arrange + ISqlLocalDbInstanceInfo other = null; + + var actual = new SqlLocalDbInstanceInfo() + { + ConfigurationCorrupt = true, + Exists = true, + IsAutomatic = true, + IsRunning = true, + IsShared = true, + LastStartTimeUtc = DateTime.UtcNow, + LocalDbVersion = new Version(2, 1), + Name = "Name", + NamedPipe = "NamedPipe", + OwnerSid = "OwnerSid", + SharedName = "SharedName", + }; + + // Act (no Assert) + actual.Update(other); + } + + [Fact] + public static void Update_Does_Not_Copy_State_If_Other_Is_Self() + { + // Arrange + var actual = new SqlLocalDbInstanceInfo() + { + ConfigurationCorrupt = true, + Exists = true, + IsAutomatic = true, + IsRunning = true, + IsShared = true, + LastStartTimeUtc = DateTime.UtcNow, + LocalDbVersion = new Version(2, 1), + Name = "Name", + NamedPipe = "NamedPipe", + OwnerSid = "OwnerSid", + SharedName = "SharedName", + }; + + // Act (no Assert) + actual.Update(actual); + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs new file mode 100644 index 00000000..133235a6 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs @@ -0,0 +1,295 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Logging; +using Moq; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + public class SqlLocalDbInstanceManagerTests + { + private readonly ILoggerFactory _loggerFactory; + + public SqlLocalDbInstanceManagerTests(ITestOutputHelper outputHelper) + { + _loggerFactory = outputHelper.AsLoggerFactory(); + } + + [Fact] + public void Constructor_Validates_Arguments() + { + // Act + ISqlLocalDbInstanceInfo instance = CreateInstance(); + var api = Mock.Of(); + + // Act and Assert + Assert.Throws("instance", () => new SqlLocalDbInstanceManager(null, api)); + Assert.Throws("api", () => new SqlLocalDbInstanceManager(instance, null)); + } + + [Fact] + public void Constructor_Initializes_Properties() + { + // Act + ISqlLocalDbInstanceInfo instance = CreateInstance(); + var api = Mock.Of(); + + // Act + var actual = new SqlLocalDbInstanceManager(instance, api); + + // Assert + actual.Name.ShouldBe("Name"); + actual.NamedPipe.ShouldBe("NamedPipe"); + } + + [Fact] + public void Share_Throws_If_SharedName_Is_Null() + { + // Act + ISqlLocalDbInstanceInfo instance = CreateInstance(); + var api = Mock.Of(); + var target = new SqlLocalDbInstanceManager(instance, api); + + Assert.Throws("sharedName", () => target.Share(null)); + } + + [Fact] + public void Share_Throws_If_SqlLocalDbEception_Is_Thrown() + { + // Act + var innerException = new SqlLocalDbException( + "It broke", + 123, + "Name"); + + var mock = new Mock(); + + mock.Setup((p) => p.ShareInstance(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(innerException); + + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + var exception = Assert.Throws(() => target.Share("SharedName")); + + exception.ErrorCode.ShouldBe(123); + exception.InstanceName.ShouldBe("Name"); + exception.Message.ShouldBe("Failed to share SQL LocalDB instance 'Name'."); + exception.InnerException.ShouldBeSameAs(innerException); + } + + [Fact] + public void Share_Shares_Instance() + { + // Act + string sharedName = Guid.NewGuid().ToString(); + + var mock = new Mock(); + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + // Act + target.Share(sharedName); + + // Assert + mock.Verify((p) => p.ShareInstance(It.IsNotNull(), "Name", sharedName), Times.Once()); + } + + [Fact] + public void Start_Starts_Instance() + { + // Act + var mock = new Mock(); + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + // Act + target.Start(); + + // Assert + mock.Verify((p) => p.StartInstance("Name"), Times.Once()); + } + + [Fact] + public void Start_Throws_If_SqlLocalDbEception_Is_Thrown() + { + // Act + var innerException = new SqlLocalDbException( + "It broke", + 123, + "Name"); + + var mock = new Mock(); + + mock.Setup((p) => p.StartInstance(It.IsAny())) + .Throws(innerException); + + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + var exception = Assert.Throws(() => target.Start()); + + exception.ErrorCode.ShouldBe(123); + exception.InstanceName.ShouldBe("Name"); + exception.Message.ShouldBe("Failed to start SQL LocalDB instance 'Name'."); + exception.InnerException.ShouldBeSameAs(innerException); + } + + [Fact] + public void Stop_Stops_Instance() + { + // Act + var mock = new Mock(); + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + // Act + target.Stop(); + + // Assert + mock.Verify((p) => p.StopInstance("Name", null), Times.Once()); + } + + [Fact] + public void Stop_Throws_If_SqlLocalDbEception_Is_Thrown() + { + // Act + var innerException = new SqlLocalDbException( + "It broke", + 123, + "Name"); + + var mock = new Mock(); + + mock.Setup((p) => p.StopInstance(It.IsAny(), It.IsAny())) + .Throws(innerException); + + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + var exception = Assert.Throws(() => target.Stop()); + + exception.ErrorCode.ShouldBe(123); + exception.InstanceName.ShouldBe("Name"); + exception.Message.ShouldBe("Failed to stop SQL LocalDB instance 'Name'."); + exception.InnerException.ShouldBeSameAs(innerException); + } + + [Fact] + public void Unshare_Unshares_Instance() + { + // Act + var mock = new Mock(); + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + // Act + target.Unshare(); + + // Assert + mock.Verify((p) => p.UnshareInstance("Name"), Times.Once()); + } + + [Fact] + public void Unshare_Throws_If_SqlLocalDbEception_Is_Thrown() + { + // Act + var innerException = new SqlLocalDbException( + "It broke", + 123, + "Name"); + + var mock = new Mock(); + + mock.Setup((p) => p.UnshareInstance(It.IsAny())) + .Throws(innerException); + + ISqlLocalDbInstanceInfo instance = CreateInstance(); + ISqlLocalDbApi api = mock.Object; + + var target = new SqlLocalDbInstanceManager(instance, api); + + var exception = Assert.Throws(() => target.Unshare()); + + exception.ErrorCode.ShouldBe(123); + exception.InstanceName.ShouldBe("Name"); + exception.Message.ShouldBe("Failed to stop sharing SQL LocalDB instance 'Name'."); + exception.InnerException.ShouldBeSameAs(innerException); + } + + [RunAsAdminFact] + public void Manager_Shares_And_Unshares_Instance() + { + // Act + string instanceName = Guid.NewGuid().ToString(); + string sharedName = Guid.NewGuid().ToString(); + + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + api.CreateInstance(instanceName); + + try + { + api.StartInstance(instanceName); + + try + { + var instance = api.GetInstanceInfo(instanceName); + + var manager = new SqlLocalDbInstanceManager(instance, api); + + // Act + manager.Share(sharedName); + + // Assert + instance.IsShared.ShouldBeTrue(); + + // Act + manager.Unshare(); + + // Assert + instance.IsShared.ShouldBeFalse(); + } + catch (Exception) + { + api.StopInstance(instanceName); + throw; + } + } + catch (Exception) + { + api.DeleteInstance(instanceName, deleteFiles: true); + throw; + } + } + } + + private static ISqlLocalDbInstanceInfo CreateInstance() + { + var mock = new Mock(); + + mock.Setup((p) => p.Name).Returns("Name"); + mock.Setup((p) => p.NamedPipe).Returns("NamedPipe"); + + return mock.Object; + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbOptionsTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbOptionsTests.cs new file mode 100644 index 00000000..7887172a --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbOptionsTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Globalization; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class SqlLocalDbOptionsTests + { + [Fact] + public static void SqlLocalDbOptions_Defaults_Are_Correct() + { + // Act + var actual = new SqlLocalDbOptions(); + + // Assert + actual.AutomaticallyDeleteInstanceFiles.ShouldBeFalse(); + actual.Language.ShouldBeNull(); + actual.NativeApiOverrideVersion.ShouldBe(string.Empty); + actual.StopOptions.ShouldBe(StopInstanceOptions.None); + actual.StopTimeout.ShouldBe(TimeSpan.FromMinutes(1)); + } + + [Fact] + public static void SqlLocalDbOptions_LanguageId_Returns_The_LCID() + { + // Act + var actual = new SqlLocalDbOptions() + { + Language = CultureInfo.GetCultureInfo("de-DE") + }; + + // Assert + actual.LanguageId.ShouldBeGreaterThan(0); + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs new file mode 100644 index 00000000..fd1c3d35 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class SqlLocalDbVersionInfoTests + { + [Fact] + public static void Update_Copies_State_From_Other_Instance() + { + // Arrange + var other = new SqlLocalDbVersionInfo() + { + Exists = true, + Name = "OtherName", + Version = new Version(2, 1), + }; + + var actual = new SqlLocalDbVersionInfo() + { + Exists = false, + Name = "Name", + Version = new Version(2, 0), + }; + + // Act + actual.Update(other); + + // Assert + actual.Exists.ShouldBe(other.Exists); + actual.Name.ShouldBe(other.Name); + actual.Version.ShouldBe(other.Version); + } + + [Fact] + public static void Update_Does_Not_Copy_State_If_Other_Is_Null() + { + // Arrange + ISqlLocalDbVersionInfo other = null; + + var actual = new SqlLocalDbVersionInfo() + { + Exists = false, + Name = "Name", + Version = new Version(2, 0), + }; + + // Act + actual.Update(other); + + // Assert + actual.Exists.ShouldBeFalse(); + actual.Name.ShouldBe("Name"); + actual.Version.ShouldBe(new Version(2, 0)); + } + + [Fact] + public static void Update_Does_Not_Copy_State_If_Other_Is_Self() + { + // Arrange + var actual = new SqlLocalDbVersionInfo() + { + Exists = false, + Name = "Name", + Version = new Version(2, 0), + }; + + // Act + actual.Update(actual); + + // Assert + actual.Exists.ShouldBeFalse(); + actual.Name.ShouldBe("Name"); + actual.Version.ShouldBe(new Version(2, 0)); + } + } +} diff --git a/tests/SqlLocalDb.Tests/XunitLogger.cs b/tests/SqlLocalDb.Tests/XunitLogger.cs new file mode 100644 index 00000000..381bb2ee --- /dev/null +++ b/tests/SqlLocalDb.Tests/XunitLogger.cs @@ -0,0 +1,36 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class representing an to use with xunit. This class cannot be inherited. + /// + internal sealed class XunitLogger : ILogger + { + private readonly string _categoryName; + private readonly ITestOutputHelper _outputHelper; + + internal XunitLogger(string categoryName, ITestOutputHelper outputHelper) + { + _categoryName = categoryName; + _outputHelper = outputHelper; + } + + /// + public IDisposable BeginScope(TState state) => null; + + /// + public bool IsEnabled(LogLevel logLevel) => true; + + /// + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _outputHelper.WriteLine($"[{DateTimeOffset.Now:u}] [{logLevel}:{_categoryName}:{eventId}] {formatter(state, exception)}"); + } + } +} diff --git a/tests/SqlLocalDb.Tests/XunitLoggerProvider.cs b/tests/SqlLocalDb.Tests/XunitLoggerProvider.cs new file mode 100644 index 00000000..d8ad6d3d --- /dev/null +++ b/tests/SqlLocalDb.Tests/XunitLoggerProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Logging; +using Xunit.Abstractions; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// A class representing an to use with xunit. This class cannot be inherited. + /// + internal sealed class XunitLoggerProvider : ILoggerProvider + { + private readonly ITestOutputHelper _outputHelper; + + internal XunitLoggerProvider(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + } + + /// + public ILogger CreateLogger(string categoryName) => new XunitLogger(categoryName, _outputHelper); + + /// + public void Dispose() + { + // Nothing to dispose of + } + } +} diff --git a/tests/SqlLocalDb.Tests/xunit.runner.json b/tests/SqlLocalDb.Tests/xunit.runner.json new file mode 100644 index 00000000..1d280220 --- /dev/null +++ b/tests/SqlLocalDb.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "methodDisplay": "method" +} diff --git a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedAndSpecified.config b/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedAndSpecified.config deleted file mode 100644 index ecc790af..00000000 --- a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedAndSpecified.config +++ /dev/null @@ -1,14 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedButNotSpecified.config b/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedButNotSpecified.config deleted file mode 100644 index fe805130..00000000 --- a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.DefinedButNotSpecified.config +++ /dev/null @@ -1,7 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.cs b/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.cs deleted file mode 100644 index 49717f55..00000000 --- a/tests/SqlLocalDb.UnitTests/Configuration/SqlLocalDbConfigurationSectionTests.cs +++ /dev/null @@ -1,180 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbConfigurationSectionTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Configuration; -using System.Globalization; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb.Configuration -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SqlLocalDbConfigurationSectionTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbConfigurationSectionTests() - { - } - - [TestMethod] - [Description("Tests that GetSection() returns an object with the correct default values if the configuration section is not defined.")] - public void SqlLocalDbConfigurationSection_GetSection_Returns_Section_With_Defaults_If_Not_Defined() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - SqlLocalDbConfigurationSection result = SqlLocalDbConfigurationSection.GetSection(); - - // Assert - Assert.IsNotNull(result, "GetSection() returned null."); - Assert.AreEqual(false, result.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfigurationSection.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(false, result.IsAutomaticallyDeleteInstanceFilesSpecified, "SqlLocalDbConfigurationSection.IsAutomaticallyDeleteInstanceFilesSpecified is incorrect."); - Assert.AreEqual(false, result.IsNativeApiOverrideVersionSpecified, "SqlLocalDbConfigurationSection.IsNativeApiOverrideVersionSpecified is incorrect."); - Assert.IsNull(result.Language, "SqlLocalDbConfigurationSection.Language is incorrect."); - Assert.IsNull(result.LoggerType, "SqlLocalDbConfigurationSection.LoggerType is incorrect."); - Assert.AreEqual(string.Empty, result.NativeApiOverrideVersion, "SqlLocalDbConfigurationSection.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.None, result.StopOptions, "SqlLocalDbConfigurationSection.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromMinutes(1), result.StopTimeout, "SqlLocalDbConfigurationSection.StopTimeout is incorrect."); - }, - configurationFile: "Empty.config"); - } - - [TestMethod] - [Description("Tests that GetSection() returns an object with the correct default values if the configuration section is defined but no attributes are set.")] - public void SqlLocalDbConfigurationSection_GetSection_Returns_Section_With_Defaults_If_Defined_But_No_Attributes_Specified() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - SqlLocalDbConfigurationSection result = SqlLocalDbConfigurationSection.GetSection(); - - // Assert - Assert.IsNotNull(result, "GetSection() returned null."); - Assert.AreEqual(false, result.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfigurationSection.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(false, result.IsAutomaticallyDeleteInstanceFilesSpecified, "SqlLocalDbConfigurationSection.IsAutomaticallyDeleteInstanceFilesSpecified is incorrect."); - Assert.AreEqual(false, result.IsNativeApiOverrideVersionSpecified, "SqlLocalDbConfigurationSection.IsNativeApiOverrideVersionSpecified is incorrect."); - Assert.IsNull(result.Language, "SqlLocalDbConfigurationSection.Language is incorrect."); - Assert.IsNull(result.LoggerType, "SqlLocalDbConfigurationSection.LoggerType is incorrect."); - Assert.AreEqual(string.Empty, result.NativeApiOverrideVersion, "SqlLocalDbConfigurationSection.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.None, result.StopOptions, "SqlLocalDbConfigurationSection.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromMinutes(1), result.StopTimeout, "SqlLocalDbConfigurationSection.StopTimeout is incorrect."); - }, - configurationFile: @"Configuration\SqlLocalDbConfigurationSectionTests.DefinedButNotSpecified.config"); - } - - [TestMethod] - [Description("Tests that GetSection() returns an object with the correct values if the configuration section is defined and all attributes are set.")] - public void SqlLocalDbConfigurationSection_GetSection_Returns_Section_With_User_Values_If_Defined_And_All_Attributes_Specified() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - SqlLocalDbConfigurationSection result = SqlLocalDbConfigurationSection.GetSection(); - - // Assert - Assert.IsNotNull(result, "GetSection() returned null."); - Assert.AreEqual(true, result.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfigurationSection.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(true, result.IsAutomaticallyDeleteInstanceFilesSpecified, "SqlLocalDbConfigurationSection.IsAutomaticallyDeleteInstanceFilesSpecified is incorrect."); - Assert.AreEqual(true, result.IsNativeApiOverrideVersionSpecified, "SqlLocalDbConfigurationSection.IsNativeApiOverrideVersionSpecified is incorrect."); - Assert.AreEqual(CultureInfo.GetCultureInfo("fr-FR"), result.Language, "SqlLocalDbConfigurationSection.Language is incorrect."); - Assert.AreEqual(typeof(EmptyLogger), result.LoggerType, "SqlLocalDbConfigurationSection.LoggerType is incorrect."); - Assert.AreEqual("11.0", result.NativeApiOverrideVersion, "SqlLocalDbConfigurationSection.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.KillProcess | StopInstanceOptions.NoWait, result.StopOptions, "SqlLocalDbConfigurationSection.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromSeconds(30), result.StopTimeout, "SqlLocalDbConfigurationSection.StopTimeout is incorrect."); - }, - configurationFile: @"Configuration\SqlLocalDbConfigurationSectionTests.DefinedAndSpecified.config"); - } - - [TestMethod] - [Description("Tests that the configuration properties of an instance of SqlLocalDbConfigurationSection can be set.")] - public void SqlLocalDbConfigurationSection_Can_Set_Properties() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - bool automaticallyDeleteInstanceFiles = true; - CultureInfo language = CultureInfo.GetCultureInfo("es-ES"); - Type loggerType = typeof(EmptyLogger); - string nativeApiOverrideVersion = "11.0"; - StopInstanceOptions stopOptions = StopInstanceOptions.KillProcess | StopInstanceOptions.NoWait; - TimeSpan stopTimeout = TimeSpan.FromSeconds(30); - - SqlLocalDbConfigurationSection target = new SqlLocalDbConfigurationSection(); - - // Act - target.AutomaticallyDeleteInstanceFiles = automaticallyDeleteInstanceFiles; - target.Language = language; - target.LoggerType = loggerType; - target.NativeApiOverrideVersion = nativeApiOverrideVersion; - target.StopOptions = stopOptions; - target.StopTimeout = stopTimeout; - - // Assert - Assert.AreEqual(automaticallyDeleteInstanceFiles, target.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfigurationSection.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(language, target.Language, "SqlLocalDbConfigurationSection.Language is incorrect."); - Assert.AreEqual(loggerType, target.LoggerType, "SqlLocalDbConfigurationSection.LoggerType is incorrect."); - Assert.AreEqual(nativeApiOverrideVersion, target.NativeApiOverrideVersion, "SqlLocalDbConfigurationSection.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(stopOptions, target.StopOptions, "SqlLocalDbConfigurationSection.StopOptions is incorrect."); - Assert.AreEqual(stopTimeout, target.StopTimeout, "SqlLocalDbConfigurationSection.StopTimeout is incorrect."); - }); - } - - [TestMethod] - [Description("Tests that the StopTimeout property of an instance of SqlLocalDbConfigurationSection cannot be set to less than TimeSpan.Zero.")] - [ExpectedException(typeof(ConfigurationErrorsException))] - public void SqlLocalDbConfigurationSection_StopTimeout_Cannot_Be_Less_Than_Zero() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TimeSpan value = TimeSpan.FromTicks(-1); - - SqlLocalDbConfigurationSection target = new SqlLocalDbConfigurationSection(); - - // Act and Assert - throw ErrorAssert.Throws( - () => target.StopTimeout = value); - }); - } - - [TestMethod] - [Description("Tests that the LoggerType property of an instance of SqlLocalDbConfigurationSection must implement ILogger.")] - [ExpectedException(typeof(ConfigurationErrorsException))] - public void SqlLocalDbConfigurationSection_LoggerType_Must_Implement_ILogger() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - Type value = typeof(object); - - SqlLocalDbConfigurationSection target = new SqlLocalDbConfigurationSection(); - - // Act and Assert - throw ErrorAssert.Throws( - () => target.LoggerType = value); - }); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/Empty.config b/tests/SqlLocalDb.UnitTests/Empty.config deleted file mode 100644 index 49cc43e1..00000000 --- a/tests/SqlLocalDb.UnitTests/Empty.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/EmptyLogger.cs b/tests/SqlLocalDb.UnitTests/EmptyLogger.cs deleted file mode 100644 index cc7125f0..00000000 --- a/tests/SqlLocalDb.UnitTests/EmptyLogger.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// EmptyLogger.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing an empty implementation of for use in tests. This class cannot be inherited. - /// - internal sealed class EmptyLogger : ILogger - { - /// - /// Initializes a new instance of the class. - /// - internal EmptyLogger() - { - } - - /// - public void WriteError(int id, string format, params object[] args) - { - } - - /// - public void WriteInformation(int id, string format, params object[] args) - { - } - - /// - public void WriteVerbose(int id, string format, params object[] args) - { - } - - /// - public void WriteWarning(int id, string format, params object[] args) - { - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/ErrorAssert.cs b/tests/SqlLocalDb.UnitTests/ErrorAssert.cs deleted file mode 100644 index 84b98717..00000000 --- a/tests/SqlLocalDb.UnitTests/ErrorAssert.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ErrorAssert.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Globalization; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing assert extension methods. This class cannot be inherited. - /// - /// - /// Based on the Assert.Throws<T>() method in the xUnit.net Test Framework. - /// Designed to work around CA1804 and CA1806 being raised by FxCop for unused objects - /// constructed in the process of testing constructors and properties. - /// - internal static class ErrorAssert - { - /// - /// Verifies that the specified delegate throws an exception of the specified - /// type derived from . - /// - /// The type of the expected to be thrown. - /// An representing the code that should throw the exception. - /// The expected name of the exception parameter. - /// - /// The exception thrown by invoking . - /// - public static T Throws(Action testCode, string paramName) - where T : ArgumentException - { - T exception = Throws(testCode); - - Assert.AreEqual( - paramName, - exception.ParamName, - "The value of the {0}.ParamName property is incorrect.", - typeof(T).FullName); - - return exception; - } - - /// - /// Verifies that the specified delegate throws an exception of the specified type. - /// - /// The type of the expected to be thrown. - /// An representing the code that should throw the exception. - /// - /// The exception thrown by invoking . - /// - public static T Throws(Action testCode) - where T : Exception - { - T exception = Invoke(testCode); - - if (exception == null) - { - string message = string.Format( - CultureInfo.InvariantCulture, - "An exception of type {0} was not thrown.", - typeof(T).FullName); - - Assert.Fail(message); - } - - Type thrownType = exception.GetType(); - Type expectedType = typeof(T); - - if (thrownType != expectedType) - { - string message = string.Format( - CultureInfo.InvariantCulture, - "An exception of type {0} was thrown, but an exception of type {1} was expected.", - thrownType.FullName, - expectedType.FullName); - - Assert.Fail(message); - } - - return exception; - } - - /// - /// Verifies that the specified delegate throws an exception of the specified - /// type derived from . - /// - /// The type of the expected to be thrown. - /// An representing the code that should throw the exception. - /// The expected name of the exception parameter. - /// - /// The exception thrown by invoking . - /// - public static T Throws(Func testCode, string paramName) - where T : ArgumentException - { - T exception = Throws(testCode); - - Assert.AreEqual( - paramName, - exception.ParamName, - "The value of the {0}.ParamName property is incorrect.", - typeof(T).FullName); - - return exception; - } - - /// - /// Verifies that the specified delegate throws an exception of the specified type. - /// - /// The type of the expected to be thrown. - /// An representing the code that should throw the exception. - /// - /// The exception thrown by invoking . - /// - public static T Throws(Func testCode) - where T : Exception - { - return Throws(new Action(() => testCode())); - } - - /// - /// Invokes the specified delegate and - /// returns any thrown. - /// - /// The type of exception expected to be thrown. - /// An representing the code that should throw the exception. - /// - /// The exception thrown by invoking , - /// if any was thrown; otherwise . - /// - private static T Invoke(Action testCode) - where T : Exception - { - Diagnostics.Debug.Assert(testCode != null, "testCode cannot be null."); - - try - { - testCode(); - return null; - } - catch (T e) - { - return e; - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/ExtensionsTests.MultipleConnectionStrings.config b/tests/SqlLocalDb.UnitTests/ExtensionsTests.MultipleConnectionStrings.config deleted file mode 100644 index d506b1bd..00000000 --- a/tests/SqlLocalDb.UnitTests/ExtensionsTests.MultipleConnectionStrings.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/ExtensionsTests.NoConnectionStrings.config b/tests/SqlLocalDb.UnitTests/ExtensionsTests.NoConnectionStrings.config deleted file mode 100644 index f0ee24bf..00000000 --- a/tests/SqlLocalDb.UnitTests/ExtensionsTests.NoConnectionStrings.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/ExtensionsTests.cs b/tests/SqlLocalDb.UnitTests/ExtensionsTests.cs deleted file mode 100644 index 4d0b6a12..00000000 --- a/tests/SqlLocalDb.UnitTests/ExtensionsTests.cs +++ /dev/null @@ -1,1154 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ExtensionsTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Data.Common; -using System.Data.EntityClient; -using System.Globalization; -using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class ExtensionsTests - { - /// - /// Initializes a new instance of the class. - /// - public ExtensionsTests() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext - { - get; - set; - } - - [TestMethod] - [Description("Tests GetConnectionForDefaultModel() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetConnectionForDefaultModel_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionForDefaultModel(), - "instance"); - } - - [TestMethod] - [Description("Tests GetConnectionForDefaultModel() returns a connection.")] - public void Extensions_GetConnectionForDefaultModel_Returns_Connection() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - using (DbConnection result = instance.GetConnectionForDefaultModel()) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForDefaultModel() returns a connection if initialCatalog is null.")] - public void Extensions_GetConnectionForDefaultModel_Returns_Connection_If_InitialCatalog_Is_Null() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - using (DbConnection result = instance.GetConnectionForDefaultModel(initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForDefaultModel() returns a connection if initialCatalog is the empty string.")] - public void Extensions_GetConnectionForDefaultModel_Returns_Connection_If_InitialCatalog_Is_Empty() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - using (DbConnection result = instance.GetConnectionForDefaultModel(initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForDefaultModel() returns a connection if an Initial Catalog is specified.")] - public void Extensions_GetConnectionForDefaultModel_Returns_Connection_If_InitialCatalog_Is_Specified() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = "MyOtherDatabase"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - using (DbConnection result = instance.GetConnectionForDefaultModel(initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, initialCatalog, "The Initial Catalog in the connection string was not changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForModel() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetConnectionForModel_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - string modelConnectionStringName = "MyConnectionString"; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionForModel(modelConnectionStringName), - "instance"); - } - - [TestMethod] - [Description("Tests GetConnectionForModel() returns a connection.")] - public void Extensions_GetConnectionForModel_Returns_Connection() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - using (DbConnection result = instance.GetConnectionForModel(modelConnectionStringName)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForModel() returns a connection if initialCatalog is null.")] - public void Extensions_GetConnectionForModel_Returns_Connection_If_InitialCatalog_Is_Null() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - using (DbConnection result = instance.GetConnectionForModel(modelConnectionStringName, initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForModel() returns a connection if initialCatalog is the empty string.")] - public void Extensions_GetConnectionForModel_Returns_Connection_If_InitialCatalog_Is_Empty() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - using (DbConnection result = instance.GetConnectionForModel(modelConnectionStringName, initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionForModel() returns a connection if an Initial Catalog is specified.")] - public void Extensions_GetConnectionForModel_Returns_Connection_If_InitialCatalog_Is_Specified() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = "MyOtherDatabase"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - using (DbConnection result = instance.GetConnectionForModel(modelConnectionStringName, initialCatalog)) - { - // Assert - Assert.IsNotNull(result, "GetConnectionForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnection), "GetConnectionForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, initialCatalog, "The Initial Catalog in the connection string was not changed."); - Assert.AreEqual(ConnectionState.Closed, result.State, "DbConnection.State is incorrect."); - } - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetConnectionStringForDefaultModel_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForDefaultModel(), - "instance"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() returns a connection string.")] - public void Extensions_GetConnectionStringForDefaultModel_Returns_Connection_String() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForDefaultModel(); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() returns a connection string if initialCatalog is null.")] - public void Extensions_GetConnectionStringForDefaultModel_Returns_Connection_String_If_InitialCatalog_Is_Null() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = null; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForDefaultModel(initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() returns a connection string if initialCatalog is the empty string.")] - public void Extensions_GetConnectionStringForDefaultModel_Returns_Connection_String_If_InitialCatalog_Is_Empty() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForDefaultModel(initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() returns a connection string if a different Initial Catalog is specified.")] - public void Extensions_GetConnectionStringForDefaultModel_Returns_Connection_String_If_Initial_Catalog_Specified() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = "MyOtherDatabase"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForDefaultModel(initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForDefaultModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForDefaultModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, initialCatalog, "The Initial Catalog in the connection string was not changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() if no connection strings are configured.")] - [ExpectedException(typeof(InvalidOperationException))] - public void Extensions_GetConnectionStringForDefaultModel_Throws_If_No_Connection_Strings_Configured() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - ISqlLocalDbInstance instance = Mock.Of(); - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForDefaultModel()); - }, - configurationFile: "ExtensionsTests.NoConnectionStrings.config"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() if more than one connection string is configured.")] - [ExpectedException(typeof(InvalidOperationException))] - public void Extensions_GetConnectionStringForDefaultModel_Throws_If_Multiple_Connection_Strings_Are_Configured() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - ISqlLocalDbInstance instance = Mock.Of(); - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForDefaultModel()); - }, - configurationFile: "ExtensionsTests.MultipleConnectionStrings.config"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetConnectionStringForModel_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - string modelConnectionStringName = "MyConnectionString"; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForModel(modelConnectionStringName), - "instance"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() if modelConnectionStringName cannot be found.")] - [ExpectedException(typeof(ArgumentException))] - public void Extensions_GetConnectionStringForModel_Throws_If_ConnectionString_Cannot_Be_Found() - { - // Arrange - ISqlLocalDbInstance instance = Mock.Of(); - string modelConnectionStringName = "NonExistentContext"; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForModel(modelConnectionStringName), - "modelConnectionStringName"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() returns a connection string.")] - public void Extensions_GetConnectionStringForModel_Returns_Connection_String() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForModel(modelConnectionStringName); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() returns a connection string if initialCatalog is null.")] - public void Extensions_GetConnectionStringForModel_Returns_Connection_String_If_InitialCatalog_Is_Null() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = null; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForModel(modelConnectionStringName, initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() returns a connection string if initialCatalog is the empty string.")] - public void Extensions_GetConnectionStringForModel_Returns_Connection_String_If_InitialCatalog_Is_Empty() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = string.Empty; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForModel(modelConnectionStringName, initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, "MyDatabase", "The Initial Catalog in the connection string was changed."); - } - - [TestMethod] - [Description("Tests GetConnectionStringForModel() returns a connection string if a different Initial Catalog is specified.")] - public void Extensions_GetConnectionStringForModel_Returns_Connection_String_If_Initial_Catalog_Specified() - { - // Arrange - string namedPipe = @"np:\\.\pipe\LOCALDB#C0209E6A\tsql\query"; - string initialCatalog = "MyOtherDatabase"; - - Mock mock = new Mock(); - mock.Setup((p) => p.NamedPipe).Returns(namedPipe); - - ISqlLocalDbInstance instance = mock.Object; - string modelConnectionStringName = "MyDataContext"; - - // Act - DbConnectionStringBuilder result = instance.GetConnectionStringForModel(modelConnectionStringName, initialCatalog); - - // Assert - Assert.IsNotNull(result, "GetConnectionStringForModel() returned null."); - Assert.IsInstanceOfType(result, typeof(EntityConnectionStringBuilder), "GetConnectionStringForModel() returned incorrect type."); - StringAssert.Contains(result.ConnectionString, namedPipe, "The named pipe is not present in the connection string."); - StringAssert.Contains(result.ConnectionString, initialCatalog, "The Initial Catalog in the connection string was not changed."); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() throws an exception if value is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetInitialCatalogName_Throws_If_Value_Is_Null() - { - // Arrange - DbConnectionStringBuilder value = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.GetInitialCatalogName(), - "value"); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() if the Initial Catalog name cannot be found.")] - public void Extensions_GetInitialCatalogName_Returns_Null_If_Not_Found() - { - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder(); - - // Act - string result = value.GetInitialCatalogName(); - - // Assert - Assert.IsNull(result, "GetInitialCatalogName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() returns the correct Initial Catalog name.")] - [DataSource( - "Microsoft.VisualStudio.TestTools.DataSource.XML", - @"|DataDirectory|\GetInitialCatalogNameTestCases.xml", - "testCase", - DataAccessMethod.Sequential)] - public void Extensions_GetInitialCatalogName_Returns_Correct_Initial_Catalog() - { - // Arrange - string connectionString = Convert.ToString(this.TestContext.DataRow["connectionString"], CultureInfo.InvariantCulture); - string expected = Convert.ToString(this.TestContext.DataRow["expected"], CultureInfo.InvariantCulture); - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - string actual = value.GetInitialCatalogName(); - - // Assert - Assert.AreEqual(expected, actual, "GetInitialCatalogName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() if the connection string is an entity connection string.")] - public void Extensions_GetInitialCatalogName_Returns_Initial_Catalog_If_Entity_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.EntityClient.EntityConnectionStringBuilder( - @"metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="";data source=.;initial catalog=MyDatabase;integrated security=True;MultipleActiveResultSets=True"""); - - // Act - string actual = value.GetInitialCatalogName(); - - // Assert - Assert.AreEqual("MyDatabase", actual, "GetInitialCatalogName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() if the connection string is an entity connection string with no Initial Catalog.")] - public void Extensions_GetInitialCatalogName_Returns_Null_If_No_Initial_Catalog_In_Entity_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.EntityClient.EntityConnectionStringBuilder( - @"metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="";data source=.;integrated security=True;MultipleActiveResultSets=True"""); - - // Act - string actual = value.GetInitialCatalogName(); - - // Assert - Assert.IsNull(actual, "GetInitialCatalogName() returned incorrect value. Value: {0}", actual); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() if the connection string is an SQL connection string.")] - public void Extensions_GetInitialCatalogName_Returns_Initial_Catalog_If_Sql_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.SqlClient.SqlConnectionStringBuilder( - "data source=.;initial catalog=MyDatabase;integrated security=True;MultipleActiveResultSets=True"); - - // Act - string actual = value.GetInitialCatalogName(); - - // Assert - Assert.AreEqual("MyDatabase", actual, "GetInitialCatalogName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetInitialCatalogName() if the connection string is an SQL connection string with no Initial Catalog.")] - public void Extensions_GetInitialCatalogName_Returns_Null_If_No_Initial_Catalog_In_Sql_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.SqlClient.SqlConnectionStringBuilder( - "data source=.;integrated security=True;MultipleActiveResultSets=True"); - - // Act - string actual = value.GetInitialCatalogName(); - - // Assert - Assert.IsNull(actual, "GetInitialCatalogName() returned incorrect value. Value: {0}", actual); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() throws an exception if value is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetPhysicalFileName_Throws_If_Value_Is_Null() - { - // Arrange - DbConnectionStringBuilder value = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.GetPhysicalFileName(), - "value"); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() if the file name cannot be found.")] - public void Extensions_GetPhysicalFileName_Returns_Null_If_Not_Found() - { - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder(); - - // Act - string result = value.GetPhysicalFileName(); - - // Assert - Assert.IsNull(result, "GetPhysicalFileName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() returns the correct file name.")] - [DataSource( - "Microsoft.VisualStudio.TestTools.DataSource.XML", - @"|DataDirectory|\GetPhysicalFileNameTestCases.xml", - "testCase", - DataAccessMethod.Sequential)] - public void Extensions_GetPhysicalFileName_Returns_Correct_FileName() - { - // Arrange - string connectionString = Convert.ToString(this.TestContext.DataRow["connectionString"], CultureInfo.InvariantCulture); - string expected = Convert.ToString(this.TestContext.DataRow["expected"], CultureInfo.InvariantCulture); - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - string actual = value.GetPhysicalFileName(); - - // Assert - Assert.AreEqual(expected, actual, "GetPhysicalFileName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() if the connection string is an entity connection string.")] - public void Extensions_GetPhysicalFileName_Returns_FileName_If_Entity_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.EntityClient.EntityConnectionStringBuilder( - @"metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="";data source=.;AttachDBFilename=C:\MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"""); - - // Act - string actual = value.GetPhysicalFileName(); - - // Assert - Assert.AreEqual(@"C:\MyDatabase.mdf", actual, "GetPhysicalFileName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() if the connection string is an entity connection string with no file name.")] - public void Extensions_GetPhysicalFileName_Returns_Null_If_No_FileName_In_Entity_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.EntityClient.EntityConnectionStringBuilder( - @"metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="";data source=.;integrated security=True;MultipleActiveResultSets=True"""); - - // Act - string actual = value.GetPhysicalFileName(); - - // Assert - Assert.IsNull(actual, "GetPhysicalFileName() returned incorrect value. Value: {0}", actual); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() if the connection string is an SQL connection string.")] - public void Extensions_GetPhysicalFileName_Returns_FileName_If_Sql_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.SqlClient.SqlConnectionStringBuilder( - @"data source=.;AttachDBFilename=""C:\MyDatabase.mdf"";integrated security=True;MultipleActiveResultSets=True"); - - // Act - string actual = value.GetPhysicalFileName(); - - // Assert - Assert.AreEqual(@"C:\MyDatabase.mdf", actual, "GetPhysicalFileName() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetPhysicalFileName() if the connection string is an SQL connection string with no file name.")] - public void Extensions_GetPhysicalFileName_Returns_Null_If_No_FileName_In_Sql_Connection_String() - { - // Arrange - DbConnectionStringBuilder value = new System.Data.SqlClient.SqlConnectionStringBuilder( - "data source=.;integrated security=True;MultipleActiveResultSets=True"); - - // Act - string actual = value.GetPhysicalFileName(); - - // Assert - Assert.IsNull(actual, "GetPhysicalFileName() returned incorrect value. Value: {0}", actual); - } - - [TestMethod] - [Description("Tests GetOrCreateInstance() if value is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetOrCreateInstance_Throws_If_Value_Is_Null() - { - // Arrange - ISqlLocalDbProvider value = null; - string instanceName = Guid.NewGuid().ToString(); - - // Act and Assert - throw ErrorAssert.Throws( - () => value.GetOrCreateInstance(instanceName), - "value"); - } - - [TestMethod] - [Description("Tests GetOrCreateInstance() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_GetOrCreateInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - ISqlLocalDbProvider value = Mock.Of(); - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.GetOrCreateInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests GetOrCreateInstance() if instanceName does not exist.")] - public void Extensions_GetOrCreateInstance_If_InstanceName_Does_Not_Exist() - { - // Arrange - ISqlLocalDbProvider value = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - ISqlLocalDbInstance result = value.GetOrCreateInstance(instanceName); - - try - { - // Assert - Assert.IsNotNull(result, "GetOrCreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "ISqlLocalDbInstance.Name is incorrect."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests GetOrCreateInstance() if instanceName exists.")] - public void Extensions_GetOrCreateInstance_If_InstanceName_Exists() - { - // Arrange - ISqlLocalDbProvider value = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - ISqlLocalDbInstance result = value.GetOrCreateInstance(instanceName); - - try - { - // Assert - Assert.IsNotNull(result, "GetOrCreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "ISqlLocalDbInstance.Name is incorrect."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2012)] - [Description("Tests GetOrCreateInstance() if instanceName is the SQL LocalDB 2012 default instance name.")] - public void Extensions_GetOrCreateInstance_For_2012_Default_Instance_Name() - { - // Arrange - Helpers.EnsureLocalDBVersionInstalled(11); - - ISqlLocalDbProvider value = new SqlLocalDbProvider(); - string instanceName = "v11.0"; - - // Act - ISqlLocalDbInstance result = value.GetOrCreateInstance(instanceName); - - // Assert - Assert.IsNotNull(result, "GetOrCreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "ISqlLocalDbInstance.Name is incorrect."); - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2014)] - [TestCategory(TestCategories.SqlServer2016)] - [Description("Tests GetOrCreateInstance() if instanceName is the SQL LocalDB 2014 and 2016 default instance name.")] - public void Extensions_GetOrCreateInstance_For_2014_And_2016_Default_Instance_Name() - { - // Arrange - ISqlLocalDbProvider value = new SqlLocalDbProvider(); - string instanceName = "MSSQLLocalDB"; - - // Act - ISqlLocalDbInstance result = value.GetOrCreateInstance(instanceName); - - // Assert - Assert.IsNotNull(result, "GetOrCreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "ISqlLocalDbInstance.Name is incorrect."); - } - - [TestMethod] - [Description("Tests SetInitialCatalogName() if value is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_SetInitialCatalogName_Throws_If_Value_Is_Null() - { - // Arrange - DbConnectionStringBuilder value = null; - string initialCatalog = "MyCatalog"; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.SetInitialCatalogName(initialCatalog), - "value"); - } - - [TestMethod] - [Description("Tests SetInitialCatalogName() sets the correct Initial Catalog name.")] - [DataSource( - "Microsoft.VisualStudio.TestTools.DataSource.XML", - @"|DataDirectory|\SetInitialCatalogNameTestCases.xml", - "testCase", - DataAccessMethod.Sequential)] - public void Extensions_SetInitialCatalogName_Returns_Correct_Initial_Catalog() - { - // Arrange - string connectionString = Convert.ToString(this.TestContext.DataRow["connectionString"], CultureInfo.InvariantCulture); - string initialCatalog = Convert.ToString(this.TestContext.DataRow["initialCatalog"], CultureInfo.InvariantCulture); - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - value.SetInitialCatalogName(initialCatalog); - - // Assert - string result = value.GetInitialCatalogName(); - Assert.AreEqual(initialCatalog, result, "SetInitialCatalogName() did not set the correct value."); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() sets the correct physical file name.")] - [DataSource( - "Microsoft.VisualStudio.TestTools.DataSource.XML", - @"|DataDirectory|\SetPhysicalFileNameTestCases.xml", - "testCase", - DataAccessMethod.Sequential)] - public void Extensions_SetPhysicalFileName_Returns_Correct_File_Name() - { - // Arrange - string connectionString = Convert.ToString(this.TestContext.DataRow["connectionString"], CultureInfo.InvariantCulture); - string fileName = Convert.ToString(this.TestContext.DataRow["physicalFileName"], CultureInfo.InvariantCulture); - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - value.SetPhysicalFileName(fileName); - - // Assert - string result = value.GetPhysicalFileName(); - Assert.AreEqual(fileName, result, "SetPhysicalFileName() did not set the correct value."); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() sets the correct physical file name if a relative path is used.")] - public void Extensions_SetPhysicalFileName_Sets_Correct_Value_If_Relative_Path_Used() - { - // Arrange - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = @".\MyDatabase.mdf"; - string expected = Path.GetFullPath(fileName); - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - value.SetPhysicalFileName(fileName); - - // Assert - string result = value.GetPhysicalFileName(); - Assert.AreEqual(expected, result, "SetPhysicalFileName() did not set the correct value."); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() sets the correct physical file name if the |DataDirectory| AppDomain value is set.")] - public void Extensions_SetPhysicalFileName_Sets_Correct_Value_If_Data_Directory_Used_And_Set() - { - // Arrange - var appDomainData = new Dictionary() - { - { "DataDirectory", @"C:\MyDatabases" }, - }; - - Helpers.InvokeInNewAppDomain( - () => - { - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = @"|DataDirectory|\MyDatabase.mdf"; - string expected = @"C:\MyDatabases\MyDatabase.mdf"; - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - value.SetPhysicalFileName(fileName); - - // Assert - string result = value.GetPhysicalFileName(); - Assert.AreEqual(expected, result, "SetPhysicalFileName() did not set the correct value."); - }, - appDomainData: appDomainData); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() sets the correct physical file name if the |DataDirectory| AppDomain value is not set.")] - [ExpectedException(typeof(NotSupportedException))] - public void Extensions_SetPhysicalFileName_Sets_Correct_Value_If_Data_Directory_Used_And_Not_Set() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = @"|DataDirectory|\MyDatabase.mdf"; - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.SetPhysicalFileName(fileName)); - }); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() clears the physical file name if null is specified.")] - public void Extensions_SetPhysicalFileName_Sets_Correct_Value_If_Null_Specified() - { - // Arrange - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = null; - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act - value.SetPhysicalFileName(fileName); - - // Assert - string result = value.GetPhysicalFileName(); - Assert.IsNull(result, "SetPhysicalFileName() did not set the correct value."); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() throws if fileName is invalid.")] - [ExpectedException(typeof(ArgumentException))] - public void Extensions_SetPhysicalFileName_Throws_If_FileName_Is_Invalid() - { - // Arrange - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = @"\\\\\\"; - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.SetPhysicalFileName(fileName), - "fileName"); - } - - [TestMethod] - [Description("Tests SetPhysicalFileName() throws if fileName is not supported.")] - [ExpectedException(typeof(ArgumentException))] - public void Extensions_SetPhysicalFileName_Throws_If_FileName_Is_Not_Supported() - { - // Arrange - string connectionString = @"data source=.;attachdbfilename=MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True"; - string fileName = @"\database:mdf"; - - // Arrange - DbConnectionStringBuilder value = new DbConnectionStringBuilder() - { - ConnectionString = connectionString, - }; - - // Act and Assert - throw ErrorAssert.Throws( - () => value.SetPhysicalFileName(fileName), - "fileName"); - } - - [TestMethod] - [Description("Tests GetConnectionStringForDefaultModel() throws an exception if the instance has no named pipe.")] - [ExpectedException(typeof(InvalidOperationException))] - public void Extensions_GetConnectionStringForDefaultModel_Throws_If_Instance_Has_No_Named_Pipe() - { - ISqlLocalDbInstance instance = Mock.Of(); - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.GetConnectionStringForDefaultModel()); - } - - [TestMethod] - [Description("Tests Restart() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void Extensions_Restart_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => instance.Restart(), - "instance"); - } - - [TestMethod] - [Description("Tests Restart().")] - public void Extensions_Restart_Restarts_Instance() - { - // Arrange - Mock mock = new Mock(); - ISqlLocalDbInstance instance = mock.Object; - - // Act - instance.Restart(); - - // Assert - mock.Verify((p) => p.Stop(), Times.Once()); - mock.Verify((p) => p.Start(), Times.Once()); - } - - [TestMethod] - [Description("Tests GetDefaultInstance() returns default instance.")] - public void Extensions_GetDefaultInstance_Returns_Default_Instance() - { - // Arrange - ISqlLocalDbProvider value = new SqlLocalDbProvider(); - - // Act - ISqlLocalDbInstance result = value.GetDefaultInstance(); - - // Assert - Assert.IsNotNull(result, "GetDefaultInstance() returned null."); - Assert.AreEqual(SqlLocalDbApi.DefaultInstanceName, result.Name, "ISqlLocalDbInstance.Name is incorrect."); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/GetInitialCatalogNameTestCases.xml b/tests/SqlLocalDb.UnitTests/GetInitialCatalogNameTestCases.xml deleted file mode 100644 index 744268e5..00000000 --- a/tests/SqlLocalDb.UnitTests/GetInitialCatalogNameTestCases.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - data source=.;initial catalog=MyDatabase;integrated security=True;MultipleActiveResultSets=True - MyDatabase - - - data source=.;Initial Catalog=MyOtherDatabase;integrated security=True;MultipleActiveResultSets=True - MyOtherDatabase - - - data source=.;INITIAL CATALOG=YetAnotherDatabase;integrated security=True;MultipleActiveResultSets=True - YetAnotherDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=MyEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - MyEntityDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;Provider Connection String="data source=.;initial catalog=MyOtherEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - MyOtherEntityDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;PROVIDER CONNECTION STRING="data source=.;initial catalog=YetAnotherEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - YetAnotherEntityDatabase - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/GetPhysicalFileNameTestCases.xml b/tests/SqlLocalDb.UnitTests/GetPhysicalFileNameTestCases.xml deleted file mode 100644 index cab120fb..00000000 --- a/tests/SqlLocalDb.UnitTests/GetPhysicalFileNameTestCases.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - data source=.;attachdbfilename=C:\MyFirstDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\MyFirstDatabase.mdf - - - data source=.;AttachDBFilename=C:\MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\MyDatabase.mdf - - - data source=.;ATTACHDBFILENAME=C:\MyOtherDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\MyOtherDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;AttachDBFilename=C:\MyFirstEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\MyFirstEntityDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;Provider Connection String="data source=.;AttachDBFilename=C:\MyOtherEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\MyOtherEntityDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;PROVIDER CONNECTION STRING="data source=.;AttachDBFilename=C:\YetAnotherEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\YetAnotherEntityDatabase.mdf - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/Helpers.cs b/tests/SqlLocalDb.UnitTests/Helpers.cs deleted file mode 100644 index f1352461..00000000 --- a/tests/SqlLocalDb.UnitTests/Helpers.cs +++ /dev/null @@ -1,178 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// Helpers.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Principal; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit test helper methods. This class cannot be inherited. - /// - internal static class Helpers - { - /// - /// Ensures that the current user has Administrative privileges. - /// - /// - /// Any unit test calling this method is marked as Inconclusive if - /// the current user does not have administrative privileges. - /// - public static void EnsureUserIsAdmin() - { - if (!IsCurrentUserAdmin(out string name)) - { - Assert.Inconclusive("The current user '{0}' does not have administrative privileges.", name); - } - } - - /// - /// Ensures that SQL Server LocalDB is installed on the current machine. - /// - /// - /// Any unit test calling this method is marked as Inconclusive if SQL - /// Server LocalDB is not installed on the local machine. - /// - public static void EnsureLocalDBInstalled() - { - if (!SqlLocalDbApi.IsLocalDBInstalled()) - { - Assert.Inconclusive("SQL Server LocalDB is not installed on {0}.", Environment.MachineName); - } - } - - /// - /// Ensures that the specified SQL Server LocalDB version is installed on the current machine. - /// - /// The major version of SQL Server LocalDB required. - /// - /// Any unit test calling this method is marked as Inconclusive if the required version of - /// SQL Server LocalDB is not installed on the local machine. - /// - public static void EnsureLocalDBVersionInstalled(int majorVersion) - { - ISqlLocalDbProvider provider = new SqlLocalDbProvider(); - - if (!provider.GetVersions().Any((p) => p.Version.Major == majorVersion)) - { - Assert.Inconclusive($"SQL Server LocalDB v{majorVersion} is not installed on {Environment.MachineName}."); - } - } - - /// - /// Invokes the specified delegate in a new . - /// - /// The delegate to invoke in the new . - /// The optional data to set for the . - /// The optional name of the configuration file to use. - /// The optional name of the caller of this method. - public static void InvokeInNewAppDomain( - CrossAppDomainDelegate callBackDelegate, - IDictionary appDomainData = null, - string configurationFile = null, - [CallerMemberName] string callerMemberName = null) - { - AppDomainSetup info = AppDomain.CurrentDomain.SetupInformation; - - if (!string.IsNullOrEmpty(configurationFile)) - { - info.ConfigurationFile = configurationFile; - } - - AppDomain domain = AppDomain.CreateDomain( - callerMemberName, - null, - info); - - try - { - if (appDomainData != null) - { - foreach (var pair in appDomainData) - { - domain.SetData(pair.Key, pair.Value); - } - } - - ILogger logger = Logger.DefaultLogger.Value; - - if (logger.GetType().IsMarshalByRef) - { - // Create an instance of the type that configures logging in the new AppDomain - Type helperType = typeof(LoggingHelper); - var handle = domain.CreateInstanceFrom(helperType.Assembly.Location, helperType.FullName); - - var helper = (LoggingHelper)handle.Unwrap(); - helper.SetLogger(logger); - } - - domain.DoCallBack(callBackDelegate); - } - finally - { - AppDomain.Unload(domain); - } - } - - /// - /// Returns whether the current user has Administrative privileges. - /// - /// - /// if the current user has Administrative - /// privileges; otherwise . - /// - public static bool IsCurrentUserAdmin() - { - return IsCurrentUserAdmin(out string name); - } - - /// - /// Returns whether the current user has Administrative privileges. - /// - /// When the method returns, contains the name of the current user. - /// - /// if the current user has Administrative - /// privileges; otherwise . - /// - private static bool IsCurrentUserAdmin(out string name) - { - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - name = identity.Name; - return new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator); - } - } - - /// - /// A helper class that is used to configure logging in an used by a test. This class cannot be inherited. - /// - [Serializable] - private sealed class LoggingHelper : MarshalByRefObject - { - /// - /// Sets the implementation in use by the . - /// - /// The to use. - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Performance", - "CA1822:MarkMembersAsStatic", - Justification = "This is an instance method to allow calling it across AppDomain boundaries.")] - internal void SetLogger(ILogger logger) - { - Logger.SetLogger(logger); - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/IntegrationTests.cs b/tests/SqlLocalDb.UnitTests/IntegrationTests.cs deleted file mode 100644 index 2158e58c..00000000 --- a/tests/SqlLocalDb.UnitTests/IntegrationTests.cs +++ /dev/null @@ -1,323 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// IntegrationTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Globalization; -using System.Linq; -using System.Security.Principal; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing integration tests for the System.Data.SqlLocalDb assembly. - /// - [TestClass] - public class IntegrationTests - { - /// - /// Initializes a new instance of the class. - /// - public IntegrationTests() - { - } - - [TestMethod] - [TestCategory(TestCategories.Integration)] - [Description("An end-to-end test for the System.Data.SqlLocalDb API using the ISqlLocalDbApi interface.")] - public void System_Data_SqlLocalDb_Assembly_Can_Be_Used_End_To_End_To_Create_And_Manage_Instances() - { - ISqlLocalDbApi localDB = new SqlLocalDbApiWrapper(); - - if (!localDB.IsLocalDBInstalled()) - { - Assert.Fail("SQL LocalDB is not installed."); - } - - ISqlLocalDbProvider provider = new SqlLocalDbProvider(); - - TestVersions(provider); - - TestInstances(provider); - - TestInstanceLifecycle(localDB, provider); - } - - [TestMethod] - [TestCategory(TestCategories.Integration)] - [TestCategory(TestCategories.RequiresAdministrativePermissions)] - [Description("An end-to-end test for the System.Data.SqlLocalDb API using the SqlLocalDbApiWrapper class.")] - public void System_Data_SqlLocalDb_Assembly_Can_Be_Used_End_To_End_To_Trace_Create_And_Share_Instances() - { - Helpers.EnsureUserIsAdmin(); - - SqlLocalDbApiWrapper target = new SqlLocalDbApiWrapper(); - - if (!target.IsLocalDBInstalled()) - { - Assert.Fail("SQL LocalDB is not installed."); - } - - string instanceName = Guid.NewGuid().ToString(); - string version = target.LatestVersion; - - target.StartTracing(); - - try - { - target.CreateInstance(instanceName, version); - - try - { - target.StartInstance(instanceName); - - try - { - string ownerSid; - - using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) - { - ownerSid = identity.User.Value; - } - - string sharedInstanceName = Guid.NewGuid().ToString(); - - target.ShareInstance(ownerSid, instanceName, sharedInstanceName); - target.UnshareInstance(instanceName); - } - finally - { - target.StopInstance(instanceName, SqlLocalDbApi.StopTimeout); - } - } - finally - { - target.DeleteInstance(instanceName); - } - } - finally - { - target.StopTracing(); - } - } - - /// - /// Tests that the instances reported by the specified are valid. - /// - /// The to test the instances for. - private static void TestInstances(ISqlLocalDbProvider provider) - { - IList instances = provider.GetInstances(); - - Assert.IsNotNull(instances, "GetInstances() returned null."); - CollectionAssert.AllItemsAreNotNull(instances.ToArray(), "GetInstances() returned a null item."); - - bool usingSqlServer2012 = NativeMethods.NativeApiVersion == new Version(11, 0); - - string[] defaultInstanceNamesForSqlServer2012 = new string[] - { - "v12.0", - }; - - foreach (ISqlLocalDbInstanceInfo instanceInfo in instances) - { - Assert.IsNotNull(instanceInfo.Name, "ISqlLocalDbInstanceInfo.Name is null.", instanceInfo.Name); - - if (usingSqlServer2012 && defaultInstanceNamesForSqlServer2012.Contains(instanceInfo.Name, StringComparer.Ordinal)) - { - // The SQL LocalDB 2012 Instance API reports the name as 'v12.0' instead of 'MSSQLLocalDB', - // which then if queried states that it does not exist (because it doesn't actually exist) - // under that name. In this case, skip this instance from being tested. We could fudge this - // in the wrapper itself, but that's probably not the best idea as the default instance name - // may change again in SQL Server 2016. - continue; - } - - Assert.AreNotEqual(string.Empty, instanceInfo.Name, "ISqlLocalDbInstanceInfo.Name is incorrect.", instanceInfo.Name); - Assert.IsFalse(instanceInfo.ConfigurationCorrupt, "ISqlLocalDbInstanceInfo.ConfigurationCorrupt is incorrect for instance '{0}'.", instanceInfo.Name); - - // The automatic instance may not yet exist on a clean machine - if (!instanceInfo.IsAutomatic) - { - Assert.IsTrue(instanceInfo.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect for instance '{0}'.", instanceInfo.Name); - } - - Assert.IsNotNull(instanceInfo.LocalDbVersion, "ISqlLocalDbInstanceInfo.LocalDbVersion is null for instance '{0}'.", instanceInfo.Name); - Assert.AreNotEqual(string.Empty, instanceInfo.LocalDbVersion, "ISqlLocalDbInstanceInfo.LocalDbVersion is incorrect for instance '{0}'.", instanceInfo.Name); - - // These values are only populated if the instance exists - if (instanceInfo.Exists) - { - Assert.IsNotNull(instanceInfo.OwnerSid, "ISqlLocalDbInstanceInfo.OwnerSid is null for instance '{0}'.", instanceInfo.Name); - Assert.AreNotEqual(string.Empty, instanceInfo.OwnerSid, "ISqlLocalDbInstanceInfo.OwnerSid is incorrect for instance '{0}'.", instanceInfo.Name); - } - } - } - - /// - /// Tests that the versions reported by the specified are valid. - /// - /// The to test the versions for. - private static void TestVersions(ISqlLocalDbProvider provider) - { - IList versions = provider.GetVersions(); - - Assert.IsNotNull(versions, "GetVersions() returned null."); - CollectionAssert.AllItemsAreNotNull(versions.ToArray(), "GetVersions() returned a null item."); - - foreach (ISqlLocalDbVersionInfo version in versions) - { - Assert.IsNotNull(version.Name, "ISqlLocalDbVersionInfo.Name is null."); - Assert.AreNotEqual(string.Empty, version.Name, "ISqlLocalDbVersionInfo.Name is incorrect."); - Assert.IsTrue(version.Exists, "ISqlLocalDbVersionInfo.Exists is incorrect for version '{0}'.", version.Name); - Assert.IsNotNull(version.Version, "ISqlLocalDbVersionInfo.Version is null for version '{0}'.", version.Name); - Assert.AreNotEqual(string.Empty, version.Version, "ISqlLocalDbVersionInfo.Version is incorrect for version '{0}'.", version.Name); - } - } - - /// - /// Tests the lifecycle of SQL LocalDB instances. - /// - /// The to use. - /// The to use. - private static void TestInstanceLifecycle(ISqlLocalDbApi localDB, ISqlLocalDbProvider provider) - { - string instanceName = Guid.NewGuid().ToString(); - string sharedInstanceName = string.Empty; - - ISqlLocalDbInstance instance = provider.CreateInstance(instanceName); - - instance.Start(); - - try - { - bool currentUserIsAdmin = Helpers.IsCurrentUserAdmin(); - - if (currentUserIsAdmin) - { - sharedInstanceName = Guid.NewGuid().ToString(); - instance.Share(sharedInstanceName); - - // Restart the instance so it listens on the new shared name's pipe - instance.Restart(); - } - - try - { - ISqlLocalDbInstanceInfo info = provider.GetInstances() - .Where((p) => string.Equals(p.Name, instanceName, StringComparison.Ordinal)) - .FirstOrDefault(); - - Assert.IsNotNull(info, "GetInstances() did not return the created instance."); - Assert.AreEqual(sharedInstanceName, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - - using (SqlConnection connection = instance.CreateConnection()) - { - Assert.IsNotNull(connection, "CreateConnection() returned null."); - TestConnection(connection); - } - - if (currentUserIsAdmin) - { - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder() - { - DataSource = $@"(localdb)\.\{sharedInstanceName}", - IntegratedSecurity = true - }; - - using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) - { - TestConnection(connection); - } - } - } - finally - { - if (currentUserIsAdmin) - { - instance.Unshare(); - } - } - } - finally - { - instance.Stop(); - localDB.DeleteInstance(instance.Name); - } - } - - /// - /// Tests that the specified can be used to create a test database. - /// - /// The to use to create the test database. - private static void TestConnection(SqlConnection connection) - { - connection.Open(); - - try - { - using (SqlCommand command = new SqlCommand("create database [MyDatabase]", connection)) - { - command.ExecuteNonQuery(); - } - - try - { - using (SqlCommand command = new SqlCommand("create table [MyDatabase].[dbo].[TestTable] ([Id] int not null primary key clustered, [Value] int not null);", connection)) - { - command.ExecuteNonQuery(); - } - - Random random = new Random(); - int id = random.Next(); - int value = random.Next(); - - using (SqlCommand command = new SqlCommand("insert into [MyDatabase].[dbo].[TestTable] ([Id], [Value]) values (@id, @value);", connection)) - { - command.Parameters.Add(new SqlParameter("id", id)); - command.Parameters.Add(new SqlParameter("value", value)); - - command.ExecuteNonQuery(); - } - - using (SqlCommand command = new SqlCommand("select top 1 [Value] from [MyDatabase].[dbo].[TestTable] where [Id] = @id;", connection)) - { - command.Parameters.Add(new SqlParameter("id", id)); - - using (SqlDataReader reader = command.ExecuteReader()) - { - Assert.IsTrue(reader.Read(), "SqlDataReader.Read() returned incorrect result."); - Assert.AreEqual(1, reader.FieldCount, "SqlDataReader.FieldCount is incorrect."); - - int actual = reader.GetInt32(0); - - Assert.AreEqual(value, actual, "The read query result value is incorrect."); - Assert.IsFalse(reader.Read(), "SqlDataReader.Read() returned incorrect result."); - } - } - } - finally - { - using (SqlCommand command = new SqlCommand("drop database [MyDatabase]", connection)) - { - command.ExecuteNonQuery(); - } - } - } - finally - { - connection.Close(); - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/LoggerTests.CustomLoggerType.config b/tests/SqlLocalDb.UnitTests/LoggerTests.CustomLoggerType.config deleted file mode 100644 index 822779a4..00000000 --- a/tests/SqlLocalDb.UnitTests/LoggerTests.CustomLoggerType.config +++ /dev/null @@ -1,7 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/LoggerTests.cs b/tests/SqlLocalDb.UnitTests/LoggerTests.cs deleted file mode 100644 index 7a6b852f..00000000 --- a/tests/SqlLocalDb.UnitTests/LoggerTests.cs +++ /dev/null @@ -1,177 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// LoggerTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class LoggerTests - { - /// - /// Initializes a new instance of the class. - /// - public LoggerTests() - { - } - - [TestMethod] - [Description("Tests SetLogger() sets the logging implementation in use and setting it to null resets the logging implementation.")] - public void Logger_SetLogger_Sets_Logger_And_Allows_Logger_To_Be_Reset() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - Mock mock = new Mock(); - - ILogger logger = mock.Object; - - int id = 42; - string format = "The machine name is {0}."; - object[] args = new object[] { Environment.MachineName }; - - // Act - Logger.SetLogger(logger); - - Logger.Error(id, format, args); - Logger.Information(id, format, args); - Logger.Verbose(id, format, args); - Logger.Warning(id, format, args); - - // Assert - mock.Verify((p) => p.WriteError(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteInformation(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteVerbose(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteWarning(id, format, args), Times.Once()); - - // Act - Logger.SetLogger(null); - - Logger.Information(id, format, args); - Logger.Verbose(id, format, args); - - // Assert - mock.Verify((p) => p.WriteError(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteInformation(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteVerbose(id, format, args), Times.Once()); - mock.Verify((p) => p.WriteWarning(id, format, args), Times.Once()); - }); - } - - [TestMethod] - [Description("Tests that the Logger class uses the custom ILogger implementation configured in the application configuration file.")] - public void Logger_SetLogger_Uses_Custom_ILogger_Implementation_From_Configuration_File() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - int id = 42; - string format = "The machine name is {0}."; - object[] args = new object[] { Environment.MachineName }; - - Logger.SetLogger(null); // Clear the special logger the cross-AppDomain logging for the tests sets up - - // Act - Logger.Error(id, format, args); - Logger.Information(id, format, args); - Logger.Verbose(id, format, args); - Logger.Warning(id, format, args); - - // Assert - Assert.AreEqual(4, TestLogger.InvocationCount, "The custom logger was not used."); - }, - configurationFile: "LoggerTests.CustomLoggerType.config"); - } - - [TestMethod] - [Description("Tests that all event ID values in the Logger.TraceEvent class are unique.")] - public void Logger_TraceEvent_All_Id_Values_Are_Unique() - { - // Arrange - Type type = typeof(Logger.TraceEvent); - var fields = type.GetFields(BindingFlags.Static | BindingFlags.NonPublic); - - IList values = new List(); - - // Act - foreach (FieldInfo field in fields) - { - int value = (int)field.GetValue(null); - values.Add(value); - } - - // Assert - Assert.AreNotEqual(0, values.Count, "No values were obtained for the {0} class.", type.FullName); - Assert.AreEqual(values.Distinct().Count(), values.Count, "The {0} class contains one or more duplicate event ID.", type.FullName); - } - - /// - /// A class for testing changing the default implementation. This class cannot be inherited. - /// - private sealed class TestLogger : ILogger - { - /// - /// The number of times any logger has been invoked. - /// - private static int _invocationCount; - - /// - /// Prevents a default instance of the class from being created. - /// - private TestLogger() - { - // Private constructor and class used to test that non-public loggers can be used - } - - /// - /// Gets the number of times any logger has been invoked. - /// - internal static int InvocationCount - { - get { return _invocationCount; } - } - - /// - public void WriteError(int id, string format, params object[] args) - { - _invocationCount++; - } - - /// - public void WriteInformation(int id, string format, params object[] args) - { - _invocationCount++; - } - - /// - public void WriteVerbose(int id, string format, params object[] args) - { - _invocationCount++; - } - - /// - public void WriteWarning(int id, string format, params object[] args) - { - _invocationCount++; - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/NativeMethodsTests.cs b/tests/SqlLocalDb.UnitTests/NativeMethodsTests.cs deleted file mode 100644 index 5c58890d..00000000 --- a/tests/SqlLocalDb.UnitTests/NativeMethodsTests.cs +++ /dev/null @@ -1,332 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// NativeMethodsTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class NativeMethodsTests - { - /// - /// Initializes a new instance of the class. - /// - public NativeMethodsTests() - { - } - - [TestMethod] - [Description("Tests that all P/Invoke methods to the SQL LocalDB Instance API return the correct value if it is not installed.")] - public void NativeMethods_Methods_Return_Correct_Value_If_Sql_LocalDb_Instance_Api_Not_Installed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - NativeMethods.Registry = Mock.Of(); - - int intValue = default(int); - IntPtr ptrValue = default(IntPtr); - StopInstanceOptions stopValue = default(StopInstanceOptions); - string stringValue = default(string); - StringBuilder builderValue = default(StringBuilder); - - // Act and Assert - AssertIsNotInstalledResult(() => NativeMethods.CreateInstance(stringValue, stringValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.DeleteInstance(stringValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.GetInstanceInfo(stringValue, ptrValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.GetInstanceNames(ptrValue, ref intValue)); - AssertIsNotInstalledResult(() => NativeMethods.GetLocalDbError(intValue, intValue, builderValue, ref intValue)); - AssertIsNotInstalledResult(() => NativeMethods.GetVersionInfo(stringValue, ptrValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.GetVersions(ptrValue, ref intValue)); - AssertIsNotInstalledResult(() => NativeMethods.ShareInstance(ptrValue, stringValue, stringValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.StartInstance(stringValue, intValue, builderValue, ref intValue)); - AssertIsNotInstalledResult(() => NativeMethods.StartTracing()); - AssertIsNotInstalledResult(() => NativeMethods.StopInstance(stringValue, stopValue, intValue)); - AssertIsNotInstalledResult(() => NativeMethods.StopTracing()); - AssertIsNotInstalledResult(() => NativeMethods.UnshareInstance(stringValue, intValue)); - }); - } - - [Ignore] // Test causes a UI pop-up from Windows when building from the command-line - [TestMethod] - [Description("Tests that P/Invoke methods to the SQL LocalDB Instance API return the correct value if the DLL cannot be loaded.")] - public void NativeMethods_Methods_Return_Correct_Value_If_Sql_LocalDb_Instance_Api_Cannot_Be_Loaded() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - string fileName = Path.GetTempFileName(); // Empty file so not a valid DLL - - try - { - var versions = new Tuple[] - { - Tuple.Create("12.0", fileName), - }; - - NativeMethods.Registry = CreateRegistry(versions); - - // Act and Assert - AssertIsNotInstalledResult(() => NativeMethods.StartTracing()); - } - finally - { - File.Delete(fileName); - } - }); - } - - [TestMethod] - [Description("Tests that P/Invoke methods to the SQL LocalDB Instance API return the correct value if the native function in the DLL cannot be found.")] - public void NativeMethods_Methods_Return_Correct_Value_If_Sql_LocalDb_Function_Not_Found() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - string fileName = typeof(NativeMethods).Assembly.Location; // .NET assembly not a native DLL, so GetProcAddress() will fail - - var versions = new Tuple[] - { - Tuple.Create("12.0", fileName), - }; - - NativeMethods.Registry = CreateRegistry(versions); - - // Act and Assert - AssertIsNotInstalledResult(() => NativeMethods.StartTracing()); - }); - } - - [TestMethod] - [Description("Tests that P/Invoke methods to the SQL LocalDB Instance API return the correct value if the native DLL cannot be found.")] - public void NativeMethods_Methods_Return_Correct_Value_If_Sql_LocalDb_Api_Path_Not_Found() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - var versions = new Tuple[] - { - Tuple.Create("12.0", string.Empty), - }; - - NativeMethods.Registry = CreateRegistry(versions); - - // Act and Assert - AssertIsNotInstalledResult(() => NativeMethods.StartTracing()); - }); - } - - [TestMethod] - [Description("Tests TryGetLocalDbApiPath() correctly enumerates the registry to get the path of the latest installed version of SQL LocalDB ignoring any invalid registry keys.")] - public void NativeMethods_TryGetLocalDbApiPath_Enumerates_Installed_Sql_LocalDb_Versions_Correctly_Ignoring_Invalid_Registry_Keys() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - string expected = typeof(NativeMethods).Assembly.Location; - - var versions = new Tuple[] - { - Tuple.Create("11.0", "SqlLocalDb_11_0.dll"), - Tuple.Create("11.1", "SqlLocalDb_11_1.dll"), - Tuple.Create("12.0", string.Empty), - Tuple.Create("12.0.1", typeof(NativeMethodsTests).Assembly.Location), - Tuple.Create("12.0.1.2", Path.GetFileName(expected)), - Tuple.Create("NotAVersion", string.Empty), - Tuple.Create("12.0.0.a", string.Empty), - Tuple.Create("13.0.0.0.0", string.Empty), - Tuple.Create("2147483648.0.0.0", string.Empty), - }; - - NativeMethods.Registry = CreateRegistry(versions); - - // Act - bool result = NativeMethods.TryGetLocalDbApiPath(out string fileName); - - // Assert - Assert.IsTrue(result, "TryGetLocalDbApiPath() returned incorrect result."); - Assert.AreEqual(expected, fileName, "fileName is incorrect."); - }); - } - - [TestMethod] - [Description("Tests TryGetLocalDbApiPath() returns false if the value specified in the registry cannot be found.")] - public void NativeMethods_TryGetLocalDbApiPath_Returns_False_If_File_Not_Found() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - var versions = new Tuple[] - { - Tuple.Create("11.0", "NonExistentFile.dll"), - }; - - NativeMethods.Registry = CreateRegistry(versions); - - // Act - bool result = NativeMethods.TryGetLocalDbApiPath(out string fileName); - - // Assert - Assert.IsFalse(result, "TryGetLocalDbApiPath() returned incorrect result."); - Assert.IsNull(fileName, "fileName is not null."); - }); - } - - [TestMethod] - [Description("Tests IRegistry.OpenSubKey() if the sub-key is not found.")] - public void NativeMethods_Registry_OpenSubkey_If_Subkey_Not_Found() - { - // Arrange - string keyName = Guid.NewGuid().ToString(); - - // Act - var result = NativeMethods.Registry.OpenSubKey(keyName); - - try - { - // Assert - Assert.IsNull(result, "OpenSubKey() did not return null."); - } - finally - { - if (result != null) - { - result.Dispose(); - } - } - } - - [TestMethod] - [Description("Tests IRegistryKey.OpenSubKey() if the sub-key is not found.")] - public void NativeMethods_RegistryKey_OpenSubkey_If_Subkey_Not_Found() - { - // Arrange - using (var key = NativeMethods.Registry.OpenSubKey(@"SOFTWARE\Microsoft")) - { - string keyName = Guid.NewGuid().ToString(); - - // Act - var result = key.OpenSubKey(keyName); - - try - { - // Assert - Assert.IsNull(result, "OpenSubKey() did not return null."); - } - finally - { - if (result != null) - { - result.Dispose(); - } - } - } - } - - [TestMethod] - [Description("Tests IRegistryKey.GetValue() if the value is not found.")] - public void NativeMethods_RegistryKey_GetValue_If_Value_Not_Found() - { - // Arrange - using (var key = NativeMethods.Registry.OpenSubKey(@"SOFTWARE\Microsoft")) - { - string name = Guid.NewGuid().ToString(); - - // Act - string result = key.GetValue(name); - - // Assert - Assert.IsNull(result, "GetValue() did not return null."); - } - } - - /// - /// Asserts that the specified delegate returns . - /// - /// The delegate to invoke to assert on the return value of. - private static void AssertIsNotInstalledResult(Func func) - { - int actual = func(); - Assert.AreEqual(SqlLocalDbErrors.NotInstalled, actual, "Method did not return {0:X}. Value: {1:X}", SqlLocalDbErrors.NotInstalled, actual); - } - - /// - /// Creates a mock set up to contain the - /// specified SQL LocalDB Instance API versions and the paths to their DLLs. - /// - /// An optional collection of version-path values to set up the registry for. - /// - /// A mock implementation of set up as specified. - /// - private static NativeMethods.IRegistry CreateRegistry(IEnumerable> collection = null) - { - IList> versionKeys = new List>(); - - if (collection != null) - { - foreach (var version in collection) - { - Mock versionKey = new Mock(); - - versionKey - .Setup((p) => p.GetValue("InstanceAPIPath")) - .Returns(version.Item2); - - versionKeys.Add(Tuple.Create(version.Item1, versionKey.Object)); - } - } - - Mock sqlLocalDbKey = new Mock(); - - sqlLocalDbKey.Setup((p) => p.GetSubKeyNames()) - .Returns(versionKeys.Select((p) => p.Item1).ToArray()); - - foreach (var version in versionKeys) - { - sqlLocalDbKey - .Setup((p) => p.OpenSubKey(version.Item1)) - .Returns(version.Item2); - } - - Mock mockRegistry = new Mock(); - - if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess) - { - mockRegistry - .Setup((p) => p.OpenSubKey(@"SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server Local DB\Installed Versions")) - .Returns(sqlLocalDbKey.Object); - } - else - { - mockRegistry - .Setup((p) => p.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server Local DB\Installed Versions")) - .Returns(sqlLocalDbKey.Object); - } - - return mockRegistry.Object; - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/Properties/AssemblyInfo.cs b/tests/SqlLocalDb.UnitTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 67a831bf..00000000 --- a/tests/SqlLocalDb.UnitTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// AssemblyInfo.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System; - -[assembly: CLSCompliant(false)] diff --git a/tests/SqlLocalDb.UnitTests/SRHelperTests.cs b/tests/SqlLocalDb.UnitTests/SRHelperTests.cs deleted file mode 100644 index 64958644..00000000 --- a/tests/SqlLocalDb.UnitTests/SRHelperTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SRHelperTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SRHelperTests - { - /// - /// Initializes a new instance of the class. - /// - public SRHelperTests() - { - } - - [TestMethod] - [Description("Tests Format() if format is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SRHelper_Format_Throws_If_Format_Is_Null() - { - // Arrange - string format = null; - object[] args = new object[0]; - - // Act and Assert - throw ErrorAssert.Throws( - () => SRHelper.Format(format, args), - "format"); - } - - [TestMethod] - [Description("Tests Format() if args is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SRHelper_Format_Throws_If_Args_Is_Null() - { - // Arrange - string format = string.Empty; - object[] args = null; - - throw ErrorAssert.Throws( - () => SRHelper.Format(format, args), - "args"); - } - - [TestMethod] - [Description("Tests Format().")] - public async Task SRHelper_Format_Formats_Parameters() - { - // Arrange - await Task.Factory.StartNew( - () => - { - // Use a non-default culture - var culture = CultureInfo.GetCultureInfo("en-GB"); - - Thread.CurrentThread.CurrentCulture = culture; - Thread.CurrentThread.CurrentUICulture = culture; - - // Use a date where the result is valid with the day and month either way around - // i.e. US format dates vs. UK format dates - DateTime value = new DateTime(2012, 2, 3, 12, 34, 56); - - // Act - string result = SRHelper.Format( - "{0}", - value); - - // Assert - Assert.AreEqual( - value.ToString(culture), - result, - "Format() returned incorrect result."); - }); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/SetInitialCatalogNameTestCases.xml b/tests/SqlLocalDb.UnitTests/SetInitialCatalogNameTestCases.xml deleted file mode 100644 index f8c2a874..00000000 --- a/tests/SqlLocalDb.UnitTests/SetInitialCatalogNameTestCases.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - data source=.;integrated security=True;MultipleActiveResultSets=True - MyFirstDatabase - - - data source=.;initial catalog=MyDatabase;integrated security=True;MultipleActiveResultSets=True - MyDatabase - - - data source=.;Initial Catalog=MyOtherDatabase;integrated security=True;MultipleActiveResultSets=True - MyOtherDatabase - - - data source=.;INITIAL CATALOG=YetAnotherDatabase;integrated security=True;MultipleActiveResultSets=True - YetAnotherDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;integrated security=True;MultipleActiveResultSets=True" - MyFirstEntityDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=MyEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - MyEntityDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;Provider Connection String="data source=.;initial catalog=MyOtherEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - MyOtherEntityDatabase - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;PROVIDER CONNECTION STRING="data source=.;initial catalog=YetAnotherEntityDatabase;integrated security=True;MultipleActiveResultSets=True" - YetAnotherEntityDatabase - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SetPhysicalFileNameTestCases.xml b/tests/SqlLocalDb.UnitTests/SetPhysicalFileNameTestCases.xml deleted file mode 100644 index ca5609c0..00000000 --- a/tests/SqlLocalDb.UnitTests/SetPhysicalFileNameTestCases.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - data source=.;integrated security=True;MultipleActiveResultSets=True - C:\MyFirstDatabase.mdf - - - data source=.;attachdbfilename=C:\MyDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\MyDatabase.mdf - - - data source=.;AttachDBFilename=C:\MyOtherDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\MyOtherDatabase.mdf - - - data source=.;ATTACHDBFILENAME=C:\YetAnotherDatabase.mdf;integrated security=True;MultipleActiveResultSets=True - C:\YetAnotherDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;integrated security=True;MultipleActiveResultSets=True" - C:\MyFirstEntityDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;provider connection string="data source=.;attachdbfilename=C:\MyEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\MyEntityDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;Provider Connection String="data source=.;AttachDBFilename=C:\MyOtherEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\MyOtherEntityDatabase.mdf - - - metadata=res://*/MyData.csdl|res://*/MyData.ssdl|res://*/MyData.msl;provider=System.Data.SqlClient;PROVIDER CONNECTION STRING="data source=.;ATTACHDBFILENAME=C:\YetAnotherEntityDatabase.mdf;integrated security=True;MultipleActiveResultSets=True" - C:\YetAnotherEntityDatabase.mdf - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.AutomaticallyDeleteInstanceFiles.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.AutomaticallyDeleteInstanceFiles.config deleted file mode 100644 index 8c4d5bf7..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.AutomaticallyDeleteInstanceFiles.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2012.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2012.config deleted file mode 100644 index 4b3d93c8..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2012.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2014.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2014.config deleted file mode 100644 index c1b2b84b..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2014.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2016.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2016.config deleted file mode 100644 index 80a1e61f..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.DefaultInstanceName.2016.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.InvalidOverrideVersion.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.InvalidOverrideVersion.config deleted file mode 100644 index ba8d7664..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.InvalidOverrideVersion.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.PropertiesOverridden.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.PropertiesOverridden.config deleted file mode 100644 index 147289f3..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.PropertiesOverridden.config +++ /dev/null @@ -1,13 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.cs deleted file mode 100644 index b1369d7f..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbApiTests.cs +++ /dev/null @@ -1,1712 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbApiTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Maintainability", - "CA1506:AvoidExcessiveClassCoupling", - Justification = "Coupling is mostly related to System.IO.* members used to validate instance creation/deletion.")] - public class SqlLocalDbApiTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbApiTests() - { - } - - [TestMethod] - [Description("Tests the default value of the AutomaticallyDeleteInstanceFiles property.")] - public void SqlLocalDbApi_AutomaticallyDeleteInstanceFiles_Is_False_By_Default() - { - // Act - bool value = SqlLocalDbApi.AutomaticallyDeleteInstanceFiles; - - // Assert - Assert.IsFalse(value, "The default value of SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is incorrect."); - } - - [TestMethod] - [Description("Tests the default value of the AutomaticallyDeleteInstanceFiles property if overridden in the configuration file.")] - public void SqlLocalDbApi_AutomaticallyDeleteInstanceFiles_Can_Be_Overridden_From_Configuration_File() - { - // Act - Helpers.InvokeInNewAppDomain( - () => - { - bool value = SqlLocalDbApi.AutomaticallyDeleteInstanceFiles; - - // Assert - Assert.IsTrue(value, "The default value of SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is incorrect."); - }, - configurationFile: "SqlLocalDbApiTests.AutomaticallyDeleteInstanceFiles.config"); - } - - [TestMethod] - [Description("Tests that the AutomaticallyDeleteInstanceFiles property can be set.")] - public void SqlLocalDbApi_AutomaticallyDeleteInstanceFiles_Can_Be_Set() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = true; - bool value = SqlLocalDbApi.AutomaticallyDeleteInstanceFiles; - - // Assert - Assert.IsTrue(value, "The value of SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is incorrect."); - }); - } - - [TestMethod] - [Description("Tests CreateInstance(string) if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_CreateInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = null; - - throw ErrorAssert.Throws( - () => SqlLocalDbApi.CreateInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests CreateInstance(string, string) if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_CreateInstance_With_Version_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - string version = string.Empty; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.CreateInstance(instanceName, version), - "instanceName"); - } - - [TestMethod] - [Description("Tests CreateInstance(string, string) if version is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_CreateInstance_Throws_If_Version_Is_Null() - { - // Arrange - string instanceName = string.Empty; - string version = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.CreateInstance(instanceName, version), - "version"); - } - - [TestMethod] - [Description("Tests CreateInstance(string) if the instance does not already exist.")] - public void SqlLocalDbApi_CreateInstance_If_Instance_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - // Act - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - // Assert - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.AreEqual(instanceName, info.Name, "ISqlLocalDbInstanceInfo.Name is incorrect."); - Assert.IsTrue(info.Exists, "The LocalDB instance has not been created."); - Assert.IsFalse(info.IsAutomatic, "ISqlLocalDbInstanceInfo.IsAutomatic is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - Assert.IsFalse(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests CreateInstance(string) if the instance cannot be created.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_CreateInstance_If_Instance_Creation_Fails() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - // Use an invalid instance name - string instanceName = string.Empty; - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.CreateInstance(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidParameter, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests DeleteInstance() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_DeleteInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.DeleteInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests DeleteInstance() if instanceName does not exist.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_DeleteInstance_Throws_If_InstanceName_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.DeleteInstance(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.UnknownInstance, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests DeleteInstance(string).")] - public void SqlLocalDbApi_DeleteInstance_Deletes_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - // Act - SqlLocalDbApi.DeleteInstance(instanceName); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.AreEqual(instanceName, info.Name, "ISqlLocalDbInstanceInfo.Name is incorrect.."); - Assert.IsFalse(info.Exists, "The LocalDB instance has not been deleted."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsTrue(Directory.Exists(instancePath), "The instance folder was deleted."); - Assert.AreNotEqual(0, Directory.GetFiles(instancePath).Length, "The instance files were deleted."); - } - - [TestMethod] - [Description("Tests DeleteInstance(string) deletes the instance folder if AutomaticallyDeleteInstanceFiles is true.")] - public void SqlLocalDbApi_DeleteInstance_Deletes_Folder_If_AutomaticallyDeleteInstanceFiles_Is_True() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - Helpers.InvokeInNewAppDomain( - () => - { - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - // Act - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = true; - SqlLocalDbApi.DeleteInstance(instanceName); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.AreEqual(instanceName, info.Name, "ISqlLocalDbInstanceInfo.Name is incorrect.."); - Assert.IsFalse(info.Exists, "The LocalDB instance has not been deleted."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsFalse(Directory.Exists(instancePath), "The instance folder was not deleted."); - }); - } - - [TestMethod] - [Description("Tests DeleteInstance(string, bool).")] - public void SqlLocalDbApi_DeleteInstance_Deletes_Instance_Folder() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - // Act - SqlLocalDbApi.DeleteInstance(instanceName, deleteFiles: true); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.AreEqual(instanceName, info.Name, "ISqlLocalDbInstanceInfo.Name is incorrect.."); - Assert.IsFalse(info.Exists, "The LocalDB instance has not been deleted."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsFalse(Directory.Exists(instancePath), "The instance folder was not deleted."); - } - - [TestMethod] - [Description("Tests DeleteUserInstances() deletes only user instances and does not delete the instance folders.")] - public void SqlLocalDbApi_DeleteUserInstances_Deletes_Only_User_Instances() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - IList namesBefore = SqlLocalDbApi.GetInstanceNames(); - IList versionsBefore = SqlLocalDbApi.Versions; - - // Act - int result = SqlLocalDbApi.DeleteUserInstances(); - - // Assert - IList namesAfter = SqlLocalDbApi.GetInstanceNames(); - IList versionsAfter = SqlLocalDbApi.Versions; - - // The default instances have a name which is the version prefixed with 'v' when - // using the SQL LocalDB 2012 native API. With the SQL LocalDB 2014 native API - // (and presumably later versions), the default instance is named 'MSSQLLocalDB'. - string[] expectedNames = namesAfter - .Where((p) => p.StartsWith("v", StringComparison.Ordinal) || - string.Equals(p, "MSSQLLocalDB", StringComparison.Ordinal) || - string.Equals(p, @".\MSSQLLocalDB", StringComparison.Ordinal)) - .ToArray(); - - CollectionAssert.AreEquivalent(expectedNames, namesAfter.ToArray(), "One or more instance was not deleted."); - CollectionAssert.AreEquivalent(versionsBefore.ToArray(), versionsAfter.ToArray(), "One or more default instances was deleted."); - Assert.AreEqual(namesBefore.Count - namesAfter.Count, result, "DeleteUserInstances() returned incorrect result."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsTrue(Directory.Exists(instancePath), "The instance folder was deleted."); - Assert.AreNotEqual(0, Directory.GetFiles(instancePath).Length, "The instance files were deleted."); - } - finally - { - // Try to delete the instance we created if the test fails - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: true); - } - } - - [TestMethod] - [Description("Tests DeleteUserInstances() deletes only user instances and deletes the instance folders if AutomaticallyDeleteInstanceFiles is true.")] - public void SqlLocalDbApi_DeleteUserInstances_Deletes_Only_User_Instances_If_AutomaticallyDeleteInstanceFiles_Is_True() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - Helpers.InvokeInNewAppDomain( - () => - { - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - IList namesBefore = SqlLocalDbApi.GetInstanceNames(); - IList versionsBefore = SqlLocalDbApi.Versions; - - // Act - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = true; - int result = SqlLocalDbApi.DeleteUserInstances(); - - // Assert - IList namesAfter = SqlLocalDbApi.GetInstanceNames(); - IList versionsAfter = SqlLocalDbApi.Versions; - - // The default instances have a name which is the version prefixed with 'v' when - // using the SQL LocalDB 2012 native API. With the SQL LocalDB 2014 native API - // (and presumably later versions), the default instance is named 'MSSQLLocalDB'. - string[] expectedNames = namesAfter - .Where((p) => p.StartsWith("v", StringComparison.Ordinal) || - string.Equals(p, "MSSQLLocalDB", StringComparison.Ordinal) || - string.Equals(p, @".\MSSQLLocalDB", StringComparison.Ordinal)) - .ToArray(); - - CollectionAssert.AreEquivalent(expectedNames, namesAfter.ToArray(), "One or more instance was not deleted."); - CollectionAssert.AreEquivalent(versionsBefore.ToArray(), versionsAfter.ToArray(), "One or more default instances was deleted."); - Assert.AreEqual(namesBefore.Count - namesAfter.Count, result, "DeleteUserInstances() returned incorrect result."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsFalse(Directory.Exists(instancePath), "The instance folder was not deleted."); - } - finally - { - // Try to delete the instance we created if the test fails - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: true); - } - }); - } - - [TestMethod] - [Description("Tests DeleteUserInstances() deletes only user instances and deletes the instance folders.")] - public void SqlLocalDbApi_DeleteUserInstances_Deletes_Only_User_Instances_And_Deletes_Instance_Folder() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - IList namesBefore = SqlLocalDbApi.GetInstanceNames(); - IList versionsBefore = SqlLocalDbApi.Versions; - - // Act - int result = SqlLocalDbApi.DeleteUserInstances(deleteFiles: true); - - // Assert - IList namesAfter = SqlLocalDbApi.GetInstanceNames(); - IList versionsAfter = SqlLocalDbApi.Versions; - - // The default instances have a name which is the version prefixed with 'v' when - // using the SQL LocalDB 2012 native API. With the SQL LocalDB 2014 native API - // (and presumably later versions), the default instance is named 'MSSQLLocalDB'. - string[] expectedNames = namesAfter - .Where((p) => p.StartsWith("v", StringComparison.Ordinal) || - string.Equals(p, "MSSQLLocalDB", StringComparison.Ordinal) || - string.Equals(p, @".\MSSQLLocalDB", StringComparison.Ordinal)) - .ToArray(); - - CollectionAssert.AreEquivalent(expectedNames, namesAfter.ToArray(), "One or more instance was not deleted."); - CollectionAssert.AreEquivalent(versionsBefore.ToArray(), versionsAfter.ToArray(), "One or more default instances was deleted."); - Assert.AreEqual(namesBefore.Count - namesAfter.Count, result, "DeleteUserInstances() returned incorrect result."); - - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsFalse(Directory.Exists(instancePath), "The instance folder was not deleted."); - } - finally - { - // Try to delete the instance we created if the test fails - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false, deleteFiles: true); - } - } - - [TestMethod] - [Description("Tests DeleteUserInstances() does not throw an exception if an instance is in use.")] - public void SqlLocalDbApi_DeleteUserInstances_Does_Not_Throw_If_Instance_In_Use() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - bool deleteFiles = true; - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - try - { - // Act - SqlLocalDbApi.DeleteUserInstances(deleteFiles); - - // Assert - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsTrue(Directory.Exists(instancePath), "The instance folder was deleted."); - Assert.IsTrue(Directory.GetFiles(instancePath, "*.mdf").Length > 0, "The instance files were deleted."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName, deleteFiles); - } - } - - [TestMethod] - [Description("Tests GetInstanceInfo() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_GetInstanceInfo_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.GetInstanceInfo(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests GetInstanceInfo() if instanceName is invalid.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_GetInstanceInfo_Throws_If_InstanceName_Is_Invalid() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = "\\\\"; - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.GetInstanceInfo(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidInstanceName, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests GetInstanceInfo() if instanceName does not exist.")] - public void SqlLocalDbApi_GetInstanceInfo_If_InstanceName_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - // Act - ISqlLocalDbInstanceInfo result = SqlLocalDbApi.GetInstanceInfo(instanceName); - - // Assert - Assert.IsNotNull(result, "GetInstanceInfo() returned null."); - Assert.IsInstanceOfType(result, typeof(LocalDbInstanceInfo), "GetInstanceInfo() returned incorrect type."); - Assert.AreEqual(instanceName, result.Name, "ISqlLocalDbInstanceInfo.Name is incorrect."); - - Assert.IsFalse(result.ConfigurationCorrupt, "ISqlLocalDbInstanceInfo.ConfigurationCorrupt is incorrect."); - Assert.IsFalse(result.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(result.IsAutomatic, "ISqlLocalDbInstanceInfo.IsAutomatic is incorrect."); - Assert.IsFalse(result.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - Assert.IsFalse(result.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(DateTime.MinValue, result.LastStartTimeUtc, "ISqlLocalDbInstanceInfo.LastStartTimeUtc is incorrect."); - Assert.AreEqual(new Version(0, 0, 0, 0), result.LocalDbVersion, "ISqlLocalDbInstanceInfo.Version is incorrect."); - Assert.AreEqual(string.Empty, result.NamedPipe, "ISqlLocalDbInstanceInfo.NamedPipe is incorrect."); - Assert.AreEqual(string.Empty, result.OwnerSid, "ISqlLocalDbInstanceInfo.OwnerSid is incorrect."); - Assert.AreEqual(string.Empty, result.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - - [TestMethod] - [Description("Tests GetInstanceNames().")] - public void SqlLocalDbApi_GetInstanceNames_Returns_Instance_Names() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - // Act - IList result = SqlLocalDbApi.GetInstanceNames(); - - // Assert - Assert.IsNotNull(result, "GetInstanceNames() returned null."); - Assert.IsTrue(result.Count > 0, "IList.Count is less than one."); - CollectionAssert.AllItemsAreNotNull(result.ToArray(), "An SQL LocalDB instance name is null."); - CollectionAssert.AllItemsAreUnique(result.ToArray(), "A duplicate SQL LocalDb instance name was returned."); - } - - [TestMethod] - [Description("Tests that GetInstancesFolderPath() returns the correct value.")] - public void SqlLocalDbApi_GetInstancesFolderPath_Returns_Correct_Path() - { - // Arrange - string expected = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Microsoft", - "Microsoft SQL Server Local DB", - "Instances"); - - // Act - string result = SqlLocalDbApi.GetInstancesFolderPath(); - - // Assert - Assert.AreEqual(expected, result, "GetInstancesFolderPath() returned incorrect value."); - } - - [TestMethod] - [Description("Tests GetVersionInfo() if version is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_GetVersionInfo_Throws_If_Version_Is_Null() - { - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.GetVersionInfo(null), - "version"); - } - - [TestMethod] - [Description("Tests GetVersionInfo() if instanceName is invalid.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_GetVersionInfo_Throws_If_InstanceName_Is_Invalid() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string version = "\\\\"; - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.GetVersionInfo(version)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidParameter, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(string.Empty, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests GetVersionInfo().")] - public void SqlLocalDbApi_GetVersionInfo_Returns_Version_Information() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string version = SqlLocalDbApi.LatestVersion; - - // Act - ISqlLocalDbVersionInfo result = SqlLocalDbApi.GetVersionInfo(version); - - // Assert - Assert.IsNotNull(result, "GetVersionInfo() returned null."); - Assert.IsInstanceOfType(result, typeof(LocalDbVersionInfo), "GetVersionInfo() returned incorrect type."); - Assert.IsTrue(result.Exists, "ISqlLocalDbVersionInfo.Exists is incorrect."); - Assert.IsNotNull(result.Name, "ISqlLocalDbVersionInfo.Name is null."); - Assert.IsNotNull(result.Version, "ISqlLocalDbVersionInfo.Version is null."); - - StringAssert.StartsWith(result.Name, version.Split('.').FirstOrDefault(), "ISqlLocalDbVersionInfo.Name is incorrect."); - - Version versionFromString = new Version(version); - - Assert.AreEqual(versionFromString.Major, result.Version.Major, "ISqlLocalDbVersionInfo.Version.Major is incorrect."); - } - - [TestMethod] - [Description("Tests the LatestVersion property returns the correct value.")] - public void SqlLocalDbApi_LatestVersion_Returns_Latest_Version() - { - // Arrange - IList installedVersions = SqlLocalDbApi.Versions; - - if (installedVersions.Count < 2) - { - Assert.Inconclusive("Only one version of SQL LocalDB is installed on '{0}'.", Environment.MachineName); - } - - List versions = installedVersions - .Select((p) => new Version(p)) - .ToList(); - - versions.Sort(); - - string expected = versions - .Last() - .ToString(); - - // Act - string result = SqlLocalDbApi.LatestVersion; - - // Assert - Assert.AreEqual(expected, result, "SqlLocalDbApi.LatestVersion returned incorrect result."); - } - - [TestMethod] - [Description("Tests ShareInstance() if sharedInstanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_ShareInstance_Throws_If_SharedInstanceName_Is_Null() - { - // Arrange - string ownerSid = string.Empty; - string instanceName = string.Empty; - string sharedInstanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.ShareInstance(ownerSid, instanceName, sharedInstanceName), - "sharedInstanceName"); - } - - [TestMethod] - [Description("Tests ShareInstance() if ownerSid is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_ShareInstance_Throws_If_OwnerSid_Is_Null() - { - // Arrange - string ownerSid = null; - string instanceName = string.Empty; - string sharedInstanceName = string.Empty; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.ShareInstance(ownerSid, instanceName, sharedInstanceName), - "ownerSid"); - } - - [TestMethod] - [Description("Tests ShareInstance() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_ShareInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - string ownerSid = string.Empty; - string instanceName = null; - string sharedInstanceName = string.Empty; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.ShareInstance(ownerSid, instanceName, sharedInstanceName), - "instanceName"); - } - - [TestMethod] - [TestCategory(TestCategories.RequiresAdministrativePermissions)] - [Description("Tests ShareInstance().")] - public void SqlLocalDbApi_ShareInstance_Shares_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - Helpers.EnsureUserIsAdmin(); - - string instanceName = Guid.NewGuid().ToString(); - string sharedName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - try - { - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsFalse(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(string.Empty, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - - // Act - SqlLocalDbApi.ShareInstance( - instanceName, - sharedName); - - try - { - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(sharedName, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - finally - { - SqlLocalDbApi.UnshareInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests ShareInstance() if instanceName is invalid.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_ShareInstance_Throws_If_InstanceName_Is_Invalid() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - string sharedInstanceName = "\\\\"; - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.ShareInstance(instanceName, sharedInstanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidInstanceName, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests ShareInstance() if instanceName is the empty string.")] - [ExpectedException(typeof(ArgumentException))] - public void SqlLocalDbApi_ShareInstance_Throws_If_InstanceName_Is_Empty_String() - { - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.ShareInstance(string.Empty, Guid.NewGuid().ToString()), - "instanceName"); - } - - [TestMethod] - [Description("Tests StartInstance() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_StartInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.StartInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests StartInstance() if instanceName does not exist.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_StartInstance_Throws_If_InstanceName_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.StartInstance(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.UnknownInstance, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests StartInstance(string).")] - public void SqlLocalDbApi_StartInstance_Starts_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - try - { - DateTime beforeStart = DateTime.UtcNow; - - // Act - string namedPipe = SqlLocalDbApi.StartInstance(instanceName); - - // Assert - DateTime afterStart = DateTime.UtcNow; - - Assert.IsNotNull(namedPipe, "StartInstance() returned null."); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - - Assert.AreEqual(namedPipe, info.NamedPipe, "The returned named pipe is incorrect."); - Assert.IsTrue(info.IsRunning, "The LocalDB instance has not been started"); - Assert.AreEqual(DateTimeKind.Utc, info.LastStartTimeUtc.Kind, "ISqlLocalDbInstanceInfo.LastStartTimeUtc.Kind is incorrect."); - Assert.IsTrue(info.LastStartTimeUtc >= beforeStart, "ISqlLocalDbInstanceInfo.LastStartTimeUtc is too early."); - Assert.IsTrue(info.LastStartTimeUtc <= afterStart, "ISqlLocalDbInstanceInfo.LastStartTimeUtc is too late."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests the StartTracing() method.")] - public void SqlLocalDbApi_StartTracing_Does_Not_Throw() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - // Act (no Assert) - // No easy way to ensure it's initialized, so just test method doesn't throw - SqlLocalDbApi.StartTracing(); - - try - { - SqlLocalDbApi.StartTracing(); - } - finally - { - SqlLocalDbApi.StopTracing(); - } - } - - [TestMethod] - [Description("Tests StopInstance(string) if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_StopInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.StopInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests StopInstance(string, TimeSpan) if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbApi_StopInstance_With_Timeout_Throws_If_InstanceName_Is_Null() - { - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.StopInstance(null, TimeSpan.Zero), - "instanceName"); - } - - [TestMethod] - [Description("Tests StopInstance(string, TimeSpan) if timeout is less than zero.")] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void SqlLocalDbApi_StopInstance_Throws_If_Timeout_Is_Less_Than_Zero() - { - // Arrange - TimeSpan value = TimeSpan.Zero - TimeSpan.FromTicks(1); - - // Act - ArgumentOutOfRangeException error = ErrorAssert.Throws( - () => SqlLocalDbApi.StopInstance(string.Empty, value), - "timeout"); - - // Assert - Assert.AreEqual( - value, - error.ActualValue, - "ArgumentOutOfRangeException.ActualValue is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests StopInstance() if instanceName does not exist.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbApi_StopInstance_Throws_If_InstanceName_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.StopInstance(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.UnknownInstance, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests StopInstance(string).")] - public void SqlLocalDbApi_StopInstance_Stops_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - // Act - SqlLocalDbApi.StopInstance(instanceName); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - - Assert.AreEqual(string.Empty, info.NamedPipe, "ISqlLocalDbInstanceInfo.NamedPipe is incorrect."); - Assert.IsFalse(info.IsRunning, "The LocalDB instance has not been started"); - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests StopInstance(string, StopInstanceOptions, TimeSpan).")] - public void SqlLocalDbApi_StopInstance_When_All_Parameters_Specified() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - StopInstanceOptions options = StopInstanceOptions.KillProcess; - TimeSpan timeout = TimeSpan.FromSeconds(30); - - IList instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.DoesNotContain(instanceNames.ToArray(), instanceName, "The specified instance name already exists."); - - SqlLocalDbApi.CreateInstance(instanceName); - - instanceNames = SqlLocalDbApi.GetInstanceNames(); - CollectionAssert.Contains(instanceNames.ToArray(), instanceName, "The specified instance was not created."); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - // Act - SqlLocalDbApi.StopInstance(instanceName, options, timeout); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - - Assert.AreEqual(string.Empty, info.NamedPipe, "ISqlLocalDbInstanceInfo.NamedPipe is incorrect."); - Assert.IsFalse(info.IsRunning, "The LocalDB instance has not been started"); - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests the default value of the StopOptions property.")] - public void SqlLocalDbApi_StopOptions_Has_Correct_Default_Value() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - StopInstanceOptions result = SqlLocalDbApi.StopOptions; - - // Assert - Assert.AreEqual( - StopInstanceOptions.None, - result, - "SqlLocalDbApi.StopOptions is incorrect."); - }); - } - - [TestMethod] - [Description("Tests the StopOptions property.")] - public void SqlLocalDbApi_StopOptions_Can_Be_Set() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - StopInstanceOptions value = StopInstanceOptions.NoWait; - - // Act - SqlLocalDbApi.StopOptions = value; - - // Assert - Assert.AreEqual(value, StopInstanceOptions.NoWait, "SqlLocalDbApi.StopOptions is incorrect."); - }); - } - - [TestMethod] - [Description("Tests the default value of the StopTimeout property.")] - public void SqlLocalDbApi_StopTimeout_DefaultValue() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - TimeSpan result = SqlLocalDbApi.StopTimeout; - - // Assert - Assert.AreEqual( - TimeSpan.FromMinutes(1), - result, - "SqlLocalDbApi.StopTimeout is incorrect."); - }); - } - - [TestMethod] - [Description("Tests the the StopTimeout property if value is invalid.")] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void SqlLocalDbApi_StopTimeout_Throws_If_Value_Is_Invalid() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TimeSpan value = TimeSpan.FromMinutes(-6); - - // Act - ArgumentOutOfRangeException error = ErrorAssert.Throws( - () => SqlLocalDbApi.StopTimeout = value, - "value"); - - // Assert - Assert.AreEqual( - value, - error.ActualValue, - "ArgumentOutOfRangeException.ActualValue is incorrect."); - - throw error; - }); - } - - [TestMethod] - [Description("Tests the StopTimeout property.")] - public void SqlLocalDbApi_StopTimeout_Can_Be_Set() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TimeSpan value = TimeSpan.FromMilliseconds(500); - - // Act - SqlLocalDbApi.StopTimeout = value; - - // Assert - Assert.AreEqual(value, SqlLocalDbApi.StopTimeout, "SqlLocalDbApi.StopTimeout is incorrect."); - }); - } - - [TestMethod] - [Description("Tests the StopTracing() method.")] - public void SqlLocalDbApi_StopTracing_Does_Not_Throw() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - // Act (no Assert) - // No easy way to ensure it's disabled, so just test method doesn't throw - SqlLocalDbApi.StopTracing(); - SqlLocalDbApi.StopTracing(); - } - - [TestMethod] - [Description("Tests UnshareInstance() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method under test.")] - public void SqlLocalDbApi_UnshareInstance_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbApi.UnshareInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests UnshareInstance() if instanceName does not exist.")] - [ExpectedException(typeof(SqlLocalDbException))] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method under test.")] - public void SqlLocalDbApi_UnshareInstance_Throws_If_InstanceName_Does_Not_Exist() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbApi.UnshareInstance(instanceName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InstanceNotShared, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [TestCategory(TestCategories.RequiresAdministrativePermissions)] - [Description("Tests UnshareInstance().")] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method under test.")] - public void SqlLocalDbApi_UnshareInstance_Stops_Sharing_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - Helpers.EnsureUserIsAdmin(); - - string instanceName = Guid.NewGuid().ToString(); - string sharedName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - try - { - SqlLocalDbApi.ShareInstance( - instanceName, - sharedName); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(sharedName, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - - // Act - SqlLocalDbApi.UnshareInstance(instanceName); - - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsFalse(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(string.Empty, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstanceInternal(instanceName, throwIfNotFound: false); - } - } - - [TestMethod] - [Description("Tests that DeleteInstanceFiles() escapes the instanceName parameter to prevent path traversal when deleting files.")] - public void SqlLocalDbApi_DeleteInstanceFiles_Escapes_InstanceName() - { - // Arrange - Create a path to a directory somewhere on disk that is separate to LocalDB - string directoryName = Guid.NewGuid().ToString(); - string path = Path.Combine(Path.GetTempPath(), directoryName); - - // Get the drive letters associated with the SQL LocalDB instances and the temporary directory - string instancesFolderPathRoot = Directory.GetDirectoryRoot(SqlLocalDbApi.GetInstancesFolderPath()); - string tempPathRoot = Directory.GetDirectoryRoot(path); - - // Assert - We can't run this test if %TEMP% isn't on the same drive as %LOCALAPPDATA%. - if (!string.Equals(instancesFolderPathRoot, tempPathRoot, StringComparison.OrdinalIgnoreCase)) - { - Assert.Inconclusive( - "The SQL LocalDB instances folder is stored on a different drive ({0}) than the temporary directory ({1}).", - instancesFolderPathRoot, - tempPathRoot); - } - - // Get the path of the temporary directory without the drive and use it to build an instance - // name that would cause path traversal from the SQL LocalDB instances folder to the temporary folder. - string pathWithoutDrive = path.Replace(tempPathRoot, string.Empty); - - int pathSegments = path.Split(Path.DirectorySeparatorChar).Length; - - string instanceName = Path.Combine( - string.Join(Path.DirectorySeparatorChar.ToString(), Enumerable.Repeat("..", pathSegments)), - pathWithoutDrive); - - Directory.CreateDirectory(path); - - try - { - // Act - SqlLocalDbApi.DeleteInstanceFiles(instanceName); - - // Assert - Assert.IsTrue(Directory.Exists(path), "The external directory was deleted due to path traversal in the instance name."); - } - finally - { - if (Directory.Exists(path)) - { - Directory.Delete(path, true); - } - } - } - - [TestMethod] - [Description("Tests DeleteInstanceFiles() does not throw an exception if an instance's files are in use.")] - public void SqlLocalDbApi_DeleteInstanceFiles_Does_Not_Throw_If_Instance_Files_In_Use() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - bool deleteFiles = true; - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbApi.StartInstance(instanceName); - - try - { - // Act - SqlLocalDbApi.DeleteInstanceFiles(instanceName); - - // Assert - string instancePath = GetInstanceFolderPath(instanceName); - Assert.IsTrue(Directory.Exists(instancePath), "The instance folder was deleted."); - Assert.IsTrue(Directory.GetFiles(instancePath, "*.mdf").Length > 0, "The instance files were deleted."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName, deleteFiles); - } - } - - [TestMethod] - [Description("Tests that SqlLocalDbApi uses the correct default values if the configuration section is defined and the attributes are set.")] - public void SqlLocalDbApi_Uses_User_Values_If_Configuration_Section_Defined_And_Attributes_Specified() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(true, SqlLocalDbApi.AutomaticallyDeleteInstanceFiles, "SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(1036, SqlLocalDbApi.LanguageId, "SqlLocalDbApi.LanguageId is incorrect."); - Assert.AreEqual(StopInstanceOptions.KillProcess | StopInstanceOptions.NoWait, SqlLocalDbApi.StopOptions, "SqlLocalDbApi.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromSeconds(30), SqlLocalDbApi.StopTimeout, "SqlLocalDbApi.StopTimeout is incorrect."); - }, - configurationFile: "SqlLocalDbApiTests.PropertiesOverridden.config"); - } - - // This test's expected result depends on what the latest version installed is - [TestMethod] - [TestCategory(TestCategories.SqlServer2012)] - [TestCategory(TestCategories.SqlServer2014)] - [TestCategory(TestCategories.SqlServer2016)] - [Description("Tests that the DefaultInstanceName property returns the correct value.")] - public void SqlLocalDbApi_DefaultInstanceName_Returns_Correct_Value() - { - // Act - string result = SqlLocalDbApi.DefaultInstanceName; - - // Assert - if (NativeMethods.NativeApiVersion.Major == 11) - { - Assert.AreEqual("v11.0", result, "SqlLocalDbApi.DefaultInstanceName returned incorrect value."); - } - else - { - Assert.AreEqual("MSSQLLocalDB", result, "SqlLocalDbApi.DefaultInstanceName returned incorrect value."); - } - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2012)] - [Description("Tests that the DefaultInstanceName property returns the correct value for SQL LocalDB 2012.")] - public void SqlLocalDbApi_DefaultInstanceName_Returns_Correct_Value_2012() - { - // Arrange - Helpers.EnsureLocalDBVersionInstalled(11); - - Helpers.InvokeInNewAppDomain( - () => - { - // Act - string result = SqlLocalDbApi.DefaultInstanceName; - - // Assert - Assert.AreEqual("v11.0", result, "SqlLocalDbApi.DefaultInstanceName returned incorrect value."); - }, - configurationFile: "SqlLocalDbApiTests.DefaultInstanceName.2012.config"); - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2014)] - [Description("Tests that the DefaultInstanceName property returns the correct value for SQL LocalDB 2014.")] - public void SqlLocalDbApi_DefaultInstanceName_Returns_Correct_Value_2014() - { - // Arrange - Helpers.EnsureLocalDBVersionInstalled(12); - - Helpers.InvokeInNewAppDomain( - () => - { - // Act - string result = SqlLocalDbApi.DefaultInstanceName; - - // Assert - Assert.AreEqual("MSSQLLocalDB", result, "SqlLocalDbApi.DefaultInstanceName returned incorrect value."); - }, - configurationFile: "SqlLocalDbApiTests.DefaultInstanceName.2014.config"); - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2016)] - [Description("Tests that the DefaultInstanceName property returns the correct value for SQL LocalDB 2016.")] - public void SqlLocalDbApi_DefaultInstanceName_Returns_Correct_Value_2016() - { - // Arrange - Helpers.EnsureLocalDBVersionInstalled(13); - - Helpers.InvokeInNewAppDomain( - () => - { - // Act - string result = SqlLocalDbApi.DefaultInstanceName; - - // Assert - Assert.AreEqual("MSSQLLocalDB", result, "SqlLocalDbApi.DefaultInstanceName returned incorrect value."); - }, - configurationFile: "SqlLocalDbApiTests.DefaultInstanceName.2016.config"); - } - - [TestMethod] - [Description("Tests that the LanguageId property returns the correct default value.")] - public void SqlLocalDbApi_LanguageId_Returns_Correct_Value() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - int result = SqlLocalDbApi.LanguageId; - - // Assert - Assert.AreEqual(0, result, "SqlLocalDbApi.LanguageId returned incorrect value."); - }); - } - - [TestMethod] - [Description("Tests that the LanguageId property is used to format error messages.")] - public void SqlLocalDbApi_LanguageId_Used_By_GetLocalDbError_To_Format_Error_Messages() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Arrange - int hr = SqlLocalDbErrors.AdminRightsRequired; - int traceEventId = 0; - - int lcid = GetLcidForCulture("en-US"); - SqlLocalDbApi.LanguageId = lcid; - - // Act - Exception result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("Administrator privileges are required in order to execute this operation.\r\n", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - - // Arrange - lcid = GetLcidForCulture("es-ES"); - SqlLocalDbApi.LanguageId = lcid; - - // Act - result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("Se requieren privilegios de administrador para ejecutar esta operación.\r\n", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - - // Arrange - lcid = GetLcidForCulture("fr-FR"); - SqlLocalDbApi.LanguageId = lcid; - - // Act - result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("Des privilèges d'administrateur sont requis pour exécuter cette opération.\r\n", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - - // Arrange - lcid = GetLcidForCulture("de-DE"); - SqlLocalDbApi.LanguageId = lcid; - - // Act - result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("Zum Ausführen dieses Vorgangs sind Administratorprivilegien erforderlich.\r\n", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - }); - } - - [TestMethod] - [Description("Tests that GetLocalDbError() does not mask the original error if the LanguageId property is not a recognized LCID.")] - public void SqlLocalDbApi_GetLocalDbError_Does_Not_Mask_Original_Error_If_LanguageId_Is_Not_Recognized() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Arrange - int hr = SqlLocalDbErrors.AdminRightsRequired; - int traceEventId = 0; - - int lcid = GetLcidForCulture("en-GB"); // N.B. A new culture must be used if a future version supports en-GB. - SqlLocalDbApi.LanguageId = lcid; - - // Act - Exception result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("An error occurred with SQL Server LocalDB. HRESULT = 89C5011E", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - }); - } - - [TestMethod] - [Description("Tests that GetLocalDbError() returns an appropriate exception if the native function call to get the exception message fails.")] - public void SqlLocalDbApi_GetLocalDbError_Returns_Different_HResult_If_Native_Function_Fails() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Arrange - int hr = SqlLocalDbErrors.AdminRightsRequired; - int traceEventId = 0; - - int lcid = int.MaxValue; // This value of LCID causes an internal error in SQL LocalDB (at the time of writing, it might not always) - SqlLocalDbApi.LanguageId = lcid; - - // Act - Exception result = SqlLocalDbApi.GetLocalDbError(hr, traceEventId); - - // Assert - Assert.AreEqual("An error occurred with SQL Server LocalDB. HRESULT = 89C50108", result.Message, "The exception message was not correct for LCID {0}.", lcid); - Assert.AreNotEqual(hr, result.HResult, "The HResult returned in the exception is incorrect."); - }); - } - - [TestMethod] - [Description("Tests that the latest version is loaded if the configured override version is invalid.")] - public void SqlLocalDbApi_Loads_Latest_Version_If_Configured_Override_Version_Is_Invalid() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Act - string result = SqlLocalDbApi.LatestVersion; - - // Assert - Assert.AreEqual(new Version(result), NativeMethods.NativeApiVersion, "The SQL LocalDB Instance API was not loaded with an invalid override version."); - }, - configurationFile: "SqlLocalDbApiTests.InvalidOverrideVersion.config"); - } - - [TestMethod] - [Description("Tests IsLocalDBInstalled() returns false if SQL LocalDB is not installed on the local machine.")] - public void SqlLocalDbApi_IsLocalDBInstalled_Returns_False_If_Sql_LocalDb_Is_Not_Installed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - SetupAsIfLocalDbNotInstalled(); - - // Act - bool result = SqlLocalDbApi.IsLocalDBInstalled(); - - // Assert - Assert.IsFalse(result, "IsLocalDBInstalled() returned incorrect result."); - }); - } - - [TestMethod] - [Description("Tests DefaultInstanceName returns an empty string if SQL LocalDB is not installed on the local machine.")] - public void SqlLocalDbApi_DefaultInstanceName_Returns_Empty_String_If_Sql_LocalDb_Is_Not_Installed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - SetupAsIfLocalDbNotInstalled(); - - // Act - string result = SqlLocalDbApi.DefaultInstanceName; - - // Assert - Assert.AreEqual(string.Empty, result, "DefaultInstanceName returned incorrect value."); - }); - } - - [TestMethod] - [Description("Tests LatestVersion throws if SQL LocalDB is not installed on the local machine.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbApi_LatestVersion_Throws_If_Sql_LocalDb_Is_Not_Installed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - SetupAsIfLocalDbNotInstalled(); - - // Act and Assert - throw ErrorAssert.Throws(() => SqlLocalDbApi.LatestVersion); - }); - } - - [TestMethod] - [Description("Tests Versions throws if SQL LocalDB is not installed on the local machine.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbApi_Versions_Throws_If_Sql_LocalDb_Is_Not_Installed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - SetupAsIfLocalDbNotInstalled(); - - // Act and Assert - throw ErrorAssert.Throws(() => SqlLocalDbApi.Versions); - }); - } - - /// - /// Sets up the as if SQL LocalDB is not installed. - /// - private static void SetupAsIfLocalDbNotInstalled() - { - NativeMethods.Registry = Moq.Mock.Of(); - } - - /// - /// Returns the full path of the folder for the specified SQL LocalDB instance. - /// - /// The name of the SQL LocalDB instance. - /// - /// The full path of the folder for the specified SQL LocalDB instance. - /// - private static string GetInstanceFolderPath(string instanceName) - { - return Path.Combine(SqlLocalDbApi.GetInstancesFolderPath(), instanceName); - } - - /// - /// Returns the LCID to use for the specified culture. - /// - /// The name of the culture to get the LCID for. - /// - /// The LCID for the culture specified by . - /// - private static int GetLcidForCulture(string name) - { - return CultureInfo.GetCultureInfo(name).LCID; - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecified.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecified.config deleted file mode 100644 index 147289f3..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecified.config +++ /dev/null @@ -1,13 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecifiedWithLegacySettings.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecifiedWithLegacySettings.config deleted file mode 100644 index 488cf41f..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedAndSpecifiedWithLegacySettings.config +++ /dev/null @@ -1,17 +0,0 @@ - - - -
- - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecified.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecified.config deleted file mode 100644 index fe805130..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecified.config +++ /dev/null @@ -1,7 +0,0 @@ - - - -
- - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecifiedWithLegacySettings.config b/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecifiedWithLegacySettings.config deleted file mode 100644 index ffe7e09d..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.DefinedButNotSpecifiedWithLegacySettings.config +++ /dev/null @@ -1,11 +0,0 @@ - - - -
- - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.cs b/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.cs deleted file mode 100644 index a7cb7e26..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbConfigTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbConfigTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SqlLocalDbConfigTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbConfigTests() - { - } - - [TestMethod] - [Description("Tests that SqlLocalDbConfig uses the correct default values if the configuration section is not defined.")] - public void SqlLocalDbConfig_Uses_Defaults_If_Not_Defined() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(false, SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(0, SqlLocalDbConfig.LanguageId, "SqlLocalDbConfig.LanguageId is incorrect."); - Assert.AreEqual(string.Empty, SqlLocalDbConfig.NativeApiOverrideVersionString, "SqlLocalDbConfig.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.None, SqlLocalDbConfig.StopOptions, "SqlLocalDbConfig.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromMinutes(1), SqlLocalDbConfig.StopTimeout, "SqlLocalDbConfig.StopTimeout is incorrect."); - }, - configurationFile: "Empty.config"); - } - - [TestMethod] - [Description("Tests that SqlLocalDbConfig uses the correct default values if the configuration section is defined but no attributes are set.")] - public void SqlLocalDbConfig_Uses_Defaults_If_Defined_But_No_Attributes_Specified() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(false, SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(0, SqlLocalDbConfig.LanguageId, "SqlLocalDbConfig.LanguageId is incorrect."); - Assert.AreEqual(string.Empty, SqlLocalDbConfig.NativeApiOverrideVersionString, "SqlLocalDbConfig.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.None, SqlLocalDbConfig.StopOptions, "SqlLocalDbConfig.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromMinutes(1), SqlLocalDbConfig.StopTimeout, "SqlLocalDbConfig.StopTimeout is incorrect."); - }, - configurationFile: "SqlLocalDbConfigTests.DefinedButNotSpecified.config"); - } - - [TestMethod] - [Description("Tests that SqlLocalDbConfig uses the correct default values if the configuration section is defined and no attributes are set but the legacy appSettings override settings are present.")] - public void SqlLocalDbConfig_Uses_Defaults_If_Defined_But_No_Attributes_Specified_And_Legacy_AppSettings_Present() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(true, SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(0, SqlLocalDbConfig.LanguageId, "SqlLocalDbConfig.LanguageId is incorrect."); - Assert.AreEqual("11.0", SqlLocalDbConfig.NativeApiOverrideVersionString, "SqlLocalDbConfig.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.None, SqlLocalDbConfig.StopOptions, "SqlLocalDbConfig.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromMinutes(1), SqlLocalDbConfig.StopTimeout, "SqlLocalDbConfig.StopTimeout is incorrect."); - }, - configurationFile: "SqlLocalDbConfigTests.DefinedButNotSpecifiedWithLegacySettings.config"); - } - - [TestMethod] - [Description("Tests that SqlLocalDbConfig uses the correct values if the configuration section is defined and the attributes are set.")] - public void SqlLocalDbConfig_Uses_User_Values_If_Defined_And_Attributes_Specified() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(true, SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(1036, SqlLocalDbConfig.LanguageId, "SqlLocalDbConfig.LanguageId is incorrect."); - Assert.AreEqual("11.0", SqlLocalDbConfig.NativeApiOverrideVersionString, "SqlLocalDbConfig.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.KillProcess | StopInstanceOptions.NoWait, SqlLocalDbConfig.StopOptions, "SqlLocalDbConfig.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromSeconds(30), SqlLocalDbConfig.StopTimeout, "SqlLocalDbConfig.StopTimeout is incorrect."); - }, - configurationFile: "SqlLocalDbConfigTests.DefinedAndSpecified.config"); - } - - [TestMethod] - [Description("Tests that SqlLocalDbConfig uses the correct default values if the configuration section is defined and the attributes are set but the legacy appSettings override settings are present.")] - public void SqlLocalDbConfig_Uses_User_Values_If_Defined_And_Attributes_Specified_With_Legacy_AppSettings_Present() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Assert - Assert.AreEqual(false, SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles, "SqlLocalDbConfig.AutomaticallyDeleteInstanceFiles is incorrect."); - Assert.AreEqual(1036, SqlLocalDbConfig.LanguageId, "SqlLocalDbConfig.LanguageId is incorrect."); - Assert.AreEqual("12.0", SqlLocalDbConfig.NativeApiOverrideVersionString, "SqlLocalDbConfig.NativeApiOverrideVersion is incorrect."); - Assert.AreEqual(StopInstanceOptions.KillProcess | StopInstanceOptions.NoWait, SqlLocalDbConfig.StopOptions, "SqlLocalDbConfig.StopOptions is incorrect."); - Assert.AreEqual(TimeSpan.FromSeconds(30), SqlLocalDbConfig.StopTimeout, "SqlLocalDbConfig.StopTimeout is incorrect."); - }, - configurationFile: "SqlLocalDbConfigTests.DefinedAndSpecifiedWithLegacySettings.config"); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbExceptionTests.cs b/tests/SqlLocalDb.UnitTests/SqlLocalDbExceptionTests.cs deleted file mode 100644 index 9e6ea342..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbExceptionTests.cs +++ /dev/null @@ -1,183 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbExceptionTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SqlLocalDbExceptionTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbExceptionTests() - { - } - - [TestMethod] - [Description("Tests .ctor().")] - public void SqlLocalDbException_Constructor_Default_Sets_Properties() - { - // Act - SqlLocalDbException target = new SqlLocalDbException(); - - // Assert - Assert.AreEqual(-2147467259, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreEqual(null, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(SR.SqlLocalDbException_DefaultMessage, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(string).")] - public void SqlLocalDbException_Constructor_With_Message_Sets_Properties() - { - // Arrange - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message); - - // Assert - Assert.AreEqual(-2147467259, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreEqual(null, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(string, Exception).")] - public void SqlLocalDbException_Constructor_With_Message_And_InnerException_Sets_Properties() - { - // Arrange - InvalidOperationException innerException = new InvalidOperationException(); - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message, innerException); - - // Assert - Assert.AreEqual(-2147467259, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreSame(innerException, target.InnerException, "The InnerException property is incorrect."); - Assert.AreEqual(null, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(string, int).")] - public void SqlLocalDbException_Constructor_With_Message_And_ErrorCode_Sets_Properties() - { - // Arrange - const int ErrorCode = 337519; - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message, ErrorCode); - - // Assert - Assert.AreEqual(ErrorCode, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreEqual(null, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(string, int, string).")] - public void SqlLocalDbException_Constructor_With_Message_ErrorCode_And_InstanceName_Sets_Properties() - { - // Arrange - const int ErrorCode = 337519; - string instanceName = Guid.NewGuid().ToString(); - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message, ErrorCode, instanceName); - - // Assert - Assert.AreEqual(ErrorCode, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreEqual(instanceName, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(string, int, string, Exception).")] - public void SqlLocalDbException_Constructor_With_Message_ErrorCode_InstanceName_And_InnerException_Sets_Properties() - { - // Arrange - InvalidOperationException innerException = new InvalidOperationException(); - const int ErrorCode = 337519; - string instanceName = Guid.NewGuid().ToString(); - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message, ErrorCode, instanceName, innerException); - - // Assert - Assert.AreEqual(ErrorCode, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreSame(innerException, target.InnerException, "The InnerException property is incorrect."); - Assert.AreEqual(instanceName, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(SerializationInfo, StreamingContext).")] - public void SqlLocalDbException_Constructor_For_Serialization_Can_Be_Serialized() - { - // Arrange - InvalidOperationException innerException = new InvalidOperationException(); - const int ErrorCode = 337519; - string instanceName = Guid.NewGuid().ToString(); - string message = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbException target = new SqlLocalDbException(message, ErrorCode, instanceName, innerException); - - BinaryFormatter formatter = new BinaryFormatter(); - - SqlLocalDbException deserialized; - - using (MemoryStream stream = new MemoryStream()) - { - formatter.Serialize(stream, target); - stream.Seek(0L, SeekOrigin.Begin); - deserialized = formatter.Deserialize(stream) as SqlLocalDbException; - } - - // Assert - Assert.IsNotNull(deserialized, "The exception was not deserialized."); - Assert.AreEqual(deserialized.ErrorCode, target.ErrorCode, "The ErrorCode property is incorrect."); - Assert.AreEqual(deserialized.InstanceName, target.InstanceName, "The InstanceName property is incorrect."); - Assert.AreEqual(deserialized.Message, target.Message, "The Message property is incorrect."); - } - - [TestMethod] - [Description("Tests GetObjectData() if info is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbException_GetObjectData_Throws_If_Info_Is_Null() - { - // Arrange - SqlLocalDbException target = new SqlLocalDbException(); - - SerializationInfo info = null; - StreamingContext context = new StreamingContext(); - - // Act and Assert - throw ErrorAssert.Throws( - () => target.GetObjectData(info, context), - "info"); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbInstanceTests.cs b/tests/SqlLocalDb.UnitTests/SqlLocalDbInstanceTests.cs deleted file mode 100644 index ab91b892..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbInstanceTests.cs +++ /dev/null @@ -1,805 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbInstanceTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Data.SqlClient; -using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SqlLocalDbInstanceTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbInstanceTests() - { - } - - [TestMethod] - [Description("Tests .ctor() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbInstance_Constructor_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => new SqlLocalDbInstance(instanceName), - "instanceName"); - } - - [TestMethod] - [Description("Tests .ctor() if instanceName does not exist.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbInstance_Constructor_Throws_If_InstanceName_Does_Not_Exist() - { - // Arrange - string instanceName = Guid.NewGuid().ToString(); - - // Act and Assert - throw ErrorAssert.Throws( - () => new SqlLocalDbInstance(instanceName)); - } - - [TestMethod] - [Description("Tests .ctor() if instanceName causes the specified ISqlLocalDbApi to return null.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbInstance_Constructor_Throws_If_LocalDb_Returns_Null() - { - // Arrange - string instanceName = Guid.NewGuid().ToString(); - ISqlLocalDbApi localDB = Mock.Of(); - - // Act and Assert - throw ErrorAssert.Throws( - () => new SqlLocalDbInstance(instanceName, localDB)); - } - - [TestMethod] - [Description("Tests .ctor() if the instance is not started.")] - public void SqlLocalDbInstance_Constructor_Throws_If_Instance_Not_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - // Act - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - // Assert - Assert.IsFalse(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreEqual(instanceName, target.Name, "SqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(string.Empty, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests .ctor() if the instance is started.")] - public void SqlLocalDbInstance_Constructor_If_Instance_Is_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - try - { - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - string namedPipe = SqlLocalDbApi.StartInstance(instanceName); - - // Act - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - // Assert - Assert.IsTrue(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreEqual(instanceName, target.Name, "SqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(namedPipe, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests CreateConnection() if the instance is started.")] - public void SqlLocalDbInstance_CreateConnection_If_Instance_Is_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - try - { - // Act - SqlConnection result = target.CreateConnection(); - - try - { - // Assert - Assert.IsNotNull(result, "CreateConnection() returned null."); - Assert.AreEqual(ConnectionState.Closed, result.State, "SqlConnection.State is incorrect."); - StringAssert.Contains(result.ConnectionString, target.NamedPipe, "SqlConnection.ConnectionString is incorrect."); - } - finally - { - if (result != null) - { - result.Dispose(); - } - } - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests CreateConnection() if the instance is started and the CreateConnectionStringBuilder() returns null.")] - public void SqlLocalDbInstance_CreateConnection_If_Instance_Is_Started_And_Returned_ConnectionStringBuilder_Is_Null() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - var mock = new Mock(instanceName) - { - CallBase = true, - }; - - mock.Setup((p) => p.CreateConnectionStringBuilder()) - .Returns(null as SqlConnectionStringBuilder); - - SqlLocalDbInstance target = mock.Object; - target.Start(); - - try - { - // Act - SqlConnection result = target.CreateConnection(); - - try - { - // Assert - Assert.IsNotNull(result, "CreateConnection() returned null."); - Assert.AreEqual(ConnectionState.Closed, result.State, "SqlConnection.State is incorrect."); - Assert.AreEqual(string.Empty, result.ConnectionString, "SqlConnection.ConnectionString is incorrect."); - } - finally - { - if (result != null) - { - result.Dispose(); - } - } - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests CreateConnectionStringBuilder() if the instance is started.")] - public void SqlLocalDbInstance_CreateConnectionStringBuilder_If_Instance_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - try - { - // Act - SqlConnectionStringBuilder result = target.CreateConnectionStringBuilder(); - - // Assert - Assert.IsNotNull(result, "CreateConnection() returned null."); - StringAssert.Contains(result.ConnectionString, target.NamedPipe, "SqlConnection.ConnectionString is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests CreateConnection() if the instance is not started.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbInstance_CreateConnectionStringBuilder_Throws_If_Instance_Not_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - // Act and Assert - throw ErrorAssert.Throws( - () => target.CreateConnectionStringBuilder()); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Delete() if instance is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbInstance_Delete_Throws_If_Instance_Is_Null() - { - // Arrange - ISqlLocalDbInstance instance = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => SqlLocalDbInstance.Delete(instance), - "instance"); - } - - [TestMethod] - [Description("Tests Delete() if instance is invalid.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbInstance_Delete_Throws_If_Instance_Is_Invalid() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - const string InstanceName = "\\\\"; - - Mock mock = new Mock(); - mock.Setup((p) => p.Name).Returns(InstanceName); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => SqlLocalDbInstance.Delete(instance)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidInstanceName, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(InstanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests Delete().")] - public void SqlLocalDbInstance_Delete_Deletes_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - Mock mock = new Mock(); - mock.Setup((p) => p.Name).Returns(instanceName); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - SqlLocalDbInstance.Delete(instance); - - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsFalse(info.Exists, "The SQL LocalDB instance was not deleted."); - - string path = Path.Combine(SqlLocalDbApi.GetInstancesFolderPath(), instanceName); - Assert.IsTrue(Directory.Exists(path), "The instance folder was deleted."); - Assert.AreNotEqual(0, Directory.GetFiles(path).Length, "The instance files were deleted."); - } - - [TestMethod] - [Description("Tests Delete() if SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is true.")] - public void SqlLocalDbInstance_Delete_If_SqlLocalDbApi_AutomaticallyDeleteInstanceFiles_Is_True() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - Helpers.InvokeInNewAppDomain( - () => - { - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = true; - - string instanceName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - Mock mock = new Mock(); - mock.Setup((p) => p.Name).Returns(instanceName); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - - ISqlLocalDbInstance instance = mock.Object; - - // Act - SqlLocalDbInstance.Delete(instance); - - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsFalse(info.Exists, "The SQL LocalDB instance was not deleted."); - - string path = Path.Combine(SqlLocalDbApi.GetInstancesFolderPath(), instanceName); - Assert.IsFalse(Directory.Exists(path), "The instance folder was not deleted."); - }); - } - - [TestMethod] - [Description("Tests GetInstanceInfo().")] - public void SqlLocalDbInstance_GetInstanceInfo_Returns_Information_For_The_Specified_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - ISqlLocalDbInstanceInfo expected = SqlLocalDbApi.GetInstanceInfo(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - // Act - ISqlLocalDbInstanceInfo result = target.GetInstanceInfo(); - - // Assert - Assert.IsNotNull(result, "GetInstanceInfo() returned null."); - Assert.AreEqual(expected.ConfigurationCorrupt, result.ConfigurationCorrupt, "ISqlLocalDbInstanceInfo. is incorrect."); - Assert.AreEqual(expected.Exists, result.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.AreEqual(expected.IsAutomatic, result.IsAutomatic, "ISqlLocalDbInstanceInfo.IsAutomatic is incorrect."); - Assert.AreEqual(expected.IsRunning, result.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - Assert.AreEqual(expected.IsShared, result.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(expected.LastStartTimeUtc, result.LastStartTimeUtc, "ISqlLocalDbInstanceInfo.LastStartTimeUtc is incorrect."); - Assert.AreEqual(expected.LocalDbVersion, result.LocalDbVersion, "ISqlLocalDbInstanceInfo.LocalDbVersion is incorrect."); - Assert.AreEqual(expected.Name, result.Name, "ISqlLocalDbInstanceInfo.Name is incorrect."); - Assert.AreEqual(expected.NamedPipe, result.NamedPipe, "ISqlLocalDbInstanceInfo.NamedPipe is incorrect."); - Assert.AreEqual(expected.OwnerSid, result.OwnerSid, "ISqlLocalDbInstanceInfo.OwnerSid is incorrect."); - Assert.AreEqual(expected.SharedName, result.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [TestCategory(TestCategories.RequiresAdministrativePermissions)] - [Description("Tests Share() if the instance is started.")] - public void SqlLocalDbInstance_Share_If_Instance_Is_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - Helpers.EnsureUserIsAdmin(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - try - { - string sharedName = Guid.NewGuid().ToString(); - - // Act - target.Share(sharedName); - - // Assert - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(sharedName, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Share() if sharedName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbInstance_Share_Throws_If_SharedName_Is_Null() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - string sharedName = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => target.Share(sharedName), - "sharedName"); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Share() if sharedName is invalid.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbInstance_Share_Throws_If_SharedName_Is_Invalid() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - string sharedName = "\\\\"; - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => target.Share(sharedName)); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InvalidInstanceName, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Start().")] - public void SqlLocalDbInstance_Start_Starts_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - - Assert.IsFalse(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreEqual(string.Empty, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - // Act - target.Start(); - - try - { - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsRunning, "The SQL LocalDB instance was not started."); - - Assert.IsTrue(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreNotEqual(string.Empty, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Start() if an error occurs.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbInstance_Start_Throws_If_An_Error_Occurs() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - SqlLocalDbInstance target; - - try - { - target = new SqlLocalDbInstance(instanceName); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => target.Start()); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.UnknownInstance, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [Description("Tests Stop().")] - public void SqlLocalDbInstance_Stop_Stops_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsRunning, "The SQL LocalDB instance was not started."); - - Assert.IsTrue(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreNotEqual(string.Empty, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - - // Act - target.Stop(); - - try - { - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.IsFalse(target.IsRunning, "SqlLocalDbInstance.IsRunning is incorrect."); - Assert.AreEqual(string.Empty, target.NamedPipe, "SqlLocalDbInstance.NamedPipe is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Stop() if an error occurs.")] - [ExpectedException(typeof(SqlLocalDbException))] - public void SqlLocalDbInstance_Stop_Throws_If_An_Error_Occurs() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - SqlLocalDbInstance target; - - try - { - target = new SqlLocalDbInstance(instanceName); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => target.Stop()); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.UnknownInstance, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - - [TestMethod] - [TestCategory(TestCategories.RequiresAdministrativePermissions)] - [Description("Tests Unshare() if the instance is started.")] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method under test.")] - public void SqlLocalDbInstance_Unshare_If_Instance_Is_Started() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - Helpers.EnsureUserIsAdmin(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - try - { - string sharedName = Guid.NewGuid().ToString(); - target.Share(sharedName); - - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsTrue(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(sharedName, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - - // Act - target.Unshare(); - - // Assert - info = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.IsFalse(info.IsShared, "ISqlLocalDbInstanceInfo.IsShared is incorrect."); - Assert.AreEqual(string.Empty, info.SharedName, "ISqlLocalDbInstanceInfo.SharedName is incorrect."); - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests Unshare() if an error occurs.")] - [ExpectedException(typeof(SqlLocalDbException))] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method under test.")] - public void SqlLocalDbInstance_Unshare_Throws_If_An_Error_Occurs() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - string instanceName = Guid.NewGuid().ToString(); - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - SqlLocalDbInstance target = new SqlLocalDbInstance(instanceName); - target.Start(); - - try - { - // Act - SqlLocalDbException error = ErrorAssert.Throws( - () => target.Unshare()); - - // Assert - Assert.AreEqual(SqlLocalDbErrors.InstanceNotShared, error.ErrorCode, "SqlLocalDbException.ErrorCode is incorrect."); - Assert.AreEqual(instanceName, error.InstanceName, "SqlLocalDbException.InstanceName is incorrect."); - - throw error; - } - finally - { - SqlLocalDbApi.StopInstance(instanceName); - } - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/SqlLocalDbProviderTests.cs b/tests/SqlLocalDb.UnitTests/SqlLocalDbProviderTests.cs deleted file mode 100644 index 6d02a732..00000000 --- a/tests/SqlLocalDb.UnitTests/SqlLocalDbProviderTests.cs +++ /dev/null @@ -1,543 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// SqlLocalDbProviderTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class SqlLocalDbProviderTests - { - /// - /// Initializes a new instance of the class. - /// - public SqlLocalDbProviderTests() - { - } - - [TestMethod] - [Description("Tests .ctor().")] - public void SqlLocalDbProvider_Default_Constructor_Uses_Correct_LocalDB_Instance() - { - // Act - SqlLocalDbProvider target = new SqlLocalDbProvider(); - - // Assert - Assert.IsNotNull(target.LocalDB, "SqlLocalDbProvider.LocalDB is null."); - Assert.IsInstanceOfType(target.LocalDB, typeof(SqlLocalDbApiWrapper), "SqlLocalDbProvider.LocalDB is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(ISqlLocalDbApi).")] - public void SqlLocalDbProvider_Constructor_Uses_Specified_LocalDB_Instance() - { - // Arrange - ISqlLocalDbApi localDB = Mock.Of(); - - // Act - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB); - - // Assert - Assert.AreSame(localDB, target.LocalDB, "SqlLocalDbProvider.LocalDB is incorrect."); - } - - [TestMethod] - [Description("Tests .ctor(ISqlLocalDbApi) throws if localDB is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void SqlLocalDbProvider_Constructor_Throws_If_LocalDB_Is_Null() - { - // Arrange - ISqlLocalDbApi localDB = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => new SqlLocalDbProvider(localDB), - "localDB"); - } - - [TestMethod] - [Description("Tests CreateInstance().")] - public void SqlLocalDbProvider_CreateInstance_Creates_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - - // Act - SqlLocalDbInstance result = target.CreateInstance(); - - // Assert - Assert.IsNotNull(result, "CreateInstance() returned null."); - Assert.IsNotNull(result.Name, "SqlLocalDbInstance.Name is null."); - - try - { - ISqlLocalDbInstanceInfo info = result.GetInstanceInfo(); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.AreEqual( - new Version(target.Version).Major, - info.LocalDbVersion.Major, - "ISqlLocalDbInstanceInfo.LocalDbVersion is incorrect."); - - Assert.IsTrue(Guid.TryParse(result.Name, out Guid unused), "SqlLocalDbInstance.Name is not a valid GUID."); - } - finally - { - SqlLocalDbInstance.Delete(result); - } - } - - [TestMethod] - [Description("Tests CreateInstance(string).")] - public void SqlLocalDbProvider_CreateInstance_Creates_Instance_With_Specified_Name() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbInstance result = target.CreateInstance(instanceName); - - // Assert - Assert.IsNotNull(result, "CreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "SqlLocalDbInstance.Name is incorrect."); - - try - { - ISqlLocalDbInstanceInfo info = result.GetInstanceInfo(); - - Assert.IsNotNull(info, "GetInstanceInfo() returned null."); - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.AreEqual( - new Version(target.Version).Major, - info.LocalDbVersion.Major, - "ISqlLocalDbInstanceInfo.LocalDbVersion is incorrect."); - } - finally - { - SqlLocalDbInstance.Delete(result); - } - } - - [TestMethod] - [Description("Tests CreateInstance(string) if the underlying API returned no instance information.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbProvider_CreateInstance_Throws_If_No_Instance_Info_From_Api() - { - // Arrange - ISqlLocalDbApi localDB = Mock.Of(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB); - string instanceName = Guid.NewGuid().ToString(); - - // Act and Assert - throw ErrorAssert.Throws( - () => target.CreateInstance(instanceName)); - } - - [TestMethod] - [Description("Tests CreateInstance(string) if the instance already exists.")] - [ExpectedException(typeof(InvalidOperationException))] - public void SqlLocalDbProvider_CreateInstance_Throws_If_Instance_Already_Exists() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - // Act and Assert - throw ErrorAssert.Throws( - () => target.CreateInstance(instanceName)); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests CreateInstance(string) uses the latest version of SQL LocalDB if not overridden.")] - public void SqlLocalDbProvider_CreateInstance_Uses_Latest_Version_If_Not_Overridden() - { - // Arrange - string instanceName = Guid.NewGuid().ToString(); - string latestVersion = "1.2.3.4"; - - Mock mockInfo = new Mock(); - - mockInfo - .SetupSequence((p) => p.Exists) - .Returns(false) - .Returns(true); - - Mock mock = new Mock(); - - mock.Setup((p) => p.LatestVersion) - .Returns(latestVersion) - .Verifiable(); - - mock.Setup((p) => p.CreateInstance(instanceName, latestVersion)) - .Verifiable(); - - mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(mockInfo.Object) - .Verifiable(); - - ISqlLocalDbApi localDB = mock.Object; - - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB); - - // Act - target.CreateInstance(instanceName); - - // Assert - mock.Verify(); - } - - [TestMethod] - [Description("Tests CreateInstance(string) uses the specfied version of SQL LocalDB if overridden.")] - public void SqlLocalDbProvider_CreateInstance_Uses_Specified_Version_If_Overridden() - { - // Arrange - string instanceName = Guid.NewGuid().ToString(); - string latestVersion = "2.3.4.5"; - string version = "1.2.3.4"; - - Mock mockInfo = new Mock(); - - mockInfo - .SetupSequence((p) => p.Exists) - .Returns(false) - .Returns(true); - - Mock mock = new Mock(); - - mock.Setup((p) => p.LatestVersion) - .Returns(latestVersion); - - mock.Setup((p) => p.CreateInstance(instanceName, version)) - .Verifiable(); - - mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(mockInfo.Object) - .Verifiable(); - - ISqlLocalDbApi localDB = mock.Object; - - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB) - { - Version = version, - }; - - // Act - target.CreateInstance(instanceName); - - // Assert - mock.Verify(); - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2012)] - [Description("Tests CreateInstance(string) uses the specfied version of SQL LocalDB if overridden for SQL Server LocalDB 2012.")] - public void SqlLocalDbProvider_CreateInstance_Specifies_No_Version_If_Default_Instance_Name_Specified_2012() - { - // Arrange - Helpers.EnsureLocalDBVersionInstalled(11); - - string instanceName = "v11.0"; - string version = "1.2.3.4"; - - Mock mockInfo = new Mock(); - - mockInfo - .SetupSequence((p) => p.Exists) - .Returns(false) - .Returns(true); - - Mock mock = new Mock(); - - mock.Setup((p) => p.CreateInstance(instanceName, string.Empty)) - .Verifiable(); - - mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(mockInfo.Object) - .Verifiable(); - - ISqlLocalDbApi localDB = mock.Object; - - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB) - { - Version = version, - }; - - // Act - target.CreateInstance(instanceName); - - // Assert - mock.Verify(); - } - - [TestMethod] - [TestCategory(TestCategories.SqlServer2014)] - [TestCategory(TestCategories.SqlServer2016)] - [Description("Tests CreateInstance(string) uses the specfied version of SQL LocalDB if overridden for SQL Server LocalDB 2014 and 2016.")] - public void SqlLocalDbProvider_CreateInstance_Specifies_No_Version_If_Default_Instance_Name_Specified_2014_2016() - { - // Arrange - string instanceName = "MSSQLLocalDB"; - string version = "1.2.3.4"; - - Mock mockInfo = new Mock(); - - mockInfo - .SetupSequence((p) => p.Exists) - .Returns(false) - .Returns(true); - - Mock mock = new Mock(); - - mock.Setup((p) => p.CreateInstance(instanceName, string.Empty)) - .Verifiable(); - - mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(mockInfo.Object) - .Verifiable(); - - ISqlLocalDbApi localDB = mock.Object; - - SqlLocalDbProvider target = new SqlLocalDbProvider(localDB) - { - Version = version, - }; - - // Act - target.CreateInstance(instanceName); - - // Assert - mock.Verify(); - } - - [TestMethod] - [Description("Tests GetInstance(string).")] - public void SqlLocalDbProvider_GetInstance_Returns_Specified_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - // Assert - SqlLocalDbInstance result = target.GetInstance(instanceName); - - Assert.IsNotNull(result, "CreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "SqlLocalDbInstance.Name is incorrect."); - - ISqlLocalDbInstanceInfo info = result.GetInstanceInfo(); - - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.IsTrue(Guid.TryParse(result.Name, out Guid unused), "SqlLocalDbInstance.Name is not a valid GUID."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests GetInstances()")] - public void SqlLocalDbProvider_GetInstances_Returns_Installed_Instances() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - - // Act - IList result = target.GetInstances(); - - int initialCount = result.Count; - - // Assert - Assert.IsNotNull(result, "GetInstances() returned null."); - Assert.IsTrue(result.Count > 0, "No instances were returned by GetInstances()."); - CollectionAssert.AllItemsAreNotNull(result.ToList(), "GetInstances() returned a null instance."); - - string instanceName = Guid.NewGuid().ToString(); - target.CreateInstance(instanceName); - - try - { - result = target.GetInstances(); - - // Assert - Assert.IsNotNull(result, "GetInstances() returned null."); - Assert.AreEqual(initialCount + 1, result.Count, "An incorrect number instances were returned by GetInstances()."); - CollectionAssert.AllItemsAreNotNull(result.ToList(), "GetInstances() returned a null instance."); - Assert.IsTrue(result.Where((p) => p.Name == instanceName).Any(), "The new instance was not returned in the created set of instances."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - - result = target.GetInstances(); - - // Assert - Assert.IsNotNull(result, "GetInstances() returned null."); - Assert.AreEqual(initialCount, result.Count, "An incorrect number instances were returned by GetInstances()."); - CollectionAssert.AllItemsAreNotNull(result.ToList(), "GetInstances() returned a null instance."); - } - - [TestMethod] - [Description("Tests GetVersions()")] - public void SqlLocalDbProvider_GetVersions_Returns_Installed_Versions() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - - // Act - IList result = target.GetVersions(); - - // Assert - Assert.IsNotNull(result, "GetVersions() returned null."); - Assert.IsTrue(result.Count > 0, "No versions were returned by GetVersions()."); - CollectionAssert.AllItemsAreNotNull(result.ToList(), "GetVersions() returned a null instance."); - } - - [TestMethod] - [Description("Tests ISqlLocalDbFactory.CreateInstance(string).")] - public void SqlLocalDbProvider_As_ISqlLocalDbFactory_CreateInstance_Creates_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - ISqlLocalDbProvider target = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - ISqlLocalDbInstance result = target.CreateInstance(instanceName); - - // Assert - Assert.IsNotNull(result, "CreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "SqlLocalDbInstance.Name is incorrect."); - - try - { - ISqlLocalDbInstanceInfo info = result.GetInstanceInfo(); - - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.IsTrue(Guid.TryParse(result.Name, out Guid unused), "SqlLocalDbInstance.Name is not a valid GUID."); - } - finally - { - SqlLocalDbInstance.Delete(result); - } - } - - [TestMethod] - [Description("Tests ISqlLocalDbFactory.GetInstance(string).")] - public void SqlLocalDbProvider_As_ISqlLocalDbFactory_GetInstance_Returns_Specified_Instance() - { - // Arrange - Helpers.EnsureLocalDBInstalled(); - - ISqlLocalDbProvider target = new SqlLocalDbProvider(); - string instanceName = Guid.NewGuid().ToString(); - - // Act - SqlLocalDbApi.CreateInstance(instanceName); - - try - { - // Assert - ISqlLocalDbInstance result = target.GetInstance(instanceName); - - Assert.IsNotNull(result, "CreateInstance() returned null."); - Assert.AreEqual(instanceName, result.Name, "SqlLocalDbInstance.Name is incorrect."); - - ISqlLocalDbInstanceInfo info = result.GetInstanceInfo(); - - Assert.IsTrue(info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - Assert.IsFalse(info.IsRunning, "ISqlLocalDbInstanceInfo.IsRunning is incorrect."); - - Assert.IsTrue(Guid.TryParse(result.Name, out Guid unused), "SqlLocalDbInstance.Name is not a valid GUID."); - } - finally - { - SqlLocalDbApi.DeleteInstance(instanceName); - } - } - - [TestMethod] - [Description("Tests the Version property getter and setter work correctly.")] - public void SqlLocalDbProvider_Version_Property_Can_Be_Set_Correctly() - { - // Arrange - string value = "3.4.5.6"; - string latestVersion = SqlLocalDbApi.LatestVersion; - - SqlLocalDbProvider target = new SqlLocalDbProvider(); - - // Act - string result = target.Version; - - // Assert - Assert.AreEqual(latestVersion, result, "SqlLocalDbProvider.Version returned incorrect value."); - - // Act - target.Version = value; - result = target.Version; - - // Assert - Assert.AreEqual(value, result, "SqlLocalDbProvider.Version returned incorrect value."); - - // Act - target.Version = null; - result = target.Version; - - // Assert - Assert.AreEqual(latestVersion, result, "SqlLocalDbProvider.Version returned incorrect value."); - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/System.Data.SqlLocalDb.UnitTests.csproj b/tests/SqlLocalDb.UnitTests/System.Data.SqlLocalDb.UnitTests.csproj deleted file mode 100644 index 3119306b..00000000 --- a/tests/SqlLocalDb.UnitTests/System.Data.SqlLocalDb.UnitTests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Tests for System.Data.SqlLocalDb. - true - System.Data.SqlLocalDb - net451 - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/SqlLocalDb.UnitTests/TemporarySqlLocalDbInstanceTests.cs b/tests/SqlLocalDb.UnitTests/TemporarySqlLocalDbInstanceTests.cs deleted file mode 100644 index ab8e3df8..00000000 --- a/tests/SqlLocalDb.UnitTests/TemporarySqlLocalDbInstanceTests.cs +++ /dev/null @@ -1,680 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// TemporarySqlLocalDbInstanceTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Data.SqlClient; -using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing unit tests for the class. - /// - [TestClass] - public class TemporarySqlLocalDbInstanceTests - { - /// - /// Initializes a new instance of the class. - /// - public TemporarySqlLocalDbInstanceTests() - { - } - - [TestMethod] - [Description("Tests .ctor() if instanceName is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void TemporarySqlLocalDbInstance_Constructor_Throws_If_InstanceName_Is_Null() - { - // Arrange - string instanceName = null; - ISqlLocalDbProvider provider = Mock.Of(); - - // Act and Assert - throw ErrorAssert.Throws( - () => - { - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider)) - { - } - }, - "instanceName"); - } - - [TestMethod] - [Description("Tests .ctor() if provider is null.")] - [ExpectedException(typeof(ArgumentNullException))] - public void TemporarySqlLocalDbInstance_Constructor_Throws_If_Provider_Is_Null() - { - // Arrange - string instanceName = Guid.NewGuid().ToString(); - ISqlLocalDbProvider provider = null; - - // Act and Assert - throw ErrorAssert.Throws( - () => - { - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider)) - { - } - }, - "provider"); - } - - [TestMethod] - [Description("Tests .ctor(string).")] - public void TemporarySqlLocalDbInstance_Constructor_Creates_Temporary_Instance() - { - // Arrange - string instanceName = "MyTempInstance" + Guid.NewGuid().ToString(); - - // Act - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName)) - { - // Assert - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.IsFalse(target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: true); - } - - [TestMethod] - [Description("Tests .ctor(string, ISqlLocalDbProvider).")] - public void TemporarySqlLocalDbInstance_Constructor_Creates_Temporary_Instance_With_Specified_Provider() - { - // Arrange - var mock = new Mock() - { - CallBase = true, - }; - - string instanceName = "MyTempInstance" + Guid.NewGuid().ToString(); - SqlLocalDbProvider provider = mock.Object; - - // Act - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider)) - { - // Assert - Mock.Get(provider).Verify((p) => p.CreateInstance(instanceName), Times.Once()); - - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.IsFalse(target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: true); - } - - [TestMethod] - [Description("Tests .ctor(string, ISqlLocalDbProvider, bool).")] - public void TemporarySqlLocalDbInstance_Constructor_Creates_Temporary_Instance_With_Specified_Provider_And_Does_Not_Delete_Instance_Files() - { - // Arrange - var mock = new Mock() - { - CallBase = true, - }; - - string instanceName = "MyTempInstance" + Guid.NewGuid().ToString(); - SqlLocalDbProvider provider = mock.Object; - bool deleteFiles = false; - - // Act - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider, deleteFiles)) - { - // Assert - Mock.Get(provider).Verify((p) => p.CreateInstance(instanceName), Times.Once()); - - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.AreEqual(deleteFiles, target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: true); - } - - [TestMethod] - [Description("Tests .ctor(string, ISqlLocalDbProvider, bool) if the instance cannot be started.")] - [ExpectedException(typeof(InvalidOperationException))] - public void TemporarySqlLocalDbInstance_Constructor_Attempts_To_Delete_Instance_If_Instance_Cannot_Be_Started() - { - // Arrange - string instanceName = "MyTempInstance" + Guid.NewGuid().ToString(); - - var mock = new Mock() - { - CallBase = true, - }; - - // Set up the CreateInstance() method to create an SQL LocalDB - // instance but that then throws an exception when started. - mock.Setup((p) => p.CreateInstance(instanceName)) - .Returns( - () => - { - SqlLocalDbApi.CreateInstance(instanceName); - return new SqlLocalDbInstanceThatCannotBeStarted(instanceName); - }) - .Verifiable(); - - ISqlLocalDbProvider provider = mock.Object; - bool deleteFiles = false; - - // Act - InvalidOperationException error = ErrorAssert.Throws( - () => - { - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider, deleteFiles)) - { - } - }); - - mock.Verify(); - - ISqlLocalDbInstanceInfo instanceInfo = SqlLocalDbApi.GetInstanceInfo(instanceName); - - Assert.IsFalse(instanceInfo.Exists, "The temporary instance was not deleted."); - - throw error; - } - - [TestMethod] - [Description("Tests Create().")] - public void TemporarySqlLocalDbInstance_Create_Creates_Temporary_Instance_With_Random_Name() - { - string instanceName; - - // Act - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - // Assert - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - instanceName = target.Instance.Name; - - Assert.IsTrue(Guid.TryParse(instanceName, out Guid unused), "The random instance name is not a valid GUID."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.IsFalse(target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: true); - - // Verify that the same random name isn't used again - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - Assert.AreNotEqual(instanceName, target.Instance.Name, "The same random name was used to generate a temporary instance."); - } - } - - [TestMethod] - [Description("Tests Create(bool) deletes the instance files if deleteFiles is true.")] - public void TemporarySqlLocalDbInstance_Create_With_DeleteFiles_Parameter_Creates_Temporary_Instance_With_Random_Name_And_Deletes_Files_When_Disposed() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - // Set the property to false to ensure that the parameter is used to control deletion not the property - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = false; - SqlLocalDbApi.StopOptions = StopInstanceOptions.NoWait; - - string instanceName; - bool deleteFiles = true; - - // Act - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create(deleteFiles)) - { - // Assert - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - instanceName = target.Instance.Name; - - Assert.IsTrue(Guid.TryParse(instanceName, out Guid unused), "The random instance name is not a valid GUID."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.IsTrue(target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: false); - }); - } - - [TestMethod] - [Description("Tests Create() and deletes when disposed if SqlLocalDbApi.AutomaticallyDeleteInstanceFiles is true.")] - public void TemporarySqlLocalDbInstance_Create_Creates_Temporary_Instance_With_Random_Name_And_Deletes_If_SqlLocalDbApi_AutomaticallyDeleteInstanceFiles_Is_True() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - SqlLocalDbApi.AutomaticallyDeleteInstanceFiles = true; - SqlLocalDbApi.StopOptions = StopInstanceOptions.NoWait; - - string instanceName; - - // Act - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - // Assert - Assert.IsNotNull(target.Instance, "TemporarySqlLocalDbInstance.Instance is null."); - - instanceName = target.Instance.Name; - - Assert.IsTrue(Guid.TryParse(instanceName, out Guid unused), "The random instance name is not a valid GUID."); - - // Check the instance was created - AssertExistence(instanceName, exists: true); - - // The instance is not running if there is no pipe open - Assert.IsFalse(string.IsNullOrEmpty(target.Instance.NamedPipe), "The temporary SQL LocalDB instance has not been started."); - - Assert.IsTrue(target.DeleteFiles, "TemporarySqlLocalDbInstance.DeleteFiles is incorrect."); - Assert.AreEqual(target.Instance.Name, target.Name, "TemporarySqlLocalDbInstance.Name is incorrect."); - Assert.AreEqual(target.Instance.NamedPipe, target.NamedPipe, "TemporarySqlLocalDbInstance.NamedPipe is incorrect."); - } - - // The instance should have been deleted - AssertExistence(instanceName, exists: false); - AssertFileExistence(instanceName, shouldFilesExist: false); - - // Verify that the same random name isn't used again - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - Assert.AreNotEqual(instanceName, target.Instance.Name, "The same random name was used to generate a temporary instance."); - } - }); - } - - [TestMethod] - [Description("Tests CreateConnection().")] - public void TemporarySqlLocalDbInstance_CreateConnection_Creates_Connection() - { - // Arrange - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - // Act - using (SqlConnection result = target.CreateConnection()) - { - // Assert - Assert.IsNotNull(result, "CreateConnection() returned null."); - } - } - } - - [TestMethod] - [Description("Tests CreateConnectionStringBuilder().")] - public void TemporarySqlLocalDbInstance_CreateConnectionStringBuilder_Creates_SqlConnectionStringBuilder() - { - // Arrange - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - // Act - SqlConnectionStringBuilder result = target.CreateConnectionStringBuilder(); - - // Assert - Assert.IsNotNull(result, "CreateConnectionStringBuilder() returned null."); - } - } - - [TestMethod] - [Description("Tests GetInstanceInfo().")] - public void TemporarySqlLocalDbInstance_GetInstanceInfo_Returns_ISqlLocalDbInstanceInfo() - { - // Arrange - using (TemporarySqlLocalDbInstance target = TemporarySqlLocalDbInstance.Create()) - { - // Act - ISqlLocalDbInstanceInfo result = target.GetInstanceInfo(); - - // Assert - Assert.IsNotNull(result, "GetInstanceInfo() returned null."); - } - } - - [TestMethod] - [Description("Tests Share().")] - public void TemporarySqlLocalDbInstance_Share_Invokes_Wrapped_Instance() - { - // Arrange - ISqlLocalDbInstance instance = CreateMockInstance(); - - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instance)) - { - string sharedName = Guid.NewGuid().ToString(); - - // Act - target.Share(sharedName); - - // Assert - Mock.Get(instance).Verify((p) => p.Share(sharedName), Times.Once()); - } - } - - [TestMethod] - [Description("Tests Start().")] - public void TemporarySqlLocalDbInstance_Start_Invokes_Wrapped_Instance() - { - // Arrange - ISqlLocalDbInstance instance = CreateMockInstance(); - - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instance)) - { - // Act - target.Start(); - - // Assert - Mock.Get(instance).Verify((p) => p.Start(), Times.Once()); - } - } - - [TestMethod] - [Description("Tests Stop().")] - public void TemporarySqlLocalDbInstance_Stop_Invokes_Wrapped_Instance() - { - // Arrange - ISqlLocalDbInstance instance = CreateMockInstance(); - - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instance)) - { - // Act - target.Stop(); - - // Assert - Mock.Get(instance).Verify((p) => p.Stop(), Times.Once()); - } - } - - [TestMethod] - [Description("Tests Unshare().")] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method being tested.")] - public void TemporarySqlLocalDbInstance_Unshare_Invokes_Wrapped_Instance() - { - // Arrange - ISqlLocalDbInstance instance = CreateMockInstance(); - - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instance)) - { - // Act - target.Unshare(); - - // Assert - Mock.Get(instance).Verify((p) => p.Unshare(), Times.Once()); - } - } - - [TestMethod] - [Description("Tests CreateConnection() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_CreateConnection_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.CreateConnection()); - } - - [TestMethod] - [Description("Tests CreateConnection() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_CreateConnectionStringBuilder_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.CreateConnectionStringBuilder()); - } - - [TestMethod] - [Description("Tests GetInstanceInfo() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_GetInstanceInfo_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.GetInstanceInfo()); - } - - [TestMethod] - [Description("Tests Share() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_Share_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.Share(string.Empty)); - } - - [TestMethod] - [Description("Tests Start() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_Start_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.Start()); - } - - [TestMethod] - [Description("Tests Stop() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - public void TemporarySqlLocalDbInstance_Stop_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.Stop()); - } - - [TestMethod] - [Description("Tests Unshare() throws an exception if it has been disposed.")] - [ExpectedException(typeof(ObjectDisposedException))] - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Naming", - "CA1704:IdentifiersShouldBeSpelledCorrectly", - MessageId = "Unshare", - Justification = "Matches the name of the method being tested.")] - public void TemporarySqlLocalDbInstance_Unshare_Throws_If_Instance_Disposed() - { - // Act and Assert - AssertThrowsObjectDisposedException((p) => p.Unshare()); - } - - [TestMethod] - [Description("Tests Dispose() does not throw if a SQL LocalDB error occurs stopping the instance that is not the error from the instance not existing.")] - public void TemporarySqlLocalDbInstance_Dispose_Does_Not_Throw_If_Instance_Cannot_Be_Stopped_Due_To_Sql_LocalDb_Error() - { - // Arrange - string instanceName = "MyTempInstance" + Guid.NewGuid().ToString(); - - var mock = new Mock() - { - CallBase = true, - }; - - // Set up the CreateInstance() method to create an SQL LocalDB - // instance but that then throws an exception when stopped. - mock.Setup((p) => p.CreateInstance(instanceName)) - .Returns( - () => - { - SqlLocalDbApi.CreateInstance(instanceName); - return new SqlLocalDbInstanceThatCannotBeStopped(instanceName); - }) - .Verifiable(); - - ISqlLocalDbProvider provider = mock.Object; - - // Act - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instanceName, provider)) - { - } - - // Assert - mock.Verify(); - - // Tidy up as the stop intentionally failed, meaning delete would also have failed - SqlLocalDbApi.StopInstance(instanceName); - SqlLocalDbApi.DeleteInstance(instanceName); - } - - /// - /// Assets that the specified SQL LocalDB instance name is in the specified state of existence. - /// - /// The instance name to assert for. - /// Whether the specified instance name is expected to exist. - private static void AssertExistence(string instanceName, bool exists) - { - ISqlLocalDbInstanceInfo info = SqlLocalDbApi.GetInstanceInfo(instanceName); - Assert.IsNotNull(info, "SqlLocalDbApi.GetInstanceInfo() returned null."); - Assert.AreEqual(exists, info.Exists, "ISqlLocalDbInstanceInfo.Exists is incorrect."); - } - - /// - /// Assets that the specified SQL LocalDB instance's file(s) are in the specified state of existence. - /// - /// The instance name to assert for. - /// Whether the specified instance name's files are expected to exist. - private static void AssertFileExistence(string instanceName, bool shouldFilesExist) - { - string path = Path.Combine(SqlLocalDbApi.GetInstancesFolderPath(), instanceName); - - if (shouldFilesExist) - { - Assert.IsTrue(Directory.Exists(path), "The instance folder was deleted."); - Assert.AreNotEqual(0, Directory.GetFiles(path).Length, "The instance file(s) were deleted."); - } - else - { - Assert.IsFalse(Directory.Exists(path), "The instance folder was not deleted."); - } - } - - /// - /// Asserts that invoking the specified delegate causes an - /// to occur if the instance has already been disposed. - /// - /// A delegate to a method to invoke for an instance of . - [Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Usage", - "CA2202:Do not dispose objects multiple times", - Justification = "The test is designed to test this scenario.")] - private static void AssertThrowsObjectDisposedException(Action action) - { - // Arrange - ISqlLocalDbInstance instance = CreateMockInstance(); - - using (TemporarySqlLocalDbInstance target = new TemporarySqlLocalDbInstance(instance)) - { - target.Dispose(); - - // Act - action(target); - } - } - - /// - /// Creates a mock instance of . - /// - /// - /// The created mock implementation of . - /// - private static ISqlLocalDbInstance CreateMockInstance() - { - Mock mock = new Mock(); - - mock.Setup((p) => p.Name) - .Returns("MyInstanceName"); - - return mock.Object; - } - - /// - /// A class representing an implementation of that cannot be started. This class cannot be inherited. - /// - private sealed class SqlLocalDbInstanceThatCannotBeStarted : SqlLocalDbInstance, ISqlLocalDbInstance - { - internal SqlLocalDbInstanceThatCannotBeStarted(string instanceName) - : base(instanceName) - { - } - - /// - void ISqlLocalDbInstance.Start() - { - throw new InvalidOperationException(); - } - } - - /// - /// A class representing an implementation of that cannot be stopped. This class cannot be inherited. - /// - private sealed class SqlLocalDbInstanceThatCannotBeStopped : SqlLocalDbInstance, ISqlLocalDbInstance - { - internal SqlLocalDbInstanceThatCannotBeStopped(string instanceName) - : base(instanceName) - { - } - - /// - void ISqlLocalDbInstance.Stop() - { - throw new SqlLocalDbException("Error", SqlLocalDbErrors.UnknownErrorCode); - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/TestCategories.cs b/tests/SqlLocalDb.UnitTests/TestCategories.cs deleted file mode 100644 index 6f86c1e1..00000000 --- a/tests/SqlLocalDb.UnitTests/TestCategories.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// ErrorAssert.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing the names of test categories. This class cannot be inherited. - /// - internal static class TestCategories - { - /// - /// The name of the test category for tests that should only run as part of a continuous integration. - /// - internal const string CIOnly = "CI Only"; - - /// - /// The name of the integration test category. - /// - internal const string Integration = "Integration"; - - /// - /// The name of the test category for tests that require administrative permissions to run. - /// - internal const string RequiresAdministrativePermissions = "Requires Administrative Permissions"; - - /// - /// The name of the test category for tests that specifically require SQL Server 2012 or test its behavior. - /// - internal const string SqlServer2012 = "SQL Server 2012"; - - /// - /// The name of the test category for tests that specifically require SQL Server 2014 or test its behavior. - /// - internal const string SqlServer2014 = "SQL Server 2014"; - - /// - /// The name of the test category for tests that specifically require SQL Server 2016 or test its behavior. - /// - internal const string SqlServer2016 = "SQL Server 2016"; - } -} diff --git a/tests/SqlLocalDb.UnitTests/TestSetup.cs b/tests/SqlLocalDb.UnitTests/TestSetup.cs deleted file mode 100644 index 7de11205..00000000 --- a/tests/SqlLocalDb.UnitTests/TestSetup.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// TestSetup.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class representing the setup class for the test assembly. This class cannot be inherited. - /// - [TestClass] - public sealed class TestSetup - { - /// - /// The SQL LocalDB instance names that existed at the start of the test run. - /// - private static readonly List InstanceNames = new List(); - - /// - /// Prevents a default instance of the class from being created. - /// - private TestSetup() - { - } - - /// - /// Initializes the test assembly. - /// - /// The test context. - [AssemblyInitialize] - public static void AssemblyInitialize(TestContext context) - { - SqlLocalDbApi.StopOptions = StopInstanceOptions.NoWait; - - // Store the names of all the SQL LocalDB instance folders that existed before the tests are run - string[] instanceNames = GetInstanceNames(); - InstanceNames.AddRange(instanceNames); - } - - /// - /// Cleans up the test assembly. - /// - [AssemblyCleanup] - public static void AssemblyCleanup() - { - // Get the names of all the SQL LocalDB instance folders that existed after the tests are run - string[] instanceNames = GetInstanceNames(); - - // Filter the list down to just the names of the instances that were created in the test run - string[] createdInstanceNames = instanceNames - .Except(InstanceNames) - .ToArray(); - - // Try and delete all the leftover file(s) from the test run - foreach (string instanceName in createdInstanceNames) - { - SqlLocalDbApi.DeleteInstanceFiles(instanceName); - } - } - - /// - /// Returns the SQL LocalDB instance names using the instance folder names on the current machine. - /// - /// - /// An containing the names of all the instance folder on the current machine. - /// - private static string[] GetInstanceNames() - { - string path = SqlLocalDbApi.GetInstancesFolderPath(); - - if (Directory.Exists(path)) - { - return Directory.GetDirectories(path); - } - else - { - return new string[0]; - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllDisabled.config b/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllDisabled.config deleted file mode 100644 index 643ed10b..00000000 --- a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllDisabled.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllEnabled.config b/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllEnabled.config deleted file mode 100644 index b31cd609..00000000 --- a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.AllEnabled.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.SomeDisabled.config b/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.SomeDisabled.config deleted file mode 100644 index d464f6ee..00000000 --- a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.SomeDisabled.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.cs b/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.cs deleted file mode 100644 index 1a6dfc7b..00000000 --- a/tests/SqlLocalDb.UnitTests/TraceSourceLoggerTests.cs +++ /dev/null @@ -1,206 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Martin Costello (c) 2012-2015 -// -// -// See license.txt in the project root for license information. -// -// -// TraceSourceLoggerTests.cs -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace System.Data.SqlLocalDb -{ - /// - /// A class containing tests for the class. - /// - [TestClass] - public class TraceSourceLoggerTests - { - /// - /// Initializes a new instance of the class. - /// - public TraceSourceLoggerTests() - { - } - - [TestMethod] - [Description("Tests TraceSourceLogger does not log if some logging levels are not enabled.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Maintainability", - "CA1502:AvoidExcessiveComplexity", - Justification = "This method is not overly complex.")] - public void TraceSourceLogger_Verbose_Does_Not_Log_If_Some_Logging_Levels_Are_Not_Enabled() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TraceSourceLogger target = TraceSourceLogger.Instance; - - // Act - target.WriteError(1, "Letter 1 is {0}.", 'a'); - target.WriteWarning(2, "Letter 2 is {0}.", 'b'); - target.WriteInformation(3, "Letter 3 is {0}.", 'c'); - target.WriteVerbose(4, "Letter 4 is {0}.", 'd'); - - // Assert - var data = TestTraceListener.LogData; - - Assert.AreEqual(2, data.Count, "An incorrect number of messages were logged."); - - int count = data - .Where((p) => p.Item1 == TraceEventType.Error) - .Where((p) => p.Item2 == 1) - .Where((p) => p.Item3 == "Letter 1 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'a') - .Count(); - - Assert.AreEqual(1, count, "The error message was not logged correctly."); - - count = data - .Where((p) => p.Item1 == TraceEventType.Warning) - .Where((p) => p.Item2 == 2) - .Where((p) => p.Item3 == "Letter 2 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'b') - .Count(); - - Assert.AreEqual(1, count, "The warning message was not logged correctly."); - }, - configurationFile: "TraceSourceLoggerTests.SomeDisabled.config"); - } - - [TestMethod] - [Description("Tests TraceSourceLogger does not log if all logging levels are not enabled.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Microsoft.Maintainability", - "CA1502:AvoidExcessiveComplexity", - Justification = "This method is not overly complex.")] - public void TraceSourceLogger_Verbose_Does_Not_Log_If_All_Logging_Levels_Are_Not_Enabled() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TraceSourceLogger target = TraceSourceLogger.Instance; - - // Act - target.WriteError(1, "Letter 1 is {0}.", 'a'); - target.WriteWarning(2, "Letter 2 is {0}.", 'b'); - target.WriteInformation(3, "Letter 3 is {0}.", 'c'); - target.WriteVerbose(4, "Letter 4 is {0}.", 'd'); - - // Assert - var data = TestTraceListener.LogData; - - Assert.AreEqual(4, data.Count, "An incorrect number of messages were logged."); - - int count = data - .Where((p) => p.Item1 == TraceEventType.Error) - .Where((p) => p.Item2 == 1) - .Where((p) => p.Item3 == "Letter 1 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'a') - .Count(); - - Assert.AreEqual(1, count, "The error message was not logged correctly."); - - count = data - .Where((p) => p.Item1 == TraceEventType.Warning) - .Where((p) => p.Item2 == 2) - .Where((p) => p.Item3 == "Letter 2 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'b') - .Count(); - - Assert.AreEqual(1, count, "The warning message was not logged correctly."); - - count = data - .Where((p) => p.Item1 == TraceEventType.Information) - .Where((p) => p.Item2 == 3) - .Where((p) => p.Item3 == "Letter 3 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'c') - .Count(); - - Assert.AreEqual(1, count, "The information message was not logged correctly."); - - count = data - .Where((p) => p.Item1 == TraceEventType.Verbose) - .Where((p) => p.Item2 == 4) - .Where((p) => p.Item3 == "Letter 4 is {0}.") - .Where((p) => p.Item4 != null) - .Where((p) => p.Item4.Length == 1) - .Where((p) => (char)p.Item4[0] == 'd') - .Count(); - - Assert.AreEqual(1, count, "The verbose message was not logged correctly."); - }, - configurationFile: "TraceSourceLoggerTests.AllEnabled.config"); - } - - [TestMethod] - [Description("Tests TraceSourceLogger does not log if all logging levels are not disabled.")] - public void TraceSourceLogger_Verbose_Does_Not_Log_If_All_Logging_Levels_Are_Not_Disabled() - { - // Arrange - Helpers.InvokeInNewAppDomain( - () => - { - TraceSourceLogger target = TraceSourceLogger.Instance; - - // Act - target.WriteError(1, "Letter 1 is {0}.", 'a'); - target.WriteWarning(2, "Letter 2 is {0}.", 'b'); - target.WriteInformation(3, "Letter 3 is {0}.", 'c'); - target.WriteVerbose(4, "Letter 4 is {0}.", 'd'); - - // Assert - var data = TestTraceListener.LogData; - - Assert.AreEqual(0, data.Count, "An incorrect number of messages were logged."); - }, - configurationFile: "TraceSourceLoggerTests.AllDisabled.config"); - } - - /// - /// A class representing an implementation of to test logging behavior. This class cannot be inherited. - /// - internal sealed class TestTraceListener : TraceListener - { - /// - /// The data logged to the trace listener. This field is read-only. - /// - internal static readonly List> LogData = new List>(); - - /// - public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, object[] args) - { - LogData.Add(Tuple.Create(eventType, id, format, args)); - } - - /// - public override void Write(string message) - { - } - - /// - public override void WriteLine(string message) - { - } - } - } -} diff --git a/tests/SqlLocalDb.UnitTests/app.config b/tests/SqlLocalDb.UnitTests/app.config deleted file mode 100644 index 898d32f7..00000000 --- a/tests/SqlLocalDb.UnitTests/app.config +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 1b6868c07168553884ddad4e8f702c9c872e97df Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 11:47:16 +0100 Subject: [PATCH 02/52] Fix incorrect link Fix incorrect link in the Code of Conduct from copy-paste. --- CODE_OF_CONDUCT.md | 2 +- SqlLocalDb.sln | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c82dee64..680cffa0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team [through a GitHub issue](https://github.com/justeat/httpclient-interception/issues). All +reported by contacting the project team [through a GitHub issue](https://github.com/martincostello/sqllocaldb/issues). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/SqlLocalDb.sln b/SqlLocalDb.sln index b7eb63e7..07717091 100644 --- a/SqlLocalDb.sln +++ b/SqlLocalDb.sln @@ -13,9 +13,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Build.ps1 = Build.ps1 build.sh = build.sh CHANGELOG = CHANGELOG + CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md CommonAssemblyInfo.cs = CommonAssemblyInfo.cs Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets + global.json = global.json LICENSE = LICENSE readme.md = readme.md releases.txt = releases.txt From afecaf4749ae5ef4977cd42e4bc4e488d55265f4 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 11:47:54 +0100 Subject: [PATCH 03/52] Delete obsolete files Delete obsolete properties and releases files. --- SqlLocalDb.Common.props | 33 --------------------------------- SqlLocalDb.sln | 2 -- releases.txt | 16 ---------------- 3 files changed, 51 deletions(-) delete mode 100644 SqlLocalDb.Common.props delete mode 100644 releases.txt diff --git a/SqlLocalDb.Common.props b/SqlLocalDb.Common.props deleted file mode 100644 index 756adba3..00000000 --- a/SqlLocalDb.Common.props +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - ../../SqlLocalDb.snk - Martin Costello - ../../SqlLocalDb.ruleset - https://github.com/martincostello/sqllocaldb - Martin Costello (c) 2015-$([System.DateTime]::Now.ToString(yyyy)) - $(DefineConstants) - false - false - false - false - true - false - en-US - - https://github.com/martincostello/sqllocaldb - $(PackageProjectUrl)/blob/master/LICENSE - See $(PackageProjectUrl)/releases for details. - false - sql;localdb - true - git - $(PackageProjectUrl).git - true - true - 2.0.0 - - - diff --git a/SqlLocalDb.sln b/SqlLocalDb.sln index 07717091..54bdd050 100644 --- a/SqlLocalDb.sln +++ b/SqlLocalDb.sln @@ -20,8 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json LICENSE = LICENSE readme.md = readme.md - releases.txt = releases.txt - SqlLocalDb.Common.props = SqlLocalDb.Common.props SqlLocalDb.ruleset = SqlLocalDb.ruleset SqlLocalDb.snk = SqlLocalDb.snk stylecop.json = stylecop.json diff --git a/releases.txt b/releases.txt deleted file mode 100644 index d0f20466..00000000 --- a/releases.txt +++ /dev/null @@ -1,16 +0,0 @@ -1.0.0.0 -1.1.0.0 -1.2.0.0 -1.3.0.0 -1.4.0.0 -1.5.0.0 -1.6.0.0 -1.7.0.0 -1.8.0.0 -1.9.13.0 -1.10.28.0 -1.11.37.0 -1.12.40.0 -1.13.60.0 -1.14.81.0 -1.15.258.0 From eb6364bb5d93607785806cbfb89061ec1a7402a5 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 11:50:27 +0100 Subject: [PATCH 04/52] Fix Travis CI build Fix Travis CI building due to it trying to create a version with the AppVeyor build number. --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1294b211..1d118854 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,7 +38,7 @@ true true 2.0.0 - beta$([System.Convert]::ToInt32(`$(APPVEYOR_BUILD_NUMBER)`).ToString(`0000`)) + beta$([System.Convert]::ToInt32(`$(APPVEYOR_BUILD_NUMBER)`).ToString(`0000`)) $(APPVEYOR_REPO_TAG_NAME.Substring($(APPVEYOR_REPO_TAG_NAME.IndexOf(`-`))).Substring(1)) From 1233ac1b845578c1e881dd08ddaca3ae43c72d6a Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 11:54:18 +0100 Subject: [PATCH 05/52] Fix Travis CI script Fix the Travis CI script by using pack not publish. --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 0b17e834..b4d20568 100755 --- a/build.sh +++ b/build.sh @@ -39,7 +39,7 @@ if [ "$dotnet_version" != "$CLI_VERSION" ]; then curl -sSL https://raw.githubusercontent.com/dotnet/cli/v$CLI_VERSION/scripts/obtain/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" fi -dotnet publish ./src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj --output $artifacts/publish --configuration $configuration || exit 1 +dotnet pack ./src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj --output $artifacts --configuration $configuration || exit 1 if [ $skipTests == 0 ]; then dotnet test ./tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj --output $artifacts --configuration $configuration || exit 1 From 2a5b74d177d6365fb298d19f8dec929789e50ddc Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:01:52 +0100 Subject: [PATCH 06/52] Fix AppVeyor build Fix dotnet pack failing in AppVeyor. Also fix stale code in unit tests. --- Build.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index a9967646..d488735c 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,4 +1,4 @@ -param( +param( [Parameter(Mandatory = $false)][string] $Configuration = "Release", [Parameter(Mandatory = $false)][string] $VersionSuffix = "", [Parameter(Mandatory = $false)][string] $OutputPath = "", @@ -76,10 +76,10 @@ function DotNetPack { param([string]$Project) if ($VersionSuffix) { - & $dotnet pack $Project --output $OutputPath --configuration $Configuration --version-suffix "$VersionSuffix" --include-symbols --include-source --no-build + & $dotnet pack $Project --output $OutputPath --configuration $Configuration --version-suffix "$VersionSuffix" --include-symbols --include-source } else { - & $dotnet pack $Project --output $OutputPath --configuration $Configuration --include-symbols --include-source --no-build + & $dotnet pack $Project --output $OutputPath --configuration $Configuration --include-symbols --include-source } if ($LASTEXITCODE -ne 0) { throw "dotnet pack failed with exit code $LASTEXITCODE" @@ -114,7 +114,7 @@ function DotNetTest { & $openCoverPath ` `"-target:$dotnetPath`" ` - `"-targetargs:test $Project --output $OutputPath --no-build`" ` + `"-targetargs:test $Project --output $OutputPath`" ` -output:$coverageOutput ` -hideskipped:All ` -mergebyhash ` From c55d563e1e511b3e93195c85615005c3b2083b86 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:02:22 +0100 Subject: [PATCH 07/52] Fix NullReferenceException Fix NullReferenceException if a temporary instance is disposed without being started. --- src/SqlLocalDb/TemporarySqlLocalDbInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs index 298c1561..5e0fc33e 100644 --- a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs +++ b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs @@ -117,7 +117,7 @@ private void DisposeInternal() { if (!_disposed) { - if (_instanceName.IsValueCreated) + if (_instanceName != null && _instanceName.IsValueCreated) { string instanceName = _instanceName.Value; From faa57fa7006d1f0ce6a510f638f8cd21b5ce4e5f Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:05:08 +0100 Subject: [PATCH 08/52] IsLocalDBInstalled() returns false on Linux/macOS Change IsLocalDBInstalled() to always return false if the executing OS is not Windows, rather than throw an exception from trying to access the Windows registry. --- src/SqlLocalDb/SqlLocalDbApi.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index d6e3d0d8..d56dba5e 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -44,6 +44,11 @@ public sealed class SqlLocalDbApi : ISqlLocalDbApi, IDisposable /// private const int ReservedValue = 0; + /// + /// Returns whether the executing platform is Microsoft Windows. + /// + private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + /// /// The native API. This field is read-only. /// @@ -637,6 +642,12 @@ public ISqlLocalDbVersionInfo GetVersionInfo(string version) /// public bool IsLocalDBInstalled() { + // SQL LocalDB is only supported on Windows, so shortcut + if (!IsWindows) + { + return false; + } + // Call one of the "get info" functions with a zero buffer. // If LocalDB is installed, it will return a "buffer too small" HRESULT, // otherwise it will return the "not installed" HRESULT. From ee5692ef3d99bb5e610cafd91868b873b98a967d Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:16:37 +0100 Subject: [PATCH 09/52] Throw PlatformNotSupportedException on Linux/macOS Fix TypeInitializationException by throwing a PlatformNotSupportedException when trying to call methods in the SQL LocalDB Instance API on non-Windows OSs. --- src/SqlLocalDb/SR.Designer.cs | 9 ++++ src/SqlLocalDb/SR.resx | 3 ++ src/SqlLocalDb/SqlLocalDbApi.cs | 88 +++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index d9214232..7430b8f7 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -510,6 +510,15 @@ internal static string SqlLocalDbApi_NoVersionsFormat { } } + /// + /// Looks up a localized string similar to The SQL LocalDB Instance API is only supported on Windows operating systems.. + /// + internal static string SqlLocalDbApi_PlatformNotSupported { + get { + return ResourceManager.GetString("SqlLocalDbApi_PlatformNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} cannot be less than TimeSpan.Zero.. /// diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index ae403b83..30e96ba1 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -288,4 +288,7 @@ SQL Server LocalDB is not installed. + + The SQL LocalDB Instance API is only supported on Windows operating systems. + \ No newline at end of file diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index d56dba5e..702cfcb7 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -189,6 +189,9 @@ public string DefaultInstanceName /// /// No versions of SQL Server LocalDB are installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// public string LatestVersion { get @@ -249,6 +252,9 @@ public TimeSpan StopTimeout /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The installed versions of SQL LocalDB could not be determined. /// @@ -315,6 +321,9 @@ public void Dispose() /// /// No versions of SQL Server LocalDB are installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could /// not be stopped or the installed versions of SQL LocalDB could not be determined. @@ -335,6 +344,9 @@ public void Dispose() /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by and could not be created. /// @@ -374,6 +386,9 @@ public ISqlLocalDbInstanceInfo CreateInstance(string instanceName, string versio /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be deleted. /// @@ -394,6 +409,9 @@ public ISqlLocalDbInstanceInfo CreateInstance(string instanceName, string versio /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be deleted. /// @@ -412,6 +430,12 @@ public void DeleteInstance(string instanceName, bool deleteFiles) /// The default instance(s) of any version(s) of SQL LocalDB that are /// installed on the local machine are not deleted. /// + /// + /// SQL Server LocalDB is not installed on the local machine. + /// + /// + /// The method is called from a non-Windows operating system. + /// public int DeleteUserInstances() => DeleteUserInstances(deleteFiles: AutomaticallyDeleteInstanceFiles); /// @@ -429,6 +453,12 @@ public void DeleteInstance(string instanceName, bool deleteFiles) /// The default instance(s) of any version(s) of SQL LocalDB that are /// installed on the local machine are not deleted. /// + /// + /// SQL Server LocalDB is not installed on the local machine. + /// + /// + /// The method is called from a non-Windows operating system. + /// public int DeleteUserInstances(bool deleteFiles) { int instancesDeleted = 0; @@ -489,6 +519,9 @@ public int DeleteUserInstances(bool deleteFiles) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The information for the SQL Server LocalDB instance specified by could not obtained. /// @@ -538,6 +571,9 @@ public ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance names could not be determined. /// @@ -594,6 +630,9 @@ public IReadOnlyList GetInstanceNames() /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The information for the SQL Server LocalDB version specified by could not be obtained. /// @@ -669,6 +708,9 @@ public bool IsLocalDBInstalled() /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be shared. /// @@ -740,6 +782,9 @@ public void ShareInstance( /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be started. /// @@ -771,6 +816,12 @@ public string StartInstance(string instanceName) /// Enables tracing of native API calls for all the SQL Server /// LocalDB instances owned by the current Windows user. /// + /// + /// SQL Server LocalDB is not installed on the local machine. + /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// Tracing could not be initialized. /// @@ -796,6 +847,9 @@ public void StartTracing() /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be stopped. /// @@ -825,6 +879,9 @@ public void StopInstance(string instanceName) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be stopped. /// @@ -860,6 +917,9 @@ public void StopInstance(string instanceName, TimeSpan? timeout) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be stopped. /// @@ -898,6 +958,12 @@ public void StopInstance(string instanceName, StopInstanceOptions options, TimeS /// Disables tracing of native API calls for all the SQL Server /// LocalDB instances owned by the current Windows user. /// + /// + /// SQL Server LocalDB is not installed on the local machine. + /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// Tracing could not be disabled. /// @@ -922,6 +988,9 @@ public void StopTracing() /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be unshared. /// @@ -981,6 +1050,9 @@ internal static bool IsDefaultInstanceName(string instanceName) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The SQL Server LocalDB instance specified by could not be deleted. /// @@ -1173,11 +1245,19 @@ private static T MarshalStruct(IntPtr ptr) /// /// SQL Server LocalDB is not installed on the local machine. /// + /// + /// The method is called from a non-Windows operating system. + /// /// /// The installed LocalDB versions could not be enumerated. /// private string[] GetLocalDbVersions() { + if (!IsWindows) + { + throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); + } + string[] versions; try @@ -1235,8 +1315,16 @@ private string[] GetLocalDbVersions() /// A delegate to a method to invoke. /// The trace event Id if a non-zero value is returned. /// The name of the instance that caused the error, if any. + /// + /// The method is called from a non-Windows operating system. + /// private void InvokeThrowOnError(Func func, EventId eventId, string instanceName = "") { + if (!IsWindows) + { + throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); + } + int hr = func(); if (hr != 0) From a740e0dccd4719d143b13623e1a91b0401c5b658 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:19:44 +0100 Subject: [PATCH 10/52] Reword exception message for non-Windows OS Simplify the exception message and match the .NET built-in exception message wording. --- src/SqlLocalDb/SR.Designer.cs | 2 +- src/SqlLocalDb/SR.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index 7430b8f7..f37c3c8b 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -511,7 +511,7 @@ internal static string SqlLocalDbApi_NoVersionsFormat { } /// - /// Looks up a localized string similar to The SQL LocalDB Instance API is only supported on Windows operating systems.. + /// Looks up a localized string similar to The SQL LocalDB Instance API is not supported on this platform.. /// internal static string SqlLocalDbApi_PlatformNotSupported { get { diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index 30e96ba1..19c4b863 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -289,6 +289,6 @@ SQL Server LocalDB is not installed. - The SQL LocalDB Instance API is only supported on Windows operating systems. + The SQL LocalDB Instance API is not supported on this platform. \ No newline at end of file From 785978d16274bbfa808289cb00492c5891831d61 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:21:34 +0100 Subject: [PATCH 11/52] Fix another TypeInitializationException on Linux Fix another TypeInitializationException exception on non-Windows OSs by checking the platform before enumerating the instance names. --- src/SqlLocalDb/SqlLocalDbApi.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index 702cfcb7..31c2f6fd 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -579,6 +579,11 @@ public ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) /// public IReadOnlyList GetInstanceNames() { + if (!IsWindows) + { + throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); + } + Logger.GettingInstanceNames(); // Query the LocalDB API to get the number of instances From eb0094c07b5754c9166a6f99eb8707a22b1f0f92 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 12:42:27 +0100 Subject: [PATCH 12/52] Trap PlatformNotSupportedException for Windows Princpal Throw a different PlatformNotSupportedException if the ShareInstance() extension method is called on a non-Windows OS. --- src/SqlLocalDb/ISqlLocalDbApiExtensions.cs | 5 +++ src/SqlLocalDb/SqlLocalDbApi.cs | 39 ++++++++++++---------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs index 5ba2202d..465c0c40 100644 --- a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs +++ b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs @@ -184,6 +184,9 @@ public static ISqlLocalDbInstanceInfo GetOrCreateInstance(this ISqlLocalDbApi ap /// /// , or is . /// + /// + /// The method is called from a non-Windows operating system. + /// public static void ShareInstance(this ISqlLocalDbApi api, string instanceName, string sharedInstanceName) { if (api == null) @@ -191,6 +194,8 @@ public static void ShareInstance(this ISqlLocalDbApi api, string instanceName, s throw new ArgumentNullException(nameof(api)); } + SqlLocalDbApi.EnsurePlatformSupported(); + string ownerSid; using (var identity = WindowsIdentity.GetCurrent()) diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index 31c2f6fd..28232b17 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -44,11 +44,6 @@ public sealed class SqlLocalDbApi : ISqlLocalDbApi, IDisposable /// private const int ReservedValue = 0; - /// - /// Returns whether the executing platform is Microsoft Windows. - /// - private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - /// /// The native API. This field is read-only. /// @@ -273,6 +268,11 @@ public IReadOnlyList Versions } } + /// + /// Gets a value indicating whether the executing platform is Microsoft Windows. + /// + internal static bool IsWindows { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + /// /// Gets the to use. /// @@ -579,10 +579,7 @@ public ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) /// public IReadOnlyList GetInstanceNames() { - if (!IsWindows) - { - throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); - } + EnsurePlatformSupported(); Logger.GettingInstanceNames(); @@ -1016,6 +1013,20 @@ public void UnshareInstance(string instanceName) Logger.UnsharedInstance(instanceName); } + /// + /// Throws an exception if the SQL LocalDB Instance API is not supported by the current platform. + /// + /// + /// The method is called from a non-Windows operating system. + /// + internal static void EnsurePlatformSupported() + { + if (!IsWindows) + { + throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); + } + } + /// /// Returns whether the specified SQL LocalDB instance name is one of the default instance names. /// @@ -1258,10 +1269,7 @@ private static T MarshalStruct(IntPtr ptr) /// private string[] GetLocalDbVersions() { - if (!IsWindows) - { - throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); - } + EnsurePlatformSupported(); string[] versions; @@ -1325,10 +1333,7 @@ private string[] GetLocalDbVersions() /// private void InvokeThrowOnError(Func func, EventId eventId, string instanceName = "") { - if (!IsWindows) - { - throw new PlatformNotSupportedException(SR.SqlLocalDbApi_PlatformNotSupported); - } + EnsurePlatformSupported(); int hr = func(); From b60b01e0903290e4ac022b86b9e2e5d7e2ac0d8d Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:01:56 +0100 Subject: [PATCH 13/52] Skip tests that only work on Windows in Travis CI Skip tests that only work on Windows in Travis CI. --- .../ISqlLocalDbApiExtensionsTests.cs | 135 ++++++++++-------- ...qlLocalDbInstanceManagerExtensionsTests.cs | 4 +- .../RunAsAdminFactAttribute.cs | 16 +-- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 2 +- .../SqlLocalDbInstanceManagerTests.cs | 76 +++++----- .../WindowsOnlyFactAttribute.cs | 23 +++ 6 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index c04042d5..c3b7a0b3 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -20,6 +20,71 @@ public ISqlLocalDbApiExtensionsTests(ITestOutputHelper outputHelper) _loggerFactory = outputHelper.AsLoggerFactory(); } + [Theory] + [InlineData(SqlLocalDbErrors.InstanceBusy)] + [InlineData(SqlLocalDbErrors.InternalError)] + public static void TemporaryInstance_Ignores_Exception_If_Delete_Fails(int errorCode) + { + // Arrange + if (!SqlLocalDbApi.IsWindows) + { + // HACK Theories dont seem to work correctly with subclasses now + // so cannot make a derived class for a "Windows-only" theory. + return; + } + + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.LatestVersion) + .Returns("v99.9"); + + mock.Setup((p) => p.CreateInstance(It.IsAny(), "v99.9")) + .Verifiable(); + + mock.Setup((p) => p.StartInstance(It.IsAny())) + .Verifiable(); + + mock.Setup((p) => p.StopInstance(It.IsAny(), null)) + .Verifiable(); + + mock.Setup((p) => p.DeleteInstance(It.IsAny())) + .Throws(new SqlLocalDbException("Error", errorCode)) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + // Act + using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance()) + { + target.GetInstanceInfo(); + } + + // Assert + mock.Verify(); + } + + [WindowsOnlyFact] + public static void ShareInstance_Uses_SID_For_Current_User() + { + // Arrange + string instanceName = "SomeName"; + string sharedInstanceName = "SomeSharedName"; + + var mock = new Mock(); + + mock.Setup((p) => p.ShareInstance(It.IsNotNull(), instanceName, sharedInstanceName)) + .Verifiable(); + + ISqlLocalDbApi api = mock.Object; + + // Act + api.ShareInstance(instanceName, sharedInstanceName); + + // Assert + mock.Verify(); + } + [Fact] public void CreateTemporaryInstance_Throws_If_Api_Is_Null() { @@ -36,6 +101,13 @@ public void CreateTemporaryInstance_Throws_If_Api_Is_Null() public void CreateTemporaryInstance_Creates_Starts_And_Deletes_An_Instance(bool deleteFiles) { // Arrange + if (!SqlLocalDbApi.IsWindows) + { + // HACK Theories dont seem to work correctly with subclasses now + // so cannot make a derived class for a "Windows-only" theory. + return; + } + using (var api = new SqlLocalDbApi(_loggerFactory)) { ISqlLocalDbInstanceInfo info; @@ -166,42 +238,6 @@ public void TemporaryInstance_Ignores_Exception_If_Stop_Fails(int errorCode) mock.Verify(); } - [Theory] - [InlineData(SqlLocalDbErrors.InstanceBusy)] - [InlineData(SqlLocalDbErrors.InternalError)] - public void TemporaryInstance_Ignores_Exception_If_Delete_Fails(int errorCode) - { - // Arrange - var mock = new Mock(); - - mock.Setup((p) => p.LatestVersion) - .Returns("v99.9"); - - mock.Setup((p) => p.CreateInstance(It.IsAny(), "v99.9")) - .Verifiable(); - - mock.Setup((p) => p.StartInstance(It.IsAny())) - .Verifiable(); - - mock.Setup((p) => p.StopInstance(It.IsAny(), null)) - .Verifiable(); - - mock.Setup((p) => p.DeleteInstance(It.IsAny())) - .Throws(new SqlLocalDbException("Error", errorCode)) - .Verifiable(); - - ISqlLocalDbApi api = mock.Object; - - // Act - using (TemporarySqlLocalDbInstance target = api.CreateTemporaryInstance()) - { - target.GetInstanceInfo(); - } - - // Assert - mock.Verify(); - } - [Fact] public void GetDefaultInstance_Throws_If_Api_Is_Null() { @@ -212,7 +248,7 @@ public void GetDefaultInstance_Throws_If_Api_Is_Null() Assert.Throws("api", () => api.GetDefaultInstance()); } - [Fact] + [WindowsOnlyFact] public void GetDefaultInstance_Returns_The_Default_Instance() { // Arrange @@ -239,7 +275,7 @@ public void GetInstances_Throws_If_Api_Is_Null() Assert.Throws("api", () => api.GetInstances()); } - [Fact] + [WindowsOnlyFact] public void GetInstances_Returns_All_The_Named_Instances() { // Arrange @@ -282,7 +318,7 @@ public void GetVersions_Throws_If_Api_Is_Null() Assert.Throws("api", () => api.GetVersions()); } - [Fact] + [WindowsOnlyFact] public void GetVersions_Returns_All_The_Installed_Versions() { // Arrange @@ -448,27 +484,6 @@ public void ShareInstance_Throws_If_Api_Is_Null() Assert.Throws("api", () => api.ShareInstance(instanceName, sharedInstanceName)); } - [Fact] - public void ShareInstance_Uses_SID_For_Current_User() - { - // Arrange - string instanceName = "SomeName"; - string sharedInstanceName = "SomeSharedName"; - - var mock = new Mock(); - - mock.Setup((p) => p.ShareInstance(It.IsNotNull(), instanceName, sharedInstanceName)) - .Verifiable(); - - ISqlLocalDbApi api = mock.Object; - - // Act - api.ShareInstance(instanceName, sharedInstanceName); - - // Assert - mock.Verify(); - } - [RunAsAdminFact] public void ShareInstance_Shares_Instance_For_Current_User() { diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs index 49ed542a..583307a6 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceManagerExtensionsTests.cs @@ -31,7 +31,7 @@ public void CreateConnection_Throws_If_Manager_Is_Null() Assert.Throws("manager", () => manager.CreateConnection()); } - [Fact] + [WindowsOnlyFact] public async Task CreateConnection_Creates_A_Sql_Connection() { // Arrange @@ -69,7 +69,7 @@ public void Restart_Throws_If_Manager_Is_Null() Assert.Throws("manager", () => manager.Restart()); } - [Fact] + [WindowsOnlyFact] public void Restart_Stops_And_Starts_Instance() { // Arrange diff --git a/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs b/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs index 3f91c13c..4b628ff9 100644 --- a/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs +++ b/tests/SqlLocalDb.Tests/RunAsAdminFactAttribute.cs @@ -28,15 +28,7 @@ public RunAsAdminFactAttribute() /// if the current user has Administrative /// privileges; otherwise . /// - internal static bool IsCurrentUserAdmin() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return false; - } - - return IsCurrentUserAdmin(out string name); - } + internal static bool IsCurrentUserAdmin() => IsCurrentUserAdmin(out string _); /// /// Returns whether the current user has Administrative privileges. @@ -48,6 +40,12 @@ internal static bool IsCurrentUserAdmin() /// private static bool IsCurrentUserAdmin(out string name) { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + name = Environment.UserName; + return false; + } + using (var identity = WindowsIdentity.GetCurrent()) { name = identity.Name; diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index 7b2a3680..f4b7b9a7 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -30,7 +30,7 @@ public void Constructor_Validates_Parameters() Assert.Throws("loggerFactory", () => new SqlLocalDbApi(options, null)); } - [Fact] + [WindowsOnlyFact] public void Constructor_Initializes_Instance() { // Arrange diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs index 133235a6..e8fc2cfb 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceManagerTests.cs @@ -19,46 +19,27 @@ public SqlLocalDbInstanceManagerTests(ITestOutputHelper outputHelper) _loggerFactory = outputHelper.AsLoggerFactory(); } - [Fact] - public void Constructor_Validates_Arguments() + [WindowsOnlyFact] + public static void Share_Shares_Instance() { // Act - ISqlLocalDbInstanceInfo instance = CreateInstance(); - var api = Mock.Of(); - - // Act and Assert - Assert.Throws("instance", () => new SqlLocalDbInstanceManager(null, api)); - Assert.Throws("api", () => new SqlLocalDbInstanceManager(instance, null)); - } + string sharedName = Guid.NewGuid().ToString(); - [Fact] - public void Constructor_Initializes_Properties() - { - // Act + var mock = new Mock(); ISqlLocalDbInstanceInfo instance = CreateInstance(); - var api = Mock.Of(); - - // Act - var actual = new SqlLocalDbInstanceManager(instance, api); + ISqlLocalDbApi api = mock.Object; - // Assert - actual.Name.ShouldBe("Name"); - actual.NamedPipe.ShouldBe("NamedPipe"); - } + var target = new SqlLocalDbInstanceManager(instance, api); - [Fact] - public void Share_Throws_If_SharedName_Is_Null() - { // Act - ISqlLocalDbInstanceInfo instance = CreateInstance(); - var api = Mock.Of(); - var target = new SqlLocalDbInstanceManager(instance, api); + target.Share(sharedName); - Assert.Throws("sharedName", () => target.Share(null)); + // Assert + mock.Verify((p) => p.ShareInstance(It.IsNotNull(), "Name", sharedName), Times.Once()); } - [Fact] - public void Share_Throws_If_SqlLocalDbEception_Is_Thrown() + [WindowsOnlyFact] + public static void Share_Throws_If_SqlLocalDbEception_Is_Thrown() { // Act var innerException = new SqlLocalDbException( @@ -85,22 +66,41 @@ public void Share_Throws_If_SqlLocalDbEception_Is_Thrown() } [Fact] - public void Share_Shares_Instance() + public void Constructor_Validates_Arguments() { // Act - string sharedName = Guid.NewGuid().ToString(); - - var mock = new Mock(); ISqlLocalDbInstanceInfo instance = CreateInstance(); - ISqlLocalDbApi api = mock.Object; + var api = Mock.Of(); - var target = new SqlLocalDbInstanceManager(instance, api); + // Act and Assert + Assert.Throws("instance", () => new SqlLocalDbInstanceManager(null, api)); + Assert.Throws("api", () => new SqlLocalDbInstanceManager(instance, null)); + } + [Fact] + public void Constructor_Initializes_Properties() + { // Act - target.Share(sharedName); + ISqlLocalDbInstanceInfo instance = CreateInstance(); + var api = Mock.Of(); + + // Act + var actual = new SqlLocalDbInstanceManager(instance, api); // Assert - mock.Verify((p) => p.ShareInstance(It.IsNotNull(), "Name", sharedName), Times.Once()); + actual.Name.ShouldBe("Name"); + actual.NamedPipe.ShouldBe("NamedPipe"); + } + + [Fact] + public void Share_Throws_If_SharedName_Is_Null() + { + // Act + ISqlLocalDbInstanceInfo instance = CreateInstance(); + var api = Mock.Of(); + var target = new SqlLocalDbInstanceManager(instance, api); + + Assert.Throws("sharedName", () => target.Share(null)); } [Fact] diff --git a/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs b/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs new file mode 100644 index 00000000..9787b543 --- /dev/null +++ b/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Attribute that is applied to a method to indicate that it is a fact that should be run by the test runner + /// if the current operating system in Windows. This class cannot be inherited. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class WindowsOnlyFactAttribute : FactAttribute + { + public WindowsOnlyFactAttribute() + : base() + { + Skip = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? string.Empty : $"This test can only be run on Windows."; + } + } +} From 3e64340ef54f660596d5b9a8a5adda21f17063a1 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:08:13 +0100 Subject: [PATCH 14/52] Fix AppVeyor not showing test failures Fix AppVeyor not showing why tests fail. --- Build.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Build.ps1 b/Build.ps1 index d488735c..c8c8e5d0 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -6,7 +6,9 @@ param( [Parameter(Mandatory = $false)][switch] $DisableCodeCoverage ) -$ErrorActionPreference = "Stop" +if ($null -eq $env:CI) { + $ErrorActionPreference = "Stop" +} $solutionPath = Split-Path $MyInvocation.MyCommand.Definition $solutionFile = Join-Path $solutionPath "SqlLocalDb.sln" From f3464db54dae076b646d6381c56063e482247f24 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:19:47 +0100 Subject: [PATCH 15/52] Override ToString() on SqlLocalDbVersionInfo Override ToString() on the SqlLocalDbVersionInfo class so it shows the name. --- src/SqlLocalDb/SqlLocalDbVersionInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbVersionInfo.cs b/src/SqlLocalDb/SqlLocalDbVersionInfo.cs index 156ded3f..e2df00ea 100644 --- a/src/SqlLocalDb/SqlLocalDbVersionInfo.cs +++ b/src/SqlLocalDb/SqlLocalDbVersionInfo.cs @@ -28,6 +28,9 @@ internal SqlLocalDbVersionInfo() /// public Version Version { get; internal set; } + /// + public override string ToString() => Name; + /// /// Updates the state of the instance from the specified value. /// From 0c9fa6944c4d561d0922e1bed1aae549e998fd3f Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:20:34 +0100 Subject: [PATCH 16/52] Add logging to debug test failure Add some logging to try and debug why a test is failing in AppVeyor. --- .../ISqlLocalDbApiExtensionsTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index c3b7a0b3..b9a74a9a 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -252,8 +252,15 @@ public void GetDefaultInstance_Throws_If_Api_Is_Null() public void GetDefaultInstance_Returns_The_Default_Instance() { // Arrange - using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + using (var api = new SqlLocalDbApi(_loggerFactory)) { + // HACK Debug failing test in AppVeyor + var logger = _loggerFactory.CreateLogger("xunit"); + logger.LogDebug($"Default Instance Name: {api.DefaultInstanceName}"); + logger.LogDebug($"Latest Version: {api.LatestVersion}"); + logger.LogDebug($"Instance Names: {string.Join(", ", api.GetInstanceNames())}"); + logger.LogDebug($"Versions: {string.Join(", ", api.Versions)}"); + // Act ISqlLocalDbInstanceInfo actual = api.GetDefaultInstance(); @@ -279,7 +286,7 @@ public void GetInstances_Throws_If_Api_Is_Null() public void GetInstances_Returns_All_The_Named_Instances() { // Arrange - using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + using (var api = new SqlLocalDbApi(_loggerFactory)) { // Act IReadOnlyList actual = api.GetInstances(); @@ -322,7 +329,7 @@ public void GetVersions_Throws_If_Api_Is_Null() public void GetVersions_Returns_All_The_Installed_Versions() { // Arrange - using (SqlLocalDbApi api = new SqlLocalDbApi(_loggerFactory)) + using (var api = new SqlLocalDbApi(_loggerFactory)) { // Act IReadOnlyList actual = api.GetVersions(); From d1100b52cb955854aa7ecc958ba56cc705f05e3c Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:26:54 +0100 Subject: [PATCH 17/52] Do not try to create automatic instances Do not try to create a default instance, as the attempt will fail. --- src/SqlLocalDb/ISqlLocalDbApiExtensions.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs index 465c0c40..cd9c724c 100644 --- a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs +++ b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs @@ -146,9 +146,14 @@ public static ISqlLocalDbInstanceInfo GetOrCreateInstance(this ISqlLocalDbApi ap // so need to query that separately to verify whether to get or create. ISqlLocalDbInstanceInfo info = api.GetInstanceInfo(instanceName); - if (info?.Exists == true) + if (info != null) { - return info; + // If it exists (or it's a default instance), we can't create + // it so just return the information about it immediately. + if (info.Exists || info.IsAutomatic) + { + return info; + } } } else From a887b2d1f8ec0bfdbf36aada90ad77fa2bc22313 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:27:19 +0100 Subject: [PATCH 18/52] Fix incorrect path in VS Code launch configuration Fix incorrect current working directory path for running tests in VS Code. --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 41a4bfed..eb678597 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "args": [ "test" ], - "cwd": "${workspaceRoot}/tests/MartinCostello.SqlLocalDb.Tests", + "cwd": "${workspaceRoot}/tests/SqlLocalDb.Tests", "console": "internalConsole", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart" From c8c5f9dad2e0582079707ac0ac8cdcca60529453 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:30:27 +0100 Subject: [PATCH 19/52] Fix incorrect assumption in test Remove assert for Exists as it will not be true if the default instance has never been started. --- tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index b9a74a9a..6c5acff7 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -254,19 +254,11 @@ public void GetDefaultInstance_Returns_The_Default_Instance() // Arrange using (var api = new SqlLocalDbApi(_loggerFactory)) { - // HACK Debug failing test in AppVeyor - var logger = _loggerFactory.CreateLogger("xunit"); - logger.LogDebug($"Default Instance Name: {api.DefaultInstanceName}"); - logger.LogDebug($"Latest Version: {api.LatestVersion}"); - logger.LogDebug($"Instance Names: {string.Join(", ", api.GetInstanceNames())}"); - logger.LogDebug($"Versions: {string.Join(", ", api.Versions)}"); - // Act ISqlLocalDbInstanceInfo actual = api.GetDefaultInstance(); // Assert actual.ShouldNotBeNull(); - actual.Exists.ShouldBeTrue(); actual.IsAutomatic.ShouldBeTrue(); actual.Name.ShouldBe(api.DefaultInstanceName); } From f50e98e6ed50da8bdf852b1bcfdfdf6827b5bed7 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:30:59 +0100 Subject: [PATCH 20/52] Restore error action preference in AppVeyor Restore AppVeyor CI builds failing if tests fail. --- Build.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index c8c8e5d0..d488735c 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -6,9 +6,7 @@ param( [Parameter(Mandatory = $false)][switch] $DisableCodeCoverage ) -if ($null -eq $env:CI) { - $ErrorActionPreference = "Stop" -} +$ErrorActionPreference = "Stop" $solutionPath = Split-Path $MyInvocation.MyCommand.Definition $solutionFile = Join-Path $solutionPath "SqlLocalDb.sln" From f5fe95da9300d2caac7ba4228f993f9ef8cfcf4f Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 13:55:12 +0100 Subject: [PATCH 21/52] Add InstanceExists() Prompted by #35, add a InstanceExists() method and change GetOrCreateInstance() to use it. --- src/SqlLocalDb/ISqlLocalDbApi.cs | 10 ++++ src/SqlLocalDb/ISqlLocalDbApiExtensions.cs | 21 ++----- src/SqlLocalDb/SqlLocalDbApi.cs | 32 +++++++++++ .../ISqlLocalDbApiExtensionsTests.cs | 4 +- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 55 +++++++++++++++++++ 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/SqlLocalDb/ISqlLocalDbApi.cs b/src/SqlLocalDb/ISqlLocalDbApi.cs index 19e76cc5..d82b5cd8 100644 --- a/src/SqlLocalDb/ISqlLocalDbApi.cs +++ b/src/SqlLocalDb/ISqlLocalDbApi.cs @@ -72,6 +72,16 @@ public interface ISqlLocalDbApi /// ISqlLocalDbVersionInfo GetVersionInfo(string version); + /// + /// Returns whether the specified LocalDB instance exists. + /// + /// The name of the LocalDB instance to check for existence. + /// + /// if the LocalDB instance specified by + /// exists; otherwise . + /// + bool InstanceExists(string instanceName); + /// /// Returns whether SQL LocalDB is installed on the current machine. /// diff --git a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs index cd9c724c..a18486f7 100644 --- a/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs +++ b/src/SqlLocalDb/ISqlLocalDbApiExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Security.Principal; namespace MartinCostello.SqlLocalDb @@ -138,9 +137,9 @@ public static ISqlLocalDbInstanceInfo GetOrCreateInstance(this ISqlLocalDbApi ap throw new ArgumentNullException(nameof(instanceName)); } - bool instanceExists = false; - - if (string.Equals(api.DefaultInstanceName, instanceName, StringComparison.Ordinal) || SqlLocalDbApi.IsDefaultInstanceName(instanceName)) + // Instance names in SQL Local DB are case-insensitive + if (string.Equals(api.DefaultInstanceName, instanceName, StringComparison.OrdinalIgnoreCase) || + SqlLocalDbApi.IsDefaultInstanceName(instanceName)) { // The default instance is always listed, even if it does not exist, // so need to query that separately to verify whether to get or create. @@ -156,20 +155,8 @@ public static ISqlLocalDbInstanceInfo GetOrCreateInstance(this ISqlLocalDbApi ap } } } - else - { - IReadOnlyList instanceNames = api.GetInstanceNames(); - - if (instanceNames != null) - { - // Instance names in SQL Local DB are case-insensitive - instanceExists = instanceNames - .Where((p) => string.Equals(p, instanceName, StringComparison.OrdinalIgnoreCase)) - .Any(); - } - } - if (instanceExists) + if (api.InstanceExists(instanceName)) { return api.GetInstanceInfo(instanceName); } diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index 28232b17..78c6bd1c 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -674,6 +674,38 @@ public ISqlLocalDbVersionInfo GetVersionInfo(string version) return result; } + /// + /// Returns whether the specified LocalDB instance exists. + /// + /// The name of the LocalDB instance to check for existence. + /// + /// if the LocalDB instance specified by + /// exists; otherwise . + /// + /// + /// is . + /// + /// + /// SQL Server LocalDB is not installed on the local machine. + /// + /// + /// The method is called from a non-Windows operating system. + /// + /// + /// Whether the SQL Server LocalDB instance specified by exists could not be determined. + /// + public bool InstanceExists(string instanceName) + { + try + { + return GetInstanceInfo(instanceName).Exists; + } + catch (SqlLocalDbException ex) when (ex.ErrorCode == SqlLocalDbErrors.UnknownInstance) + { + return false; + } + } + /// /// Returns whether SQL LocalDB is installed on the current machine. /// diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index 6c5acff7..4347be1f 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -430,8 +430,8 @@ public void GetOrCreateInstance_Returns_An_Instance_If_It_Exists() mock.Setup((p) => p.DefaultInstanceName) .Returns("Blah"); - mock.Setup((p) => p.GetInstanceNames()) - .Returns(new[] { "a", "b", instanceName }); + mock.Setup((p) => p.InstanceExists(instanceName)) + .Returns(true); mock.Setup((p) => p.GetInstanceInfo(instanceName)) .Returns(new SqlLocalDbInstanceInfo()); diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index f4b7b9a7..dcf688f6 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; using Shouldly; @@ -57,5 +58,59 @@ public void Constructor_Initializes_Instance() actual.Versions.ShouldBeUnique(); } } + + [WindowsOnlyFact] + public void Can_Get_Instances_From_Names() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + IReadOnlyList names = api.GetInstanceNames(); + + foreach (string name in names) + { + // Act + ISqlLocalDbInstanceInfo info = api.GetInstanceInfo(name); + + // Assert + info.ShouldNotBeNull(); + } + + // Arrange + string instanceName = Guid.NewGuid().ToString(); + + // Act + bool actual = api.InstanceExists(instanceName); + + // Assert + actual.ShouldBeFalse(); + } + } + + [WindowsOnlyFact] + public void Can_Test_Whether_Instances_Exist() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + // Arrange + string instanceName = api.DefaultInstanceName; + + // Act + bool actual = api.InstanceExists(instanceName); + + // Assert + actual.ShouldBeTrue(); + + // Arrange + instanceName = Guid.NewGuid().ToString(); + + // Act + actual = api.InstanceExists(instanceName); + + // Assert + actual.ShouldBeFalse(); + } + } } } From 2e7a52933fe66d1241124dd1a1c16f2a94f5c83a Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:02:17 +0100 Subject: [PATCH 22/52] Fix test broken in AppVeyor Fix test that fails in AppVeyor because the default instance has never been started. --- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 34 +++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index dcf688f6..4f8e4f8f 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -93,23 +93,33 @@ public void Can_Test_Whether_Instances_Exist() // Arrange using (var api = new SqlLocalDbApi(_loggerFactory)) { - // Arrange - string instanceName = api.DefaultInstanceName; + // Start the default instance to ensure it exists + api.StartInstance(api.DefaultInstanceName); - // Act - bool actual = api.InstanceExists(instanceName); + try + { + // Arrange + string instanceName = api.DefaultInstanceName; - // Assert - actual.ShouldBeTrue(); + // Act + bool actual = api.InstanceExists(instanceName); - // Arrange - instanceName = Guid.NewGuid().ToString(); + // Assert + actual.ShouldBeTrue(); - // Act - actual = api.InstanceExists(instanceName); + // Arrange + instanceName = Guid.NewGuid().ToString(); - // Assert - actual.ShouldBeFalse(); + // Act + actual = api.InstanceExists(instanceName); + + // Assert + actual.ShouldBeFalse(); + } + finally + { + api.StopInstance(api.DefaultInstanceName); + } } } } From ee638c5a788092512d7ead073d1557af7b6c242a Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:18:33 +0100 Subject: [PATCH 23/52] Add Linux/macOS tests Add tests specific to behaviour on Linux/macOS. Return an empty arry for SqlLocalDbApi.Versions on unsupported platforms instead of throwing an exception. --- src/SqlLocalDb/SqlLocalDbApi.cs | 8 ++-- .../NotWindowsFactAttribute.cs | 23 ++++++++++ tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 43 +++++++++++++++++++ .../WindowsOnlyFactAttribute.cs | 4 +- 4 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 tests/SqlLocalDb.Tests/NotWindowsFactAttribute.cs diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index 78c6bd1c..c8ea877d 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -247,9 +247,6 @@ public TimeSpan StopTimeout /// /// SQL Server LocalDB is not installed on the local machine. /// - /// - /// The method is called from a non-Windows operating system. - /// /// /// The installed versions of SQL LocalDB could not be determined. /// @@ -257,6 +254,11 @@ public IReadOnlyList Versions { get { + if (!IsWindows) + { + return Array.Empty(); + } + if (_versions == null) { // Use lazy initialization to allow some functionality to be diff --git a/tests/SqlLocalDb.Tests/NotWindowsFactAttribute.cs b/tests/SqlLocalDb.Tests/NotWindowsFactAttribute.cs new file mode 100644 index 00000000..e345a6c9 --- /dev/null +++ b/tests/SqlLocalDb.Tests/NotWindowsFactAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Attribute that is applied to a method to indicate that it is a fact that should be run by the + /// test runner if the current operating system is not Windows. This class cannot be inherited. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class NotWindowsFactAttribute : FactAttribute + { + public NotWindowsFactAttribute() + : base() + { + Skip = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? string.Empty : $"This test can only be run on Windows."; + } + } +} diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index 4f8e4f8f..eddc86db 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -122,5 +122,48 @@ public void Can_Test_Whether_Instances_Exist() } } } + + [NotWindowsFact] + public void Does_Not_Throw_PlatformNotSupportedException() + { + // Arrange + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act and Assert + actual.DefaultInstanceName.ShouldBe(string.Empty); + actual.IsLocalDBInstalled().ShouldBeFalse(); + actual.Versions.ShouldBeEmpty(); + } + } + + [NotWindowsFact] + public void Throws_PlatformNotSupportedException() + { + // Arrange + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act and Assert + Assert.Throws(() => actual.CreateInstance("name")); + Assert.Throws(() => actual.CreateTemporaryInstance()); + Assert.Throws(() => actual.DeleteInstance("name")); + Assert.Throws(() => actual.DeleteUserInstances()); + Assert.Throws(() => actual.GetDefaultInstance()); + Assert.Throws(() => actual.GetInstanceInfo("name")); + Assert.Throws(() => actual.GetInstanceNames()); + Assert.Throws(() => actual.GetInstances()); + Assert.Throws(() => actual.GetOrCreateInstance("name")); + Assert.Throws(() => actual.GetVersionInfo("name")); + Assert.Throws(() => actual.InstanceExists("name")); + Assert.Throws(() => actual.LatestVersion); + Assert.Throws(() => actual.ShareInstance("name", "sharedName")); + Assert.Throws(() => actual.ShareInstance("sid", "name", "sharedName")); + Assert.Throws(() => actual.StartInstance("name")); + Assert.Throws(() => actual.StartTracing()); + Assert.Throws(() => actual.StopInstance("name")); + Assert.Throws(() => actual.StopTracing()); + Assert.Throws(() => actual.UnshareInstance("name")); + Assert.Throws(() => actual.Versions); + } + } } } diff --git a/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs b/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs index 9787b543..210ed9c8 100644 --- a/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs +++ b/tests/SqlLocalDb.Tests/WindowsOnlyFactAttribute.cs @@ -8,8 +8,8 @@ namespace MartinCostello.SqlLocalDb { /// - /// Attribute that is applied to a method to indicate that it is a fact that should be run by the test runner - /// if the current operating system in Windows. This class cannot be inherited. + /// Attribute that is applied to a method to indicate that it is a fact that should be run by the + /// test runner if the current operating system is Windows. This class cannot be inherited. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class WindowsOnlyFactAttribute : FactAttribute From c79dff81c5e9ae4eeec0a46b3640acb5a739c489 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:22:07 +0100 Subject: [PATCH 24/52] Fix broken Linux/macOS test Fix PlatformNotSupportedException not being thrown by LatestVersion. --- src/SqlLocalDb/SqlLocalDbApi.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index c8ea877d..21adebb4 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -191,6 +191,8 @@ public string LatestVersion { get { + EnsurePlatformSupported(); + // Access through property to ensure initialized IReadOnlyList versions = Versions; From 9744a2b3c928105ce6dbcf0994f1ad133e49b327 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:26:09 +0100 Subject: [PATCH 25/52] Fix another missing PNSE Add PlatformNotSupportedException for non-Windows OS when trying to delete an instance. --- src/SqlLocalDb/SqlLocalDbApi.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index 21adebb4..bc0ff943 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -1115,6 +1115,8 @@ internal bool DeleteInstanceInternal(string instanceName, bool throwIfNotFound, throw new ArgumentNullException(nameof(instanceName)); } + EnsurePlatformSupported(); + Logger.DeletingInstance(instanceName); int hr = _api.DeleteInstance(instanceName, ReservedValue); From 46ca4a51881567668e07bc248b76b8cc8bd261f5 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:33:18 +0100 Subject: [PATCH 26/52] Honour [ExcludeFromCodeCoverage] Exclude members marked with [ExcludeFromCodeCoverage] from the code coverage metrics. --- Build.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Build.ps1 b/Build.ps1 index d488735c..c63de035 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -116,6 +116,7 @@ function DotNetTest { `"-target:$dotnetPath`" ` `"-targetargs:test $Project --output $OutputPath`" ` -output:$coverageOutput ` + `"-excludebyattribute:System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage*`" ` -hideskipped:All ` -mergebyhash ` -mergeoutput ` From 15176e04a1089089d3fd11a9220fee5283af6dca Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:35:36 +0100 Subject: [PATCH 27/52] Fix incorrect assert Remove incorrect assert for CreateTemporaryInstance() by itself, as if you don't do anything with it it's lazy. --- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index eddc86db..0b237b01 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -144,7 +144,6 @@ public void Throws_PlatformNotSupportedException() { // Act and Assert Assert.Throws(() => actual.CreateInstance("name")); - Assert.Throws(() => actual.CreateTemporaryInstance()); Assert.Throws(() => actual.DeleteInstance("name")); Assert.Throws(() => actual.DeleteUserInstances()); Assert.Throws(() => actual.GetDefaultInstance()); From 7e79d06350b6b7a076db596c668d559526072ba8 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:38:44 +0100 Subject: [PATCH 28/52] Fix another incorrect assert Fix assert made incorrect by changes in ee638c5a788092512d7ead073d1557af7b6c242a. --- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index 0b237b01..a03a53fe 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -161,7 +161,6 @@ public void Throws_PlatformNotSupportedException() Assert.Throws(() => actual.StopInstance("name")); Assert.Throws(() => actual.StopTracing()); Assert.Throws(() => actual.UnshareInstance("name")); - Assert.Throws(() => actual.Versions); } } } From 383c3befe81bf515b078aa106146d690c6543e57 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 14:45:39 +0100 Subject: [PATCH 29/52] Add ToString() override for SqlLocalDbInstanceInfo Add a override for ToString() to SqlLocalDbInstanceInfo and add unit tests for the two ToString() overrides. --- src/SqlLocalDb/SqlLocalDbInstanceInfo.cs | 3 +++ .../SqlLocalDbInstanceInfoTests.cs | 26 +++++++++++++++++++ .../SqlLocalDbVersionInfoTests.cs | 18 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs index 86b8aea7..19df2aae 100644 --- a/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs +++ b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs @@ -55,6 +55,9 @@ internal SqlLocalDbInstanceInfo() /// public string SharedName { get; internal set; } + /// + public override string ToString() => Name; + /// /// Updates the state of the instance from the specified value. /// diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs index 786e1be7..273c9fa7 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs @@ -107,5 +107,31 @@ public static void Update_Does_Not_Copy_State_If_Other_Is_Self() // Act (no Assert) actual.Update(actual); } + + [Fact] + public static void ToString_Returns_The_Name() + { + // Arrange + var info = new SqlLocalDbInstanceInfo() + { + ConfigurationCorrupt = true, + Exists = true, + IsAutomatic = true, + IsRunning = true, + IsShared = true, + LastStartTimeUtc = DateTime.UtcNow, + LocalDbVersion = new Version(2, 1), + Name = "Name", + NamedPipe = "NamedPipe", + OwnerSid = "OwnerSid", + SharedName = "SharedName", + }; + + // Act and Assert + string actual = info.ToString(); + + // Assert + actual.ShouldBe("Name"); + } } } diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs index fd1c3d35..571e54a6 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbVersionInfoTests.cs @@ -77,5 +77,23 @@ public static void Update_Does_Not_Copy_State_If_Other_Is_Self() actual.Name.ShouldBe("Name"); actual.Version.ShouldBe(new Version(2, 0)); } + + [Fact] + public static void ToString_Returns_The_Name() + { + // Arrange + var version = new SqlLocalDbVersionInfo() + { + Exists = false, + Name = "Name", + Version = new Version(2, 0), + }; + + // Act and Assert + string actual = version.ToString(); + + // Assert + actual.ShouldBe("Name"); + } } } From c348062d7bbdb1092c6d6125ccd02a5b1ac148da Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 15:44:23 +0100 Subject: [PATCH 30/52] Add more unit tests for SqlLocalDbApi Add more unit tests for the SqlLocalDbApi class. --- tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 226 ++++++++++++++++++ .../WindowsCIOnlyFactAttribute.cs | 27 +++ 2 files changed, 253 insertions(+) create mode 100644 tests/SqlLocalDb.Tests/WindowsCIOnlyFactAttribute.cs diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index a03a53fe..ee35d1f1 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using Shouldly; using Xunit; using Xunit.Abstractions; @@ -29,6 +31,7 @@ public void Constructor_Validates_Parameters() Assert.Throws("options", () => new SqlLocalDbApi(null, _loggerFactory)); Assert.Throws("loggerFactory", () => new SqlLocalDbApi(null)); Assert.Throws("loggerFactory", () => new SqlLocalDbApi(options, null)); + Assert.Throws("registry", () => new SqlLocalDbApi(options, null, _loggerFactory)); } [WindowsOnlyFact] @@ -163,5 +166,228 @@ public void Throws_PlatformNotSupportedException() Assert.Throws(() => actual.UnshareInstance("name")); } } + + [WindowsOnlyFact] + public void Can_Start_And_Stop_Tracing() + { + // Arrange + using (var api = new SqlLocalDbApi(_loggerFactory)) + { + // Act (no Assert) + api.StartTracing(); + api.StopTracing(); + } + } + + [WindowsOnlyFact] + public void Throws_InvalidOperationException_If_SQL_LocalDB_Not_Installed() + { + // Arrange + var options = new SqlLocalDbOptions(); + var registry = Mock.Of(); + + using (var actual = new SqlLocalDbApi(options, registry, _loggerFactory)) + { + // Act and Assert + Assert.Throws(() => actual.CreateInstance("name")); + Assert.Throws(() => actual.DeleteInstance("name")); + Assert.Throws(() => actual.DeleteUserInstances()); + Assert.Throws(() => actual.GetDefaultInstance()); + Assert.Throws(() => actual.GetInstanceInfo("name")); + Assert.Throws(() => actual.GetInstanceNames()); + Assert.Throws(() => actual.GetInstances()); + Assert.Throws(() => actual.GetOrCreateInstance("name")); + Assert.Throws(() => actual.GetVersionInfo("name")); + Assert.Throws(() => actual.InstanceExists("name")); + Assert.Throws(() => actual.LatestVersion); + Assert.Throws(() => actual.ShareInstance("name", "sharedName")); + Assert.Throws(() => actual.StartInstance("name")); + Assert.Throws(() => actual.StartTracing()); + Assert.Throws(() => actual.StopInstance("name")); + Assert.Throws(() => actual.StopTracing()); + Assert.Throws(() => actual.UnshareInstance("name")); + } + } + + [WindowsOnlyFact] + public void StopTimeout_Throws_If_Value_Is_Negative() + { + // Arrange + TimeSpan value = TimeSpan.Zero.Add(TimeSpan.FromTicks(-1)); + + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act and Assert + var exception = Assert.Throws("value", () => actual.StopTimeout = value); + exception.ActualValue.ShouldBe(value); + } + } + + [WindowsOnlyFact] + public void Methods_Validate_Parameters() + { + // Arrange + TimeSpan timeout = TimeSpan.Zero.Add(TimeSpan.FromTicks(-1)); + + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act and Assert + Assert.Throws("instanceName", () => actual.CreateInstance(null, "version")); + Assert.Throws("version", () => actual.CreateInstance("instanceName", null)); + Assert.Throws("instanceName", () => actual.DeleteInstance(null)); + Assert.Throws("instanceName", () => actual.GetInstanceInfo(null)); + Assert.Throws("version", () => actual.GetVersionInfo(null)); + Assert.Throws("ownerSid", () => actual.ShareInstance(null, "instanceName", "sharedInstanceName")); + Assert.Throws("instanceName", () => actual.ShareInstance("ownerSid", null, "sharedInstanceName")); + Assert.Throws("sharedInstanceName", () => actual.ShareInstance("ownerSid", "instanceName", null)); + Assert.Throws("instanceName", () => actual.ShareInstance("sid", string.Empty, "sharedInstanceName")); + Assert.Throws("instanceName", () => actual.StartInstance(null)); + Assert.Throws("instanceName", () => actual.StopInstance(null, TimeSpan.Zero)); + Assert.Throws("timeout", () => actual.StopInstance("instanceName", timeout)).ActualValue.ShouldBe(timeout); + Assert.Throws("instanceName", () => actual.UnshareInstance(null)); + } + } + + [WindowsOnlyFact] + public void DeleteInstanceInternal_Returns_False_If_ThrownIfNotFound_Is_False_And_Instance_Does_Not_Exist() + { + // Arrange + TimeSpan timeout = TimeSpan.Zero.Add(TimeSpan.FromTicks(-1)); + + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act and Assert + actual.DeleteInstanceInternal("NotARealInstance", throwIfNotFound: false).ShouldBeFalse(); + } + } + + [WindowsOnlyFact] + public void Can_Manage_SqlLocalDB_Instances() + { + // Arrange + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + string instanceName = Guid.NewGuid().ToString(); + + // Act + ISqlLocalDbInstanceInfo instance = actual.GetInstanceInfo(instanceName); + + // Assert + instance.ShouldNotBeNull(); + instance.Name.ShouldBe(instanceName); + instance.Exists.ShouldBeFalse(); + instance.IsRunning.ShouldBeFalse(); + + // Act and Assert + actual.InstanceExists(instanceName).ShouldBeFalse(); + + // Act + actual.CreateInstance(instanceName); + + // Assert + instance = actual.GetInstanceInfo(instanceName); + instance.ShouldNotBeNull(); + instance.Name.ShouldBe(instanceName); + instance.Exists.ShouldBeTrue(); + instance.IsRunning.ShouldBeFalse(); + + // Act + actual.StartInstance(instanceName); + + // Assert + instance = actual.GetInstanceInfo(instanceName); + instance.ShouldNotBeNull(); + instance.Name.ShouldBe(instanceName); + instance.Exists.ShouldBeTrue(); + instance.IsRunning.ShouldBeTrue(); + + // Act and Assert + actual.InstanceExists(instanceName).ShouldBeTrue(); + + // Act + actual.StopInstance(instanceName); + + // Assert + instance = actual.GetInstanceInfo(instanceName); + instance.ShouldNotBeNull(); + instance.Name.ShouldBe(instanceName); + instance.Exists.ShouldBeTrue(); + instance.IsRunning.ShouldBeFalse(); + + // Act + actual.DeleteInstance(instanceName); + + // Assert + instance = actual.GetInstanceInfo(instanceName); + instance.ShouldNotBeNull(); + instance.Name.ShouldBe(instanceName); + instance.Exists.ShouldBeFalse(); + instance.IsRunning.ShouldBeFalse(); + + // Act and Assert + actual.InstanceExists(instanceName).ShouldBeFalse(); + + // Act (no Assert) + actual.DeleteInstanceFiles(instanceName); + } + } + + [WindowsOnlyFact] + public void Can_Create_SqlLocalDB_Instances_With_Different_Versions() + { + // Arrange + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + foreach (string version in actual.Versions) + { + // Act + ISqlLocalDbVersionInfo versionInfo = actual.GetVersionInfo(version); + + // Assert + versionInfo.ShouldNotBeNull(); + versionInfo.Name.ShouldStartWith(version.Split('.').First()); + versionInfo.Exists.ShouldBeTrue(); + versionInfo.Version.ShouldNotBeNull(); + versionInfo.Version.ShouldNotBe(new Version()); + + string instanceName = Guid.NewGuid().ToString(); + + // Act + actual.CreateInstance(instanceName, version); + + // Assert + ISqlLocalDbInstanceInfo instanceInfo = actual.GetInstanceInfo(instanceName); + instanceInfo.ShouldNotBeNull(); + instanceInfo.Name.ShouldBe(instanceName); + instanceInfo.Exists.ShouldBeTrue(); + instanceInfo.IsRunning.ShouldBeFalse(); + instanceInfo.LocalDbVersion.ShouldBe(versionInfo.Version); + + // Act (no Assert) + actual.DeleteInstance(instanceName); + actual.DeleteInstanceFiles(instanceName); + } + } + } + + [WindowsCIOnlyFact] + public void Can_Delete_User_Instances() + { + // Arrange + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + actual.CreateInstance(Guid.NewGuid().ToString()); + + IReadOnlyList namesBefore = actual.GetInstanceNames(); + + // Act + int deleted = actual.DeleteUserInstances(deleteFiles: true); + + // Assert + deleted.ShouldBeGreaterThanOrEqualTo(1); + IReadOnlyList namesAfter = actual.GetInstanceNames(); + namesAfter.ShouldBeSubsetOf(namesBefore); + } + } } } diff --git a/tests/SqlLocalDb.Tests/WindowsCIOnlyFactAttribute.cs b/tests/SqlLocalDb.Tests/WindowsCIOnlyFactAttribute.cs new file mode 100644 index 00000000..c3ef6cb1 --- /dev/null +++ b/tests/SqlLocalDb.Tests/WindowsCIOnlyFactAttribute.cs @@ -0,0 +1,27 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Attribute that is applied to a method to indicate that it is a fact that should be run by the + /// test runner if the current operating system is Windows. This class cannot be inherited. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class WindowsCIOnlyFactAttribute : FactAttribute + { + public WindowsCIOnlyFactAttribute() + : base() + { + bool isWindowsCI = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")); + + Skip = isWindowsCI ? string.Empty : $"This test can only be run on Windows CI."; + } + } +} From d55b18418d849e7051bea397455e7eb9d00c18b3 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 5 Aug 2018 15:52:21 +0100 Subject: [PATCH 31/52] Add log message for library unload Add a logging message for when the Instance API DLL is unloaded. --- src/SqlLocalDb/EventIds.cs | 5 +++++ src/SqlLocalDb/ILoggerExtensions.cs | 16 ++++++++++++++++ src/SqlLocalDb/Interop/LocalDbInstanceApi.cs | 16 +++++++++++++++- src/SqlLocalDb/SR.Designer.cs | 9 +++++++++ src/SqlLocalDb/SR.resx | 3 +++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/SqlLocalDb/EventIds.cs b/src/SqlLocalDb/EventIds.cs index ae1e214b..48894fae 100644 --- a/src/SqlLocalDb/EventIds.cs +++ b/src/SqlLocalDb/EventIds.cs @@ -282,6 +282,11 @@ internal static class EventIds /// internal static readonly EventId UnsharedInstance = new EventId(++Id, nameof(UnsharedInstance)); + /// + /// The for when the SQL LocalDB Instance API is unloaded. This field is read-only. + /// + internal static readonly EventId NativeApiUnloaded = new EventId(++Id, nameof(NativeApiUnloaded)); + /// /// The base Id for the event Ids. /// diff --git a/src/SqlLocalDb/ILoggerExtensions.cs b/src/SqlLocalDb/ILoggerExtensions.cs index 13e1ecc1..17e1cc55 100644 --- a/src/SqlLocalDb/ILoggerExtensions.cs +++ b/src/SqlLocalDb/ILoggerExtensions.cs @@ -219,6 +219,14 @@ internal static class ILoggerExtensions EventIds.NativeApiPathNotFound, SR.ILoggerExtensions_NativeApiNotFoundFormat); + /// + /// Logging delegate for when the SQL LocalDB Instance API DLL was unloaded. + /// + private static readonly Action _nativeApiUnloaded = LoggerMessage.Define( + LogLevel.Debug, + EventIds.NativeApiUnloaded, + SR.ILoggerExtensions_NativeApiUnloadedFormat); + /// /// Logging delegate for when the version of the SQL LocalDB Instance API to use was overridden by the user. /// @@ -567,6 +575,14 @@ internal static void NativeApiNotFound(this ILogger logger) internal static void NativeApiNotLoaded(this ILogger logger) => _nativeApiNotLoaded(logger, null); + /// + /// Logs that the SQL LocalDB Instance API DLL was unloaded. + /// + /// The logger to use. + /// The full path to the DLL that was unloaded. + internal static void NativeApiUnloaded(this ILogger logger, string fileName) + => _nativeApiUnloaded(logger, fileName, null); + /// /// Logs that the version of the SQL LocalDB Instance API to use was overridden by the user. /// diff --git a/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs index 9a17839a..a63882e9 100644 --- a/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs +++ b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs @@ -56,6 +56,11 @@ internal sealed class LocalDbInstanceApi : IDisposable /// private bool _disposed; + /// + /// The path of the library that was loaded. + /// + private string _libraryPath; + /// /// The handle to the native SQL LocalDB API. /// @@ -628,6 +633,7 @@ private SafeLibraryHandle EnsureLocalDBLoaded() else { Logger.NativeApiLoaded(fileName); + _libraryPath = fileName; } } } @@ -686,7 +692,15 @@ private void Dispose(bool disposing) } // Dispose of unmanaged resources - _handle?.Dispose(); + if (_handle != null) + { + _handle.Dispose(); + + Logger.NativeApiUnloaded(_libraryPath); + + _libraryPath = null; + } + _disposed = true; } } diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index f37c3c8b..85274eb8 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -294,6 +294,15 @@ internal static string ILoggerExtensions_NativeApiNotLoaded { } } + /// + /// Looks up a localized string similar to Unloaded SQL LocalDB Instance API library '{0}'.. + /// + internal static string ILoggerExtensions_NativeApiUnloadedFormat { + get { + return ResourceManager.GetString("ILoggerExtensions_NativeApiUnloadedFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to SQL LocalDB returned HRESULT {0:X}.. /// diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index 19c4b863..2f148fb4 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -291,4 +291,7 @@ The SQL LocalDB Instance API is not supported on this platform. + + Unloaded SQL LocalDB Instance API library '{0}'. + \ No newline at end of file From bc8fbdfc98143c1bcf9176443d9624425595f43e Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 11 Aug 2018 12:13:14 +0100 Subject: [PATCH 32/52] Initial README updates for 2.0.0 Initial updates to the README document for 2.0.0. --- readme.md | 108 +++++++++++++++++++++++------------------------------- 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/readme.md b/readme.md index ab17f731..a3be55d0 100644 --- a/readme.md +++ b/readme.md @@ -1,106 +1,88 @@ # SQL LocalDB Wrapper -[![Build Status](https://img.shields.io/appveyor/ci/martincostello/sqllocaldb/master.svg)](https://ci.appveyor.com/project/martincostello/sqllocaldb) [![Coverage Status](https://coveralls.io/repos/martincostello/sqllocaldb/badge.svg?branch=master)](https://coveralls.io/r/martincostello/sqllocaldb?branch=master) +SQL LocalDB Wrapper is a .NET Standard 2.0 library providing interop with the [Microsoft SQL Server LocalDB](https://docs.microsoft.com/en-us/sql/relational-databases/express-localdb-instance-apis/sql-server-express-localdb-reference-instance-apis?view=sql-server-2017 "SQL Server Express LocalDB Reference - Instance APIs") Instance API from managed code using .NET APIs. - +[![NuGet](https://buildstats.info/nuget/MartinCostello.SqlLocalDb)](http://www.nuget.org/packages/MartinCostello.SqlLocalDb "Download MartinCostello.SqlLocalDb from NuGet") -[![NuGet](https://buildstats.info/nuget/System.Data.SqlLocalDb)](http://www.nuget.org/packages/System.Data.SqlLocalDb) +| | Windows | Linux | +|:-:|:-:|:-:| +| **Build Status** | [![Windows build status](https://img.shields.io/appveyor/ci/martincostello/sqllocaldb/master.svg)](https://ci.appveyor.com/project/martincostello/sqllocaldb) [![Code coverage](https://codecov.io/gh/martincostello/sqllocaldb/branch/master/graph/badge.svg)](https://codecov.io/gh/martincostello/sqllocaldb) | [![Linux build status](https://img.shields.io/travis/martincostello/sqllocaldb/master.svg)](https://travis-ci.org/martincostello/sqllocaldb) | +| **Build History** | [![Windows build history](https://buildstats.info/appveyor/chart/martincostello/sqllocaldb?branch=master&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/martincostello/sqllocaldb) | [![Linux build history](https://buildstats.info/travisci/chart/martincostello/sqllocaldb?branch=master&includeBuildsFromPullRequest=false)](https://travis-ci.org/martincostello/sqllocaldb) | -[![Join the chat at https://gitter.im/martincostello/sqllocaldb](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/martincostello/sqllocaldb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +## Introduction -[![Build History](https://buildstats.info/appveyor/chart/martincostello/sqllocaldb?branch=master&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/martincostello/sqllocaldb) +This library exposes types that wrap the native SQL LocalDB Instance API to perform operations on SQL LocalDB such as for managing instances (create, delete, start, stop) and obtaining SQL connection strings for existing instances. -## Overview +Microsoft SQL Server LocalDB 2012 and later is supported for both x86 and x64 on Microsoft Windows and targets `netstandard2.0`. -SQL LocalDB Wrapper is a .NET 4.0 assembly providing interop with the [Microsoft SQL Server LocalDB](http://msdn.microsoft.com/en-us/library/hh510202.aspx) native API from managed code using .NET APIs. +While the library can be compiled and referenced in .NET Core applications on non-Windows Operating Systems, SQL LocalDB is only supported on Windows. Non-Windows Operating Systems can query to determine that the SQL LocalDB Instance API is not installed, but other usage will cause a `PlatformNotSupportedException` to be thrown. -It is designed to support use of dependency injection by consumers by implementing interfaces, and is also designed to fit with the other data access providers defined under the System.Data namespaces. +### Installation -The assembly supports using SQL Server LocalDB 2012 and 2014 for both the x86 and x64 platforms and SQL Server LocalDB 2016 for the x64 platform. +To install the library from [NuGet](https://www.nuget.org/packages/MartinCostello.SqlLocalDb/ "MartinCostello.SqlLocalDb on NuGet.org") using the .NET SDK run: -## Downloads - -The recommended way of obtaining the assembly is using [NuGet](https://www.nuget.org/packages/System.Data.SqlLocalDb). - -Alternatively, a ZIP file containing the assembly can be downloaded from [GitHub](https://github.com/martincostello/sqllocaldb/releases/latest). - -## Documentation - -### Basic Usage - -First install the [NuGet package](https://www.nuget.org/packages/System.Data.SqlLocalDb/): - -```batchfile -Install-Package System.Data.SqlLocalDb ``` - -Add the appropriate namespace: - -```csharp -using System.Data.SqlLocalDb; +dotnet add package MartinCostello.SqlLocalDb ``` -Then create an instance, start it and connect to it: +### Basic Examples -```csharp -ISqlLocalDbProvider provider = new SqlLocalDbProvider(); -ISqlLocalDbInstance instance = provider.GetOrCreateInstance("MyInstance"); +_TODO_ -instance.Start(); +### Further Examples -using (SqlConnection connection = instance.CreateConnection()) -{ - connection.Open(); +Further examples of using the library can be found by following the links below: - // Use the connection... -} +_TODO_ -instance.Stop(); -``` +## Migrating from System.Data.SqlLocalDb -### Further Details +Version `1.x.x` of this library was previously published as [`System.Data.SqlLocalDb`](https://www.nuget.org/packages/System.Data.SqlLocalDb/ "System.Data.SqlLocalDb on NuGet"). The current version (`2.x.x`) has been renamed and is a breaking change to the previous version with various changes to namespaces and types. -For further documentation about the assembly and how to use it, consult the [Wiki](https://github.com/martincostello/sqllocaldb/wiki) in GitHub. - -You can also check out the [examples below](https://github.com/martincostello/sqllocaldb#examples). - -## Examples - - 1. An example of using the API can be found [here](https://github.com/martincostello/sqllocaldb/blob/master/src/TestApp/Program.cs) in the TestApp project in the source code. - 1. ~~An runnable example solution using the API to test an ASP.NET MVC application using SQL Server with MSTest is included as [BlogSample.sln](https://github.com/martincostello/sqllocaldb/blob/master/src/BlogSample.sln) in the source code.~~ +To migrate a project from using the previous version follow the migration guide: [Migrating to MartinCostello.SqlLocalDb from System.Data.SqlLocalDb](https://github.com/martincostello/sqllocaldb/wiki/Migrating-to-MartinCostello.SqlLocalDb-from-System.Data.SqlLocalDb "Migrating to MartinCostello.SqlLocalDb from System.Data.SqlLocalDb") ## Feedback -Any feedback or issues can be added to the issues for this project in [GitHub](https://github.com/martincostello/sqllocaldb/issues). +Any feedback or issues can be added to the issues for this project in [GitHub](https://github.com/martincostello/sqllocaldb/issues "Issues for this project on GitHub.com"). ## Repository -The repository is hosted in [GitHub](https://github.com/martincostello/sqllocaldb): https://github.com/martincostello/sqllocaldb.git +The repository is hosted in [GitHub](https://github.com/martincostello/sqllocaldb "This project on GitHub.com"): https://github.com/martincostello/sqllocaldb.git ## License -This project is licensed under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) license. +This project is licensed under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt "The Apache 2.0 license") license. ## Building and Testing -Building and testing the project is supported using Microsoft Visual Studio 2017. +Compiling the library yourself requires Git and the [.NET Core SDK](https://www.microsoft.com/net/download/core "Download the .NET Core SDK") to be installed (version 2.1.302 or later). + +For all of the tests to be functional you must also have at least one version of SQL LocalDB installed. -The simplest way to build and test the assembly from the source code is by using the [Build.ps1](https://github.com/martincostello/sqllocaldb/blob/master/Build.ps1) script in the root of the repository like so: +To build and test the library locally from a terminal/command-line, run one of the following set of commands: -```batchfile +**Windows** + +```powershell +git clone https://github.com/martincostello/sqllocaldb.git +cd sqllocaldb .\Build.ps1 ``` -The project can also be built and tested from Visual Studio. +**Note**: To run all the tests successfully, you must run either `Build.ps1` or Visual Studio with administrative privileges. This is because the SQL LocalDB APIs for sharing LocalDB instances can only be used with administrative privileges. Not running the tests with administrative privileges will cause all tests that exercise such functionality to be skipped. + +**Linux/macOS** -Building the project from the command-line using [Build.ps1](https://github.com/martincostello/sqllocaldb/blob/master/Build.ps1) invokes MSBuild to compile the source, examples and tests (including running the configured static analysis tools), and then uses MSTest to test the compiled assembly (```System.Data.SqlLocalDb.dll```). +```sh +git clone https://github.com/martincostello/sqllocaldb.git +cd sqllocaldb +./build.sh +``` -The standard build process also includes running [StyleCop](https://github.com/DotNetAnalyzers/StyleCopAnalyzers). +**Note**: Several tests are skipped on non-Windows Operating Systems. -To only compile the source code and not run the tests, use the following command: +## Copyright and Trademarks -```batchfile -.\Build.ps1 -RunTests $False -``` +This library is copyright (©) Martin Costello 2012-2018. -__Note__: To run all the tests, you must run either ```Build.ps1``` or Visual Studio with administrative privileges. This is because the SQL LocalDB APIs for sharing LocalDB instances can only be used with administrative privileges. Not running the tests with administrative privileges will cause all tests that exercise such functionality to be marked as Inconclusive by MSTest. +[Microsoft SQL Server](https://www.microsoft.com/en-gb/sql-server/) is a trademark and copyright of the Microsoft Corporation. From 178da5cd7c25d8b64f1fa3952d3ccce4a80f0341 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 11 Aug 2018 12:15:52 +0100 Subject: [PATCH 33/52] Add C# code preferences Add C# code preferences for EditorConfig. --- .editorconfig | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/.editorconfig b/.editorconfig index 2a098e24..6294ecf9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,125 @@ trim_trailing_whitespace = true [*.{csproj,json,props,ruleset,targets}] indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true + +# this. preferences +dotnet_style_qualification_for_field = false:none +dotnet_style_qualification_for_property = false:none +dotnet_style_qualification_for_method = false:none +dotnet_style_qualification_for_event = false:none + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:none +dotnet_style_predefined_type_for_member_access = true:none + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:none +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:none +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:none + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:none +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_elsewhere = true:none + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:none +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true From 6cdb7c0230e863876f57f6761a7c91f94745882b Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 11 Aug 2018 12:48:14 +0100 Subject: [PATCH 34/52] Use structured logging Update logging resource strings to support structured logging. Update some logging messages. Fix some incorrect resource string names. --- src/SqlLocalDb/EventIds.cs | 7 +- src/SqlLocalDb/ILoggerExtensions.cs | 17 ++--- src/SqlLocalDb/SR.Designer.cs | 103 +++++++++++++++------------- src/SqlLocalDb/SR.resx | 81 +++++++++++----------- src/SqlLocalDb/SqlLocalDbApi.cs | 20 ++++-- 5 files changed, 127 insertions(+), 101 deletions(-) diff --git a/src/SqlLocalDb/EventIds.cs b/src/SqlLocalDb/EventIds.cs index 48894fae..aec8c238 100644 --- a/src/SqlLocalDb/EventIds.cs +++ b/src/SqlLocalDb/EventIds.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using Microsoft.Extensions.Logging; @@ -287,6 +287,11 @@ internal static class EventIds /// internal static readonly EventId NativeApiUnloaded = new EventId(++Id, nameof(NativeApiUnloaded)); + /// + /// The for when the SQL LocalDB Instance API returns a generic error. This field is read-only. + /// + internal static readonly EventId GenericError = new EventId(++Id, nameof(GenericError)); + /// /// The base Id for the event Ids. /// diff --git a/src/SqlLocalDb/ILoggerExtensions.cs b/src/SqlLocalDb/ILoggerExtensions.cs index 17e1cc55..ffbdc313 100644 --- a/src/SqlLocalDb/ILoggerExtensions.cs +++ b/src/SqlLocalDb/ILoggerExtensions.cs @@ -1,7 +1,8 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; +using System.Globalization; using Microsoft.Extensions.Logging; namespace MartinCostello.SqlLocalDb @@ -46,7 +47,7 @@ internal static class ILoggerExtensions /// /// Logging delegate for when a SQL LocalDB instance that could not be deleted. /// - private static readonly Action _deletingInstanceFailed = LoggerMessage.Define( + private static readonly Action _deletingInstanceFailed = LoggerMessage.Define( LogLevel.Error, EventIds.DeletingInstanceFailed, SR.ILoggerExtensions_DeleteFailedFormat); @@ -233,12 +234,12 @@ internal static class ILoggerExtensions private static readonly Action _nativeApiVersionOverriddenByUser = LoggerMessage.Define( LogLevel.Debug, EventIds.NativeApiVersionOverriddenByUser, - SR.ILoggerExtensions_ApiVersionOverriddenByUserFormat); + SR.ILoggerExtensions_NativeApiVersionOverriddenByUserFormat); /// /// Logging delegate for when the version of the SQL LocalDB Instance API specified by the user could not be found. /// - private static readonly Action _nativeApiVersionOverrideNotFound = LoggerMessage.Define( + private static readonly Action _nativeApiVersionOverrideNotFound = LoggerMessage.Define( LogLevel.Warning, EventIds.NativeApiVersionOverrideNotFound, SR.ILoggerExtensions_OverrideVersionNotFoundFormat); @@ -326,7 +327,7 @@ internal static class ILoggerExtensions /// /// Logging delegate for when a temporary SQL LocalDB instance failed to stop. /// - private static readonly Action _stoppingTemporaryInstanceFailed = LoggerMessage.Define( + private static readonly Action _stoppingTemporaryInstanceFailed = LoggerMessage.Define( LogLevel.Error, EventIds.StopTemporaryInstanceFailed, SR.ILoggerExtensions_StopFailedFormat); @@ -404,7 +405,7 @@ internal static void DeletingInstance(this ILogger logger, string instanceName) /// The name of the instance that could not be deleted. /// The error code. internal static void DeletingInstanceFailed(this ILogger logger, string instanceName, int error) - => _deletingInstanceFailed(logger, instanceName, error, null); + => _deletingInstanceFailed(logger, instanceName, error.ToString("X", CultureInfo.InvariantCulture), null); /// /// Logs that a SQL LocalDB instance could not be deleted because it is still in use. @@ -597,7 +598,7 @@ internal static void NativeApiVersionOverriddenByUser(this ILogger logger, Versi /// The logger to use. /// The version specified to be used. internal static void NativeApiVersionOverrideNotFound(this ILogger logger, string version) - => _nativeApiVersionOverrideNotFound(logger, version, Environment.MachineName, null); + => _nativeApiVersionOverrideNotFound(logger, version, null); /// /// Logs that SQL LocalDB is not installed. @@ -691,7 +692,7 @@ internal static void StoppingInstance(this ILogger logger, string instanceName, /// The name of the instance that failed to stop. /// The error code. internal static void StoppingTemporaryInstanceFailed(this ILogger logger, string instanceName, int error) - => _stoppingTemporaryInstanceFailed(logger, instanceName, error, null); + => _stoppingTemporaryInstanceFailed(logger, instanceName, error.ToString("X", CultureInfo.InvariantCulture), null); /// /// Logs that SQL LocalDB tracing was stopped. diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index 85274eb8..3477aa5b 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -61,16 +61,7 @@ internal SR() { } /// - /// Looks up a localized string similar to The SQL LocalDB native API version to load was overridden by the user to {0}.. - /// - internal static string ILoggerExtensions_ApiVersionOverriddenByUserFormat { - get { - return ResourceManager.GetString("ILoggerExtensions_ApiVersionOverriddenByUserFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Created named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. + /// Looks up a localized string similar to Created named instance {InstanceName} of SQL LocalDB with version {InstanceVersion}.. /// internal static string ILoggerExtensions_CreatedInstanceFormat { get { @@ -79,7 +70,7 @@ internal static string ILoggerExtensions_CreatedInstanceFormat { } /// - /// Looks up a localized string similar to Creating named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'.. + /// Looks up a localized string similar to Creating named instance {InstanceName} of SQL LocalDB with version {InstanceVersion}.. /// internal static string ILoggerExtensions_CreatingInstanceFormat { get { @@ -88,7 +79,7 @@ internal static string ILoggerExtensions_CreatingInstanceFormat { } /// - /// Looks up a localized string similar to Deleted file(s) for SQL LocalDB instance '{0}' from '{1}'.. + /// Looks up a localized string similar to Deleted file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}.. /// internal static string ILoggerExtensions_DeletedInstanceFilesFormat { get { @@ -97,7 +88,7 @@ internal static string ILoggerExtensions_DeletedInstanceFilesFormat { } /// - /// Looks up a localized string similar to Deleted named instance of SQL LocalDB '{0}'.. + /// Looks up a localized string similar to Deleted named instance {InstanceName} of SQL LocalDB.. /// internal static string ILoggerExtensions_DeletedInstanceFormat { get { @@ -106,7 +97,7 @@ internal static string ILoggerExtensions_DeletedInstanceFormat { } /// - /// Looks up a localized string similar to The SQL LocalDB instance '{0}' could not be deleted as it is currently in use.. + /// Looks up a localized string similar to The SQL LocalDB instance {InstanceName} could not be deleted as it is currently in use.. /// internal static string ILoggerExtensions_DeleteFailedAsInUseFormat { get { @@ -115,7 +106,7 @@ internal static string ILoggerExtensions_DeleteFailedAsInUseFormat { } /// - /// Looks up a localized string similar to Failed to delete SQL LocalDB instance '{0}'. HRESULT = {1:X}.. + /// Looks up a localized string similar to Failed to delete SQL LocalDB instance {InstanceName} with HRESULT {HResult}.. /// internal static string ILoggerExtensions_DeleteFailedFormat { get { @@ -124,7 +115,7 @@ internal static string ILoggerExtensions_DeleteFailedFormat { } /// - /// Looks up a localized string similar to Deleting named instance of SQL LocalDB '{0}'.. + /// Looks up a localized string similar to Deleting named instance {InstanceName} of SQL LocalDB.. /// internal static string ILoggerExtensions_DeletingFormat { get { @@ -133,7 +124,7 @@ internal static string ILoggerExtensions_DeletingFormat { } /// - /// Looks up a localized string similar to Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}'.. + /// Looks up a localized string similar to Failed to delete file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}.. /// internal static string ILoggerExtensions_DeletingInstanceFilesFailedFormat { get { @@ -142,7 +133,7 @@ internal static string ILoggerExtensions_DeletingInstanceFilesFailedFormat { } /// - /// Looks up a localized string similar to Deleting file(s) for SQL LocalDB instance '{0}' from '{1}'.. + /// Looks up a localized string similar to Deleting file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}.. /// internal static string ILoggerExtensions_DeletingInstanceFilesFormat { get { @@ -151,7 +142,7 @@ internal static string ILoggerExtensions_DeletingInstanceFilesFormat { } /// - /// Looks up a localized string similar to The SQL LocalDB function {0} could not be found.. + /// Looks up a localized string similar to The SQL LocalDB function {LocalDBFunctionName} could not be found.. /// internal static string ILoggerExtensions_FunctionNotFoundFormat { get { @@ -169,7 +160,7 @@ internal static string ILoggerExtensions_GetInstances { } /// - /// Looks up a localized string similar to Obtaining information for SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Obtaining information for SQL LocalDB instance {InstanceName}.. /// internal static string ILoggerExtensions_GettingInfoFormat { get { @@ -178,7 +169,7 @@ internal static string ILoggerExtensions_GettingInfoFormat { } /// - /// Looks up a localized string similar to Obtaining version information for SQL LocalDB version '{0}'.. + /// Looks up a localized string similar to Obtaining version information for SQL LocalDB version {Version}.. /// internal static string ILoggerExtensions_GetVersionInfoFormat { get { @@ -196,7 +187,7 @@ internal static string ILoggerExtensions_GetVersions { } /// - /// Looks up a localized string similar to Obtained information for SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Obtained information for SQL LocalDB instance {InstanceName}.. /// internal static string ILoggerExtensions_GotInfoFormat { get { @@ -205,7 +196,7 @@ internal static string ILoggerExtensions_GotInfoFormat { } /// - /// Looks up a localized string similar to Obtained {0} instance names for SQL LocalDB.. + /// Looks up a localized string similar to Obtained {InstanceCount} instance names for SQL LocalDB.. /// internal static string ILoggerExtensions_GotInstancesFormat { get { @@ -214,7 +205,7 @@ internal static string ILoggerExtensions_GotInstancesFormat { } /// - /// Looks up a localized string similar to Obtained version information for SQL LocalDB version '{0}'.. + /// Looks up a localized string similar to Obtained version information for SQL LocalDB version {Version}.. /// internal static string ILoggerExtensions_GotVersionInfoFormat { get { @@ -223,7 +214,7 @@ internal static string ILoggerExtensions_GotVersionInfoFormat { } /// - /// Looks up a localized string similar to Obtained {0} versions for SQL LocalDB.. + /// Looks up a localized string similar to Obtained {VersionCount} versions for SQL LocalDB.. /// internal static string ILoggerExtensions_GotVersionsFormat { get { @@ -232,7 +223,7 @@ internal static string ILoggerExtensions_GotVersionsFormat { } /// - /// Looks up a localized string similar to SQL LocalDB instance '{0}' cannot be found so was not deleted.. + /// Looks up a localized string similar to SQL LocalDB instance {InstanceName} cannot be found so was not deleted.. /// internal static string ILoggerExtensions_InstanceDoesNotExistFormat { get { @@ -241,7 +232,7 @@ internal static string ILoggerExtensions_InstanceDoesNotExistFormat { } /// - /// Looks up a localized string similar to The current Language Id {0} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults.. + /// Looks up a localized string similar to The current Language Id {LanguageId} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults.. /// internal static string ILoggerExtensions_InvalidLanguageIdFormat { get { @@ -250,7 +241,7 @@ internal static string ILoggerExtensions_InvalidLanguageIdFormat { } /// - /// Looks up a localized string similar to Ignoring invalid registry key name '{0}'.. + /// Looks up a localized string similar to Ignoring invalid registry key name {RegistryKeyName}.. /// internal static string ILoggerExtensions_InvalidRegistryKeyNameFormat { get { @@ -259,7 +250,7 @@ internal static string ILoggerExtensions_InvalidRegistryKeyNameFormat { } /// - /// Looks up a localized string similar to Loaded SQL LocalDB API from '{0}'.. + /// Looks up a localized string similar to Loaded SQL LocalDB API from {InstanceApiPath}.. /// internal static string ILoggerExtensions_NativeApiLoadedFormat { get { @@ -268,7 +259,7 @@ internal static string ILoggerExtensions_NativeApiLoadedFormat { } /// - /// Looks up a localized string similar to Failed to load SQL LocalDB API from '{0}'. Error: {1}.. + /// Looks up a localized string similar to Failed to load SQL LocalDB API from {InstanceApiPath}. Error: {ErrorCode}.. /// internal static string ILoggerExtensions_NativeApiLoadFailedFormat { get { @@ -277,7 +268,7 @@ internal static string ILoggerExtensions_NativeApiLoadFailedFormat { } /// - /// Looks up a localized string similar to Could not find SQL LocalDB API DLL '{0}'.. + /// Looks up a localized string similar to Could not find SQL LocalDB API DLL {InstanceApiPath}.. /// internal static string ILoggerExtensions_NativeApiNotFoundFormat { get { @@ -295,7 +286,7 @@ internal static string ILoggerExtensions_NativeApiNotLoaded { } /// - /// Looks up a localized string similar to Unloaded SQL LocalDB Instance API library '{0}'.. + /// Looks up a localized string similar to Unloaded SQL LocalDB Instance API library {InstanceApiPath}.. /// internal static string ILoggerExtensions_NativeApiUnloadedFormat { get { @@ -304,11 +295,11 @@ internal static string ILoggerExtensions_NativeApiUnloadedFormat { } /// - /// Looks up a localized string similar to SQL LocalDB returned HRESULT {0:X}.. + /// Looks up a localized string similar to The SQL LocalDB native API version to load was overridden by the user to {InstanceApiOverrideVersion}.. /// - internal static string ILoggerExtensions_NativeResultFormat { + internal static string ILoggerExtensions_NativeApiVersionOverriddenByUserFormat { get { - return ResourceManager.GetString("ILoggerExtensions_NativeResultFormat", resourceCulture); + return ResourceManager.GetString("ILoggerExtensions_NativeApiVersionOverriddenByUserFormat", resourceCulture); } } @@ -331,7 +322,7 @@ internal static string ILoggerExtensions_NotInstalled { } /// - /// Looks up a localized string similar to The configured SQL LocalDB Instance API override version '{0}' cannot be found on {1}.. + /// Looks up a localized string similar to The configured SQL LocalDB Instance API override version {InstanceApiOverrideVersion} cannot be found.. /// internal static string ILoggerExtensions_OverrideVersionNotFoundFormat { get { @@ -340,7 +331,7 @@ internal static string ILoggerExtensions_OverrideVersionNotFoundFormat { } /// - /// Looks up a localized string similar to Could not open registry key '{0}'.. + /// Looks up a localized string similar to Could not open registry key {RegistryKeyName}.. /// internal static string ILoggerExtensions_RegistryKeyNotFoundFormat { get { @@ -349,7 +340,7 @@ internal static string ILoggerExtensions_RegistryKeyNotFoundFormat { } /// - /// Looks up a localized string similar to Shared SQL LocalDB instance '{0}' for owner SID '{1}' as '{2}'.. + /// Looks up a localized string similar to Shared SQL LocalDB instance {InstanceName} for owner SID {OwnerSid} as {SharedInstanceName}.. /// internal static string ILoggerExtensions_SharedInstanceFormat { get { @@ -358,7 +349,7 @@ internal static string ILoggerExtensions_SharedInstanceFormat { } /// - /// Looks up a localized string similar to Sharing SQL LocalDB instance '{0}' for owner SID '{1}'. Shared instance name: '{2}'.. + /// Looks up a localized string similar to Sharing SQL LocalDB instance {InstanceName} for owner SID {OwnerSid} as shared instance name {SharedInstanceName}.. /// internal static string ILoggerExtensions_SharingInstanceFormat { get { @@ -367,7 +358,7 @@ internal static string ILoggerExtensions_SharingInstanceFormat { } /// - /// Looks up a localized string similar to Started SQL LocalDB instance '{0}' using named pipe '{1}'.. + /// Looks up a localized string similar to Started SQL LocalDB instance {InstanceName} using named pipe {NamedPipe}.. /// internal static string ILoggerExtensions_StartedFormat { get { @@ -385,7 +376,7 @@ internal static string ILoggerExtensions_StartedTracing { } /// - /// Looks up a localized string similar to Starting SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Starting SQL LocalDB instance {InstanceName}.. /// internal static string ILoggerExtensions_StartingFormat { get { @@ -403,7 +394,7 @@ internal static string ILoggerExtensions_StartTracing { } /// - /// Looks up a localized string similar to Failed to stop SQL LocalDB instance '{0}'. HRESULT = {1:X}.. + /// Looks up a localized string similar to Failed to stop SQL LocalDB instance {InstanceName} with HRESULT {HResult}.. /// internal static string ILoggerExtensions_StopFailedFormat { get { @@ -412,7 +403,7 @@ internal static string ILoggerExtensions_StopFailedFormat { } /// - /// Looks up a localized string similar to Stopped SQL LocalDB instance '{0}' after {1}.. + /// Looks up a localized string similar to Stopped SQL LocalDB instance {InstanceName} after {Timeout}.. /// internal static string ILoggerExtensions_StoppedFormat { get { @@ -421,7 +412,7 @@ internal static string ILoggerExtensions_StoppedFormat { } /// - /// Looks up a localized string similar to Stopped sharing SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Stopped sharing SQL LocalDB instance {InstanceName}.. /// internal static string ILoggerExtensions_StoppedSharingFormat { get { @@ -439,7 +430,7 @@ internal static string ILoggerExtensions_StoppedTracing { } /// - /// Looks up a localized string similar to Stopping SQL LocalDB instance '{0}'. Timeout: {1}; Option(s): {2}.. + /// Looks up a localized string similar to Stopping SQL LocalDB instance {InstanceName} with timeout: {Timeout} and option(s) {StopOptions}.. /// internal static string ILoggerExtensions_StoppingFormat { get { @@ -448,7 +439,7 @@ internal static string ILoggerExtensions_StoppingFormat { } /// - /// Looks up a localized string similar to Stopping sharing SQL LocalDB instance '{0}'.. + /// Looks up a localized string similar to Stopping sharing SQL LocalDB instance {InstanceName}.. /// internal static string ILoggerExtensions_StoppingSharingFormat { get { @@ -475,7 +466,7 @@ internal static string ISqlLocalDbInstanceInfoExtensions_NotRunningFormat { } /// - /// Looks up a localized string similar to An error occurred with SQL Server LocalDB. HRESULT = {0:X}. + /// Looks up a localized string similar to An error occurred calling the SQL Server LocalDB with HRESULT {0:X}.. /// internal static string SqlLocalDbApi_GenericFailureFormat { get { @@ -483,6 +474,24 @@ internal static string SqlLocalDbApi_GenericFailureFormat { } } + /// + /// Looks up a localized string similar to An error occurred calling the SQL Server LocalDB with HRESULT {HResult}.. + /// + internal static string SqlLocalDbApi_LogGenericFailureFormat { + get { + return ResourceManager.GetString("SqlLocalDbApi_LogGenericFailureFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SQL LocalDB returned HRESULT {HResult}.. + /// + internal static string SqlLocalDbApi_NativeResultFormat { + get { + return ResourceManager.GetString("SqlLocalDbApi_NativeResultFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to No SQL LocalDB instance name specified.. /// diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index 2f148fb4..c42d08fe 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - An error occurred with SQL Server LocalDB. HRESULT = {0:X} + An error occurred calling the SQL Server LocalDB with HRESULT {0:X}. SQL Server LocalDB is not installed on {0}. @@ -154,85 +154,85 @@ No SQL LocalDB instance name specified. - Created named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'. + Created named instance {InstanceName} of SQL LocalDB with version {InstanceVersion}. - Creating named instance of SQL LocalDB. Instance name: '{0}'; version: '{1}'. + Creating named instance {InstanceName} of SQL LocalDB with version {InstanceVersion}. - Deleted named instance of SQL LocalDB '{0}'. + Deleted named instance {InstanceName} of SQL LocalDB. - Deleting named instance of SQL LocalDB '{0}'. + Deleting named instance {InstanceName} of SQL LocalDB. Obtaining instance names for SQL LocalDB. - Obtaining information for SQL LocalDB instance '{0}'. + Obtaining information for SQL LocalDB instance {InstanceName}. - Obtaining version information for SQL LocalDB version '{0}'. + Obtaining version information for SQL LocalDB version {Version}. - Obtained information for SQL LocalDB instance '{0}'. + Obtained information for SQL LocalDB instance {InstanceName}. - Obtained {0} instance names for SQL LocalDB. + Obtained {InstanceCount} instance names for SQL LocalDB. - Obtained version information for SQL LocalDB version '{0}'. + Obtained version information for SQL LocalDB version {Version}. - - SQL LocalDB returned HRESULT {0:X}. + + SQL LocalDB returned HRESULT {HResult}. - Shared SQL LocalDB instance '{0}' for owner SID '{1}' as '{2}'. + Shared SQL LocalDB instance {InstanceName} for owner SID {OwnerSid} as {SharedInstanceName}. - Sharing SQL LocalDB instance '{0}' for owner SID '{1}'. Shared instance name: '{2}'. + Sharing SQL LocalDB instance {InstanceName} for owner SID {OwnerSid} as shared instance name {SharedInstanceName}. - Started SQL LocalDB instance '{0}' using named pipe '{1}'. + Started SQL LocalDB instance {InstanceName} using named pipe {NamedPipe}. Tracing started for SQL LocalDB. - Starting SQL LocalDB instance '{0}'. + Starting SQL LocalDB instance {InstanceName}. Starting tracing for SQL LocalDB. - Stopped SQL LocalDB instance '{0}' after {1}. + Stopped SQL LocalDB instance {InstanceName} after {Timeout}. - Stopped sharing SQL LocalDB instance '{0}'. + Stopped sharing SQL LocalDB instance {InstanceName}. Tracing stopped for SQL LocalDB. - Stopping SQL LocalDB instance '{0}'. Timeout: {1}; Option(s): {2}. + Stopping SQL LocalDB instance {InstanceName} with timeout: {Timeout} and option(s) {StopOptions}. - Stopping sharing SQL LocalDB instance '{0}'. + Stopping sharing SQL LocalDB instance {InstanceName}. Stopping tracing for SQL LocalDB. - The SQL LocalDB function {0} could not be found. + The SQL LocalDB function {LocalDBFunctionName} could not be found. - Ignoring invalid registry key name '{0}'. + Ignoring invalid registry key name {RegistryKeyName}. - Failed to load SQL LocalDB API from '{0}'. Error: {1}. + Failed to load SQL LocalDB API from {InstanceApiPath}. Error: {ErrorCode}. - Could not find SQL LocalDB API DLL '{0}'. + Could not find SQL LocalDB API DLL {InstanceApiPath}. The SQL LocalDB API was not loaded. @@ -241,40 +241,40 @@ No SQL LocalDB API DLL path could be found. - Could not open registry key '{0}'. + Could not open registry key {RegistryKeyName}. - Loaded SQL LocalDB API from '{0}'. + Loaded SQL LocalDB API from {InstanceApiPath}. - SQL LocalDB instance '{0}' cannot be found so was not deleted. + SQL LocalDB instance {InstanceName} cannot be found so was not deleted. - - The SQL LocalDB native API version to load was overridden by the user to {0}. + + The SQL LocalDB native API version to load was overridden by the user to {InstanceApiOverrideVersion}. - The SQL LocalDB instance '{0}' could not be deleted as it is currently in use. + The SQL LocalDB instance {InstanceName} could not be deleted as it is currently in use. - Deleted file(s) for SQL LocalDB instance '{0}' from '{1}'. + Deleted file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}. - Failed to delete file(s) for SQL LocalDB instance '{0}' from '{1}'. + Failed to delete file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}. - Deleting file(s) for SQL LocalDB instance '{0}' from '{1}'. + Deleting file(s) for SQL LocalDB instance {InstanceName} from {InstanceFilesPath}. - The current Language Id {0} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. + The current Language Id {LanguageId} is not recognized by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. - The configured SQL LocalDB Instance API override version '{0}' cannot be found on {1}. + The configured SQL LocalDB Instance API override version {InstanceApiOverrideVersion} cannot be found. - Failed to delete SQL LocalDB instance '{0}'. HRESULT = {1:X}. + Failed to delete SQL LocalDB instance {InstanceName} with HRESULT {HResult}. - Failed to stop SQL LocalDB instance '{0}'. HRESULT = {1:X}. + Failed to stop SQL LocalDB instance {InstanceName} with HRESULT {HResult}. No logger was provided for the {0} instance. @@ -283,7 +283,7 @@ Obtaining versions for SQL LocalDB. - Obtained {0} versions for SQL LocalDB. + Obtained {VersionCount} versions for SQL LocalDB. SQL Server LocalDB is not installed. @@ -292,6 +292,9 @@ The SQL LocalDB Instance API is not supported on this platform. - Unloaded SQL LocalDB Instance API library '{0}'. + Unloaded SQL LocalDB Instance API library {InstanceApiPath}. + + + An error occurred calling the SQL Server LocalDB with HRESULT {HResult}. \ No newline at end of file diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index bc0ff943..b0b6a105 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -1,9 +1,10 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -1185,7 +1186,7 @@ internal Exception GetLocalDbError(int hr, EventId eventId, string instanceName { string message; - Logger.LogError(eventId, SR.ILoggerExtensions_NativeResultFormat, hr); + Logger.LogError(eventId, SR.SqlLocalDbApi_NativeResultFormat, hr.ToString("X", CultureInfo.InvariantCulture)); if (hr == SqlLocalDbErrors.NotInstalled) { @@ -1206,9 +1207,15 @@ internal Exception GetLocalDbError(int hr, EventId eventId, string instanceName if (hr2 == 0) { message = buffer.ToString(); + Logger.LogError(eventId, message); } else if (hr2 == SqlLocalDbErrors.UnknownLanguageId) { + Logger.LogError( + eventId, + SR.SqlLocalDbApi_LogGenericFailureFormat, + hr2.ToString("X", CultureInfo.InvariantCulture)); + // If the value of DefaultLanguageId was not understood by the API, // then log an error informing the user. Do not throw an exception in // this case as otherwise we will mask the original exception from the user. @@ -1219,17 +1226,18 @@ internal Exception GetLocalDbError(int hr, EventId eventId, string instanceName } else { + Logger.LogError( + eventId, + SR.SqlLocalDbApi_LogGenericFailureFormat, + hr2.ToString("X", CultureInfo.InvariantCulture)); + // Use a generic message if getting the message from the API failed. // N.B. That if this occurs, then the original error is masked (although it is logged). message = SRHelper.Format(SR.SqlLocalDbApi_GenericFailureFormat, hr2); - Logger.LogError(eventId, message); - return new SqlLocalDbException(message, hr2, instanceName); } - Logger.LogError(eventId, message); - return new SqlLocalDbException(message, hr, instanceName); } From e8fc37719a4c8887204180dad07401dbb7e14658 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 11 Aug 2018 13:22:24 +0100 Subject: [PATCH 35/52] Add sample application shell Add a shell sample application that will demonstrate a TODO application backed by SQL LocalDB. --- .gitignore | 3 +- SqlLocalDb.sln | 9 ++++ samples/TodoApp/Controllers/HomeController.cs | 28 ++++++++++ samples/TodoApp/Models/ErrorViewModel.cs | 12 +++++ samples/TodoApp/Program.cs | 20 ++++++++ .../TodoApp/Properties/launchSettings.json | 27 ++++++++++ samples/TodoApp/Startup.cs | 48 ++++++++++++++++++ samples/TodoApp/TodoApp.csproj | 13 +++++ samples/TodoApp/Views/Home/Index.cshtml | 11 ++++ samples/TodoApp/Views/Shared/Error.cshtml | 14 +++++ samples/TodoApp/Views/Shared/_Layout.cshtml | 38 ++++++++++++++ samples/TodoApp/Views/_ViewImports.cshtml | 3 ++ samples/TodoApp/Views/_ViewStart.cshtml | 3 ++ samples/TodoApp/appsettings.Development.json | 9 ++++ samples/TodoApp/appsettings.json | 8 +++ samples/TodoApp/bundleconfig.json | 8 +++ samples/TodoApp/wwwroot/css/site.css | 15 ++++++ samples/TodoApp/wwwroot/favicon.ico | Bin 0 -> 32038 bytes 18 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 samples/TodoApp/Controllers/HomeController.cs create mode 100644 samples/TodoApp/Models/ErrorViewModel.cs create mode 100644 samples/TodoApp/Program.cs create mode 100644 samples/TodoApp/Properties/launchSettings.json create mode 100644 samples/TodoApp/Startup.cs create mode 100644 samples/TodoApp/TodoApp.csproj create mode 100644 samples/TodoApp/Views/Home/Index.cshtml create mode 100644 samples/TodoApp/Views/Shared/Error.cshtml create mode 100644 samples/TodoApp/Views/Shared/_Layout.cshtml create mode 100644 samples/TodoApp/Views/_ViewImports.cshtml create mode 100644 samples/TodoApp/Views/_ViewStart.cshtml create mode 100644 samples/TodoApp/appsettings.Development.json create mode 100644 samples/TodoApp/appsettings.json create mode 100644 samples/TodoApp/bundleconfig.json create mode 100644 samples/TodoApp/wwwroot/css/site.css create mode 100644 samples/TodoApp/wwwroot/favicon.ico diff --git a/.gitignore b/.gitignore index dd892948..815dc717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.DS_Store +.DS_Store .dotnetcli .metadata .settings @@ -17,6 +17,7 @@ node_modules obj packages project.lock.json +samples/TodoApp/wwwroot/**/*.min.css TestResults typings UpgradeLog*.htm diff --git a/SqlLocalDb.sln b/SqlLocalDb.sln index 54bdd050..aaf5f2f7 100644 --- a/SqlLocalDb.sln +++ b/SqlLocalDb.sln @@ -49,6 +49,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{701E .vscode\tasks.json = .vscode\tasks.json EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{825D6F08-8321-4C6E-92A9-92543FDAF965}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApp", "samples\TodoApp\TodoApp.csproj", "{1B720E31-3AA8-4010-A3DB-187B2D0BC471}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +71,10 @@ Global {C80D956D-9878-4955-B89F-C29F9996DD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {C80D956D-9878-4955-B89F-C29F9996DD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C80D956D-9878-4955-B89F-C29F9996DD1F}.Release|Any CPU.Build.0 = Release|Any CPU + {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -77,6 +85,7 @@ Global {C80D956D-9878-4955-B89F-C29F9996DD1F} = {278BCCB1-39B2-46DB-9395-7F85995A6132} {D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA} = {E207A447-68A1-4D72-B24F-89FB7890AE12} {701E574A-6366-4AF9-9319-237968FA1089} = {E207A447-68A1-4D72-B24F-89FB7890AE12} + {1B720E31-3AA8-4010-A3DB-187B2D0BC471} = {825D6F08-8321-4C6E-92A9-92543FDAF965} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3B9E157C-5E92-4357-B233-281B4530EABD} diff --git a/samples/TodoApp/Controllers/HomeController.cs b/samples/TodoApp/Controllers/HomeController.cs new file mode 100644 index 00000000..86946c10 --- /dev/null +++ b/samples/TodoApp/Controllers/HomeController.cs @@ -0,0 +1,28 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Diagnostics; +using MartinCostello.SqlLocalDb; +using Microsoft.AspNetCore.Mvc; +using TodoApp.Models; + +namespace TodoApp.Controllers +{ + public class HomeController : Controller + { + private readonly ISqlLocalDbApi _localDB; + + public HomeController(ISqlLocalDbApi localDB) + { + _localDB = localDB; + } + + public IActionResult Index() => View(); + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + } +} diff --git a/samples/TodoApp/Models/ErrorViewModel.cs b/samples/TodoApp/Models/ErrorViewModel.cs new file mode 100644 index 00000000..995cba7b --- /dev/null +++ b/samples/TodoApp/Models/ErrorViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +namespace TodoApp.Models +{ + public class ErrorViewModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + } +} diff --git a/samples/TodoApp/Program.cs b/samples/TodoApp/Program.cs new file mode 100644 index 00000000..b48861f1 --- /dev/null +++ b/samples/TodoApp/Program.cs @@ -0,0 +1,20 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace TodoApp +{ + public static class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/samples/TodoApp/Properties/launchSettings.json b/samples/TodoApp/Properties/launchSettings.json new file mode 100644 index 00000000..3158ab9c --- /dev/null +++ b/samples/TodoApp/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64325", + "sslPort": 44344 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "TodoApp": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/TodoApp/Startup.cs b/samples/TodoApp/Startup.cs new file mode 100644 index 00000000..357a9cb1 --- /dev/null +++ b/samples/TodoApp/Startup.cs @@ -0,0 +1,48 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using MartinCostello.SqlLocalDb; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace TodoApp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseCookiePolicy(); + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/samples/TodoApp/TodoApp.csproj b/samples/TodoApp/TodoApp.csproj new file mode 100644 index 00000000..6015c0b5 --- /dev/null +++ b/samples/TodoApp/TodoApp.csproj @@ -0,0 +1,13 @@ + + + $(NoWarn);CA1822 + TodoApp + netcoreapp2.1 + + + + + + + + diff --git a/samples/TodoApp/Views/Home/Index.cshtml b/samples/TodoApp/Views/Home/Index.cshtml new file mode 100644 index 00000000..c547f558 --- /dev/null +++ b/samples/TodoApp/Views/Home/Index.cshtml @@ -0,0 +1,11 @@ +@{ + ViewData["Title"] = "My List"; +} + +

Things To Do

+
+
    +
  • Item 1
  • +
  • Item 2
  • +
+
diff --git a/samples/TodoApp/Views/Shared/Error.cshtml b/samples/TodoApp/Views/Shared/Error.cshtml new file mode 100644 index 00000000..4e17e056 --- /dev/null +++ b/samples/TodoApp/Views/Shared/Error.cshtml @@ -0,0 +1,14 @@ +@model ErrorViewModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} diff --git a/samples/TodoApp/Views/Shared/_Layout.cshtml b/samples/TodoApp/Views/Shared/_Layout.cshtml new file mode 100644 index 00000000..df4bb12c --- /dev/null +++ b/samples/TodoApp/Views/Shared/_Layout.cshtml @@ -0,0 +1,38 @@ + + + + + + @ViewData["Title"] - TodoApp + + + + + +
+ @RenderBody() +
+
+

© 2018 - Martin Costello

+
+
+ + + + @RenderSection("Scripts", required: false) + + diff --git a/samples/TodoApp/Views/_ViewImports.cshtml b/samples/TodoApp/Views/_ViewImports.cshtml new file mode 100644 index 00000000..652222cc --- /dev/null +++ b/samples/TodoApp/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using TodoApp +@using TodoApp.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/TodoApp/Views/_ViewStart.cshtml b/samples/TodoApp/Views/_ViewStart.cshtml new file mode 100644 index 00000000..a5f10045 --- /dev/null +++ b/samples/TodoApp/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/samples/TodoApp/appsettings.Development.json b/samples/TodoApp/appsettings.Development.json new file mode 100644 index 00000000..d76d68ea --- /dev/null +++ b/samples/TodoApp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Warning", + "Microsoft": "Warning" + } + } +} diff --git a/samples/TodoApp/appsettings.json b/samples/TodoApp/appsettings.json new file mode 100644 index 00000000..def9159a --- /dev/null +++ b/samples/TodoApp/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/TodoApp/bundleconfig.json b/samples/TodoApp/bundleconfig.json new file mode 100644 index 00000000..bdac79b2 --- /dev/null +++ b/samples/TodoApp/bundleconfig.json @@ -0,0 +1,8 @@ +[ + { + "outputFileName": "wwwroot/css/site.min.css", + "inputFiles": [ + "wwwroot/css/site.css" + ] + } +] diff --git a/samples/TodoApp/wwwroot/css/site.css b/samples/TodoApp/wwwroot/css/site.css new file mode 100644 index 00000000..42f2c6cf --- /dev/null +++ b/samples/TodoApp/wwwroot/css/site.css @@ -0,0 +1,15 @@ +html { + font-size: 16px; +} + +a { + color: #0060C7; +} + +.body-content { + padding-top: 10px; +} + +.navbar-dark .navbar-nav .nav-link { + color: #B0B0B0; +} diff --git a/samples/TodoApp/wwwroot/favicon.ico b/samples/TodoApp/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 zcmeHwX>eTEbtY7aYbrGrkNjgie?1jXjZ#zP%3n{}GObKv$BxI7Sl;Bwl5E+Qtj&t8 z*p|m4DO#HoJC-FyvNnp8NP<{Na0LMnTtO21(rBP}?EAiNjWgeO?z`{3ZoURUQlV2d zY1Pqv{m|X_oO91|?^z!6@@~od!@OH>&BN;>c@O+yUfy5w>LccTKJJ&`-k<%M^Zvi( z<$dKp=jCnNX5Qa+M_%6g|IEv~4R84q9|7E=|Ho(Wz3f-0wPjaRL;W*N^>q%^KGRr7 zxbjSORb_c&eO;oV_DZ7ua!sPH=0c+W;`vzJ#j~-x3uj};50#vqo*0w4!LUqs*UCh9 zvy2S%$#8$K4EOa&e@~aBS65_hc~Mpu=454VT2^KzWqEpBA=ME|O;1cn?8p<+{MKJf zbK#@1wzL44m$k(?85=Obido7=C|xWKe%66$z)NrzRwR>?hK?_bbwT z@Da?lBrBL}Zemo1@!9pYRau&!ld17h{f+UV0sY(R{ET$PBB|-=Nr@l-nY6w8HEAw* zRMIQU`24Jl_IFEPcS=_HdrOP5yf81z_?@M>83Vv65$QFr9nPg(wr`Ke8 zaY4ogdnMA*F7a4Q1_uXadTLUpCk;$ZPRRJ^sMOch;rlbvUGc1R9=u;dr9YANbQ<4Z z#P|Cp9BP$FXNPolgyr1XGt$^lFPF}rmBF5rj1Kh5%dforrP8W}_qJL$2qMBS-#%-|s#BPZBSETsn_EBYcr(W5dq( z@f%}C|iN7)YN`^)h7R?Cg}Do*w-!zwZb9=BMp%Wsh@nb22hA zA{`wa8Q;yz6S)zfo%sl08^GF`9csI9BlGnEy#0^Y3b);M+n<(}6jziM7nhe57a1rj zC@(2ISYBL^UtWChKzVWgf%4LW2Tqg_^7jMw`C$KvU+mcakFjV(BGAW9g%CzSyM;Df z143=mq0oxaK-H;o>F3~zJ<(3-j&?|QBn)WJfP#JR zRuA;`N?L83wQt78QIA$(Z)lGQY9r^SFal;LB^qi`8%8@y+mwcGsf~nv)bBy2S7z~9 z=;X@Gglk)^jpbNz?1;`!J3QUfAOp4U$Uxm5>92iT`mek#$>s`)M>;e4{#%HAAcb^8_Ax%ersk|}# z0bd;ZPu|2}18KtvmIo8`1@H~@2ejwo(5rFS`Z4&O{$$+ch2hC0=06Jh`@p+p8LZzY z&2M~8T6X^*X?yQ$3N5EzRv$(FtSxhW>>ABUyp!{484f8(%C1_y)3D%Qgfl_!sz`LTXOjR&L!zPA0qH_iNS!tY{!^2WfD%uT}P zI<~&?@&))5&hPPHVRl9);TPO>@UI2d!^ksb!$9T96V(F){puTsn(}qt_WXNw4VvHj zf;6A_XCvE`Z@}E-IOaG0rs>K>^=Sr&OgT_p;F@v0VCN0Y$r|Lw1?Wjt`AKK~RT*kJ z2>QPuVgLNcF+XKno;WBv$yj@d_WFJbl*#*V_Cwzo@%3n5%z4g21G*PVZ)wM5$A{klYozmGlB zT@u2+s}=f}25%IA!yNcXUr!!1)z(Nqbhojg0lv@7@0UlvUMT)*r;M$d0-t)Z?B1@qQk()o!4fqvfr_I0r7 zy1(NdkHEj#Yu{K>T#We#b#FD=c1XhS{hdTh9+8gy-vkcdkk*QS@y(xxEMb1w6z<^~ zYcETGfB#ibR#ql0EiD;PR$L&Vrh2uRv5t_$;NxC;>7_S5_OXxsi8udY3BUUdi55Sk zcyKM+PQ9YMA%D1kH1q48OFG(Gbl=FmV;yk8o>k%0$rJ8%-IYsHclnYuTskkaiCGkUlkMY~mx&K}XRlKIW;odWIeuKjtbc^8bBOTqK zjj(ot`_j?A6y_h%vxE9o*ntx#PGrnK7AljD_r58ylE*oy@{IY%+mA^!|2vW_`>`aC{#3`#3;D_$^S^cM zRcF+uTO2sICledvFgNMU@A%M)%8JbSLq{dD|2|2Sg8vvh_uV6*Q?F&rKaV{v_qz&y z`f;stIb?Cb2!Cg7CG91Bhu@D@RaIrq-+o+T2fwFu#|j>lD6ZS9-t^5cx>p|?flqUA z;Cgs#V)O#`Aw4$Kr)L5?|7f4izl!;n0jux}tEW$&&YBXz9o{+~HhoiYDJ`w5BVTl&ARya=M7zdy$FEe}iGBur8XE>rhLj&_yDk5D4n2GJZ07u7%zyAfNtOLn;)M?h*Py-Xtql5aJOtL4U8e|!t? z((sc6&OJXrPdVef^wZV&x=Z&~uA7^ix8rly^rEj?#d&~pQ{HN8Yq|fZ#*bXn-26P^ z5!)xRzYO9{u6vx5@q_{FE4#7BipS#{&J7*>y}lTyV94}dfE%Yk>@@pDe&F7J09(-0|wuI|$of-MRfK51#t@t2+U|*s=W; z!Y&t{dS%!4VEEi$efA!#<<7&04?kB}Soprd8*jYv;-Qj~h~4v>{XX~kjF+@Z7<t?^|i z#>_ag2i-CRAM8Ret^rZt*^K?`G|o>1o(mLkewxyA)38k93`<~4VFI?5VB!kBh%NNU zxb8K(^-MU1ImWQxG~nFB-Un;6n{lQz_FfsW9^H$Xcn{;+W^ZcG$0qLM#eNV=vGE@# z1~k&!h4@T|IiI<47@pS|i?Qcl=XZJL#$JKve;booMqDUYY{(xcdj6STDE=n?;fsS1 ze`h~Q{CT$K{+{t+#*I1=&&-UU8M&}AwAxD-rMa=e!{0gQXP@6azBq9(ji11uJF%@5 zCvV`#*?;ZguQ7o|nH%bm*s&jLej#@B35gy32ZAE0`Pz@#j6R&kN5w{O4~1rhDoU zEBdU)%Nl?8zi|DR((u|gg~r$aLYmGMyK%FO*qLvwxK5+cn*`;O`16c!&&XT{$j~5k zXb^fbh1GT-CI*Nj{-?r7HNg=e3E{6rxuluPXY z5Nm8ktc$o4-^SO0|Es_sp!A$8GVwOX+%)cH<;=u#R#nz;7QsHl;J@a{5NUAmAHq4D zIU5@jT!h?kUp|g~iN*!>jM6K!W5ar0v~fWrSHK@})@6Lh#h)C6F6@)&-+C3(zO! z8+kV|B7LctM3DpI*~EYo>vCj>_?x&H;>y0*vKwE0?vi$CLt zfSJB##P|M2dEUDBPKW=9cY-F;L;h3Fs4E2ERdN#NSL7ctAC z?-}_a{*L@GA7JHJudxtDVA{K5Yh*k(%#x4W7w+^ zcb-+ofbT5ieG+@QG2lx&7!MyE2JWDP@$k`M;0`*d+oQmJ2A^de!3c53HFcfW_Wtv< zKghQ;*FifmI}kE4dc@1y-u;@qs|V75Z^|Q0l0?teobTE8tGl@EB?k#q_wUjypJ*R zyEI=DJ^Z+d*&}B_xoWvs27LtH7972qqMxVFcX9}c&JbeNCXUZM0`nQIkf&C}&skSt z^9fw@b^Hb)!^hE2IJq~~GktG#ZWwWG<`@V&ckVR&r=JAO4YniJewVcG`HF;59}=bf zLyz0uxf6MhuSyH#-^!ZbHxYl^mmBVrx) zyrb8sQ*qBd_WXm9c~Of$&ZP$b^)<~0%nt#7y$1Jg$e}WCK>TeUB{P>|b1FAB?%K7>;XiOfd}JQ`|IP#Vf%kVy zXa4;XFZ+>n;F>uX&3|4zqWK2u3c<>q;tzjsb1;d{u;L$-hq3qe@82(ob<3qom#%`+ z;vzYAs7TIMl_O75BXu|r`Qhc4UT*vN$3Oo0kAC!{f2#HexDy|qUpgTF;k{o6|L>7l z=?`=*LXaow1o;oNNLXsGTrvC)$R&{m=94Tf+2iTT3Y_Or z-!;^0a{kyWtO4vksG_3cyc7HQ0~detf0+2+qxq(e1NS251N}w5iTSrM)`0p8rem!j zZ56hGD=pHI*B+dd)2B`%|9f0goozCSeXPw3 z+58k~sI02Yz#lOneJzYcG)EB0|F+ggC6D|B`6}d0khAK-gz7U3EGT|M_9$ZINqZjwf>P zJCZ=ogSoE`=yV5YXrcTQZx@Un(64*AlLiyxWnCJ9I<5Nc*eK6eV1Mk}ci0*NrJ=t| zCXuJG`#7GBbPceFtFEpl{(lTm`LX=B_!H+& z>$*Hf}}y zkt@nLXFG9%v**s{z&{H4e?aqp%&l#oU8lxUxk2o%K+?aAe6jLojA& z_|J0<-%u^<;NT*%4)n2-OdqfctSl6iCHE?W_Q2zpJken#_xUJlidzs249H=b#g z?}L4-Tnp6)t_5X?_$v)vz`s9@^BME2X@w<>sKZ3=B{%*B$T5Nj%6!-Hr;I!Scj`lH z&2dHFlOISwWJ&S2vf~@I4i~(0*T%OFiuX|eD*nd2utS4$1_JM?zmp>a#CsVy6Er^z zeNNZZDE?R3pM?>~e?H_N`C`hy%m4jb;6L#8=a7l>3eJS2LGgEUxsau-Yh9l~o7=Yh z2mYg3`m5*3Ik|lKQf~euzZlCWzaN&=vHuHtOwK!2@W6)hqq$Zm|7`Nmu%9^F6UH?+ z@2ii+=iJ;ZzhiUKu$QB()nKk3FooI>Jr_IjzY6=qxYy;&mvi7BlQ?t4kRjIhb|2q? zd^K~{-^cxjVSj?!Xs=Da5IHmFzRj!Kzh~b!?`P7c&T9s77VLYB?8_?F zauM^)p;qFG!9PHLfIsnt43UnmV?Wn?Ki7aXSosgq;f?MYUuSIYwOn(5vWhb{f%$pn z4ySN-z}_%7|B);A@PA5k*7kkdr4xZ@s{e9j+9w;*RFm;XPDQwx%~;8iBzSKTIGKO z{53ZZU*OLr@S5=k;?CM^i#zkxs3Sj%z0U`L%q`qM+tP zX$aL;*^g$7UyM2Go+_4A+f)IQcy^G$h2E zb?nT$XlgTEFJI8GN6NQf%-eVn9mPilRqUbT$pN-|;FEjq@Ao&TxpZg=mEgBHB zU@grU;&sfmqlO=6|G3sU;7t8rbK$?X0y_v9$^{X`m4jZ_BR|B|@?ZCLSPPEzz`w1n zP5nA;4(kQFKm%$enjkkBxM%Y}2si&d|62L)U(dCzCGn56HN+i#6|nV-TGIo0;W;`( zW-y=1KF4dp$$mC_|6}pbb>IHoKQeZajXQB>jVR?u`R>%l1o54?6NnS*arpVopdEF; zeC5J3*M0p`*8lif;!irrcjC?(uExejsi~>4wKYwstGY^N@KY}TujLx`S=Cu+T=!dx zKWlPm->I**E{A*q-Z^FFT5$G%7Ij0_*Mo4-y6~RmyTzUB&lfae(WZfO>um}mnsDXPEbau-!13!!xd!qh*{C)6&bz0j1I{>y$D-S)b*)JMCPk!=~KL&6Ngin0p6MCOxF2L_R9t8N!$2Wpced<#`y!F;w zKTi5V_kX&X09wAIJ#anfg9Dhn0s7(C6Nj3S-mVn(i|C6ZAVq0$hE)874co};g z^hR7pe4lU$P;*ggYc4o&UTQC%liCXooIfkI3TNaBV%t~FRr}yHu7kjQ2J*3;e%;iW zvDVCh8=G80KAeyhCuY2LjrC!Od1rvF7h}zszxGV)&!)6ChP5WAjv-zQAMNJIG!JHS zwl?pLxC-V5II#(hQ`l)ZAp&M0xd4%cxmco*MIk?{BD=BK`1vpc}D39|XlV z{c&0oGdDa~TL2FT4lh=~1NL5O-P~0?V2#ie`v^CnANfGUM!b4F=JkCwd7Q`c8Na2q zJGQQk^?6w}Vg9-{|2047((lAV84uN%sK!N2?V(!_1{{v6rdgZl56f0zDMQ+q)jKzzu^ztsVken;=DjAh6G`Cw`Q4G+BjS+n*=KI~^K{W=%t zbD-rN)O4|*Q~@<#@1Vx$E!0W9`B~IZeFn87sHMXD>$M%|Bh93rdGf1lKoX3K651t&nhsl= zXxG|%@8}Bbrlp_u#t*DZX<}_0Yb{A9*1Pd_)LtqNwy6xT4pZrOY{s?N4)pPwT(i#y zT%`lRi8U#Ken4fw>H+N`{f#FF?ZxFlLZg7z7#cr4X>id z{9kUD`d2=w_Zlb{^c`5IOxWCZ1k<0T1D1Z31IU0Q2edsZ1K0xv$pQVYq2KEp&#v#Z z?{m@Lin;*Str(C2sfF^L>{R3cjY`~#)m>Wm$Y|1fzeS0-$(Q^z@} zEO*vlb-^XK9>w&Ef^=Zzo-1AFSP#9zb~X5_+){$(eB4K z8gtW+nl{q+CTh+>v(gWrsP^DB*ge(~Q$AGxJ-eYc1isti%$%nM<_&Ev?%|??PK`$p z{f-PM{Ym8k<$$)(F9)tqzFJ?h&Dk@D?Dt{4CHKJWLs8$zy6+(R)pr@0ur)xY{=uXFFzH_> z-F^tN1y(2hG8V)GpDg%wW0Px_ep~nIjD~*HCSxDi0y`H!`V*~RHs^uQsb1*bK1qGpmd zB1m`Cjw0`nLBF2|umz+a#2X$c?Lj;M?Lj;MUp*d>7j~ayNAyj@SLpeH`)BgRH}byy zyQSat!;U{@O(<<2fp&oQkIy$z`_CQ-)O@RN;QD9T4y|wIJ^%U#(BF%=`i49}j!D-) zkOwPSJaG03SMkE~BzW}b_v>LA&y)EEYO6sbdnTX*$>UF|JhZ&^MSb4}Tgbne_4n+C zwI8U4i~PI>7a3{kVa8|))*%C0|K+bIbmV~a`|G#+`TU#g zXW;bWIcWsQi9c4X*RUDpIfyoPY)2bI-r9)xulm1CJDkQd6u+f)_N=w1ElgEBjprPF z3o?Ly0RVeY_{3~fPVckRMxe2lM8hj!B8F)JO z!`AP6>u>5Y&3o9t0QxBpNE=lJx#NyIbp1gD zzUYBIPYHIv9ngk-Zt~<)62^1Zs1LLYMh@_tP^I7EX-9)Ed0^@y{k65Gp0KRcTmMWw zU|+)qx{#q0SL+4q?Q`i0>COIIF8a0Cf&C`hbMj?LmG9K&iW-?PJt*u)38tTXAP>@R zZL6uH^!RYNq$p>PKz7f-zvg>OKXcZ8h!%Vo@{VUZp|+iUD_xb(N~G|6c#oQK^nHZU zKg#F6<)+`rf~k*Xjjye+syV{bwU2glMMMs-^ss4`bYaVroXzn`YQUd__UlZL_mLs z(vO}k!~(mi|L+(5&;>r<;|OHnbXBE78LruP;{yBxZ6y7K3)nMo-{6PCI7gQi6+rF_ zkPod!Z8n}q46ykrlQS|hVB(}(2Kf7BCZ>Vc;V>ccbk2~NGaf6wGQH@W9&?Zt3v(h*P4xDrN>ex7+jH*+Qg z%^jH$&+*!v{sQ!xkWN4+>|b}qGvEd6ANzgqoVy5Qfws}ef2QqF{iiR5{pT}PS&yjo z>lron#va-p=v;m>WB+XVz|o;UJFdjo5_!RRD|6W{4}A2a#bZv)gS_`b|KsSH)Sd_JIr%<%n06TX&t{&!H#{)?4W9hlJ`R1>FyugOh3=D_{einr zu(Wf`qTkvED+gEULO0I*Hs%f;&=`=X4;N8Ovf28x$A*11`dmfy2=$+PNqX>XcG`h% zJY&A6@&)*WT^rC(Caj}2+|X|6cICm5h0OK0cGB_!wEKFZJU)OQ+TZ1q2bTx9hxnq& z$9ee|f9|0M^)#E&Pr4)f?o&DMM4w>Ksb{hF(0|wh+5_{vPow{V%TFzU2za&gjttNi zIyR9qA56dX52Qbv2aY^g`U7R43-p`#sO1A=KS2aKgfR+Yu^bQ*i-qu z%0mP;Ap)B~zZgO9lG^`325gOf?iUHF{~7jyGC)3L(eL(SQ70VzR~wLN18tnx(Cz2~ zctBl1kI)wAe+cxWHw*NW-d;=pd+>+wd$a@GBju*wFvabSaPtHiT!o#QFC+wBVwYo3s=y;z1jM+M=Fj!FZM>UzpL-eZzOT( zhmZmEfWa=%KE#V3-ZK5#v!Hzd{zc^{ctF~- z>DT-U`}5!fk$aj24`#uGdB7r`>oX5tU|d*b|N3V1lXmv%MGrvE(dXG)^-J*LA>$LE z7kut4`zE)v{@Op|(|@i#c>tM!12FQh?}PfA0`Bp%=%*RiXVzLDXnXtE@4B)5uR}a> zbNU}q+712pIrM`k^odG8dKtG$zwHmQI^c}tfjx5?egx3!e%JRm_64e+>`Ra1IRfLb z1KQ`SxmH{cZfyVS5m(&`{V}Y4j6J{b17`h6KWqZ&hfc(oR zxM%w!$F(mKy05kY&lco3%zvLCxBW+t*rxO+i=qGMvobx0-<7`VUu)ka`){=ew+Ovt zg%52_{&UbkUA8aJPWsk)gYWV4`dnxI%s?7^fGpq{ZQuu=VH{-t7w~K%_E<8`zS;V- zKTho*>;UQQul^1GT^HCt@I-q?)&4!QDgBndn?3sNKYKCQFU4LGKJ$n@Je$&w9@E$X z^p@iJ(v&`1(tq~1zc>0Vow-KR&vm!GUzT?Eqgnc)leZ9p)-Z*C!zqb=-$XG0 z^!8RfuQs5s>Q~qcz92(a_Q+KH?C*vCTr~UdTiR`JGuNH8v(J|FTiSEcPrBpmHRtmd zI2Jng0J=bXK);YY^rM?jzn?~X-Pe`GbAy{D)Y6D&1GY-EBcy%Bq?bKh?A>DD9DD!p z?{q02wno2sraGUkZv5dx+J8)&K$)No43Zr(*S`FEdL!4C)}WE}vJd%{S6-3VUw>Wp z?Aasv`T0^%P$2vE?L+Qhj~qB~K%eW)xH(=b_jU}TLD&BP*Pc9hz@Z=e0nkpLkWl}> z_5J^i(9Z7$(XG9~I3sY)`OGZ#_L06+Dy4E>UstcP-rU@xJ$&rxvo!n1Ao`P~KLU-8 z{zDgN4-&A6N!kPSYbQ&7sLufi`YtE2uN$S?e&5n>Y4(q#|KP!cc1j)T^QrUXMPFaP z_SoYO8S8G}Z$?AL4`;pE?7J5K8yWqy23>cCT2{=-)+A$X^-I9=e!@J@A&-;Ufc)`H}c(VI&;0x zrrGv()5mjP%jXzS{^|29?bLNXS0bC%p!YXI!;O457rjCEEzMkGf~B3$T}dXBO23tP z+Ci>;5UoM?C@bU@f9G1^X3=ly&ZeFH<@|RnOG--A&)fd)AUgjw?%izq{p(KJ`EP0v z2mU)P!+3t@X14DA=E2RR-|p${GZ9ETX=d+kJRZL$nSa0daI@&oUUxnZg0xd_xu>Vz lzF#z5%kSKX?YLH3ll^(hI(_`L*t#Iva2Ede*Z;>H_ Date: Sun, 12 Aug 2018 11:22:56 +0100 Subject: [PATCH 36/52] Add GetConnectionString() extension method Add a convenience extension method that just gets the connection string for a SQL LocalDB instance. --- .../ISqlLocalDbInstanceInfoExtensions.cs | 17 +++++++++++++++- .../ISqlLocalDbInstanceInfoExtensionsTests.cs | 20 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs index e0aa6e32..362f3b8f 100644 --- a/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs +++ b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -65,5 +65,20 @@ public static SqlConnectionStringBuilder CreateConnectionStringBuilder(this ISql DataSource = instance.NamedPipe, }; } + + /// + /// Gets the default SQL connection string to connect to the LocalDB instance. + /// + /// The SQL LocalDB instance to get the default connection string for. + /// + /// The default SQL connection string to connect to the LocalDB instance. + /// + /// + /// is . + /// + /// + /// The SQL LocalDB instance specified by is not running. + /// + public static string GetConnectionString(this ISqlLocalDbInstanceInfo instance) => instance.CreateConnectionStringBuilder().ConnectionString; } } diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs index 51a40bf2..5a1b57cd 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -66,5 +66,23 @@ public static void CreateConnectionStringBuilder_Initializes_Connection_String_B actual.ShouldNotBeNull(); actual.DataSource.ShouldBe("MyNamedPipe"); } + + [Fact] + public static void GetConnectionString_Returns_Sql_Connection_String() + { + // Arrange + var mock = new Mock(); + + mock.Setup((p) => p.IsRunning).Returns(true); + mock.Setup((p) => p.NamedPipe).Returns("MyNamedPipe"); + + ISqlLocalDbInstanceInfo instance = mock.Object; + + // Act + string actual = instance.GetConnectionString(); + + // Assert + actual.ShouldBe("Data Source=MyNamedPipe"); + } } } From b7300aef2bbe62c017390e1b4b7a76a06cfbdc54 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 11:25:21 +0100 Subject: [PATCH 37/52] Implement the TodoApp sample Implement sample TodoApp storing items in SQL LocalDB. --- .gitignore | 1 + samples/TodoApp/Controllers/HomeController.cs | 72 +++++++++++- samples/TodoApp/Data/ITodoRepository.cs | 61 ++++++++++ samples/TodoApp/Data/TodoContext.cs | 28 +++++ samples/TodoApp/Data/TodoInitializer.cs | 30 +++++ samples/TodoApp/Data/TodoItem.cs | 33 ++++++ samples/TodoApp/Data/TodoMigration.cs | 62 ++++++++++ samples/TodoApp/Data/TodoRepository.cs | 107 ++++++++++++++++++ samples/TodoApp/Models/TodoItemModel.cs | 16 +++ samples/TodoApp/Models/TodoListViewModel.cs | 12 ++ samples/TodoApp/Services/ITodoService.cs | 60 ++++++++++ samples/TodoApp/Services/TodoService.cs | 73 ++++++++++++ samples/TodoApp/Startup.cs | 36 ++++++ samples/TodoApp/TodoApp.csproj | 12 +- samples/TodoApp/Views/Home/Index.cshtml | 43 +++++-- samples/TodoApp/Views/Home/_AddItem.cshtml | 17 +++ samples/TodoApp/Views/Home/_ViewItem.cshtml | 29 +++++ samples/TodoApp/Views/Shared/_Layout.cshtml | 1 + samples/TodoApp/appsettings.json | 3 +- samples/TodoApp/bundleconfig.json | 6 + samples/TodoApp/wwwroot/js/site.js | 13 +++ 21 files changed, 701 insertions(+), 14 deletions(-) create mode 100644 samples/TodoApp/Data/ITodoRepository.cs create mode 100644 samples/TodoApp/Data/TodoContext.cs create mode 100644 samples/TodoApp/Data/TodoInitializer.cs create mode 100644 samples/TodoApp/Data/TodoItem.cs create mode 100644 samples/TodoApp/Data/TodoMigration.cs create mode 100644 samples/TodoApp/Data/TodoRepository.cs create mode 100644 samples/TodoApp/Models/TodoItemModel.cs create mode 100644 samples/TodoApp/Models/TodoListViewModel.cs create mode 100644 samples/TodoApp/Services/ITodoService.cs create mode 100644 samples/TodoApp/Services/TodoService.cs create mode 100644 samples/TodoApp/Views/Home/_AddItem.cshtml create mode 100644 samples/TodoApp/Views/Home/_ViewItem.cshtml create mode 100644 samples/TodoApp/wwwroot/js/site.js diff --git a/.gitignore b/.gitignore index 815dc717..da31d1f5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ obj packages project.lock.json samples/TodoApp/wwwroot/**/*.min.css +samples/TodoApp/wwwroot/**/*.min.js TestResults typings UpgradeLog*.htm diff --git a/samples/TodoApp/Controllers/HomeController.cs b/samples/TodoApp/Controllers/HomeController.cs index 86946c10..5dbaaec4 100644 --- a/samples/TodoApp/Controllers/HomeController.cs +++ b/samples/TodoApp/Controllers/HomeController.cs @@ -2,22 +2,84 @@ // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System.Diagnostics; -using MartinCostello.SqlLocalDb; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using TodoApp.Models; +using TodoApp.Services; namespace TodoApp.Controllers { public class HomeController : Controller { - private readonly ISqlLocalDbApi _localDB; + private readonly ITodoService _service; - public HomeController(ISqlLocalDbApi localDB) + public HomeController(ITodoService service) { - _localDB = localDB; + _service = service; } - public IActionResult Index() => View(); + [HttpGet] + public async Task Index(CancellationToken cancellationToken = default) + { + TodoListViewModel model = await _service.GetListAsync(cancellationToken); + return View(model); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddItem(string text, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(text)) + { + return BadRequest(); + } + + await _service.AddItemAsync(text, cancellationToken); + + return RedirectToAction(nameof(Index)); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task CompleteItem(string id, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(id)) + { + return BadRequest(); + } + + bool? result = await _service.CompleteItemAsync(id, cancellationToken); + + if (result == null) + { + return NotFound(); + } + + if (!result.Value) + { + return BadRequest(); + } + + return RedirectToAction(nameof(Index)); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DeleteItem(string id, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(id)) + { + return BadRequest(); + } + + if (!await _service.DeleteItemAsync(id, cancellationToken)) + { + return NotFound(); + } + + return RedirectToAction(nameof(Index)); + } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() diff --git a/samples/TodoApp/Data/ITodoRepository.cs b/samples/TodoApp/Data/ITodoRepository.cs new file mode 100644 index 00000000..3ec40c56 --- /dev/null +++ b/samples/TodoApp/Data/ITodoRepository.cs @@ -0,0 +1,61 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace TodoApp.Data +{ + /// + /// Defines the repository for TODO items. + /// + public interface ITodoRepository + { + /// + /// Adds a new item as an asynchronous operation. + /// + /// The text of the item to add. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation + /// to add the new item that returns the created item. + /// + Task AddItemAsync(string text, CancellationToken cancellationToken); + + /// + /// Marks an item as completed as an asynchronous operation. + /// + /// The id of the item to mark as completed. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation + /// to complete the item that if it was completed, + /// if it was already completed, or + /// if an item with the specified Id cannot be found. + /// + Task CompleteItemAsync(string id, CancellationToken cancellationToken); + + /// + /// Deletes an item as an asynchronous operation. + /// + /// The id of the item to delete. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation + /// to delete the item that returns if it was deleted, + /// otherwise if an item with the specified Id cannot be found. + /// + Task DeleteItemAsync(string id, CancellationToken cancellationToken); + + /// + /// Returns all items as an asynchronous operation. + /// + /// The cancellation token to use. + /// + /// A representing the asynchronous + /// operation to return all of the available items. + /// + Task> GetItemsAsync(CancellationToken cancellationToken); + } +} diff --git a/samples/TodoApp/Data/TodoContext.cs b/samples/TodoApp/Data/TodoContext.cs new file mode 100644 index 00000000..3c2d2ca4 --- /dev/null +++ b/samples/TodoApp/Data/TodoContext.cs @@ -0,0 +1,28 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using Microsoft.EntityFrameworkCore; + +namespace TodoApp.Data +{ + /// + /// A class representing the database context for TodoApp. + /// + public class TodoContext : DbContext + { + /// + /// Initializes a new instance of the class. + /// + /// The options for this context. + public TodoContext(DbContextOptions options) + : base(options) + { + Database.EnsureCreated(); + } + + /// + /// Gets or sets the database set containing the Todo items. + /// + public DbSet Items { get; set; } + } +} diff --git a/samples/TodoApp/Data/TodoInitializer.cs b/samples/TodoApp/Data/TodoInitializer.cs new file mode 100644 index 00000000..29a1714a --- /dev/null +++ b/samples/TodoApp/Data/TodoInitializer.cs @@ -0,0 +1,30 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace TodoApp.Data +{ + /// + /// A class representing the initializer for the TodoApp database context. + /// + public static class TodoInitializer + { + /// + /// Initializes the database for TodoApp. + /// + /// The to use. + public static void Initialize(IServiceProvider serviceProvider) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + var context = scope.ServiceProvider.GetService(); + + context.Database.EnsureCreated(); + context.Database.Migrate(); + } + } + } +} diff --git a/samples/TodoApp/Data/TodoItem.cs b/samples/TodoApp/Data/TodoItem.cs new file mode 100644 index 00000000..eaa0e55f --- /dev/null +++ b/samples/TodoApp/Data/TodoItem.cs @@ -0,0 +1,33 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; + +namespace TodoApp.Data +{ + /// + /// A class representing the database entity for a TODO item. + /// + public class TodoItem + { + /// + /// Gets or sets the Id of the item. + /// + public string Id { get; set; } + + /// + /// Gets or sets the text of the item. + /// + public string Text { get; set; } + + /// + /// Gets or sets the date and time the item was created. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// Gets or sets the date and time the item was completed. + /// + public DateTimeOffset? CompletedAt { get; set; } + } +} diff --git a/samples/TodoApp/Data/TodoMigration.cs b/samples/TodoApp/Data/TodoMigration.cs new file mode 100644 index 00000000..b965a379 --- /dev/null +++ b/samples/TodoApp/Data/TodoMigration.cs @@ -0,0 +1,62 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace TodoApp.Data +{ + /// + /// A class representing a migration that creates the initial database schema. This class cannot be inherited. + /// + [DbContext(typeof(TodoContext))] + [Migration(nameof(TodoMigration))] + public sealed class TodoMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: nameof(TodoContext.Items), + columns: (table) => new + { + Id = table.Column(nullable: false), + Text = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + CompletedAt = table.Column(nullable: true) + }, + constraints: (table) => + { + table.PrimaryKey($"PK_{nameof(TodoContext.Items)}", (p) => p.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + => migrationBuilder.DropTable(name: nameof(TodoContext.Items)); + + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity( + typeof(TodoItem).Name, + b => + { + b.Property(nameof(TodoItem.Id)).ValueGeneratedOnAdd(); + b.Property(nameof(TodoItem.Text)); + b.Property(nameof(TodoItem.CreatedAt)); + b.Property(nameof(TodoItem.CompletedAt)); + b.HasKey(nameof(TodoItem.Id)); + b.ToTable(nameof(TodoContext.Items)); + }); + } + } +} diff --git a/samples/TodoApp/Data/TodoRepository.cs b/samples/TodoApp/Data/TodoRepository.cs new file mode 100644 index 00000000..d66d6434 --- /dev/null +++ b/samples/TodoApp/Data/TodoRepository.cs @@ -0,0 +1,107 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace TodoApp.Data +{ + /// + /// A class representing a repository of TODO items. This class cannot be inherited. + /// + public sealed class TodoRepository : ITodoRepository + { + private readonly IClock _clock; + private readonly TodoContext _context; + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// The to use. + public TodoRepository(IClock clock, TodoContext context) + { + _clock = clock; + _context = context; + } + + /// + public async Task AddItemAsync(string text, CancellationToken cancellationToken) + { + var item = new TodoItem() + { + CreatedAt = Now(), + Text = text, + }; + + _context.Add(item); + + await _context.SaveChangesAsync(cancellationToken); + + return item; + } + + /// + public async Task CompleteItemAsync(string id, CancellationToken cancellationToken) + { + TodoItem item = await _context.Items.FindAsync(new[] { id }, cancellationToken); + + if (item == null) + { + return null; + } + + if (item.CompletedAt.HasValue) + { + return false; + } + + item.CompletedAt = Now(); + + _context.Items.Update(item); + + await _context.SaveChangesAsync(cancellationToken); + + return true; + } + + /// + public async Task DeleteItemAsync(string id, CancellationToken cancellationToken) + { + TodoItem item = await _context.Items.FindAsync(new[] { id }, cancellationToken); + + if (item == null) + { + return false; + } + + _context.Items.Remove(item); + + await _context.SaveChangesAsync(cancellationToken); + + return true; + } + + /// + public async Task> GetItemsAsync(CancellationToken cancellationToken) + { + return await _context.Items + .OrderBy((p) => p.CompletedAt.HasValue) + .ThenBy((p) => p.CreatedAt) + .ToListAsync(cancellationToken); + } + + /// + /// Returns the current date and time. + /// + /// + /// The for the current date and time. + /// + private DateTimeOffset Now() => _clock.GetCurrentInstant().ToDateTimeOffset(); + } +} diff --git a/samples/TodoApp/Models/TodoItemModel.cs b/samples/TodoApp/Models/TodoItemModel.cs new file mode 100644 index 00000000..7a07d451 --- /dev/null +++ b/samples/TodoApp/Models/TodoItemModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +namespace TodoApp.Models +{ + public class TodoItemModel + { + public string Id { get; set; } + + public string Text { get; set; } + + public bool IsCompleted { get; set; } + + public string LastUpdated { get; set; } + } +} diff --git a/samples/TodoApp/Models/TodoListViewModel.cs b/samples/TodoApp/Models/TodoListViewModel.cs new file mode 100644 index 00000000..51a2bed3 --- /dev/null +++ b/samples/TodoApp/Models/TodoListViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace TodoApp.Models +{ + public class TodoListViewModel + { + public ICollection Items { get; set; } = new List(); + } +} diff --git a/samples/TodoApp/Services/ITodoService.cs b/samples/TodoApp/Services/ITodoService.cs new file mode 100644 index 00000000..c3556d0b --- /dev/null +++ b/samples/TodoApp/Services/ITodoService.cs @@ -0,0 +1,60 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Threading; +using System.Threading.Tasks; +using TodoApp.Models; + +namespace TodoApp.Services +{ + /// + /// Defines a service for managing TODO items. + /// + public interface ITodoService + { + /// + /// Adds a new item as an asynchronous operation. + /// + /// The text of the item to add. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation to add the new item. + /// + Task AddItemAsync(string text, CancellationToken cancellationToken); + + /// + /// Marks an item as completed as an asynchronous operation. + /// + /// The id of the item to mark as completed. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation + /// to complete the item that if it was completed, + /// if it was already completed, or + /// if an item with the specified Id cannot be found. + /// + Task CompleteItemAsync(string id, CancellationToken cancellationToken); + + /// + /// Deletes an item as an asynchronous operation. + /// + /// The id of the item to delete. + /// The cancellation token to use. + /// + /// A representing the asynchronous operation + /// to delete the item that returns if it was deleted, + /// otherwise if an item with the specified Id cannot be found. + /// + Task DeleteItemAsync(string id, CancellationToken cancellationToken); + + /// + /// Returns all the TODO items as an asynchronous operation. + /// + /// The cancellation token to use. + /// + /// A representing the asynchronous + /// operation to return all of the available TODO items. + /// + Task GetListAsync(CancellationToken cancellationToken); + } +} diff --git a/samples/TodoApp/Services/TodoService.cs b/samples/TodoApp/Services/TodoService.cs new file mode 100644 index 00000000..9d814fbe --- /dev/null +++ b/samples/TodoApp/Services/TodoService.cs @@ -0,0 +1,73 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Humanizer; +using TodoApp.Data; +using TodoApp.Models; + +namespace TodoApp.Services +{ + /// + /// A class representing the class for managing TODO items. This class cannot be inherited. + /// + public sealed class TodoService : ITodoService + { + private readonly ITodoRepository _repository; + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + public TodoService(ITodoRepository repository) + { + _repository = repository; + } + + /// + public Task AddItemAsync(string text, CancellationToken cancellationToken) + { + return _repository.AddItemAsync(text, cancellationToken); + } + + /// + public Task CompleteItemAsync(string id, CancellationToken cancellationToken) + { + return _repository.CompleteItemAsync(id, cancellationToken); + } + + /// + public Task DeleteItemAsync(string id, CancellationToken cancellationToken) + { + return _repository.DeleteItemAsync(id, cancellationToken); + } + + /// + public async Task GetListAsync(CancellationToken cancellationToken) + { + IList items = await _repository.GetItemsAsync(cancellationToken); + + var result = new TodoListViewModel(); + + foreach (var todo in items) + { + result.Items.Add(MapItem(todo)); + } + + return result; + } + + private static TodoItemModel MapItem(TodoItem item) + { + return new TodoItemModel() + { + Id = item.Id, + IsCompleted = item.CompletedAt.HasValue, + LastUpdated = (item.CompletedAt ?? item.CreatedAt).Humanize(), + Text = item.Text, + }; + } + } +} diff --git a/samples/TodoApp/Startup.cs b/samples/TodoApp/Startup.cs index 357a9cb1..3fda526a 100644 --- a/samples/TodoApp/Startup.cs +++ b/samples/TodoApp/Startup.cs @@ -1,12 +1,17 @@ // Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +using System; using MartinCostello.SqlLocalDb; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using NodaTime; +using TodoApp.Data; +using TodoApp.Services; namespace TodoApp { @@ -21,10 +26,15 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { + services.AddSingleton((_) => SystemClock.Instance); services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddDbContext(AddTodoContext); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) @@ -43,6 +53,32 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvcWithDefaultRoute(); + + TodoInitializer.Initialize(app.ApplicationServices); + } + + private static void AddTodoContext(IServiceProvider serviceProvider, DbContextOptionsBuilder options) + { + IConfiguration config = serviceProvider.GetRequiredService(); + ISqlLocalDbApi localDB = serviceProvider.GetRequiredService(); + + if (!localDB.IsLocalDBInstalled()) + { + throw new NotSupportedException("SQL LocalDB is not installed."); + } + + string instanceName = config["SqlLocalDbInstance"]; + ISqlLocalDbInstanceInfo instance = localDB.GetOrCreateInstance(instanceName); + + if (!instance.IsRunning) + { + var manager = new SqlLocalDbInstanceManager(instance, localDB); + manager.Start(); + } + + string connectionString = instance.GetConnectionString(); + + options.UseSqlServer(connectionString); } } } diff --git a/samples/TodoApp/TodoApp.csproj b/samples/TodoApp/TodoApp.csproj index 6015c0b5..6c184e7d 100644 --- a/samples/TodoApp/TodoApp.csproj +++ b/samples/TodoApp/TodoApp.csproj @@ -1,13 +1,23 @@  - $(NoWarn);CA1822 + $(NoWarn);CA1822;CA2007 TodoApp netcoreapp2.1 + + + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + diff --git a/samples/TodoApp/Views/Home/Index.cshtml b/samples/TodoApp/Views/Home/Index.cshtml index c547f558..bf405658 100644 --- a/samples/TodoApp/Views/Home/Index.cshtml +++ b/samples/TodoApp/Views/Home/Index.cshtml @@ -1,11 +1,40 @@ +@model TodoListViewModel + @{ ViewData["Title"] = "My List"; } -

Things To Do

-
-
    -
  • Item 1
  • -
  • Item 2
  • -
-
+

Things To Do

+
+@if (Model.Items.Count == 0) +{ + +} +else +{ +

+ My List +

+
+ + + + + + + + + + + @foreach (var item in Model.Items) + { + + } + +
ItemLast Updated
+
+} +
+ diff --git a/samples/TodoApp/Views/Home/_AddItem.cshtml b/samples/TodoApp/Views/Home/_AddItem.cshtml new file mode 100644 index 00000000..c88eebe0 --- /dev/null +++ b/samples/TodoApp/Views/Home/_AddItem.cshtml @@ -0,0 +1,17 @@ +

Add New Item

+ +
+ + + + + + + +
diff --git a/samples/TodoApp/Views/Home/_ViewItem.cshtml b/samples/TodoApp/Views/Home/_ViewItem.cshtml new file mode 100644 index 00000000..23938eb5 --- /dev/null +++ b/samples/TodoApp/Views/Home/_ViewItem.cshtml @@ -0,0 +1,29 @@ +@model TodoItemModel + + + @if (Model.IsCompleted) + { + @Model.Text + } + else + { + @Model.Text + } + + @Model.LastUpdated + + @if (!Model.IsCompleted) + { +
+ + +
+ } + + +
+ + +
+ + diff --git a/samples/TodoApp/Views/Shared/_Layout.cshtml b/samples/TodoApp/Views/Shared/_Layout.cshtml index df4bb12c..5fab3c78 100644 --- a/samples/TodoApp/Views/Shared/_Layout.cshtml +++ b/samples/TodoApp/Views/Shared/_Layout.cshtml @@ -33,6 +33,7 @@ + @RenderSection("Scripts", required: false) diff --git a/samples/TodoApp/appsettings.json b/samples/TodoApp/appsettings.json index def9159a..fd10c22b 100644 --- a/samples/TodoApp/appsettings.json +++ b/samples/TodoApp/appsettings.json @@ -4,5 +4,6 @@ "Default": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "SqlLocalDbInstance": "SqlLocalDb-TodoApp" } diff --git a/samples/TodoApp/bundleconfig.json b/samples/TodoApp/bundleconfig.json index bdac79b2..2aee0e7f 100644 --- a/samples/TodoApp/bundleconfig.json +++ b/samples/TodoApp/bundleconfig.json @@ -4,5 +4,11 @@ "inputFiles": [ "wwwroot/css/site.css" ] + }, + { + "outputFileName": "wwwroot/js/site.min.js", + "inputFiles": [ + "wwwroot/js/site.js" + ] } ] diff --git a/samples/TodoApp/wwwroot/js/site.js b/samples/TodoApp/wwwroot/js/site.js new file mode 100644 index 00000000..7671a033 --- /dev/null +++ b/samples/TodoApp/wwwroot/js/site.js @@ -0,0 +1,13 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +const text = document.querySelector('#new-item-text'); +const button = document.querySelector('#add-new-item'); + +text.addEventListener('input', () => { + if (text.value.length === 0) { + button.setAttribute('disabled', ''); + } else { + button.removeAttribute('disabled'); + } +}); From 20be22bd5391e7acd611aa17c6330b54fbc3acd4 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 11:31:44 +0100 Subject: [PATCH 38/52] Add some comments to the TodoApp sample Add some comments to the Startup class of TodoApp to show where the library is used to configure the EntityFramework Core connection for the data context using LocalDB. --- samples/TodoApp/Startup.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/samples/TodoApp/Startup.cs b/samples/TodoApp/Startup.cs index 3fda526a..8bd7e2d3 100644 --- a/samples/TodoApp/Startup.cs +++ b/samples/TodoApp/Startup.cs @@ -54,12 +54,13 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseCookiePolicy(); app.UseMvcWithDefaultRoute(); + // Ensure that the database and schema exists TodoInitializer.Initialize(app.ApplicationServices); } private static void AddTodoContext(IServiceProvider serviceProvider, DbContextOptionsBuilder options) { - IConfiguration config = serviceProvider.GetRequiredService(); + // Check that SQL Server LocalDB is installed ISqlLocalDbApi localDB = serviceProvider.GetRequiredService(); if (!localDB.IsLocalDBInstalled()) @@ -67,17 +68,19 @@ private static void AddTodoContext(IServiceProvider serviceProvider, DbContextOp throw new NotSupportedException("SQL LocalDB is not installed."); } - string instanceName = config["SqlLocalDbInstance"]; - ISqlLocalDbInstanceInfo instance = localDB.GetOrCreateInstance(instanceName); + // Get the configured SQL LocalDB instance to store the TODO items in, creating it if it does not exist + IConfiguration config = serviceProvider.GetRequiredService(); + ISqlLocalDbInstanceInfo instance = localDB.GetOrCreateInstance(config["SqlLocalDbInstance"]); + // Ensure that the SQL LocalDB instance is running and start it if not already running if (!instance.IsRunning) { var manager = new SqlLocalDbInstanceManager(instance, localDB); manager.Start(); } + // Get the SQL connection string to use to connect to the LocalDB instance string connectionString = instance.GetConnectionString(); - options.UseSqlServer(connectionString); } } From 2c3a9fb947152f5ca8b60f6d5e8c69986e1aa97d Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:07:34 +0100 Subject: [PATCH 39/52] Remove leftover EnsureCreated() call Remove the call to create the EF database from TodoContext now that that is handled by the initializer. --- samples/TodoApp/Data/TodoContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/TodoApp/Data/TodoContext.cs b/samples/TodoApp/Data/TodoContext.cs index 3c2d2ca4..ec3544d6 100644 --- a/samples/TodoApp/Data/TodoContext.cs +++ b/samples/TodoApp/Data/TodoContext.cs @@ -17,7 +17,6 @@ public class TodoContext : DbContext public TodoContext(DbContextOptions options) : base(options) { - Database.EnsureCreated(); } /// From 6835c26b3a4a8cb6a374d679fb4fb202cb8550da Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:08:08 +0100 Subject: [PATCH 40/52] Make CancellationToken optional Make the CancellationToken parameter on the methods for ITodoRepository optional so tests can be less verbose. --- samples/TodoApp/Data/ITodoRepository.cs | 16 ++++++++-------- samples/TodoApp/Data/TodoRepository.cs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/samples/TodoApp/Data/ITodoRepository.cs b/samples/TodoApp/Data/ITodoRepository.cs index 3ec40c56..3c701b1b 100644 --- a/samples/TodoApp/Data/ITodoRepository.cs +++ b/samples/TodoApp/Data/ITodoRepository.cs @@ -16,46 +16,46 @@ public interface ITodoRepository /// Adds a new item as an asynchronous operation. /// /// The text of the item to add. - /// The cancellation token to use. + /// The optional cancellation token to use. /// /// A representing the asynchronous operation /// to add the new item that returns the created item. /// - Task AddItemAsync(string text, CancellationToken cancellationToken); + Task AddItemAsync(string text, CancellationToken cancellationToken = default); /// /// Marks an item as completed as an asynchronous operation. /// /// The id of the item to mark as completed. - /// The cancellation token to use. + /// The optional cancellation token to use. /// /// A representing the asynchronous operation /// to complete the item that if it was completed, /// if it was already completed, or /// if an item with the specified Id cannot be found. /// - Task CompleteItemAsync(string id, CancellationToken cancellationToken); + Task CompleteItemAsync(string id, CancellationToken cancellationToken = default); /// /// Deletes an item as an asynchronous operation. /// /// The id of the item to delete. - /// The cancellation token to use. + /// The optional cancellation token to use. /// /// A representing the asynchronous operation /// to delete the item that returns if it was deleted, /// otherwise if an item with the specified Id cannot be found. /// - Task DeleteItemAsync(string id, CancellationToken cancellationToken); + Task DeleteItemAsync(string id, CancellationToken cancellationToken = default); /// /// Returns all items as an asynchronous operation. /// - /// The cancellation token to use. + /// The optional cancellation token to use. /// /// A representing the asynchronous /// operation to return all of the available items. /// - Task> GetItemsAsync(CancellationToken cancellationToken); + Task> GetItemsAsync(CancellationToken cancellationToken = default); } } diff --git a/samples/TodoApp/Data/TodoRepository.cs b/samples/TodoApp/Data/TodoRepository.cs index d66d6434..23fa882e 100644 --- a/samples/TodoApp/Data/TodoRepository.cs +++ b/samples/TodoApp/Data/TodoRepository.cs @@ -31,7 +31,7 @@ public TodoRepository(IClock clock, TodoContext context) } /// - public async Task AddItemAsync(string text, CancellationToken cancellationToken) + public async Task AddItemAsync(string text, CancellationToken cancellationToken = default) { var item = new TodoItem() { @@ -47,7 +47,7 @@ public async Task AddItemAsync(string text, CancellationToken cancella } /// - public async Task CompleteItemAsync(string id, CancellationToken cancellationToken) + public async Task CompleteItemAsync(string id, CancellationToken cancellationToken = default) { TodoItem item = await _context.Items.FindAsync(new[] { id }, cancellationToken); @@ -71,7 +71,7 @@ public async Task AddItemAsync(string text, CancellationToken cancella } /// - public async Task DeleteItemAsync(string id, CancellationToken cancellationToken) + public async Task DeleteItemAsync(string id, CancellationToken cancellationToken = default) { TodoItem item = await _context.Items.FindAsync(new[] { id }, cancellationToken); @@ -88,7 +88,7 @@ public async Task DeleteItemAsync(string id, CancellationToken cancellatio } /// - public async Task> GetItemsAsync(CancellationToken cancellationToken) + public async Task> GetItemsAsync(CancellationToken cancellationToken = default) { return await _context.Items .OrderBy((p) => p.CompletedAt.HasValue) From 97992de901546f64f1e798e92e961c970a98480a Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:08:29 +0100 Subject: [PATCH 41/52] Tidy up TodoApp project file Remove redundant items from the TodoApp's project file. --- samples/TodoApp/TodoApp.csproj | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/samples/TodoApp/TodoApp.csproj b/samples/TodoApp/TodoApp.csproj index 6c184e7d..4b005051 100644 --- a/samples/TodoApp/TodoApp.csproj +++ b/samples/TodoApp/TodoApp.csproj @@ -1,4 +1,4 @@ - + $(NoWarn);CA1822;CA2007 TodoApp @@ -12,12 +12,4 @@ - - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) - - From 2f2573b9d88ae59e8da825eeb3fa3f5e6ce46add Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:09:18 +0100 Subject: [PATCH 42/52] Add sample test project Add a test project for TodoApp showing usage of the TemporarySqlLocalDbInstance class. Add a README for the samples. --- SqlLocalDb.sln | 10 ++ samples/README.md | 19 ++++ samples/TodoApp.Tests/TodoApp.Tests.csproj | 17 +++ samples/TodoApp.Tests/TodoRepositoryTests.cs | 110 +++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 samples/README.md create mode 100644 samples/TodoApp.Tests/TodoApp.Tests.csproj create mode 100644 samples/TodoApp.Tests/TodoRepositoryTests.cs diff --git a/SqlLocalDb.sln b/SqlLocalDb.sln index aaf5f2f7..7c6d85a4 100644 --- a/SqlLocalDb.sln +++ b/SqlLocalDb.sln @@ -50,9 +50,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{701E EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{825D6F08-8321-4C6E-92A9-92543FDAF965}" + ProjectSection(SolutionItems) = preProject + samples\README.md = samples\README.md + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApp", "samples\TodoApp\TodoApp.csproj", "{1B720E31-3AA8-4010-A3DB-187B2D0BC471}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoApp.Tests", "samples\TodoApp.Tests\TodoApp.Tests.csproj", "{5B2AD682-C000-45D0-AECE-8DADED1616B7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +80,10 @@ Global {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B720E31-3AA8-4010-A3DB-187B2D0BC471}.Release|Any CPU.Build.0 = Release|Any CPU + {5B2AD682-C000-45D0-AECE-8DADED1616B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B2AD682-C000-45D0-AECE-8DADED1616B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B2AD682-C000-45D0-AECE-8DADED1616B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B2AD682-C000-45D0-AECE-8DADED1616B7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -86,6 +95,7 @@ Global {D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA} = {E207A447-68A1-4D72-B24F-89FB7890AE12} {701E574A-6366-4AF9-9319-237968FA1089} = {E207A447-68A1-4D72-B24F-89FB7890AE12} {1B720E31-3AA8-4010-A3DB-187B2D0BC471} = {825D6F08-8321-4C6E-92A9-92543FDAF965} + {5B2AD682-C000-45D0-AECE-8DADED1616B7} = {825D6F08-8321-4C6E-92A9-92543FDAF965} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3B9E157C-5E92-4357-B233-281B4530EABD} diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000..4b315c78 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,19 @@ +# SQL LocalDB Wrapper Samples + +This folder contains a sample application for showing usage of `MartinCostello.SqlLocalDb`. + +## TodoApp + +_TodoApp_ is an [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/ "Introduction to ASP.NET Core") MVC application that uses [EntityFramework Core](https://docs.microsoft.com/en-us/ef/core/ "Entity Framework Core") to manage a simple Todo list. + +The list is stored in a SQL Server database backed by a SQL Server LocalDB Instance on the local machine. + +The application is configured to create the LocalDB instance if it does not already exist, start it if necessary, and then configure the EntityFramework data context with the connection string for the LocalDB instance. + +## TodoApp.Tests + +A test project for the _TodoApp_ application that uses the `TemporarySqlLocalDbInstance` class in `MartinCostello.SqlLocalDb` to setup a temporary SQL database to run functional tests against the TodoApp's data-access logic. + +The test sets up a test instance, database and schema, then tests the query, create, update and delete functionality of the `TodoRepository` class. + +Once the test is run, the test database and LocalDB instance are deleted, also cleaning up the instance's files from the local machine. diff --git a/samples/TodoApp.Tests/TodoApp.Tests.csproj b/samples/TodoApp.Tests/TodoApp.Tests.csproj new file mode 100644 index 00000000..0851a1ed --- /dev/null +++ b/samples/TodoApp.Tests/TodoApp.Tests.csproj @@ -0,0 +1,17 @@ + + + $(NoWarn);CA1707;CA2007 + TodoApp + netcoreapp2.1 + false + + + + + + + + + + + diff --git a/samples/TodoApp.Tests/TodoRepositoryTests.cs b/samples/TodoApp.Tests/TodoRepositoryTests.cs new file mode 100644 index 00000000..813ab6ba --- /dev/null +++ b/samples/TodoApp.Tests/TodoRepositoryTests.cs @@ -0,0 +1,110 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MartinCostello.SqlLocalDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; +using NodaTime.Testing; +using TodoApp.Data; +using Xunit; + +namespace TodoApp.Tests +{ + public class TodoRepositoryTests + { + [Fact] + public async Task Can_Create_Update_And_Delete_Todo_Items() + { + // Arrange + var now = new DateTimeOffset(2018, 08, 12, 10, 41, 0, TimeSpan.Zero); + var clock = new FakeClock(Instant.FromDateTimeOffset(now)); + + var loggerFactory = new LoggerFactory(); + var options = new SqlLocalDbOptions() + { + AutomaticallyDeleteInstanceFiles = true, + StopOptions = StopInstanceOptions.NoWait, + StopTimeout = TimeSpan.FromSeconds(1), + }; + + using (var localDB = new SqlLocalDbApi(options, loggerFactory)) + { + using (TemporarySqlLocalDbInstance instance = localDB.CreateTemporaryInstance(deleteFiles: true)) + { + string connectionString = instance.GetInstanceInfo().GetConnectionString(); + + var builder = new DbContextOptionsBuilder().UseSqlServer(connectionString); + + using (var context = new TodoContext(builder.Options)) + { + context.Database.EnsureCreated(); + context.Database.Migrate(); + + var target = new TodoRepository(clock, context); + + // Act - Verify the repository is empty + IList items = await target.GetItemsAsync(); + + // Assert + Assert.NotNull(items); + Assert.Empty(items); + + // Arrange - Add a new item + string text = "Buy cheese"; + + // Act + TodoItem item = await target.AddItemAsync(text); + + // Assert + Assert.NotNull(item); + Assert.NotEmpty(item.Id); + Assert.Equal(text, item.Text); + Assert.Equal(now, item.CreatedAt); + Assert.Null(item.CompletedAt); + + // Arrange - Mark the item as completed + string id = item.Id; + + // Act + bool? completeResult = await target.CompleteItemAsync(id); + + // Assert + Assert.True(completeResult); + + // Act - Verify the repository has one item that is completed + items = await target.GetItemsAsync(); + + // Assert + Assert.NotNull(items); + Assert.NotEmpty(items); + Assert.Equal(1, items.Count); + + item = items[0]; + Assert.NotNull(item); + Assert.NotEmpty(item.Id); + Assert.Equal(text, item.Text); + Assert.Equal(now, item.CreatedAt); + Assert.Equal(now, item.CompletedAt); + + // Act - Delete the item + bool deleteResult = await target.DeleteItemAsync(id); + + // Assert + Assert.True(deleteResult); + + // Act - Verify the repository is empty again + items = await target.GetItemsAsync(); + + // Assert + Assert.NotNull(items); + Assert.Empty(items); + } + } + } + } + } +} From a6c901eec68c05c78ad26eef7c41bc2fc37e564f Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:15:50 +0100 Subject: [PATCH 43/52] Add sample project to CI builds Run the tests for TestApp in AppVeyor CI. Compile the solution in Travis CI. --- Build.ps1 | 10 ++++++++-- build.sh | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index c63de035..001fd652 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -13,7 +13,11 @@ $solutionFile = Join-Path $solutionPath "SqlLocalDb.sln" $sdkFile = Join-Path $solutionPath "global.json" $libraryProject = Join-Path $solutionPath "src\SqlLocalDb\MartinCostello.SqlLocalDb.csproj" -$testProject = Join-Path $solutionPath "tests\SqlLocalDb.Tests\MartinCostello.SqlLocalDb.Tests.csproj" + +$testProjects = @( + (Join-Path $solutionPath "tests\SqlLocalDb.Tests\MartinCostello.SqlLocalDb.Tests.csproj"), + (Join-Path $solutionPath "samples\TodoApp.Tests\TodoApp.Tests.csproj") +) $dotnetVersion = (Get-Content $sdkFile | Out-String | ConvertFrom-Json).sdk.version @@ -143,4 +147,6 @@ Write-Host "Packaging library..." -ForegroundColor Green DotNetPack $libraryProject Write-Host "Running tests..." -ForegroundColor Green -DotNetTest $testProject +ForEach ($testProject in $testProjects) { + DotNetTest $testProject +} diff --git a/build.sh b/build.sh index b4d20568..c9e39e23 100755 --- a/build.sh +++ b/build.sh @@ -39,6 +39,7 @@ if [ "$dotnet_version" != "$CLI_VERSION" ]; then curl -sSL https://raw.githubusercontent.com/dotnet/cli/v$CLI_VERSION/scripts/obtain/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR" fi +dotnet build ./SqlLocalDb.sln --output $artifacts --configuration $configuration || exit 1 dotnet pack ./src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj --output $artifacts --configuration $configuration || exit 1 if [ $skipTests == 0 ]; then From 3e632e856ee9ba9712f77b4b4d84495205d6c896 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:18:58 +0100 Subject: [PATCH 44/52] Add links to code to sample README Add links to the code to the sample's README. --- samples/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/README.md b/samples/README.md index 4b315c78..51824d69 100644 --- a/samples/README.md +++ b/samples/README.md @@ -4,15 +4,15 @@ This folder contains a sample application for showing usage of `MartinCostello.S ## TodoApp -_TodoApp_ is an [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/ "Introduction to ASP.NET Core") MVC application that uses [EntityFramework Core](https://docs.microsoft.com/en-us/ef/core/ "Entity Framework Core") to manage a simple Todo list. +_TodoApp_ is an [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/ "Introduction to ASP.NET Core") MVC application that uses [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/ "Entity Framework Core") to manage a simple Todo list. The list is stored in a SQL Server database backed by a SQL Server LocalDB Instance on the local machine. -The application is configured to create the LocalDB instance if it does not already exist, start it if necessary, and then configure the EntityFramework data context with the connection string for the LocalDB instance. +The application is configured to create the LocalDB instance if it does not already exist, start it if necessary, and then [configure the Entity Framework data context](https://github.com/martincostello/sqllocaldb/blob/a6c901eec68c05c78ad26eef7c41bc2fc37e564f/samples/TodoApp/Startup.cs#L61-L85 "View Startup.cs") with the connection string for the LocalDB instance. ## TodoApp.Tests -A test project for the _TodoApp_ application that uses the `TemporarySqlLocalDbInstance` class in `MartinCostello.SqlLocalDb` to setup a temporary SQL database to run functional tests against the TodoApp's data-access logic. +A test project for the _TodoApp_ application that uses the `TemporarySqlLocalDbInstance` class in `MartinCostello.SqlLocalDb` to [setup a temporary SQL database](https://github.com/martincostello/sqllocaldb/blob/a6c901eec68c05c78ad26eef7c41bc2fc37e564f/samples/TodoApp.Tests/TodoRepositoryTests.cs#L36 "View TodoRepositoryTests.cs") to run functional tests against the TodoApp's data-access logic. The test sets up a test instance, database and schema, then tests the query, create, update and delete functionality of the `TodoRepository` class. From 066d08b6c5a5cc918e65bb8e526058ee326660fd Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 12:22:51 +0100 Subject: [PATCH 45/52] Add links to samples to README Update the README to add links to the samples. --- readme.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index a3be55d0..53232bf9 100644 --- a/readme.md +++ b/readme.md @@ -27,13 +27,17 @@ dotnet add package MartinCostello.SqlLocalDb ### Basic Examples -_TODO_ +```cs +// TODO +``` ### Further Examples Further examples of using the library can be found by following the links below: -_TODO_ + 1. The [wiki](https://github.com/martincostello/sqllocaldb/wiki/Examples "Examples in the SQL LocalDB Wrapper wiki"). + 1. The [sample application](https://github.com/martincostello/sqllocaldb/tree/master/samples "TodoApp sample"). + 1. The library's [own tests](https://github.com/martincostello/sqllocaldb/tree/master/tests/SqlLocalDb.Tests "View MartinCostello.SqlLocalDb's tests"). ## Migrating from System.Data.SqlLocalDb From 346f41843eb26fb2c51a1546e515c12663fd1898 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 13:32:51 +0100 Subject: [PATCH 46/52] Add AddSqlLocalDB() extension Add an extension method to configure services for dependency injection. --- samples/TodoApp/Startup.cs | 3 +- .../MartinCostello.SqlLocalDb.csproj | 3 +- .../SqlLocalDbServiceCollectionExtensions.cs | 128 +++++++++++++++ .../MartinCostello.SqlLocalDb.Tests.csproj | 1 + ...LocalDbServiceCollectionExtensionsTests.cs | 153 ++++++++++++++++++ 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/SqlLocalDb/SqlLocalDbServiceCollectionExtensions.cs create mode 100644 tests/SqlLocalDb.Tests/SqlLocalDbServiceCollectionExtensionsTests.cs diff --git a/samples/TodoApp/Startup.cs b/samples/TodoApp/Startup.cs index 8bd7e2d3..2f0aec65 100644 --- a/samples/TodoApp/Startup.cs +++ b/samples/TodoApp/Startup.cs @@ -26,8 +26,9 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { + services.AddSqlLocalDB(); + services.AddSingleton((_) => SystemClock.Instance); - services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj b/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj index d0a61fe1..262e3951 100644 --- a/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj +++ b/src/SqlLocalDb/MartinCostello.SqlLocalDb.csproj @@ -1,4 +1,4 @@ - + SQL LocalDB Wrapper full @@ -19,6 +19,7 @@ + diff --git a/src/SqlLocalDb/SqlLocalDbServiceCollectionExtensions.cs b/src/SqlLocalDb/SqlLocalDbServiceCollectionExtensions.cs new file mode 100644 index 00000000..c10b15d4 --- /dev/null +++ b/src/SqlLocalDb/SqlLocalDbServiceCollectionExtensions.cs @@ -0,0 +1,128 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using System.ComponentModel; +using MartinCostello.SqlLocalDb; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// A class containing extension methods for the interface. This class cannot be inherited. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class SqlLocalDbServiceCollectionExtensions + { + /// + /// Adds SQL Server LocalDB services. + /// + /// The to add services to. + /// + /// The passed as the value of . + /// + /// + /// is . + /// + public static IServiceCollection AddSqlLocalDB(this IServiceCollection services) + => services.AddSqlLocalDB((_) => { }); + + /// + /// Adds SQL Server LocalDB services. + /// + /// The to add services to. + /// A delegate to a method to use to configure the SQL Server LocalDB options. + /// + /// The passed as the value of . + /// + /// + /// or is . + /// + public static IServiceCollection AddSqlLocalDB(this IServiceCollection services, Action configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + return services.AddSqlLocalDB( + (_) => + { + var options = new SqlLocalDbOptions(); + + configure(options); + + return options; + }); + } + + /// + /// Adds SQL Server LocalDB services. + /// + /// The to add services to. + /// A delegate to a method to use to configure the SQL Server LocalDB options. + /// + /// The passed as the value of . + /// + /// + /// or is . + /// + public static IServiceCollection AddSqlLocalDB(this IServiceCollection services, Func configure) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + services.TryAddSingleton(configure); + + services.TryAddSingleton( + (p) => + { + var options = p.GetRequiredService(); + var loggerFactory = p.GetRequiredService(); + + return new SqlLocalDbApi(options, loggerFactory); + }); + + return services; + } + + /// + /// Adds SQL Server LocalDB services. + /// + /// The to add services to. + /// The SQL Server LocalDB options to use. + /// + /// The passed as the value of . + /// + /// + /// or is . + /// + public static IServiceCollection AddSqlLocalDB(this IServiceCollection services, SqlLocalDbOptions options) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + return services.AddSqlLocalDB((_) => options); + } + } +} diff --git a/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj b/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj index 0633d79f..fa8c9e30 100644 --- a/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj +++ b/tests/SqlLocalDb.Tests/MartinCostello.SqlLocalDb.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbServiceCollectionExtensionsTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000..cdf33ee2 --- /dev/null +++ b/tests/SqlLocalDb.Tests/SqlLocalDbServiceCollectionExtensionsTests.cs @@ -0,0 +1,153 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Shouldly; +using Xunit; + +namespace MartinCostello.SqlLocalDb +{ + public static class SqlLocalDbServiceCollectionExtensionsTests + { + [Fact] + public static void AddSqlLocalDB_Validates_Parameters() + { + // Arrange + var services = Mock.Of(); + + SqlLocalDbOptions options = null; + Action configureAction = null; + Func configureFunc = null; + + // Act and Assert + Assert.Throws("options", () => services.AddSqlLocalDB(options)); + Assert.Throws("configure", () => services.AddSqlLocalDB(configureAction)); + Assert.Throws("configure", () => services.AddSqlLocalDB(configureFunc)); + + services = null; + + // Act and Assert + Assert.Throws("services", () => services.AddSqlLocalDB()); + Assert.Throws("services", () => services.AddSqlLocalDB(options)); + Assert.Throws("services", () => services.AddSqlLocalDB(configureAction)); + Assert.Throws("services", () => services.AddSqlLocalDB(configureFunc)); + } + + [Fact] + public static void AddSqlLocalDB_Registers_Services_Using_Defaults() + { + // Arrange + IServiceCollection services = CreateServiceCollection(); + + // Act + services.AddSqlLocalDB(); + + // Assert + IServiceProvider provider = services.BuildServiceProvider(); + + var options = provider.GetRequiredService(); + options.ShouldNotBeNull(); + + var localDB = provider.GetRequiredService(); + localDB.ShouldNotBeNull(); + localDB.ShouldBeOfType(); + } + + [Fact] + public static void AddSqlLocalDB_Registers_Services_Using_Options() + { + // Arrange + var options = new SqlLocalDbOptions() + { + StopOptions = (StopInstanceOptions)int.MinValue, + }; + + IServiceCollection services = CreateServiceCollection(); + + // Act + services.AddSqlLocalDB(options); + + // Assert + IServiceProvider provider = services.BuildServiceProvider(); + + var actualOptions = provider.GetRequiredService(); + options.ShouldBeSameAs(options); + + var localDB = provider.GetRequiredService(); + localDB.ShouldNotBeNull(); + localDB.ShouldBeOfType(); + } + + [Fact] + public static void AddSqlLocalDB_Registers_Services_Using_Action() + { + // Arrange + IServiceCollection services = CreateServiceCollection(); + + // Act + services.AddSqlLocalDB((p) => p.StopOptions = (StopInstanceOptions)int.MinValue); + + // Assert + IServiceProvider provider = services.BuildServiceProvider(); + + var options = provider.GetRequiredService(); + options.ShouldNotBeNull(); + options.StopOptions.ShouldBe((StopInstanceOptions)int.MinValue); + + var localDB = provider.GetRequiredService(); + localDB.ShouldNotBeNull(); + localDB.ShouldBeOfType(); + } + + [Fact] + public static void AddSqlLocalDB_Registers_Services_Using_Function() + { + // Arrange + IServiceCollection services = CreateServiceCollection(); + + // Act + services.AddSqlLocalDB((p) => new SqlLocalDbOptions() { StopOptions = (StopInstanceOptions)int.MinValue }); + + // Assert + IServiceProvider provider = services.BuildServiceProvider(); + + var options = provider.GetRequiredService(); + options.ShouldNotBeNull(); + options.StopOptions.ShouldBe((StopInstanceOptions)int.MinValue); + + var localDB = provider.GetRequiredService(); + localDB.ShouldNotBeNull(); + localDB.ShouldBeOfType(); + } + + [Fact] + public static void AddSqlLocalDB_Does_Not_Overwrite_Existing_Services() + { + // Arrange + IServiceCollection services = CreateServiceCollection(); + + var existingOptions = new SqlLocalDbOptions(); + var existingLocalDB = Mock.Of(); + + services.AddTransient((_) => existingOptions); + services.AddTransient((_) => existingLocalDB); + + // Act + services.AddSqlLocalDB(); + + // Assert + IServiceProvider provider = services.BuildServiceProvider(); + + var actualOptions = provider.GetRequiredService(); + actualOptions.ShouldBeSameAs(existingOptions); + + var actualLocalDB = provider.GetRequiredService(); + actualLocalDB.ShouldBeSameAs(existingLocalDB); + } + + private static IServiceCollection CreateServiceCollection() + => new ServiceCollection().AddLogging(); + } +} From 87c575eff7ce98e5161fd64d9f71c715a331b1e6 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 15:02:03 +0100 Subject: [PATCH 47/52] Fix en-GB resource string Fix incorrect en-GB resource string that wasn't updated when structured logging was added. --- src/SqlLocalDb/SR.en-GB.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SqlLocalDb/SR.en-GB.resx b/src/SqlLocalDb/SR.en-GB.resx index cad06d4a..91fa975e 100644 --- a/src/SqlLocalDb/SR.en-GB.resx +++ b/src/SqlLocalDb/SR.en-GB.resx @@ -118,6 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The current Language Id {0} is not recognised by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. + The current Language Id {LanguageId} is not recognised by SQL LocalDB. Use a valid Windows Locale ID (LCID) or set the value to zero to use the Windows defaults. \ No newline at end of file From ef6c1e2918ce084274cbc1e7d173371a7fbaebd3 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 15:28:18 +0100 Subject: [PATCH 48/52] Simplify instance management Add infrastructure to make it easier to manage instances of SQL Server LocalDB from the information interface. --- samples/TodoApp/Startup.cs | 3 +- src/SqlLocalDb/ISqlLocalDbApiAdapter.cs | 16 +++++++ .../ISqlLocalDbInstanceInfoExtensions.cs | 33 ++++++++++++++ src/SqlLocalDb/SR.Designer.cs | 9 ++++ src/SqlLocalDb/SR.resx | 3 ++ src/SqlLocalDb/SqlLocalDbApi.cs | 7 ++- src/SqlLocalDb/SqlLocalDbInstanceInfo.cs | 23 +++++++--- src/SqlLocalDb/SqlLocalDbInstanceManager.cs | 7 ++- src/SqlLocalDb/TemporarySqlLocalDbInstance.cs | 13 +++--- .../ISqlLocalDbApiExtensionsTests.cs | 19 +++++--- .../ISqlLocalDbInstanceInfoExtensionsTests.cs | 44 +++++++++++++++++++ .../SqlLocalDbInstanceInfoTests.cs | 20 ++++++--- 12 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 src/SqlLocalDb/ISqlLocalDbApiAdapter.cs diff --git a/samples/TodoApp/Startup.cs b/samples/TodoApp/Startup.cs index 2f0aec65..6e18835b 100644 --- a/samples/TodoApp/Startup.cs +++ b/samples/TodoApp/Startup.cs @@ -76,8 +76,7 @@ private static void AddTodoContext(IServiceProvider serviceProvider, DbContextOp // Ensure that the SQL LocalDB instance is running and start it if not already running if (!instance.IsRunning) { - var manager = new SqlLocalDbInstanceManager(instance, localDB); - manager.Start(); + instance.Manage().Start(); } // Get the SQL connection string to use to connect to the LocalDB instance diff --git a/src/SqlLocalDb/ISqlLocalDbApiAdapter.cs b/src/SqlLocalDb/ISqlLocalDbApiAdapter.cs new file mode 100644 index 00000000..51fb7237 --- /dev/null +++ b/src/SqlLocalDb/ISqlLocalDbApiAdapter.cs @@ -0,0 +1,16 @@ +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. + +namespace MartinCostello.SqlLocalDb +{ + /// + /// Defines an interface implemented by objects that can provide an instance. + /// + public interface ISqlLocalDbApiAdapter + { + /// + /// Gets the instance. + /// + ISqlLocalDbApi LocalDb { get; } + } +} diff --git a/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs index 362f3b8f..58cfe183 100644 --- a/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs +++ b/src/SqlLocalDb/ISqlLocalDbInstanceInfoExtensions.cs @@ -80,5 +80,38 @@ public static SqlConnectionStringBuilder CreateConnectionStringBuilder(this ISql /// The SQL LocalDB instance specified by is not running. /// public static string GetConnectionString(this ISqlLocalDbInstanceInfo instance) => instance.CreateConnectionStringBuilder().ConnectionString; + + /// + /// Returns an that can be used to manage the instance. + /// + /// The to manage. + /// + /// An that can be used to manage the SQL LocalDB instance. + /// + /// + /// is . + /// + /// + /// does not implement . + /// + public static ISqlLocalDbInstanceManager Manage(this ISqlLocalDbInstanceInfo instance) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + if (instance is ISqlLocalDbApiAdapter adapter) + { + return new SqlLocalDbInstanceManager(instance, adapter.LocalDb); + } + + string message = SRHelper.Format( + SR.ISqlLocalDbInstanceInfoExtensions_DoesNotImplementAdapterFormat, + nameof(ISqlLocalDbInstanceInfo), + nameof(ISqlLocalDbApiAdapter)); + + throw new ArgumentException(message, nameof(instance)); + } } } diff --git a/src/SqlLocalDb/SR.Designer.cs b/src/SqlLocalDb/SR.Designer.cs index 3477aa5b..375afef7 100644 --- a/src/SqlLocalDb/SR.Designer.cs +++ b/src/SqlLocalDb/SR.Designer.cs @@ -456,6 +456,15 @@ internal static string ILoggerExtensions_StoppingTracing { } } + /// + /// Looks up a localized string similar to The specified instance of {0} does not implement the {1} interface.. + /// + internal static string ISqlLocalDbInstanceInfoExtensions_DoesNotImplementAdapterFormat { + get { + return ResourceManager.GetString("ISqlLocalDbInstanceInfoExtensions_DoesNotImplementAdapterFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to The SQL LocalDB instance '{0}' is not running.. /// diff --git a/src/SqlLocalDb/SR.resx b/src/SqlLocalDb/SR.resx index c42d08fe..8f79e52b 100644 --- a/src/SqlLocalDb/SR.resx +++ b/src/SqlLocalDb/SR.resx @@ -297,4 +297,7 @@ An error occurred calling the SQL Server LocalDB with HRESULT {HResult}. + + The specified instance of {0} does not implement the {1} interface. + \ No newline at end of file diff --git a/src/SqlLocalDb/SqlLocalDbApi.cs b/src/SqlLocalDb/SqlLocalDbApi.cs index b0b6a105..97e103c7 100644 --- a/src/SqlLocalDb/SqlLocalDbApi.cs +++ b/src/SqlLocalDb/SqlLocalDbApi.cs @@ -18,7 +18,7 @@ namespace MartinCostello.SqlLocalDb /// /// A class representing a wrapper to the SQL Server LocalDB Instance API. This class cannot be inherited. /// - public sealed class SqlLocalDbApi : ISqlLocalDbApi, IDisposable + public sealed class SqlLocalDbApi : ISqlLocalDbApi, ISqlLocalDbApiAdapter, IDisposable { /// /// The name of the default instance in SQL LocalDB 2012. @@ -273,6 +273,9 @@ public IReadOnlyList Versions } } + /// + ISqlLocalDbApi ISqlLocalDbApiAdapter.LocalDb => this; + /// /// Gets a value indicating whether the executing platform is Microsoft Windows. /// @@ -560,7 +563,7 @@ public ISqlLocalDbInstanceInfo GetInstanceInfo(string instanceName) Logger.GotInstanceInfo(instanceName); - var result = new SqlLocalDbInstanceInfo(); + var result = new SqlLocalDbInstanceInfo(this); result.Update(info); diff --git a/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs index 19df2aae..b3f213eb 100644 --- a/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs +++ b/src/SqlLocalDb/SqlLocalDbInstanceInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -10,16 +10,19 @@ namespace MartinCostello.SqlLocalDb /// A class representing information about an instance of SQL LocalDB. This class cannot be inherited. /// [DebuggerDisplay("{Name}")] - internal sealed class SqlLocalDbInstanceInfo : ISqlLocalDbInstanceInfo + internal sealed class SqlLocalDbInstanceInfo : ISqlLocalDbInstanceInfo, ISqlLocalDbApiAdapter { + //// Internally created type used to provide "pit-of-success" semantics on + //// instances of ISqlLocalDbInstanceInfo so that the state can be updated + //// during mutations of the API without returning marshalled types directly. + /// /// Initializes a new instance of the class. /// - internal SqlLocalDbInstanceInfo() + /// The associated with the instance. + internal SqlLocalDbInstanceInfo(ISqlLocalDbApi api) { - // Internally created type used to provide "pit-of-success" semantics on - // instances of ISqlLocalDbInstanceInfo so that the state can be updated - // during mutations of the API without returning marshalled types directly. + Api = api; } /// @@ -55,6 +58,14 @@ internal SqlLocalDbInstanceInfo() /// public string SharedName { get; internal set; } + /// + ISqlLocalDbApi ISqlLocalDbApiAdapter.LocalDb => Api; + + /// + /// Gets the associated with this instance. + /// + private ISqlLocalDbApi Api { get; } + /// public override string ToString() => Name; diff --git a/src/SqlLocalDb/SqlLocalDbInstanceManager.cs b/src/SqlLocalDb/SqlLocalDbInstanceManager.cs index b5178c17..2daceeeb 100644 --- a/src/SqlLocalDb/SqlLocalDbInstanceManager.cs +++ b/src/SqlLocalDb/SqlLocalDbInstanceManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -10,7 +10,7 @@ namespace MartinCostello.SqlLocalDb /// A class that can be used to manage instances of SQL LocalDB. This class cannot be inherited. ///
[DebuggerDisplay("{Name}")] - public sealed class SqlLocalDbInstanceManager : ISqlLocalDbInstanceManager + public sealed class SqlLocalDbInstanceManager : ISqlLocalDbInstanceManager, ISqlLocalDbApiAdapter { /// /// Initializes a new instance of the class. @@ -32,6 +32,9 @@ public SqlLocalDbInstanceManager(ISqlLocalDbInstanceInfo instance, ISqlLocalDbAp /// public string NamedPipe => Instance.NamedPipe; + /// + ISqlLocalDbApi ISqlLocalDbApiAdapter.LocalDb => Api; + /// /// Gets the to use. /// diff --git a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs index 5e0fc33e..b3de29b4 100644 --- a/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs +++ b/src/SqlLocalDb/TemporarySqlLocalDbInstance.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -13,7 +13,7 @@ namespace MartinCostello.SqlLocalDb /// The temporary SQL LocalDB instances that are created by instances of this class are automatically /// started when they are instantiated, and are then subsequently deleted when they are disposed of. /// - public sealed class TemporarySqlLocalDbInstance : IDisposable + public sealed class TemporarySqlLocalDbInstance : IDisposable, ISqlLocalDbApiAdapter { /// /// The lazily initialized name of the temporary SQL LocalDB instance. This field is read-only. @@ -65,6 +65,9 @@ public string Name } } + /// + ISqlLocalDbApi ISqlLocalDbApiAdapter.LocalDb => Api; + /// /// Gets the to use. /// @@ -104,11 +107,7 @@ public void Dispose() /// /// Thrown if the instance has been disposed of. /// - public ISqlLocalDbInstanceManager Manage() - { - ISqlLocalDbInstanceInfo instance = GetInstanceInfo(); - return new SqlLocalDbInstanceManager(instance, Api); - } + public ISqlLocalDbInstanceManager Manage() => GetInstanceInfo().Manage(); /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index 4347be1f..221858ec 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -383,7 +383,7 @@ public void GetOrCreateInstance_Returns_The_Default_Instance_If_It_Exists_With_T .Returns(instanceName); mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(new SqlLocalDbInstanceInfo() { Exists = true }); + .Returns(CreateInstanceInfo(exists: true)); ISqlLocalDbApi api = mock.Object; @@ -407,7 +407,7 @@ public void GetOrCreateInstance_Returns_The_Default_Instance_If_It_Exists(string .Returns("Blah"); mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(new SqlLocalDbInstanceInfo() { Exists = true }); + .Returns(CreateInstanceInfo(exists: true)); ISqlLocalDbApi api = mock.Object; @@ -434,7 +434,7 @@ public void GetOrCreateInstance_Returns_An_Instance_If_It_Exists() .Returns(true); mock.Setup((p) => p.GetInstanceInfo(instanceName)) - .Returns(new SqlLocalDbInstanceInfo()); + .Returns(CreateInstanceInfo(exists: true)); ISqlLocalDbApi api = mock.Object; @@ -460,7 +460,7 @@ public void GetOrCreateInstance_Returns_An_Instance_If_It_Does_Not_Exist() .Returns("v99.0"); mock.Setup((p) => p.CreateInstance(instanceName, "v99.0")) - .Returns(new SqlLocalDbInstanceInfo()); + .Returns(CreateInstanceInfo(exists: false)); ISqlLocalDbApi api = mock.Object; @@ -501,5 +501,14 @@ public void ShareInstance_Shares_Instance_For_Current_User() } } } + + private static ISqlLocalDbInstanceInfo CreateInstanceInfo(bool exists) + { + var mock = new Mock(); + + mock.Setup((p) => p.Exists).Returns(exists); + + return mock.Object; + } } } diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs index 5a1b57cd..b212876a 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbInstanceInfoExtensionsTests.cs @@ -84,5 +84,49 @@ public static void GetConnectionString_Returns_Sql_Connection_String() // Assert actual.ShouldBe("Data Source=MyNamedPipe"); } + + [Fact] + public static void Manage_Throws_If_Instance_Is_Null() + { + // Arrange + ISqlLocalDbInstanceInfo instance = null; + + // Act and Assert + Assert.Throws("instance", () => instance.Manage()); + } + + [Fact] + public static void Manage_Throws_If_Instance_Does_Not_Implement_ISqlLocalDbApiAdapter() + { + // Arrange + var instance = Mock.Of(); + + // Act and Assert + Assert.Throws("instance", () => instance.Manage()).Message.ShouldStartWith("The specified instance of ISqlLocalDbInstanceInfo does not implement the ISqlLocalDbApiAdapter interface."); + } + + [Fact] + public static void Manage_Returns_An_ISqlLocalDbInstanceManager() + { + // Arrange + var api = Mock.Of(); + var mock = new Mock(); + + mock.As() + .Setup((p) => p.LocalDb).Returns(api); + + ISqlLocalDbInstanceInfo instance = mock.Object; + + // Act + ISqlLocalDbInstanceManager actual = instance.Manage(); + + // Assert + actual.ShouldNotBeNull(); + + var adapter = actual as ISqlLocalDbApiAdapter; + + adapter.ShouldNotBeNull(); + adapter.LocalDb.ShouldBeSameAs(api); + } } } diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs index 273c9fa7..23b1fc30 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbInstanceInfoTests.cs @@ -1,7 +1,8 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; +using Moq; using Shouldly; using Xunit; @@ -13,7 +14,9 @@ public static class SqlLocalDbInstanceInfoTests public static void Update_Copies_State_From_Other_Instance() { // Arrange - var other = new SqlLocalDbInstanceInfo() + var api = Mock.Of(); + + var other = new SqlLocalDbInstanceInfo(api) { ConfigurationCorrupt = true, Exists = true, @@ -28,7 +31,7 @@ public static void Update_Copies_State_From_Other_Instance() SharedName = "OtherSharedName", }; - var actual = new SqlLocalDbInstanceInfo() + var actual = new SqlLocalDbInstanceInfo(api) { ConfigurationCorrupt = false, Exists = false, @@ -64,9 +67,10 @@ public static void Update_Copies_State_From_Other_Instance() public static void Update_Does_Not_Copy_State_If_Other_Is_Null() { // Arrange + var api = Mock.Of(); ISqlLocalDbInstanceInfo other = null; - var actual = new SqlLocalDbInstanceInfo() + var actual = new SqlLocalDbInstanceInfo(api) { ConfigurationCorrupt = true, Exists = true, @@ -89,7 +93,9 @@ public static void Update_Does_Not_Copy_State_If_Other_Is_Null() public static void Update_Does_Not_Copy_State_If_Other_Is_Self() { // Arrange - var actual = new SqlLocalDbInstanceInfo() + var api = Mock.Of(); + + var actual = new SqlLocalDbInstanceInfo(api) { ConfigurationCorrupt = true, Exists = true, @@ -112,7 +118,9 @@ public static void Update_Does_Not_Copy_State_If_Other_Is_Self() public static void ToString_Returns_The_Name() { // Arrange - var info = new SqlLocalDbInstanceInfo() + var api = Mock.Of(); + + var info = new SqlLocalDbInstanceInfo(api) { ConfigurationCorrupt = true, Exists = true, From 640aea38bcbaa73abb241f42fc7a395adbda7e6d Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 15:29:41 +0100 Subject: [PATCH 49/52] Update sample link Update the link to the sample LocalDB configuration in TodoApp. --- samples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/README.md b/samples/README.md index 51824d69..4d031539 100644 --- a/samples/README.md +++ b/samples/README.md @@ -8,7 +8,7 @@ _TodoApp_ is an [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/ "In The list is stored in a SQL Server database backed by a SQL Server LocalDB Instance on the local machine. -The application is configured to create the LocalDB instance if it does not already exist, start it if necessary, and then [configure the Entity Framework data context](https://github.com/martincostello/sqllocaldb/blob/a6c901eec68c05c78ad26eef7c41bc2fc37e564f/samples/TodoApp/Startup.cs#L61-L85 "View Startup.cs") with the connection string for the LocalDB instance. +The application is configured to create the LocalDB instance if it does not already exist, start it if necessary, and then [configure the Entity Framework data context](https://github.com/martincostello/sqllocaldb/blob/ef6c1e2918ce084274cbc1e7d173371a7fbaebd3/samples/TodoApp/Startup.cs#L62-L85 "View Startup.cs") with the connection string for the LocalDB instance. ## TodoApp.Tests From 67a38f0580668de9eb178bc89d515020381881b0 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 15:48:21 +0100 Subject: [PATCH 50/52] Improve test coverage Add some additional unit tests to increase code coverage. --- tests/SqlLocalDb.Tests/EventIdsTests.cs | 4 ++- .../ISqlLocalDbApiExtensionsTests.cs | 16 +++++++++ tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs | 34 ++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/SqlLocalDb.Tests/EventIdsTests.cs b/tests/SqlLocalDb.Tests/EventIdsTests.cs index 305e4803..2fedd421 100644 --- a/tests/SqlLocalDb.Tests/EventIdsTests.cs +++ b/tests/SqlLocalDb.Tests/EventIdsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System.Reflection; @@ -65,6 +65,8 @@ public static class EventIdsTests [InlineData("UnsharingInstance", 52)] [InlineData("UnsharingInstanceFailed", 53)] [InlineData("UnsharedInstance", 54)] + [InlineData("NativeApiUnloaded", 55)] + [InlineData("GenericError", 56)] public static void EventId_Name_And_Value_Is_Correct(string name, int expected) { // Arrange diff --git a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs index 221858ec..c4d3ecf4 100644 --- a/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs +++ b/tests/SqlLocalDb.Tests/ISqlLocalDbApiExtensionsTests.cs @@ -173,6 +173,22 @@ public void TemporaryInstance_Disposes_Cleanly_If_Not_Used() } } + [Fact] + public void TemporaryInstance_Is_ISqlLocalDbApiAdapter() + { + // Arrange + var api = Mock.Of(); + + using (TemporarySqlLocalDbInstance instance = api.CreateTemporaryInstance()) + { + // Act + ISqlLocalDbApiAdapter adapter = instance; + + // Assert + adapter.LocalDb.ShouldBeSameAs(api); + } + } + [Fact] public void TemporaryInstance_Deletes_Instance_If_Start_Fails() { diff --git a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs index ee35d1f1..3879653a 100644 --- a/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs +++ b/tests/SqlLocalDb.Tests/SqlLocalDbApiTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -389,5 +389,37 @@ public void Can_Delete_User_Instances() namesAfter.ShouldBeSubsetOf(namesBefore); } } + + [Fact] + public void SqlLocalDbApi_Is_ISqlLocalDbApiAdapter() + { + // Arrange + using (var instance = new SqlLocalDbApi(_loggerFactory)) + { + // Act + ISqlLocalDbApiAdapter adapter = instance; + + // Assert + adapter.LocalDb.ShouldBeSameAs(instance); + } + } + + [WindowsOnlyFact] + public void SqlLocalDbApi_Throws_Exception_For_Native_Errors() + { + // Arrange + string instanceName = new string('$', 10000); + + using (var actual = new SqlLocalDbApi(_loggerFactory)) + { + // Act + var exception = Assert.Throws(() => actual.CreateInstance(instanceName)); + + // Assert + exception.ErrorCode.ShouldBe(SqlLocalDbErrors.InvalidParameter); + exception.Message.ShouldStartWith("The parameter for the LocalDB Instance API method is incorrect. Consult the API documentation."); + exception.InstanceName.ShouldBe(instanceName); + } + } } } From 584c975cb21e58d9a7b6a4a671f3bad1351973c5 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 15:55:20 +0100 Subject: [PATCH 51/52] Refactor TryGetLocalDbApiPath() Refactor the TryGetLocalDbApiPath() method to reduce the complexity and leverage Version.TryParse(). --- src/SqlLocalDb/Interop/LocalDbInstanceApi.cs | 36 +++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs index a63882e9..3b91c29d 100644 --- a/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs +++ b/src/SqlLocalDb/Interop/LocalDbInstanceApi.cs @@ -1,4 +1,4 @@ -// Copyright (c) Martin Costello, 2012-2018. All rights reserved. +// Copyright (c) Martin Costello, 2012-2018. All rights reserved. // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System; @@ -442,14 +442,7 @@ internal bool TryGetLocalDbApiPath(out string fileName) { fileName = null; - bool isWow64Process = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess; - - // Open the appropriate Registry key if running as a 32-bit process on a 64-bit machine - string keyName = string.Format( - CultureInfo.InvariantCulture, - @"SOFTWARE\{0}Microsoft\Microsoft SQL Server Local DB\Installed Versions", - isWow64Process ? @"Wow6432Node\" : string.Empty); - + string keyName = DeriveLocalDbRegistryKey(); IRegistryKey key = Registry.OpenSubKey(keyName); if (key == null) @@ -469,13 +462,7 @@ internal bool TryGetLocalDbApiPath(out string fileName) foreach (string versionString in key.GetSubKeyNames()) { - Version version; - - try - { - version = new Version(versionString); - } - catch (Exception ex) when (ex is ArgumentException || ex is FormatException || ex is OverflowException) + if (!Version.TryParse(versionString, out Version version)) { Logger.InvalidRegistryKey(versionString); continue; @@ -534,6 +521,23 @@ internal bool TryGetLocalDbApiPath(out string fileName) return true; } + /// + /// Derives the name of the Windows registry key name to use to locate the SQL LocalDB Instance API. + /// + /// + /// The registry key name to use for the current process. + /// + private static string DeriveLocalDbRegistryKey() + { + // Open the appropriate Registry key if running as a 32-bit process on a 64-bit machine + bool isWow64Process = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess; + + return string.Format( + CultureInfo.InvariantCulture, + @"SOFTWARE\{0}Microsoft\Microsoft SQL Server Local DB\Installed Versions", + isWow64Process ? @"Wow6432Node\" : string.Empty); + } + /// /// Ensures that the specified delegate to an unmanaged function is initialized. /// From 3f3b1d95b5ec56a225efc7a3a80e50908f51a554 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 12 Aug 2018 16:05:21 +0100 Subject: [PATCH 52/52] Update NuGet API key Update the NuGet API key for the package. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5b2296d0..3010c60c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ deploy: - provider: NuGet api_key: - secure: AUZFfhSn20XPUPIzbbt2x3xoa/5vdAVcYFVz7JkQYkYJYWbrIyz3chgTjEhI9Gs2 + secure: i1W6tTJcvjsCBgTrhbjqjyDi3pGWr8mLfn0J5dycmUK5WY6O8MApgPEQEVl1nEEp artifact: /.*\.nupkg/ skip_symbols: false on: