mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-27 01:25:42 -05:00
Isolate Shared Test Code (#252)
The goal of this make sure that test code is correctly organized to ensure that test suites aren't dependent on each other.
* UnitTests get their own project now (renaming Microsoft.SqlTools.ServiceLayer.Test to Microsoft.SqlTools.ServiceLayer.UnitTests) which is about 90% of the changes to the files.
* IntegrationTests no longer depends on UnitTests, only Test.Common
* Any shared components from TestObjects that spins up a "live" connection has been moved to IntegrationTests Utility/LiveConnectionHelper.cs
* The dictionary-based mock file stream factory has been moved to Test.Common since it is used by UnitTests and IntegrationTests
* Added a overload that doesn't take a dictionary for when we don't care about monitoring the storage (about 90% of the time)
* The RunIf* wrapper methods have been moved to Test.Common
* OwnerUri and StandardQuery constants have been moved to Test.Common Constants file
* Updating to latest SDK version available at https://www.microsoft.com/net/core#windowscmd
* Moving unit tests to unit test folder
* Changing namespaces to UnitTests
* Moving some constants and shared functionality into common project, making the UnitTests reference it
* Unit tests are working!
* Integration tests are working
* Updating automated test runs
* Fixing one last broken unit test
* Exposing internals for other projects
* Moving edit data tests to UnitTest project
* Applying refactor fixes to unit tests
* Fixing flaky test that wasn't awaiting completion
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="xunit.methodDisplay" value="method"/>
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,8 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Completion
|
||||
{
|
||||
public class AutoCompletionResultTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
AutoCompletionResult result = new AutoCompletionResult();
|
||||
int duration = 2000;
|
||||
Thread.Sleep(duration);
|
||||
result.CompleteResult(new CompletionItem[] { });
|
||||
Assert.True(result.Duration >= duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Completion
|
||||
{
|
||||
public class ScriptDocumentInfoTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo();
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
Assert.Equal(docInfo.StartLine, 1);
|
||||
Assert.Equal(docInfo.ParserLine, 2);
|
||||
Assert.Equal(docInfo.StartColumn, 44);
|
||||
Assert.Equal(docInfo.EndColumn, 14);
|
||||
Assert.Equal(docInfo.ParserColumn, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for Sever Information Caching Class
|
||||
/// </summary>
|
||||
public class CachedServerInfoTests
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)] // is SqlDW instance
|
||||
[InlineData(false)] // is not a SqlDw Instance
|
||||
public void AddOrUpdateIsSqlDw(bool state)
|
||||
{
|
||||
// Set sqlDw result into cache
|
||||
bool isSqlDwResult;
|
||||
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||
|
||||
// Expect the same returned result
|
||||
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||
Assert.Equal(isSqlDwResult, state);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)] // is SqlDW instance
|
||||
[InlineData(false)] // is not a SqlDw Instance
|
||||
public void AddOrUpdateIsSqlDwFalseToggle(bool state)
|
||||
{
|
||||
// Set sqlDw result into cache
|
||||
bool isSqlDwResult;
|
||||
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||
|
||||
// Expect the same returned result
|
||||
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||
Assert.Equal(isSqlDwResult, state);
|
||||
|
||||
// Toggle isSqlDw cache state
|
||||
bool isSqlDwResultToggle;
|
||||
CachedServerInfo.AddOrUpdateCache("testDataSource", !state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||
|
||||
// Expect the oppisite returned result
|
||||
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResultToggle));
|
||||
Assert.Equal(isSqlDwResultToggle, !state);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdateIsSqlDwFalseToggle()
|
||||
{
|
||||
bool state = true;
|
||||
// Set sqlDw result into cache
|
||||
bool isSqlDwResult;
|
||||
bool isSqlDwResult2;
|
||||
CachedServerInfo.AddOrUpdateCache("testDataSource", state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||
CachedServerInfo.AddOrUpdateCache("testDataSource2", !state, CachedServerInfo.CacheVariable.IsSqlDw);
|
||||
|
||||
// Expect the same returned result
|
||||
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource", out isSqlDwResult));
|
||||
Assert.True(CachedServerInfo.TryGetIsSqlDw("testDataSource2", out isSqlDwResult2));
|
||||
|
||||
// Assert cache is set on a per connection basis
|
||||
Assert.Equal(isSqlDwResult, state);
|
||||
Assert.Equal(isSqlDwResult2, !state);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AskforSqlDwBeforeCached()
|
||||
{
|
||||
bool isSqlDwResult;
|
||||
Assert.False(CachedServerInfo.TryGetIsSqlDw("testDataSourceWithNoCache", out isSqlDwResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials
|
||||
{
|
||||
/// <summary>
|
||||
/// Credential Service tests that should pass on all platforms, regardless of backing store.
|
||||
/// These tests run E2E, storing values in the native credential store for whichever platform
|
||||
/// tests are being run on
|
||||
/// </summary>
|
||||
public class CredentialServiceTests : IDisposable
|
||||
{
|
||||
private static readonly StoreConfig config = new StoreConfig()
|
||||
{
|
||||
CredentialFolder = ".testsecrets",
|
||||
CredentialFile = "sqltestsecrets.json",
|
||||
IsRelativeToUserHomeDir = true
|
||||
};
|
||||
|
||||
const string credentialId = "Microsoft_SqlToolsTest_TestId";
|
||||
const string password1 = "P@ssw0rd1";
|
||||
const string password2 = "2Pass2Furious";
|
||||
|
||||
const string otherCredId = credentialId + "2345";
|
||||
const string otherPassword = credentialId + "2345";
|
||||
|
||||
// Test-owned credential store used to clean up before/after tests to ensure code works as expected
|
||||
// even if previous runs stopped midway through
|
||||
private ICredentialStore credStore;
|
||||
private CredentialService service;
|
||||
/// <summary>
|
||||
/// Constructor called once for every test
|
||||
/// </summary>
|
||||
public CredentialServiceTests()
|
||||
{
|
||||
credStore = CredentialService.GetStoreForOS(config);
|
||||
service = new CredentialService(credStore, config);
|
||||
DeleteDefaultCreds();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeleteDefaultCreds();
|
||||
}
|
||||
|
||||
private void DeleteDefaultCreds()
|
||||
{
|
||||
credStore.DeletePassword(credentialId);
|
||||
credStore.DeletePassword(otherCredId);
|
||||
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
string credsFolder = ((LinuxCredentialStore)credStore).CredentialFolderPath;
|
||||
if (Directory.Exists(credsFolder))
|
||||
{
|
||||
Directory.Delete(credsFolder, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialThrowsIfCredentialIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
await service.HandleSaveCredentialRequest(new Credential(null), contextMock.Object);
|
||||
TestUtils.VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialThrowsIfPasswordMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
await service.HandleSaveCredentialRequest(new Credential(credentialId), contextMock.Object);
|
||||
TestUtils.VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialWorksForSingleCredential()
|
||||
{
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveCredentialSupportsSavingCredentialMultipleTimes()
|
||||
{
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialWorksForSingleCredential()
|
||||
{
|
||||
// Given we have saved the credential
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
|
||||
|
||||
// Expect read of the credential to return the password
|
||||
await TestUtils.RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password1, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialWorksForMultipleCredentials()
|
||||
{
|
||||
|
||||
// Given we have saved multiple credentials
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(otherCredId, otherPassword), requestContext),
|
||||
verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully")));
|
||||
|
||||
|
||||
// Expect read of the credentials to return the right password
|
||||
await TestUtils.RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password1, actual.Password);
|
||||
}));
|
||||
await TestUtils.RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(otherCredId, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(otherPassword, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialHandlesPasswordUpdate()
|
||||
{
|
||||
// Given we have saved twice with a different password
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password2), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// When we read the value for this credential
|
||||
// Then we expect only the last saved password to be found
|
||||
await TestUtils.RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.Equal(password2, actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialThrowsIfCredentialIsNull()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<Credential>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws on null, and this is sent as an error
|
||||
await service.HandleReadCredentialRequest(null, contextMock.Object);
|
||||
TestUtils.VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentNullException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialThrowsIfIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<Credential>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws with no ID
|
||||
await service.HandleReadCredentialRequest(new Credential(), contextMock.Object);
|
||||
TestUtils.VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadCredentialReturnsNullPasswordForMissingCredential()
|
||||
{
|
||||
// Given a credential whose password doesn't exist
|
||||
string credWithNoPassword = "Microsoft_SqlTools_CredThatDoesNotExist";
|
||||
|
||||
// When reading the credential
|
||||
// Then expect the credential to be returned but password left blank
|
||||
await TestUtils.RunAndVerify<Credential>(
|
||||
test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credWithNoPassword, null), requestContext),
|
||||
verify: (actual =>
|
||||
{
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal(credWithNoPassword, actual.CredentialId);
|
||||
Assert.Null(actual.Password);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteCredentialThrowsIfIdMissing()
|
||||
{
|
||||
object errorResponse = null;
|
||||
var contextMock = RequestContextMocks.Create<bool>(null).AddErrorHandling(obj => errorResponse = obj);
|
||||
|
||||
// Verify throws with no ID
|
||||
await service.HandleDeleteCredentialRequest(new Credential(), contextMock.Object);
|
||||
TestUtils.VerifyErrorSent(contextMock);
|
||||
Assert.True(((string)errorResponse).Contains("ArgumentException"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteCredentialReturnsTrueOnlyIfCredentialExisted()
|
||||
{
|
||||
// Save should be true
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// Then delete - should return true
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual => Assert.True(actual)));
|
||||
|
||||
// Then delete - should return false as no longer exists
|
||||
await TestUtils.RunAndVerify<bool>(
|
||||
test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext),
|
||||
verify: (actual => Assert.False(actual)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials.Linux
|
||||
{
|
||||
public class LinuxInteropTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetEUidReturnsInt()
|
||||
{
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
RunIfWrapper.RunIfLinux(() =>
|
||||
{
|
||||
Assert.NotNull(Interop.Sys.GetEUid());
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHomeDirectoryFromPwFindsHomeDir()
|
||||
{
|
||||
#if !WINDOWS_ONLY_BUILD
|
||||
RunIfWrapper.RunIfLinux(() =>
|
||||
{
|
||||
string userDir = LinuxCredentialStore.GetHomeDirectoryFromPw();
|
||||
Assert.StartsWith("/", userDir);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials.Win32
|
||||
{
|
||||
public class CredentialSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void CredentialSetCreate()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new CredentialSet());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetCreateWithTarget()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new CredentialSet("target"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetShouldBeIDisposable()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.True(new CredentialSet() is IDisposable, "CredentialSet needs to implement IDisposable Interface.");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoad()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential
|
||||
{
|
||||
Username = "username",
|
||||
Password = "password",
|
||||
Target = "target",
|
||||
Type = CredentialType.Generic
|
||||
};
|
||||
credential.Save();
|
||||
|
||||
CredentialSet set = new CredentialSet();
|
||||
set.Load();
|
||||
Assert.NotNull(set);
|
||||
Assert.NotEmpty(set);
|
||||
|
||||
credential.Delete();
|
||||
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoadShouldReturnSelf()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
CredentialSet set = new CredentialSet();
|
||||
Assert.IsType<CredentialSet>(set.Load());
|
||||
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CredentialSetLoadWithTargetFilter()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential
|
||||
{
|
||||
Username = "filteruser",
|
||||
Password = "filterpassword",
|
||||
Target = "filtertarget"
|
||||
};
|
||||
credential.Save();
|
||||
|
||||
CredentialSet set = new CredentialSet("filtertarget");
|
||||
Assert.Equal(1, set.Load().Count);
|
||||
set.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||
// Licensed under the Apache License 2.0
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials.Win32
|
||||
{
|
||||
public class Win32CredentialTests
|
||||
{
|
||||
[Fact]
|
||||
public void Credential_Create_ShouldNotThrowNull()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_ShouldNotThrowNull()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_And_Password_ShouldNotThrowNull()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username", "password"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Create_With_Username_Password_Target_ShouldNotThrowNull()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.NotNull(new Win32Credential("username", "password", "target"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_ShouldBe_IDisposable()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Assert.True(new Win32Credential() is IDisposable, "Credential should implement IDisposable Interface.");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Dispose_ShouldNotThrowException()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential().Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_ShouldThrowObjectDisposedException()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential disposed = new Win32Credential { Password = "password" };
|
||||
disposed.Dispose();
|
||||
Assert.Throws<ObjectDisposedException>(() => disposed.Username = "username");
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Save()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential saved = new Win32Credential("username", "password", "target", CredentialType.Generic);
|
||||
saved.PersistanceType = PersistanceType.LocalComputer;
|
||||
Assert.True(saved.Save());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Delete()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential("username", "password", "target").Save();
|
||||
Assert.True(new Win32Credential("username", "password", "target").Delete());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Delete_NullTerminator()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential credential = new Win32Credential((string)null, (string)null, "\0", CredentialType.None);
|
||||
credential.Description = (string)null;
|
||||
Assert.False(credential.Delete());
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Load()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
Win32Credential setup = new Win32Credential("username", "password", "target", CredentialType.Generic);
|
||||
setup.Save();
|
||||
|
||||
Win32Credential credential = new Win32Credential { Target = "target", Type = CredentialType.Generic };
|
||||
Assert.True(credential.Load());
|
||||
|
||||
Assert.NotEmpty(credential.Username);
|
||||
Assert.NotNull(credential.Password);
|
||||
Assert.Equal("username", credential.Username);
|
||||
Assert.Equal("password", credential.Password);
|
||||
Assert.Equal("target", credential.Target);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Credential_Exists_Target_ShouldNotBeNull()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
new Win32Credential { Username = "username", Password = "password", Target = "target" }.Save();
|
||||
|
||||
Win32Credential existingCred = new Win32Credential { Target = "target" };
|
||||
Assert.True(existingCred.Exists());
|
||||
|
||||
existingCred.Delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class CellUpdateTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullColumnTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate with a null column
|
||||
// Then: I should get an exception thrown
|
||||
Assert.Throws<ArgumentNullException>(() => new CellUpdate(null, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStringValueTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate with a null string value
|
||||
// Then: I should get an exception thrown
|
||||
Assert.Throws<ArgumentNullException>(() => new CellUpdate(new CellUpdateTestDbColumn(null), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStringTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate to set it to NULL (with mixed cases)
|
||||
const string nullString = "NULL";
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(string));
|
||||
CellUpdate cu = new CellUpdate(col, nullString);
|
||||
|
||||
// Then: The value should be a DBNull and the string value should be the same as what
|
||||
// was given
|
||||
Assert.IsType<DBNull>(cu.Value);
|
||||
Assert.Equal(DBNull.Value, cu.Value);
|
||||
Assert.Equal(nullString, cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullTextStringTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate with the text 'NULL' (with mixed case)
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(string));
|
||||
CellUpdate cu = new CellUpdate(col, "'NULL'");
|
||||
|
||||
// Then: The value should be NULL
|
||||
Assert.IsType<string>(cu.Value);
|
||||
Assert.Equal("NULL", cu.Value);
|
||||
Assert.Equal("'NULL'", cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ByteArrayTestParams))]
|
||||
public void ByteArrayTest(string strValue, byte[] expectedValue, string expectedString)
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a binary column
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(byte[]));
|
||||
CellUpdate cu = new CellUpdate(col, strValue);
|
||||
|
||||
// Then: The value should be a binary and should match the expected data
|
||||
Assert.IsType<byte[]>(cu.Value);
|
||||
Assert.Equal(expectedValue, cu.Value);
|
||||
Assert.Equal(expectedString, cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> ByteArrayTestParams
|
||||
{
|
||||
get
|
||||
{
|
||||
// All zero tests
|
||||
yield return new object[] {"00000000", new byte[] {0x00}, "0x00"}; // Base10
|
||||
yield return new object[] {"0x000000", new byte[] {0x00, 0x00, 0x00}, "0x000000"}; // Base16
|
||||
yield return new object[] {"0x000", new byte[] {0x00, 0x00}, "0x0000"}; // Base16, odd
|
||||
|
||||
// Single byte tests
|
||||
yield return new object[] {"50", new byte[] {0x32}, "0x32"}; // Base10
|
||||
yield return new object[] {"050", new byte[] {0x32}, "0x32"}; // Base10, leading zeros
|
||||
yield return new object[] {"0xF0", new byte[] {0xF0}, "0xF0"}; // Base16
|
||||
yield return new object[] {"0x0F", new byte[] {0x0F}, "0x0F"}; // Base16, leading zeros
|
||||
yield return new object[] {"0xF", new byte[] {0x0F}, "0x0F"}; // Base16, odd
|
||||
|
||||
// Two byte tests
|
||||
yield return new object[] {"1000", new byte[] {0x03, 0xE8}, "0x03E8"}; // Base10
|
||||
yield return new object[] {"01000", new byte[] {0x03, 0xE8}, "0x03E8"}; // Base10, leading zeros
|
||||
yield return new object[] {"0xF001", new byte[] {0xF0, 0x01}, "0xF001"}; // Base16
|
||||
yield return new object[] {"0x0F10", new byte[] {0x0F, 0x10}, "0x0F10"}; // Base16, leading zeros
|
||||
yield return new object[] {"0xF10", new byte[] {0x0F, 0x10}, "0x0F10"}; // Base16, odd
|
||||
|
||||
// Three byte tests
|
||||
yield return new object[] {"100000", new byte[] {0x01, 0x86, 0xA0}, "0x0186A0"}; // Base10
|
||||
yield return new object[] {"0100000", new byte[] {0x01, 0x86, 0xA0}, "0x0186A0"}; // Base10, leading zeros
|
||||
yield return new object[] {"0x101010", new byte[] {0x10, 0x10, 0x10}, "0x101010"}; // Base16
|
||||
yield return new object[] {"0x010101", new byte[] {0x01, 0x01, 0x01}, "0x010101"}; // Base16, leading zeros
|
||||
yield return new object[] {"0x10101", new byte[] {0x01, 0x01, 0x01}, "0x010101"}; // Base16, odd
|
||||
|
||||
// Four byte tests
|
||||
yield return new object[] {"20000000", new byte[] {0x01, 0x31, 0x2D, 0x00}, "0x01312D00"}; // Base10
|
||||
yield return new object[] {"020000000", new byte[] {0x01, 0x31, 0x2D, 0x00}, "0x01312D00"}; // Base10, leading zeros
|
||||
yield return new object[] {"0xF0F00101", new byte[] {0xF0, 0xF0, 0x01, 0x01}, "0xF0F00101"}; // Base16
|
||||
yield return new object[] {"0x0F0F1010", new byte[] {0x0F, 0x0F, 0x10, 0x10}, "0x0F0F1010"}; // Base16, leading zeros
|
||||
yield return new object[] {"0xF0F1010", new byte[] {0x0F, 0x0F, 0x10, 0x10}, "0x0F0F1010"}; // Base16, odd
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ByteArrayInvalidFormatTest()
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a binary column
|
||||
// Then: It should throw an exception
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(byte[]));
|
||||
Assert.Throws<FormatException>(() => new CellUpdate(col, "this is totally invalid"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BoolTestParams))]
|
||||
public void BoolTest(string input, bool output, string outputString)
|
||||
{
|
||||
// If: I attempt to create a CellUpdate for a boolean column
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(bool));
|
||||
CellUpdate cu = new CellUpdate(col, input);
|
||||
|
||||
// Then: The value should match what was expected
|
||||
Assert.IsType<bool>(cu.Value);
|
||||
Assert.Equal(output, cu.Value);
|
||||
Assert.Equal(outputString, cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> BoolTestParams
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {"1", true, bool.TrueString};
|
||||
yield return new object[] {"0", false, bool.FalseString};
|
||||
yield return new object[] {bool.TrueString, true, bool.TrueString};
|
||||
yield return new object[] {bool.FalseString, false, bool.FalseString};
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolInvalidFormatTest()
|
||||
{
|
||||
// If: I create a CellUpdate for a bool column and provide an invalid numeric value
|
||||
// Then: It should throw an exception
|
||||
DbColumn col = new CellUpdateTestDbColumn(typeof(bool));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new CellUpdate(col, "12345"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RoundTripTestParams))]
|
||||
public void RoundTripTest(Type dbColType, object obj)
|
||||
{
|
||||
// Setup: Figure out the test string
|
||||
string testString = obj.ToString();
|
||||
|
||||
// If: I attempt to create a CellUpdate for a GUID column
|
||||
DbColumn col = new CellUpdateTestDbColumn(dbColType);
|
||||
CellUpdate cu = new CellUpdate(col, testString);
|
||||
|
||||
// Then: The value and type should match what we put in
|
||||
Assert.IsType(dbColType, cu.Value);
|
||||
Assert.Equal(obj, cu.Value);
|
||||
Assert.Equal(testString, cu.ValueAsString);
|
||||
Assert.Equal(col, cu.Column);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> RoundTripTestParams
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {typeof(Guid), Guid.NewGuid()};
|
||||
yield return new object[] {typeof(TimeSpan), new TimeSpan(0, 1, 20, 0, 123)};
|
||||
yield return new object[] {typeof(DateTime), new DateTime(2016, 04, 25, 9, 45, 0)};
|
||||
yield return new object[]
|
||||
{
|
||||
typeof(DateTimeOffset),
|
||||
new DateTimeOffset(2016, 04, 25, 9, 45, 0, TimeSpan.FromHours(8))
|
||||
};
|
||||
yield return new object[] {typeof(long), 1000L};
|
||||
yield return new object[] {typeof(decimal), new decimal(3.14)};
|
||||
yield return new object[] {typeof(int), 1000};
|
||||
yield return new object[] {typeof(short), (short) 1000};
|
||||
yield return new object[] {typeof(byte), (byte) 5};
|
||||
yield return new object[] {typeof(double), 3.14d};
|
||||
yield return new object[] {typeof(float), 3.14f};
|
||||
}
|
||||
}
|
||||
|
||||
private class CellUpdateTestDbColumn : DbColumn
|
||||
{
|
||||
public CellUpdateTestDbColumn(Type dataType)
|
||||
{
|
||||
DataType = dataType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
public const string OwnerUri = "testFile";
|
||||
|
||||
public static IEditTableMetadata GetMetadata(DbColumn[] columns, bool allKeys = true, bool isMemoryOptimized = false)
|
||||
{
|
||||
// Create a Column Metadata Provider
|
||||
var columnMetas = columns.Select((c, i) =>
|
||||
new EditColumnWrapper
|
||||
{
|
||||
DbColumn = new DbColumnWrapper(c),
|
||||
EscapedName = c.ColumnName,
|
||||
Ordinal = i,
|
||||
IsKey = c.IsIdentity.HasTrue()
|
||||
}).ToArray();
|
||||
|
||||
// Create a table metadata provider
|
||||
var tableMetaMock = new Mock<IEditTableMetadata>();
|
||||
if (allKeys)
|
||||
{
|
||||
// All columns should be returned as "keys"
|
||||
tableMetaMock.Setup(m => m.KeyColumns).Returns(columnMetas);
|
||||
}
|
||||
else
|
||||
{
|
||||
// All identity columns should be returned as keys
|
||||
tableMetaMock.Setup(m => m.KeyColumns).Returns(columnMetas.Where(c => c.DbColumn.IsIdentity.HasTrue()));
|
||||
}
|
||||
tableMetaMock.Setup(m => m.Columns).Returns(columnMetas);
|
||||
tableMetaMock.Setup(m => m.IsMemoryOptimized).Returns(isMemoryOptimized);
|
||||
tableMetaMock.Setup(m => m.EscapedMultipartName).Returns("tbl");
|
||||
|
||||
return tableMetaMock.Object;
|
||||
}
|
||||
|
||||
public static DbColumn[] GetColumns(bool includeIdentity)
|
||||
{
|
||||
List<DbColumn> columns = new List<DbColumn>();
|
||||
|
||||
if (includeIdentity)
|
||||
{
|
||||
columns.Add(new TestDbColumn("id", true));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
columns.Add(new TestDbColumn($"col{i}"));
|
||||
}
|
||||
return columns.ToArray();
|
||||
}
|
||||
|
||||
public static ResultSet GetResultSet(DbColumn[] columns, bool includeIdentity)
|
||||
{
|
||||
object[][] rows = includeIdentity
|
||||
? new[] { new object[] { "id", "1", "2", "3" } }
|
||||
: new[] { new object[] { "1", "2", "3" } };
|
||||
var testResultSet = new TestResultSet(columns, rows);
|
||||
var reader = new TestDbDataReader(new[] { testResultSet });
|
||||
var resultSet = new ResultSet(reader, 0, 0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(CancellationToken.None).Wait();
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
public static void AddCells(RowEditBase rc, bool includeIdentity)
|
||||
{
|
||||
// Skip the first column since if identity, since identity columns can't be updated
|
||||
int start = includeIdentity ? 1 : 0;
|
||||
for (int i = start; i < rc.AssociatedResultSet.Columns.Length; i++)
|
||||
{
|
||||
rc.SetCell(i, "123");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class RowCreateTests
|
||||
{
|
||||
[Fact]
|
||||
public void RowCreateConstruction()
|
||||
{
|
||||
// Setup: Create the values to store
|
||||
const long rowId = 100;
|
||||
ResultSet rs = QueryExecution.Common.GetBasicExecutedBatch().ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
|
||||
// If: I create a RowCreate instance
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
|
||||
// Then: The values I provided should be available
|
||||
Assert.Equal(rowId, rc.RowId);
|
||||
Assert.Equal(rs, rc.AssociatedResultSet);
|
||||
Assert.Equal(etm, rc.AssociatedObjectMetadata);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetScript(bool includeIdentity)
|
||||
{
|
||||
// Setup: Generate the parameters for the row create
|
||||
const long rowId = 100;
|
||||
DbColumn[] columns = Common.GetColumns(includeIdentity);
|
||||
ResultSet rs = Common.GetResultSet(columns, includeIdentity);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns);
|
||||
|
||||
// If: I ask for a script to be generated without an identity column
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
Common.AddCells(rc, includeIdentity);
|
||||
string script = rc.GetScript();
|
||||
|
||||
// Then:
|
||||
// ... The script should not be null,
|
||||
Assert.NotNull(script);
|
||||
|
||||
// ... It should be formatted as an insert script
|
||||
Regex r = new Regex(@"INSERT INTO (.+)\((.*)\) VALUES \((.*)\)");
|
||||
var m = r.Match(script);
|
||||
Assert.True(m.Success);
|
||||
|
||||
// ... It should have 3 columns and 3 values (regardless of the presence of an identity col)
|
||||
string tbl = m.Groups[1].Value;
|
||||
string cols = m.Groups[2].Value;
|
||||
string vals = m.Groups[3].Value;
|
||||
Assert.Equal(etm.EscapedMultipartName, tbl);
|
||||
Assert.Equal(3, cols.Split(',').Length);
|
||||
Assert.Equal(3, vals.Split(',').Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetScriptMissingCell()
|
||||
{
|
||||
// Setup: Generate the parameters for the row create
|
||||
const long rowId = 100;
|
||||
DbColumn[] columns = Common.GetColumns(false);
|
||||
ResultSet rs = Common.GetResultSet(columns, false);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns);
|
||||
|
||||
// If: I ask for a script to be generated without setting any values
|
||||
// Then: An exception should be thrown for missing cells
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
Assert.Throws<InvalidOperationException>(() => rc.GetScript());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class RowDeleteTests
|
||||
{
|
||||
[Fact]
|
||||
public void RowDeleteConstruction()
|
||||
{
|
||||
// Setup: Create the values to store
|
||||
const long rowId = 100;
|
||||
ResultSet rs = QueryExecution.Common.GetBasicExecutedBatch().ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
|
||||
// If: I create a RowCreate instance
|
||||
RowCreate rc = new RowCreate(rowId, rs, etm);
|
||||
|
||||
// Then: The values I provided should be available
|
||||
Assert.Equal(rowId, rc.RowId);
|
||||
Assert.Equal(rs, rc.AssociatedResultSet);
|
||||
Assert.Equal(etm, rc.AssociatedObjectMetadata);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetScriptTest(bool isHekaton)
|
||||
{
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isHekaton);
|
||||
|
||||
// If: I ask for a script to be generated for delete
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
string script = rd.GetScript();
|
||||
|
||||
// Then:
|
||||
// ... The script should not be null
|
||||
Assert.NotNull(script);
|
||||
|
||||
// ... It should be formatted as a delete script
|
||||
string scriptStart = $"DELETE FROM {etm.EscapedMultipartName}";
|
||||
if (isHekaton)
|
||||
{
|
||||
scriptStart += " WITH(SNAPSHOT)";
|
||||
}
|
||||
Assert.StartsWith(scriptStart, script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCell()
|
||||
{
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false);
|
||||
|
||||
// If: I set a cell on a delete row edit
|
||||
// Then: It should throw as invalid operation
|
||||
RowDelete rd = new RowDelete(0, rs, etm);
|
||||
Assert.Throws<InvalidOperationException>(() => rd.SetCell(0, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class RowEditBaseTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(-1)] // Negative index
|
||||
[InlineData(100)] // Index larger than number of columns
|
||||
public void ValidateUpdatableColumnOutOfRange(int columnId)
|
||||
{
|
||||
// Setup: Create a result set
|
||||
ResultSet rs = GetResultSet(
|
||||
new DbColumn[] { new TestDbColumn("id", true), new TestDbColumn("col1")},
|
||||
new object[] { "id", "1" });
|
||||
|
||||
// If: I validate a column ID that is out of range
|
||||
// Then: It should throw
|
||||
RowEditTester tester = new RowEditTester(rs, null);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => tester.ValidateColumn(columnId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateUpdatableColumnNotUpdatable()
|
||||
{
|
||||
// Setup: Create a result set with an identity column
|
||||
ResultSet rs = GetResultSet(
|
||||
new DbColumn[] { new TestDbColumn("id", true), new TestDbColumn("col1") },
|
||||
new object[] { "id", "1" });
|
||||
|
||||
// If: I validate a column ID that is not updatable
|
||||
// Then: It should throw
|
||||
RowEditTester tester = new RowEditTester(rs, null);
|
||||
Assert.Throws<InvalidOperationException>(() => tester.ValidateColumn(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetWhereClauseIsNotNullData))]
|
||||
public void GetWhereClauseSimple(DbColumn col, object val, string nullClause)
|
||||
{
|
||||
// Setup: Create a result set and metadata provider with a single column
|
||||
var cols = new[] {col};
|
||||
ResultSet rs = GetResultSet(cols, new[] {val});
|
||||
IEditTableMetadata etm = Common.GetMetadata(cols);
|
||||
|
||||
RowEditTester rt = new RowEditTester(rs, etm);
|
||||
rt.ValidateWhereClauseSingleKey(nullClause);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> GetWhereClauseIsNotNullData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {new TestDbColumn("col"), DBNull.Value, "IS NULL"};
|
||||
yield return new object[] {new TestDbColumn("col", "VARBINARY", typeof(byte[])), new byte[5], "IS NOT NULL"};
|
||||
yield return new object[] {new TestDbColumn("col", "TEXT", typeof(string)), "abc", "IS NOT NULL"};
|
||||
yield return new object[] {new TestDbColumn("col", "NTEXT", typeof(string)), "abc", "IS NOT NULL"};
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetWhereClauseMultipleKeyColumns()
|
||||
{
|
||||
// Setup: Create a result set and metadata provider with multiple key columns
|
||||
DbColumn[] cols = {new TestDbColumn("col1"), new TestDbColumn("col2")};
|
||||
ResultSet rs = GetResultSet(cols, new object[] {"abc", "def"});
|
||||
IEditTableMetadata etm = Common.GetMetadata(cols);
|
||||
|
||||
RowEditTester rt = new RowEditTester(rs, etm);
|
||||
rt.ValidateWhereClauseMultipleKeys();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetWhereClauseNoKeyColumns()
|
||||
{
|
||||
// Setup: Create a result set and metadata provider with no key columns
|
||||
DbColumn[] cols = {new TestDbColumn("col1"), new TestDbColumn("col2")};
|
||||
ResultSet rs = GetResultSet(cols, new object[] {"abc", "def"});
|
||||
IEditTableMetadata etm = Common.GetMetadata(new DbColumn[] {});
|
||||
|
||||
RowEditTester rt = new RowEditTester(rs, etm);
|
||||
rt.ValidateWhereClauseNoKeys();
|
||||
}
|
||||
|
||||
private static ResultSet GetResultSet(DbColumn[] columns, object[] row)
|
||||
{
|
||||
object[][] rows = {row};
|
||||
var testResultSet = new TestResultSet(columns, rows);
|
||||
var testReader = new TestDbDataReader(new [] {testResultSet});
|
||||
var resultSet = new ResultSet(testReader, 0,0, MemoryFileSystem.GetFileStreamFactory());
|
||||
resultSet.ReadResultToEnd(CancellationToken.None).Wait();
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private class RowEditTester : RowEditBase
|
||||
{
|
||||
public RowEditTester(ResultSet rs, IEditTableMetadata meta) : base(0, rs, meta) { }
|
||||
|
||||
public void ValidateColumn(int columnId)
|
||||
{
|
||||
ValidateColumnIsUpdatable(columnId);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
public void ValidateWhereClauseSingleKey(string nullValue)
|
||||
{
|
||||
// If: I generate a where clause with one is null column value
|
||||
WhereClause wc = GetWhereClause(false);
|
||||
|
||||
// Then:
|
||||
// ... There should only be one component
|
||||
Assert.Equal(1, wc.ClauseComponents.Count);
|
||||
|
||||
// ... Parameterization should be empty
|
||||
Assert.Empty(wc.Parameters);
|
||||
|
||||
// ... The component should contain the name of the column and be null
|
||||
Assert.Equal(
|
||||
$"({AssociatedObjectMetadata.Columns.First().EscapedName} {nullValue})",
|
||||
wc.ClauseComponents[0]);
|
||||
|
||||
// ... The complete clause should contain a single WHERE
|
||||
Assert.Equal($"WHERE {wc.ClauseComponents[0]}", wc.CommandText);
|
||||
}
|
||||
|
||||
public void ValidateWhereClauseMultipleKeys()
|
||||
{
|
||||
// If: I generate a where clause with multiple key columns
|
||||
WhereClause wc = GetWhereClause(false);
|
||||
|
||||
// Then:
|
||||
// ... There should two components
|
||||
var keys = AssociatedObjectMetadata.KeyColumns.ToArray();
|
||||
Assert.Equal(keys.Length, wc.ClauseComponents.Count);
|
||||
|
||||
// ... Parameterization should be empty
|
||||
Assert.Empty(wc.Parameters);
|
||||
|
||||
// ... The components should contain the name of the column and the value
|
||||
Regex r = new Regex(@"\([0-9a-z]+ = .+\)");
|
||||
Assert.All(wc.ClauseComponents, s => Assert.True(r.IsMatch(s)));
|
||||
|
||||
// ... The complete clause should contain multiple cause components joined
|
||||
// with and
|
||||
Assert.True(wc.CommandText.StartsWith("WHERE "));
|
||||
Assert.True(wc.CommandText.EndsWith(string.Join(" AND ", wc.ClauseComponents)));
|
||||
}
|
||||
|
||||
public void ValidateWhereClauseNoKeys()
|
||||
{
|
||||
// If: I generate a where clause from metadata that doesn't have keys
|
||||
// Then: An exception should be thrown
|
||||
Assert.Throws<InvalidOperationException>(() => GetWhereClause(false));
|
||||
}
|
||||
|
||||
public override string GetScript()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override EditUpdateCellResult SetCell(int columnId, string newValue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class RowUpdateTests
|
||||
{
|
||||
[Fact]
|
||||
public void RowUpdateConstruction()
|
||||
{
|
||||
// Setup: Create the values to store
|
||||
const long rowId = 0;
|
||||
ResultSet rs = QueryExecution.Common.GetBasicExecutedBatch().ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
|
||||
// If: I create a RowUpdate instance
|
||||
RowUpdate rc = new RowUpdate(rowId, rs, etm);
|
||||
|
||||
// Then: The values I provided should be available
|
||||
Assert.Equal(rowId, rc.RowId);
|
||||
Assert.Equal(rs, rc.AssociatedResultSet);
|
||||
Assert.Equal(etm, rc.AssociatedObjectMetadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ImplicitRevertTest()
|
||||
{
|
||||
// Setup: Create a fake table to update
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns);
|
||||
|
||||
// If:
|
||||
// ... I add updates to all the cells in the row
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
Common.AddCells(ru, true);
|
||||
|
||||
// ... Then I update a cell back to it's old value
|
||||
var output = ru.SetCell(1, (string) rs.GetRow(0)[1].RawObject);
|
||||
|
||||
// Then:
|
||||
// ... The output should indicate a revert
|
||||
Assert.NotNull(output);
|
||||
Assert.True(output.IsRevert);
|
||||
Assert.False(output.HasCorrections);
|
||||
Assert.False(output.IsNull);
|
||||
Assert.Equal(rs.GetRow(0)[1].DisplayValue, output.NewValue);
|
||||
|
||||
// ... It should be formatted as an update script
|
||||
Regex r = new Regex(@"UPDATE .+ SET (.*) WHERE");
|
||||
var m = r.Match(ru.GetScript());
|
||||
|
||||
// ... It should have 2 updates
|
||||
string updates = m.Groups[1].Value;
|
||||
string[] updateSplit = updates.Split(',');
|
||||
Assert.Equal(2, updateSplit.Length);
|
||||
Assert.All(updateSplit, s => Assert.Equal(2, s.Split('=').Length));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GetScriptTest(bool isHekaton)
|
||||
{
|
||||
// Setup: Create a fake table to update
|
||||
DbColumn[] columns = Common.GetColumns(true);
|
||||
ResultSet rs = Common.GetResultSet(columns, true);
|
||||
IEditTableMetadata etm = Common.GetMetadata(columns, false, isHekaton);
|
||||
|
||||
// If: I ask for a script to be generated for update
|
||||
RowUpdate ru = new RowUpdate(0, rs, etm);
|
||||
Common.AddCells(ru, true);
|
||||
string script = ru.GetScript();
|
||||
|
||||
// Then:
|
||||
// ... The script should not be null
|
||||
Assert.NotNull(script);
|
||||
|
||||
// ... It should be formatted as an update script
|
||||
string regexString = isHekaton
|
||||
? @"UPDATE (.+) WITH \(SNAPSHOT\) SET (.*) WHERE .+"
|
||||
: @"UPDATE (.+) SET (.*) WHERE .+";
|
||||
Regex r = new Regex(regexString);
|
||||
var m = r.Match(script);
|
||||
Assert.True(m.Success);
|
||||
|
||||
// ... It should have 3 updates
|
||||
string tbl = m.Groups[1].Value;
|
||||
string updates = m.Groups[2].Value;
|
||||
string[] updateSplit = updates.Split(',');
|
||||
Assert.Equal(etm.EscapedMultipartName, tbl);
|
||||
Assert.Equal(3, updateSplit.Length);
|
||||
Assert.All(updateSplit, s => Assert.Equal(2, s.Split('=').Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class ServiceIntegrationTests
|
||||
{
|
||||
#region Session Operation Helper Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" \t\n\r")]
|
||||
[InlineData("Does not exist")]
|
||||
public async Task NullOrMissingSessionId(string sessionId)
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a edit data service
|
||||
var eds = new EditDataService(null, null, null);
|
||||
|
||||
// ... Create a session params that returns the provided session ID
|
||||
var mockParams = new EditCreateRowParams {OwnerUri = sessionId};
|
||||
|
||||
// If: I ask to perform an action that requires a session
|
||||
// Then: I should get an error from it
|
||||
var efv = new EventFlowValidator<EditDisposeResult>()
|
||||
.AddStandardErrorValidation()
|
||||
.Complete();
|
||||
await eds.HandleSessionRequest(mockParams, efv.Object, session => null);
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OperationThrows()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
eds.ActiveSessions[Common.OwnerUri] = GetDefaultSession();
|
||||
|
||||
// ... Create a session param that returns the common owner uri
|
||||
var mockParams = new EditCreateRowParams { OwnerUri = Common.OwnerUri };
|
||||
|
||||
// If: I ask to perform an action that requires a session
|
||||
// Then: I should get an error from it
|
||||
var efv = new EventFlowValidator<EditDisposeResult>()
|
||||
.AddStandardErrorValidation()
|
||||
.Complete();
|
||||
await eds.HandleSessionRequest(mockParams, efv.Object, s => { throw new Exception(); });
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dispose Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" \t\n\r")]
|
||||
[InlineData("Does not exist")]
|
||||
public async Task DisposeNullOrMissingSessionId(string sessionId)
|
||||
{
|
||||
// Setup: Create a edit data service
|
||||
var eds = new EditDataService(null, null, null);
|
||||
|
||||
// If: I ask to perform an action that requires a session
|
||||
// Then: I should get an error from it
|
||||
var efv = new EventFlowValidator<EditDisposeResult>()
|
||||
.AddStandardErrorValidation()
|
||||
.Complete();
|
||||
await eds.HandleDisposeRequest(new EditDisposeParams {OwnerUri = sessionId}, efv.Object);
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeSuccess()
|
||||
{
|
||||
// Setup: Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
eds.ActiveSessions[Common.OwnerUri] = GetDefaultSession();
|
||||
|
||||
// If: I ask to dispose of an existing session
|
||||
var efv = new EventFlowValidator<EditDisposeResult>()
|
||||
.AddResultValidation(Assert.NotNull)
|
||||
.Complete();
|
||||
await eds.HandleDisposeRequest(new EditDisposeParams {OwnerUri = Common.OwnerUri}, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should have completed successfully
|
||||
efv.Validate();
|
||||
|
||||
// ... And the session should have been removed from the active session list
|
||||
Assert.Empty(eds.ActiveSessions);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteSuccess()
|
||||
{
|
||||
// Setup: Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
eds.ActiveSessions[Constants.OwnerUri] = GetDefaultSession();
|
||||
|
||||
// If: I validly ask to delete a row
|
||||
var efv = new EventFlowValidator<EditDeleteRowResult>()
|
||||
.AddResultValidation(Assert.NotNull)
|
||||
.Complete();
|
||||
await eds.HandleDeleteRowRequest(new EditDeleteRowParams {OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should be successful
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be a delete in the session
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.True(s.EditCache.Any(e => e.Value is RowDelete));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateSucceeds()
|
||||
{
|
||||
// Setup: Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
eds.ActiveSessions[Constants.OwnerUri] = GetDefaultSession();
|
||||
|
||||
// If: I ask to create a row from a non existant session
|
||||
var efv = new EventFlowValidator<EditCreateRowResult>()
|
||||
.AddResultValidation(ecrr => { Assert.True(ecrr.NewRowId > 0); })
|
||||
.Complete();
|
||||
await eds.HandleCreateRowRequest(new EditCreateRowParams { OwnerUri = Constants.OwnerUri }, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should have been successful
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be a create in the session
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.True(s.EditCache.Any(e => e.Value is RowCreate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RevertSucceeds()
|
||||
{
|
||||
// Setup: Create an edit data service with a session that has an pending edit
|
||||
var eds = new EditDataService(null, null, null);
|
||||
var session = GetDefaultSession();
|
||||
session.EditCache[0] = new Mock<RowEditBase>().Object;
|
||||
eds.ActiveSessions[Constants.OwnerUri] = session;
|
||||
|
||||
// If: I ask to revert a row that has a pending edit
|
||||
var efv = new EventFlowValidator<EditRevertRowResult>()
|
||||
.AddResultValidation(Assert.NotNull)
|
||||
.Complete();
|
||||
await eds.HandleRevertRowRequest(new EditRevertRowParams { OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should have succeeded
|
||||
efv.Validate();
|
||||
|
||||
// ... The edit cache should be empty again
|
||||
Session s = eds.ActiveSessions[Constants.OwnerUri];
|
||||
Assert.Empty(s.EditCache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSuccess()
|
||||
{
|
||||
// Setup: Create an edit data service with a session
|
||||
var eds = new EditDataService(null, null, null);
|
||||
var session = GetDefaultSession();
|
||||
eds.ActiveSessions[Constants.OwnerUri] = session;
|
||||
var edit = new Mock<RowEditBase>();
|
||||
edit.Setup(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>())).Returns(new EditUpdateCellResult
|
||||
{
|
||||
NewValue = string.Empty,
|
||||
HasCorrections = true,
|
||||
IsRevert = false,
|
||||
IsNull = false
|
||||
});
|
||||
session.EditCache[0] = edit.Object;
|
||||
|
||||
// If: I validly ask to update a cell
|
||||
var efv = new EventFlowValidator<EditUpdateCellResult>()
|
||||
.AddResultValidation(eucr =>
|
||||
{
|
||||
Assert.NotNull(eucr);
|
||||
Assert.NotNull(eucr.NewValue);
|
||||
Assert.False(eucr.IsRevert);
|
||||
Assert.False(eucr.IsNull);
|
||||
})
|
||||
.Complete();
|
||||
await eds.HandleUpdateCellRequest(new EditUpdateCellParams { OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... It should be successful
|
||||
efv.Validate();
|
||||
|
||||
// ... Set cell should have been called once
|
||||
edit.Verify(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
private static Session GetDefaultSession()
|
||||
{
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EditServiceEventFlowValidatorExtensions
|
||||
{
|
||||
public static EventFlowValidator<T> AddStandardErrorValidation<T>(this EventFlowValidator<T> evf)
|
||||
{
|
||||
return evf.AddErrorValidation<string>(p =>
|
||||
{
|
||||
Assert.NotNull(p);
|
||||
Assert.NotEmpty(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData;
|
||||
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
||||
{
|
||||
public class SessionTests
|
||||
{
|
||||
#region Construction Tests
|
||||
|
||||
[Fact]
|
||||
public void SessionConstructionNullQuery()
|
||||
{
|
||||
// If: I create a session object without a null query
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Session(null, Common.GetMetadata(new DbColumn[] {})));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionConstructionNullMetadataProvider()
|
||||
{
|
||||
// If: I create a session object without a null metadata provider
|
||||
// Then: It should throw an exception
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
Assert.Throws<ArgumentNullException>(() => new Session(rs, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionConstructionValid()
|
||||
{
|
||||
// If: I create a session object with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// Then:
|
||||
// ... The edit cache should exist and be empty
|
||||
Assert.NotNull(s.EditCache);
|
||||
Assert.Empty(s.EditCache);
|
||||
|
||||
// ... The next row ID should be equivalent to the number of rows in the result set
|
||||
Assert.Equal(q.Batches[0].ResultSets[0].RowCount, s.NextRowId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validate Tests
|
||||
|
||||
[Fact]
|
||||
public void SessionValidateUnfinishedQuery()
|
||||
{
|
||||
// If: I create a session object with a query that hasn't finished execution
|
||||
// Then: It should throw an exception
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
q.HasExecuted = false;
|
||||
Assert.Throws<InvalidOperationException>(() => Session.ValidateQueryForSession(q));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionValidateIncorrectResultSet()
|
||||
{
|
||||
// Setup: Create a query that yields >1 result sets
|
||||
TestResultSet[] results =
|
||||
{
|
||||
QueryExecution.Common.StandardTestResultSet,
|
||||
QueryExecution.Common.StandardTestResultSet
|
||||
};
|
||||
|
||||
// @TODO: Fix when the connection service is fixed
|
||||
ConnectionInfo ci = QueryExecution.Common.CreateConnectedConnectionInfo(results, false);
|
||||
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
||||
|
||||
var fsf = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(Constants.StandardQuery, ci, new QueryExecutionSettings(), fsf);
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// If: I create a session object with a query that has !=1 result sets
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<InvalidOperationException>(() => Session.ValidateQueryForSession(query));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionValidateValidResultSet()
|
||||
{
|
||||
// If: I validate a query for a session with a valid query
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = Session.ValidateQueryForSession(q);
|
||||
|
||||
// Then: I should get the only result set back
|
||||
Assert.NotNull(rs);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Create Row Tests
|
||||
|
||||
[Fact]
|
||||
public void CreateRowAddFailure()
|
||||
{
|
||||
// NOTE: This scenario should theoretically never occur, but is tested for completeness
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
s.EditCache[rs.RowCount] = mockEdit;
|
||||
|
||||
// If: I create a row in the session
|
||||
// Then:
|
||||
// ... An exception should be thrown
|
||||
Assert.Throws<InvalidOperationException>(() => s.CreateRow());
|
||||
|
||||
// ... The mock edit should still exist
|
||||
Assert.Equal(mockEdit, s.EditCache[rs.RowCount]);
|
||||
|
||||
// ... The next row ID should not have changes
|
||||
Assert.Equal(rs.RowCount, s.NextRowId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRowSuccess()
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I add a row to the session
|
||||
long newId = s.CreateRow();
|
||||
|
||||
// Then:
|
||||
// ... The new ID should be equal to the row count
|
||||
Assert.Equal(rs.RowCount, newId);
|
||||
|
||||
// ... The next row ID should have been incremented
|
||||
Assert.Equal(rs.RowCount + 1, s.NextRowId);
|
||||
|
||||
// ... There should be a new row create object in the cache
|
||||
Assert.Contains(newId, s.EditCache.Keys);
|
||||
Assert.IsType<RowCreate>(s.EditCache[newId]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RowIdOutOfRangeData))]
|
||||
public void RowIdOutOfRange(long rowId, Action<Session, long> testAction)
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I delete a row that is out of range for the result set
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => testAction(s, rowId));
|
||||
}
|
||||
|
||||
public static IEnumerable<object> RowIdOutOfRangeData
|
||||
{
|
||||
get
|
||||
{
|
||||
// Delete Row
|
||||
Action<Session, long> delAction = (s, l) => s.DeleteRow(l);
|
||||
yield return new object[] { -1L, delAction };
|
||||
yield return new object[] { 100L, delAction };
|
||||
|
||||
// Update Cell
|
||||
Action<Session, long> upAction = (s, l) => s.UpdateCell(l, 0, null);
|
||||
yield return new object[] { -1L, upAction };
|
||||
yield return new object[] { 100L, upAction };
|
||||
}
|
||||
}
|
||||
|
||||
#region Delete Row Tests
|
||||
|
||||
[Fact]
|
||||
public void DeleteRowAddFailure()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
s.EditCache[0] = mockEdit;
|
||||
|
||||
// If: I delete a row in the session
|
||||
// Then:
|
||||
// ... An exception should be thrown
|
||||
Assert.Throws<InvalidOperationException>(() => s.DeleteRow(0));
|
||||
|
||||
// ... The mock edit should still exist
|
||||
Assert.Equal(mockEdit, s.EditCache[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteRowSuccess()
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I add a row to the session
|
||||
s.DeleteRow(0);
|
||||
|
||||
// Then: There should be a new row delete object in the cache
|
||||
Assert.Contains(0, s.EditCache.Keys);
|
||||
Assert.IsType<RowDelete>(s.EditCache[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Revert Row Tests
|
||||
|
||||
[Fact]
|
||||
public void RevertRowOutOfRange()
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I revert a row that doesn't have any pending changes
|
||||
// Then: I should get an exception
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => s.RevertRow(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevertRowSuccess()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>().Object;
|
||||
s.EditCache[0] = mockEdit;
|
||||
|
||||
// If: I revert the row that has a pending update
|
||||
s.RevertRow(0);
|
||||
|
||||
// Then:
|
||||
// ... The edit cache should not contain a pending edit for the row
|
||||
Assert.DoesNotContain(0, s.EditCache.Keys);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Cell Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdateCellExisting()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// ... Add a mock edit to the edit cache to cause the .TryAdd to fail
|
||||
var mockEdit = new Mock<RowEditBase>();
|
||||
mockEdit.Setup(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>()));
|
||||
s.EditCache[0] = mockEdit.Object;
|
||||
|
||||
// If: I update a cell on a row that already has a pending edit
|
||||
s.UpdateCell(0, 0, null);
|
||||
|
||||
// Then:
|
||||
// ... The mock update should still be in the cache
|
||||
// ... And it should have had set cell called on it
|
||||
Assert.Contains(mockEdit.Object, s.EditCache.Values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateCellNew()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I update a cell on a row that does not have a pending edit
|
||||
s.UpdateCell(0, 0, "");
|
||||
|
||||
// Then:
|
||||
// ... A new update row edit should have been added to the cache
|
||||
Assert.Contains(0, s.EditCache.Keys);
|
||||
Assert.IsType<RowUpdate>(s.EditCache[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Script Edits Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" \t\r\n")]
|
||||
public void ScriptNullOrEmptyOutput(string outputPath)
|
||||
{
|
||||
// Setup: Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// If: I try to script the edit cache with a null or whitespace output path
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => s.ScriptEdits(outputPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptProvidedOutputPath()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a session with a proper query and metadata
|
||||
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
||||
ResultSet rs = q.Batches[0].ResultSets[0];
|
||||
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
||||
Session s = new Session(rs, etm);
|
||||
|
||||
// ... Add two mock edits that will generate a script
|
||||
Mock<RowEditBase> edit = new Mock<RowEditBase>();
|
||||
edit.Setup(e => e.GetScript()).Returns("test");
|
||||
s.EditCache[0] = edit.Object;
|
||||
s.EditCache[1] = edit.Object;
|
||||
|
||||
using (SelfCleaningTempFile file = new SelfCleaningTempFile())
|
||||
{
|
||||
// If: I script the edit cache to a local output path
|
||||
string outputPath = s.ScriptEdits(file.FilePath);
|
||||
|
||||
// Then:
|
||||
// ... The output path used should be the same as the one we provided
|
||||
Assert.Equal(file.FilePath, outputPath);
|
||||
|
||||
// ... The written file should have two lines, one for each edit
|
||||
Assert.Equal(2, File.ReadAllLines(outputPath).Length);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Extensibility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Extensibility
|
||||
{
|
||||
public class ExtensionTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void CreateAssemblyStoreShouldFindTypesInAssembly()
|
||||
{
|
||||
// Given a store for MyExportType
|
||||
ExtensionStore store = ExtensionStore.CreateAssemblyStore<MyExportType>(GetType().GetTypeInfo().Assembly);
|
||||
// Then should get any export for this type and subtypes
|
||||
Assert.Equal(2, store.GetExports<MyExportType>().Count());
|
||||
|
||||
// But for a different type, expect throw as the store only contains MyExportType
|
||||
Assert.Throws<InvalidCastException>(() => store.GetExports<MyOtherType>().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDefaultLoaderShouldFindTypesOnlyInMainAssembly()
|
||||
{
|
||||
// Given a store created using CreateDefaultLoader
|
||||
// Then not should find exports from a different assembly
|
||||
ExtensionStore store = ExtensionStore.CreateDefaultLoader<MyExportType>();
|
||||
Assert.Equal(0, store.GetExports<MyExportType>().Count());
|
||||
|
||||
// And should not find exports that are defined in the ServiceLayer assembly
|
||||
store = ExtensionStore.CreateDefaultLoader<ASTNodeFormatterFactory>();
|
||||
Assert.Empty(store.GetExports<ASTNodeFormatterFactory>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDefaultServiceProviderShouldFindTypesInAllKnownAssemblies()
|
||||
{
|
||||
// Given a default ExtensionServiceProvider
|
||||
// Then we should not find exports from a test assembly
|
||||
ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider();
|
||||
Assert.Empty(serviceProvider.GetServices<MyExportType>());
|
||||
|
||||
// But should find exports that are defined in the main assembly
|
||||
Assert.NotEmpty(serviceProvider.GetServices<ASTNodeFormatterFactory>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStoreForCurrentDirectoryShouldFindExportsInDirectory()
|
||||
{
|
||||
// Given stores created for types in different assemblies
|
||||
ExtensionStore myStore = ExtensionStore.CreateStoreForCurrentDirectory<MyExportType>();
|
||||
ExtensionStore querierStore = ExtensionStore.CreateStoreForCurrentDirectory<ASTNodeFormatterFactory>();
|
||||
|
||||
// When I query exports
|
||||
// Then exports for all assemblies should be found
|
||||
Assert.Equal(2, myStore.GetExports<MyExportType>().Count());
|
||||
Assert.NotEmpty(querierStore.GetExports<ASTNodeFormatterFactory>());
|
||||
}
|
||||
}
|
||||
|
||||
// Note: in order for the MEF lookup to succeed, one class must have
|
||||
[Export(typeof(MyExportType))]
|
||||
public class MyExportType
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class MyExportSubType : MyExportType
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class MyOtherType
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Extensibility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Extensibility
|
||||
{
|
||||
public class ServiceProviderTests
|
||||
{
|
||||
private RegisteredServiceProvider provider;
|
||||
public ServiceProviderTests()
|
||||
{
|
||||
provider = new RegisteredServiceProvider();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceShouldReturnNullIfNoServicesRegistered()
|
||||
{
|
||||
// Given no service registered
|
||||
// When I call GetService
|
||||
var service = provider.GetService<MyProviderService>();
|
||||
// Then I expect null to be returned
|
||||
Assert.Null(service);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetSingleServiceThrowsMultipleServicesRegistered()
|
||||
{
|
||||
// Given 2 services registered
|
||||
provider.Register<MyProviderService>(() => new[] { new MyProviderService(), new MyProviderService() });
|
||||
// When I call GetService
|
||||
// Then I expect to throw
|
||||
Assert.Throws<InvalidOperationException>(() => provider.GetService<MyProviderService>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServicesShouldReturnEmptyIfNoServicesRegistered()
|
||||
{
|
||||
// Given no service regisstered
|
||||
// When I call GetService
|
||||
var services = provider.GetServices<MyProviderService>();
|
||||
// Then I expect empty enumerable to be returned
|
||||
Assert.NotNull(services);
|
||||
Assert.Equal(0, services.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceShouldReturnRegisteredService()
|
||||
{
|
||||
MyProviderService service = new MyProviderService();
|
||||
provider.RegisterSingleService(service);
|
||||
|
||||
var returnedService = provider.GetService<MyProviderService>();
|
||||
Assert.Equal(service, returnedService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServicesShouldReturnRegisteredServiceWhenMultipleServicesRegistered()
|
||||
{
|
||||
MyProviderService service = new MyProviderService();
|
||||
provider.RegisterSingleService(service);
|
||||
|
||||
var returnedServices = provider.GetServices<MyProviderService>();
|
||||
Assert.Equal(service, returnedServices.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterServiceProviderShouldThrowIfServiceIsIncompatible()
|
||||
{
|
||||
MyProviderService service = new MyProviderService();
|
||||
Assert.Throws<InvalidOperationException>(() => provider.RegisterSingleService(typeof(OtherService), service));
|
||||
}
|
||||
[Fact]
|
||||
public void RegisterServiceProviderShouldThrowIfServiceAlreadyRegistered()
|
||||
{
|
||||
MyProviderService service = new MyProviderService();
|
||||
provider.RegisterSingleService(service);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => provider.RegisterSingleService(service));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterShouldThrowIfServiceAlreadyRegistered()
|
||||
{
|
||||
MyProviderService service = new MyProviderService();
|
||||
provider.RegisterSingleService(service);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => provider.Register(() => service.SingleItemAsEnumerable()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterShouldThrowIfServicesAlreadyRegistered()
|
||||
{
|
||||
provider.Register<MyProviderService>(() => new [] { new MyProviderService(), new MyProviderService() });
|
||||
Assert.Throws<InvalidOperationException>(() => provider.Register(() => new MyProviderService().SingleItemAsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class MyProviderService
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class OtherService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
public class BinaryQueryExpressionFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void BQE_IndentOperands()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
//options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("BQE_IndentOperands", GetInputFile("BQE_IndentOperands.sql"),
|
||||
GetBaselineFile("BQE_IndentOperands.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BQE_KeywordCasing_UpperCase()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Uppercase;
|
||||
LoadAndFormatAndCompare("BQE_KeywordCasing_UpperCase", GetInputFile("BQE_KeywordCasing.sql"),
|
||||
GetBaselineFile("BQE_KeywordCasing_UpperCase.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BQE_KeywordCasing_LowerCase()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Lowercase;
|
||||
LoadAndFormatAndCompare("BQE_KeywordCasing_LowerCase", GetInputFile("BQE_KeywordCasing.sql"),
|
||||
GetBaselineFile("BQE_KeywordCasing_LowerCase.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BQE_KeywordCasing_NoFormat()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.None;
|
||||
LoadAndFormatAndCompare("BQE_KeywordCasing_NoFormat", GetInputFile("BQE_KeywordCasing.sql"),
|
||||
GetBaselineFile("BQE_KeywordCasing_NoFormat.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BQE_OperatorsOnNewLine()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
LoadAndFormatAndCompare("BQE_OperatorsOnNewLine", GetInputFile("BQE_OperatorsOnNewLine.sql"),
|
||||
GetBaselineFile("BQE_OperatorsOnNewLine.sql"), options, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class CommonTableExpressionFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void CTE()
|
||||
{
|
||||
LoadAndFormatAndCompare("CTE", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_OneColumn()
|
||||
{
|
||||
LoadAndFormatAndCompare("CTE_OneColumn", GetInputFile("CTE_OneColumn.sql"),
|
||||
GetBaselineFile("CTE_OneColumn.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_MultipleExpressions()
|
||||
{
|
||||
LoadAndFormatAndCompare("CTE_MultipleExpressions", GetInputFile("CTE_MultipleExpressions.sql"),
|
||||
GetBaselineFile("CTE_MultipleExpressions.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_CommasBeforeDefinition()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
|
||||
// TODO: fix verify to account for commma placement - this can
|
||||
LoadAndFormatAndCompare("CTE_CommasBeforeDefinition", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_CommasBeforeDefinition.sql"), options, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_EachReferenceOnNewLine()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
|
||||
LoadAndFormatAndCompare("CTE_EachReferenceOnNewLine", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_EachReferenceOnNewLine.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_EachReferenceOnNewLine_CommasBeforeDefinition()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
|
||||
// TODO: fix verify to account for commma placement - this can
|
||||
LoadAndFormatAndCompare("CTE_EachReferenceOnNewLine_CommasBeforeDefinition", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_EachReferenceOnNewLine_CommasBeforeDefinition.sql"), options, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_UseTabs()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = true;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("CTE_UseTabs", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_UseTabs.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_20Spaces()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.SpacesPerIndent = 20;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("CTE_20Spaces", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_20Spaces.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_UpperCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Uppercase;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("CTE_UpperCaseKeywords", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_UpperCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CTE_LowerCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Lowercase;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("CTE_LowerCaseKeywords", GetInputFile("CTE.sql"),
|
||||
GetBaselineFile("CTE_LowerCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class CreateProcedureFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void CreateProcedure_BackwardsCompatible()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_BackwardsCompatible", GetInputFile("CreateProcedure_BackwardsCompatible.sql"),
|
||||
GetBaselineFile("CreateProcedure_BackwardsCompatible.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_BeginEnd()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_BeginEnd", GetInputFile("CreateProcedure_BeginEnd.sql"),
|
||||
GetBaselineFile("CreateProcedure_BeginEnd.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_Minimal()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_Minimal", GetInputFile("CreateProcedure_Minimal.sql"),
|
||||
GetBaselineFile("CreateProcedure_Minimal.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_MultipleBatches()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_MultipleBatches", GetInputFile("CreateProcedure_MultipleBatches.sql"),
|
||||
GetBaselineFile("CreateProcedure_MultipleBatches.sql"), new FormatOptions(), true);
|
||||
}
|
||||
[Fact]
|
||||
public void CreateProcedure_MultipleParams()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_MultipleParams", GetInputFile("CreateProcedure_MultipleParams.sql"),
|
||||
GetBaselineFile("CreateProcedure_MultipleParams.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_OneParam()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_OneParam", GetInputFile("CreateProcedure_OneParam.sql"),
|
||||
GetBaselineFile("CreateProcedure_OneParam.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_ParamsRecompileReturn()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_ParamsRecompileReturn", GetInputFile("CreateProcedure_ParamsRecompileReturn.sql"),
|
||||
GetBaselineFile("CreateProcedure_ParamsRecompileReturn.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_Select()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_Select", GetInputFile("CreateProcedure_Select.sql"),
|
||||
GetBaselineFile("CreateProcedure_Select.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_CommaBeforeNextColumn()
|
||||
{
|
||||
// Verifies that commas are placed before the next column instead of after the current one,
|
||||
// when PlaceCommasBeforeNextStatement = true
|
||||
LoadAndFormatAndCompare("CreateProcedure_CommaBeforeNextColumn",
|
||||
GetInputFile("CreateProcedure_CommaHandling1.sql"),
|
||||
GetBaselineFile("CreateProcedure_CommaBeforeNext.sql"),
|
||||
new FormatOptions()
|
||||
{
|
||||
DatatypeCasing = CasingOptions.Lowercase,
|
||||
KeywordCasing = CasingOptions.Uppercase,
|
||||
PlaceCommasBeforeNextStatement = true,
|
||||
PlaceEachReferenceOnNewLineInQueryStatements = true
|
||||
}, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_CommaBeforeNextColumnRepeated()
|
||||
{
|
||||
// Verifies that formatting isn't changed for text that's already been formatted
|
||||
// as having the comma placed before the next column instead of after the prevous one
|
||||
LoadAndFormatAndCompare("CreateProcedure_CommaBeforeNextColumnRepeated",
|
||||
GetInputFile("CreateProcedure_CommaHandling2.sql"),
|
||||
GetBaselineFile("CreateProcedure_CommaBeforeNext.sql"),
|
||||
new FormatOptions()
|
||||
{
|
||||
DatatypeCasing = CasingOptions.Lowercase,
|
||||
KeywordCasing = CasingOptions.Uppercase,
|
||||
PlaceCommasBeforeNextStatement = true,
|
||||
PlaceEachReferenceOnNewLineInQueryStatements = true
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_CommaBeforeNextColumnNoNewline()
|
||||
{
|
||||
// Verifies that commas are placed before the next column instead of after the current one,
|
||||
// when PlaceCommasBeforeNextStatement = true
|
||||
// And that whitespace is used instead of newline if PlaceEachReferenceOnNewLineInQueryStatements = false
|
||||
LoadAndFormatAndCompare("CreateProcedure_CommaBeforeNextColumnNoNewline",
|
||||
GetInputFile("CreateProcedure_CommaHandling1.sql"),
|
||||
GetBaselineFile("CreateProcedure_CommaSpaced.sql"),
|
||||
new FormatOptions()
|
||||
{
|
||||
DatatypeCasing = CasingOptions.Lowercase,
|
||||
KeywordCasing = CasingOptions.Uppercase,
|
||||
PlaceCommasBeforeNextStatement = true,
|
||||
PlaceEachReferenceOnNewLineInQueryStatements = false
|
||||
}, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_TwoPartName()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_TwoPartName", GetInputFile("CreateProcedure_TwoPartName.sql"),
|
||||
GetBaselineFile("CreateProcedure_TwoPartName.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_WithCTE()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_WithCTE", GetInputFile("CreateProcedure_WithCTE.sql"),
|
||||
GetBaselineFile("CreateProcedure_WithCTE.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_WithEncryptionModule()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_WithEncryptionModule", GetInputFile("CreateProcedure_WithEncryptionModule.sql"),
|
||||
GetBaselineFile("CreateProcedure_WithEncryptionModule.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_WithExecuteAsModule()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_WithExecuteAsModule", GetInputFile("CreateProcedure_WithExecuteAsModule.sql"),
|
||||
GetBaselineFile("CreateProcedure_WithExecuteAsModule.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateProcedure_WithThreeModules()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateProcedure_WithThreeModules", GetInputFile("CreateProcedure_WithThreeModules.sql"),
|
||||
GetBaselineFile("CreateProcedure_WithThreeModules.sql"), new FormatOptions(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class CreateTableFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void CreateTable()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateTable", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The test contains a timestamp column, which is the shortest (1 token) possible length for a column item.
|
||||
*/
|
||||
[Fact]
|
||||
public void CreateTable_Timestamp()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.AlignColumnDefinitionsInColumns = true;
|
||||
LoadAndFormatAndCompare("CreateTable_Timestamp", GetInputFile("CreateTable_Timestamp.sql"), GetBaselineFile("CreateTable_Timestamp.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_CommasBeforeDefinition()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
|
||||
// TODO: fix verify to account for commma placement - this can
|
||||
LoadAndFormatAndCompare("CreateTable_CommasBeforeDefinition", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_CommasBeforeDefinition.sql"), options, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_UseTabs()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = true;
|
||||
LoadAndFormatAndCompare("CreateTable_UseTabs", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UseTabs.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_20Spaces()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.SpacesPerIndent = 20;
|
||||
LoadAndFormatAndCompare("CreateTable_20Spaces", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_20Spaces.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_UpperCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Uppercase;
|
||||
LoadAndFormatAndCompare("CreateTable_UpperCaseKeywords", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UpperCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_LowerCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Lowercase;
|
||||
LoadAndFormatAndCompare("CreateTable_LowerCaseKeywords", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_LowerCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_UpperCaseDataTypes()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.DatatypeCasing = CasingOptions.Uppercase;
|
||||
LoadAndFormatAndCompare("CreateTable_UpperCaseDataTypes", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UpperCaseDataTypes.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_LowerCaseDataTypes()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.DatatypeCasing = CasingOptions.Lowercase;
|
||||
LoadAndFormatAndCompare("CreateTable_LowerCaseDataTypes", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_LowerCaseDataTypes.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_AlignInColumns()
|
||||
{
|
||||
FormatOptions options = new FormatOptions() { AlignColumnDefinitionsInColumns = true };
|
||||
LoadAndFormatAndCompare("CreateTable_AlignInColumns", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_AlignInColumns.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_AlignInColumnsUseTabs()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = true;
|
||||
options.AlignColumnDefinitionsInColumns = true;
|
||||
LoadAndFormatAndCompare("CreateTable_AlignInColumnsUseTabs", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_AlignInColumnsUseTabs.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_On()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateTableOn", GetInputFile("CreateTableFull.sql"), GetBaselineFile("CreateTableOn.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_Formatted()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateTable_Formatted", GetInputFile("CreateTable_Formatted.sql"), GetBaselineFile("CreateTable_Formatted.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTable_CommentsBeforeComma()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = false;
|
||||
options.AlignColumnDefinitionsInColumns = true;
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
LoadAndFormatAndCompare("CreateTable_CommentsBeforeComma", GetInputFile("CreateTable_CommentBeforeComma.sql"), GetBaselineFile("CreateTable_CommentBeforeComma.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTableAddress_AlignInColumns()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.AlignColumnDefinitionsInColumns = true;
|
||||
LoadAndFormatAndCompare("CreateTableAddress_AlignInColumns", GetInputFile("Address.sql"), GetBaselineFile("CreateTableAddress_AlignInColumns.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateTableAddress_AlignInColumnsUseTabs()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = true;
|
||||
options.AlignColumnDefinitionsInColumns = true;
|
||||
LoadAndFormatAndCompare("CreateTableAddress_AlignInColumnsUseTabs", GetInputFile("Address.sql"), GetBaselineFile("CreateTableAddress_AlignInColumnsUseTabs.sql"), options, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class CreateViewFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void CreateView_Full()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_Full", GetInputFile("CreateView_Full.sql"),
|
||||
GetBaselineFile("CreateView_Full.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_FullWithComments()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_FullWithComments", GetInputFile("CreateView_FullWithComments.sql"), GetBaselineFile("CreateView_FullWithComments.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_MultipleColumns()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_MultipleColumns", GetInputFile("CreateView_MultipleColumns.sql"),
|
||||
GetBaselineFile("CreateView_MultipleColumns.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_MultipleOptions()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_MultipleOptions", GetInputFile("CreateView_MultipleOptions.sql"),
|
||||
GetBaselineFile("CreateView_MultipleOptions.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_OneColumn()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_OneColumn", GetInputFile("CreateView_OneColumn.sql"),
|
||||
GetBaselineFile("CreateView_OneColumn.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_OneColumnOneOption()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_OneColumnOneOption", GetInputFile("CreateView_OneColumnOneOption.sql"),
|
||||
GetBaselineFile("CreateView_OneColumnOneOption.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_OneOption()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_OneOption", GetInputFile("CreateView_OneOption.sql"),
|
||||
GetBaselineFile("CreateView_OneOption.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateView_Simple()
|
||||
{
|
||||
LoadAndFormatAndCompare("CreateView_Simple", GetInputFile("CreateView_Simple.sql"),
|
||||
GetBaselineFile("CreateView_Simple.sql"), new FormatOptions(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
public class FormatterSettingsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ValidateFormatterServiceDefaults()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
Assert.Null(sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns);
|
||||
Assert.Equal(CasingOptions.None, sqlToolsSettings.SqlTools.Format.DatatypeCasing);
|
||||
Assert.Equal(CasingOptions.None, sqlToolsSettings.SqlTools.Format.KeywordCasing);
|
||||
Assert.Null(sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement);
|
||||
Assert.Null(sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine);
|
||||
Assert.Null(sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateFormatSettingsParsedFromJson()
|
||||
{
|
||||
const string settingsJson = @"
|
||||
{
|
||||
""params"": {
|
||||
""mssql"": {
|
||||
""format"": {
|
||||
useBracketForIdentifiers: true,
|
||||
placeCommasBeforeNextStatement: true,
|
||||
placeSelectStatementReferencesOnNewLine: true,
|
||||
keywordCasing: ""uppercase"",
|
||||
datatypeCasing: ""lowercase"",
|
||||
alignColumnDefinitionsInColumns: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}";
|
||||
|
||||
JObject message = JObject.Parse(settingsJson);
|
||||
JToken messageParams = null;
|
||||
message.TryGetValue("params", out messageParams);
|
||||
SqlToolsSettings sqlToolsSettings = messageParams.ToObject<SqlToolsSettings>();
|
||||
|
||||
Assert.True(sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns);
|
||||
Assert.Equal(CasingOptions.Lowercase, sqlToolsSettings.SqlTools.Format.DatatypeCasing);
|
||||
Assert.Equal(CasingOptions.Uppercase, sqlToolsSettings.SqlTools.Format.KeywordCasing);
|
||||
Assert.True(sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement);
|
||||
Assert.True(sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine);
|
||||
Assert.True(sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatOptionsMatchDefaultSettings()
|
||||
{
|
||||
var options = new FormatOptions();
|
||||
AssertOptionsHaveDefaultValues(options);
|
||||
}
|
||||
|
||||
private static void AssertOptionsHaveDefaultValues(FormatOptions options)
|
||||
{
|
||||
Assert.False(options.AlignColumnDefinitionsInColumns);
|
||||
Assert.Equal(CasingOptions.None, options.DatatypeCasing);
|
||||
Assert.Equal(CasingOptions.None, options.KeywordCasing);
|
||||
Assert.False(options.PlaceCommasBeforeNextStatement);
|
||||
Assert.False(options.PlaceEachReferenceOnNewLineInQueryStatements);
|
||||
Assert.False(options.EncloseIdentifiersInSquareBrackets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCopyDefaultFormatSettingsToOptions()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
FormatOptions options = new FormatOptions();
|
||||
TSqlFormatterService.UpdateFormatOptionsFromSettings(options, sqlToolsSettings.SqlTools.Format);
|
||||
AssertOptionsHaveDefaultValues(options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCopyAlteredFormatSettingsToOptions()
|
||||
{
|
||||
SqlToolsSettings sqlToolsSettings = CreateNonDefaultFormatSettings();
|
||||
|
||||
FormatOptions options = new FormatOptions();
|
||||
TSqlFormatterService.UpdateFormatOptionsFromSettings(options, sqlToolsSettings.SqlTools.Format);
|
||||
|
||||
AssertOptionsHaveExpectedNonDefaultValues(options);
|
||||
}
|
||||
|
||||
private static SqlToolsSettings CreateNonDefaultFormatSettings()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns = true;
|
||||
sqlToolsSettings.SqlTools.Format.DatatypeCasing = CasingOptions.Lowercase;
|
||||
sqlToolsSettings.SqlTools.Format.KeywordCasing = CasingOptions.Uppercase;
|
||||
sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement = true;
|
||||
sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine = true;
|
||||
sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers = true;
|
||||
return sqlToolsSettings;
|
||||
}
|
||||
|
||||
private static void AssertOptionsHaveExpectedNonDefaultValues(FormatOptions options)
|
||||
{
|
||||
Assert.True(options.AlignColumnDefinitionsInColumns);
|
||||
Assert.Equal(CasingOptions.Lowercase, options.DatatypeCasing);
|
||||
Assert.Equal(CasingOptions.Uppercase, options.KeywordCasing);
|
||||
Assert.True(options.PlaceCommasBeforeNextStatement);
|
||||
Assert.True(options.PlaceEachReferenceOnNewLineInQueryStatements);
|
||||
Assert.True(options.EncloseIdentifiersInSquareBrackets);
|
||||
|
||||
Assert.False(options.UppercaseDataTypes);
|
||||
Assert.True(options.UppercaseKeywords);
|
||||
Assert.True(options.LowercaseDataTypes);
|
||||
Assert.False(options.LowercaseKeywords);
|
||||
Assert.False(options.DoNotFormatDataTypes);
|
||||
Assert.False(options.DoNotFormatKeywords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeRequestOptionsAndSettings()
|
||||
{
|
||||
var sqlToolsSettings = CreateNonDefaultFormatSettings();
|
||||
|
||||
FormatOptions options = TSqlFormatterService.MergeFormatOptions(
|
||||
new FormattingOptions { InsertSpaces = true, TabSize = 2 },
|
||||
sqlToolsSettings.SqlTools.Format);
|
||||
|
||||
AssertOptionsHaveExpectedNonDefaultValues(options);
|
||||
Assert.False(options.UseTabs);
|
||||
Assert.True(options.UseSpaces);
|
||||
Assert.Equal(2, options.SpacesPerIndent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Extensibility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
public class FormatterUnitTestsBase
|
||||
{
|
||||
public FormatterUnitTestsBase()
|
||||
{
|
||||
HostMock = new Mock<IProtocolEndpoint>();
|
||||
WorkspaceServiceMock = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
ServiceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider();
|
||||
ServiceProvider.RegisterSingleService(WorkspaceServiceMock.Object);
|
||||
HostLoader.InitializeHostedServices(ServiceProvider, HostMock.Object);
|
||||
FormatterService = ServiceProvider.GetService<TSqlFormatterService>();
|
||||
}
|
||||
|
||||
protected ExtensionServiceProvider ServiceProvider { get; private set; }
|
||||
protected Mock<IProtocolEndpoint> HostMock { get; private set; }
|
||||
protected Mock<WorkspaceService<SqlToolsSettings>> WorkspaceServiceMock { get; private set; }
|
||||
|
||||
protected TSqlFormatterService FormatterService { get; private set; }
|
||||
|
||||
protected void LoadAndFormatAndCompare(string testName, FileInfo inputFile, FileInfo baselineFile, FormatOptions options, bool verifyFormat)
|
||||
{
|
||||
string inputSql = TestUtilities.ReadTextAndNormalizeLineEndings(inputFile.FullName);
|
||||
string formattedSql = string.Empty;
|
||||
formattedSql = FormatterService.Format(inputSql, options, verifyFormat);
|
||||
|
||||
formattedSql = TestUtilities.NormalizeLineEndings(formattedSql);
|
||||
|
||||
string assemblyPath = GetType().GetTypeInfo().Assembly.Location;
|
||||
string directory = Path.Combine(Path.GetDirectoryName(assemblyPath), "FormatterTests");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
FileInfo outputFile = new FileInfo(Path.Combine(directory, testName + ".out"));
|
||||
File.WriteAllText(outputFile.FullName, formattedSql);
|
||||
TestUtilities.CompareTestFiles(baselineFile, outputFile);
|
||||
}
|
||||
|
||||
public FileInfo GetInputFile(string fileName)
|
||||
{
|
||||
return new FileInfo(Path.Combine(InputFileDirectory.FullName, fileName));
|
||||
}
|
||||
|
||||
public FileInfo GetBaselineFile(string fileName)
|
||||
{
|
||||
return new FileInfo(Path.Combine(BaselineDirectory.FullName, fileName));
|
||||
}
|
||||
|
||||
public DirectoryInfo BaselineDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
string d = Path.Combine(TestLocationDirectory, "BaselineFiles");
|
||||
return new DirectoryInfo(d);
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryInfo InputFileDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
string d = Path.Combine(TestLocationDirectory, "TestFiles");
|
||||
return new DirectoryInfo(d);
|
||||
}
|
||||
}
|
||||
|
||||
private static string TestLocationDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(RunEnvironmentInfo.GetTestDataLocation(), "TSqlFormatter");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
public class GeneralFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void KeywordCaseConversionUppercase()
|
||||
{
|
||||
LoadAndFormatAndCompare("KeywordCaseConversion",
|
||||
GetInputFile("KeywordCaseConversion.sql"),
|
||||
GetBaselineFile("KeywordCaseConversion_Uppercase.sql"),
|
||||
new FormatOptions() { KeywordCasing = CasingOptions.Uppercase },
|
||||
verifyFormat: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeywordCaseConversionLowercase()
|
||||
{
|
||||
LoadAndFormatAndCompare("KeywordCaseConversion",
|
||||
GetInputFile("KeywordCaseConversion.sql"),
|
||||
GetBaselineFile("KeywordCaseConversion_Lowercase.sql"),
|
||||
new FormatOptions() { KeywordCasing = CasingOptions.Lowercase },
|
||||
verifyFormat: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectWithOrderByShouldCorrectlyIndent()
|
||||
{
|
||||
LoadAndFormatAndCompare("SelectWithOrderByShouldCorrectlyIndent",
|
||||
GetInputFile("SelectWithOrderBy.sql"),
|
||||
GetBaselineFile("SelectWithOrderBy_CorrectIndents.sql"),
|
||||
new FormatOptions(),
|
||||
verifyFormat: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectStatementShouldCorrectlyIndent()
|
||||
{
|
||||
LoadAndFormatAndCompare("SelectStatementShouldCorrectlyIndent",
|
||||
GetInputFile("CreateProcedure.sql"),
|
||||
GetBaselineFile("CreateProcedure_CorrectIndents.sql"),
|
||||
new FormatOptions(),
|
||||
verifyFormat: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class InsertFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void Insert_DefaultValues()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_DefaultValues", GetInputFile("Insert_DefaultValues.sql"),
|
||||
GetBaselineFile("Insert_DefaultValues.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_OpenQuery()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_OpenQuery", GetInputFile("Insert_OpenQuery.sql"),
|
||||
GetBaselineFile("Insert_OpenQuery.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_OutputInto()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_OutputInto", GetInputFile("Insert_OutputInto.sql"),
|
||||
GetBaselineFile("Insert_OutputInto.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_OutputStatement()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_OutputStatement", GetInputFile("Insert_OutputStatement.sql"),
|
||||
GetBaselineFile("Insert_OutputStatement.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_Select()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("Insert_Select", GetInputFile("Insert_Select.sql"),
|
||||
GetBaselineFile("Insert_Select.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_SelectSource()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("Insert_SelectSource", GetInputFile("Insert_SelectSource.sql"),
|
||||
GetBaselineFile("Insert_SelectSource.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_TopSpecification()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_TopSpecification", GetInputFile("Insert_TopSpecification.sql"),
|
||||
GetBaselineFile("Insert_TopSpecification.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_TopWithComments()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_TopWithComments", GetInputFile("Insert_TopWithComments.sql"),
|
||||
GetBaselineFile("Insert_TopWithComments.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_Full()
|
||||
{
|
||||
LoadAndFormatAndCompare("Insert_Full", GetInputFile("Insert_Full.sql"),
|
||||
GetBaselineFile("Insert_Full.sql"), new FormatOptions(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
|
||||
public class SqlSelectStatementFormatterTests : FormatterUnitTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public void SimpleQuery()
|
||||
{
|
||||
LoadAndFormatAndCompare("SimpleQuery", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_CommasBeforeDefinition()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
|
||||
// TODO: fix verify to account for commma placement - this can
|
||||
LoadAndFormatAndCompare("SimpleQuery_CommasBeforeDefinition", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_CommasBeforeDefinition.sql"), options, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_EachReferenceOnNewLine()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
|
||||
LoadAndFormatAndCompare("SimpleQuery_EachReferenceOnNewLine", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_EachReferenceOnNewLine.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.PlaceCommasBeforeNextStatement = true;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
|
||||
// TODO: fix verify to account for commma placement - this can
|
||||
LoadAndFormatAndCompare("SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition",
|
||||
GetInputFile("SimpleQuery.sql"), GetBaselineFile("SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition.sql"), options, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_UseTabs()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.UseTabs = true;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("SimpleQuery_UseTabs", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_UseTabs.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_20Spaces()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.SpacesPerIndent = 20;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("SimpleQuery_20Spaces", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_20Spaces.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_UpperCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Uppercase;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("SimpleQuery_UpperCaseKeywords", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_UpperCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_LowerCaseKeywords()
|
||||
{
|
||||
FormatOptions options = new FormatOptions();
|
||||
options.KeywordCasing = CasingOptions.Lowercase;
|
||||
options.PlaceEachReferenceOnNewLineInQueryStatements = true;
|
||||
LoadAndFormatAndCompare("SimpleQuery_LowerCaseKeywords", GetInputFile("SimpleQuery.sql"),
|
||||
GetBaselineFile("SimpleQuery_LowerCaseKeywords.sql"), options, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_ForBrowseClause()
|
||||
{
|
||||
LoadAndFormatAndCompare("SimpleQuery_ForBrowseClause", GetInputFile("SimpleQuery_ForBrowseClause.sql"),
|
||||
GetBaselineFile("SimpleQuery_ForBrowseClause.sql"), new FormatOptions(), true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleQuery_ForXmlClause()
|
||||
{
|
||||
LoadAndFormatAndCompare("SimpleQuery_ForXmlClause", GetInputFile("SimpleQuery_ForXmlClause.sql"),
|
||||
GetBaselineFile("SimpleQuery_ForXmlClause.sql"), new FormatOptions(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter
|
||||
{
|
||||
public class TSqlFormatterServiceTests : FormatterUnitTestsBase
|
||||
{
|
||||
private Mock<ServiceLayer.Workspace.Workspace> workspaceMock = new Mock<ServiceLayer.Workspace.Workspace>();
|
||||
private TextDocumentIdentifier textDocument;
|
||||
DocumentFormattingParams docFormatParams;
|
||||
DocumentRangeFormattingParams rangeFormatParams;
|
||||
|
||||
public TSqlFormatterServiceTests()
|
||||
{
|
||||
textDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
};
|
||||
docFormatParams = new DocumentFormattingParams()
|
||||
{
|
||||
TextDocument = textDocument,
|
||||
Options = new FormattingOptions() { InsertSpaces = true, TabSize = 4 }
|
||||
};
|
||||
rangeFormatParams = new DocumentRangeFormattingParams()
|
||||
{
|
||||
TextDocument = textDocument,
|
||||
Options = new FormattingOptions() { InsertSpaces = true, TabSize = 4 },
|
||||
Range = new ServiceLayer.Workspace.Contracts.Range()
|
||||
{
|
||||
// From first "(" to last ")"
|
||||
Start = new Position { Line = 0, Character = 16 },
|
||||
End = new Position { Line = 0, Character = 56 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private string defaultSqlContents = TestUtilities.NormalizeLineEndings(@"create TABLE T1 ( C1 int NOT NULL, C2 nvarchar(50) NULL)");
|
||||
// TODO fix bug where '\r' is appended
|
||||
private string formattedSqlContents = TestUtilities.NormalizeLineEndings(@"create TABLE T1
|
||||
(
|
||||
C1 int NOT NULL,
|
||||
C2 nvarchar(50) NULL
|
||||
)");
|
||||
|
||||
[Fact]
|
||||
public async Task FormatDocumentShouldReturnSingleEdit()
|
||||
{
|
||||
// Given a document that we want to format
|
||||
SetupScriptFile(defaultSqlContents);
|
||||
// When format document is called
|
||||
await TestUtils.RunAndVerify<TextEdit[]>(
|
||||
test: (requestContext) => FormatterService.HandleDocFormatRequest(docFormatParams, requestContext),
|
||||
verify: (edits =>
|
||||
{
|
||||
// Then expect a single edit to be returned and for it to match the standard formatting
|
||||
Assert.Equal(1, edits.Length);
|
||||
AssertFormattingEqual(formattedSqlContents, edits[0].NewText);
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormatDocumentTelemetryShouldIncludeFormatTypeProperty()
|
||||
{
|
||||
await RunAndVerifyTelemetryTest(
|
||||
// Given a document that we want to format
|
||||
preRunSetup: () => SetupScriptFile(defaultSqlContents),
|
||||
// When format document is called
|
||||
test: (requestContext) => FormatterService.HandleDocFormatRequest(docFormatParams, requestContext),
|
||||
verify: (result, actualParams) =>
|
||||
{
|
||||
// Then expect a telemetry event to have been sent with the right format definition
|
||||
Assert.NotNull(actualParams);
|
||||
Assert.Equal(TelemetryEventNames.FormatCode, actualParams.Params.EventName);
|
||||
Assert.Equal(TelemetryPropertyNames.DocumentFormatType, actualParams.Params.Properties[TelemetryPropertyNames.FormatType]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FormatRangeTelemetryShouldIncludeFormatTypeProperty()
|
||||
{
|
||||
await RunAndVerifyTelemetryTest(
|
||||
// Given a document that we want to format
|
||||
preRunSetup: () => SetupScriptFile(defaultSqlContents),
|
||||
// When format range is called
|
||||
test: (requestContext) => FormatterService.HandleDocRangeFormatRequest(rangeFormatParams, requestContext),
|
||||
verify: (result, actualParams) =>
|
||||
{
|
||||
// Then expect a telemetry event to have been sent with the right format definition
|
||||
Assert.NotNull(actualParams);
|
||||
Assert.Equal(TelemetryEventNames.FormatCode, actualParams.Params.EventName);
|
||||
Assert.Equal(TelemetryPropertyNames.RangeFormatType, actualParams.Params.Properties[TelemetryPropertyNames.FormatType]);
|
||||
|
||||
// And expect range to have been correctly formatted
|
||||
Assert.Equal(1, result.Length);
|
||||
AssertFormattingEqual(formattedSqlContents, result[0].NewText);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task RunAndVerifyTelemetryTest(
|
||||
Action preRunSetup,
|
||||
Func<RequestContext<TextEdit[]>, Task> test,
|
||||
Action<TextEdit[], TelemetryParams> verify)
|
||||
{
|
||||
SemaphoreSlim semaphore = new SemaphoreSlim(0, 1);
|
||||
TelemetryParams actualParams = null;
|
||||
TextEdit[] result = null;
|
||||
var contextMock = RequestContextMocks.Create<TextEdit[]>(r =>
|
||||
{
|
||||
result = r;
|
||||
})
|
||||
.AddErrorHandling(null)
|
||||
.AddEventHandling(TelemetryNotification.Type, (e, p) =>
|
||||
{
|
||||
actualParams = p;
|
||||
semaphore.Release();
|
||||
});
|
||||
|
||||
// Given a document that we want to format
|
||||
SetupScriptFile(defaultSqlContents);
|
||||
|
||||
// When format document is called
|
||||
await RunAndVerify<TextEdit[]>(
|
||||
test: test,
|
||||
contextMock: contextMock,
|
||||
verify: () =>
|
||||
{
|
||||
// Wait for the telemetry notification to be processed on a background thread
|
||||
semaphore.Wait(TimeSpan.FromSeconds(10));
|
||||
verify(result, actualParams);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task RunAndVerify<T>(Func<RequestContext<T>, Task> test, Mock<RequestContext<T>> contextMock, Action verify)
|
||||
{
|
||||
await test(contextMock.Object);
|
||||
VerifyResult(contextMock, verify);
|
||||
}
|
||||
|
||||
public static void VerifyResult<T>(Mock<RequestContext<T>> contextMock, Action verify)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Never);
|
||||
verify();
|
||||
}
|
||||
|
||||
private static void AssertFormattingEqual(string expected, string actual)
|
||||
{
|
||||
if (string.Compare(expected, actual) != 0)
|
||||
{
|
||||
StringBuilder error = new StringBuilder();
|
||||
error.AppendLine("======================");
|
||||
error.AppendLine("Comparison failed:");
|
||||
error.AppendLine("==Expected==");
|
||||
error.AppendLine(expected);
|
||||
error.AppendLine("==Actual==");
|
||||
error.AppendLine(actual);
|
||||
Assert.False(false, error.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupScriptFile(string fileContents)
|
||||
{
|
||||
WorkspaceServiceMock.SetupGet(service => service.Workspace).Returns(workspaceMock.Object);
|
||||
workspaceMock.Setup(w => w.GetFile(It.IsAny<string>())).Returns(CreateScriptFile(fileContents));
|
||||
}
|
||||
|
||||
private ScriptFile CreateScriptFile(string content)
|
||||
{
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = content
|
||||
};
|
||||
return scriptFile;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the language service autocomplete component
|
||||
/// </summary>
|
||||
public class AutocompleteTests
|
||||
{
|
||||
private const int TaskTimeout = 60000;
|
||||
|
||||
private readonly string testScriptUri = TestObjects.ScriptUri;
|
||||
|
||||
private readonly string testConnectionKey = "testdbcontextkey";
|
||||
|
||||
private Mock<ConnectedBindingQueue> bindingQueue;
|
||||
|
||||
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
|
||||
|
||||
private Mock<RequestContext<CompletionItem[]>> requestContext;
|
||||
|
||||
private Mock<ScriptFile> scriptFile;
|
||||
|
||||
private Mock<IBinder> binder;
|
||||
|
||||
private ScriptParseInfo scriptParseInfo;
|
||||
|
||||
private TextDocumentPosition textDocument;
|
||||
|
||||
private void InitializeTestObjects()
|
||||
{
|
||||
// initial cursor position in the script file
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
|
||||
// default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// set up file for returning the query
|
||||
scriptFile = new Mock<ScriptFile>();
|
||||
scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
|
||||
scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
|
||||
|
||||
// set up workspace mock
|
||||
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(scriptFile.Object);
|
||||
|
||||
// setup binding queue mock
|
||||
bindingQueue = new Mock<ConnectedBindingQueue>();
|
||||
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
|
||||
.Returns(this.testConnectionKey);
|
||||
|
||||
// inject mock instances into the Language Service
|
||||
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
|
||||
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
|
||||
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
|
||||
LanguageService.Instance.BindingQueue = bindingQueue.Object;
|
||||
|
||||
// setup the mock for SendResult
|
||||
requestContext = new Mock<RequestContext<CompletionItem[]>>();
|
||||
requestContext.Setup(rc => rc.SendResult(It.IsAny<CompletionItem[]>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
// setup the IBinder mock
|
||||
binder = new Mock<IBinder>();
|
||||
binder.Setup(b => b.Bind(
|
||||
It.IsAny<IEnumerable<ParseResult>>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BindMode>()));
|
||||
|
||||
scriptParseInfo = new ScriptParseInfo();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, scriptParseInfo);
|
||||
scriptParseInfo.IsConnected = true;
|
||||
scriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
|
||||
|
||||
// setup the binding context object
|
||||
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
|
||||
bindingContext.Binder = binder.Object;
|
||||
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
LanguageService.Instance.BindingQueue.BindingContextMap.Add(scriptParseInfo.ConnectionKey, bindingContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleCompletionRequestDisabled()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
Assert.NotNull(LanguageService.HandleCompletionRequest(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleCompletionResolveRequestDisabled()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
Assert.NotNull(LanguageService.HandleCompletionResolveRequest(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleSignatureHelpRequestDisabled()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
Assert.NotNull(LanguageService.HandleSignatureHelpRequest(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrUpdateScriptParseInfoNullUri()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo);
|
||||
Assert.True(LanguageService.Instance.ScriptParseInfoMap.ContainsKey("abracadabra"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefinitionInvalidTextDocument()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
textDocument.TextDocument.Uri = "invaliduri";
|
||||
Assert.Null(LanguageService.Instance.GetDefinition(textDocument, null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveScriptParseInfoNullUri()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
Assert.False(LanguageService.Instance.RemoveScriptParseInfo("abc123"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsPreviewWindowNullScriptFileTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
Assert.False(LanguageService.Instance.IsPreviewWindow(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItemsInvalidTextDocument()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
textDocument.TextDocument.Uri = "somethinggoeshere";
|
||||
Assert.True(LanguageService.Instance.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDiagnosticFromMarkerTest()
|
||||
{
|
||||
var scriptFileMarker = new ScriptFileMarker()
|
||||
{
|
||||
Message = "Message",
|
||||
Level = ScriptFileMarkerLevel.Error,
|
||||
ScriptRegion = new ScriptRegion()
|
||||
{
|
||||
File = "file://nofile.sql",
|
||||
StartLineNumber = 1,
|
||||
StartColumnNumber = 1,
|
||||
StartOffset = 0,
|
||||
EndLineNumber = 1,
|
||||
EndColumnNumber = 1,
|
||||
EndOffset = 0
|
||||
}
|
||||
};
|
||||
var diagnostic = DiagnosticsHelper.GetDiagnosticFromMarker(scriptFileMarker);
|
||||
Assert.Equal(diagnostic.Message, scriptFileMarker.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapDiagnosticSeverityTest()
|
||||
{
|
||||
var level = ScriptFileMarkerLevel.Error;
|
||||
Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Error);
|
||||
level = ScriptFileMarkerLevel.Warning;
|
||||
Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Warning);
|
||||
level = ScriptFileMarkerLevel.Information;
|
||||
Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Information);
|
||||
level = (ScriptFileMarkerLevel)100;
|
||||
Assert.Equal(DiagnosticsHelper.MapDiagnosticSeverity(level), DiagnosticSeverity.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the primary completion list event handler
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetCompletionsHandlerTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
|
||||
// request the completion list
|
||||
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object);
|
||||
handleCompletion.Wait(TaskTimeout);
|
||||
|
||||
// verify that send result was called with a completion array
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test class for the test binding context
|
||||
/// </summary>
|
||||
public class TestBindingContext : IBindingContext
|
||||
{
|
||||
public TestBindingContext()
|
||||
{
|
||||
this.BindingLock = new ManualResetEvent(true);
|
||||
this.BindingTimeout = 3000;
|
||||
}
|
||||
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public ServerConnection ServerConnection { get; set; }
|
||||
|
||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||
|
||||
public SmoMetadataProvider SmoMetadataProvider { get; set; }
|
||||
|
||||
public IBinder Binder { get; set; }
|
||||
|
||||
public ManualResetEvent BindingLock { get; set; }
|
||||
|
||||
public int BindingTimeout { get; set; }
|
||||
|
||||
public ParseOptions ParseOptions { get; }
|
||||
|
||||
public ServerVersion ServerVersion { get; }
|
||||
|
||||
public DatabaseEngineType DatabaseEngineType { get; }
|
||||
|
||||
public TransactSqlVersion TransactSqlVersion { get; }
|
||||
|
||||
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Binding Queue
|
||||
/// </summary>
|
||||
public class BindingQueueTests
|
||||
{
|
||||
private int bindCallCount = 0;
|
||||
|
||||
private int timeoutCallCount = 0;
|
||||
|
||||
private int bindCallbackDelay = 0;
|
||||
|
||||
private bool isCancelationRequested = false;
|
||||
|
||||
private IBindingContext bindingContext = null;
|
||||
|
||||
private BindingQueue<TestBindingContext> bindingQueue = null;
|
||||
|
||||
private void InitializeTestSettings()
|
||||
{
|
||||
this.bindCallCount = 0;
|
||||
this.timeoutCallCount = 0;
|
||||
this.bindCallbackDelay = 10;
|
||||
this.isCancelationRequested = false;
|
||||
this.bindingContext = GetMockBindingContext();
|
||||
this.bindingQueue = new BindingQueue<TestBindingContext>();
|
||||
}
|
||||
|
||||
private IBindingContext GetMockBindingContext()
|
||||
{
|
||||
return new TestBindingContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test bind operation callback
|
||||
/// </summary>
|
||||
private object TestBindOperation(
|
||||
IBindingContext bindContext,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay);
|
||||
this.isCancelationRequested = cancelToken.IsCancellationRequested;
|
||||
if (!this.isCancelationRequested)
|
||||
{
|
||||
++this.bindCallCount;
|
||||
}
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test callback for the bind timeout operation
|
||||
/// </summary>
|
||||
private object TestTimeoutOperation(
|
||||
IBindingContext bindingContext)
|
||||
{
|
||||
++this.timeoutCallCount;
|
||||
return new CompletionItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a single task
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueOneBindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 1);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a 100 short tasks
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Queue100BindingOperationTest()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
}
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 100);
|
||||
Assert.True(this.timeoutCallCount == 0);
|
||||
Assert.False(this.isCancelationRequested);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an task with a long operation causing a timeout
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void QueueWithTimeout()
|
||||
{
|
||||
InitializeTestSettings();
|
||||
|
||||
this.bindCallbackDelay = 1000;
|
||||
|
||||
this.bindingQueue.QueueBindingOperation(
|
||||
key: "testkey",
|
||||
bindingTimeout: bindCallbackDelay / 2,
|
||||
bindOperation: TestBindOperation,
|
||||
timeoutOperation: TestTimeoutOperation);
|
||||
|
||||
Thread.Sleep(this.bindCallbackDelay + 100);
|
||||
|
||||
this.bindingQueue.StopQueueProcessor(15000);
|
||||
|
||||
Assert.True(this.bindCallCount == 0);
|
||||
Assert.True(this.timeoutCallCount == 1);
|
||||
Assert.True(this.isCancelationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
public class CompletionServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingSqlParserIfTheProcessDoesNotTimeout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEqual(result.CompletionItems == null ? 0 : result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompletionItemsShouldCreatedUsingDefaultListIfTheSqlParserProcessTimesout()
|
||||
{
|
||||
ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
|
||||
ScriptDocumentInfo docInfo = CreateScriptDocumentInfo();
|
||||
CompletionService completionService = new CompletionService(bindingQueue);
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(null, null, null);
|
||||
bool useLowerCaseSuggestions = true;
|
||||
List<Declaration> declarations = new List<Declaration>();
|
||||
CompletionItem[] defaultCompletionList = AutoCompleteHelper.GetDefaultCompletionItems(docInfo, useLowerCaseSuggestions);
|
||||
|
||||
var sqlParserWrapper = new Mock<ISqlParserWrapper>();
|
||||
sqlParserWrapper.Setup(x => x.FindCompletions(docInfo.ScriptParseInfo.ParseResult, docInfo.ParserLine, docInfo.ParserColumn,
|
||||
It.IsAny<IMetadataDisplayInfoProvider>())).Callback(() => Thread.Sleep(LanguageService.BindingTimeout + 100)).Returns(declarations);
|
||||
completionService.SqlParserWrapper = sqlParserWrapper.Object;
|
||||
|
||||
AutoCompletionResult result = completionService.CreateCompletions(connectionInfo, docInfo, useLowerCaseSuggestions);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result.CompletionItems.Count(), defaultCompletionList.Count());
|
||||
Thread.Sleep(3000);
|
||||
Assert.True(connectionInfo.IntellisenseMetrics.Quantile.Any());
|
||||
}
|
||||
|
||||
private ScriptDocumentInfo CreateScriptDocumentInfo()
|
||||
{
|
||||
TextDocumentPosition doc = new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier
|
||||
{
|
||||
Uri = "script file"
|
||||
},
|
||||
Position = new Position()
|
||||
{
|
||||
Line = 1,
|
||||
Character = 14
|
||||
}
|
||||
};
|
||||
ScriptFile scriptFile = new ScriptFile()
|
||||
{
|
||||
Contents = "Select * from sys.all_objects"
|
||||
};
|
||||
|
||||
ScriptParseInfo scriptParseInfo = new ScriptParseInfo() { IsConnected = true };
|
||||
ScriptDocumentInfo docInfo = new ScriptDocumentInfo(doc, scriptFile, scriptParseInfo);
|
||||
|
||||
return docInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
public class InteractionMetricsTest
|
||||
{
|
||||
[Fact]
|
||||
public void MetricsShouldGetSortedGivenUnSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 4, 8, 1, 11, 3 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenNullInput()
|
||||
{
|
||||
int[] metrics = null;
|
||||
Assert.Throws<ArgumentNullException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldThrowExceptionGivenEmptyInput()
|
||||
{
|
||||
int[] metrics = new int[] { };
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new InteractionMetrics<int>(metrics));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenSortedArray()
|
||||
{
|
||||
int[] metrics = new int[] { 1, 3, 4, 8, 11 };
|
||||
int[] expected = new int[] { 1, 3, 4, 8, 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsShouldNotChangeGivenArrayWithOneItem()
|
||||
{
|
||||
int[] metrics = new int[] { 11 };
|
||||
int[] expected = new int[] { 11 };
|
||||
InteractionMetrics<int> interactionMetrics = new InteractionMetrics<int>(metrics);
|
||||
|
||||
Assert.Equal(interactionMetrics.Metrics, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetricsCalculateQuantileCorrectlyGivenSeveralUpdates()
|
||||
{
|
||||
int[] metrics = new int[] { 50, 100, 300, 500, 1000, 2000 };
|
||||
Func<string, double, double> updateValueFactory = (k, current) => current + 1;
|
||||
InteractionMetrics<double> interactionMetrics = new InteractionMetrics<double>(metrics);
|
||||
interactionMetrics.UpdateMetrics(54.4, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(345, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(23, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(51, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(4005, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(2500, 1, updateValueFactory);
|
||||
interactionMetrics.UpdateMetrics(123, 1, updateValueFactory);
|
||||
|
||||
Dictionary<string, double> quantile = interactionMetrics.Quantile;
|
||||
Assert.NotNull(quantile);
|
||||
Assert.Equal(quantile.Count, 5);
|
||||
Assert.Equal(quantile["50"], 1);
|
||||
Assert.Equal(quantile["100"], 2);
|
||||
Assert.Equal(quantile["300"], 1);
|
||||
Assert.Equal(quantile["500"], 2);
|
||||
Assert.Equal(quantile["2000"], 2);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the ServiceHost Language Service tests
|
||||
/// </summary>
|
||||
public class LanguageServiceTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LatestSqlParserIsUsedByDefault()
|
||||
{
|
||||
// This should only parse correctly on SQL server 2016 or newer
|
||||
const string sql2016Text =
|
||||
@"CREATE SECURITY POLICY [FederatedSecurityPolicy]" + "\r\n" +
|
||||
@"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
|
||||
@"ON [dbo].[Customer];";
|
||||
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
|
||||
// parse
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sql2016Text);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
|
||||
// verify that no errors are detected
|
||||
Assert.Equal(0, fileMarkers.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the SQL parser correctly detects errors in text
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ParseSelectStatementWithoutErrors()
|
||||
{
|
||||
// sql statement with no errors
|
||||
const string sqlWithErrors = "SELECT * FROM sys.objects";
|
||||
|
||||
// get the test service
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
|
||||
// parse the sql statement
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
|
||||
// verify there are no errors
|
||||
Assert.Equal(0, fileMarkers.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the SQL parser correctly detects errors in text
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ParseSelectStatementWithError()
|
||||
{
|
||||
// sql statement with errors
|
||||
const string sqlWithErrors = "SELECT *** FROM sys.objects";
|
||||
|
||||
// get test service
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
|
||||
// parse sql statement
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
|
||||
// verify there is one error
|
||||
Assert.Equal(1, fileMarkers.Length);
|
||||
|
||||
// verify the position of the error
|
||||
Assert.Equal(9, fileMarkers[0].ScriptRegion.StartColumnNumber);
|
||||
Assert.Equal(1, fileMarkers[0].ScriptRegion.StartLineNumber);
|
||||
Assert.Equal(10, fileMarkers[0].ScriptRegion.EndColumnNumber);
|
||||
Assert.Equal(1, fileMarkers[0].ScriptRegion.EndLineNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the SQL parser correctly detects errors in text
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ParseMultilineSqlWithErrors()
|
||||
{
|
||||
// multiline sql with errors
|
||||
const string sqlWithErrors =
|
||||
"SELECT *** FROM sys.objects;\n" +
|
||||
"GO\n" +
|
||||
"SELECT *** FROM sys.objects;\n";
|
||||
|
||||
// get test service
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
|
||||
// parse sql
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(sqlWithErrors);
|
||||
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||
|
||||
// verify there are two errors
|
||||
Assert.Equal(2, fileMarkers.Length);
|
||||
|
||||
// check position of first error
|
||||
Assert.Equal(9, fileMarkers[0].ScriptRegion.StartColumnNumber);
|
||||
Assert.Equal(1, fileMarkers[0].ScriptRegion.StartLineNumber);
|
||||
Assert.Equal(10, fileMarkers[0].ScriptRegion.EndColumnNumber);
|
||||
Assert.Equal(1, fileMarkers[0].ScriptRegion.EndLineNumber);
|
||||
|
||||
// check position of second error
|
||||
Assert.Equal(9, fileMarkers[1].ScriptRegion.StartColumnNumber);
|
||||
Assert.Equal(3, fileMarkers[1].ScriptRegion.StartLineNumber);
|
||||
Assert.Equal(10, fileMarkers[1].ScriptRegion.EndColumnNumber);
|
||||
Assert.Equal(3, fileMarkers[1].ScriptRegion.EndLineNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetSignatureHelp returns null when the provided TextDocumentPosition
|
||||
/// has no associated ScriptParseInfo.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetSignatureHelpReturnsNullIfParseInfoNotInitialized()
|
||||
{
|
||||
// Given service doesn't have parseinfo intialized for a document
|
||||
const string docContent = "SELECT * FROM sys.objects";
|
||||
LanguageService service = TestObjects.GetTestLanguageService();
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents(docContent);
|
||||
|
||||
// When requesting SignatureHelp
|
||||
SignatureHelp signatureHelp = service.GetSignatureHelp(TestObjects.GetTestDocPosition(), scriptFile);
|
||||
|
||||
// Then null is returned as no parse info can be used to find the signature
|
||||
Assert.Null(signatureHelp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyCompletionListTest()
|
||||
{
|
||||
Assert.Equal(AutoCompleteHelper.EmptyCompletionList.Length, 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetWorkspaceServiceInstanceTest()
|
||||
{
|
||||
AutoCompleteHelper.WorkspaceServiceInstance = null;
|
||||
// workspace will be recreated if it's set to null
|
||||
Assert.NotNull(AutoCompleteHelper.WorkspaceServiceInstance);
|
||||
}
|
||||
|
||||
internal class TestScriptDocumentInfo : ScriptDocumentInfo
|
||||
{
|
||||
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
|
||||
string tokenText = null)
|
||||
:base(textDocumentPosition, scriptFile, scriptParseInfo)
|
||||
{
|
||||
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
|
||||
}
|
||||
|
||||
private string tokenText;
|
||||
|
||||
public override string TokenText
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.tokenText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultCompletionListWithNoMatchesTest()
|
||||
{
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents("koko wants a bananas");
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = false };
|
||||
|
||||
var scriptDocumentInfo = new TestScriptDocumentInfo(
|
||||
new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
||||
Position = new Position() { Line = 0, Character = 0 }
|
||||
}, scriptFile, scriptInfo);
|
||||
|
||||
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultCompletionListWithMatchesTest()
|
||||
{
|
||||
var scriptFile = new ScriptFile();
|
||||
scriptFile.SetFileContents("koko wants a bananas");
|
||||
|
||||
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = false };
|
||||
|
||||
var scriptDocumentInfo = new TestScriptDocumentInfo(
|
||||
new TextDocumentPosition()
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
|
||||
Position = new Position() { Line = 0, Character = 0 }
|
||||
}, scriptFile, scriptInfo, "all");
|
||||
|
||||
CompletionItem[] result = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
|
||||
Assert.Equal(result.Length, 1);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the language service peek definition/ go to definition feature
|
||||
/// </summary>
|
||||
public class PeekDefinitionTests
|
||||
{
|
||||
private const int TaskTimeout = 30000;
|
||||
|
||||
private readonly string testScriptUri = TestObjects.ScriptUri;
|
||||
|
||||
private readonly string testConnectionKey = "testdbcontextkey";
|
||||
|
||||
private Mock<ConnectedBindingQueue> bindingQueue;
|
||||
|
||||
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
|
||||
|
||||
private Mock<RequestContext<Location[]>> requestContext;
|
||||
|
||||
private Mock<IBinder> binder;
|
||||
|
||||
private TextDocumentPosition textDocument;
|
||||
|
||||
private void InitializeTestObjects()
|
||||
{
|
||||
// initial cursor position in the script file
|
||||
textDocument = new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri},
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 23
|
||||
}
|
||||
};
|
||||
|
||||
// default settings are stored in the workspace service
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
|
||||
// set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
|
||||
fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri);
|
||||
|
||||
// set up workspace mock
|
||||
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
// setup binding queue mock
|
||||
bindingQueue = new Mock<ConnectedBindingQueue>();
|
||||
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
|
||||
.Returns(this.testConnectionKey);
|
||||
|
||||
// inject mock instances into the Language Service
|
||||
LanguageService.WorkspaceServiceInstance = workspaceService.Object;
|
||||
LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService();
|
||||
ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo();
|
||||
LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo);
|
||||
LanguageService.Instance.BindingQueue = bindingQueue.Object;
|
||||
|
||||
// setup the mock for SendResult
|
||||
requestContext = new Mock<RequestContext<Location[]>>();
|
||||
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
requestContext.Setup(rc => rc.SendError(It.IsAny<DefinitionError>())).Returns(Task.FromResult(0));;
|
||||
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));;
|
||||
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));;
|
||||
|
||||
// setup the IBinder mock
|
||||
binder = new Mock<IBinder>();
|
||||
binder.Setup(b => b.Bind(
|
||||
It.IsAny<IEnumerable<ParseResult>>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<BindMode>()));
|
||||
|
||||
var testScriptParseInfo = new ScriptParseInfo();
|
||||
LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo);
|
||||
testScriptParseInfo.IsConnected = false;
|
||||
testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo);
|
||||
|
||||
// setup the binding context object
|
||||
ConnectedBindingContext bindingContext = new ConnectedBindingContext();
|
||||
bindingContext.Binder = binder.Object;
|
||||
bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||
LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tests the definition event handler. When called with no active connection, an error is sent
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DefinitionsHandlerWithNoConnectionTest()
|
||||
{
|
||||
InitializeTestObjects();
|
||||
// request definition
|
||||
var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout));
|
||||
await definitionTask;
|
||||
// verify that send result was not called and send error was called
|
||||
requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never());
|
||||
requestContext.Verify(m => m.SendError(It.IsAny<DefinitionError>()), Times.Once());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests creating location objects on windows and non-windows systems
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetLocationFromFileForValidFilePathTest()
|
||||
{
|
||||
string filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql";
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0);
|
||||
|
||||
string expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql";
|
||||
Assert.Equal(locations[0].Uri, expectedFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid database name
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string validDatabaseQualifiedName = "master.test.test_table";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "test";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid object name and no schema
|
||||
/// </summary>
|
||||
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string validDatabaseQualifiedName = "test_table";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "dbo";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a invalid database name
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string validDatabaseQualifiedName = "x.y.z";
|
||||
string objectName = "test_table";
|
||||
string expectedSchemaName = "dbo";
|
||||
|
||||
string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName);
|
||||
Assert.Equal(actualSchemaName, expectedSchemaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test deletion of peek definition scripts for a valid temp folder that exists
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DeletePeekDefinitionScriptsTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
var languageService = LanguageService.Instance;
|
||||
Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
|
||||
languageService.DeletePeekDefinitionScripts();
|
||||
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test deletion of peek definition scripts for a temp folder that does not exist
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest()
|
||||
{
|
||||
var languageService = LanguageService.Instance;
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true);
|
||||
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
|
||||
// Expected not to throw any exception
|
||||
languageService.DeletePeekDefinitionScripts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the full object name from quickInfoText.
|
||||
/// Given a valid object name string and a vaild quickInfo string containing the object name
|
||||
/// Expect the full object name (database.schema.objectName)
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFullObjectNameFromQuickInfoWithValidStringsTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "testTable";
|
||||
string quickInfoText = "table master.dbo.testTable";
|
||||
string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
string expected = "master.dbo.testTable";
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the full object name from quickInfoText with case insensitive comparison.
|
||||
/// Given a valid object name string and a vaild quickInfo string containing the object name
|
||||
/// Expect the full object name (database.schema.objectName)
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFullObjectNameFromQuickInfoWithValidStringsandIgnoreCaseTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "testtable";
|
||||
string quickInfoText = "table master.dbo.testTable";
|
||||
string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase);
|
||||
string expected = "master.dbo.testTable";
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the full object name from quickInfoText.
|
||||
/// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa)
|
||||
/// Expect null
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFullObjectNameFromQuickInfoWithNullStringsTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string expected = null;
|
||||
|
||||
string objectName = null;
|
||||
string quickInfoText = "table master.dbo.testTable";
|
||||
string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
|
||||
quickInfoText = null;
|
||||
objectName = "tableName";
|
||||
result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
|
||||
quickInfoText = null;
|
||||
objectName = null;
|
||||
result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the full object name from quickInfoText.
|
||||
/// Given a valid object name string and a vaild quickInfo string that does not contain the object name
|
||||
/// Expect null
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetFullObjectNameFromQuickInfoWithIncorrectObjectNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "test";
|
||||
string quickInfoText = "table master.dbo.tableName";
|
||||
string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
string expected = null;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the object type from quickInfoText.
|
||||
/// Given a valid object name string and a vaild quickInfo string containing the object name
|
||||
/// Expect correct object type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTokenTypeFromQuickInfoWithValidStringsTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "tableName";
|
||||
string quickInfoText = "table master.dbo.tableName";
|
||||
string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
string expected = "table";
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the object type from quickInfoText with case insensitive comparison.
|
||||
/// Given a valid object name string and a vaild quickInfo string containing the object name
|
||||
/// Expect correct object type
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTokenTypeFromQuickInfoWithValidStringsandIgnoreCaseTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "tablename";
|
||||
string quickInfoText = "table master.dbo.tableName";
|
||||
string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase);
|
||||
string expected = "table";
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting theobject type from quickInfoText.
|
||||
/// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa)
|
||||
/// Expect null
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTokenTypeFromQuickInfoWithNullStringsTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string expected = null;
|
||||
|
||||
string objectName = null;
|
||||
string quickInfoText = "table master.dbo.testTable";
|
||||
string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
|
||||
quickInfoText = null;
|
||||
objectName = "tableName";
|
||||
result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
|
||||
quickInfoText = null;
|
||||
objectName = null;
|
||||
result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting the object type from quickInfoText.
|
||||
/// Given a valid object name string and a vaild quickInfo string that does not containthe object name
|
||||
/// Expect null
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetTokenTypeFromQuickInfoWithIncorrectObjectNameTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "test";
|
||||
string quickInfoText = "table master.dbo.tableName";
|
||||
string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal);
|
||||
string expected = null;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test getting definition using quickInfo text without a live connection
|
||||
/// Expect an error result (because you cannot script without a live connection)
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetDefinitionUsingQuickInfoWithoutConnectionTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "tableName";
|
||||
string quickInfoText = "table master.dbo.tableName";
|
||||
DefinitionResult result = peekDefinition.GetDefinitionUsingQuickInfoText(quickInfoText, objectName, null);
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsErrorResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test getting definition using declaration Type without a live connection
|
||||
/// Expect an error result (because you cannot script without a live connection)
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetDefinitionUsingDeclarationItemWithoutConnectionTest()
|
||||
{
|
||||
PeekDefinition peekDefinition = new PeekDefinition(null, null);
|
||||
string objectName = "tableName";
|
||||
string fullObjectName = "master.dbo.tableName";
|
||||
DefinitionResult result = peekDefinition.GetDefinitionUsingDeclarationType(DeclarationType.Table, fullObjectName, objectName, null);
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsErrorResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
|
||||
{
|
||||
public class SqlCompletionItemTests
|
||||
{
|
||||
private static readonly string[] ReservedWords = new string[]
|
||||
{
|
||||
"all",
|
||||
"alter",
|
||||
"and",
|
||||
"apply",
|
||||
"as",
|
||||
"asc",
|
||||
"at",
|
||||
"backup",
|
||||
"begin",
|
||||
"binary",
|
||||
"bit",
|
||||
"break",
|
||||
"bulk",
|
||||
"by",
|
||||
"call",
|
||||
"cascade",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"character",
|
||||
"check",
|
||||
"checkpoint",
|
||||
"close",
|
||||
"clustered",
|
||||
"column",
|
||||
"columnstore",
|
||||
"commit",
|
||||
"connect",
|
||||
"constraint",
|
||||
"continue",
|
||||
"create",
|
||||
"cross",
|
||||
"current_date",
|
||||
"cursor",
|
||||
"cursor_close_on_commit",
|
||||
"cursor_default",
|
||||
"data",
|
||||
"data_compression",
|
||||
"database",
|
||||
"date",
|
||||
"datetime",
|
||||
"datetime2",
|
||||
"days",
|
||||
"dbcc",
|
||||
"dec",
|
||||
"decimal",
|
||||
"declare",
|
||||
"default",
|
||||
"delete",
|
||||
"deny",
|
||||
"desc",
|
||||
"description",
|
||||
"disabled",
|
||||
"disk",
|
||||
"distinct",
|
||||
"double",
|
||||
"drop",
|
||||
"drop_existing",
|
||||
"dump",
|
||||
"dynamic",
|
||||
"else",
|
||||
"enable",
|
||||
"encrypted",
|
||||
"end",
|
||||
"end-exec",
|
||||
"exec",
|
||||
"execute",
|
||||
"exists",
|
||||
"exit",
|
||||
"external",
|
||||
"fast_forward",
|
||||
"fetch",
|
||||
"file",
|
||||
"filegroup",
|
||||
"filename",
|
||||
"filestream",
|
||||
"filter",
|
||||
"first",
|
||||
"float",
|
||||
"for",
|
||||
"foreign",
|
||||
"from",
|
||||
"full",
|
||||
"function",
|
||||
"geography",
|
||||
"get",
|
||||
"global",
|
||||
"go",
|
||||
"goto",
|
||||
"grant",
|
||||
"group",
|
||||
"hash",
|
||||
"hashed",
|
||||
"having",
|
||||
"hidden",
|
||||
"hierarchyid",
|
||||
"holdlock",
|
||||
"hours",
|
||||
"identity",
|
||||
"identitycol",
|
||||
"if",
|
||||
"image",
|
||||
"immediate",
|
||||
"include",
|
||||
"index",
|
||||
"inner",
|
||||
"insert",
|
||||
"instead",
|
||||
"int",
|
||||
"integer",
|
||||
"intersect",
|
||||
"into",
|
||||
"isolation",
|
||||
"join",
|
||||
"json",
|
||||
"key",
|
||||
"language",
|
||||
"last",
|
||||
"left",
|
||||
"level",
|
||||
"lineno",
|
||||
"load",
|
||||
"local",
|
||||
"locate",
|
||||
"location",
|
||||
"login",
|
||||
"masked",
|
||||
"maxdop",
|
||||
"merge",
|
||||
"message",
|
||||
"modify",
|
||||
"move",
|
||||
"namespace",
|
||||
"native_compilation",
|
||||
"nchar",
|
||||
"next",
|
||||
"no",
|
||||
"nocheck",
|
||||
"nocount",
|
||||
"nonclustered",
|
||||
"none",
|
||||
"norecompute",
|
||||
"not",
|
||||
"now",
|
||||
"null",
|
||||
"numeric",
|
||||
"object",
|
||||
"of",
|
||||
"off",
|
||||
"offsets",
|
||||
"on",
|
||||
"online",
|
||||
"open",
|
||||
"openrowset",
|
||||
"openxml",
|
||||
"option",
|
||||
"or",
|
||||
"order",
|
||||
"out",
|
||||
"outer",
|
||||
"output",
|
||||
"over",
|
||||
"owner",
|
||||
"partial",
|
||||
"partition",
|
||||
"password",
|
||||
"path",
|
||||
"percent",
|
||||
"percentage",
|
||||
"period",
|
||||
"persisted",
|
||||
"plan",
|
||||
"policy",
|
||||
"precision",
|
||||
"predicate",
|
||||
"primary",
|
||||
"print",
|
||||
"prior",
|
||||
"proc",
|
||||
"procedure",
|
||||
"public",
|
||||
"query_store",
|
||||
"quoted_identifier",
|
||||
"raiserror",
|
||||
"range",
|
||||
"raw",
|
||||
"read",
|
||||
"read_committed_snapshot",
|
||||
"read_only",
|
||||
"read_write",
|
||||
"readonly",
|
||||
"readtext",
|
||||
"real",
|
||||
"rebuild",
|
||||
"receive",
|
||||
"reconfigure",
|
||||
"recovery",
|
||||
"recursive",
|
||||
"recursive_triggers",
|
||||
"references",
|
||||
"relative",
|
||||
"remove",
|
||||
"reorganize",
|
||||
"required",
|
||||
"restart",
|
||||
"restore",
|
||||
"restrict",
|
||||
"resume",
|
||||
"return",
|
||||
"returns",
|
||||
"revert",
|
||||
"revoke",
|
||||
"rollback",
|
||||
"rollup",
|
||||
"row",
|
||||
"rowcount",
|
||||
"rowguidcol",
|
||||
"rows",
|
||||
"rule",
|
||||
"sample",
|
||||
"save",
|
||||
"schema",
|
||||
"schemabinding",
|
||||
"scoped",
|
||||
"scroll",
|
||||
"secondary",
|
||||
"security",
|
||||
"select",
|
||||
"send",
|
||||
"sent",
|
||||
"sequence",
|
||||
"server",
|
||||
"session",
|
||||
"set",
|
||||
"sets",
|
||||
"setuser",
|
||||
"simple",
|
||||
"smallint",
|
||||
"smallmoney",
|
||||
"snapshot",
|
||||
"sql",
|
||||
"standard",
|
||||
"start",
|
||||
"started",
|
||||
"state",
|
||||
"statement",
|
||||
"static",
|
||||
"statistics",
|
||||
"statistics_norecompute",
|
||||
"status",
|
||||
"stopped",
|
||||
"sysname",
|
||||
"system",
|
||||
"system_time",
|
||||
"table",
|
||||
"take",
|
||||
"target",
|
||||
"then",
|
||||
"throw",
|
||||
"time",
|
||||
"timestamp",
|
||||
"tinyint",
|
||||
"to",
|
||||
"top",
|
||||
"tran",
|
||||
"transaction",
|
||||
"trigger",
|
||||
"truncate",
|
||||
"try",
|
||||
"tsql",
|
||||
"type",
|
||||
"uncommitted",
|
||||
"union",
|
||||
"unique",
|
||||
"uniqueidentifier",
|
||||
"updatetext",
|
||||
"use",
|
||||
"user",
|
||||
"using",
|
||||
"value",
|
||||
"values",
|
||||
"varchar",
|
||||
"version",
|
||||
"view",
|
||||
"waitfor",
|
||||
"when",
|
||||
"where",
|
||||
"while",
|
||||
"with",
|
||||
"within",
|
||||
"without",
|
||||
"writetext",
|
||||
"xact_abort",
|
||||
"xml",
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void InsertTextShouldIncludeBracketGivenNameWithSpace()
|
||||
{
|
||||
string declarationTitle = "name with space";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.True(completionItem.InsertText.StartsWith("[") && completionItem.InsertText.EndsWith("]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrowExceptionGivenEmptyDeclarionType()
|
||||
{
|
||||
string declarationTitle = "";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
Assert.Throws<ArgumentException>(() => new SqlCompletionItem(declarationTitle, declarationType, tokenText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorShouldThrowExceptionGivenNullDeclarion()
|
||||
{
|
||||
string tokenText = "";
|
||||
Assert.Throws<ArgumentException>(() => new SqlCompletionItem(null, tokenText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertTextShouldIncludeBracketGivenNameWithSpecialCharacter()
|
||||
{
|
||||
string declarationTitle = "name @";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, declarationTitle);
|
||||
Assert.Equal(completionItem.Label, declarationTitle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenTokenWithBracket()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenTokenWithBrackets()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenSqlObjectNameWithBracket()
|
||||
{
|
||||
string declarationTitle = @"Bracket\[";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, declarationTitle);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, declarationTitle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenSqlObjectNameWithBracketAndTokenWithBracket()
|
||||
{
|
||||
string declarationTitle = @"Bracket\[";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldNotIncludeBracketGivenNameWithBrackets()
|
||||
{
|
||||
string declarationTitle = "[name]";
|
||||
string expected = declarationTitle;
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeBracketGivenNameWithOneBracket()
|
||||
{
|
||||
string declarationTitle = "[name";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[]";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeQuotedIdentifiersGivenTokenWithQuotedIdentifier()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "\"" + declarationTitle + "\"";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "\"";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldIncludeQuotedIdentifiersGivenTokenWithQuotedIdentifiers()
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string expected = "\"" + declarationTitle + "\"";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "\"\"";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertTextShouldIncludeBracketGivenReservedName()
|
||||
{
|
||||
foreach (string word in ReservedWords)
|
||||
{
|
||||
string declarationTitle = word;
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, word);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, word);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldNotIncludeBracketIfTokenIncludesQuotedIdentifiersGivenReservedName()
|
||||
{
|
||||
string declarationTitle = "User";
|
||||
string expected = "\"" + declarationTitle + "\"";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "\"";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelShouldNotIncludeDoubleBracketIfTokenIncludesBracketsGivenReservedName()
|
||||
{
|
||||
string declarationTitle = "User";
|
||||
string expected = "[" + declarationTitle + "]";
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "[";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TempTablesShouldNotBeEscaped()
|
||||
{
|
||||
string declarationTitle = "#TestTable";
|
||||
string expected = declarationTitle;
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
Assert.Equal(completionItem.Label, expected);
|
||||
Assert.Equal(completionItem.InsertText, expected);
|
||||
Assert.Equal(completionItem.Detail, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeModuleGivenSchemaDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Module;
|
||||
DeclarationType declarationType = DeclarationType.Schema;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFieldGivenColumnDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Field;
|
||||
DeclarationType declarationType = DeclarationType.Column;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFileGivenTableDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.File;
|
||||
DeclarationType declarationType = DeclarationType.Table;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeFileGivenViewDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.File;
|
||||
DeclarationType declarationType = DeclarationType.View;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeMethodGivenDatabaseDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Method;
|
||||
DeclarationType declarationType = DeclarationType.Database;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeValueGivenScalarValuedFunctionDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Value;
|
||||
DeclarationType declarationType = DeclarationType.ScalarValuedFunction;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeValueGivenTableValuedFunctionDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Value;
|
||||
DeclarationType declarationType = DeclarationType.TableValuedFunction;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KindShouldBeUnitGivenUnknownDeclarationType()
|
||||
{
|
||||
CompletionItemKind expectedType = CompletionItemKind.Unit;
|
||||
DeclarationType declarationType = DeclarationType.XmlIndex;
|
||||
ValidateDeclarationType(declarationType, expectedType);
|
||||
}
|
||||
|
||||
private void ValidateDeclarationType(DeclarationType declarationType, CompletionItemKind expectedType)
|
||||
{
|
||||
string declarationTitle = "name";
|
||||
string tokenText = "";
|
||||
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
|
||||
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
|
||||
|
||||
|
||||
Assert.Equal(completionItem.Kind, expectedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Messaging
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
public const string TestEventString = @"{""type"":""event"",""event"":""testEvent"",""body"":null}";
|
||||
public const string TestEventFormatString = @"{{""event"":""testEvent"",""body"":{{""someString"":""{0}""}},""seq"":0,""type"":""event""}}";
|
||||
public static readonly int ExpectedMessageByteCount = Encoding.UTF8.GetByteCount(TestEventString);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Messaging
|
||||
{
|
||||
public class MessageDispatcherTests
|
||||
{
|
||||
[Fact]
|
||||
public void SetRequestHandlerWithOverrideTest()
|
||||
{
|
||||
RequestType<int, int> requestType = RequestType<int, int>.Create("test/requestType");
|
||||
var dispatcher = new MessageDispatcher(new Mock<ChannelBase>().Object);
|
||||
dispatcher.SetRequestHandler<int, int>(
|
||||
requestType,
|
||||
(i, j) =>
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
},
|
||||
true);
|
||||
Assert.True(dispatcher.requestHandlers.Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetEventHandlerTest()
|
||||
{
|
||||
EventType<int> eventType = EventType<int>.Create("test/eventType");
|
||||
var dispatcher = new MessageDispatcher(new Mock<ChannelBase>().Object);
|
||||
dispatcher.SetEventHandler<int>(
|
||||
eventType,
|
||||
(i, j) =>
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
Assert.True(dispatcher.eventHandlers.Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetEventHandlerWithOverrideTest()
|
||||
{
|
||||
EventType<int> eventType = EventType<int>.Create("test/eventType");
|
||||
var dispatcher = new MessageDispatcher(new Mock<ChannelBase>().Object);
|
||||
dispatcher.SetEventHandler<int>(
|
||||
eventType,
|
||||
(i, j) =>
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
},
|
||||
true);
|
||||
Assert.True(dispatcher.eventHandlers.Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnListenTaskCompletedFaultedTaskTest()
|
||||
{
|
||||
Task t = null;
|
||||
|
||||
try
|
||||
{
|
||||
t = Task.Run(() =>
|
||||
{
|
||||
throw new Exception();
|
||||
});
|
||||
t.Wait();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
bool handlerCalled = false;
|
||||
var dispatcher = new MessageDispatcher(new Mock<ChannelBase>().Object);
|
||||
dispatcher.UnhandledException += (s, e) => handlerCalled = true;
|
||||
dispatcher.OnListenTaskCompleted(t);
|
||||
Assert.True(handlerCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Messaging
|
||||
{
|
||||
public class MessageReaderTests
|
||||
{
|
||||
|
||||
private readonly IMessageSerializer messageSerializer;
|
||||
|
||||
public MessageReaderTests()
|
||||
{
|
||||
this.messageSerializer = new V8MessageSerializer();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsMessage()
|
||||
{
|
||||
MemoryStream inputStream = new MemoryStream();
|
||||
MessageReader messageReader = new MessageReader(inputStream, this.messageSerializer);
|
||||
|
||||
// Write a message to the stream
|
||||
byte[] messageBuffer = this.GetMessageBytes(Common.TestEventString);
|
||||
inputStream.Write(this.GetMessageBytes(Common.TestEventString), 0, messageBuffer.Length);
|
||||
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
Message messageResult = messageReader.ReadMessage().Result;
|
||||
Assert.Equal("testEvent", messageResult.Method);
|
||||
|
||||
inputStream.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsManyBufferedMessages()
|
||||
{
|
||||
MemoryStream inputStream = new MemoryStream();
|
||||
MessageReader messageReader =
|
||||
new MessageReader(
|
||||
inputStream,
|
||||
this.messageSerializer);
|
||||
|
||||
// Get a message to use for writing to the stream
|
||||
byte[] messageBuffer = this.GetMessageBytes(Common.TestEventString);
|
||||
|
||||
// How many messages of this size should we write to overflow the buffer?
|
||||
int overflowMessageCount =
|
||||
(int)Math.Ceiling(
|
||||
(MessageReader.DefaultBufferSize * 1.5) / messageBuffer.Length);
|
||||
|
||||
// Write the necessary number of messages to the stream
|
||||
for (int i = 0; i < overflowMessageCount; i++)
|
||||
{
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
}
|
||||
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Read the written messages from the stream
|
||||
for (int i = 0; i < overflowMessageCount; i++)
|
||||
{
|
||||
Message messageResult = messageReader.ReadMessage().Result;
|
||||
Assert.Equal("testEvent", messageResult.Method);
|
||||
}
|
||||
|
||||
inputStream.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMalformedMissingHeaderTest()
|
||||
{
|
||||
using (MemoryStream inputStream = new MemoryStream())
|
||||
{
|
||||
// If:
|
||||
// ... I create a new stream and pass it information that is malformed
|
||||
// ... and attempt to read a message from it
|
||||
MessageReader messageReader = new MessageReader(inputStream, messageSerializer);
|
||||
byte[] messageBuffer = Encoding.ASCII.GetBytes("This is an invalid header\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Then:
|
||||
// ... An exception should be thrown while reading
|
||||
Assert.ThrowsAsync<ArgumentException>(() => messageReader.ReadMessage()).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMalformedContentLengthNonIntegerTest()
|
||||
{
|
||||
using (MemoryStream inputStream = new MemoryStream())
|
||||
{
|
||||
// If:
|
||||
// ... I create a new stream and pass it a non-integer content-length header
|
||||
// ... and attempt to read a message from it
|
||||
MessageReader messageReader = new MessageReader(inputStream, messageSerializer);
|
||||
byte[] messageBuffer = Encoding.ASCII.GetBytes("Content-Length: asdf\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Then:
|
||||
// ... An exception should be thrown while reading
|
||||
Assert.ThrowsAsync<MessageParseException>(() => messageReader.ReadMessage()).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMissingContentLengthHeaderTest()
|
||||
{
|
||||
using (MemoryStream inputStream = new MemoryStream())
|
||||
{
|
||||
// If:
|
||||
// ... I create a new stream and pass it a a message without a content-length header
|
||||
// ... and attempt to read a message from it
|
||||
MessageReader messageReader = new MessageReader(inputStream, messageSerializer);
|
||||
byte[] messageBuffer = Encoding.ASCII.GetBytes("Content-Type: asdf\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Then:
|
||||
// ... An exception should be thrown while reading
|
||||
Assert.ThrowsAsync<MessageParseException>(() => messageReader.ReadMessage()).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMalformedContentLengthTooShortTest()
|
||||
{
|
||||
using (MemoryStream inputStream = new MemoryStream())
|
||||
{
|
||||
// If:
|
||||
// ... Pass in an event that has an incorrect content length
|
||||
// ... And pass in an event that is correct
|
||||
MessageReader messageReader = new MessageReader(inputStream, messageSerializer);
|
||||
byte[] messageBuffer = Encoding.ASCII.GetBytes("Content-Length: 10\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
messageBuffer = Encoding.UTF8.GetBytes(Common.TestEventString);
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
messageBuffer = Encoding.ASCII.GetBytes("\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Then:
|
||||
// ... The first read should fail with an exception while deserializing
|
||||
Assert.ThrowsAsync<JsonReaderException>(() => messageReader.ReadMessage()).Wait();
|
||||
|
||||
// ... The second read should fail with an exception while reading headers
|
||||
Assert.ThrowsAsync<MessageParseException>(() => messageReader.ReadMessage()).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMalformedThenValidTest()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new stream and pass it information that is malformed
|
||||
// ... and attempt to read a message from it
|
||||
// ... Then pass it information that is valid and attempt to read a message from it
|
||||
using (MemoryStream inputStream = new MemoryStream())
|
||||
{
|
||||
MessageReader messageReader = new MessageReader(inputStream, messageSerializer);
|
||||
byte[] messageBuffer = Encoding.ASCII.GetBytes("This is an invalid header\r\n\r\n");
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
messageBuffer = GetMessageBytes(Common.TestEventString);
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// Then:
|
||||
// ... An exception should be thrown while reading the first one
|
||||
Assert.ThrowsAsync<ArgumentException>(() => messageReader.ReadMessage()).Wait();
|
||||
|
||||
// ... A test event should be successfully read from the second one
|
||||
Message messageResult = messageReader.ReadMessage().Result;
|
||||
Assert.NotNull(messageResult);
|
||||
Assert.Equal("testEvent", messageResult.Method);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderResizesBufferForLargeMessages()
|
||||
{
|
||||
MemoryStream inputStream = new MemoryStream();
|
||||
MessageReader messageReader =
|
||||
new MessageReader(
|
||||
inputStream,
|
||||
this.messageSerializer);
|
||||
|
||||
// Get a message with content so large that the buffer will need
|
||||
// to be resized to fit it all.
|
||||
byte[] messageBuffer = this.GetMessageBytes(
|
||||
string.Format(
|
||||
Common.TestEventFormatString,
|
||||
new String('X', (int) (MessageReader.DefaultBufferSize*3))));
|
||||
|
||||
inputStream.Write(messageBuffer, 0, messageBuffer.Length);
|
||||
inputStream.Flush();
|
||||
inputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
Message messageResult = messageReader.ReadMessage().Result;
|
||||
Assert.Equal("testEvent", messageResult.Method);
|
||||
|
||||
inputStream.Dispose();
|
||||
}
|
||||
|
||||
private byte[] GetMessageBytes(string messageString, Encoding encoding = null)
|
||||
{
|
||||
if (encoding == null)
|
||||
{
|
||||
encoding = Encoding.UTF8;
|
||||
}
|
||||
|
||||
byte[] messageBytes = Encoding.UTF8.GetBytes(messageString);
|
||||
byte[] headerBytes = Encoding.ASCII.GetBytes(string.Format(Constants.ContentLengthFormatString, messageBytes.Length));
|
||||
|
||||
// Copy the bytes into a single buffer
|
||||
byte[] finalBytes = new byte[headerBytes.Length + messageBytes.Length];
|
||||
Buffer.BlockCopy(headerBytes, 0, finalBytes, 0, headerBytes.Length);
|
||||
Buffer.BlockCopy(messageBytes, 0, finalBytes, headerBytes.Length, messageBytes.Length);
|
||||
|
||||
return finalBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Messaging
|
||||
{
|
||||
public class MessageWriterTests
|
||||
{
|
||||
private readonly IMessageSerializer messageSerializer;
|
||||
|
||||
public MessageWriterTests()
|
||||
{
|
||||
this.messageSerializer = new V8MessageSerializer();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeMessageTest()
|
||||
{
|
||||
// serialize\deserialize a request
|
||||
var message = new Message();
|
||||
message.MessageType = MessageType.Request;
|
||||
message.Id = "id";
|
||||
message.Method = "method";
|
||||
message.Contents = null;
|
||||
var serializedMessage = this.messageSerializer.SerializeMessage(message);
|
||||
Assert.NotNull(serializedMessage);
|
||||
var deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage);
|
||||
Assert.Equal(message.Id, deserializedMessage.Id);
|
||||
|
||||
// serialize\deserialize a response
|
||||
message.MessageType = MessageType.Response;
|
||||
serializedMessage = this.messageSerializer.SerializeMessage(message);
|
||||
Assert.NotNull(serializedMessage);
|
||||
deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage);
|
||||
Assert.Equal(message.Id, deserializedMessage.Id);
|
||||
|
||||
// serialize\deserialize a response with an error
|
||||
message.Error = JToken.FromObject("error");
|
||||
serializedMessage = this.messageSerializer.SerializeMessage(message);
|
||||
Assert.NotNull(serializedMessage);
|
||||
deserializedMessage = this.messageSerializer.DeserializeMessage(serializedMessage);
|
||||
Assert.Equal(message.Error, deserializedMessage.Error);
|
||||
|
||||
// serialize\deserialize an unknown response type
|
||||
serializedMessage.Remove("type");
|
||||
serializedMessage.Add("type", JToken.FromObject("dontknowthisone"));
|
||||
Assert.Equal(this.messageSerializer.DeserializeMessage(serializedMessage).MessageType, MessageType.Unknown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WritesMessage()
|
||||
{
|
||||
MemoryStream outputStream = new MemoryStream();
|
||||
MessageWriter messageWriter = new MessageWriter(outputStream, this.messageSerializer);
|
||||
|
||||
// Write the message and then roll back the stream to be read
|
||||
// TODO: This will need to be redone!
|
||||
await messageWriter.WriteMessage(Hosting.Protocol.Contracts.Message.Event("testEvent", null));
|
||||
outputStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
string expectedHeaderString = string.Format(Constants.ContentLengthFormatString,
|
||||
Common.ExpectedMessageByteCount);
|
||||
|
||||
byte[] buffer = new byte[128];
|
||||
await outputStream.ReadAsync(buffer, 0, expectedHeaderString.Length);
|
||||
|
||||
Assert.Equal(
|
||||
expectedHeaderString,
|
||||
Encoding.ASCII.GetString(buffer, 0, expectedHeaderString.Length));
|
||||
|
||||
// Read the message
|
||||
await outputStream.ReadAsync(buffer, 0, Common.ExpectedMessageByteCount);
|
||||
|
||||
Assert.Equal(Common.TestEventString,
|
||||
Encoding.UTF8.GetString(buffer, 0, Common.ExpectedMessageByteCount));
|
||||
|
||||
outputStream.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Messaging
|
||||
{
|
||||
#region Request Types
|
||||
|
||||
internal class TestRequest
|
||||
{
|
||||
public Task ProcessMessage(MessageWriter messageWriter)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestRequestArguments
|
||||
{
|
||||
public string SomeString { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Response Types
|
||||
|
||||
internal class TestResponse
|
||||
{
|
||||
}
|
||||
|
||||
internal class TestResponseBody
|
||||
{
|
||||
public string SomeString { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Types
|
||||
|
||||
internal class TestEvent
|
||||
{
|
||||
}
|
||||
|
||||
internal class TestEventBody
|
||||
{
|
||||
public string SomeString { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>2d771d16-9d85-4053-9f79-e2034737deef</ProjectGuid>
|
||||
<RootNamespace>Microsoft.SqlTools.ServiceLayer.UnitTests</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'==''">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SqlToolsEditorServices.Test.Transport.Stdio")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("SqlToolsEditorServices.Test.Transport.Stdio")]
|
||||
[assembly: AssemblyCopyright("Copyright <20> 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("07137FCA-76D0-4CE7-9764-C21DB7A57093")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class CancelTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CancelInProgressQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and execute it
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Constants.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution
|
||||
|
||||
// ... And then I request to cancel the query
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = Constants.OwnerUri};
|
||||
var cancelRequest = new EventFlowValidator<QueryCancelResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
Assert.Null(r.Messages);
|
||||
}).Complete();
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... The query should not have been disposed
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
cancelRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelExecutedQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind) and wait for execution
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams {QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And then I request to cancel the query
|
||||
var cancelParams = new QueryCancelParams {OwnerUri = Constants.OwnerUri};
|
||||
var cancelRequest = new EventFlowValidator<QueryCancelResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
Assert.False(string.IsNullOrWhiteSpace(r.Messages));
|
||||
}).Complete();
|
||||
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... The query should not have been disposed
|
||||
Assert.NotEmpty(queryService.ActiveQueries);
|
||||
cancelRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelNonExistantTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to cancel a query that doesn't exist
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object);
|
||||
|
||||
var cancelParams = new QueryCancelParams { OwnerUri = "Doesn't Exist" };
|
||||
var cancelRequest = new EventFlowValidator<QueryCancelResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
Assert.False(string.IsNullOrWhiteSpace(r.Messages));
|
||||
}).Complete();
|
||||
await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object);
|
||||
cancelRequest.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using HostingProtocol = Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class Common
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const string InvalidQuery = "SELECT *** FROM sys.objects";
|
||||
|
||||
public const string NoOpQuery = "-- No ops here, just us chickens.";
|
||||
|
||||
public const int Ordinal = 100; // We'll pick something other than default(int)
|
||||
|
||||
public const int StandardColumns = 5;
|
||||
|
||||
public const int StandardRows = 5;
|
||||
|
||||
public const string UdtQuery = "SELECT hierarchyid::Parse('/')";
|
||||
|
||||
public const SelectionData WholeDocument = null;
|
||||
|
||||
public static readonly ConnectionDetails StandardConnectionDetails = new ConnectionDetails
|
||||
{
|
||||
DatabaseName = "123",
|
||||
Password = "456",
|
||||
ServerName = "789",
|
||||
UserName = "012"
|
||||
};
|
||||
|
||||
public static readonly SelectionData SubsectionDocument = new SelectionData(0, 0, 2, 2);
|
||||
|
||||
#endregion
|
||||
|
||||
public static TestResultSet StandardTestResultSet => new TestResultSet(StandardColumns, StandardRows);
|
||||
|
||||
public static TestResultSet[] StandardTestDataSet => new[] {StandardTestResultSet};
|
||||
|
||||
public static TestResultSet[] ExecutionPlanTestDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
DbColumn[] columns = { new TestDbColumn("Microsoft SQL Server 2005 XML Showplan") };
|
||||
object[][] rows = { new object[] { "Execution Plan" } };
|
||||
return new[] {new TestResultSet(columns, rows)};
|
||||
}
|
||||
}
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static Batch GetBasicExecutedBatch()
|
||||
{
|
||||
Batch batch = new Batch(Constants.StandardQuery, SubsectionDocument, 1,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
batch.Execute(CreateTestConnection(StandardTestDataSet, false), CancellationToken.None).Wait();
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static Batch GetExecutedBatchWithExecutionPlan()
|
||||
{
|
||||
Batch batch = new Batch(Constants.StandardQuery, SubsectionDocument, 1,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
batch.Execute(CreateTestConnection(ExecutionPlanTestDataSet, false), CancellationToken.None).Wait();
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static Query GetBasicExecutedQuery()
|
||||
{
|
||||
ConnectionInfo ci = CreateConnectedConnectionInfo(StandardTestDataSet, false);
|
||||
|
||||
// Query won't be able to request a new query DbConnection unless the ConnectionService has a
|
||||
// ConnectionInfo with the same URI as the query, so we will manually set it
|
||||
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
||||
|
||||
Query query = new Query(Constants.StandardQuery, ci, new QueryExecutionSettings(),
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
return query;
|
||||
}
|
||||
|
||||
public static Query GetBasicExecutedQuery(QueryExecutionSettings querySettings)
|
||||
{
|
||||
ConnectionInfo ci = CreateConnectedConnectionInfo(StandardTestDataSet, false);
|
||||
|
||||
// Query won't be able to request a new query DbConnection unless the ConnectionService has a
|
||||
// ConnectionInfo with the same URI as the query, so we will manually set it
|
||||
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
||||
|
||||
Query query = new Query(Constants.StandardQuery, ci, querySettings,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
return query;
|
||||
}
|
||||
|
||||
public static TestResultSet[] GetTestDataSet(int dataSets)
|
||||
{
|
||||
return Enumerable.Repeat(StandardTestResultSet, dataSets).ToArray();
|
||||
}
|
||||
|
||||
public static async Task AwaitExecution(QueryExecutionService service, ExecuteDocumentSelectionParams qeParams,
|
||||
HostingProtocol.RequestContext<ExecuteRequestResult> requestContext)
|
||||
{
|
||||
await service.HandleExecuteRequest(qeParams, requestContext);
|
||||
if (service.ActiveQueries.ContainsKey(qeParams.OwnerUri) && service.ActiveQueries[qeParams.OwnerUri].ExecutionTask != null)
|
||||
{
|
||||
await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DbConnection Mocking
|
||||
|
||||
public static DbCommand CreateTestCommand(TestResultSet[] data, bool throwOnRead)
|
||||
{
|
||||
var commandMock = new Mock<DbCommand> { CallBase = true };
|
||||
var commandMockSetup = commandMock.Protected()
|
||||
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
|
||||
|
||||
// Setup the expected behavior
|
||||
if (throwOnRead)
|
||||
{
|
||||
var mockException = new Mock<DbException>();
|
||||
mockException.SetupGet(dbe => dbe.Message).Returns("Message");
|
||||
commandMockSetup.Throws(mockException.Object);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandMockSetup.Returns(new TestDbDataReader(data));
|
||||
}
|
||||
|
||||
|
||||
return commandMock.Object;
|
||||
}
|
||||
|
||||
public static DbConnection CreateTestConnection(TestResultSet[] data, bool throwOnRead)
|
||||
{
|
||||
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
||||
connectionMock.Protected()
|
||||
.Setup<DbCommand>("CreateDbCommand")
|
||||
.Returns(() => CreateTestCommand(data, throwOnRead));
|
||||
connectionMock.Setup(dbc => dbc.Open())
|
||||
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Open));
|
||||
connectionMock.Setup(dbc => dbc.Close())
|
||||
.Callback(() => connectionMock.SetupGet(dbc => dbc.State).Returns(ConnectionState.Closed));
|
||||
|
||||
return connectionMock.Object;
|
||||
}
|
||||
|
||||
public static ISqlConnectionFactory CreateMockFactory(TestResultSet[] data, bool throwOnRead)
|
||||
{
|
||||
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||
.Returns(() => CreateTestConnection(data, throwOnRead));
|
||||
|
||||
return mockFactory.Object;
|
||||
}
|
||||
|
||||
public static ConnectionInfo CreateTestConnectionInfo(TestResultSet[] data, bool throwOnRead)
|
||||
{
|
||||
// Create a connection info and add the default connection to it
|
||||
ISqlConnectionFactory factory = CreateMockFactory(data, throwOnRead);
|
||||
ConnectionInfo ci = new ConnectionInfo(factory, Constants.OwnerUri, StandardConnectionDetails);
|
||||
ci.ConnectionTypeToConnectionMap[ConnectionType.Default] = factory.CreateSqlConnection(null);
|
||||
return ci;
|
||||
}
|
||||
|
||||
public static ConnectionInfo CreateConnectedConnectionInfo(TestResultSet[] data, bool throwOnRead, string type = ConnectionType.Default)
|
||||
{
|
||||
ConnectionService connectionService = ConnectionService.Instance;
|
||||
connectionService.OwnerToConnectionMap.Clear();
|
||||
connectionService.ConnectionFactory = CreateMockFactory(data, throwOnRead);
|
||||
|
||||
ConnectParams connectParams = new ConnectParams
|
||||
{
|
||||
Connection = StandardConnectionDetails,
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
Type = type
|
||||
};
|
||||
|
||||
connectionService.Connect(connectParams).Wait();
|
||||
return connectionService.OwnerToConnectionMap[connectParams.OwnerUri];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Mocking
|
||||
|
||||
public static QueryExecutionService GetPrimedExecutionService(TestResultSet[] data,
|
||||
bool isConnected, bool throwOnRead, WorkspaceService<SqlToolsSettings> workspaceService,
|
||||
out Dictionary<string, byte[]> storage)
|
||||
{
|
||||
// Create a place for the temp "files" to be written
|
||||
storage = new Dictionary<string, byte[]>();
|
||||
|
||||
// Mock the connection service
|
||||
var connectionService = new Mock<ConnectionService>();
|
||||
ConnectionInfo ci = CreateConnectedConnectionInfo(data, throwOnRead);
|
||||
ConnectionInfo outValMock;
|
||||
connectionService
|
||||
.Setup(service => service.TryFindConnection(It.IsAny<string>(), out outValMock))
|
||||
.OutCallback((string owner, out ConnectionInfo connInfo) => connInfo = isConnected ? ci : null)
|
||||
.Returns(isConnected);
|
||||
|
||||
return new QueryExecutionService(connectionService.Object, workspaceService) { BufferFileStreamFactory = MemoryFileSystem.GetFileStreamFactory(storage) };
|
||||
}
|
||||
|
||||
public static QueryExecutionService GetPrimedExecutionService(TestResultSet[] data, bool isConnected, bool throwOnRead, WorkspaceService<SqlToolsSettings> workspaceService)
|
||||
{
|
||||
Dictionary<string, byte[]> storage;
|
||||
return GetPrimedExecutionService(data, isConnected, throwOnRead, workspaceService, out storage);
|
||||
}
|
||||
|
||||
public static WorkspaceService<SqlToolsSettings> GetPrimedWorkspaceService(string query)
|
||||
{
|
||||
// Set up file for returning the query
|
||||
var fileMock = new Mock<ScriptFile>();
|
||||
fileMock.SetupGet(file => file.Contents).Returns(query);
|
||||
|
||||
// Set up workspace mock
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
||||
.Returns(fileMock.Object);
|
||||
|
||||
return workspaceService.Object;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
{
|
||||
public class SaveAsCsvFileStreamWriterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Something\rElse")]
|
||||
[InlineData("Something\nElse")]
|
||||
[InlineData("Something\"Else")]
|
||||
[InlineData("Something,Else")]
|
||||
[InlineData("\tSomething")]
|
||||
[InlineData("Something\t")]
|
||||
[InlineData(" Something")]
|
||||
[InlineData("Something ")]
|
||||
[InlineData(" \t\r\n\",\r\n\"\r ")]
|
||||
public void EncodeCsvFieldShouldWrap(string field)
|
||||
{
|
||||
// If: I CSV encode a field that has forbidden characters in it
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(field);
|
||||
|
||||
// Then: It should wrap it in quotes
|
||||
Assert.True(Regex.IsMatch(output, "^\".*")
|
||||
&& Regex.IsMatch(output, ".*\"$"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Something")]
|
||||
[InlineData("Something valid.")]
|
||||
[InlineData("Something\tvalid")]
|
||||
public void EncodeCsvFieldShouldNotWrap(string field)
|
||||
{
|
||||
// If: I CSV encode a field that does not have forbidden characters in it
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(field);
|
||||
|
||||
// Then: It should not wrap it in quotes
|
||||
Assert.False(Regex.IsMatch(output, "^\".*\"$"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncodeCsvFieldReplace()
|
||||
{
|
||||
// If: I CSV encode a field that has a double quote in it,
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField("Some\"thing");
|
||||
|
||||
// Then: It should be replaced with double double quotes
|
||||
Assert.Equal("\"Some\"\"thing\"", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncodeCsvFieldNull()
|
||||
{
|
||||
// If: I CSV encode a null
|
||||
string output = SaveAsCsvFileStreamWriter.EncodeCsvField(null);
|
||||
|
||||
// Then: there should be a string version of null returned
|
||||
Assert.Equal("NULL", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRowWithoutColumnSelectionOrHeader()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams();
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then: It should write one line with 2 items, comma delimited
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] {Environment.NewLine}, StringSplitOptions.None);
|
||||
Assert.Equal(1, lines.Length);
|
||||
string[] values = lines[0].Split(',');
|
||||
Assert.Equal(2, values.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRowWithHeader()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made, headers should be printed
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
IncludeHeaders = true
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have written two lines
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
Assert.Equal(2, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two, comma separated names
|
||||
string[] headerValues = lines[0].Split(',');
|
||||
Assert.Equal(2, headerValues.Length);
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
Assert.Equal(columns[i].ColumnName, headerValues[i]);
|
||||
}
|
||||
|
||||
// Note: No need to check values, it is done as part of the previous test
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRowWithColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that selects n-1 columns from the front and back
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var requestParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
ColumnStartIndex = 1,
|
||||
ColumnEndIndex = 2,
|
||||
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
||||
RowEndIndex = 10,
|
||||
IncludeHeaders = true // Including headers to test both column selection logic
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1" },
|
||||
new DbCellValue { DisplayValue = "item2" },
|
||||
new DbCellValue { DisplayValue = "item3" },
|
||||
new DbCellValue { DisplayValue = "item4" }
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2")),
|
||||
new DbColumnWrapper(new TestDbColumn("column3")),
|
||||
new DbColumnWrapper(new TestDbColumn("column4"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write a row
|
||||
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... It should have written two lines
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0', '\r', '\n');
|
||||
string[] lines = outputString.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
Assert.Equal(2, lines.Length);
|
||||
|
||||
// ... It should have written a header line with two, comma separated names
|
||||
string[] headerValues = lines[0].Split(',');
|
||||
Assert.Equal(2, headerValues.Length);
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
Assert.Equal(columns[i].ColumnName, headerValues[i-1]);
|
||||
}
|
||||
|
||||
// ... The second line should have two, comma separated values
|
||||
string[] dataValues = lines[1].Split(',');
|
||||
Assert.Equal(2, dataValues.Length);
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
Assert.Equal(data[i].DisplayValue, dataValues[i-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
{
|
||||
public class SaveAsJsonFileStreamWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public void ArrayWrapperTest()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create storage for the output
|
||||
byte[] output = new byte[8192];
|
||||
SaveResultsAsJsonRequestParams saveParams = new SaveResultsAsJsonRequestParams();
|
||||
|
||||
// If:
|
||||
// ... I create and then destruct a json writer
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
jsonWriter.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... The output should be an empty array
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0');
|
||||
object[] outputArray = JsonConvert.DeserializeObject<object[]>(outputString);
|
||||
Assert.Equal(0, outputArray.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRowWithoutColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that has no selection made
|
||||
// ... Create a set of data to write
|
||||
// ... Create storage for the output
|
||||
SaveResultsAsJsonRequestParams saveParams = new SaveResultsAsJsonRequestParams();
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue {DisplayValue = "item1", RawObject = "item1"},
|
||||
new DbCellValue {DisplayValue = "null", RawObject = null}
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If:
|
||||
// ... I write two rows
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
using (jsonWriter)
|
||||
{
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... Upon deserialization to an array of dictionaries
|
||||
string outputString = Encoding.UTF8.GetString(output).TrimEnd('\0');
|
||||
Dictionary<string, string>[] outputObject =
|
||||
JsonConvert.DeserializeObject<Dictionary<string, string>[]>(outputString);
|
||||
|
||||
// ... There should be 2 items in the array,
|
||||
// ... The item should have two fields, and two values, assigned appropriately
|
||||
Assert.Equal(2, outputObject.Length);
|
||||
foreach (var item in outputObject)
|
||||
{
|
||||
Assert.Equal(2, item.Count);
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
Assert.True(item.ContainsKey(columns[i].ColumnName));
|
||||
Assert.Equal(data[i].RawObject == null ? null : data[i].DisplayValue, item[columns[i].ColumnName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRowWithColumnSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a request params that selects n-1 columns from the front and back
|
||||
// ... Create a set of data to write
|
||||
// ... Create a memory location to store the data
|
||||
var saveParams = new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
ColumnStartIndex = 1,
|
||||
ColumnEndIndex = 2,
|
||||
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
||||
RowEndIndex = 10
|
||||
};
|
||||
List<DbCellValue> data = new List<DbCellValue>
|
||||
{
|
||||
new DbCellValue { DisplayValue = "item1", RawObject = "item1"},
|
||||
new DbCellValue { DisplayValue = "item2", RawObject = "item2"},
|
||||
new DbCellValue { DisplayValue = "null", RawObject = null},
|
||||
new DbCellValue { DisplayValue = "null", RawObject = null}
|
||||
};
|
||||
List<DbColumnWrapper> columns = new List<DbColumnWrapper>
|
||||
{
|
||||
new DbColumnWrapper(new TestDbColumn("column1")),
|
||||
new DbColumnWrapper(new TestDbColumn("column2")),
|
||||
new DbColumnWrapper(new TestDbColumn("column3")),
|
||||
new DbColumnWrapper(new TestDbColumn("column4"))
|
||||
};
|
||||
byte[] output = new byte[8192];
|
||||
|
||||
// If: I write two rows
|
||||
var jsonWriter = new SaveAsJsonFileStreamWriter(new MemoryStream(output), saveParams);
|
||||
using (jsonWriter)
|
||||
{
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
jsonWriter.WriteRow(data, columns);
|
||||
}
|
||||
|
||||
// Then:
|
||||
// ... Upon deserialization to an array of dictionaries
|
||||
string outputString = Encoding.UTF8.GetString(output).Trim('\0');
|
||||
Dictionary<string, string>[] outputObject =
|
||||
JsonConvert.DeserializeObject<Dictionary<string, string>[]>(outputString);
|
||||
|
||||
// ... There should be 2 items in the array
|
||||
// ... The items should have 2 fields and values
|
||||
Assert.Equal(2, outputObject.Length);
|
||||
foreach (var item in outputObject)
|
||||
{
|
||||
Assert.Equal(2, item.Count);
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
Assert.True(item.ContainsKey(columns[i].ColumnName));
|
||||
Assert.Equal(data[i].RawObject == null ? null : data[i].DisplayValue, item[columns[i].ColumnName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
{
|
||||
public class ReaderWriterPairTest
|
||||
{
|
||||
[Fact]
|
||||
public void ReaderStreamNull()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with a null stream
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ServiceBufferFileStreamReader(null, new QueryExecutionSettings()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderSettingsNull()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with null settings
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ServiceBufferFileStreamReader(Stream.Null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderInvalidStreamCannotRead()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with a stream that cannot be read
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanRead).Returns(false);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(true);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object, new QueryExecutionSettings());
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReaderInvalidStreamCannotSeek()
|
||||
{
|
||||
// If: I create a service buffer file stream reader with a stream that cannot seek
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanRead).Returns(true);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(false);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamReader obj = new ServiceBufferFileStreamReader(invalidStream.Object, new QueryExecutionSettings());
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterStreamNull()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with a null stream
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ServiceBufferFileStreamWriter(null, new QueryExecutionSettings()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterSettingsNull()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with null settings
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ServiceBufferFileStreamWriter(Stream.Null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterInvalidStreamCannotWrite()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with a stream that cannot be read
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanWrite).Returns(false);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(true);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, new QueryExecutionSettings());
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriterInvalidStreamCannotSeek()
|
||||
{
|
||||
// If: I create a service buffer file stream writer with a stream that cannot seek
|
||||
// Then: I should get an exception
|
||||
var invalidStream = new Mock<Stream>();
|
||||
invalidStream.SetupGet(s => s.CanWrite).Returns(true);
|
||||
invalidStream.SetupGet(s => s.CanSeek).Returns(false);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
ServiceBufferFileStreamWriter obj = new ServiceBufferFileStreamWriter(invalidStream.Object, new QueryExecutionSettings());
|
||||
obj.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
|
||||
private static string VerifyReadWrite<T>(int valueLength, T value,
|
||||
Func<ServiceBufferFileStreamWriter, T, int> writeFunc,
|
||||
Func<ServiceBufferFileStreamReader, FileStreamReadResult> readFunc,
|
||||
QueryExecutionSettings overrideSettings = null)
|
||||
{
|
||||
// Setup: Create a mock file stream
|
||||
byte[] storage = new byte[8192];
|
||||
overrideSettings = overrideSettings ?? new QueryExecutionSettings();
|
||||
|
||||
// If:
|
||||
// ... I write a type T to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(new MemoryStream(storage), overrideSettings))
|
||||
{
|
||||
int writtenBytes = writeFunc(writer, value);
|
||||
Assert.Equal(valueLength, writtenBytes);
|
||||
}
|
||||
|
||||
// ... And read the type T back
|
||||
FileStreamReadResult outValue;
|
||||
using (ServiceBufferFileStreamReader reader = new ServiceBufferFileStreamReader(new MemoryStream(storage), overrideSettings))
|
||||
{
|
||||
outValue = readFunc(reader);
|
||||
}
|
||||
|
||||
// Then:
|
||||
Assert.Equal(value, outValue.Value.RawObject);
|
||||
Assert.Equal(valueLength, outValue.TotalLength);
|
||||
Assert.NotNull(outValue.Value);
|
||||
|
||||
return outValue.Value.DisplayValue;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10)]
|
||||
[InlineData(-10)]
|
||||
[InlineData(short.MaxValue)] // Two byte number
|
||||
[InlineData(short.MinValue)] // Negative two byte number
|
||||
public void Int16(short value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(short) + 1, value, (writer, val) => writer.WriteInt16(val), reader => reader.ReadInt16(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10)]
|
||||
[InlineData(-10)]
|
||||
[InlineData(short.MaxValue)] // Two byte number
|
||||
[InlineData(short.MinValue)] // Negative two byte number
|
||||
[InlineData(int.MaxValue)] // Four byte number
|
||||
[InlineData(int.MinValue)] // Negative four byte number
|
||||
public void Int32(int value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(int) + 1, value, (writer, val) => writer.WriteInt32(val), reader => reader.ReadInt32(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10)]
|
||||
[InlineData(-10)]
|
||||
[InlineData(short.MaxValue)] // Two byte number
|
||||
[InlineData(short.MinValue)] // Negative two byte number
|
||||
[InlineData(int.MaxValue)] // Four byte number
|
||||
[InlineData(int.MinValue)] // Negative four byte number
|
||||
[InlineData(long.MaxValue)] // Eight byte number
|
||||
[InlineData(long.MinValue)] // Negative eight byte number
|
||||
public void Int64(long value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteInt64(val), reader => reader.ReadInt64(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10)]
|
||||
public void Byte(byte value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(byte) + 1, value, (writer, val) => writer.WriteByte(val), reader => reader.ReadByte(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData('a')]
|
||||
[InlineData('1')]
|
||||
[InlineData((char)0x9152)] // Test something in the UTF-16 space
|
||||
public void Char(char value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(char) + 1, value, (writer, val) => writer.WriteChar(val), reader => reader.ReadChar(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(false, false)]
|
||||
public void Boolean(bool value, bool preferNumeric)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(bool) + 1, value,
|
||||
(writer, val) => writer.WriteBoolean(val),
|
||||
reader => reader.ReadBoolean(0),
|
||||
new QueryExecutionSettings {DisplayBitAsNumber = preferNumeric}
|
||||
);
|
||||
|
||||
// Validate the display value
|
||||
if (preferNumeric)
|
||||
{
|
||||
int output;
|
||||
Assert.True(int.TryParse(displayValue, out output));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool output;
|
||||
Assert.True(bool.TryParse(displayValue, out output));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10.1)]
|
||||
[InlineData(-10.1)]
|
||||
[InlineData(float.MinValue)]
|
||||
[InlineData(float.MaxValue)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
public void Single(float value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(float) + 1, value, (writer, val) => writer.WriteSingle(val), reader => reader.ReadSingle(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(10.1)]
|
||||
[InlineData(-10.1)]
|
||||
[InlineData(float.MinValue)]
|
||||
[InlineData(float.MaxValue)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
[InlineData(double.PositiveInfinity)]
|
||||
[InlineData(double.NegativeInfinity)]
|
||||
[InlineData(double.MinValue)]
|
||||
[InlineData(double.MaxValue)]
|
||||
public void Double(double value)
|
||||
{
|
||||
VerifyReadWrite(sizeof(double) + 1, value, (writer, val) => writer.WriteDouble(val), reader => reader.ReadDouble(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SqlDecimalTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because SqlDecimal values can't be written as constant expressions
|
||||
SqlDecimal[] testValues =
|
||||
{
|
||||
SqlDecimal.MaxValue, SqlDecimal.MinValue, new SqlDecimal(0x01, 0x01, true, 0, 0, 0, 0)
|
||||
};
|
||||
foreach (SqlDecimal value in testValues)
|
||||
{
|
||||
int valueLength = 4 + value.BinData.Length;
|
||||
VerifyReadWrite(valueLength, value, (writer, val) => writer.WriteSqlDecimal(val), reader => reader.ReadSqlDecimal(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Decimal()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because Decimal values can't be written as constant expressions
|
||||
decimal[] testValues =
|
||||
{
|
||||
decimal.Zero, decimal.One, decimal.MinusOne, decimal.MinValue, decimal.MaxValue
|
||||
};
|
||||
|
||||
foreach (decimal value in testValues)
|
||||
{
|
||||
int valueLength = decimal.GetBits(value).Length*4 + 1;
|
||||
VerifyReadWrite(valueLength, value, (writer, val) => writer.WriteDecimal(val), reader => reader.ReadDecimal(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
|
||||
// Setup: Create a DATE column
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("col", "DaTe"));
|
||||
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0, col));
|
||||
|
||||
// Make sure the display value does not have a time string
|
||||
Assert.True(Regex.IsMatch(displayValue, @"^[\d]{4}-[\d]{2}-[\d]{2}$"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
|
||||
// Setup: Create a DATETIME column
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("col", "DaTeTiMe"));
|
||||
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0, col));
|
||||
|
||||
// Make sure the display value has a time string with 3 milliseconds
|
||||
Assert.True(Regex.IsMatch(displayValue, @"^[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}\.[\d]{3}$"));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
[InlineData(5)]
|
||||
[InlineData(6)]
|
||||
[InlineData(7)]
|
||||
public void DateTime2Test(int precision)
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
|
||||
// Setup: Create a DATETIME column
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("col", "DaTeTiMe2", precision));
|
||||
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0, col));
|
||||
|
||||
// Make sure the display value has a time string with variable number of milliseconds
|
||||
Assert.True(Regex.IsMatch(displayValue, @"^[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}"));
|
||||
if (precision > 0)
|
||||
{
|
||||
Assert.True(Regex.IsMatch(displayValue, $@"\.[\d]{{{precision}}}$"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTime2ZeroScaleTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
|
||||
// Setup: Create a DATETIME2 column
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("col", "DaTeTiMe2", 0));
|
||||
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0, col));
|
||||
|
||||
// Make sure the display value has a time string with 0 milliseconds
|
||||
Assert.True(Regex.IsMatch(displayValue, @"^[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}$"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTime2InvalidScaleTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions
|
||||
DateTime[] testValues =
|
||||
{
|
||||
DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue
|
||||
};
|
||||
|
||||
// Setup: Create a DATETIME2 column
|
||||
DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("col", "DaTeTiMe2", 255));
|
||||
|
||||
foreach (DateTime value in testValues)
|
||||
{
|
||||
string displayValue = VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0, col));
|
||||
|
||||
// Make sure the display value has a time string with 7 milliseconds
|
||||
Assert.True(Regex.IsMatch(displayValue, @"^[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}\.[\d]{7}$"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeOffsetTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because DateTimeOffset values can't be written as constant expressions
|
||||
DateTimeOffset[] testValues =
|
||||
{
|
||||
DateTimeOffset.Now, DateTimeOffset.UtcNow, DateTimeOffset.MinValue, DateTimeOffset.MaxValue
|
||||
};
|
||||
foreach (DateTimeOffset value in testValues)
|
||||
{
|
||||
VerifyReadWrite(sizeof(long)*2 + 1, value, (writer, val) => writer.WriteDateTimeOffset(val), reader => reader.ReadDateTimeOffset(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimeSpanTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because TimeSpan values can't be written as constant expressions
|
||||
TimeSpan[] testValues =
|
||||
{
|
||||
TimeSpan.Zero, TimeSpan.MinValue, TimeSpan.MaxValue, TimeSpan.FromMinutes(60)
|
||||
};
|
||||
foreach (TimeSpan value in testValues)
|
||||
{
|
||||
VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteTimeSpan(val), reader => reader.ReadTimeSpan(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringNullTest()
|
||||
{
|
||||
// Setup: Create a mock file stream
|
||||
using (MemoryStream stream = new MemoryStream(new byte[8192]))
|
||||
{
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, new QueryExecutionSettings()))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteString(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, null)] // Test of empty string
|
||||
[InlineData(1, new[] { 'j' })]
|
||||
[InlineData(1, new[] { (char)0x9152 })]
|
||||
[InlineData(100, new[] { 'j', (char)0x9152 })] // Test alternating utf-16/ascii characters
|
||||
[InlineData(512, new[] { 'j', (char)0x9152 })] // Test that requires a 4 byte length
|
||||
public void StringTest(int length, char[] values)
|
||||
{
|
||||
// Setup:
|
||||
// ... Generate the test value
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
sb.Append(values[i%values.Length]);
|
||||
}
|
||||
string value = sb.ToString();
|
||||
int lengthLength = length == 0 || length > 255 ? 5 : 1;
|
||||
VerifyReadWrite(sizeof(char)*length + lengthLength, value, (writer, val) => writer.WriteString(value), reader => reader.ReadString(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BytesNullTest()
|
||||
{
|
||||
// Setup: Create a mock file stream wrapper
|
||||
using (MemoryStream stream = new MemoryStream(new byte[8192]))
|
||||
{
|
||||
// If:
|
||||
// ... I write null as a string to the writer
|
||||
using (ServiceBufferFileStreamWriter writer = new ServiceBufferFileStreamWriter(stream, new QueryExecutionSettings()))
|
||||
{
|
||||
// Then:
|
||||
// ... I should get an argument null exception
|
||||
Assert.Throws<ArgumentNullException>(() => writer.WriteBytes(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, new byte[] { 0x00 })] // Test of empty byte[]
|
||||
[InlineData(1, new byte[] { 0x00 })]
|
||||
[InlineData(1, new byte[] { 0xFF })]
|
||||
[InlineData(100, new byte[] { 0x10, 0xFF, 0x00 })]
|
||||
[InlineData(512, new byte[] { 0x10, 0xFF, 0x00 })] // Test that requires a 4 byte length
|
||||
public void Bytes(int length, byte[] values)
|
||||
{
|
||||
// Setup:
|
||||
// ... Generate the test value
|
||||
List<byte> sb = new List<byte>();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
sb.Add(values[i % values.Length]);
|
||||
}
|
||||
byte[] value = sb.ToArray();
|
||||
int lengthLength = length == 0 || length > 255 ? 5 : 1;
|
||||
int valueLength = sizeof(byte)*length + lengthLength;
|
||||
VerifyReadWrite(valueLength, value, (writer, val) => writer.WriteBytes(value), reader => reader.ReadBytes(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GuidTest()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because Guid type can't be written as constant expressions
|
||||
Guid[] guids =
|
||||
{
|
||||
Guid.Empty, Guid.NewGuid(), Guid.NewGuid()
|
||||
};
|
||||
foreach (Guid guid in guids)
|
||||
{
|
||||
VerifyReadWrite(guid.ToByteArray().Length + 1, new SqlGuid(guid), (writer, val) => writer.WriteGuid(guid), reader => reader.ReadGuid(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoneyTest()
|
||||
{
|
||||
// Setup: Create some test values
|
||||
// NOTE: We are doing these here instead of InlineData because SqlMoney can't be written as a constant expression
|
||||
SqlMoney[] monies =
|
||||
{
|
||||
SqlMoney.Zero, SqlMoney.MinValue, SqlMoney.MaxValue, new SqlMoney(1.02)
|
||||
};
|
||||
foreach (SqlMoney money in monies)
|
||||
{
|
||||
VerifyReadWrite(sizeof(decimal) + 1, money, (writer, val) => writer.WriteMoney(money), reader => reader.ReadMoney(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class DisposeTests
|
||||
{
|
||||
[Fact]
|
||||
public void DisposeResultSet()
|
||||
{
|
||||
// Setup: Mock file stream factory, mock db reader
|
||||
var mockFileStreamFactory = new Mock<IFileStreamFactory>();
|
||||
var mockDataReader = Common.CreateTestConnection(null, false).CreateCommand().ExecuteReaderAsync().Result;
|
||||
|
||||
// If: I setup a single resultset and then dispose it
|
||||
ResultSet rs = new ResultSet(mockDataReader, Common.Ordinal, Common.Ordinal, mockFileStreamFactory.Object);
|
||||
rs.Dispose();
|
||||
|
||||
// Then: The file that was created should have been deleted
|
||||
mockFileStreamFactory.Verify(fsf => fsf.DisposeFile(It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeExecutedQuery()
|
||||
{
|
||||
// If:
|
||||
// ... I request a query (doesn't matter what kind)
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams {QuerySelection = null, OwnerUri = Constants.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And then I dispose of the query
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Constants.OwnerUri};
|
||||
var disposeRequest = new EventFlowValidator<QueryDisposeResult>()
|
||||
.AddStandardQueryDisposeValidator()
|
||||
.Complete();
|
||||
await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object);
|
||||
|
||||
// Then:
|
||||
// ... And the active queries should be empty
|
||||
disposeRequest.Validate();
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryDisposeMissingQuery()
|
||||
{
|
||||
// If:
|
||||
// ... I attempt to dispose a query that doesn't exist
|
||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object);
|
||||
var disposeParams = new QueryDisposeParams {OwnerUri = Constants.OwnerUri};
|
||||
|
||||
var disposeRequest = new EventFlowValidator<QueryDisposeResult>()
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object);
|
||||
|
||||
// Then: I should have received an error
|
||||
disposeRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServiceDispose()
|
||||
{
|
||||
// Setup:
|
||||
// ... We need a query service
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
|
||||
// If:
|
||||
// ... I execute some bogus query
|
||||
var queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri };
|
||||
var requestContext = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And it sticks around as an active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
|
||||
// ... The query execution service is disposed, like when the service is shutdown
|
||||
queryService.Dispose();
|
||||
|
||||
// Then:
|
||||
// ... There should no longer be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueryDisposeEventFlowValidatorExtensions
|
||||
{
|
||||
public static EventFlowValidator<QueryDisposeResult> AddStandardQueryDisposeValidator(
|
||||
this EventFlowValidator<QueryDisposeResult> evf)
|
||||
{
|
||||
// We just need to make sure that the result is not null
|
||||
evf.AddResultValidation(Assert.NotNull);
|
||||
|
||||
return evf;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
{
|
||||
public class BatchTests
|
||||
{
|
||||
[Fact]
|
||||
public void BatchCreationTest()
|
||||
{
|
||||
// If I create a new batch...
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... The text of the batch should be stored
|
||||
Assert.NotEmpty(batch.BatchText);
|
||||
|
||||
// ... It should not have executed and no error
|
||||
Assert.False(batch.HasExecuted, "The query should not have executed.");
|
||||
Assert.False(batch.HasError);
|
||||
|
||||
// ... The results should be empty
|
||||
Assert.Empty(batch.ResultSets);
|
||||
Assert.Empty(batch.ResultSummaries);
|
||||
|
||||
// ... The start line of the batch should be 0
|
||||
Assert.Equal(0, batch.Selection.StartLine);
|
||||
|
||||
// ... It's ordinal ID should be what I set it to
|
||||
Assert.Equal(Common.Ordinal, batch.Id);
|
||||
|
||||
// ... The summary should have the same info
|
||||
Assert.Equal(Common.Ordinal, batch.Summary.Id);
|
||||
Assert.Null(batch.Summary.ResultSetSummaries);
|
||||
Assert.Equal(0, batch.Summary.Selection.StartLine);
|
||||
Assert.NotEqual(default(DateTime).ToString("o"), batch.Summary.ExecutionStart); // Should have been set at construction
|
||||
Assert.Null(batch.Summary.ExecutionEnd);
|
||||
Assert.Null(batch.Summary.ExecutionElapsed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteNoResultSets()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of callbacks being called
|
||||
int batchStartCalls = 0;
|
||||
int batchEndCalls = 0;
|
||||
int resultSetCalls = 0;
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// If I execute a query that should get no result sets
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
BatchCallbackHelper(batch,
|
||||
b => batchStartCalls++,
|
||||
b => batchEndCalls++,
|
||||
m => messages.Add(m),
|
||||
r => resultSetCalls++);
|
||||
await batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... Callbacks should have been called the appropriate number of times
|
||||
Assert.Equal(1, batchStartCalls);
|
||||
Assert.Equal(1, batchEndCalls);
|
||||
Assert.Equal(0, resultSetCalls);
|
||||
|
||||
// ... The batch and the summary should be correctly assigned
|
||||
ValidateBatch(batch, 0, false);
|
||||
ValidateBatchSummary(batch);
|
||||
ValidateMessages(batch, 1, messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteOneResultSet()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of callbacks being called
|
||||
int batchStartCalls = 0;
|
||||
int batchEndCalls = 0;
|
||||
int resultSetCalls = 0;
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// ... Build a data set to return
|
||||
const int resultSets = 1;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(Common.GetTestDataSet(resultSets), false);
|
||||
|
||||
// If I execute a query that should get one result set
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
BatchCallbackHelper(batch,
|
||||
b => batchStartCalls++,
|
||||
b => batchEndCalls++,
|
||||
m => messages.Add(m),
|
||||
r => resultSetCalls++);
|
||||
await batch.Execute(GetConnection(ci), CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... Callbacks should have been called the appropriate number of times
|
||||
Assert.Equal(1, batchStartCalls);
|
||||
Assert.Equal(1, batchEndCalls);
|
||||
Assert.Equal(1, resultSetCalls);
|
||||
|
||||
// ... There should be exactly one result set
|
||||
ValidateBatch(batch, resultSets, false);
|
||||
ValidateBatchSummary(batch);
|
||||
ValidateMessages(batch, 1, messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteTwoResultSets()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of callbacks being called
|
||||
int batchStartCalls = 0;
|
||||
int batchEndCalls = 0;
|
||||
int resultSetCalls = 0;
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// ... Build a data set to return
|
||||
const int resultSets = 2;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(Common.GetTestDataSet(resultSets), false);
|
||||
|
||||
// If I execute a query that should get two result sets
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
BatchCallbackHelper(batch,
|
||||
b => batchStartCalls++,
|
||||
b => batchEndCalls++,
|
||||
m => messages.Add(m),
|
||||
r => resultSetCalls++);
|
||||
await batch.Execute(GetConnection(ci), CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... Callbacks should have been called the appropriate number of times
|
||||
Assert.Equal(1, batchStartCalls);
|
||||
Assert.Equal(1, batchEndCalls);
|
||||
Assert.Equal(2, resultSetCalls);
|
||||
|
||||
// ... It should have executed without error
|
||||
ValidateBatch(batch, resultSets, false);
|
||||
ValidateBatchSummary(batch);
|
||||
ValidateMessages(batch, 1, messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteInvalidQuery()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of callbacks being called
|
||||
int batchStartCalls = 0;
|
||||
int batchEndCalls = 0;
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// If I execute a batch that is invalid
|
||||
var ci = Common.CreateTestConnectionInfo(null, true);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
BatchCallbackHelper(batch,
|
||||
b => batchStartCalls++,
|
||||
b => batchEndCalls++,
|
||||
m => messages.Add(m),
|
||||
r => { throw new Exception("ResultSet callback was called when it should not have been."); });
|
||||
await batch.Execute(GetConnection(ci), CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... Callbacks should have been called the appropriate number of times
|
||||
Assert.Equal(1, batchStartCalls);
|
||||
Assert.Equal(1, batchEndCalls);
|
||||
|
||||
// ... It should have executed with error
|
||||
ValidateBatch(batch, 0, true);
|
||||
ValidateBatchSummary(batch);
|
||||
|
||||
// ... There should be one error message returned
|
||||
Assert.Equal(1, messages.Count);
|
||||
Assert.All(messages, m =>
|
||||
{
|
||||
Assert.True(m.IsError);
|
||||
Assert.Equal(batch.Id, m.BatchId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecuteExecuted()
|
||||
{
|
||||
// Setup: Build a data set to return
|
||||
const int resultSets = 1;
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(Common.GetTestDataSet(resultSets), false);
|
||||
|
||||
// If I execute a batch
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory);
|
||||
await batch.Execute(GetConnection(ci), CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... It should have executed without error
|
||||
Assert.True(batch.HasExecuted, "The batch should have been marked executed.");
|
||||
|
||||
// If I execute it again
|
||||
// Then:
|
||||
// ... It should throw an invalid operation exception
|
||||
BatchCallbackHelper(batch,
|
||||
b => { throw new Exception("Batch start callback should not have been called"); },
|
||||
b => { throw new Exception("Batch completion callback should not have been called"); },
|
||||
m => { throw new Exception("Message callback should not have been called"); },
|
||||
null);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => batch.Execute(GetConnection(ci), CancellationToken.None));
|
||||
|
||||
// ... The data should still be available without error
|
||||
ValidateBatch(batch, resultSets, false);
|
||||
ValidateBatchSummary(batch);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void BatchExecuteNoSql(string query)
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has an empty query
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() => new Batch(query, Common.SubsectionDocument, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch that has no file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Batch("stuff", Common.SubsectionDocument, Common.Ordinal, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchInvalidOrdinal()
|
||||
{
|
||||
// If:
|
||||
// ... I create a batch has has an ordinal less than 0
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Batch("stuff", Common.SubsectionDocument, -1, MemoryFileSystem.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatementCompletedHandlerTest()
|
||||
{
|
||||
// If:
|
||||
// ... I call the StatementCompletedHandler
|
||||
Batch batch = new Batch(Constants.StandardQuery, Common.SubsectionDocument, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||
int messageCalls = 0;
|
||||
batch.BatchMessageSent += args =>
|
||||
{
|
||||
messageCalls++;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Then:
|
||||
// ... The message handler for the batch should havve been called twice
|
||||
batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(1));
|
||||
Assert.True(messageCalls == 1);
|
||||
batch.StatementCompletedHandler(null, new StatementCompletedEventArgs(2));
|
||||
Assert.True(messageCalls == 2);
|
||||
}
|
||||
|
||||
private static DbConnection GetConnection(ConnectionInfo info)
|
||||
{
|
||||
return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
|
||||
private static void ValidateBatch(Batch batch, int expectedResultSets, bool isError)
|
||||
{
|
||||
// The batch should be executed
|
||||
Assert.True(batch.HasExecuted, "The query should have been marked executed.");
|
||||
|
||||
// Result set list should never be null
|
||||
Assert.NotNull(batch.ResultSets);
|
||||
Assert.NotNull(batch.ResultSummaries);
|
||||
|
||||
// Make sure the number of result sets matches
|
||||
Assert.Equal(expectedResultSets, batch.ResultSets.Count);
|
||||
Assert.Equal(expectedResultSets, batch.ResultSummaries.Length);
|
||||
|
||||
// Make sure that the error state is set properly
|
||||
Assert.Equal(isError, batch.HasError);
|
||||
}
|
||||
|
||||
private static void ValidateBatchSummary(Batch batch)
|
||||
{
|
||||
BatchSummary batchSummary = batch.Summary;
|
||||
|
||||
Assert.NotNull(batchSummary);
|
||||
Assert.Equal(batch.Id, batchSummary.Id);
|
||||
Assert.Equal(batch.ResultSets.Count, batchSummary.ResultSetSummaries.Length);
|
||||
Assert.Equal(batch.Selection, batchSummary.Selection);
|
||||
Assert.Equal(batch.HasError, batchSummary.HasError);
|
||||
|
||||
// Something other than default date is provided for start and end times
|
||||
Assert.True(DateTime.Parse(batchSummary.ExecutionStart) > default(DateTime));
|
||||
Assert.True(DateTime.Parse(batchSummary.ExecutionEnd) > default(DateTime));
|
||||
Assert.NotNull(batchSummary.ExecutionElapsed);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
|
||||
private static void ValidateMessages(Batch batch, int expectedMessages, IList<ResultMessage> messages)
|
||||
{
|
||||
// There should be equal number of messages to result sets
|
||||
Assert.Equal(expectedMessages, messages.Count);
|
||||
|
||||
// No messages should be errors
|
||||
// All messages must have the batch ID
|
||||
Assert.All(messages, m =>
|
||||
{
|
||||
Assert.False(m.IsError);
|
||||
Assert.Equal(batch.Id, m.BatchId);
|
||||
});
|
||||
}
|
||||
|
||||
private static void BatchCallbackHelper(Batch batch, Action<Batch> startCallback, Action<Batch> endCallback,
|
||||
Action<ResultMessage> messageCallback, Action<ResultSet> resultCallback)
|
||||
{
|
||||
// Setup the callback for batch start
|
||||
batch.BatchStart += b =>
|
||||
{
|
||||
startCallback?.Invoke(b);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Setup the callback for batch completion
|
||||
batch.BatchCompletion += b =>
|
||||
{
|
||||
endCallback?.Invoke(b);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Setup the callback for batch messages
|
||||
batch.BatchMessageSent += (m) =>
|
||||
{
|
||||
messageCallback?.Invoke(m);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Setup the result set completion callback
|
||||
batch.ResultSetCompletion += r =>
|
||||
{
|
||||
resultCallback?.Invoke(r);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
{
|
||||
/// <summary>
|
||||
/// DbColumnWrapper tests
|
||||
/// </summary>
|
||||
public class DbColumnWrapperTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test DbColumn derived class
|
||||
/// </summary>
|
||||
private class TestColumn : DbColumn
|
||||
{
|
||||
public TestColumn(
|
||||
string dataTypeName = null,
|
||||
int? columnSize = null,
|
||||
string columnName = null,
|
||||
string udtAssemblyQualifiedName = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dataTypeName))
|
||||
{
|
||||
this.DataTypeName = dataTypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.DataTypeName = "int";
|
||||
this.DataType = typeof(int);
|
||||
}
|
||||
|
||||
if (columnSize.HasValue)
|
||||
{
|
||||
this.ColumnSize = columnSize;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(columnName))
|
||||
{
|
||||
this.ColumnName = columnName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(udtAssemblyQualifiedName))
|
||||
{
|
||||
this.UdtAssemblyQualifiedName = udtAssemblyQualifiedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic data type and properties test
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DataTypeAndPropertiesTest()
|
||||
{
|
||||
// check default constructor doesn't throw
|
||||
Assert.NotNull(new DbColumnWrapper());
|
||||
|
||||
// check various properties are either null or not null
|
||||
var column = new TestColumn();
|
||||
var wrapper = new DbColumnWrapper(column);
|
||||
Assert.NotNull(wrapper.DataTypeName);
|
||||
Assert.NotNull(wrapper.DataType);
|
||||
Assert.Null(wrapper.AllowDBNull);
|
||||
Assert.Null(wrapper.BaseCatalogName);
|
||||
Assert.Null(wrapper.BaseColumnName);
|
||||
Assert.Null(wrapper.BaseServerName);
|
||||
Assert.Null(wrapper.BaseTableName);
|
||||
Assert.Null(wrapper.ColumnOrdinal);
|
||||
Assert.Null(wrapper.ColumnSize);
|
||||
Assert.Null(wrapper.IsAliased);
|
||||
Assert.Null(wrapper.IsAutoIncrement);
|
||||
Assert.Null(wrapper.IsExpression);
|
||||
Assert.Null(wrapper.IsHidden);
|
||||
Assert.Null(wrapper.IsIdentity);
|
||||
Assert.Null(wrapper.IsKey);
|
||||
Assert.Null(wrapper.IsReadOnly);
|
||||
Assert.Null(wrapper.IsUnique);
|
||||
Assert.Null(wrapper.NumericPrecision);
|
||||
Assert.Null(wrapper.NumericScale);
|
||||
Assert.Null(wrapper.UdtAssemblyQualifiedName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// constructor test
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DbColumnConstructorTests()
|
||||
{
|
||||
// check that various constructor parameters initial the wrapper correctly
|
||||
var w1 = new DbColumnWrapper(new TestColumn("varchar", int.MaxValue, "Microsoft SQL Server 2005 XML Showplan"));
|
||||
Assert.True(w1.IsXml);
|
||||
|
||||
var w2 = new DbColumnWrapper(new TestColumn("binary"));
|
||||
Assert.True(w2.IsBytes);
|
||||
|
||||
var w3 = new DbColumnWrapper(new TestColumn("varbinary", int.MaxValue));
|
||||
Assert.True(w3.IsBytes);
|
||||
|
||||
var w4 = new DbColumnWrapper(new TestColumn("sql_variant"));
|
||||
Assert.True(w4.IsSqlVariant);
|
||||
|
||||
var w5 = new DbColumnWrapper(new TestColumn("my_udt"));
|
||||
Assert.True(w5.IsUdt);
|
||||
|
||||
var w6 = new DbColumnWrapper(new TestColumn("my_hieracrchy", null, null, "MICROSOFT.SQLSERVER.TYPES.SQLHIERARCHYID"));
|
||||
Assert.True(w6.IsUdt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
{
|
||||
public class QueryTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void QueryCreationCorrect()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(Constants.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
|
||||
// Then:
|
||||
// ... I should get back two batches to execute that haven't been executed
|
||||
Assert.NotEmpty(query.QueryText);
|
||||
Assert.False(query.HasExecuted);
|
||||
Assert.Throws<InvalidOperationException>(() => query.BatchSummaries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoQueryText()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null query text
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), MemoryFileSystem.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoConnectionInfo()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null connection info
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new Query("Some Query", null, new QueryExecutionSettings(), MemoryFileSystem.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoSettings()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null settings
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), null, MemoryFileSystem.GetFileStreamFactory()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteNoBufferFactory()
|
||||
{
|
||||
// If:
|
||||
// ... I create a query that has a null file stream factory
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new Query("Some query", Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteSingleBatch()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of how many times the callbacks were called
|
||||
int batchStartCallbacksReceived = 0;
|
||||
int batchCompleteCallbacksReceived = 0;
|
||||
int batchMessageCallbacksReceived = 0;
|
||||
|
||||
// If:
|
||||
// ... I create a query from a single batch (without separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(Constants.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => batchStartCallbacksReceived++,
|
||||
b => batchCompleteCallbacksReceived++,
|
||||
m => batchMessageCallbacksReceived++);
|
||||
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... There should be exactly 1 batch
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
|
||||
// ... The query should have completed successfully with one batch summary returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
|
||||
// ... The batch callbacks should have been called precisely 1 time
|
||||
Assert.Equal(1, batchStartCallbacksReceived);
|
||||
Assert.Equal(1, batchCompleteCallbacksReceived);
|
||||
Assert.Equal(1, batchMessageCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleNoOpBatch()
|
||||
{
|
||||
// Setup: Keep track of all the messages received
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// If:
|
||||
// ... I create a query from a single batch that does nothing
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => { throw new Exception("Batch startup callback should not have been called."); },
|
||||
b => { throw new Exception("Batch completion callback was called"); },
|
||||
m => messages.Add(m));
|
||||
|
||||
// If:
|
||||
// ... I Then execute the query
|
||||
query.Execute();
|
||||
await query.ExecutionTask;
|
||||
|
||||
// Then:
|
||||
// ... There should be no batches
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
|
||||
// ... The query shouldn't have completed successfully
|
||||
Assert.False(query.HasExecuted);
|
||||
|
||||
// ... The message callback should have been called 0 times
|
||||
Assert.Equal(0, messages.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteMultipleResultBatches()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of how many callbacks are received
|
||||
int batchStartCallbacksReceived = 0;
|
||||
int batchCompletedCallbacksReceived = 0;
|
||||
int batchMessageCallbacksReceived = 0;
|
||||
|
||||
// If:
|
||||
// ... I create a query from two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateConnectedConnectionInfo(null, false);
|
||||
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{0}", Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => batchStartCallbacksReceived++,
|
||||
b => batchCompletedCallbacksReceived++,
|
||||
m => batchMessageCallbacksReceived++);
|
||||
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with one batch (no op batch is not included)
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(2, query.Batches.Length);
|
||||
|
||||
// ... The query should have completed successfully with two batch summaries returned
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(2, query.BatchSummaries.Length);
|
||||
|
||||
// ... The batch start, complete, and message callbacks should have been called precisely 2 times
|
||||
Assert.Equal(2, batchStartCallbacksReceived);
|
||||
Assert.Equal(2, batchCompletedCallbacksReceived);
|
||||
Assert.Equal(2, batchMessageCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMultipleBatchesWithNoOp()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of how many times callbacks are called
|
||||
int batchStartCallbacksReceived = 0;
|
||||
int batchCompletionCallbacksReceived = 0;
|
||||
int batchMessageCallbacksReceived = 0;
|
||||
|
||||
// If:
|
||||
// ... I create a query from a two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateConnectedConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{1}", Constants.StandardQuery, Common.NoOpQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => batchStartCallbacksReceived++,
|
||||
b => batchCompletionCallbacksReceived++,
|
||||
m => batchMessageCallbacksReceived++);
|
||||
|
||||
// .. I then execute the query
|
||||
query.Execute();
|
||||
await query.ExecutionTask;
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with two batches
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(2, query.Batches.Length);
|
||||
|
||||
// ... The query should have completed successfully
|
||||
Assert.True(query.HasExecuted);
|
||||
|
||||
// ... The batch callbacks should have been called 2 times (for each no op batch)
|
||||
Assert.Equal(2, batchStartCallbacksReceived);
|
||||
Assert.Equal(2, batchCompletionCallbacksReceived);
|
||||
Assert.Equal(2, batchMessageCallbacksReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMultipleNoOpBatches()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of how many messages were sent
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// If:
|
||||
// ... I create a query from a two batches (with separator)
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false);
|
||||
string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.NoOpQuery, Common.NoOpQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => { throw new Exception("Batch start handler was called"); },
|
||||
b => { throw new Exception("Batch completed handler was called"); },
|
||||
m => messages.Add(m));
|
||||
|
||||
// .. I then execute the query
|
||||
query.Execute();
|
||||
await query.ExecutionTask;
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with no batches
|
||||
Assert.Equal(2, query.Batches.Length);
|
||||
|
||||
// ... The query shouldn't have completed successfully
|
||||
Assert.False(query.HasExecuted);
|
||||
|
||||
// ... The message callback should have been called exactly once
|
||||
Assert.Equal(0, messages.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryExecuteInvalidBatch()
|
||||
{
|
||||
// Setup:
|
||||
// ... Keep track of how many times a method is called
|
||||
int batchStartCallbacksReceived = 0;
|
||||
int batchCompletionCallbacksReceived = 0;
|
||||
List<ResultMessage> messages = new List<ResultMessage>();
|
||||
|
||||
// If:
|
||||
// ... I create a query from an invalid batch
|
||||
ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true);
|
||||
ConnectionService.Instance.OwnerToConnectionMap[ci.OwnerUri] = ci;
|
||||
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), fileStreamFactory);
|
||||
BatchCallbackHelper(query,
|
||||
b => batchStartCallbacksReceived++,
|
||||
b => batchCompletionCallbacksReceived++,
|
||||
m => messages.Add(m));
|
||||
|
||||
// ... I then execute the query
|
||||
query.Execute();
|
||||
query.ExecutionTask.Wait();
|
||||
|
||||
// Then:
|
||||
// ... I should get back a query with one batch
|
||||
Assert.NotEmpty(query.Batches);
|
||||
Assert.Equal(1, query.Batches.Length);
|
||||
|
||||
// ... There should be an error on the batch
|
||||
Assert.True(query.HasExecuted);
|
||||
Assert.NotEmpty(query.BatchSummaries);
|
||||
Assert.Equal(1, query.BatchSummaries.Length);
|
||||
Assert.True(messages.Any(m => m.IsError));
|
||||
|
||||
// ... The batch callbacks should have been called once
|
||||
Assert.Equal(1, batchStartCallbacksReceived);
|
||||
Assert.Equal(1, batchCompletionCallbacksReceived);
|
||||
}
|
||||
|
||||
private static void BatchCallbackHelper(Query q, Action<Batch> startCallback, Action<Batch> endCallback,
|
||||
Action<ResultMessage> messageCallback)
|
||||
{
|
||||
// Setup the callback for batch start
|
||||
q.BatchStarted += b =>
|
||||
{
|
||||
startCallback?.Invoke(b);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Setup the callback for batch completion
|
||||
q.BatchCompleted += b =>
|
||||
{
|
||||
endCallback?.Invoke(b);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Setup the callback for batch messages
|
||||
q.BatchMessageSent += (m) =>
|
||||
{
|
||||
messageCallback?.Invoke(m);
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
{
|
||||
public class ResultSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResultCreation()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
DbDataReader mockReader = GetReader(null, false, string.Empty);
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||
|
||||
// Then:
|
||||
// ... There should not be any data read yet
|
||||
Assert.Null(resultSet.Columns);
|
||||
Assert.Equal(0, resultSet.RowCount);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Id);
|
||||
|
||||
// ... The summary should include the same info
|
||||
Assert.Null(resultSet.Summary.ColumnInfo);
|
||||
Assert.Equal(0, resultSet.Summary.RowCount);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Summary.Id);
|
||||
Assert.Equal(Common.Ordinal, resultSet.Summary.BatchId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResultCreationInvalidReader()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set without a reader
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.Throws<ArgumentNullException>(() => new ResultSet(null, Common.Ordinal, Common.Ordinal, null));
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadToEndSuccess()
|
||||
{
|
||||
// Setup: Create a callback for resultset completion
|
||||
ResultSetSummary resultSummaryFromCallback = null;
|
||||
ResultSet.ResultSetAsyncEventHandler callback = r =>
|
||||
{
|
||||
resultSummaryFromCallback = r.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a new resultset with a valid db data reader that has data
|
||||
// ... and I read it to the end
|
||||
DbDataReader mockReader = GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
resultSet.ResultCompletion += callback;
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... The columns should be set
|
||||
// ... There should be rows to read back
|
||||
Assert.NotNull(resultSet.Columns);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Columns.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.RowCount);
|
||||
|
||||
// ... The summary should have the same info
|
||||
Assert.NotNull(resultSet.Summary.ColumnInfo);
|
||||
Assert.Equal(Common.StandardColumns, resultSet.Summary.ColumnInfo.Length);
|
||||
Assert.Equal(Common.StandardRows, resultSet.Summary.RowCount);
|
||||
|
||||
// ... The callback for result set completion should have been fired
|
||||
Assert.NotNull(resultSummaryFromCallback);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("JSON")]
|
||||
[InlineData("XML")]
|
||||
public async Task ReadToEndForXmlJson(string forType)
|
||||
{
|
||||
// Setup:
|
||||
// ... Build a FOR XML or FOR JSON data set
|
||||
DbColumn[] columns = {new TestDbColumn(string.Format("{0}_F52E2B61-18A1-11d1-B105-00805F49916B", forType))};
|
||||
object[][] rows = Enumerable.Repeat(new object[] {"test data"}, Common.StandardRows).ToArray();
|
||||
TestResultSet[] dataSets = {new TestResultSet(columns, rows) };
|
||||
|
||||
// ... Create a callback for resultset completion
|
||||
ResultSetSummary resultSummary = null;
|
||||
ResultSet.ResultSetAsyncEventHandler callback = r =>
|
||||
{
|
||||
resultSummary = r.Summary;
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// If:
|
||||
// ... I create a new resultset with a valid db data reader that is FOR XML/JSON
|
||||
// ... and I read it to the end
|
||||
DbDataReader mockReader = GetReader(dataSets, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
resultSet.ResultCompletion += callback;
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// Then:
|
||||
// ... There should only be one column
|
||||
// ... There should only be one row
|
||||
// ... The result should be marked as complete
|
||||
Assert.Equal(1, resultSet.Columns.Length);
|
||||
Assert.Equal(1, resultSet.RowCount);
|
||||
|
||||
// ... The callback should have been called
|
||||
Assert.NotNull(resultSummary);
|
||||
|
||||
// If:
|
||||
// ... I attempt to read back the results
|
||||
// Then:
|
||||
// ... I should only get one row
|
||||
var subset = await resultSet.GetSubset(0, 10);
|
||||
Assert.Equal(1, subset.RowCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSubsetWithoutExecution()
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader without executing it
|
||||
DbDataReader mockReader = GetReader(null, false, string.Empty);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
|
||||
// Then:
|
||||
// ... Attempting to read a subset should fail miserably
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => resultSet.GetSubset(0, 0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 0)] // Too small start row
|
||||
[InlineData(20, 0)] // Too large start row
|
||||
[InlineData(0, -1)] // Negative row count
|
||||
public async Task GetSubsetInvalidParameters(int startRow, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
// ... And execute the result
|
||||
DbDataReader mockReader = GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... And attempt to get a subset with invalid parameters
|
||||
// Then:
|
||||
// ... It should throw an exception for an invalid parameter
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => resultSet.GetSubset(startRow, rowCount));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 3)] // Standard scenario, 3 rows should come back
|
||||
[InlineData(0, 20)] // Asking for too many rows, 5 rows should come back
|
||||
[InlineData(1, 3)] // Standard scenario from non-zero start
|
||||
[InlineData(1, 20)] // Asking for too many rows at a non-zero start
|
||||
public async Task GetSubsetSuccess(int startRow, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// ... I create a new result set with a valid db data reader
|
||||
// ... And execute the result set
|
||||
DbDataReader mockReader = GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery);
|
||||
var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
ResultSet resultSet = new ResultSet(mockReader, Common.Ordinal, Common.Ordinal, fileStreamFactory);
|
||||
await resultSet.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... And attempt to get a subset with valid number of rows
|
||||
ResultSetSubset subset = await resultSet.GetSubset(startRow, rowCount);
|
||||
|
||||
// Then:
|
||||
// ... There should be rows in the subset, either the number of rows or the number of
|
||||
// rows requested or the number of rows in the result set, whichever is lower
|
||||
long availableRowsFromStart = resultSet.RowCount - startRow;
|
||||
Assert.Equal(Math.Min(availableRowsFromStart, rowCount), subset.RowCount);
|
||||
|
||||
// ... The rows should have the same number of columns as the resultset
|
||||
Assert.Equal(resultSet.Columns.Length, subset.Rows[0].Length);
|
||||
}
|
||||
|
||||
private static DbDataReader GetReader(TestResultSet[] dataSet, bool throwOnRead, string query)
|
||||
{
|
||||
var info = Common.CreateTestConnectionInfo(dataSet, throwOnRead);
|
||||
var connection = info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = query;
|
||||
return command.ExecuteReader();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
|
||||
{
|
||||
public class ServiceIntegrationTests
|
||||
{
|
||||
|
||||
#region Get SQL Tests
|
||||
|
||||
[Fact]
|
||||
public void GetSqlTextFromDocumentRequestFull()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a workspace service with a multi-line constructed query
|
||||
// ... Create a query execution service without a connection service (we won't be
|
||||
// executing queries), and the previously created workspace service
|
||||
string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine);
|
||||
var workspaceService = GetDefaultWorkspaceService(query);
|
||||
var queryService = new QueryExecutionService(null, workspaceService);
|
||||
|
||||
// If: I attempt to get query text from execute document params (entire document)
|
||||
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
var queryText = queryService.GetSqlText(queryParams);
|
||||
|
||||
// Then: The text should match the constructed query
|
||||
Assert.Equal(query, queryText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSqlTextFromDocumentRequestPartial()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a workspace service with a multi-line constructed query
|
||||
string query = string.Format("{0}{1}GO{1}{0}", Constants.StandardQuery, Environment.NewLine);
|
||||
var workspaceService = GetDefaultWorkspaceService(query);
|
||||
var queryService = new QueryExecutionService(null, workspaceService);
|
||||
|
||||
// If: I attempt to get query text from execute document params (partial document)
|
||||
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.SubsectionDocument};
|
||||
var queryText = queryService.GetSqlText(queryParams);
|
||||
|
||||
// Then: The text should be a subset of the constructed query
|
||||
Assert.Contains(queryText, query);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSqlTextFromStringRequest()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a query execution service without a connection service or workspace
|
||||
// service (we won't execute code that uses either
|
||||
var queryService = new QueryExecutionService(null, null);
|
||||
|
||||
// If: I attempt to get query text from execute string params
|
||||
var queryParams = new ExecuteStringParams {OwnerUri = Constants.OwnerUri, Query = Constants.StandardQuery};
|
||||
var queryText = queryService.GetSqlText(queryParams);
|
||||
|
||||
// Then: The text should match the standard query
|
||||
Assert.Equal(Constants.StandardQuery, queryText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSqlTextFromInvalidType()
|
||||
{
|
||||
// Setup:
|
||||
// ... Mock up an implementation of ExecuteRequestParamsBase
|
||||
// ... Create a query execution service without a connection service or workspace
|
||||
// service (we won't execute code that uses either
|
||||
var mockParams = new Mock<ExecuteRequestParamsBase>().Object;
|
||||
var queryService = new QueryExecutionService(null, null);
|
||||
|
||||
// If: I attempt to get query text from the mock params
|
||||
// Then: It should throw an exception
|
||||
Assert.Throws<InvalidCastException>(() => queryService.GetSqlText(mockParams));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inter-Service API Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InterServiceExecuteNullExecuteParams()
|
||||
{
|
||||
// Setup: Create a query service
|
||||
var qes = new QueryExecutionService(null, null);
|
||||
var eventSender = new EventFlowValidator<ExecuteRequestResult>().Complete().Object;
|
||||
|
||||
|
||||
// If: I call the inter-service API to execute with a null execute params
|
||||
// Then: It should throw
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => qes.InterServiceExecuteQuery(null, eventSender, null, null, null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterServiceExecuteNullEventSender()
|
||||
{
|
||||
// Setup: Create a query service, and execute params
|
||||
var qes = new QueryExecutionService(null, null);
|
||||
var executeParams = new ExecuteStringParams();
|
||||
|
||||
// If: I call the inter-service API to execute a query with a a null event sender
|
||||
// Then: It should throw
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => qes.InterServiceExecuteQuery(executeParams, null, null, null, null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterServiceDisposeNullSuccessFunc()
|
||||
{
|
||||
// Setup: Create a query service and dispose params
|
||||
var qes = new QueryExecutionService(null, null);
|
||||
Func<string, Task> failureFunc = Task.FromResult;
|
||||
|
||||
// If: I call the inter-service API to dispose a query with a null success function
|
||||
// Then: It should throw
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => qes.InterServiceDisposeQuery(Constants.OwnerUri, null, failureFunc));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterServiceDisposeNullFailureFunc()
|
||||
{
|
||||
// Setup: Create a query service and dispose params
|
||||
var qes = new QueryExecutionService(null, null);
|
||||
Func<Task> successFunc = () => Task.FromResult(0);
|
||||
|
||||
// If: I call the inter-service API to dispose a query with a null success function
|
||||
// Then: It should throw
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => qes.InterServiceDisposeQuery(Constants.OwnerUri, successFunc, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Execution Tests
|
||||
// NOTE: In order to limit test duplication, we're running the ExecuteDocumentSelection
|
||||
// version of execute query. The code paths are almost identical.
|
||||
|
||||
[Fact]
|
||||
private async Task QueryExecuteAllBatchesNoOp()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a valid query with all batches as no op
|
||||
var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.NoOpQuery));
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri };
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddEventValidation(QueryCompleteEvent.Type, p =>
|
||||
{
|
||||
// Validate OwnerURI matches
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.BatchSummaries);
|
||||
Assert.Equal(2, p.BatchSummaries.Length);
|
||||
Assert.All(p.BatchSummaries, bs => Assert.Equal(0, bs.ResultSetSummaries.Length));
|
||||
}).Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchNoResultsTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a valid query with no results
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { QuerySelection = Common.WholeDocument, OwnerUri = Constants.OwnerUri};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardQueryCompleteValidator(1)
|
||||
.Complete();
|
||||
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchSingleResultTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a valid query with results
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false,
|
||||
workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardResultSetValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardQueryCompleteValidator(1)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteSingleBatchMultipleResultTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a valid query with one batch and multiple result sets
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var dataset = new[] {Common.StandardTestResultSet, Common.StandardTestResultSet};
|
||||
var queryService = Common.GetPrimedExecutionService(dataset, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardResultSetValidator()
|
||||
.AddStandardResultSetValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardQueryCompleteValidator(1)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMultipleBatchSingleResultTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request a to execute a valid query with multiple batches
|
||||
var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Constants.StandardQuery));
|
||||
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardResultSetValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardResultSetValidator()
|
||||
.AddStandardMessageValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardQueryCompleteValidator(2)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteUnconnectedUriTest()
|
||||
{
|
||||
// Given:
|
||||
// If:
|
||||
// ... I request to execute a query using a file URI that isn't connected
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument };
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should be no active queries
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteInProgressTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query without waiting for the first to complete
|
||||
queryService.ActiveQueries[Constants.OwnerUri].HasExecuted = false; // Simulate query hasn't finished
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should only be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteCompletedTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a query
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
// Note, we don't care about the results of the first request
|
||||
var firstRequestContext = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object);
|
||||
|
||||
// ... And then I request another query after waiting for the first to complete
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardQueryCompleteValidator(1)
|
||||
.Complete();
|
||||
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... All events should have been called as per their flow validator
|
||||
efv.Validate();
|
||||
|
||||
// ... There should only be one active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteMissingSelectionTest()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace with a standard query is configured
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(string.Empty);
|
||||
|
||||
// If:
|
||||
// ... I request to execute a query with a missing query string
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = null};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddErrorValidation<string>(Assert.NotEmpty)
|
||||
.Complete();
|
||||
await queryService.HandleExecuteRequest(queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... Am error should have been sent
|
||||
efv.Validate();
|
||||
|
||||
// ... There should not be an active query
|
||||
Assert.Empty(queryService.ActiveQueries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueryExecuteInvalidQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I request to execute a query that is invalid
|
||||
var workspaceService = GetDefaultWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, true, workspaceService);
|
||||
var queryParams = new ExecuteDocumentSelectionParams {OwnerUri = Constants.OwnerUri, QuerySelection = Common.WholeDocument};
|
||||
|
||||
var efv = new EventFlowValidator<ExecuteRequestResult>()
|
||||
.AddStandardQueryResultValidator()
|
||||
.AddStandardBatchStartValidator()
|
||||
.AddStandardBatchCompleteValidator()
|
||||
.AddStandardQueryCompleteValidator(1)
|
||||
.Complete();
|
||||
await Common.AwaitExecution(queryService, queryParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... Am error should have been sent
|
||||
efv.Validate();
|
||||
|
||||
// ... There should not be an active query
|
||||
Assert.Equal(1, queryService.ActiveQueries.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static WorkspaceService<SqlToolsSettings> GetDefaultWorkspaceService(string query)
|
||||
{
|
||||
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(query);
|
||||
return workspaceService;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueryExecutionEventFlowValidatorExtensions
|
||||
{
|
||||
public static EventFlowValidator<ExecuteRequestResult> AddStandardQueryResultValidator(
|
||||
this EventFlowValidator<ExecuteRequestResult> efv)
|
||||
{
|
||||
// We just need to makes sure we get a result back, there's no params to validate
|
||||
return efv.AddResultValidation(Assert.NotNull);
|
||||
}
|
||||
|
||||
public static EventFlowValidator<TRequestContext> AddStandardBatchStartValidator<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv)
|
||||
{
|
||||
return efv.AddEventValidation(BatchStartEvent.Type, p =>
|
||||
{
|
||||
// Validate OwnerURI and batch summary is returned
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.BatchSummary);
|
||||
});
|
||||
}
|
||||
|
||||
public static EventFlowValidator<TRequestContext> AddStandardBatchCompleteValidator<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv)
|
||||
{
|
||||
return efv.AddEventValidation(BatchCompleteEvent.Type, p =>
|
||||
{
|
||||
// Validate OwnerURI and result summary are returned
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.BatchSummary);
|
||||
});
|
||||
}
|
||||
|
||||
public static EventFlowValidator<TRequestContext> AddStandardMessageValidator<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv)
|
||||
{
|
||||
return efv.AddEventValidation(MessageEvent.Type, p =>
|
||||
{
|
||||
// Validate OwnerURI and message are returned
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.Message);
|
||||
});
|
||||
}
|
||||
|
||||
public static EventFlowValidator<TRequestContext> AddStandardResultSetValidator<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv)
|
||||
{
|
||||
return efv.AddEventValidation(ResultSetCompleteEvent.Type, p =>
|
||||
{
|
||||
// Validate OwnerURI and summary are returned
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.ResultSetSummary);
|
||||
});
|
||||
}
|
||||
|
||||
public static EventFlowValidator<TRequestContext> AddStandardQueryCompleteValidator<TRequestContext>(
|
||||
this EventFlowValidator<TRequestContext> efv, int expectedBatches)
|
||||
{
|
||||
return efv.AddEventValidation(QueryCompleteEvent.Type, p =>
|
||||
{
|
||||
Assert.Equal(Constants.OwnerUri, p.OwnerUri);
|
||||
Assert.NotNull(p.BatchSummaries);
|
||||
Assert.Equal(expectedBatches, p.BatchSummaries.Length);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class ExecutionPlanTests
|
||||
{
|
||||
#region ResultSet Class Tests
|
||||
|
||||
[Fact]
|
||||
public void ExecutionPlanValid()
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a batch that has been executed with a execution plan
|
||||
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||
|
||||
// If:
|
||||
// ... I have a result set and I ask for a valid execution plan
|
||||
ResultSet planResultSet = b.ResultSets.First();
|
||||
ExecutionPlan plan = planResultSet.GetExecutionPlan().Result;
|
||||
|
||||
// Then:
|
||||
// ... I should get the execution plan back
|
||||
Assert.Equal("xml", plan.Format);
|
||||
Assert.Contains("Execution Plan", plan.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecutionPlanInvalid()
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a batch that has been executed
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// If:
|
||||
// ... I have a result set and I ask for an execution plan that doesn't exist
|
||||
ResultSet planResultSet = b.ResultSets.First();
|
||||
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
await Assert.ThrowsAsync<Exception>(() => planResultSet.GetExecutionPlan());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Batch Class Tests
|
||||
|
||||
[Fact]
|
||||
public void BatchExecutionPlanValidTest()
|
||||
{
|
||||
// If I have an executed batch which has an execution plan
|
||||
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||
|
||||
// ... And I ask for a valid execution plan
|
||||
ExecutionPlan plan = b.GetExecutionPlan(0).Result;
|
||||
|
||||
// Then:
|
||||
// ... I should get the execution plan back
|
||||
Assert.Equal("xml", plan.Format);
|
||||
Assert.Contains("Execution Plan", plan.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BatchExecutionPlanInvalidTest()
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a batch that has been executed without an execution plan
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// If:
|
||||
// ... I ask for an invalid execution plan
|
||||
await Assert.ThrowsAsync<Exception>(() => b.GetExecutionPlan(0));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Invalid result set, too low
|
||||
[InlineData(2)] // Invalid result set, too high
|
||||
public async Task BatchExecutionPlanInvalidParamsTest(int resultSetIndex)
|
||||
{
|
||||
// If I have an executed batch which has an execution plan
|
||||
Batch b = Common.GetExecutedBatchWithExecutionPlan();
|
||||
|
||||
// ... And I ask for an execution plan with an invalid result set index
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => b.GetExecutionPlan(resultSetIndex));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Invalid batch, too low
|
||||
[InlineData(2)] // Invalid batch, too high
|
||||
public async Task QueryExecutionPlanInvalidParamsTest(int batchIndex)
|
||||
{
|
||||
// Setup query settings
|
||||
QueryExecutionSettings querySettings = new QueryExecutionSettings
|
||||
{
|
||||
ExecutionPlanOptions = new ExecutionPlanOptions
|
||||
{
|
||||
IncludeActualExecutionPlanXml = false,
|
||||
IncludeEstimatedExecutionPlanXml = true
|
||||
}
|
||||
};
|
||||
|
||||
// If I have an executed query
|
||||
Query q = Common.GetBasicExecutedQuery(querySettings);
|
||||
|
||||
// ... And I ask for a subset with an invalid result set index
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => q.GetExecutionPlan(batchIndex, 0));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Service Intergration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ExecutionPlanServiceValidTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that has results in the form of an execution plan
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(Common.ExecutionPlanTestDataSet, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams
|
||||
{
|
||||
QuerySelection = null,
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
ExecutionPlanOptions = new ExecutionPlanOptions
|
||||
{
|
||||
IncludeActualExecutionPlanXml = false,
|
||||
IncludeEstimatedExecutionPlanXml = true
|
||||
}
|
||||
};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for a valid execution plan
|
||||
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, BatchIndex = 0, ResultSetIndex = 0 };
|
||||
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: Messages should be null and execution plan should not be null
|
||||
Assert.NotNull(r.ExecutionPlan);
|
||||
}).Complete();
|
||||
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||
executionPlanRequest.Validate();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ExecutionPlanServiceMissingQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I ask for an execution plan for a file that hasn't executed a query
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||
.AddErrorValidation<string>(Assert.NotNull).Complete();
|
||||
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||
executionPlanRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecutionPlanServiceUnexecutedQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that hasn't finished executing (doesn't matter what)
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(Common.ExecutionPlanTestDataSet, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams
|
||||
{
|
||||
QuerySelection = null,
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
ExecutionPlanOptions = new ExecutionPlanOptions
|
||||
{
|
||||
IncludeActualExecutionPlanXml = false,
|
||||
IncludeEstimatedExecutionPlanXml = true
|
||||
}
|
||||
};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Constants.OwnerUri].Batches[0].ResultSets[0].hasBeenRead = false;
|
||||
|
||||
// ... And I then ask for a valid execution plan from it
|
||||
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||
.AddErrorValidation<string>(Assert.NotNull).Complete();
|
||||
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||
executionPlanRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecutionPlanServiceOutOfRangeSubsetTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that doesn't have any result sets
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams
|
||||
{
|
||||
QuerySelection = null,
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
ExecutionPlanOptions = new ExecutionPlanOptions
|
||||
{
|
||||
IncludeActualExecutionPlanXml = false,
|
||||
IncludeEstimatedExecutionPlanXml = true
|
||||
}
|
||||
};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for an execution plan from a result set
|
||||
var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 };
|
||||
var executionPlanRequest = new EventFlowValidator<QueryExecutionPlanResult>()
|
||||
.AddErrorValidation<string>(Assert.NotNull).Complete();
|
||||
await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object);
|
||||
executionPlanRequest.Validate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults
|
||||
{
|
||||
public class BatchTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(100)]
|
||||
public void SaveAsFailsOutOfRangeResultSet(int resultSetIndex)
|
||||
{
|
||||
// If: I attempt to save results for an invalid result set index
|
||||
// Then: I should get an ArgumentOutOfRange exception
|
||||
Batch batch = Common.GetBasicExecutedBatch();
|
||||
SaveResultsRequestParams saveParams = new SaveResultsRequestParams {ResultSetIndex = resultSetIndex};
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
batch.SaveAs(saveParams, null, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults
|
||||
{
|
||||
public class QueryTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(100)]
|
||||
public void SaveAsFailsOutOfRangeBatch(int batchIndex)
|
||||
{
|
||||
// If: I save a basic query's results with out of range batch index
|
||||
// Then: I should get an out of range exception
|
||||
Query query = Common.GetBasicExecutedQuery();
|
||||
SaveResultsRequestParams saveParams = new SaveResultsRequestParams {BatchIndex = batchIndex};
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
query.SaveAs(saveParams, null, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults
|
||||
{
|
||||
public class ResultSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void SaveAsNullParams()
|
||||
{
|
||||
// If: I attempt to save with a null set of params
|
||||
// Then: I should get a null argument exception
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(null, false, Common.NoOpQuery), Common.Ordinal, Common.Ordinal,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
Assert.Throws<ArgumentNullException>(() => rs.SaveAs(
|
||||
null,
|
||||
MemoryFileSystem.GetFileStreamFactory(),
|
||||
null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveAsNullFactory()
|
||||
{
|
||||
// If: I attempt to save with a null set of params
|
||||
// Then: I should get a null argument exception
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(null, false, Common.NoOpQuery), Common.Ordinal, Common.Ordinal,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
Assert.Throws<ArgumentNullException>(() => rs.SaveAs(
|
||||
new SaveResultsRequestParams(),
|
||||
null, null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveAsFailedIncomplete()
|
||||
{
|
||||
// If: I attempt to save a result set that hasn't completed execution
|
||||
// Then: I should get an invalid operation exception
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(null, false, Common.NoOpQuery), Common.Ordinal, Common.Ordinal,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
Assert.Throws<InvalidOperationException>(() => rs.SaveAs(
|
||||
new SaveResultsRequestParams(),
|
||||
MemoryFileSystem.GetFileStreamFactory(),
|
||||
null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SaveAsFailedExistingTaskInProgress()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a result set that has been executed
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery),
|
||||
Common.Ordinal, Common.Ordinal,
|
||||
MemoryFileSystem.GetFileStreamFactory());
|
||||
|
||||
// ... Insert a non-started task into the save as tasks
|
||||
rs.SaveTasks.AddOrUpdate(Constants.OwnerUri, new Task(() => { }), (s, t) => null);
|
||||
|
||||
// If: I attempt to save results with the same name as the non-completed task
|
||||
// Then: I should get an invalid operation exception
|
||||
var requestParams = new SaveResultsRequestParams {FilePath = Constants.OwnerUri};
|
||||
Assert.Throws<InvalidOperationException>(() => rs.SaveAs(
|
||||
requestParams, GetMockFactory(GetMockWriter().Object, null),
|
||||
null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAsWithoutRowSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a mock reader/writer for reading the result
|
||||
IFileStreamFactory resultFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
|
||||
// ... Create a result set with dummy data and read to the end
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery),
|
||||
Common.Ordinal, Common.Ordinal,
|
||||
resultFactory);
|
||||
await rs.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... Create a mock writer for writing the save as file
|
||||
Mock<IFileStreamWriter> saveWriter = GetMockWriter();
|
||||
IFileStreamFactory saveFactory = GetMockFactory(saveWriter.Object, resultFactory.GetReader);
|
||||
|
||||
// If: I attempt to save results and await completion
|
||||
rs.SaveAs(new SaveResultsRequestParams {FilePath = Constants.OwnerUri}, saveFactory, null, null);
|
||||
Assert.True(rs.SaveTasks.ContainsKey(Constants.OwnerUri));
|
||||
await rs.SaveTasks[Constants.OwnerUri];
|
||||
|
||||
// Then:
|
||||
// ... The task should have completed successfully
|
||||
Assert.Equal(TaskStatus.RanToCompletion, rs.SaveTasks[Constants.OwnerUri].Status);
|
||||
|
||||
// ... All the rows should have been written successfully
|
||||
saveWriter.Verify(
|
||||
w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()),
|
||||
Times.Exactly(Common.StandardRows));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAsWithRowSelection()
|
||||
{
|
||||
// Setup:
|
||||
// ... Create a mock reader/writer for reading the result
|
||||
IFileStreamFactory resultFactory = MemoryFileSystem.GetFileStreamFactory();
|
||||
|
||||
// ... Create a result set with dummy data and read to the end
|
||||
ResultSet rs = new ResultSet(
|
||||
GetReader(Common.StandardTestDataSet, false, Constants.StandardQuery),
|
||||
Common.Ordinal, Common.Ordinal,
|
||||
resultFactory);
|
||||
await rs.ReadResultToEnd(CancellationToken.None);
|
||||
|
||||
// ... Create a mock writer for writing the save as file
|
||||
Mock<IFileStreamWriter> saveWriter = GetMockWriter();
|
||||
IFileStreamFactory saveFactory = GetMockFactory(saveWriter.Object, resultFactory.GetReader);
|
||||
|
||||
// If: I attempt to save results that has a selection made
|
||||
var saveParams = new SaveResultsRequestParams
|
||||
{
|
||||
FilePath = Constants.OwnerUri,
|
||||
RowStartIndex = 1,
|
||||
RowEndIndex = Common.StandardRows - 2,
|
||||
ColumnStartIndex = 0, // Column start/end doesn't matter, but are required to be
|
||||
ColumnEndIndex = 10 // considered a "save selection"
|
||||
};
|
||||
rs.SaveAs(saveParams, saveFactory, null, null);
|
||||
Assert.True(rs.SaveTasks.ContainsKey(Constants.OwnerUri));
|
||||
await rs.SaveTasks[Constants.OwnerUri];
|
||||
|
||||
// Then:
|
||||
// ... The task should have completed successfully
|
||||
Assert.Equal(TaskStatus.RanToCompletion, rs.SaveTasks[Constants.OwnerUri].Status);
|
||||
|
||||
// ... All the rows should have been written successfully
|
||||
saveWriter.Verify(
|
||||
w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()),
|
||||
Times.Exactly(Common.StandardRows - 2));
|
||||
}
|
||||
|
||||
private static Mock<IFileStreamWriter> GetMockWriter()
|
||||
{
|
||||
var mockWriter = new Mock<IFileStreamWriter>();
|
||||
mockWriter.Setup(w => w.WriteRow(It.IsAny<IList<DbCellValue>>(), It.IsAny<IList<DbColumnWrapper>>()));
|
||||
return mockWriter;
|
||||
}
|
||||
|
||||
private static IFileStreamFactory GetMockFactory(IFileStreamWriter writer, Func<string, IFileStreamReader> readerGenerator)
|
||||
{
|
||||
var mockFactory = new Mock<IFileStreamFactory>();
|
||||
mockFactory.Setup(f => f.GetWriter(It.IsAny<string>()))
|
||||
.Returns(writer);
|
||||
mockFactory.Setup(f => f.GetReader(It.IsAny<string>()))
|
||||
.Returns(readerGenerator);
|
||||
return mockFactory.Object;
|
||||
}
|
||||
|
||||
private static DbDataReader GetReader(TestResultSet[] dataSet, bool throwOnRead, string query)
|
||||
{
|
||||
var info = Common.CreateTestConnectionInfo(dataSet, throwOnRead);
|
||||
var connection = info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails));
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = query;
|
||||
return command.ExecuteReader();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults
|
||||
{
|
||||
public class ServiceIntegrationTests
|
||||
{
|
||||
#region CSV Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultsCsvNonExistentQuery()
|
||||
{
|
||||
// Given: A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(null);
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(null, false, false, ws);
|
||||
|
||||
// If: I attempt to save a result set from a query that doesn't exist
|
||||
SaveResultsAsCsvRequestParams saveParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
OwnerUri = Constants.OwnerUri // Won't exist because nothing has executed
|
||||
};
|
||||
var evf = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardErrorValidator()
|
||||
.Complete();
|
||||
await qes.HandleSaveResultsAsCsvRequest(saveParams, evf.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error event should have been fired
|
||||
// ... No success event should have been fired
|
||||
evf.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultAsCsvFailure()
|
||||
{
|
||||
// Given:
|
||||
// ... A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
Dictionary<string, byte[]> storage;
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(Common.ExecutionPlanTestDataSet, true, false, ws, out storage);
|
||||
|
||||
// ... The query execution service has executed a query with results
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = null, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await qes.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await qes.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// If: I attempt to save a result set and get it to throw because of invalid column selection
|
||||
SaveResultsAsCsvRequestParams saveParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
BatchIndex = 0,
|
||||
FilePath = "qqq",
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
ColumnStartIndex = -1,
|
||||
ColumnEndIndex = 100,
|
||||
RowStartIndex = 0,
|
||||
RowEndIndex = 5
|
||||
};
|
||||
qes.CsvFileFactory = GetCsvStreamFactory(storage, saveParams);
|
||||
var efv = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardErrorValidator()
|
||||
.Complete();
|
||||
|
||||
|
||||
await qes.HandleSaveResultsAsCsvRequest(saveParams, efv.Object);
|
||||
await qes.ActiveQueries[saveParams.OwnerUri]
|
||||
.Batches[saveParams.BatchIndex]
|
||||
.ResultSets[saveParams.ResultSetIndex]
|
||||
.SaveTasks[saveParams.FilePath];
|
||||
|
||||
// Then:
|
||||
// ... An error event should have been fired
|
||||
// ... No success event should have been fired
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultsAsCsvSuccess()
|
||||
{
|
||||
// Given:
|
||||
// ... A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
Dictionary<string, byte[]> storage;
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(Common.ExecutionPlanTestDataSet, true, false, ws, out storage);
|
||||
|
||||
// ... The query execution service has executed a query with results
|
||||
var executeParams = new ExecuteDocumentSelectionParams {QuerySelection = null, OwnerUri = Constants.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await qes.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await qes.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// If: I attempt to save a result set from a query
|
||||
SaveResultsAsCsvRequestParams saveParams = new SaveResultsAsCsvRequestParams
|
||||
{
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
FilePath = "qqq",
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0
|
||||
};
|
||||
qes.CsvFileFactory = GetCsvStreamFactory(storage, saveParams);
|
||||
var efv = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardResultValidator()
|
||||
.Complete();
|
||||
|
||||
await qes.HandleSaveResultsAsCsvRequest(saveParams, efv.Object);
|
||||
await qes.ActiveQueries[saveParams.OwnerUri]
|
||||
.Batches[saveParams.BatchIndex]
|
||||
.ResultSets[saveParams.ResultSetIndex]
|
||||
.SaveTasks[saveParams.FilePath];
|
||||
|
||||
// Then:
|
||||
// ... I should have a successful result
|
||||
// ... There should not have been an error
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JSON tests
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultsJsonNonExistentQuery()
|
||||
|
||||
{
|
||||
// Given: A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(null);
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(null, false, false, ws);
|
||||
|
||||
// If: I attempt to save a result set from a query that doesn't exist
|
||||
SaveResultsAsJsonRequestParams saveParams = new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
OwnerUri = Constants.OwnerUri // Won't exist because nothing has executed
|
||||
};
|
||||
var efv = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardErrorValidator()
|
||||
.Complete();
|
||||
await qes.HandleSaveResultsAsJsonRequest(saveParams, efv.Object);
|
||||
|
||||
// Then:
|
||||
// ... An error event should have been fired
|
||||
// ... No success event should have been fired
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultAsJsonFailure()
|
||||
{
|
||||
// Given:
|
||||
// ... A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
Dictionary<string, byte[]> storage;
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, ws, out storage);
|
||||
|
||||
// ... The query execution service has executed a query with results
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = null, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await qes.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await qes.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// If: I attempt to save a result set and get it to throw because of invalid column selection
|
||||
SaveResultsAsJsonRequestParams saveParams = new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
BatchIndex = 0,
|
||||
FilePath = "qqq",
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
ResultSetIndex = 0,
|
||||
ColumnStartIndex = -1,
|
||||
ColumnEndIndex = 100,
|
||||
RowStartIndex = 0,
|
||||
RowEndIndex = 5
|
||||
};
|
||||
qes.JsonFileFactory = GetJsonStreamFactory(storage, saveParams);
|
||||
var efv = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardErrorValidator()
|
||||
.Complete();
|
||||
await qes.HandleSaveResultsAsJsonRequest(saveParams, efv.Object);
|
||||
await qes.ActiveQueries[saveParams.OwnerUri]
|
||||
.Batches[saveParams.BatchIndex]
|
||||
.ResultSets[saveParams.ResultSetIndex]
|
||||
.SaveTasks[saveParams.FilePath];
|
||||
|
||||
// Then:
|
||||
// ... An error event should have been fired
|
||||
// ... No success event should have been fired
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveResultsAsJsonSuccess()
|
||||
{
|
||||
// Given:
|
||||
// ... A working query and workspace service
|
||||
WorkspaceService<SqlToolsSettings> ws = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
Dictionary<string, byte[]> storage;
|
||||
QueryExecutionService qes = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, ws, out storage);
|
||||
|
||||
// ... The query execution service has executed a query with results
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = null, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await qes.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await qes.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// If: I attempt to save a result set from a query
|
||||
SaveResultsAsJsonRequestParams saveParams = new SaveResultsAsJsonRequestParams
|
||||
{
|
||||
OwnerUri = Constants.OwnerUri,
|
||||
FilePath = "qqq",
|
||||
BatchIndex = 0,
|
||||
ResultSetIndex = 0
|
||||
};
|
||||
qes.JsonFileFactory = GetJsonStreamFactory(storage, saveParams);
|
||||
var efv = new EventFlowValidator<SaveResultRequestResult>()
|
||||
.AddStandardResultValidator()
|
||||
.Complete();
|
||||
await qes.HandleSaveResultsAsJsonRequest(saveParams, efv.Object);
|
||||
await qes.ActiveQueries[saveParams.OwnerUri]
|
||||
.Batches[saveParams.BatchIndex]
|
||||
.ResultSets[saveParams.ResultSetIndex]
|
||||
.SaveTasks[saveParams.FilePath];
|
||||
|
||||
// Then:
|
||||
// ... I should have a successful result
|
||||
// ... There should not have been an error
|
||||
efv.Validate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
private static IFileStreamFactory GetCsvStreamFactory(IDictionary<string, byte[]> storage, SaveResultsAsCsvRequestParams saveParams)
|
||||
{
|
||||
Mock<IFileStreamFactory> mock = new Mock<IFileStreamFactory>();
|
||||
mock.Setup(fsf => fsf.GetReader(It.IsAny<string>()))
|
||||
.Returns<string>(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings()));
|
||||
mock.Setup(fsf => fsf.GetWriter(It.IsAny<string>()))
|
||||
.Returns<string>(output =>
|
||||
{
|
||||
storage.Add(output, new byte[8192]);
|
||||
return new SaveAsCsvFileStreamWriter(new MemoryStream(storage[output]), saveParams);
|
||||
});
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
private static IFileStreamFactory GetJsonStreamFactory(IDictionary<string, byte[]> storage, SaveResultsAsJsonRequestParams saveParams)
|
||||
{
|
||||
Mock<IFileStreamFactory> mock = new Mock<IFileStreamFactory>();
|
||||
mock.Setup(fsf => fsf.GetReader(It.IsAny<string>()))
|
||||
.Returns<string>(output => new ServiceBufferFileStreamReader(new MemoryStream(storage[output]), new QueryExecutionSettings()));
|
||||
mock.Setup(fsf => fsf.GetWriter(It.IsAny<string>()))
|
||||
.Returns<string>(output =>
|
||||
{
|
||||
storage.Add(output, new byte[8192]);
|
||||
return new SaveAsJsonFileStreamWriter(new MemoryStream(storage[output]), saveParams);
|
||||
});
|
||||
|
||||
return mock.Object;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class SaveResultEventFlowValidatorExtensions
|
||||
{
|
||||
public static EventFlowValidator<SaveResultRequestResult> AddStandardErrorValidator(
|
||||
this EventFlowValidator<SaveResultRequestResult> efv)
|
||||
{
|
||||
return efv.AddErrorValidation<SaveResultRequestError>(e =>
|
||||
{
|
||||
Assert.NotNull(e);
|
||||
Assert.NotNull(e.message);
|
||||
});
|
||||
}
|
||||
|
||||
public static EventFlowValidator<SaveResultRequestResult> AddStandardResultValidator(
|
||||
this EventFlowValidator<SaveResultRequestResult> efv)
|
||||
{
|
||||
return efv.AddResultValidation(r =>
|
||||
{
|
||||
Assert.NotNull(r);
|
||||
Assert.Null(r.Messages);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class SettingsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ValidateQueryExecuteDefaults()
|
||||
{
|
||||
// If: I create a new settings object
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// Then: The default values should be as expected
|
||||
Assert.Equal("GO", sqlToolsSettings.QueryExecutionSettings.BatchSeparator);
|
||||
Assert.Equal(ushort.MaxValue, sqlToolsSettings.QueryExecutionSettings.MaxCharsToStore);
|
||||
Assert.Equal(2*1024*1024, sqlToolsSettings.QueryExecutionSettings.MaxXmlCharsToStore);
|
||||
Assert.False(sqlToolsSettings.QueryExecutionSettings.ExecutionPlanOptions.IncludeActualExecutionPlanXml);
|
||||
Assert.False(sqlToolsSettings.QueryExecutionSettings.ExecutionPlanOptions.IncludeEstimatedExecutionPlanXml);
|
||||
Assert.True(sqlToolsSettings.QueryExecutionSettings.DisplayBitAsNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateSettingsParsedFromJson()
|
||||
{
|
||||
// NOTE: Only testing displayBitAsNumber for now because it is the only one piped through
|
||||
const string settingsJson = @"{"
|
||||
+ @"""params"": {"
|
||||
+ @"""mssql"": {"
|
||||
+ @"""query"": {"
|
||||
+ @"displayBitAsNumber: false"
|
||||
+ @"}"
|
||||
+ @"}"
|
||||
+ @"}"
|
||||
+ @"}";
|
||||
|
||||
// If: I parse the settings JSON object
|
||||
JObject message = JObject.Parse(settingsJson);
|
||||
JToken messageParams;
|
||||
Assert.True(message.TryGetValue("params", out messageParams));
|
||||
SqlToolsSettings sqlToolsSettings = messageParams.ToObject<SqlToolsSettings>();
|
||||
|
||||
// Then: The values defined in the JSON should propagate to the setting object
|
||||
Assert.False(sqlToolsSettings.QueryExecutionSettings.DisplayBitAsNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateSettingsObjectUpdates()
|
||||
{
|
||||
// If: I update a settings object with a new settings object
|
||||
var qes = new QueryExecutionService(null, null);
|
||||
SqlToolsSettings settings = new SqlToolsSettings()
|
||||
{
|
||||
SqlTools = new SqlToolsSettingsValues
|
||||
{
|
||||
QueryExecutionSettings = new QueryExecutionSettings
|
||||
{
|
||||
DisplayBitAsNumber = false,
|
||||
MaxXmlCharsToStore = 1,
|
||||
MaxCharsToStore = 1,
|
||||
ExecutionPlanOptions = new ExecutionPlanOptions
|
||||
{
|
||||
IncludeActualExecutionPlanXml = true,
|
||||
IncludeEstimatedExecutionPlanXml = true
|
||||
},
|
||||
BatchSeparator = "YO"
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
qes.UpdateSettings(settings, null, new EventContext());
|
||||
|
||||
// Then: The settings object should match what it was updated to
|
||||
Assert.False(qes.Settings.QueryExecutionSettings.DisplayBitAsNumber);
|
||||
Assert.True(qes.Settings.QueryExecutionSettings.ExecutionPlanOptions.IncludeActualExecutionPlanXml);
|
||||
Assert.True(qes.Settings.QueryExecutionSettings.ExecutionPlanOptions.IncludeEstimatedExecutionPlanXml);
|
||||
Assert.Equal(1, qes.Settings.QueryExecutionSettings.MaxCharsToStore);
|
||||
Assert.Equal(1, qes.Settings.QueryExecutionSettings.MaxXmlCharsToStore);
|
||||
Assert.Equal("YO", qes.Settings.QueryExecutionSettings.BatchSeparator);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class SpecialActionTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void SpecialActionInstantiation()
|
||||
{
|
||||
// If:
|
||||
// ... I create a special action object
|
||||
var specialAction = new SpecialAction();
|
||||
|
||||
// Then:
|
||||
// ... The special action should be set to none and only none
|
||||
Assert.Equal(true, specialAction.None);
|
||||
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpecialActionNoneProperty()
|
||||
{
|
||||
// If:
|
||||
// ... I create a special action object and add properties but set it back to none
|
||||
var specialAction = new SpecialAction();
|
||||
specialAction.ExpectYukonXMLShowPlan = true;
|
||||
specialAction.None = true;
|
||||
|
||||
// Then:
|
||||
// ... The special action should be set to none and only none
|
||||
Assert.Equal(true, specialAction.None);
|
||||
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpecialActionExpectYukonXmlShowPlanReset()
|
||||
{
|
||||
// If:
|
||||
// ... I create a special action object and add properties but set the property back to false
|
||||
var specialAction = new SpecialAction();
|
||||
specialAction.ExpectYukonXMLShowPlan = true;
|
||||
specialAction.ExpectYukonXMLShowPlan = false;
|
||||
|
||||
// Then:
|
||||
// ... The special action should be set to none and only none
|
||||
Assert.Equal(true, specialAction.None);
|
||||
Assert.Equal(false, specialAction.ExpectYukonXMLShowPlan);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpecialActionCombiningProperties()
|
||||
{
|
||||
// If:
|
||||
// ... I create a special action object and add properties and combine with the same property
|
||||
var specialAction = new SpecialAction();
|
||||
specialAction.ExpectYukonXMLShowPlan = true;
|
||||
|
||||
var specialAction2 = new SpecialAction();
|
||||
specialAction2.ExpectYukonXMLShowPlan = true;
|
||||
|
||||
specialAction.CombineSpecialAction(specialAction2);
|
||||
|
||||
// Then:
|
||||
// ... The special action should be set to none and only none
|
||||
Assert.Equal(false, specialAction.None);
|
||||
Assert.Equal(true, specialAction.ExpectYukonXMLShowPlan);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution
|
||||
{
|
||||
public class SubsetTests
|
||||
{
|
||||
#region ResultSet Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 2)]
|
||||
[InlineData(0, 20)]
|
||||
[InlineData(1, 2)]
|
||||
public void ResultSetValidTest(int startRow, int rowCount)
|
||||
{
|
||||
// Setup:
|
||||
// ... I have a batch that has been executed
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// If:
|
||||
// ... I have a result set and I ask for a subset with valid arguments
|
||||
ResultSet rs = b.ResultSets.First();
|
||||
ResultSetSubset subset = rs.GetSubset(startRow, rowCount).Result;
|
||||
|
||||
// Then:
|
||||
// ... I should get the requested number of rows back
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestResultSet.Count()), subset.RowCount);
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestResultSet.Count()), subset.Rows.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1, 2)] // Invalid start index, too low
|
||||
[InlineData(10, 2)] // Invalid start index, too high
|
||||
[InlineData(0, -1)] // Invalid row count, too low
|
||||
[InlineData(0, 0)] // Invalid row count, zero
|
||||
public void ResultSetInvalidParmsTest(int rowStartIndex, int rowCount)
|
||||
{
|
||||
// If:
|
||||
// I have an executed batch with a resultset in it and request invalid result set from it
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
ResultSet rs = b.ResultSets.First();
|
||||
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => rs.GetSubset(rowStartIndex, rowCount)).Wait();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResultSetNotReadTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a resultset that hasn't been executed and I request a valid result set from it
|
||||
// Then:
|
||||
// ... It should throw an exception for having not been read
|
||||
ResultSet rs = new ResultSet(new TestDbDataReader(null), Common.Ordinal, Common.Ordinal, MemoryFileSystem.GetFileStreamFactory());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => rs.GetSubset(0, 1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Batch Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(2)]
|
||||
[InlineData(20)]
|
||||
public void BatchSubsetValidTest(int rowCount)
|
||||
{
|
||||
// If I have an executed batch
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// ... And I ask for a subset with valid arguments
|
||||
ResultSetSubset subset = b.GetSubset(0, 0, rowCount).Result;
|
||||
|
||||
// Then:
|
||||
// I should get the requested number of rows
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestResultSet.Count()), subset.RowCount);
|
||||
Assert.Equal(Math.Min(rowCount, Common.StandardTestResultSet.Count()), subset.Rows.Length);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Invalid result set, too low
|
||||
[InlineData(2)] // Invalid result set, too high
|
||||
public void BatchSubsetInvalidParamsTest(int resultSetIndex)
|
||||
{
|
||||
// If I have an executed batch
|
||||
Batch b = Common.GetBasicExecutedBatch();
|
||||
|
||||
// ... And I ask for a subset with an invalid result set index
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => b.GetSubset(resultSetIndex, 0, 2)).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query Class Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)] // Invalid batch, too low
|
||||
[InlineData(2)] // Invalid batch, too high
|
||||
public void QuerySubsetInvalidParamsTest(int batchIndex)
|
||||
{
|
||||
// If I have an executed query
|
||||
Query q = Common.GetBasicExecutedQuery();
|
||||
|
||||
// ... And I ask for a subset with an invalid result set index
|
||||
// Then:
|
||||
// ... It should throw an exception
|
||||
Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => q.GetSubset(batchIndex, 0, 0, 1)).Wait();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Service Intergration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SubsetServiceValidTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that has results (doesn't matter what)
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(Common.ExecutionPlanTestDataSet, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams {QuerySelection = null, OwnerUri = Constants.OwnerUri};
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: Messages should be null and subset should not be null
|
||||
Assert.Null(r.Message);
|
||||
Assert.NotNull(r.ResultSubset);
|
||||
}).Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubsetServiceMissingQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I ask for a set of results for a file that hasn't executed a query
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: Messages should not be null and the subset should be null
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubsetServiceUnexecutedQueryTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that hasn't finished executing (doesn't matter what)
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(Common.StandardTestDataSet, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = null, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
queryService.ActiveQueries[Constants.OwnerUri].Batches[0].ResultSets[0].hasBeenRead = false;
|
||||
|
||||
// ... And I then ask for a valid set of results from it
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: There should not be a subset and message should not be null
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubsetServiceOutOfRangeSubsetTest()
|
||||
{
|
||||
// If:
|
||||
// ... I have a query that doesn't have any result sets
|
||||
var workspaceService = Common.GetPrimedWorkspaceService(Constants.StandardQuery);
|
||||
var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService);
|
||||
var executeParams = new ExecuteDocumentSelectionParams { QuerySelection = null, OwnerUri = Constants.OwnerUri };
|
||||
var executeRequest = RequestContextMocks.Create<ExecuteRequestResult>(null);
|
||||
await queryService.HandleExecuteRequest(executeParams, executeRequest.Object);
|
||||
await queryService.ActiveQueries[Constants.OwnerUri].ExecutionTask;
|
||||
|
||||
// ... And I then ask for a set of results from it
|
||||
var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 };
|
||||
var subsetRequest = new EventFlowValidator<SubsetResult>()
|
||||
.AddResultValidation(r =>
|
||||
{
|
||||
// Then: There should be an error message and no subset
|
||||
Assert.NotNull(r.Message);
|
||||
Assert.Null(r.ResultSubset);
|
||||
}).Complete();
|
||||
await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object);
|
||||
subsetRequest.Validate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
public class AsyncLockTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AsyncLockSynchronizesAccess()
|
||||
{
|
||||
AsyncLock asyncLock = new AsyncLock();
|
||||
|
||||
Task<IDisposable> lockOne = asyncLock.LockAsync();
|
||||
Task<IDisposable> lockTwo = asyncLock.LockAsync();
|
||||
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status);
|
||||
Assert.Equal(TaskStatus.WaitingForActivation, lockTwo.Status);
|
||||
lockOne.Result.Dispose();
|
||||
|
||||
await lockTwo;
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockTwo.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncLockCancelsWhenRequested()
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
AsyncLock asyncLock = new AsyncLock();
|
||||
|
||||
Task<IDisposable> lockOne = asyncLock.LockAsync();
|
||||
Task<IDisposable> lockTwo = asyncLock.LockAsync(cts.Token);
|
||||
|
||||
// Cancel the second lock before the first is released
|
||||
cts.Cancel();
|
||||
lockOne.Result.Dispose();
|
||||
|
||||
Assert.Equal(TaskStatus.RanToCompletion, lockOne.Status);
|
||||
Assert.Equal(TaskStatus.Canceled, lockTwo.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
public class AsyncQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AsyncQueueSynchronizesAccess()
|
||||
{
|
||||
ConcurrentBag<int> outputItems = new ConcurrentBag<int>();
|
||||
AsyncQueue<int> inputQueue = new AsyncQueue<int>(Enumerable.Range(0, 100));
|
||||
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
// Start 5 consumers
|
||||
await Task.WhenAll(
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(() => ConsumeItems(inputQueue, outputItems, cancellationTokenSource.Token)),
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// Wait for a bit and then add more items to the queue
|
||||
await Task.Delay(250);
|
||||
|
||||
foreach (var i in Enumerable.Range(100, 200))
|
||||
{
|
||||
await inputQueue.EnqueueAsync(i);
|
||||
}
|
||||
|
||||
// Cancel the waiters
|
||||
cancellationTokenSource.Cancel();
|
||||
}));
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Do nothing, this is expected.
|
||||
}
|
||||
|
||||
// At this point, numbers 0 through 299 should be in the outputItems
|
||||
IEnumerable<int> expectedItems = Enumerable.Range(0, 300);
|
||||
Assert.Equal(0, expectedItems.Except(outputItems).Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncQueueSkipsCancelledTasks()
|
||||
{
|
||||
AsyncQueue<int> inputQueue = new AsyncQueue<int>();
|
||||
|
||||
// Queue up a couple of tasks to wait for input
|
||||
CancellationTokenSource cancellationSource = new CancellationTokenSource();
|
||||
Task<int> taskOne = inputQueue.DequeueAsync(cancellationSource.Token);
|
||||
Task<int> taskTwo = inputQueue.DequeueAsync();
|
||||
|
||||
// Cancel the first task and then enqueue a number
|
||||
cancellationSource.Cancel();
|
||||
await inputQueue.EnqueueAsync(1);
|
||||
|
||||
// Did the second task get the number?
|
||||
Assert.Equal(TaskStatus.Canceled, taskOne.Status);
|
||||
Assert.Equal(TaskStatus.RanToCompletion, taskTwo.Status);
|
||||
Assert.Equal(1, taskTwo.Result);
|
||||
}
|
||||
|
||||
private async Task ConsumeItems(
|
||||
AsyncQueue<int> inputQueue,
|
||||
ConcurrentBag<int> outputItems,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
int consumedItem = await inputQueue.DequeueAsync(cancellationToken);
|
||||
outputItems.Add(consumedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using HostingMessage = Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts.Message;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
public class TestMessageContents
|
||||
{
|
||||
public const string SomeFieldValue = "Some value";
|
||||
public const int NumberValue = 42;
|
||||
|
||||
public string SomeField { get; set; }
|
||||
|
||||
public int Number { get; set; }
|
||||
|
||||
public TestMessageContents()
|
||||
{
|
||||
this.SomeField = SomeFieldValue;
|
||||
this.Number = NumberValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonRpcMessageSerializerTests
|
||||
{
|
||||
private IMessageSerializer messageSerializer;
|
||||
|
||||
private const string MessageId = "42";
|
||||
private const string MethodName = "testMethod";
|
||||
private static readonly JToken MessageContent = JToken.FromObject(new TestMessageContents());
|
||||
|
||||
public JsonRpcMessageSerializerTests()
|
||||
{
|
||||
this.messageSerializer = new JsonRpcMessageSerializer();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesRequestMessages()
|
||||
{
|
||||
var messageObj =
|
||||
this.messageSerializer.SerializeMessage(
|
||||
HostingMessage.Request(
|
||||
MessageId,
|
||||
MethodName,
|
||||
MessageContent));
|
||||
|
||||
AssertMessageFields(
|
||||
messageObj,
|
||||
checkId: true,
|
||||
checkMethod: true,
|
||||
checkParams: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesEventMessages()
|
||||
{
|
||||
var messageObj =
|
||||
this.messageSerializer.SerializeMessage(
|
||||
HostingMessage.Event(
|
||||
MethodName,
|
||||
MessageContent));
|
||||
|
||||
AssertMessageFields(
|
||||
messageObj,
|
||||
checkMethod: true,
|
||||
checkParams: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesResponseMessages()
|
||||
{
|
||||
var messageObj =
|
||||
this.messageSerializer.SerializeMessage(
|
||||
HostingMessage.Response(
|
||||
MessageId,
|
||||
null,
|
||||
MessageContent));
|
||||
|
||||
AssertMessageFields(
|
||||
messageObj,
|
||||
checkId: true,
|
||||
checkResult: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesResponseWithErrorMessages()
|
||||
{
|
||||
var messageObj =
|
||||
this.messageSerializer.SerializeMessage(
|
||||
HostingMessage.ResponseError(
|
||||
MessageId,
|
||||
null,
|
||||
MessageContent));
|
||||
|
||||
AssertMessageFields(
|
||||
messageObj,
|
||||
checkId: true,
|
||||
checkError: true);
|
||||
}
|
||||
|
||||
private static void AssertMessageFields(
|
||||
JObject messageObj,
|
||||
bool checkId = false,
|
||||
bool checkMethod = false,
|
||||
bool checkParams = false,
|
||||
bool checkResult = false,
|
||||
bool checkError = false)
|
||||
{
|
||||
JToken token = null;
|
||||
|
||||
Assert.True(messageObj.TryGetValue("jsonrpc", out token));
|
||||
Assert.Equal("2.0", token.ToString());
|
||||
|
||||
if (checkId)
|
||||
{
|
||||
Assert.True(messageObj.TryGetValue("id", out token));
|
||||
Assert.Equal(MessageId, token.ToString());
|
||||
}
|
||||
|
||||
if (checkMethod)
|
||||
{
|
||||
Assert.True(messageObj.TryGetValue("method", out token));
|
||||
Assert.Equal(MethodName, token.ToString());
|
||||
}
|
||||
|
||||
if (checkError)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
else
|
||||
{
|
||||
string contentField = checkParams ? "params" : "result";
|
||||
Assert.True(messageObj.TryGetValue(contentField, out token));
|
||||
Assert.True(JToken.DeepEquals(token, MessageContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Logger test cases
|
||||
/// </summary>
|
||||
public class LoggerTests
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test to verify that the logger initialization is generating a valid file
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LoggerDefaultFile()
|
||||
{
|
||||
// delete any existing log files from the current directory
|
||||
Directory.GetFiles(Directory.GetCurrentDirectory())
|
||||
.Where(fileName
|
||||
=> fileName.Contains("sqltools_")
|
||||
&& fileName.EndsWith(".log", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList()
|
||||
.ForEach(File.Delete);
|
||||
|
||||
// initialize the logger
|
||||
Logger.Initialize(
|
||||
logFilePath: Path.Combine(Directory.GetCurrentDirectory(), "sqltools"),
|
||||
minimumLogLevel: LogLevel.Verbose);
|
||||
|
||||
// close the logger
|
||||
Logger.Close();
|
||||
|
||||
// find the name of the new log file
|
||||
string logFileName = Directory.GetFiles(Directory.GetCurrentDirectory())
|
||||
.SingleOrDefault(fileName =>
|
||||
fileName.Contains("sqltools_")
|
||||
&& fileName.EndsWith(".log", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// validate the log file was created with desired name
|
||||
Assert.True(!string.IsNullOrWhiteSpace(logFileName));
|
||||
if (!string.IsNullOrWhiteSpace(logFileName))
|
||||
{
|
||||
Assert.True(logFileName.Length > "sqltools_.log".Length);
|
||||
Assert.True(File.Exists(logFileName));
|
||||
|
||||
// delete the test log file
|
||||
if (File.Exists(logFileName))
|
||||
{
|
||||
File.Delete(logFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptFile test case
|
||||
/// </summary>
|
||||
public class ScriptFileTests
|
||||
{
|
||||
internal static object fileLock = new object();
|
||||
|
||||
private static readonly string query =
|
||||
"SELECT * FROM sys.objects as o1" + Environment.NewLine +
|
||||
"SELECT * FROM sys.objects as o2" + Environment.NewLine +
|
||||
"SELECT * FROM sys.objects as o3" + Environment.NewLine;
|
||||
|
||||
internal static ScriptFile GetTestScriptFile(string initialText = null)
|
||||
{
|
||||
if (initialText == null)
|
||||
{
|
||||
initialText = ScriptFileTests.query;
|
||||
}
|
||||
|
||||
string ownerUri = System.IO.Path.GetTempFileName();
|
||||
|
||||
// Write the query text to a backing file
|
||||
lock (fileLock)
|
||||
{
|
||||
System.IO.File.WriteAllText(ownerUri, initialText);
|
||||
}
|
||||
|
||||
return new ScriptFile(ownerUri, ownerUri, initialText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate GetLinesInRange with invalid range
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetLinesInRangeWithInvalidRangeTest()
|
||||
{
|
||||
ScriptFile scriptFile = GetTestScriptFile();
|
||||
|
||||
bool exceptionRaised = false;
|
||||
try
|
||||
{
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(
|
||||
new BufferPosition(1, 0),
|
||||
new BufferPosition(2, 0)));
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
exceptionRaised = true;
|
||||
}
|
||||
|
||||
Assert.True(exceptionRaised, "ArgumentOutOfRangeException raised for invalid index");
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate GetLinesInRange
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetLinesInRangeTest()
|
||||
{
|
||||
ScriptFile scriptFile = GetTestScriptFile();
|
||||
|
||||
string id = scriptFile.Id;
|
||||
Assert.True(!string.IsNullOrWhiteSpace(id));
|
||||
|
||||
BufferRange range =scriptFile.FileRange;
|
||||
Assert.Null(range);
|
||||
|
||||
string[] lines = scriptFile.GetLinesInRange(
|
||||
new BufferRange(
|
||||
new BufferPosition(2, 1),
|
||||
new BufferPosition(2, 7)));
|
||||
|
||||
Assert.True(lines.Length == 1, "One line in range");
|
||||
Assert.True(lines[0].Equals("SELECT"), "Range text is correct");
|
||||
|
||||
string[] queryLines = query.Split('\n');
|
||||
|
||||
string line = scriptFile.GetLine(2);
|
||||
Assert.True(queryLines[1].StartsWith(line), "GetLine text is correct");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOffsetAtPositionTest()
|
||||
{
|
||||
ScriptFile scriptFile = GetTestScriptFile();
|
||||
int offset = scriptFile.GetOffsetAtPosition(2, 5);
|
||||
int expected = 35 + Environment.NewLine.Length;
|
||||
Assert.True(offset == expected, "Offset is at expected location");
|
||||
|
||||
BufferPosition position = scriptFile.GetPositionAtOffset(offset);
|
||||
Assert.True(position.Line == 2 && position.Column == 5, "Position is at expected location");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRangeBetweenOffsetsTest()
|
||||
{
|
||||
ScriptFile scriptFile = GetTestScriptFile();
|
||||
BufferRange range = scriptFile.GetRangeBetweenOffsets(
|
||||
scriptFile.GetOffsetAtPosition(2, 1),
|
||||
scriptFile.GetOffsetAtPosition(2, 7));
|
||||
Assert.NotNull(range);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplySingleLineInsert()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a test.",
|
||||
"This is a working test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 10,
|
||||
EndOffset = 10,
|
||||
InsertString = " working"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplySingleLineReplace()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a potentially broken test.",
|
||||
"This is a working test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 11,
|
||||
EndOffset = 29,
|
||||
InsertString = "working"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplySingleLineDelete()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a test of the emergency broadcasting system.",
|
||||
"This is a test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 15,
|
||||
EndOffset = 52,
|
||||
InsertString = ""
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplyMultiLineInsert()
|
||||
{
|
||||
AssertFileChange(
|
||||
"first\r\nsecond\r\nfifth",
|
||||
"first\r\nsecond\r\nthird\r\nfourth\r\nfifth",
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 3,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = "third\r\nfourth\r\n"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplyMultiLineReplace()
|
||||
{
|
||||
AssertFileChange(
|
||||
"first\r\nsecoXX\r\nXXfth",
|
||||
"first\r\nsecond\r\nthird\r\nfourth\r\nfifth",
|
||||
new FileChange
|
||||
{
|
||||
Line = 2,
|
||||
EndLine = 3,
|
||||
Offset = 5,
|
||||
EndOffset = 3,
|
||||
InsertString = "nd\r\nthird\r\nfourth\r\nfi"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplyMultiLineReplaceWithRemovedLines()
|
||||
{
|
||||
AssertFileChange(
|
||||
"first\r\nsecoXX\r\nREMOVE\r\nTHESE\r\nLINES\r\nXXfth",
|
||||
"first\r\nsecond\r\nthird\r\nfourth\r\nfifth",
|
||||
new FileChange
|
||||
{
|
||||
Line = 2,
|
||||
EndLine = 6,
|
||||
Offset = 5,
|
||||
EndOffset = 3,
|
||||
InsertString = "nd\r\nthird\r\nfourth\r\nfi"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApplyMultiLineDelete()
|
||||
{
|
||||
AssertFileChange(
|
||||
"first\r\nsecond\r\nREMOVE\r\nTHESE\r\nLINES\r\nthird",
|
||||
"first\r\nsecond\r\nthird",
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 6,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = ""
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsExceptionWithEditOutsideOfRange()
|
||||
{
|
||||
Assert.Throws(
|
||||
typeof(ArgumentOutOfRangeException),
|
||||
() =>
|
||||
{
|
||||
AssertFileChange(
|
||||
"first\r\nsecond\r\nREMOVE\r\nTHESE\r\nLINES\r\nthird",
|
||||
"first\r\nsecond\r\nthird",
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 7,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = ""
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void AssertFileChange(
|
||||
string initialString,
|
||||
string expectedString,
|
||||
FileChange fileChange)
|
||||
{
|
||||
// Create an in-memory file from the StringReader
|
||||
ScriptFile fileToChange = GetTestScriptFile(initialString);
|
||||
|
||||
// Apply the FileChange and assert the resulting contents
|
||||
fileToChange.ApplyChange(fileChange);
|
||||
Assert.Equal(expectedString, fileToChange.Contents);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptFileGetLinesTests
|
||||
{
|
||||
private ScriptFile scriptFile;
|
||||
|
||||
private const string TestString = "Line One\r\nLine Two\r\nLine Three\r\nLine Four\r\nLine Five";
|
||||
private readonly string[] TestStringLines =
|
||||
TestString.Split(
|
||||
new string[] { "\r\n" },
|
||||
StringSplitOptions.None);
|
||||
|
||||
public ScriptFileGetLinesTests()
|
||||
{
|
||||
scriptFile =
|
||||
ScriptFileTests.GetTestScriptFile(
|
||||
"Line One\r\nLine Two\r\nLine Three\r\nLine Four\r\nLine Five\r\n");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetWholeLine()
|
||||
{
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(5, 1, 5, 10));
|
||||
|
||||
Assert.Equal(1, lines.Length);
|
||||
Assert.Equal("Line Five", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetMultipleWholeLines()
|
||||
{
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(2, 1, 4, 10));
|
||||
|
||||
Assert.Equal(TestStringLines.Skip(1).Take(3), lines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetSubstringInSingleLine()
|
||||
{
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(4, 3, 4, 8));
|
||||
|
||||
Assert.Equal(1, lines.Length);
|
||||
Assert.Equal("ne Fo", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetEmptySubstringRange()
|
||||
{
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(4, 3, 4, 3));
|
||||
|
||||
Assert.Equal(1, lines.Length);
|
||||
Assert.Equal("", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetSubstringInMultipleLines()
|
||||
{
|
||||
string[] expectedLines = new string[]
|
||||
{
|
||||
"Two",
|
||||
"Line Three",
|
||||
"Line Fou"
|
||||
};
|
||||
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(2, 6, 4, 9));
|
||||
|
||||
Assert.Equal(expectedLines, lines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetRangeAtLineBoundaries()
|
||||
{
|
||||
string[] expectedLines = new string[]
|
||||
{
|
||||
"",
|
||||
"Line Three",
|
||||
""
|
||||
};
|
||||
|
||||
string[] lines =
|
||||
scriptFile.GetLinesInRange(
|
||||
new BufferRange(2, 9, 4, 1));
|
||||
|
||||
Assert.Equal(expectedLines, lines);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptFilePositionTests
|
||||
{
|
||||
private ScriptFile scriptFile;
|
||||
|
||||
public ScriptFilePositionTests()
|
||||
{
|
||||
scriptFile =
|
||||
ScriptFileTests.GetTestScriptFile(@"
|
||||
First line
|
||||
Second line is longer
|
||||
Third line
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanOffsetByLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
1, 1,
|
||||
2, 0,
|
||||
3, 1);
|
||||
|
||||
AssertNewPosition(
|
||||
3, 1,
|
||||
-2, 0,
|
||||
1, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanOffsetByColumn()
|
||||
{
|
||||
AssertNewPosition(
|
||||
2, 1,
|
||||
0, 2,
|
||||
2, 3);
|
||||
|
||||
AssertNewPosition(
|
||||
2, 5,
|
||||
0, -3,
|
||||
2, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsWhenPositionOutOfRange()
|
||||
{
|
||||
// Less than line range
|
||||
Assert.Throws(
|
||||
typeof(ArgumentOutOfRangeException),
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
-10, 0);
|
||||
});
|
||||
|
||||
// Greater than line range
|
||||
Assert.Throws(
|
||||
typeof(ArgumentOutOfRangeException),
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
10, 0);
|
||||
});
|
||||
|
||||
// Less than column range
|
||||
Assert.Throws(
|
||||
typeof(ArgumentOutOfRangeException),
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
0, -10);
|
||||
});
|
||||
|
||||
// Greater than column range
|
||||
Assert.Throws(
|
||||
typeof(ArgumentOutOfRangeException),
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
0, 10);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFindBeginningOfLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.GetLineStart(),
|
||||
4, 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFindEndOfLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.GetLineEnd(),
|
||||
4, 15);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanComposePositionOperations()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.AddOffset(-1, 1).GetLineStart(),
|
||||
3, 3);
|
||||
}
|
||||
|
||||
private void AssertNewPosition(
|
||||
int originalLine, int originalColumn,
|
||||
int lineOffset, int columnOffset,
|
||||
int expectedLine, int expectedColumn)
|
||||
{
|
||||
AssertNewPosition(
|
||||
originalLine, originalColumn,
|
||||
pos => pos.AddOffset(lineOffset, columnOffset),
|
||||
expectedLine, expectedColumn);
|
||||
}
|
||||
|
||||
private void AssertNewPosition(
|
||||
int originalLine, int originalColumn,
|
||||
Func<FilePosition, FilePosition> positionOperation,
|
||||
int expectedLine, int expectedColumn)
|
||||
{
|
||||
var newPosition =
|
||||
positionOperation(
|
||||
new FilePosition(
|
||||
scriptFile,
|
||||
originalLine,
|
||||
originalColumn));
|
||||
|
||||
Assert.Equal(expectedLine, newPosition.Line);
|
||||
Assert.Equal(expectedColumn, newPosition.Column);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
public class ServiceHostTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task InitializeResultShouldIncludeTheCharactersThatWouldTriggerTheCompletion()
|
||||
{
|
||||
Hosting.ServiceHost host = Hosting.ServiceHost.Instance;
|
||||
var requesContext = new Mock<RequestContext<InitializeResult>>();
|
||||
requesContext.Setup(x => x.SendResult(It.IsAny<InitializeResult>())).Returns(Task.FromResult(new object()));
|
||||
await host.HandleInitializeRequest(new InitializeRequest(), requesContext.Object);
|
||||
requesContext.Verify(x => x.SendResult(It.Is<InitializeResult>(
|
||||
i => i.Capabilities.CompletionProvider.TriggerCharacters.All(t => Hosting.ServiceHost.CompletionTriggerCharacters.Contains(t)))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptFile test case
|
||||
/// </summary>
|
||||
public class SrTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple "test" to access string resources
|
||||
/// The purpose of this test is for code coverage. It's probably better to just
|
||||
/// exclude string resources in the code coverage report than maintain this test.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SrStringsTest()
|
||||
{
|
||||
var culture = SR.Culture;
|
||||
SR.Culture = culture;
|
||||
Assert.True(SR.Culture == culture);
|
||||
|
||||
var connectionServiceListDbErrorNullOwnerUri = SR.ConnectionServiceListDbErrorNullOwnerUri;
|
||||
var connectionParamsValidateNullConnection = SR.ConnectionParamsValidateNullConnection;
|
||||
var queryServiceCancelDisposeFailed = SR.QueryServiceCancelDisposeFailed;
|
||||
var queryServiceQueryCancelled = SR.QueryServiceQueryCancelled;
|
||||
var queryServiceDataReaderByteCountInvalid = SR.QueryServiceDataReaderByteCountInvalid;
|
||||
var queryServiceDataReaderCharCountInvalid = SR.QueryServiceDataReaderCharCountInvalid;
|
||||
var queryServiceDataReaderXmlCountInvalid = SR.QueryServiceDataReaderXmlCountInvalid;
|
||||
var queryServiceFileWrapperReadOnly = SR.QueryServiceFileWrapperReadOnly;
|
||||
var queryServiceAffectedOneRow = SR.QueryServiceAffectedOneRow;
|
||||
var queryServiceMessageSenderNotSql = SR.QueryServiceMessageSenderNotSql;
|
||||
var queryServiceResultSetNotRead = SR.QueryServiceResultSetNotRead;
|
||||
var queryServiceResultSetNoColumnSchema = SR.QueryServiceResultSetNoColumnSchema;
|
||||
var connectionServiceListDbErrorNotConnected = SR.ConnectionServiceListDbErrorNotConnected("..");
|
||||
var connectionServiceConnStringInvalidAuthType = SR.ConnectionServiceConnStringInvalidAuthType("..");
|
||||
var connectionServiceConnStringInvalidIntent = SR.ConnectionServiceConnStringInvalidIntent("..");
|
||||
var queryServiceAffectedRows = SR.QueryServiceAffectedRows(10);
|
||||
var queryServiceErrorFormat = SR.QueryServiceErrorFormat(1, 1, 1, 1, "\n", "..");
|
||||
var queryServiceQueryFailed = SR.QueryServiceQueryFailed("..");
|
||||
var workspaceServiceBufferPositionOutOfOrder = SR.WorkspaceServiceBufferPositionOutOfOrder(1, 2, 3, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SrStringsTestWithEnLocalization()
|
||||
{
|
||||
string locale = "en";
|
||||
var args = new string[] { "--locale " + locale };
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.Equal(SR.Culture.Name, options.Locale);
|
||||
Assert.Equal(options.Locale, locale);
|
||||
|
||||
var TestLocalizationConstant = SR.TestLocalizationConstant;
|
||||
Assert.Equal(TestLocalizationConstant, "EN_LOCALIZATION");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SrStringsTestWithEsLocalization()
|
||||
{
|
||||
string locale = "es";
|
||||
var args = new string[] { "--locale " + locale };
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.Equal(SR.Culture.Name, options.Locale);
|
||||
Assert.Equal(options.Locale, locale);
|
||||
|
||||
var TestLocalizationConstant = SR.TestLocalizationConstant;
|
||||
Assert.Equal(TestLocalizationConstant, "ES_LOCALIZATION");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SrStringsTestWithNullLocalization()
|
||||
{
|
||||
SR.Culture = null;
|
||||
var args = new string[] { "" };
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.Null(SR.Culture);
|
||||
Assert.Equal(options.Locale, "");
|
||||
|
||||
var TestLocalizationConstant = SR.TestLocalizationConstant;
|
||||
Assert.Equal(TestLocalizationConstant, "EN_LOCALIZATION");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the SqlContext settins
|
||||
/// </summary>
|
||||
public class SettingsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate that the Language Service default settings are as expected
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateLanguageServiceDefaults()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions);
|
||||
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo);
|
||||
Assert.False(sqlToolsSettings.SqlTools.IntelliSense.LowerCaseSuggestions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsDiagnositicsEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsDiagnosticsEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// diagnostics is enabled if IntelliSense and Diagnostics flags are set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
|
||||
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
// diagnostics is disabled if either IntelliSense and Diagnostics flags is not set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false;
|
||||
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsSuggestionsEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsSuggestionsEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// suggestions is enabled if IntelliSense and Suggestions flags are set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
// suggestions is disabled if either IntelliSense and Suggestions flags is not set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = true;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableSuggestions = false;
|
||||
Assert.False(sqlToolsSettings.IsSuggestionsEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the IsQuickInfoEnabled flag behavior
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ValidateIsQuickInfoEnabled()
|
||||
{
|
||||
var sqlToolsSettings = new SqlToolsSettings();
|
||||
|
||||
// quick info is enabled if IntelliSense and quick info flags are set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.True(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
// quick info is disabled if either IntelliSense and quick info flags is not set
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = true;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
|
||||
sqlToolsSettings.SqlTools.IntelliSense.EnableQuickInfo = false;
|
||||
Assert.False(sqlToolsSettings.IsQuickInfoEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the CommandOptions class
|
||||
/// </summary>
|
||||
public class CommandOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void LoggingEnabledWhenFlagProvided()
|
||||
{
|
||||
var args = new string[] {"--enable-logging"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoggingDisabledWhenFlagNotProvided()
|
||||
{
|
||||
var args = new string[] {};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.False(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsageIsShownWhenHelpFlagProvided()
|
||||
{
|
||||
var args = new string[] {"--help"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsageIsShownWhenBadArgumentsProvided()
|
||||
{
|
||||
var args = new string[] {"--unknown-argument", "/bad-argument"};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.True(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultValuesAreUsedWhenNoArgumentsAreProvided()
|
||||
{
|
||||
var args = new string[] {};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
Assert.NotNull(options);
|
||||
|
||||
Assert.False(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("en")]
|
||||
[InlineData("es")]
|
||||
public void LocaleSetWhenProvided(string locale)
|
||||
{
|
||||
var args = new string[] {"--locale " + locale};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
|
||||
// Asserting all options were properly set
|
||||
Assert.NotNull(options);
|
||||
Assert.False(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, locale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldExitSetWhenInvalidLocale()
|
||||
{
|
||||
string locale = "invalid";
|
||||
var args = new string[] { "--locale " + locale };
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
|
||||
// Asserting all options were properly set
|
||||
Assert.NotNull(options);
|
||||
Assert.False(options.ShouldExit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocaleNotSetWhenNotProvided()
|
||||
{
|
||||
var args = new string[] {};
|
||||
CommandOptions options = new CommandOptions(args);
|
||||
|
||||
// Asserting all options were properly set
|
||||
Assert.NotNull(options);
|
||||
Assert.False(options.EnableLogging);
|
||||
Assert.False(options.ShouldExit);
|
||||
Assert.Equal(options.Locale, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class EventFlowValidator<TRequestContext>
|
||||
{
|
||||
private readonly List<ExpectedEvent> expectedEvents = new List<ExpectedEvent>();
|
||||
private readonly List<ReceivedEvent> receivedEvents = new List<ReceivedEvent>();
|
||||
private readonly Mock<RequestContext<TRequestContext>> requestContext;
|
||||
private bool completed;
|
||||
|
||||
public EventFlowValidator()
|
||||
{
|
||||
requestContext = new Mock<RequestContext<TRequestContext>>(MockBehavior.Strict);
|
||||
}
|
||||
|
||||
public RequestContext<TRequestContext> Object
|
||||
{
|
||||
get { return requestContext.Object; }
|
||||
}
|
||||
|
||||
public EventFlowValidator<TRequestContext> AddEventValidation<TParams>(EventType<TParams> expectedEvent, Action<TParams> paramValidation)
|
||||
{
|
||||
expectedEvents.Add(new ExpectedEvent
|
||||
{
|
||||
EventType = EventTypes.Event,
|
||||
ParamType = typeof(TParams),
|
||||
Validator = paramValidation
|
||||
});
|
||||
|
||||
requestContext.Setup(rc => rc.SendEvent(expectedEvent, It.IsAny<TParams>()))
|
||||
.Callback<EventType<TParams>, TParams>((et, p) =>
|
||||
{
|
||||
receivedEvents.Add(new ReceivedEvent
|
||||
{
|
||||
EventObject = p,
|
||||
EventType = EventTypes.Event
|
||||
});
|
||||
})
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventFlowValidator<TRequestContext> AddResultValidation(Action<TRequestContext> paramValidation)
|
||||
{
|
||||
// Add the expected event
|
||||
expectedEvents.Add(new ExpectedEvent
|
||||
{
|
||||
EventType = EventTypes.Result,
|
||||
ParamType = typeof(TRequestContext),
|
||||
Validator = paramValidation
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventFlowValidator<TRequestContext> AddErrorValidation<TParams>(Action<TParams> paramValidation)
|
||||
{
|
||||
// Add the expected result
|
||||
expectedEvents.Add(new ExpectedEvent
|
||||
{
|
||||
EventType = EventTypes.Error,
|
||||
ParamType = typeof(TParams),
|
||||
Validator = paramValidation
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventFlowValidator<TRequestContext> Complete()
|
||||
{
|
||||
// Add general handler for result handling
|
||||
requestContext.Setup(rc => rc.SendResult(It.IsAny<TRequestContext>()))
|
||||
.Callback<TRequestContext>(r => receivedEvents.Add(new ReceivedEvent
|
||||
{
|
||||
EventObject = r,
|
||||
EventType = EventTypes.Result
|
||||
}))
|
||||
.Returns(Task.FromResult(0));
|
||||
|
||||
// Add general handler for error event
|
||||
requestContext.AddErrorHandling(o =>
|
||||
{
|
||||
receivedEvents.Add(new ReceivedEvent
|
||||
{
|
||||
EventObject = o,
|
||||
EventType = EventTypes.Error
|
||||
});
|
||||
});
|
||||
|
||||
completed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
// Make sure the handlers have been added
|
||||
if (!completed)
|
||||
{
|
||||
throw new Exception("EventFlowValidator must be completed before it can be validated.");
|
||||
}
|
||||
|
||||
// Iterate over the two lists in sync to see if they are the same
|
||||
for (int i = 0; i < Math.Max(expectedEvents.Count, receivedEvents.Count); i++)
|
||||
{
|
||||
// Step 0) Make sure both events exist
|
||||
if (i >= expectedEvents.Count)
|
||||
{
|
||||
throw new Exception($"Unexpected event received: [{receivedEvents[i].EventType}] {receivedEvents[i].EventObject}");
|
||||
}
|
||||
ExpectedEvent expected = expectedEvents[i];
|
||||
|
||||
if (i >= receivedEvents.Count)
|
||||
{
|
||||
throw new Exception($"Expected additional events: [{expectedEvents[i].EventType}] {expectedEvents[i].ParamType}");
|
||||
}
|
||||
ReceivedEvent received = receivedEvents[i];
|
||||
|
||||
// Step 1) Make sure the event type matches
|
||||
Assert.Equal(expected.EventType, received.EventType);
|
||||
|
||||
// Step 2) Make sure the param type matches
|
||||
Assert.Equal(expected.ParamType, received.EventObject.GetType());
|
||||
|
||||
// Step 3) Run the validator on the param object
|
||||
Assert.NotNull(received.EventObject);
|
||||
expected.Validator?.DynamicInvoke(received.EventObject);
|
||||
}
|
||||
}
|
||||
|
||||
private enum EventTypes
|
||||
{
|
||||
Result,
|
||||
Error,
|
||||
Event
|
||||
}
|
||||
|
||||
private class ExpectedEvent
|
||||
{
|
||||
public EventTypes EventType { get; set; }
|
||||
public Type ParamType { get; set; }
|
||||
public Delegate Validator { get; set; }
|
||||
}
|
||||
|
||||
private class ReceivedEvent
|
||||
{
|
||||
public object EventObject { get; set; }
|
||||
public EventTypes EventType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the LongList class
|
||||
/// </summary>
|
||||
public class LongListTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Add and remove and item in a LongList
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LongListTest()
|
||||
{
|
||||
var longList = new LongList<char>();
|
||||
longList.Add('.');
|
||||
Assert.True(longList.Count == 1);
|
||||
longList.RemoveAt(0);
|
||||
Assert.True(longList.Count == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add and remove and item in a LongList causing an expansion
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LongListExpandTest()
|
||||
{
|
||||
var longList = new LongList<int>();
|
||||
longList.ExpandListSize = 3;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
longList.Add(i);
|
||||
}
|
||||
Assert.Equal(longList.Count, 6);
|
||||
Assert.NotNull(longList.GetItem(4));
|
||||
|
||||
bool didEnum = false;
|
||||
foreach (var j in longList)
|
||||
{
|
||||
didEnum = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.True(didEnum);
|
||||
|
||||
longList.RemoveAt(4);
|
||||
Assert.Equal(longList.Count, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using Moq.Language;
|
||||
using Moq.Language.Flow;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public static class MoqExtensions
|
||||
{
|
||||
public delegate void OutAction<TOut>(out TOut outVal);
|
||||
|
||||
public delegate void OutAction<in T1, TOut>(T1 arg1, out TOut outVal);
|
||||
|
||||
public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(
|
||||
this ICallback<TMock, TReturn> mock, OutAction<TOut> action) where TMock : class
|
||||
{
|
||||
return OutCallbackInternal(mock, action);
|
||||
}
|
||||
|
||||
public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(
|
||||
this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action) where TMock : class
|
||||
{
|
||||
return OutCallbackInternal(mock, action);
|
||||
}
|
||||
|
||||
private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(
|
||||
ICallback<TMock, TReturn> mock, object action) where TMock : class
|
||||
{
|
||||
typeof(ICallback<TMock, TReturn>).GetTypeInfo()
|
||||
.Assembly.GetType("Moq.MethodCall")
|
||||
.GetMethod("SetCallbackWithArguments",
|
||||
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Invoke(mock, new[] { action });
|
||||
return mock as IReturnsThrows<TMock, TReturn>;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public static class RequestContextMocks
|
||||
{
|
||||
|
||||
public static Mock<RequestContext<TResponse>> Create<TResponse>(Action<TResponse> resultCallback)
|
||||
{
|
||||
var requestContext = new Mock<RequestContext<TResponse>>();
|
||||
|
||||
// Setup the mock for SendResult
|
||||
var sendResultFlow = requestContext
|
||||
.Setup(rc => rc.SendResult(It.IsAny<TResponse>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (resultCallback != null)
|
||||
{
|
||||
sendResultFlow.Callback(resultCallback);
|
||||
}
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> AddEventHandling<TResponse, TParams>(
|
||||
this Mock<RequestContext<TResponse>> mock,
|
||||
EventType<TParams> expectedEvent,
|
||||
Action<EventType<TParams>, TParams> eventCallback)
|
||||
{
|
||||
var flow = mock.Setup(rc => rc.SendEvent(
|
||||
It.Is<EventType<TParams>>(m => m == expectedEvent),
|
||||
It.IsAny<TParams>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (eventCallback != null)
|
||||
{
|
||||
flow.Callback(eventCallback);
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> AddErrorHandling<TResponse>(
|
||||
this Mock<RequestContext<TResponse>> mock,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
// Setup the mock for SendError
|
||||
var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny<object>()))
|
||||
.Returns(Task.FromResult(0));
|
||||
if (errorCallback != null)
|
||||
{
|
||||
sendErrorFlow.Callback(errorCallback);
|
||||
}
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
public static Mock<RequestContext<TResponse>> SetupRequestContextMock<TResponse, TParams>(
|
||||
Action<TResponse> resultCallback,
|
||||
EventType<TParams> expectedEvent,
|
||||
Action<EventType<TParams>, TParams> eventCallback,
|
||||
Action<object> errorCallback)
|
||||
{
|
||||
return Create(resultCallback)
|
||||
.AddEventHandling(expectedEvent, eventCallback)
|
||||
.AddErrorHandling(errorCallback);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
||||
{
|
||||
public class SqlScriptFormatterTests
|
||||
{
|
||||
#region Format Identifier Tests
|
||||
|
||||
[Fact]
|
||||
public void FormatIdentifierNull()
|
||||
{
|
||||
// If: I attempt to format null as an identifier
|
||||
// Then: I should get an exception thrown
|
||||
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatIdentifier(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test", "[test]")] // No escape characters
|
||||
[InlineData("]test", "[]]test]")] // Escape character at beginning
|
||||
[InlineData("te]st", "[te]]st]")] // Escape character in middle
|
||||
[InlineData("test]", "[test]]]")] // Escape character at end
|
||||
[InlineData("t]]est", "[t]]]]est]")] // Multiple escape characters
|
||||
public void FormatIdentifierTest(string value, string expectedOutput)
|
||||
{
|
||||
// If: I attempt to format a value as an identifier
|
||||
string output = SqlScriptFormatter.FormatIdentifier(value);
|
||||
|
||||
// Then: The output should match the expected output
|
||||
Assert.Equal(expectedOutput, output);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test", "[test]")] // No splits, no escape characters
|
||||
[InlineData("test.test", "[test].[test]")] // One split, no escape characters
|
||||
[InlineData("test.te]st", "[test].[te]]st]")] // One split, one escape character
|
||||
[InlineData("test.test.test", "[test].[test].[test]")] // Two splits, no escape characters
|
||||
public void FormatMultipartIdentifierTest(string value, string expectedOutput)
|
||||
{
|
||||
// If: I attempt to format a value as a multipart identifier
|
||||
string output = SqlScriptFormatter.FormatMultipartIdentifier(value);
|
||||
|
||||
// Then: The output should match the expected output
|
||||
Assert.Equal(expectedOutput, output);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetMultipartIdentifierArrays))]
|
||||
public void FormatMultipartIdentifierArrayTest(string expectedOutput, string[] splits)
|
||||
{
|
||||
// If: I attempt to format a value as a multipart identifier
|
||||
string output = SqlScriptFormatter.FormatMultipartIdentifier(splits);
|
||||
|
||||
// Then: The output should match the expected output
|
||||
Assert.Equal(expectedOutput, output);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GetMultipartIdentifierArrays
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {"[test]", new[] {"test"}}; // No splits, no escape characters
|
||||
yield return new object[] {"[test].[test]", new[] {"test", "test"}}; // One split, no escape characters
|
||||
yield return new object[] {"[test].[te]]st]", new[] {"test", "te]st"}}; // One split, one escape character
|
||||
yield return new object[] {"[test].[test].[test]", new[] {"test", "test", "test"}}; // Two splits, no escape characters
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FormatValue Tests
|
||||
|
||||
[Fact]
|
||||
public void NullDbCellTest()
|
||||
{
|
||||
// If: I attempt to format a null db cell
|
||||
// Then: It should throw
|
||||
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatValue(null, new FormatterTestDbColumn(null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullDbColumnTest()
|
||||
{
|
||||
// If: I attempt to format a null db column
|
||||
// Then: It should throw
|
||||
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatValue(new DbCellValue(), null));
|
||||
}
|
||||
|
||||
public void UnsupportedColumnTest()
|
||||
{
|
||||
// If: I attempt to format an unsupported datatype
|
||||
// Then: It should throw
|
||||
DbColumn column = new FormatterTestDbColumn("unsupported");
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => SqlScriptFormatter.FormatValue(new DbCellValue(), column));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullTest()
|
||||
{
|
||||
// If: I attempt to format a db cell that contains null
|
||||
// Then: I should get the null string back
|
||||
string formattedString = SqlScriptFormatter.FormatValue(new DbCellValue(), new FormatterTestDbColumn(null));
|
||||
Assert.Equal(SqlScriptFormatter.NullString, formattedString);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("BIGINT")]
|
||||
[InlineData("INT")]
|
||||
[InlineData("SMALLINT")]
|
||||
[InlineData("TINYINT")]
|
||||
public void IntegerNumericTest(string dataType)
|
||||
{
|
||||
// Setup: Build a column and cell for the integer type column
|
||||
DbColumn column = new FormatterTestDbColumn(dataType);
|
||||
DbCellValue cell = new DbCellValue { RawObject = (long)123 };
|
||||
|
||||
// If: I attempt to format an integer type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a long
|
||||
Assert.Equal(cell.RawObject, long.Parse(output));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("MONEY", "MONEY", null, null)]
|
||||
[InlineData("SMALLMONEY", "SMALLMONEY", null, null)]
|
||||
[InlineData("NUMERIC", @"NUMERIC\(\d+, \d+\)", 18, 0)]
|
||||
[InlineData("DECIMAL", @"DECIMAL\(\d+, \d+\)", 18, 0)]
|
||||
public void DecimalTest(string dataType, string regex, int? precision, int? scale)
|
||||
{
|
||||
// Setup: Build a column and cell for the decimal type column
|
||||
DbColumn column = new FormatterTestDbColumn(dataType, precision, scale);
|
||||
DbCellValue cell = new DbCellValue { RawObject = 123.45m };
|
||||
|
||||
// If: I attempt to format a decimal type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: It should match a something like CAST(123.45 AS MONEY)
|
||||
Regex castRegex = new Regex($@"CAST\([\d\.]+ AS {regex}", RegexOptions.IgnoreCase);
|
||||
Assert.True(castRegex.IsMatch(output));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleTest()
|
||||
{
|
||||
// Setup: Build a column and cell for the approx numeric type column
|
||||
DbColumn column = new FormatterTestDbColumn("FLOAT");
|
||||
DbCellValue cell = new DbCellValue { RawObject = 3.14159d };
|
||||
|
||||
// If: I attempt to format a approx numeric type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a double
|
||||
Assert.Equal(cell.RawObject, double.Parse(output));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FloatTest()
|
||||
{
|
||||
// Setup: Build a column and cell for the approx numeric type column
|
||||
DbColumn column = new FormatterTestDbColumn("REAL");
|
||||
DbCellValue cell = new DbCellValue { RawObject = (float)3.14159 };
|
||||
|
||||
// If: I attempt to format a approx numeric type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a double
|
||||
Assert.Equal(cell.RawObject, float.Parse(output));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("SMALLDATETIME")]
|
||||
[InlineData("DATETIME")]
|
||||
[InlineData("DATETIME2")]
|
||||
[InlineData("DATE")]
|
||||
public void DateTimeTest(string dataType)
|
||||
{
|
||||
// Setup: Build a column and cell for the datetime type column
|
||||
DbColumn column = new FormatterTestDbColumn(dataType);
|
||||
DbCellValue cell = new DbCellValue { RawObject = DateTime.Now };
|
||||
|
||||
// If: I attempt to format a datetime type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a datetime
|
||||
Regex dateTimeRegex = new Regex("N'(.*)'");
|
||||
DateTime outputDateTime;
|
||||
Assert.True(DateTime.TryParse(dateTimeRegex.Match(output).Groups[1].Value, out outputDateTime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DateTimeOffsetTest()
|
||||
{
|
||||
// Setup: Build a column and cell for the datetime offset type column
|
||||
DbColumn column = new FormatterTestDbColumn("DATETIMEOFFSET");
|
||||
DbCellValue cell = new DbCellValue { RawObject = DateTimeOffset.Now };
|
||||
|
||||
// If: I attempt to format a datetime offset type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a datetime offset
|
||||
Regex dateTimeRegex = new Regex("N'(.*)'");
|
||||
DateTimeOffset outputDateTime;
|
||||
Assert.True(DateTimeOffset.TryParse(dateTimeRegex.Match(output).Groups[1].Value, out outputDateTime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TimeTest()
|
||||
{
|
||||
// Setup: Build a column and cell for the time type column
|
||||
DbColumn column = new FormatterTestDbColumn("TIME");
|
||||
DbCellValue cell = new DbCellValue { RawObject = TimeSpan.FromHours(12) };
|
||||
|
||||
// If: I attempt to format a time type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be able to be converted back into a timespan
|
||||
Regex dateTimeRegex = new Regex("N'(.*)'");
|
||||
TimeSpan outputDateTime;
|
||||
Assert.True(TimeSpan.TryParse(dateTimeRegex.Match(output).Groups[1].Value, out outputDateTime));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "N''")] // Make sure empty string works
|
||||
[InlineData(" \t\r\n", "N' \t\r\n'")] // Test for whitespace
|
||||
[InlineData("some text \x9152", "N'some text \x9152'")] // Test unicode (UTF-8 and UTF-16)
|
||||
[InlineData("'", "N''''")] // Test with escaped character
|
||||
public void StringFormattingTest(string input, string expectedOutput)
|
||||
{
|
||||
// Setup: Build a column and cell for the string type column
|
||||
// NOTE: We're using VARCHAR because it's very general purpose.
|
||||
DbColumn column = new FormatterTestDbColumn("VARCHAR");
|
||||
DbCellValue cell = new DbCellValue { RawObject = input };
|
||||
|
||||
// If: I attempt to format a string type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should be quoted and escaped properly
|
||||
Assert.Equal(expectedOutput, output);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("CHAR")]
|
||||
[InlineData("NCHAR")]
|
||||
[InlineData("VARCHAR")]
|
||||
[InlineData("TEXT")]
|
||||
[InlineData("NTEXT")]
|
||||
[InlineData("XML")]
|
||||
public void StringTypeTest(string datatype)
|
||||
{
|
||||
// Setup: Build a column and cell for the string type column
|
||||
DbColumn column = new FormatterTestDbColumn(datatype);
|
||||
DbCellValue cell = new DbCellValue { RawObject = "test string" };
|
||||
|
||||
// If: I attempt to format a string type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should match the output string
|
||||
Assert.Equal("N'test string'", output);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("BINARY")]
|
||||
[InlineData("VARBINARY")]
|
||||
[InlineData("IMAGE")]
|
||||
public void BinaryTest(string datatype)
|
||||
{
|
||||
// Setup: Build a column and cell for the string type column
|
||||
DbColumn column = new FormatterTestDbColumn(datatype);
|
||||
DbCellValue cell = new DbCellValue
|
||||
{
|
||||
RawObject = new byte[] { 0x42, 0x45, 0x4e, 0x49, 0x53, 0x43, 0x4f, 0x4f, 0x4c }
|
||||
};
|
||||
|
||||
// If: I attempt to format a string type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should match the output string
|
||||
Regex regex = new Regex("0x[0-9A-F]+", RegexOptions.IgnoreCase);
|
||||
Assert.True(regex.IsMatch(output));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GuidTest()
|
||||
{
|
||||
// Setup: Build a column and cell for the string type column
|
||||
DbColumn column = new FormatterTestDbColumn("UNIQUEIDENTIFIER");
|
||||
DbCellValue cell = new DbCellValue { RawObject = Guid.NewGuid() };
|
||||
|
||||
// If: I attempt to format a string type column
|
||||
string output = SqlScriptFormatter.FormatValue(cell, column);
|
||||
|
||||
// Then: The output string should match the output string
|
||||
Regex regex = new Regex(@"N'[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}'", RegexOptions.IgnoreCase);
|
||||
Assert.True(regex.IsMatch(output));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class FormatterTestDbColumn : DbColumn
|
||||
{
|
||||
public FormatterTestDbColumn(string dataType, int? precision = null, int? scale = null)
|
||||
{
|
||||
DataTypeName = dataType;
|
||||
NumericPrecision = precision;
|
||||
NumericScale = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class SrTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Add and remove and item in a LongList
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SrPropertiesTest()
|
||||
{
|
||||
Assert.NotNull(ServiceLayer.SR.QueryServiceSubsetBatchNotCompleted);
|
||||
Assert.NotNull(ServiceLayer.SR.QueryServiceFileWrapperWriteOnly);
|
||||
Assert.NotNull(ServiceLayer.SR.QueryServiceFileWrapperNotInitialized);
|
||||
Assert.NotNull(ServiceLayer.SR.QueryServiceColumnNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class TestDbColumn : DbColumn
|
||||
{
|
||||
public TestDbColumn(string columnName)
|
||||
{
|
||||
base.IsLong = false;
|
||||
base.ColumnName = columnName;
|
||||
base.ColumnSize = 128;
|
||||
base.AllowDBNull = true;
|
||||
base.DataType = typeof(string);
|
||||
base.DataTypeName = "nvarchar";
|
||||
}
|
||||
|
||||
public TestDbColumn(string columnName, string columnType)
|
||||
: this(columnName)
|
||||
{
|
||||
base.DataTypeName = columnType;
|
||||
}
|
||||
|
||||
public TestDbColumn(string columnName, string columnType, Type columnDataType)
|
||||
{
|
||||
base.IsLong = false;
|
||||
base.ColumnName = columnName;
|
||||
base.ColumnSize = 128;
|
||||
base.AllowDBNull = true;
|
||||
base.DataType = columnDataType;
|
||||
base.DataTypeName = columnType;
|
||||
}
|
||||
|
||||
public TestDbColumn(string columnName, string columnType, int scale)
|
||||
: this(columnName, columnType)
|
||||
{
|
||||
base.NumericScale = scale;
|
||||
}
|
||||
|
||||
public TestDbColumn(string columnName, bool isAutoIncrement)
|
||||
: this(columnName)
|
||||
{
|
||||
base.IsAutoIncrement = isAutoIncrement;
|
||||
base.IsIdentity = isAutoIncrement;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class TestDbDataReader : DbDataReader, IDbColumnSchemaGenerator
|
||||
{
|
||||
|
||||
#region Test Specific Implementations
|
||||
|
||||
private IEnumerable<TestResultSet> Data { get; }
|
||||
|
||||
public IEnumerator<TestResultSet> ResultSetEnumerator { get; }
|
||||
|
||||
private IEnumerator<object[]> RowEnumerator { get; set; }
|
||||
|
||||
public TestDbDataReader(IEnumerable<TestResultSet> data)
|
||||
{
|
||||
Data = data;
|
||||
if (Data != null)
|
||||
{
|
||||
ResultSetEnumerator = Data.GetEnumerator();
|
||||
ResultSetEnumerator.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public override int FieldCount => ResultSetEnumerator?.Current.Columns.Count ?? 0;
|
||||
|
||||
public override bool HasRows => ResultSetEnumerator?.Current.Rows.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Mimicks the behavior of SqlDbDataReader
|
||||
/// </summary>
|
||||
public override int RecordsAffected => RowEnumerator != null ? -1 : 1;
|
||||
|
||||
public override object this[int ordinal] => RowEnumerator.Current[ordinal];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implemented Methods
|
||||
|
||||
/// <summary>
|
||||
/// If the row enumerator hasn't been initialized for the current result set, the
|
||||
/// enumerator for the current result set is defined. Increments the enumerator
|
||||
/// </summary>
|
||||
/// <returns>True if tere were more rows, false otherwise</returns>
|
||||
public override bool Read()
|
||||
{
|
||||
if (RowEnumerator == null)
|
||||
{
|
||||
RowEnumerator = ResultSetEnumerator.Current.GetEnumerator();
|
||||
}
|
||||
return RowEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the result set enumerator and initializes the row enumerator
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool NextResult()
|
||||
{
|
||||
if (Data == null || !ResultSetEnumerator.MoveNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RowEnumerator = ResultSetEnumerator.Current.GetEnumerator();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the value for the cell of the current row in the given column
|
||||
/// </summary>
|
||||
/// <param name="ordinal">Ordinal of the column</param>
|
||||
/// <returns>The object in the cell</returns>
|
||||
public override object GetValue(int ordinal)
|
||||
{
|
||||
return this[ordinal];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the values of all cells in this row in the given object array
|
||||
/// </summary>
|
||||
/// <param name="values">Destination for all cell values</param>
|
||||
/// <returns>Number of cells in the current row</returns>
|
||||
public override int GetValues(object[] values)
|
||||
{
|
||||
for (int i = 0; i < RowEnumerator.Current.Count(); i++)
|
||||
{
|
||||
values[i] = this[i];
|
||||
}
|
||||
return RowEnumerator.Current.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a given cell in the current row is null
|
||||
/// </summary>
|
||||
/// <param name="ordinal">Ordinal of the column</param>
|
||||
/// <returns>True if the cell is null, false otherwise</returns>
|
||||
public override bool IsDBNull(int ordinal)
|
||||
{
|
||||
return this[ordinal] == null;
|
||||
}
|
||||
|
||||
/// <returns>Collection of test columns in the current result set</returns>
|
||||
public ReadOnlyCollection<DbColumn> GetColumnSchema()
|
||||
{
|
||||
if (ResultSetEnumerator?.Current == null || ResultSetEnumerator.Current.Rows.Count <= 0)
|
||||
{
|
||||
return new ReadOnlyCollection<DbColumn>(new List<DbColumn>());
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<DbColumn>(ResultSetEnumerator.Current.Columns);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Not Implemented
|
||||
|
||||
public override bool GetBoolean(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override byte GetByte(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override char GetChar(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
|
||||
{
|
||||
char[] allChars = ((string) RowEnumerator.Current[ordinal]).ToCharArray();
|
||||
int outLength = allChars.Length;
|
||||
if (buffer != null)
|
||||
{
|
||||
Array.Copy(allChars, (int) dataOffset, buffer, bufferOffset, outLength);
|
||||
}
|
||||
return outLength;
|
||||
}
|
||||
|
||||
public override string GetDataTypeName(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override DateTime GetDateTime(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override decimal GetDecimal(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override double GetDouble(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int GetOrdinal(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetName(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long GetInt64(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int GetInt32(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override short GetInt16(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Guid GetGuid(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetFloat(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Type GetFieldType(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetString(int ordinal)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IEnumerator GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override int Depth { get { throw new NotImplementedException(); } }
|
||||
|
||||
public override bool IsClosed { get { throw new NotImplementedException(); } }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class TestResultSet : IEnumerable<object[]>
|
||||
{
|
||||
public List<DbColumn> Columns;
|
||||
public List<object[]> Rows;
|
||||
|
||||
public TestResultSet(int columns, int rows)
|
||||
{
|
||||
Columns = Enumerable.Range(0, columns).Select(i => new TestDbColumn($"Col{i}")).Cast<DbColumn>().ToList();
|
||||
Rows = new List<object[]>(rows);
|
||||
for (int i = 0; i < rows; i++)
|
||||
{
|
||||
var row = Enumerable.Range(0, columns).Select(j => $"Cell{i}.{j}").Cast<object>().ToArray();
|
||||
Rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
public TestResultSet(IEnumerable<DbColumn> columns, IEnumerable<object[]> rows)
|
||||
{
|
||||
Columns = new List<DbColumn>(columns);
|
||||
Rows = new List<object[]>(rows);
|
||||
}
|
||||
|
||||
#region IEnumerable<object[]> Impementation
|
||||
|
||||
public IEnumerator<object[]> GetEnumerator()
|
||||
{
|
||||
return (IEnumerator<object[]>) Rows.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the ServiceHost Connection Service tests
|
||||
/// </summary>
|
||||
public class TestObjects
|
||||
{
|
||||
|
||||
public const string ScriptUri = "file://some/file.sql";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test connection service
|
||||
/// </summary>
|
||||
public static ConnectionService GetTestConnectionService()
|
||||
{
|
||||
// use mock database connection
|
||||
return new ConnectionService(new TestSqlConnectionFactory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test connection info instance.
|
||||
/// </summary>
|
||||
public static ConnectionInfo GetTestConnectionInfo()
|
||||
{
|
||||
return new ConnectionInfo(
|
||||
new TestSqlConnectionFactory(),
|
||||
ScriptUri,
|
||||
GetTestConnectionDetails());
|
||||
}
|
||||
|
||||
public static ConnectParams GetTestConnectionParams()
|
||||
{
|
||||
return new ConnectParams()
|
||||
{
|
||||
OwnerUri = ScriptUri,
|
||||
Connection = GetTestConnectionDetails()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a test connection details object
|
||||
/// </summary>
|
||||
public static ConnectionDetails GetTestConnectionDetails()
|
||||
{
|
||||
return new ConnectionDetails()
|
||||
{
|
||||
UserName = "user",
|
||||
Password = "password",
|
||||
DatabaseName = "databaseName",
|
||||
ServerName = "serverName"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a test language service instance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LanguageService GetTestLanguageService()
|
||||
{
|
||||
return new LanguageService();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a dummy TextDocumentPosition
|
||||
/// </summary>
|
||||
public static TextDocumentPosition GetTestDocPosition()
|
||||
{
|
||||
return new TextDocumentPosition
|
||||
{
|
||||
TextDocument = new TextDocumentIdentifier { Uri = ScriptUri },
|
||||
Position = new Position
|
||||
{
|
||||
Line = 0,
|
||||
Character = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test mock class for IDbCommand
|
||||
/// </summary>
|
||||
public class TestSqlCommand : DbCommand
|
||||
{
|
||||
internal TestSqlCommand(TestResultSet[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
internal TestResultSet[] Data { get; set; }
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int ExecuteNonQuery()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ExecuteScalar()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Prepare()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string CommandText { get; set; }
|
||||
public override int CommandTimeout { get; set; }
|
||||
public override CommandType CommandType { get; set; }
|
||||
public override UpdateRowSource UpdatedRowSource { get; set; }
|
||||
protected override DbConnection DbConnection { get; set; }
|
||||
protected override DbParameterCollection DbParameterCollection { get; }
|
||||
protected override DbTransaction DbTransaction { get; set; }
|
||||
public override bool DesignTimeVisible { get; set; }
|
||||
|
||||
protected override DbParameter CreateDbParameter()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
|
||||
{
|
||||
return new TestDbDataReader(Data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test mock class for SqlConnection wrapper
|
||||
/// </summary>
|
||||
public class TestSqlConnection : DbConnection
|
||||
{
|
||||
internal TestSqlConnection(TestResultSet[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
internal TestResultSet[] Data { get; set; }
|
||||
|
||||
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
// No Op
|
||||
}
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
// No Op, unless credentials are bad
|
||||
if(ConnectionString.Contains("invalidUsername"))
|
||||
{
|
||||
throw new Exception("Invalid credentials provided");
|
||||
}
|
||||
}
|
||||
|
||||
public override string ConnectionString { get; set; }
|
||||
public override string Database { get; }
|
||||
public override ConnectionState State { get; }
|
||||
public override string DataSource { get; }
|
||||
public override string ServerVersion { get; }
|
||||
|
||||
protected override DbCommand CreateDbCommand()
|
||||
{
|
||||
return new TestSqlCommand(Data);
|
||||
}
|
||||
|
||||
public override void ChangeDatabase(string databaseName)
|
||||
{
|
||||
// No Op
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test mock class for SqlConnection factory
|
||||
/// </summary>
|
||||
public class TestSqlConnectionFactory : ISqlConnectionFactory
|
||||
{
|
||||
public DbConnection CreateSqlConnection(string connectionString)
|
||||
{
|
||||
return new TestSqlConnection(null)
|
||||
{
|
||||
ConnectionString = connectionString
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public static class TestUtils
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a condition to be true for a limited amount of time.
|
||||
/// </summary>
|
||||
/// <param name="condition">Function that returns a boolean on a condition</param>
|
||||
/// <param name="intervalMilliseconds">Number of milliseconds to wait between test intervals.</param>
|
||||
/// <param name="intervalCount">Number of test intervals to perform before giving up.</param>
|
||||
/// <returns>True if the condition was met before the test interval limit.</returns>
|
||||
public static bool WaitFor(Func<bool> condition, int intervalMilliseconds = 10, int intervalCount = 200)
|
||||
{
|
||||
int count = 0;
|
||||
while (count++ < intervalCount && !condition.Invoke())
|
||||
{
|
||||
Thread.Sleep(intervalMilliseconds);
|
||||
}
|
||||
|
||||
return (count < intervalCount);
|
||||
}
|
||||
|
||||
|
||||
public static async Task RunAndVerify<T>(Func<RequestContext<T>, Task> test, Action<T> verify)
|
||||
{
|
||||
T result = default(T);
|
||||
var contextMock = RequestContextMocks.Create<T>(r => result = r).AddErrorHandling(null);
|
||||
await test(contextMock.Object);
|
||||
VerifyResult(contextMock, verify, result);
|
||||
}
|
||||
|
||||
public static void VerifyErrorSent<T>(Mock<RequestContext<T>> contextMock)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Never);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
public static void VerifyResult<T, U>(Mock<RequestContext<T>> contextMock, U expected, U actual)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||
Assert.Equal(expected, actual);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
public static void VerifyResult<T>(Mock<RequestContext<T>> contextMock, Action<T> verify, T actual)
|
||||
{
|
||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||
contextMock.Verify(c => c.SendError(It.IsAny<string>()), Times.Never);
|
||||
verify(actual);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the TextUtilitiesTests class
|
||||
/// </summary>
|
||||
public class TextUtilitiesTests
|
||||
{
|
||||
[Fact]
|
||||
public void PositionOfCursorFirstLine()
|
||||
{
|
||||
string sql = "EXEC sys.fn_isrolemember ";
|
||||
|
||||
int prevNewLine;
|
||||
int cursorPosition = TextUtilities.PositionOfCursor(sql, 0, sql.Length, out prevNewLine);
|
||||
|
||||
Assert.Equal(prevNewLine, 0);
|
||||
Assert.Equal(cursorPosition, sql.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PositionOfCursorSecondLine()
|
||||
{
|
||||
string sql = "--lineone\nEXEC sys.fn_isrolemember ";
|
||||
|
||||
int prevNewLine;
|
||||
int cursorPosition = TextUtilities.PositionOfCursor(sql, 1, 15, out prevNewLine);
|
||||
|
||||
Assert.Equal(prevNewLine, 10);
|
||||
Assert.Equal(cursorPosition, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
|
||||
{
|
||||
public class ValidateTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsWithinRangeTest()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Validate.IsWithinRange("parameterName", 1, 2, 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsLessThanTest()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => Validate.IsLessThan("parameterName", 2, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNotEqualTest()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => Validate.IsNotEqual<int>("parameterName", 1, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsNullOrWhiteSpaceTest()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => Validate.IsNotNullOrWhitespaceString("parameterName", null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Workspace
|
||||
{
|
||||
public class WorkspaceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task FileClosedSuccessfully()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace that has a single file open
|
||||
var workspace = new ServiceLayer.Workspace.Workspace();
|
||||
var workspaceService = new WorkspaceService<SqlToolsSettings> {Workspace = workspace};
|
||||
var openedFile = workspace.GetFileBuffer(TestObjects.ScriptUri, string.Empty);
|
||||
Assert.NotNull(openedFile);
|
||||
Assert.NotEmpty(workspace.GetOpenedFiles());
|
||||
|
||||
// ... And there is a callback registered for the file closed event
|
||||
ScriptFile closedFile = null;
|
||||
workspaceService.RegisterTextDocCloseCallback((f, c) =>
|
||||
{
|
||||
closedFile = f;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
|
||||
// If:
|
||||
// ... An event to close the open file occurs
|
||||
var eventContext = new Mock<EventContext>().Object;
|
||||
var requestParams = new DidCloseTextDocumentParams
|
||||
{
|
||||
TextDocument = new TextDocumentItem {Uri = TestObjects.ScriptUri}
|
||||
};
|
||||
await workspaceService.HandleDidCloseTextDocumentNotification(requestParams, eventContext);
|
||||
|
||||
// Then:
|
||||
// ... The file should no longer be in the open files
|
||||
Assert.Empty(workspace.GetOpenedFiles());
|
||||
|
||||
// ... The callback should have been called
|
||||
// ... The provided script file should be the one we created
|
||||
Assert.NotNull(closedFile);
|
||||
Assert.Equal(openedFile, closedFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FileClosedNotOpen()
|
||||
{
|
||||
// Given:
|
||||
// ... A workspace that has no files open
|
||||
var workspace = new ServiceLayer.Workspace.Workspace();
|
||||
var workspaceService = new WorkspaceService<SqlToolsSettings> {Workspace = workspace};
|
||||
Assert.Empty(workspace.GetOpenedFiles());
|
||||
|
||||
// ... And there is a callback registered for the file closed event
|
||||
bool callbackCalled = false;
|
||||
workspaceService.RegisterTextDocCloseCallback((f, c) =>
|
||||
{
|
||||
callbackCalled = true;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
|
||||
// If:
|
||||
// ... An event to close the a file occurs
|
||||
var eventContext = new Mock<EventContext>().Object;
|
||||
var requestParams = new DidCloseTextDocumentParams
|
||||
{
|
||||
TextDocument = new TextDocumentItem {Uri = TestObjects.ScriptUri}
|
||||
};
|
||||
// Then:
|
||||
// ... There should be a file not found exception thrown
|
||||
// TODO: This logic should be changed to not create the ScriptFile
|
||||
await Assert.ThrowsAnyAsync<IOException>(
|
||||
() => workspaceService.HandleDidCloseTextDocumentNotification(requestParams, eventContext));
|
||||
|
||||
// ... There should still be no open files
|
||||
// ... The callback should not have been called
|
||||
Assert.Empty(workspace.GetOpenedFiles());
|
||||
Assert.False(callbackCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferRangeNoneNotNull()
|
||||
{
|
||||
Assert.NotNull(BufferRange.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferRangeStartGreaterThanEnd()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new BufferRange(new BufferPosition(2, 2), new BufferPosition(1, 1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferRangeEquals()
|
||||
{
|
||||
var range = new BufferRange(new BufferPosition(1, 1), new BufferPosition(2, 2));
|
||||
Assert.False(range.Equals(null));
|
||||
Assert.True(range.Equals(range));
|
||||
Assert.NotNull(range.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnescapePath()
|
||||
{
|
||||
Assert.NotNull(Microsoft.SqlTools.ServiceLayer.Workspace.Workspace.UnescapePath("`/path/`"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetBaseFilePath()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
using (var workspace = new ServiceLayer.Workspace.Workspace())
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() => workspace.GetBaseFilePath("path"));
|
||||
Assert.NotNull(workspace.GetBaseFilePath(@"c:\path\file.sql"));
|
||||
Assert.Equal(workspace.GetBaseFilePath("tsqloutput://c:/path/file.sql"), workspace.WorkspacePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveRelativeScriptPath()
|
||||
{
|
||||
RunIfWrapper.RunIfWindows(() =>
|
||||
{
|
||||
var workspace = new ServiceLayer.Workspace.Workspace();
|
||||
Assert.NotNull(workspace.ResolveRelativeScriptPath(null, @"c:\path\file.sql"));
|
||||
Assert.NotNull(workspace.ResolveRelativeScriptPath(@"c:\path\", "file.sql"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DontProcessGitFileEvents()
|
||||
{
|
||||
// setup test workspace
|
||||
var workspace = new ServiceLayer.Workspace.Workspace();
|
||||
var workspaceService = new WorkspaceService<SqlToolsSettings> {Workspace = workspace};
|
||||
|
||||
// send a document open event with git:/ prefix URI
|
||||
string filePath = "git:/myfile.sql";
|
||||
var openParams = new DidOpenTextDocumentNotification
|
||||
{
|
||||
TextDocument = new TextDocumentItem { Uri = filePath }
|
||||
};
|
||||
|
||||
await workspaceService.HandleDidOpenTextDocumentNotification(openParams, eventContext: null);
|
||||
|
||||
// verify the file is not being tracked by workspace
|
||||
Assert.False(workspaceService.Workspace.ContainsFile(filePath));
|
||||
|
||||
// send a close event with git:/ prefix URI
|
||||
var closeParams = new DidCloseTextDocumentParams
|
||||
{
|
||||
TextDocument = new TextDocumentItem { Uri = filePath }
|
||||
};
|
||||
|
||||
await workspaceService.HandleDidCloseTextDocumentNotification(closeParams, eventContext: null);
|
||||
|
||||
// this is not that interesting validation since the open is ignored
|
||||
// the main validation is that close doesn't raise an exception
|
||||
Assert.False(workspaceService.Workspace.ContainsFile(filePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WorkspaceContainsFile()
|
||||
{
|
||||
var workspace = new ServiceLayer.Workspace.Workspace();
|
||||
var workspaceService = new WorkspaceService<SqlToolsSettings> {Workspace = workspace};
|
||||
var openedFile = workspace.GetFileBuffer(TestObjects.ScriptUri, string.Empty);
|
||||
|
||||
// send a document open event
|
||||
var openParams = new DidOpenTextDocumentNotification
|
||||
{
|
||||
TextDocument = new TextDocumentItem { Uri = TestObjects.ScriptUri }
|
||||
};
|
||||
|
||||
await workspaceService.HandleDidOpenTextDocumentNotification(openParams, eventContext: null);
|
||||
|
||||
// verify the file is being tracked by workspace
|
||||
Assert.True(workspaceService.Workspace.ContainsFile(TestObjects.ScriptUri));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net461" />
|
||||
<package id="xunit" version="2.1.0" targetFramework="net45" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net45" />
|
||||
<package id="xunit.assert" version="2.1.0" targetFramework="net45" />
|
||||
<package id="xunit.core" version="2.1.0" targetFramework="net45" />
|
||||
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net45" />
|
||||
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net45" />
|
||||
<package id="xunit.runner.visualstudio" version="2.1.0" targetFramework="net45" />
|
||||
</packages>
|
||||
55
test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json
Normal file
55
test/Microsoft.SqlTools.ServiceLayer.UnitTests/project.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "Microsoft.SqlTools.ServiceLayer.UnitTests",
|
||||
"version": "1.0.0-*",
|
||||
"buildOptions": {
|
||||
"debugType": "portable",
|
||||
"define": []
|
||||
},
|
||||
"configurations": {
|
||||
"Integration": {
|
||||
"buildOptions": {
|
||||
"define": [
|
||||
"WINDOWS_ONLY_BUILD"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "9.0.1",
|
||||
"System.Runtime.Serialization.Primitives": "4.1.1",
|
||||
"System.Data.Common": "4.1.0",
|
||||
"System.Data.SqlClient": "4.4.0-sqltools-24613-04",
|
||||
"Microsoft.SqlServer.Smo": "140.1.12",
|
||||
"System.Security.SecureString": "4.0.0",
|
||||
"System.Collections.Specialized": "4.0.1",
|
||||
"System.ComponentModel.TypeConverter": "4.1.0",
|
||||
"xunit": "2.1.0",
|
||||
"dotnet-test-xunit": "2.2.0-preview2-build1029",
|
||||
"Microsoft.SqlTools.ServiceLayer": {
|
||||
"target": "project"
|
||||
},
|
||||
"Moq": "4.6.36-alpha",
|
||||
"Microsoft.SqlTools.ServiceLayer.Test.Common": "1.0.0-*",
|
||||
"Microsoft.SqlTools.Hosting": {
|
||||
"target": "project"
|
||||
},
|
||||
"Microsoft.SqlTools.Credentials": {
|
||||
"target": "project"
|
||||
}
|
||||
},
|
||||
"testRunner": "xunit",
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"type": "platform",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"dotnet5.4",
|
||||
"portable-net451+win8"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user