diff --git a/All.sln b/All.sln index 8d3e4962940..d563d5774aa 100644 --- a/All.sln +++ b/All.sln @@ -108,6 +108,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.Benchmarks", "benchm EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EF.Benchmarks.Shared", "benchmark\EF.Benchmarks.Shared\EF.Benchmarks.Shared.csproj", "{BFC26566-4C6D-4904-A559-8FFE09369901}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Sqlite.e_sqlcipher.Tests", "test\Microsoft.Data.Sqlite.Tests\Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj", "{7B598E0C-B8E2-4F1F-B53C-ED84178E65BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Sqlite.winsqlite3.Tests", "test\Microsoft.Data.Sqlite.Tests\Microsoft.Data.Sqlite.winsqlite3.Tests.csproj", "{B163761D-FB4A-4C80-BAB9-01905E1351EF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -282,6 +286,14 @@ Global {BFC26566-4C6D-4904-A559-8FFE09369901}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFC26566-4C6D-4904-A559-8FFE09369901}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFC26566-4C6D-4904-A559-8FFE09369901}.Release|Any CPU.Build.0 = Release|Any CPU + {7B598E0C-B8E2-4F1F-B53C-ED84178E65BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B598E0C-B8E2-4F1F-B53C-ED84178E65BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B598E0C-B8E2-4F1F-B53C-ED84178E65BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B598E0C-B8E2-4F1F-B53C-ED84178E65BE}.Release|Any CPU.Build.0 = Release|Any CPU + {B163761D-FB4A-4C80-BAB9-01905E1351EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B163761D-FB4A-4C80-BAB9-01905E1351EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B163761D-FB4A-4C80-BAB9-01905E1351EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B163761D-FB4A-4C80-BAB9-01905E1351EF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -329,6 +341,8 @@ Global {678AB38D-B27C-4690-A3F9-2D2488391658} = {293B4F79-3CB9-402A-A74C-B8108C41A7CF} {2642F4F0-69BE-4C43-94B7-B298FEC87D89} = {293B4F79-3CB9-402A-A74C-B8108C41A7CF} {BFC26566-4C6D-4904-A559-8FFE09369901} = {293B4F79-3CB9-402A-A74C-B8108C41A7CF} + {7B598E0C-B8E2-4F1F-B53C-ED84178E65BE} = {258D5057-81B9-40EC-A872-D21E27452749} + {B163761D-FB4A-4C80-BAB9-01905E1351EF} = {258D5057-81B9-40EC-A872-D21E27452749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {285A5EB4-BCF4-40EB-B9E1-DF6DBCB5E705} diff --git a/Microsoft.Data.Sqlite.slnf b/Microsoft.Data.Sqlite.slnf index 0ea10736dc3..02eee0ff2f8 100644 --- a/Microsoft.Data.Sqlite.slnf +++ b/Microsoft.Data.Sqlite.slnf @@ -4,7 +4,9 @@ "projects": [ "src\\Microsoft.Data.Sqlite.Core\\Microsoft.Data.Sqlite.Core.csproj", "src\\Microsoft.Data.Sqlite\\Microsoft.Data.Sqlite.csproj", - "test\\Microsoft.Data.Sqlite.Tests\\Microsoft.Data.Sqlite.Tests.csproj" + "test\\Microsoft.Data.Sqlite.Tests\\Microsoft.Data.Sqlite.Tests.csproj", + "test\\Microsoft.Data.Sqlite.Tests\\Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj", + "test\\Microsoft.Data.Sqlite.Tests\\Microsoft.Data.Sqlite.winsqlite3.Tests.csproj" ] } } \ No newline at end of file diff --git a/eng/Versions.props b/eng/Versions.props index 39cfa937609..a03c3acca25 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -27,6 +27,7 @@ 2.0.0 2.0.2 2.0.2 + 2.0.2 2.0.2 1.1.118 0.12.0 diff --git a/src/Microsoft.Data.Sqlite.Core/Extensions/SQLitePCLExtensions.cs b/src/Microsoft.Data.Sqlite.Core/Extensions/SQLitePCLExtensions.cs index 4fde8afcd64..c823566cf5f 100644 --- a/src/Microsoft.Data.Sqlite.Core/Extensions/SQLitePCLExtensions.cs +++ b/src/Microsoft.Data.Sqlite.Core/Extensions/SQLitePCLExtensions.cs @@ -1,13 +1,25 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; + // ReSharper disable once CheckNamespace // ReSharper disable InconsistentNaming namespace SQLitePCL { internal static class SQLitePCLExtensions { - public static bool EncryptionNotSupported() - => raw.GetNativeLibraryName() == "e_sqlite3"; + private static readonly Dictionary _knownLibraries = new Dictionary + { + { "e_sqlcipher", true }, + { "e_sqlite3", false}, + { "sqlcipher", true }, + { "winsqlite3", false } + }; + + public static bool? EncryptionSupported() + => _knownLibraries.TryGetValue(raw.GetNativeLibraryName(), out var supported) + ? supported + : default(bool?); } } diff --git a/src/Microsoft.Data.Sqlite.Core/Properties/InternalsVisibleTo.cs b/src/Microsoft.Data.Sqlite.Core/Properties/InternalsVisibleTo.cs index 623cd1c76f2..da0899d9556 100644 --- a/src/Microsoft.Data.Sqlite.Core/Properties/InternalsVisibleTo.cs +++ b/src/Microsoft.Data.Sqlite.Core/Properties/InternalsVisibleTo.cs @@ -3,5 +3,9 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo( + "Microsoft.Data.Sqlite.e_sqlcipher.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo( "Microsoft.Data.Sqlite.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo( + "Microsoft.Data.Sqlite.winsqlite3.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index d3e8ec0d28d..206dfe218bb 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -245,7 +245,7 @@ public override void Open() { if (!string.IsNullOrEmpty(ConnectionOptions.Password)) { - if (SQLitePCLExtensions.EncryptionNotSupported()) + if (SQLitePCLExtensions.EncryptionSupported() == false) { throw new InvalidOperationException(Resources.EncryptionNotSupported); } @@ -256,7 +256,10 @@ public override void Open() "SELECT quote($password);", new SqliteParameter("$password", ConnectionOptions.Password)); this.ExecuteNonQuery("PRAGMA key = " + quotedPassword + ";"); + } + if (SQLitePCLExtensions.EncryptionSupported() != false) + { // NB: Forces decryption. Throws when the key is incorrect. this.ExecuteNonQuery("SELECT COUNT(*) FROM sqlite_master;"); } diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj index f774e88af44..b1d2594a916 100644 --- a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.Tests.csproj @@ -2,6 +2,7 @@ $(DefaultNetCoreTargetFramework) + $(DefineConstants);E_SQLITE3 diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj new file mode 100644 index 00000000000..1d4bf4b7a03 --- /dev/null +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.e_sqlcipher.Tests.csproj @@ -0,0 +1,16 @@ + + + + $(DefaultNetCoreTargetFramework) + $(DefineConstants);E_SQLCIPHER + + + + + + + + + + + diff --git a/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj new file mode 100644 index 00000000000..900da98f60d --- /dev/null +++ b/test/Microsoft.Data.Sqlite.Tests/Microsoft.Data.Sqlite.winsqlite3.Tests.csproj @@ -0,0 +1,16 @@ + + + + $(DefaultNetCoreTargetFramework) + $(DefineConstants);WINSQLITE3 + + + + + + + + + + + diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs index 3132cdadf5e..6b52947b996 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs @@ -5,11 +5,11 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Globalization; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; using Microsoft.Data.Sqlite.Properties; +using Microsoft.Data.Sqlite.Utilities; using Xunit; using static SQLitePCL.raw; @@ -260,20 +260,18 @@ public void Open_works_when_memory_shared() [Fact] public void Open_works_when_password() { - switch (GetNativeLibraryName()) - { - case "e_sqlite3": - Open_works_when_password_e_sqlite3(); - break; - - // NB: Change project dependencies to test this - case "e_sqlcipher": - Open_works_when_password_sqlcipher(); - break; - } +#if E_SQLITE3 || WINSQLITE3 + Open_works_when_password_unsupported(); +#elif E_SQLCIPHER || SQLCIPHER + Open_works_when_password_supported(); +#elif SQLITE3 + Open_works_when_password_might_be_supported(); +#else +#error Unexpected native library +#endif } - private void Open_works_when_password_e_sqlite3() + private void Open_works_when_password_unsupported() { using (var connection = new SqliteConnection("Data Source=encrypted.db;Password=password")) { @@ -288,12 +286,8 @@ private void Open_works_when_password_e_sqlite3() } } - private void Open_works_when_password_sqlcipher() + private void Open_works_when_password_supported() { - var es = new CultureInfo("es"); - Thread.CurrentThread.CurrentCulture = es; - Thread.CurrentThread.CurrentUICulture = es; - using (var connection1 = new SqliteConnection("Data Source=encrypted.db;Password=password")) { connection1.Open(); @@ -315,6 +309,12 @@ private void Open_works_when_password_sqlcipher() } } + private void Open_works_when_password_might_be_supported() + { + using var connection = new SqliteConnection("Data Source=encrypted.db;Password=password"); + connection.Open(); + } + [Theory] [InlineData("True", 1L)] [InlineData("False", 0L)] diff --git a/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteTestFramework.cs b/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteTestFramework.cs new file mode 100644 index 00000000000..e475625eb10 --- /dev/null +++ b/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteTestFramework.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using System; + +#if WINSQLITE3 +using System.Runtime.InteropServices; +#endif + +[assembly: TestFramework( + "Microsoft.Data.Sqlite.Tests.TestUtilities.SqliteTestFramework", +#if E_SQLITE3 + "Microsoft.Data.Sqlite.Tests")] +#elif E_SQLCIPHER + "Microsoft.Data.Sqlite.e_sqlcipher.Tests")] +#elif WINSQLITE3 + "Microsoft.Data.Sqlite.winsqlite3.Tests")] +#else +#error Unexpected native library +#endif + +namespace Microsoft.Data.Sqlite.Tests.TestUtilities +{ + class SqliteTestFramework : XunitTestFramework + { + protected SqliteTestFramework(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + } + + protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) + => new SqliteTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); + } + + class SqliteTestFrameworkExecutor : XunitTestFrameworkExecutor + { + public SqliteTestFrameworkExecutor( + AssemblyName assemblyName, + ISourceInformationProvider sourceInformationProvider, + IMessageSink diagnosticMessageSink) + : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) + { + } + + protected override async void RunTestCases( + IEnumerable testCases, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + { + using var assemblyRunner = new SqliteTestAssemblyRunner( + TestAssembly, + testCases, + DiagnosticMessageSink, + executionMessageSink, + executionOptions); + await assemblyRunner.RunAsync(); + } + } + + class SqliteTestAssemblyRunner : XunitTestAssemblyRunner + { + public SqliteTestAssemblyRunner( + ITestAssembly testAssembly, + IEnumerable testCases, + IMessageSink diagnosticMessageSink, + IMessageSink executionMessageSink, + ITestFrameworkExecutionOptions executionOptions) + : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions) + { + } + + + protected override Task RunTestCollectionAsync( + IMessageBus messageBus, + ITestCollection testCollection, + IEnumerable testCases, + CancellationTokenSource cancellationTokenSource) + { +#if WINSQLITE3 + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return SkipAll("winsqlite3 isn't supported on " + RuntimeInformation.OSDescription); + } + +#endif + var version = new SqliteConnection().ServerVersion; + if (new Version(version) < new Version(3, 16, 0)) + { + return SkipAll("SQLite " + version + " isn't supported. Upgrade to 3.16.0 or higher"); + } + + return new XunitTestCollectionRunner( + testCollection, + testCases, + DiagnosticMessageSink, + messageBus, + TestCaseOrderer, + new ExceptionAggregator(Aggregator), + cancellationTokenSource) + .RunAsync(); + + Task SkipAll(string reason) + { + var count = 0; + foreach (var testCase in testCases) + { + messageBus.QueueMessage(new TestSkipped(new XunitTest(testCase, testCase.DisplayName), reason)); + count++; + } + + return Task.FromResult(new RunSummary { Skipped = count, Total = count }); + } + } + } +}