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:
Benjamin Russell
2017-03-02 13:00:31 -08:00
committed by GitHub
parent f9abe5f0bd
commit 1166778249
110 changed files with 700 additions and 764 deletions

View File

@@ -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>

View File

@@ -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)]

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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)));
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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();
});
}
}
}

View File

@@ -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();
});
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
});
}
}
}

View File

@@ -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
}
}

View File

@@ -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
{
}
}

View File

@@ -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
{
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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")]

View File

@@ -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();
}
}
}

View File

@@ -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
}
}

View File

@@ -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]);
}
}
}
}

View File

@@ -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]);
}
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
};
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
};
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
});
}
}
}

View File

@@ -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
}
}

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
});
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)))));
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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; }
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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>

View 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"
]
}
}
}