mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
@@ -4,8 +4,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.Extensibility;
|
using Microsoft.SqlTools.Extensibility;
|
||||||
using Microsoft.SqlTools.Hosting.Protocol;
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.Utility;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.Hosting
|
namespace Microsoft.SqlTools.Hosting
|
||||||
{
|
{
|
||||||
@@ -65,6 +67,23 @@ namespace Microsoft.SqlTools.Hosting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task<T> HandleRequestAsync<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, requestType);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
T result = await handler();
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(ex.ToString());
|
||||||
|
}
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void InitializeService(IProtocolEndpoint serviceHost);
|
public abstract void InitializeService(IProtocolEndpoint serviceHost);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,23 +480,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
|
|||||||
return new ExpandResponse() { SessionId = session.Uri, NodePath = expandParams.NodePath };
|
return new ExpandResponse() { SessionId = session.Uri, NodePath = expandParams.NodePath };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> HandleRequestAsync<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, requestType);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
T result = await handler();
|
|
||||||
await requestContext.SendResult(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await requestContext.SendError(ex.ToString());
|
|
||||||
}
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a URI for object explorer using a similar pattern to Mongo DB (which has URI-based database definition)
|
/// Generates a URI for object explorer using a similar pattern to Mongo DB (which has URI-based database definition)
|
||||||
/// as this should ensure uniqueness
|
/// as this should ensure uniqueness
|
||||||
|
|||||||
@@ -4,22 +4,18 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
||||||
{
|
{
|
||||||
|
|
||||||
public class ListTasksParams
|
public class ListTasksParams
|
||||||
{
|
{
|
||||||
bool ListActiveTasksOnly { get; set; }
|
bool ListActiveTasksOnly { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListTasksResponse
|
public class ListTasksResponse
|
||||||
{
|
{
|
||||||
TaskInfo[] Tasks { get; set; }
|
public TaskInfo[] Tasks { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListTasksRequest
|
public class ListTasksRequest
|
||||||
|
|||||||
@@ -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 Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
||||||
|
{
|
||||||
|
public class CancelTaskParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An id to unify the task
|
||||||
|
/// </summary>
|
||||||
|
public string TaskId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CancelTaskRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<CancelTaskParams, bool> Type =
|
||||||
|
RequestType<CancelTaskParams, bool>.Create("tasks/canceltask");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,43 @@
|
|||||||
using System;
|
//
|
||||||
using System.Collections.Generic;
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
using System.Linq;
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
using System.Threading.Tasks;
|
//
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
||||||
{
|
{
|
||||||
public enum TaskState
|
|
||||||
{
|
|
||||||
NotStarted = 0,
|
|
||||||
Running = 1,
|
|
||||||
Complete = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TaskInfo
|
public class TaskInfo
|
||||||
{
|
{
|
||||||
public int TaskId { get; set; }
|
/// <summary>
|
||||||
|
/// An id to unify the task
|
||||||
|
/// </summary>
|
||||||
|
public string TaskId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task status
|
||||||
|
/// </summary>
|
||||||
|
public SqlTaskStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database server name this task is created for
|
||||||
|
/// </summary>
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database name this task is created for
|
||||||
|
/// </summary>
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task name which defines the type of the task (e.g. CreateDatabase, Backup)
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task description
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
public TaskState State { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// 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.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Expand notification mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class TaskCreatedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<TaskInfo> Type =
|
||||||
|
EventType<TaskInfo>.Create("task/newtaskcreated");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expand notification mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class TaskStatusChangedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<TaskProgressInfo> Type =
|
||||||
|
EventType<TaskProgressInfo>.Create("task/statuschanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
|
||||||
|
{
|
||||||
|
public class TaskProgressInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An id to unify the task
|
||||||
|
/// </summary>
|
||||||
|
public string TaskId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task status
|
||||||
|
/// </summary>
|
||||||
|
public SqlTaskStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database server name this task is created for
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of millisecond the task was running
|
||||||
|
/// </summary>
|
||||||
|
public double Duration { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
413
src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs
Normal file
413
src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
//
|
||||||
|
// 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.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapper to a long running database operation. The class holds a refrence to the actual task that's running
|
||||||
|
/// and keeps track of the task status to send notifications
|
||||||
|
/// </summary>
|
||||||
|
public class SqlTask : IDisposable
|
||||||
|
{
|
||||||
|
private bool isCompleted;
|
||||||
|
private bool isCanceled;
|
||||||
|
private bool isDisposed;
|
||||||
|
private readonly object lockObject = new object();
|
||||||
|
private readonly List<TaskMessage> messages = new List<TaskMessage>();
|
||||||
|
|
||||||
|
private DateTime startTime;
|
||||||
|
private SqlTaskStatus status = SqlTaskStatus.NotStarted;
|
||||||
|
private DateTime stopTime;
|
||||||
|
|
||||||
|
public event EventHandler<TaskEventArgs<TaskMessage>> MessageAdded;
|
||||||
|
public event EventHandler<TaskEventArgs<SqlTaskStatus>> StatusChanged;
|
||||||
|
public event EventHandler<TaskEventArgs<SqlTaskStatus>> TaskCanceled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new instance of SQL task
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskMetdata">Task Metadata</param>
|
||||||
|
/// <param name="testToRun">The function to run to start the task</param>
|
||||||
|
public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> testToRun)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(taskMetdata), taskMetdata);
|
||||||
|
Validate.IsNotNull(nameof(testToRun), testToRun);
|
||||||
|
|
||||||
|
TaskMetadata = taskMetdata;
|
||||||
|
TaskToRun = testToRun;
|
||||||
|
StartTime = DateTime.UtcNow;
|
||||||
|
TaskId = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task Metadata
|
||||||
|
/// </summary>
|
||||||
|
internal TaskMetadata TaskMetadata { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The function to run
|
||||||
|
/// </summary>
|
||||||
|
private Func<SqlTask, Task<TaskResult>> TaskToRun
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task unique id
|
||||||
|
/// </summary>
|
||||||
|
public Guid TaskId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the task and monitor the task progress
|
||||||
|
/// </summary>
|
||||||
|
public async Task Run()
|
||||||
|
{
|
||||||
|
TaskStatus = SqlTaskStatus.InProgress;
|
||||||
|
await TaskToRun(this).ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompleted)
|
||||||
|
{
|
||||||
|
TaskResult taskResult = task.Result;
|
||||||
|
TaskStatus = taskResult.TaskStatus;
|
||||||
|
}
|
||||||
|
else if(task.IsCanceled)
|
||||||
|
{
|
||||||
|
TaskStatus = SqlTaskStatus.Canceled;
|
||||||
|
}
|
||||||
|
else if(task.IsFaulted)
|
||||||
|
{
|
||||||
|
TaskStatus = SqlTaskStatus.Failed;
|
||||||
|
if(task.Exception != null)
|
||||||
|
{
|
||||||
|
AddMessage(task.Exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if task has any messages
|
||||||
|
/// </summary>
|
||||||
|
public bool HasMessages
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
return messages.Any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setting this to True will not change the Slot status.
|
||||||
|
/// Setting the Slot status to Canceled will set this to true.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCanceled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return isCanceled;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (isCanceled != value)
|
||||||
|
{
|
||||||
|
isCanceled = value;
|
||||||
|
OnTaskCanceled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if task is canceled, failed or succeed
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return isCompleted;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (isCompleted != value)
|
||||||
|
{
|
||||||
|
isCompleted = value;
|
||||||
|
if (isCompleted)
|
||||||
|
{
|
||||||
|
StopTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task Messages
|
||||||
|
/// </summary>
|
||||||
|
internal ReadOnlyCollection<TaskMessage> Messages
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
return messages.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start Time
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
startTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of seconds to run the task
|
||||||
|
/// </summary>
|
||||||
|
public double Duration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (stopTime - startTime).TotalMilliseconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task Status
|
||||||
|
/// </summary>
|
||||||
|
public SqlTaskStatus TaskStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
status = value;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case SqlTaskStatus.Canceled:
|
||||||
|
case SqlTaskStatus.Failed:
|
||||||
|
case SqlTaskStatus.Succeeded:
|
||||||
|
case SqlTaskStatus.SucceededWithWarning:
|
||||||
|
IsCompleted = true;
|
||||||
|
break;
|
||||||
|
case SqlTaskStatus.InProgress:
|
||||||
|
case SqlTaskStatus.NotStarted:
|
||||||
|
IsCompleted = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException("IsCompleted is not determined for status: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == SqlTaskStatus.Canceled)
|
||||||
|
{
|
||||||
|
IsCanceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnStatusChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The date time that the task was complete
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StopTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return stopTime;
|
||||||
|
}
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
stopTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to cancel the task, and even to cancel the task will be raised
|
||||||
|
/// but the status won't change until that task actually get canceled by it's owner
|
||||||
|
/// </summary>
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
IsCanceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new message to the task messages
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">Message description</param>
|
||||||
|
/// <param name="status">Status of the message</param>
|
||||||
|
/// <param name="insertAboveLast">If true, the new messages will be added to the top. Default is false</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public TaskMessage AddMessage(string description, SqlTaskStatus status = SqlTaskStatus.NotStarted, bool insertAboveLast = false)
|
||||||
|
{
|
||||||
|
ValidateNotDisposed();
|
||||||
|
|
||||||
|
if (!insertAboveLast)
|
||||||
|
{
|
||||||
|
// Make sure the last message is set to a completed status if a new message is being added at the bottom
|
||||||
|
CompleteLastMessageStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
var newMessage = new TaskMessage
|
||||||
|
{
|
||||||
|
Description = description,
|
||||||
|
Status = status,
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
if (!insertAboveLast || messages.Count == 0)
|
||||||
|
{
|
||||||
|
messages.Add(newMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int lastMessageIndex = messages.Count - 1;
|
||||||
|
messages.Insert(lastMessageIndex, newMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OnMessageAdded(new TaskEventArgs<TaskMessage>(newMessage, this));
|
||||||
|
|
||||||
|
// If the slot is completed, this may be the last message, make sure the message is also set to completed.
|
||||||
|
if (IsCompleted)
|
||||||
|
{
|
||||||
|
CompleteLastMessageStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the task to Task info to be used in the contracts
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public TaskInfo ToTaskInfo()
|
||||||
|
{
|
||||||
|
return new TaskInfo
|
||||||
|
{
|
||||||
|
DatabaseName = TaskMetadata.DatabaseName,
|
||||||
|
ServerName = TaskMetadata.ServerName,
|
||||||
|
Name = TaskMetadata.Name,
|
||||||
|
Description = TaskMetadata.Description,
|
||||||
|
TaskId = TaskId.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes sure the last message has a 'completed' status if it has a status of InProgress.
|
||||||
|
/// If success is true, then sets the status to Succeeded. Sets it to Failed if success is false.
|
||||||
|
/// If success is null (default), then the message status is based on the status of the slot.
|
||||||
|
/// </summary>
|
||||||
|
private void CompleteLastMessageStatus(bool? success = null)
|
||||||
|
{
|
||||||
|
var message = GetLastMessage();
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
if (message.Status == SqlTaskStatus.InProgress)
|
||||||
|
{
|
||||||
|
// infer the success boolean from the slot status if it's not set
|
||||||
|
if (success == null)
|
||||||
|
{
|
||||||
|
switch (TaskStatus)
|
||||||
|
{
|
||||||
|
case SqlTaskStatus.Canceled:
|
||||||
|
case SqlTaskStatus.Failed:
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Status = success.Value ? SqlTaskStatus.Succeeded : SqlTaskStatus.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMessageAdded(TaskEventArgs<TaskMessage> e)
|
||||||
|
{
|
||||||
|
var handler = MessageAdded;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStatusChanged()
|
||||||
|
{
|
||||||
|
var handler = StatusChanged;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, new TaskEventArgs<SqlTaskStatus>(TaskStatus, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTaskCanceled()
|
||||||
|
{
|
||||||
|
var handler = TaskCanceled;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, new TaskEventArgs<SqlTaskStatus>(TaskStatus, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
//Dispose
|
||||||
|
isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected void ValidateNotDisposed()
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(typeof(SqlTask).FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the most recently created message. Returns null if there are no messages on the slot.
|
||||||
|
/// </summary>
|
||||||
|
public TaskMessage GetLastMessage()
|
||||||
|
{
|
||||||
|
ValidateNotDisposed();
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
if (messages.Count > 0)
|
||||||
|
{
|
||||||
|
// get
|
||||||
|
return messages.Last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
//
|
||||||
|
// 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.Concurrent;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A singleton class to manager the current long running operations
|
||||||
|
/// </summary>
|
||||||
|
public class SqlTaskManager : IDisposable
|
||||||
|
{
|
||||||
|
private static SqlTaskManager instance = new SqlTaskManager();
|
||||||
|
private static readonly object lockObject = new object();
|
||||||
|
private bool isDisposed;
|
||||||
|
private readonly ConcurrentDictionary<Guid, SqlTask> tasks = new ConcurrentDictionary<Guid, SqlTask>();
|
||||||
|
|
||||||
|
public event EventHandler<TaskEventArgs<SqlTask>> TaskAdded;
|
||||||
|
public event EventHandler<TaskEventArgs<SqlTask>> TaskRemoved;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor to create an instance for test purposes use only
|
||||||
|
/// </summary>
|
||||||
|
internal SqlTaskManager()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton instance
|
||||||
|
/// </summary>
|
||||||
|
public static SqlTaskManager Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task connections
|
||||||
|
/// </summary>
|
||||||
|
internal ReadOnlyCollection<SqlTask> Tasks
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
return new ReadOnlyCollection<SqlTask>(tasks.Values.ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear completed tasks
|
||||||
|
/// </summary>
|
||||||
|
internal void ClearCompletedTasks()
|
||||||
|
{
|
||||||
|
ValidateNotDisposed();
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
var tasksToRemove = (from task in tasks.Values
|
||||||
|
where task.IsCompleted
|
||||||
|
select task).ToList();
|
||||||
|
foreach (var task in tasksToRemove)
|
||||||
|
{
|
||||||
|
RemoveCompletedTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new task
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskMetadata">Task Metadata</param>
|
||||||
|
/// <param name="taskToRun">The function to run the operation</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public SqlTask CreateTask(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun)
|
||||||
|
{
|
||||||
|
ValidateNotDisposed();
|
||||||
|
|
||||||
|
var newtask = new SqlTask(taskMetadata, taskToRun );
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
tasks.AddOrUpdate(newtask.TaskId, newtask, (key, oldValue) => newtask);
|
||||||
|
}
|
||||||
|
OnTaskAdded(new TaskEventArgs<SqlTask>(newtask));
|
||||||
|
return newtask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
foreach (var task in tasks.Values)
|
||||||
|
{
|
||||||
|
task.Dispose();
|
||||||
|
}
|
||||||
|
tasks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if there's any completed task
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal bool HasCompletedTasks()
|
||||||
|
{
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
return tasks.Values.Any(task => task.IsCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTaskAdded(TaskEventArgs<SqlTask> e)
|
||||||
|
{
|
||||||
|
var handler = TaskAdded;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTaskRemoved(TaskEventArgs<SqlTask> e)
|
||||||
|
{
|
||||||
|
var handler = TaskRemoved;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel a task
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskId"></param>
|
||||||
|
public void CancelTask(Guid taskId)
|
||||||
|
{
|
||||||
|
SqlTask taskToCancel;
|
||||||
|
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
tasks.TryGetValue(taskId, out taskToCancel);
|
||||||
|
}
|
||||||
|
if(taskToCancel != null)
|
||||||
|
{
|
||||||
|
taskToCancel.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for test purposes only.
|
||||||
|
/// Removes all tasks regardless of status from the model.
|
||||||
|
/// This is used as a test aid since Monitor is a singleton class.
|
||||||
|
/// </summary>
|
||||||
|
internal void Reset()
|
||||||
|
{
|
||||||
|
foreach (var task in tasks.Values)
|
||||||
|
{
|
||||||
|
RemoveTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveCompletedTask(SqlTask task)
|
||||||
|
{
|
||||||
|
if (task.IsCompleted)
|
||||||
|
{
|
||||||
|
RemoveTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveTask(SqlTask task)
|
||||||
|
{
|
||||||
|
SqlTask removedTask;
|
||||||
|
tasks.TryRemove(task.TaskId, out removedTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValidateNotDisposed()
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(typeof(SqlTaskManager).FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 System;
|
||||||
|
using Microsoft.SqlTools.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
public sealed class TaskEventArgs<T> : EventArgs
|
||||||
|
{
|
||||||
|
readonly T taskData;
|
||||||
|
|
||||||
|
public TaskEventArgs(T taskData, SqlTask sqlTask)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(taskData), taskData);
|
||||||
|
|
||||||
|
this.taskData = taskData;
|
||||||
|
SqlTask = sqlTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TaskEventArgs(SqlTask sqlTask)
|
||||||
|
{
|
||||||
|
taskData = (T)Convert.ChangeType(sqlTask, typeof(T));
|
||||||
|
SqlTask = sqlTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T TaskData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return taskData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlTask SqlTask { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
public class TaskMessage
|
||||||
|
{
|
||||||
|
public SqlTaskStatus Status { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
public class TaskMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Task Description
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task name to define the type of the task e.g. Create Db, back up
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of seconds to wait before canceling the task.
|
||||||
|
/// This is a optional field and 0 or negative numbers means no timeout
|
||||||
|
/// </summary>
|
||||||
|
public int Timeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines if the task can be canceled
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCancelable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database server name this task is created for
|
||||||
|
/// </summary>
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database name this task is created for
|
||||||
|
/// </summary>
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
public class TaskResult
|
||||||
|
{
|
||||||
|
public SqlTaskStatus TaskStatus { get; set; }
|
||||||
|
|
||||||
|
public string ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,23 +4,29 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.Hosting.Protocol;
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.Hosting;
|
||||||
|
using Microsoft.SqlTools.Extensibility;
|
||||||
|
using Microsoft.SqlTools.Utility;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
{
|
{
|
||||||
public class TaskService
|
public class TaskService: HostedService<TaskService>, IComposableService
|
||||||
{
|
{
|
||||||
private static readonly Lazy<TaskService> instance = new Lazy<TaskService>(() => new TaskService());
|
private static readonly Lazy<TaskService> instance = new Lazy<TaskService>(() => new TaskService());
|
||||||
|
private SqlTaskManager taskManager = SqlTaskManager.Instance;
|
||||||
|
private IProtocolEndpoint serviceHost;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default, parameterless constructor.
|
/// Default, parameterless constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal TaskService()
|
public TaskService()
|
||||||
{
|
{
|
||||||
|
taskManager.TaskAdded += OnTaskAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,22 +37,128 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
|||||||
get { return instance.Value; }
|
get { return instance.Value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task Manager Instance to use for testing
|
||||||
|
/// </summary>
|
||||||
|
internal SqlTaskManager TaskManager
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return taskManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the service instance
|
/// Initializes the service instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context)
|
public override void InitializeService(IProtocolEndpoint serviceHost)
|
||||||
{
|
{
|
||||||
|
this.serviceHost = serviceHost;
|
||||||
|
Logger.Write(LogLevel.Verbose, "TaskService initialized");
|
||||||
serviceHost.SetRequestHandler(ListTasksRequest.Type, HandleListTasksRequest);
|
serviceHost.SetRequestHandler(ListTasksRequest.Type, HandleListTasksRequest);
|
||||||
|
serviceHost.SetRequestHandler(CancelTaskRequest.Type, HandleCancelTaskRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles a list tasks request
|
/// Handles a list tasks request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task HandleListTasksRequest(
|
internal async Task HandleListTasksRequest(
|
||||||
ListTasksParams listTasksParams,
|
ListTasksParams listTasksParams,
|
||||||
RequestContext<ListTasksResponse> requestContext)
|
RequestContext<ListTasksResponse> context)
|
||||||
{
|
{
|
||||||
await requestContext.SendResult(new ListTasksResponse());
|
Logger.Write(LogLevel.Verbose, "HandleListTasksRequest");
|
||||||
|
|
||||||
|
Func<Task<ListTasksResponse>> getAllTasks = () =>
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(listTasksParams), listTasksParams);
|
||||||
|
return Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
ListTasksResponse response = new ListTasksResponse();
|
||||||
|
response.Tasks = taskManager.Tasks.Select(x => x.ToTaskInfo()).ToArray();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
await HandleRequestAsync(getAllTasks, context, "HandleListTasksRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task HandleCancelTaskRequest(CancelTaskParams cancelTaskParams, RequestContext<bool> context)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleCancelTaskRequest");
|
||||||
|
Func<Task<bool>> cancelTask = () =>
|
||||||
|
{
|
||||||
|
Validate.IsNotNull(nameof(cancelTaskParams), cancelTaskParams);
|
||||||
|
|
||||||
|
return Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Guid taskId;
|
||||||
|
if (Guid.TryParse(cancelTaskParams.TaskId, out taskId))
|
||||||
|
{
|
||||||
|
taskManager.CancelTask(taskId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
await HandleRequestAsync(cancelTask, context, "HandleCancelTaskRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnTaskAdded(object sender, TaskEventArgs<SqlTask> e)
|
||||||
|
{
|
||||||
|
SqlTask sqlTask = e.TaskData;
|
||||||
|
if (sqlTask != null)
|
||||||
|
{
|
||||||
|
TaskInfo taskInfo = sqlTask.ToTaskInfo();
|
||||||
|
sqlTask.MessageAdded += OnTaskMessageAdded;
|
||||||
|
sqlTask.StatusChanged += OnTaskStatusChanged;
|
||||||
|
await serviceHost.SendEvent(TaskCreatedNotification.Type, taskInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnTaskStatusChanged(object sender, TaskEventArgs<SqlTaskStatus> e)
|
||||||
|
{
|
||||||
|
SqlTask sqlTask = e.SqlTask;
|
||||||
|
if (sqlTask != null)
|
||||||
|
{
|
||||||
|
TaskProgressInfo progressInfo = new TaskProgressInfo
|
||||||
|
{
|
||||||
|
TaskId = sqlTask.TaskId.ToString(),
|
||||||
|
Status = e.TaskData
|
||||||
|
|
||||||
|
};
|
||||||
|
if (sqlTask.IsCompleted)
|
||||||
|
{
|
||||||
|
progressInfo.Duration = sqlTask.Duration;
|
||||||
|
}
|
||||||
|
await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnTaskMessageAdded(object sender, TaskEventArgs<TaskMessage> e)
|
||||||
|
{
|
||||||
|
SqlTask sqlTask = e.SqlTask;
|
||||||
|
if (sqlTask != null)
|
||||||
|
{
|
||||||
|
TaskProgressInfo progressInfo = new TaskProgressInfo
|
||||||
|
{
|
||||||
|
TaskId = sqlTask.TaskId.ToString(),
|
||||||
|
Message = e.TaskData.Description
|
||||||
|
};
|
||||||
|
await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
taskManager.TaskAdded -= OnTaskAdded;
|
||||||
|
taskManager.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
||||||
|
{
|
||||||
|
public enum SqlTaskStatus
|
||||||
|
{
|
||||||
|
NotStarted,
|
||||||
|
InProgress,
|
||||||
|
Succeeded,
|
||||||
|
SucceededWithWarning,
|
||||||
|
Failed,
|
||||||
|
Canceled
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -396,30 +396,5 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
|
|||||||
ServerInfo = TestObjects.GetTestServerInfo()
|
ServerInfo = TestObjects.GetTestServerInfo()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAndVerify<T, TResult>(Func<RequestContext<T>, Task<TResult>> test, Action<TResult> verify)
|
|
||||||
{
|
|
||||||
T result = default(T);
|
|
||||||
var contextMock = RequestContextMocks.Create<T>(r => result = r).AddErrorHandling(null);
|
|
||||||
TResult actualResult = await test(contextMock.Object);
|
|
||||||
if (actualResult == null && typeof(TResult) == typeof(T))
|
|
||||||
{
|
|
||||||
actualResult = (TResult)Convert.ChangeType(result, typeof(TResult));
|
|
||||||
}
|
|
||||||
VerifyResult(contextMock, verify, actualResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyResult<T, TResult>(Mock<RequestContext<T>> contextMock, Action<TResult> verify, TResult actual)
|
|
||||||
{
|
|
||||||
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
|
||||||
contextMock.Verify(c => c.SendError(It.IsAny<string>(), It.IsAny<int>()), Times.Never);
|
|
||||||
verify(actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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>(), It.IsAny<int>()), Times.Once);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,27 +12,16 @@ using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
|
|||||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
|
||||||
{
|
{
|
||||||
// Base class providing common test functionality for OE tests
|
// Base class providing common test functionality for OE tests
|
||||||
public abstract class ObjectExplorerTestBase
|
public abstract class ObjectExplorerTestBase : ServiceTestBase
|
||||||
{
|
{
|
||||||
protected RegisteredServiceProvider ServiceProvider
|
|
||||||
{
|
protected override RegisteredServiceProvider CreateServiceProviderWithMinServices()
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RegisteredServiceProvider CreateServiceProviderWithMinServices()
|
|
||||||
{
|
{
|
||||||
return CreateProvider()
|
return CreateProvider()
|
||||||
.RegisterSingleService(new ConnectionService())
|
.RegisterSingleService(new ConnectionService())
|
||||||
.RegisterSingleService(new ObjectExplorerService());
|
.RegisterSingleService(new ObjectExplorerService());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected RegisteredServiceProvider CreateProvider()
|
|
||||||
{
|
|
||||||
ServiceProvider = new RegisteredServiceProvider();
|
|
||||||
return ServiceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ObjectExplorerService CreateOEService(ConnectionService connService)
|
protected ObjectExplorerService CreateOEService(ConnectionService connService)
|
||||||
{
|
{
|
||||||
CreateProvider()
|
CreateProvider()
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// 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.Extensibility;
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests
|
||||||
|
{
|
||||||
|
public abstract class ServiceTestBase
|
||||||
|
{
|
||||||
|
protected RegisteredServiceProvider ServiceProvider
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RegisteredServiceProvider CreateProvider()
|
||||||
|
{
|
||||||
|
ServiceProvider = new RegisteredServiceProvider();
|
||||||
|
return ServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract RegisteredServiceProvider CreateServiceProviderWithMinServices();
|
||||||
|
|
||||||
|
protected async Task RunAndVerify<T, TResult>(Func<RequestContext<T>, Task<TResult>> test, Action<TResult> verify)
|
||||||
|
{
|
||||||
|
T result = default(T);
|
||||||
|
var contextMock = RequestContextMocks.Create<T>(r => result = r).AddErrorHandling(null);
|
||||||
|
TResult actualResult = await test(contextMock.Object);
|
||||||
|
if (actualResult == null && typeof(TResult) == typeof(T))
|
||||||
|
{
|
||||||
|
actualResult = (TResult)Convert.ChangeType(result, typeof(TResult));
|
||||||
|
}
|
||||||
|
VerifyResult<T, TResult>(contextMock, verify, actualResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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<T>(contextMock, verify, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void VerifyResult<T, TResult>(Mock<RequestContext<T>> contextMock, Action<TResult> verify, TResult actual)
|
||||||
|
{
|
||||||
|
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
|
||||||
|
contextMock.Verify(c => c.SendError(It.IsAny<string>(), It.IsAny<int>()), Times.Never);
|
||||||
|
verify(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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>(), It.IsAny<int>()), Times.Never);
|
||||||
|
verify(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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>(), It.IsAny<int>()), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// 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.TaskServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
|
||||||
|
{
|
||||||
|
public class DatabaseOperationStub
|
||||||
|
{
|
||||||
|
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
IsStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FailTheOperation()
|
||||||
|
{
|
||||||
|
Failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskResult TaskResult { get; set; }
|
||||||
|
|
||||||
|
public bool IsStopped { get; set; }
|
||||||
|
|
||||||
|
public bool Failed { get; set; }
|
||||||
|
|
||||||
|
public async Task<TaskResult> FunctionToRun(SqlTask sqlTask)
|
||||||
|
{
|
||||||
|
sqlTask.TaskCanceled += OnTaskCanceled;
|
||||||
|
return await Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
while (!IsStopped)
|
||||||
|
{
|
||||||
|
//Just keep running
|
||||||
|
if (cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
if (Failed)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
sqlTask.AddMessage("still running", SqlTaskStatus.InProgress, true);
|
||||||
|
}
|
||||||
|
sqlTask.AddMessage("done!", SqlTaskStatus.Succeeded);
|
||||||
|
|
||||||
|
return TaskResult;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTaskCanceled(object sender, TaskEventArgs<SqlTaskStatus> e)
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
//
|
||||||
|
// 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.TaskServices;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
|
||||||
|
{
|
||||||
|
public class SqlTaskTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateSqlTaskGivenInvalidArgumentShouldThrowException()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new SqlTask(null, new DatabaseOperationStub().FunctionToRun));
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new SqlTask(new TaskMetadata(), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateSqlTaskShouldGenerateANewId()
|
||||||
|
{
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata(), new DatabaseOperationStub().FunctionToRun);
|
||||||
|
Assert.NotNull(sqlTask.TaskId);
|
||||||
|
Assert.True(sqlTask.TaskId != Guid.Empty);
|
||||||
|
|
||||||
|
SqlTask sqlTask2 = new SqlTask(new TaskMetadata(), new DatabaseOperationStub().FunctionToRun);
|
||||||
|
Assert.False(sqlTask.TaskId.CompareTo(sqlTask2.TaskId) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RunShouldRunTheFunctionAndGetTheResult()
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Succeeded;
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
TaskStatus = expectedStatus
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata(), operation.FunctionToRun);
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.NotStarted);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task => {
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCompleted, true);
|
||||||
|
Assert.True(sqlTask.Duration > 0);
|
||||||
|
});
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.InProgress);
|
||||||
|
operation.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToTaskInfoShouldReturnTaskInfo()
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Succeeded;
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
TaskStatus = expectedStatus
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata
|
||||||
|
{
|
||||||
|
ServerName = "server name",
|
||||||
|
DatabaseName = "database name"
|
||||||
|
}, operation.FunctionToRun);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
var taskInfo = sqlTask.ToTaskInfo();
|
||||||
|
Assert.Equal(taskInfo.TaskId, sqlTask.TaskId.ToString());
|
||||||
|
Assert.Equal(taskInfo.ServerName, "server name");
|
||||||
|
Assert.Equal(taskInfo.DatabaseName, "database name");
|
||||||
|
});
|
||||||
|
operation.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FailedOperationShouldReturnTheFailedResult()
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Failed;
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
TaskStatus = expectedStatus
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata(), operation.FunctionToRun);
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.NotStarted);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task => {
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCompleted, true);
|
||||||
|
Assert.True(sqlTask.Duration > 0);
|
||||||
|
});
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.InProgress);
|
||||||
|
operation.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CancelingTheTaskShouldCancelTheOperation()
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Canceled;
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata(), operation.FunctionToRun);
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.NotStarted);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task => {
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCanceled, true);
|
||||||
|
Assert.True(sqlTask.Duration > 0);
|
||||||
|
});
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.InProgress);
|
||||||
|
sqlTask.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FailedOperationShouldFailTheTask()
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Failed;
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = new SqlTask(new TaskMetadata(), operation.FunctionToRun);
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.NotStarted);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task => {
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCanceled, true);
|
||||||
|
Assert.True(sqlTask.Duration > 0);
|
||||||
|
});
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.InProgress);
|
||||||
|
operation.FailTheOperation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// 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.TaskServices;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
|
||||||
|
{
|
||||||
|
|
||||||
|
public class TaskManagerTests
|
||||||
|
{
|
||||||
|
private TaskMetadata taskMetaData = new TaskMetadata
|
||||||
|
{
|
||||||
|
ServerName = "server name",
|
||||||
|
DatabaseName = "database name"
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ManagerInstanceWithNoTaskShouldNotBreakOnCancelTask()
|
||||||
|
{
|
||||||
|
SqlTaskManager manager = new SqlTaskManager();
|
||||||
|
Assert.True(manager.Tasks.Count == 0);
|
||||||
|
manager.CancelTask(Guid.NewGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VerifyCreateAndRunningTask()
|
||||||
|
{
|
||||||
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
|
{
|
||||||
|
bool taskAddedEventRaised = false;
|
||||||
|
manager.TaskAdded += (object sender, TaskEventArgs<SqlTask> e) =>
|
||||||
|
{
|
||||||
|
taskAddedEventRaised = true;
|
||||||
|
};
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = manager.CreateTask(taskMetaData, operation.FunctionToRun);
|
||||||
|
Assert.NotNull(sqlTask);
|
||||||
|
Assert.True(taskAddedEventRaised);
|
||||||
|
|
||||||
|
Assert.False(manager.HasCompletedTasks());
|
||||||
|
sqlTask.Run().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
Assert.True(manager.HasCompletedTasks());
|
||||||
|
manager.RemoveCompletedTask(sqlTask);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
operation.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CancelTaskShouldCancelTheOperation()
|
||||||
|
{
|
||||||
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
|
{
|
||||||
|
SqlTaskStatus expectedStatus = SqlTaskStatus.Canceled;
|
||||||
|
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
operation.TaskResult = new TaskResult
|
||||||
|
{
|
||||||
|
};
|
||||||
|
SqlTask sqlTask = manager.CreateTask(taskMetaData, operation.FunctionToRun);
|
||||||
|
Assert.NotNull(sqlTask);
|
||||||
|
|
||||||
|
sqlTask.Run().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCanceled, true);
|
||||||
|
manager.Reset();
|
||||||
|
|
||||||
|
});
|
||||||
|
manager.CancelTask(sqlTask.TaskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
//
|
||||||
|
// 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.Extensibility;
|
||||||
|
using Microsoft.SqlTools.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.TaskServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
|
||||||
|
{
|
||||||
|
public class TaskServiceTests : ServiceTestBase
|
||||||
|
{
|
||||||
|
private TaskService service;
|
||||||
|
private Mock<IProtocolEndpoint> serviceHostMock;
|
||||||
|
private TaskMetadata taskMetaData = new TaskMetadata
|
||||||
|
{
|
||||||
|
ServerName = "server name",
|
||||||
|
DatabaseName = "database name"
|
||||||
|
};
|
||||||
|
|
||||||
|
public TaskServiceTests()
|
||||||
|
{
|
||||||
|
serviceHostMock = new Mock<IProtocolEndpoint>();
|
||||||
|
service = CreateService();
|
||||||
|
service.InitializeService(serviceHostMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TaskListRequestErrorsIfParameterIsNull()
|
||||||
|
{
|
||||||
|
object errorResponse = null;
|
||||||
|
var contextMock = RequestContextMocks.Create<ListTasksResponse>(null)
|
||||||
|
.AddErrorHandling((errorMessage, errorCode) => errorResponse = errorMessage);
|
||||||
|
|
||||||
|
await service.HandleListTasksRequest(null, contextMock.Object);
|
||||||
|
VerifyErrorSent(contextMock);
|
||||||
|
Assert.True(((string)errorResponse).Contains("ArgumentNullException"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NewTaskShouldSendNotification()
|
||||||
|
{
|
||||||
|
serviceHostMock.AddEventHandling(TaskCreatedNotification.Type, null);
|
||||||
|
serviceHostMock.AddEventHandling(TaskStatusChangedNotification.Type, null);
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
SqlTask sqlTask = service.TaskManager.CreateTask(taskMetaData, operation.FunctionToRun);
|
||||||
|
sqlTask.Run().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(TaskCreatedNotification.Type,
|
||||||
|
It.Is<TaskInfo>(t => t.TaskId == sqlTask.TaskId.ToString())), Times.Once());
|
||||||
|
operation.Stop();
|
||||||
|
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(TaskStatusChangedNotification.Type,
|
||||||
|
It.Is<TaskProgressInfo>(t => t.TaskId == sqlTask.TaskId.ToString())), Times.AtLeastOnce());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CancelTaskShouldCancelTheOperationAndSendNotification()
|
||||||
|
{
|
||||||
|
serviceHostMock.AddEventHandling(TaskCreatedNotification.Type, null);
|
||||||
|
serviceHostMock.AddEventHandling(TaskStatusChangedNotification.Type, null);
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
SqlTask sqlTask = service.TaskManager.CreateTask(taskMetaData, operation.FunctionToRun);
|
||||||
|
sqlTask.Run().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(TaskStatusChangedNotification.Type,
|
||||||
|
It.Is<TaskProgressInfo>(t => t.Status == SqlTaskStatus.Canceled)), Times.Once());
|
||||||
|
});
|
||||||
|
CancelTaskParams cancelParams = new CancelTaskParams
|
||||||
|
{
|
||||||
|
TaskId = sqlTask.TaskId.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
await RunAndVerify<bool>(
|
||||||
|
test: (requestContext) => service.HandleCancelTaskRequest(cancelParams, requestContext),
|
||||||
|
verify: ((result) =>
|
||||||
|
{
|
||||||
|
}));
|
||||||
|
|
||||||
|
serviceHostMock.Verify(x => x.SendEvent(TaskCreatedNotification.Type,
|
||||||
|
It.Is<TaskInfo>(t => t.TaskId == sqlTask.TaskId.ToString())), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TaskListTaskShouldReturnAllTasks()
|
||||||
|
{
|
||||||
|
serviceHostMock.AddEventHandling(TaskCreatedNotification.Type, null);
|
||||||
|
serviceHostMock.AddEventHandling(TaskStatusChangedNotification.Type, null);
|
||||||
|
DatabaseOperationStub operation = new DatabaseOperationStub();
|
||||||
|
SqlTask sqlTask = service.TaskManager.CreateTask(taskMetaData, operation.FunctionToRun);
|
||||||
|
sqlTask.Run();
|
||||||
|
ListTasksParams listParams = new ListTasksParams
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
await RunAndVerify<ListTasksResponse>(
|
||||||
|
test: (requestContext) => service.HandleListTasksRequest(listParams, requestContext),
|
||||||
|
verify: ((result) =>
|
||||||
|
{
|
||||||
|
Assert.True(result.Tasks.Any(x => x.TaskId == sqlTask.TaskId.ToString()));
|
||||||
|
}));
|
||||||
|
|
||||||
|
operation.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TaskService CreateService()
|
||||||
|
{
|
||||||
|
CreateServiceProviderWithMinServices();
|
||||||
|
|
||||||
|
// Create the service using the service provider, which will initialize dependencies
|
||||||
|
return ServiceProvider.GetService<TaskService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override RegisteredServiceProvider CreateServiceProviderWithMinServices()
|
||||||
|
{
|
||||||
|
return CreateProvider()
|
||||||
|
.RegisterSingleService(new TaskService());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user