From 4e70a76c620e97bd84955d5c913815b4abb733cc Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 12 Jun 2020 17:47:48 -0400 Subject: [PATCH 01/22] Mock AzureEnvironment and end-to-end tests --- src/AzureClient/AzureClient.cs | 25 +- src/AzureClient/AzureClientError.cs | 93 +++++++ src/AzureClient/AzureEnvironment.cs | 19 +- src/AzureClient/AzureExecutionTarget.cs | 4 +- src/AzureClient/AzureSubmissionContext.cs | 2 +- src/AzureClient/AzureWorkspace.cs | 2 +- src/AzureClient/Extensions.cs | 19 +- src/AzureClient/IAzureClient.cs | 86 +----- src/AzureClient/IAzureWorkspace.cs | 10 +- src/AzureClient/Magic/AzureClientMagicBase.cs | 2 +- src/AzureClient/Magic/ConnectMagic.cs | 2 +- src/AzureClient/Magic/ExecuteMagic.cs | 2 +- src/AzureClient/Magic/JobsMagic.cs | 2 +- src/AzureClient/Magic/OutputMagic.cs | 2 +- src/AzureClient/Magic/StatusMagic.cs | 2 +- src/AzureClient/Magic/SubmitMagic.cs | 2 +- src/AzureClient/Magic/TargetMagic.cs | 2 +- src/AzureClient/Mocks/MockAzureWorkspace.cs | 63 +++++ src/AzureClient/Mocks/MockCloudJob.cs | 38 +++ src/AzureClient/Mocks/MockQuantumMachine.cs | 61 ++++ .../Visualization/AzureClientErrorEncoders.cs | 64 +++++ .../Visualization/CloudJobEncoders.cs | 8 +- .../Visualization/HistogramEncoders.cs | 2 +- .../Visualization/JsonConverters.cs | 5 +- .../Visualization/TargetStatusEncoders.cs | 6 +- src/Python/qsharp/azure.py | 99 +++++-- src/Python/qsharp/tests/test_azure.py | 102 +++++++ src/Tests/AzureClientEntryPointTests.cs | 59 ---- src/Tests/AzureClientTests.cs | 263 +++++++++++++++--- 29 files changed, 792 insertions(+), 254 deletions(-) create mode 100644 src/AzureClient/AzureClientError.cs create mode 100644 src/AzureClient/Mocks/MockAzureWorkspace.cs create mode 100644 src/AzureClient/Mocks/MockCloudJob.cs create mode 100644 src/AzureClient/Mocks/MockQuantumMachine.cs create mode 100644 src/AzureClient/Visualization/AzureClientErrorEncoders.cs create mode 100644 src/Python/qsharp/tests/test_azure.py diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index c03ad4a61f..1622cb0ccd 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -20,12 +20,12 @@ namespace Microsoft.Quantum.IQSharp.AzureClient /// public class AzureClient : IAzureClient { + internal IAzureWorkspace? ActiveWorkspace { get; private set; } private ILogger Logger { get; } private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } private string ConnectionString { get; set; } = string.Empty; private AzureExecutionTarget? ActiveTarget { get; set; } - private IAzureWorkspace? ActiveWorkspace { get; set; } private string MostRecentJobId { get; set; } = string.Empty; private IEnumerable? AvailableProviders { get; set; } private IEnumerable? AvailableTargets => AvailableProviders?.SelectMany(provider => provider.Targets); @@ -55,6 +55,8 @@ public AzureClient( baseEngine.RegisterDisplayEncoder(new TargetStatusToTextEncoder()); baseEngine.RegisterDisplayEncoder(new HistogramToHtmlEncoder()); baseEngine.RegisterDisplayEncoder(new HistogramToTextEncoder()); + baseEngine.RegisterDisplayEncoder(new AzureClientErrorToHtmlEncoder()); + baseEngine.RegisterDisplayEncoder(new AzureClientErrorToTextEncoder()); } } @@ -83,6 +85,11 @@ public async Task ConnectAsync(IChannel channel, channel.Stdout($"Connected to Azure Quantum workspace {ActiveWorkspace.Name}."); + if (ValidExecutionTargets.Count() == 0) + { + channel.Stderr($"No valid Q# execution targets found in Azure Quantum workspace {ActiveWorkspace.Name}."); + } + return ValidExecutionTargets.ToExecutionResult(); } @@ -219,7 +226,8 @@ public async Task GetActiveTargetAsync(IChannel channel) channel.Stdout($"Current execution target: {ActiveTarget.TargetId}"); channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); - return ActiveTarget.TargetId.ToExecutionResult(); + + return AvailableTargets.First(target => target.Id == ActiveTarget.TargetId).ToExecutionResult(); } /// @@ -254,7 +262,9 @@ public async Task SetActiveTargetAsync(IChannel channel, string channel.Stdout($"Loading package {ActiveTarget.PackageName} and dependencies..."); await References.AddPackage(ActiveTarget.PackageName); - return $"Active target is now {ActiveTarget.TargetId}".ToExecutionResult(); + channel.Stdout($"Active target is now {ActiveTarget.TargetId}"); + + return AvailableTargets.First(target => target.Id == ActiveTarget.TargetId).ToExecutionResult(); } /// @@ -343,13 +353,12 @@ public async Task GetJobListAsync(IChannel channel) return AzureClientError.NotConnected.ToExecutionResult(); } - var jobs = await ActiveWorkspace.ListJobsAsync(); - if (jobs == null || jobs.Count() == 0) + var jobs = await ActiveWorkspace.ListJobsAsync() ?? new List(); + if (jobs.Count() == 0) { channel.Stderr("No jobs found in current Azure Quantum workspace."); - return AzureClientError.JobNotFound.ToExecutionResult(); } - + return jobs.ToExecutionResult(); } } diff --git a/src/AzureClient/AzureClientError.cs b/src/AzureClient/AzureClientError.cs new file mode 100644 index 0000000000..a5ac286870 --- /dev/null +++ b/src/AzureClient/AzureClientError.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.ComponentModel; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + /// + /// Describes possible error results from methods. + /// + public enum AzureClientError + { + /// + /// Method completed with an unknown error. + /// + [Description(Resources.AzureClientErrorUnknownError)] + UnknownError = 1000, + + /// + /// No connection has been made to any Azure Quantum workspace. + /// + [Description(Resources.AzureClientErrorNotConnected)] + NotConnected, + + /// + /// A target has not yet been configured for job submission. + /// + [Description(Resources.AzureClientErrorNoTarget)] + NoTarget, + + /// + /// The specified target is not valid for job submission. + /// + [Description(Resources.AzureClientErrorInvalidTarget)] + InvalidTarget, + + /// + /// A job meeting the specified criteria was not found. + /// + [Description(Resources.AzureClientErrorJobNotFound)] + JobNotFound, + + /// + /// The result of a job was requested, but the job has not yet completed. + /// + [Description(Resources.AzureClientErrorJobNotCompleted)] + JobNotCompleted, + + /// + /// The job output failed to be downloaded from the Azure storage location. + /// + [Description(Resources.AzureClientErrorJobOutputDownloadFailed)] + JobOutputDownloadFailed, + + /// + /// No Q# operation name was provided where one was required. + /// + [Description(Resources.AzureClientErrorNoOperationName)] + NoOperationName, + + /// + /// The specified Q# operation name is not recognized. + /// + [Description(Resources.AzureClientErrorUnrecognizedOperationName)] + UnrecognizedOperationName, + + /// + /// The specified Q# operation cannot be used as an entry point. + /// + [Description(Resources.AzureClientErrorInvalidEntryPoint)] + InvalidEntryPoint, + + /// + /// The Azure Quantum job submission failed. + /// + [Description(Resources.AzureClientErrorJobSubmissionFailed)] + JobSubmissionFailed, + + /// + /// Authentication with the Azure service failed. + /// + [Description(Resources.AzureClientErrorAuthenticationFailed)] + AuthenticationFailed, + + /// + /// A workspace meeting the specified criteria was not found. + /// + [Description(Resources.AzureClientErrorWorkspaceNotFound)] + WorkspaceNotFound, + } +} diff --git a/src/AzureClient/AzureEnvironment.cs b/src/AzureClient/AzureEnvironment.cs index 729a56ef59..66dc1de62d 100644 --- a/src/AzureClient/AzureEnvironment.cs +++ b/src/AzureClient/AzureEnvironment.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -16,10 +16,11 @@ namespace Microsoft.Quantum.IQSharp.AzureClient { - internal enum AzureEnvironmentType { Production, Canary, Dogfood }; + internal enum AzureEnvironmentType { Production, Canary, Dogfood, Mock }; internal class AzureEnvironment { + public static string EnvironmentVariableName => "AZURE_QUANTUM_ENV"; public AzureEnvironmentType Type { get; private set; } private string SubscriptionId { get; set; } = string.Empty; @@ -34,8 +35,7 @@ private AzureEnvironment() public static AzureEnvironment Create(string subscriptionId) { - var azureEnvironmentEnvVarName = "AZURE_QUANTUM_ENV"; - var azureEnvironmentName = System.Environment.GetEnvironmentVariable(azureEnvironmentEnvVarName); + var azureEnvironmentName = System.Environment.GetEnvironmentVariable(EnvironmentVariableName); if (Enum.TryParse(azureEnvironmentName, true, out AzureEnvironmentType environmentType)) { @@ -47,6 +47,8 @@ public static AzureEnvironment Create(string subscriptionId) return Canary(subscriptionId); case AzureEnvironmentType.Dogfood: return Dogfood(subscriptionId); + case AzureEnvironmentType.Mock: + return Mock(); default: throw new InvalidOperationException("Unexpected EnvironmentType value."); } @@ -57,6 +59,12 @@ public static AzureEnvironment Create(string subscriptionId) public async Task GetAuthenticatedWorkspaceAsync(IChannel channel, string resourceGroupName, string workspaceName, bool refreshCredentials) { + if (Type == AzureEnvironmentType.Mock) + { + channel.Stdout("AZURE_QUANTUM_ENV set to Mock. Using mock Azure workspace rather than connecting to the real service."); + return new MockAzureWorkspace(workspaceName); + } + // Find the token cache folder var cacheDirectoryEnvVarName = "AZURE_QUANTUM_TOKEN_CACHE"; var cacheDirectory = System.Environment.GetEnvironmentVariable(cacheDirectoryEnvVarName); @@ -154,6 +162,9 @@ private static AzureEnvironment Canary(string subscriptionId) return canary; } + private static AzureEnvironment Mock() => + new AzureEnvironment() { Type = AzureEnvironmentType.Mock }; + private static string GetDogfoodAuthority(string subscriptionId) { try diff --git a/src/AzureClient/AzureExecutionTarget.cs b/src/AzureClient/AzureExecutionTarget.cs index d56064efa5..f2cebf24f0 100644 --- a/src/AzureClient/AzureExecutionTarget.cs +++ b/src/AzureClient/AzureExecutionTarget.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -11,7 +11,7 @@ internal enum AzureProvider { IonQ, Honeywell, QCI } internal class AzureExecutionTarget { - public string TargetId { get; private set; } + public string TargetId { get; private set; } = string.Empty; public string PackageName => $"Microsoft.Quantum.Providers.{GetProvider(TargetId)}"; public static bool IsValid(string targetId) => GetProvider(targetId) != null; diff --git a/src/AzureClient/AzureSubmissionContext.cs b/src/AzureClient/AzureSubmissionContext.cs index bfe023e9fc..f299d90694 100644 --- a/src/AzureClient/AzureSubmissionContext.cs +++ b/src/AzureClient/AzureSubmissionContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/AzureWorkspace.cs b/src/AzureClient/AzureWorkspace.cs index 54fb782abc..a5083c442b 100644 --- a/src/AzureClient/AzureWorkspace.cs +++ b/src/AzureClient/AzureWorkspace.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Extensions.cs b/src/AzureClient/Extensions.cs index 1fda56014e..6e4cc96c56 100644 --- a/src/AzureClient/Extensions.cs +++ b/src/AzureClient/Extensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -37,24 +37,9 @@ internal static ExecutionResult ToExecutionResult(this AzureClientError azureCli new ExecutionResult { Status = ExecuteStatus.Error, - Output = azureClientError.ToDescription() + Output = azureClientError, }; - /// - /// Returns the string value of the for the given - /// enumeration value. - /// - /// - /// - internal static string ToDescription(this AzureClientError azureClientError) - { - var attributes = azureClientError - .GetType() - .GetField(azureClientError.ToString()) - .GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[]; - return attributes?.Length > 0 ? attributes[0].Description : string.Empty; - } - /// /// Encapsulates a given as the result of an execution. /// diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index d6c67678ff..441abc7bff 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -10,90 +10,6 @@ namespace Microsoft.Quantum.IQSharp.AzureClient { - /// - /// Describes possible error results from methods. - /// - public enum AzureClientError - { - /// - /// Method completed with an unknown error. - /// - [Description(Resources.AzureClientErrorUnknownError)] - UnknownError, - - /// - /// No connection has been made to any Azure Quantum workspace. - /// - [Description(Resources.AzureClientErrorNotConnected)] - NotConnected, - - /// - /// A target has not yet been configured for job submission. - /// - [Description(Resources.AzureClientErrorNoTarget)] - NoTarget, - - /// - /// The specified target is not valid for job submission. - /// - [Description(Resources.AzureClientErrorInvalidTarget)] - InvalidTarget, - - /// - /// A job meeting the specified criteria was not found. - /// - [Description(Resources.AzureClientErrorJobNotFound)] - JobNotFound, - - /// - /// The result of a job was requested, but the job has not yet completed. - /// - [Description(Resources.AzureClientErrorJobNotCompleted)] - JobNotCompleted, - - /// - /// The job output failed to be downloaded from the Azure storage location. - /// - [Description(Resources.AzureClientErrorJobOutputDownloadFailed)] - JobOutputDownloadFailed, - - /// - /// No Q# operation name was provided where one was required. - /// - [Description(Resources.AzureClientErrorNoOperationName)] - NoOperationName, - - /// - /// The specified Q# operation name is not recognized. - /// - [Description(Resources.AzureClientErrorUnrecognizedOperationName)] - UnrecognizedOperationName, - - /// - /// The specified Q# operation cannot be used as an entry point. - /// - [Description(Resources.AzureClientErrorInvalidEntryPoint)] - InvalidEntryPoint, - - /// - /// The Azure Quantum job submission failed. - /// - [Description(Resources.AzureClientErrorJobSubmissionFailed)] - JobSubmissionFailed, - - /// - /// Authentication with the Azure service failed. - /// - [Description(Resources.AzureClientErrorAuthenticationFailed)] - AuthenticationFailed, - - /// - /// A workspace meeting the specified criteria was not found. - /// - [Description(Resources.AzureClientErrorWorkspaceNotFound)] - WorkspaceNotFound, - } - /// /// This service is capable of connecting to Azure Quantum workspaces /// and submitting jobs. diff --git a/src/AzureClient/IAzureWorkspace.cs b/src/AzureClient/IAzureWorkspace.cs index a08152b3b7..1c2c71b813 100644 --- a/src/AzureClient/IAzureWorkspace.cs +++ b/src/AzureClient/IAzureWorkspace.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -13,11 +13,11 @@ namespace Microsoft.Quantum.IQSharp.AzureClient { internal interface IAzureWorkspace { - public string Name { get; } + public string? Name { get; } - public Task> GetProvidersAsync(); - public Task GetJobAsync(string jobId); - public Task> ListJobsAsync(); + public Task?> GetProvidersAsync(); + public Task GetJobAsync(string jobId); + public Task?> ListJobsAsync(); public IQuantumMachine? CreateQuantumMachine(string targetId, string storageAccountConnectionString); } } \ No newline at end of file diff --git a/src/AzureClient/Magic/AzureClientMagicBase.cs b/src/AzureClient/Magic/AzureClientMagicBase.cs index f7eac3b5ce..823554c48f 100644 --- a/src/AzureClient/Magic/AzureClientMagicBase.cs +++ b/src/AzureClient/Magic/AzureClientMagicBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/ConnectMagic.cs b/src/AzureClient/Magic/ConnectMagic.cs index 559c04e3e7..ffdceff925 100644 --- a/src/AzureClient/Magic/ConnectMagic.cs +++ b/src/AzureClient/Magic/ConnectMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/ExecuteMagic.cs b/src/AzureClient/Magic/ExecuteMagic.cs index 6a3080d274..122882abe5 100644 --- a/src/AzureClient/Magic/ExecuteMagic.cs +++ b/src/AzureClient/Magic/ExecuteMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/JobsMagic.cs b/src/AzureClient/Magic/JobsMagic.cs index f23708d9ea..590e77b418 100644 --- a/src/AzureClient/Magic/JobsMagic.cs +++ b/src/AzureClient/Magic/JobsMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/OutputMagic.cs b/src/AzureClient/Magic/OutputMagic.cs index 17ca257a46..dd542d7329 100644 --- a/src/AzureClient/Magic/OutputMagic.cs +++ b/src/AzureClient/Magic/OutputMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/StatusMagic.cs b/src/AzureClient/Magic/StatusMagic.cs index e7eaa56d6c..80a2a85733 100644 --- a/src/AzureClient/Magic/StatusMagic.cs +++ b/src/AzureClient/Magic/StatusMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/SubmitMagic.cs b/src/AzureClient/Magic/SubmitMagic.cs index 516646382e..3c7bcfccdd 100644 --- a/src/AzureClient/Magic/SubmitMagic.cs +++ b/src/AzureClient/Magic/SubmitMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Magic/TargetMagic.cs b/src/AzureClient/Magic/TargetMagic.cs index 778f88ab74..d0c93be7e1 100644 --- a/src/AzureClient/Magic/TargetMagic.cs +++ b/src/AzureClient/Magic/TargetMagic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Mocks/MockAzureWorkspace.cs b/src/AzureClient/Mocks/MockAzureWorkspace.cs new file mode 100644 index 0000000000..212d605714 --- /dev/null +++ b/src/AzureClient/Mocks/MockAzureWorkspace.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum; +using Microsoft.Azure.Quantum.Client.Models; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal class MockAzureWorkspace : IAzureWorkspace + { + public const string NameWithMockProviders = "WorkspaceNameWithMockProviders"; + + public string Name { get; private set; } + + public List Providers { get; } = new List(); + + public List Jobs { get; } = new List(); + + public MockAzureWorkspace(string workspaceName) + { + Name = workspaceName; + if (Name == NameWithMockProviders) + { + // add a mock target for each provider: "ionq.mock", "honeywell.mock", etc. + AddMockTargets( + Enum.GetNames(typeof(AzureProvider)) + .Select(provider => $"{provider.ToLowerInvariant()}.mock") + .ToArray()); + } + } + + public async Task GetJobAsync(string jobId) => Jobs.FirstOrDefault(job => job.Id == jobId); + + public async Task?> GetProvidersAsync() => Providers; + + public async Task?> ListJobsAsync() => Jobs; + + public IQuantumMachine? CreateQuantumMachine(string targetId, string storageAccountConnectionString) => new MockQuantumMachine(this); + + public void AddMockJobs(params string[] jobIds) + { + foreach (var jobId in jobIds) + { + var mockJob = new MockCloudJob(); + mockJob.Details.Id = jobId; + Jobs.Add(mockJob); + } + } + + public void AddMockTargets(params string[] targetIds) + { + var targets = targetIds.Select(id => new TargetStatus(id)).ToList(); + Providers.Add(new ProviderStatus(null, null, targets)); + } + } +} \ No newline at end of file diff --git a/src/AzureClient/Mocks/MockCloudJob.cs b/src/AzureClient/Mocks/MockCloudJob.cs new file mode 100644 index 0000000000..73d6cc789d --- /dev/null +++ b/src/AzureClient/Mocks/MockCloudJob.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using Microsoft.Azure.Quantum; +using Microsoft.Azure.Quantum.Client.Models; +using System; +using System.IO; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal class MockCloudJob : CloudJob + { + public MockCloudJob() + : base( + new Azure.Quantum.Workspace("mockSubscriptionId", "mockResourceGroupName", "mockWorkspaceName"), + new JobDetails( + containerUri: null, + inputDataFormat: null, + providerId: null, + target: null, + id: Guid.NewGuid().ToString(), + status: "Succeeded", + outputDataUri: CreateMockOutputFileUri() + )) + { + } + + private static string CreateMockOutputFileUri() + { + var tempFilePath = Path.GetTempFileName(); + using var outputFile = new StreamWriter(tempFilePath); + outputFile.WriteLine(@"{'Histogram':['0',0.5,'1',0.5]}"); + return new Uri(tempFilePath).AbsoluteUri; + } + } +} \ No newline at end of file diff --git a/src/AzureClient/Mocks/MockQuantumMachine.cs b/src/AzureClient/Mocks/MockQuantumMachine.cs new file mode 100644 index 0000000000..9e6c28411f --- /dev/null +++ b/src/AzureClient/Mocks/MockQuantumMachine.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Quantum.Runtime; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal class MockQuantumMachine : IQuantumMachine + { + public string ProviderId => throw new NotImplementedException(); + + public string Target => throw new NotImplementedException(); + + private MockAzureWorkspace? Workspace { get; } + + public MockQuantumMachine(MockAzureWorkspace? workspace = null) => Workspace = workspace; + + public Task> ExecuteAsync(EntryPointInfo info, TInput input) + => ExecuteAsync(info, input, null as IQuantumMachineSubmissionContext); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext) + => ExecuteAsync(info, input, submissionContext, null as IQuantumMachine.ConfigureJob); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext, IQuantumMachine.ConfigureJob? configureJobCallback) + => ExecuteAsync(info, input, submissionContext, null, configureJobCallback); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineExecutionContext? executionContext) + => ExecuteAsync(info, input, executionContext, null); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineExecutionContext? executionContext, IQuantumMachine.ConfigureJob? configureJobCallback) + => ExecuteAsync(info, input, null, executionContext); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext, IQuantumMachineExecutionContext? executionContext) + => ExecuteAsync(info, input, submissionContext, executionContext, null); + + public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext, IQuantumMachineExecutionContext? executionContext, IQuantumMachine.ConfigureJob? configureJobCallback) + => throw new NotImplementedException(); + + public Task SubmitAsync(EntryPointInfo info, TInput input) + => SubmitAsync(info, input, null); + + public Task SubmitAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext) + => SubmitAsync(info, input, submissionContext, null); + + public Task SubmitAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext? submissionContext, IQuantumMachine.ConfigureJob? configureJobCallback) + { + var job = new MockCloudJob(); + Workspace?.AddMockJobs(job.Id); + return Task.FromResult(job as IQuantumMachineJob); + } + + public (bool IsValid, string Message) Validate(EntryPointInfo info, TInput input) + => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/AzureClient/Visualization/AzureClientErrorEncoders.cs b/src/AzureClient/Visualization/AzureClientErrorEncoders.cs new file mode 100644 index 0000000000..4848d7a8a3 --- /dev/null +++ b/src/AzureClient/Visualization/AzureClientErrorEncoders.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Microsoft.Jupyter.Core; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Quantum.IQSharp.AzureClient +{ + internal static class AzureClientErrorExtensions + { + /// + /// Returns the string value of the for the given + /// enumeration value. + /// + internal static string ToDescription(this AzureClientError error) + { + var attributes = error + .GetType() + .GetField(error.ToString()) + .GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[]; + return attributes?.Length > 0 ? attributes[0].Description : string.Empty; + } + + /// + /// Returns a dictionary representing the properties of the . + /// + internal static Dictionary ToDictionary(this AzureClientError error) => + new Dictionary() + { + ["error_code"] = System.Convert.ToInt32(error), + ["error_name"] = error.ToString(), + ["error_description"] = error.ToDescription(), + }; + } + + public class AzureClientErrorToHtmlEncoder : IResultEncoder + { + public string MimeType => MimeTypes.Html; + + public EncodedData? Encode(object displayable) => (displayable as AzureClientError?)?.ToDescription().ToEncodedData(); + } + + public class AzureClientErrorToTextEncoder : IResultEncoder + { + public string MimeType => MimeTypes.PlainText; + + public EncodedData? Encode(object displayable) => (displayable as AzureClientError?)?.ToDescription().ToEncodedData(); + } + + public class AzureClientErrorJsonConverter : JsonConverter + { + public override AzureClientError ReadJson(JsonReader reader, Type objectType, AzureClientError existingValue, bool hasExistingValue, JsonSerializer serializer) => + throw new NotImplementedException(); + + public override void WriteJson(JsonWriter writer, AzureClientError value, JsonSerializer serializer) => + JToken.FromObject(value.ToDictionary()).WriteTo(writer); + } +} diff --git a/src/AzureClient/Visualization/CloudJobEncoders.cs b/src/AzureClient/Visualization/CloudJobEncoders.cs index 8e5f9b5610..d4b20ced6f 100644 --- a/src/AzureClient/Visualization/CloudJobEncoders.cs +++ b/src/AzureClient/Visualization/CloudJobEncoders.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -31,9 +31,9 @@ internal static Dictionary ToDictionary(this CloudJob cloudJob) ["status"] = cloudJob.Status, ["provider"] = cloudJob.Details.ProviderId, ["target"] = cloudJob.Details.Target, - ["creationTime"] = cloudJob.Details.CreationTime.ToDateTime()?.ToUniversalTime(), - ["beginExecutionTime"] = cloudJob.Details.BeginExecutionTime.ToDateTime()?.ToUniversalTime(), - ["endExecutionTime"] = cloudJob.Details.EndExecutionTime.ToDateTime()?.ToUniversalTime(), + ["creation_time"] = cloudJob.Details.CreationTime.ToDateTime()?.ToUniversalTime(), + ["begin_execution_time"] = cloudJob.Details.BeginExecutionTime.ToDateTime()?.ToUniversalTime(), + ["end_execution_time"] = cloudJob.Details.EndExecutionTime.ToDateTime()?.ToUniversalTime(), }; internal static Table ToJupyterTable(this IEnumerable jobsList) => diff --git a/src/AzureClient/Visualization/HistogramEncoders.cs b/src/AzureClient/Visualization/HistogramEncoders.cs index befbbf095b..41f3f0698b 100644 --- a/src/AzureClient/Visualization/HistogramEncoders.cs +++ b/src/AzureClient/Visualization/HistogramEncoders.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable diff --git a/src/AzureClient/Visualization/JsonConverters.cs b/src/AzureClient/Visualization/JsonConverters.cs index 24f26f2515..394370155a 100644 --- a/src/AzureClient/Visualization/JsonConverters.cs +++ b/src/AzureClient/Visualization/JsonConverters.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -19,7 +19,8 @@ public static class JsonConverters new CloudJobJsonConverter(), new CloudJobListJsonConverter(), new TargetStatusJsonConverter(), - new TargetStatusListJsonConverter() + new TargetStatusListJsonConverter(), + new AzureClientErrorJsonConverter() ); public static JsonConverter[] AllConverters => allConverters.ToArray(); diff --git a/src/AzureClient/Visualization/TargetStatusEncoders.cs b/src/AzureClient/Visualization/TargetStatusEncoders.cs index 1de24e7bc0..6925163fcb 100644 --- a/src/AzureClient/Visualization/TargetStatusEncoders.cs +++ b/src/AzureClient/Visualization/TargetStatusEncoders.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #nullable enable @@ -20,8 +20,8 @@ internal static Dictionary ToDictionary(this TargetStatus target new Dictionary() { ["id"] = target.Id, - ["currentAvailability"] = target.CurrentAvailability, - ["averageQueueTime"] = target.AverageQueueTime, + ["current_availability"] = target.CurrentAvailability, + ["average_queue_time"] = target.AverageQueueTime, }; internal static Table ToJupyterTable(this IEnumerable targets) => diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py index f69fb83a00..e9c9abe633 100644 --- a/src/Python/qsharp/azure.py +++ b/src/Python/qsharp/azure.py @@ -11,11 +11,7 @@ import qsharp import json -import typing -from typing import List, Dict, Callable, Any - -from qsharp.serialization import map_tuples -from typing import List, Tuple, Dict, Iterable +from typing import List, Dict, Callable, Any, Union from enum import Enum ## LOGGING ## @@ -33,27 +29,92 @@ 'status', 'output', 'jobs' + 'AzureTarget', + 'AzureJob', + 'AzureError' ] +## CLASSES ## + +class AzureTarget(object): + """ + Represents an instance of an Azure Quantum execution target for Q# job submission. + """ + def __init__(self, data: Dict): + self.__dict__ = data + self.id = data["id"] + self.current_availability = data["current_availability"] + self.average_queue_time = data["average_queue_time"] + + def __eq__(self, other): + if not isinstance(other, AzureTarget): + # don't attempt to compare against unrelated types + return NotImplemented + return self.__dict__ == other.__dict__ + +class AzureJob(object): + """ + Represents an instance of an Azure Quantum job. + """ + def __init__(self, data: Dict): + self.__dict__ = data + self.id = data["id"] + self.name = data["name"] + self.status = data["status"] + self.provider = data["provider"] + self.target = data["target"] + self.creation_time = data["creation_time"] + self.begin_execution_time = data["begin_execution_time"] + self.end_execution_time = data["end_execution_time"] + + def __eq__(self, other): + if not isinstance(other, AzureJob): + # don't attempt to compare against unrelated types + return NotImplemented + return self.__dict__ == other.__dict__ + +class AzureError(object): + """ + Contains error information resulting from an attempt to interact with Azure. + """ + def __init__(self, data: Dict): + self.__dict__ = data + self.error_code = data["error_code"] + self.error_name = data["error_name"] + self.error_description = data["error_description"] + + def __eq__(self, other): + if not isinstance(other, AzureError): + # don't attempt to compare against unrelated types + return NotImplemented + return self.__dict__ == other.__dict__ + ## FUNCTIONS ## -def connect(**params) -> Any: - return qsharp.client._execute_magic(f"azure.connect", raise_on_stderr=False, **params) +def connect(**params) -> Union[List[AzureTarget], AzureError]: + result = qsharp.client._execute_magic(f"azure.connect", raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else [AzureTarget(target) for target in result] -def target(name : str = '', **params) -> Any: - return qsharp.client._execute_magic(f"azure.target {name}", raise_on_stderr=False, **params) +def target(name : str = '', **params) -> Union[AzureTarget, AzureError]: + result = qsharp.client._execute_magic(f"azure.target {name}", raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else AzureTarget(result) -def submit(op, **params) -> Any: - return qsharp.client._execute_callable_magic("azure.submit", op, raise_on_stderr=False, **params) +def submit(op, **params) -> Union[AzureJob, AzureError]: + result = qsharp.client._execute_callable_magic("azure.submit", op, raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else AzureJob(result) -def execute(op, **params) -> Any: - return qsharp.client._execute_callable_magic("azure.execute", op, raise_on_stderr=False, **params) +def execute(op, **params) -> Union[Dict, AzureError]: + result = qsharp.client._execute_callable_magic("azure.execute", op, raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else result -def status(jobId : str = '', **params) -> Any: - return qsharp.client._execute_magic(f"azure.status {jobId}", raise_on_stderr=False, **params) +def status(jobId : str = '', **params) -> Union[AzureJob, AzureError]: + result = qsharp.client._execute_magic(f"azure.status {jobId}", raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else AzureJob(result) -def output(jobId : str = '', **params) -> Any: - return qsharp.client._execute_magic(f"azure.output {jobId}", raise_on_stderr=False, **params) +def output(jobId : str = '', **params) -> Union[Dict, AzureError]: + result = qsharp.client._execute_magic(f"azure.output {jobId}", raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else result -def jobs(**params) -> Any: - return qsharp.client._execute_magic(f"azure.jobs", raise_on_stderr=False, **params) +def jobs(**params) -> Union[List[AzureJob], AzureError]: + result = qsharp.client._execute_magic(f"azure.jobs", raise_on_stderr=False, **params) + return AzureError(result) if "error_code" in result else [AzureJob(job) for job in result] diff --git a/src/Python/qsharp/tests/test_azure.py b/src/Python/qsharp/tests/test_azure.py new file mode 100644 index 0000000000..4e195e4f88 --- /dev/null +++ b/src/Python/qsharp/tests/test_azure.py @@ -0,0 +1,102 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +## +# test_azure.py: Tests Azure Quantum functionality against a mock workspace. +## +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +## + +## IMPORTS ## + +import importlib +import os +import pytest +import qsharp +from qsharp.azure import AzureError, AzureJob, AzureTarget +import sys + +## SETUP ## + +@pytest.fixture(scope="session", autouse=True) +def set_environment_variables(): + # Need to restart the IQ# kernel after setting the environment variable + os.environ["AZURE_QUANTUM_ENV"] = "mock" + importlib.reload(qsharp) + if "qsharp.chemistry" in sys.modules: + importlib.reload(qsharp.chemistry) + +## TESTS ## + +def test_empty_workspace(monkeypatch): + """ + Tests behavior of a mock workspace with no providers. + """ + targets = qsharp.azure.connect( + storageAccountConnectionString="test", + subscriptionId="test", + resourceGroupName="test", + workspaceName="test" + ) + assert targets == [] + + result = qsharp.azure.target("invalid.target") + assert isinstance(result, AzureError) + + jobs = qsharp.azure.jobs() + assert jobs == [] + +def test_workspace_with_providers(): + """ + Tests behavior of a mock workspace with mock providers. + """ + result = qsharp.azure.target() + assert isinstance(result, AzureError) + + targets = qsharp.azure.connect( + storageAccountConnectionString="test", + subscriptionId="test", + resourceGroupName="test", + workspaceName="WorkspaceNameWithMockProviders" + ) + assert isinstance(targets, list) + assert len(targets) > 0 + + for target in targets: + active_target = qsharp.azure.target(target.id) + assert isinstance(active_target, AzureTarget) + assert active_target == target + + # Submit a snippet operation without parameters + op = qsharp.compile(""" + operation HelloQ() : Result + { + Message($"Hello from quantum world!"); + return Zero; + } + """) + + job = qsharp.azure.submit(op) + assert isinstance(job, AzureJob) + + retrieved_job = qsharp.azure.status(job.id) + assert isinstance(retrieved_job, AzureJob) + assert job.id == retrieved_job.id + + # Execute a workspace operation with parameters + op = qsharp.QSharpCallable("Microsoft.Quantum.SanityTests.HelloAgain", None) + + result = qsharp.azure.execute(op) # missing parameters + assert isinstance(result, AzureError) + + histogram = qsharp.azure.execute(op, count=3, name="test") + assert isinstance(histogram, dict) + + retrieved_histogram = qsharp.azure.output() + assert isinstance(retrieved_histogram, dict) + assert histogram == retrieved_histogram + + # Check that both submitted jobs exist in the workspace + jobs = qsharp.azure.jobs() + assert isinstance(jobs, list) + assert len(jobs) == 2 diff --git a/src/Tests/AzureClientEntryPointTests.cs b/src/Tests/AzureClientEntryPointTests.cs index 48d0416c61..a4a121c994 100644 --- a/src/Tests/AzureClientEntryPointTests.cs +++ b/src/Tests/AzureClientEntryPointTests.cs @@ -132,63 +132,4 @@ public async Task InvalidEntryPointOperation() entryPointGenerator.Generate("InvalidEntryPoint", null)); } } - - public class MockQuantumMachine : IQuantumMachine - { - public string ProviderId => throw new NotImplementedException(); - - public string Target => throw new NotImplementedException(); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input) - => ExecuteAsync(info, input, null as IQuantumMachineSubmissionContext); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext) - => ExecuteAsync(info, input, submissionContext, null as IQuantumMachine.ConfigureJob); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext, IQuantumMachine.ConfigureJob configureJobCallback) - => ExecuteAsync(info, input, submissionContext, null, configureJobCallback); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineExecutionContext executionContext) - => ExecuteAsync(info, input, executionContext, null); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineExecutionContext executionContext, IQuantumMachine.ConfigureJob configureJobCallback) - => ExecuteAsync(info, input, null, executionContext); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext, IQuantumMachineExecutionContext executionContext) - => ExecuteAsync(info, input, submissionContext, executionContext, null); - - public Task> ExecuteAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext, IQuantumMachineExecutionContext executionContext, IQuantumMachine.ConfigureJob configureJobCallback) - => throw new NotImplementedException(); - - public Task SubmitAsync(EntryPointInfo info, TInput input) - => SubmitAsync(info, input, null); - - public Task SubmitAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext) - => SubmitAsync(info, input, submissionContext, null); - - public Task SubmitAsync(EntryPointInfo info, TInput input, IQuantumMachineSubmissionContext submissionContext, IQuantumMachine.ConfigureJob configureJobCallback) - => Task.FromResult(new MockQuantumMachineJob() as IQuantumMachineJob); - - public (bool IsValid, string Message) Validate(EntryPointInfo info, TInput input) - => throw new NotImplementedException(); - } - - public class MockQuantumMachineJob : IQuantumMachineJob - { - public bool Failed => throw new NotImplementedException(); - - public string Id => throw new NotImplementedException(); - - public bool InProgress => throw new NotImplementedException(); - - public string Status => throw new NotImplementedException(); - - public bool Succeeded => throw new NotImplementedException(); - - public Uri Uri => throw new NotImplementedException(); - - public Task CancelAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task RefreshAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - } } diff --git a/src/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs index a115085104..099c251268 100644 --- a/src/Tests/AzureClientTests.cs +++ b/src/Tests/AzureClientTests.cs @@ -3,47 +3,84 @@ #nullable enable -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum; +using Microsoft.Azure.Quantum.Client.Models; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Jupyter.Core; -using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.AzureClient; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.IQSharp { - public static class AzureClientTestExtensions - { - } - [TestClass] public class AzureClientTests { - private readonly string subscriptionId = "TEST_SUBSCRIPTION_ID"; - private readonly string resourceGroupName = "TEST_RESOURCE_GROUP_NAME"; - private readonly string workspaceName = "TEST_WORKSPACE_NAME"; - private readonly string storageAccountConnectionString = "TEST_CONNECTION_STRING"; - private readonly string jobId = "TEST_JOB_ID"; - private readonly string operationName = "TEST_OPERATION_NAME"; + private string originalEnvironmentName = string.Empty; + + [TestInitialize] + public void SetMockEnvironment() + { + originalEnvironmentName = Environment.GetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName) ?? string.Empty; + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, AzureEnvironmentType.Mock.ToString()); + } + + [TestCleanup] + public void RestoreEnvironment() + { + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, originalEnvironmentName); + } + + private T ExpectSuccess(Task task) + { + var result = task.GetAwaiter().GetResult(); + Assert.AreEqual(ExecuteStatus.Ok, result.Status); + Assert.IsInstanceOfType(result.Output, typeof(T)); + return (T)result.Output; + } + + private void ExpectError(AzureClientError expectedError, Task task) + { + var result = task.GetAwaiter().GetResult(); + Assert.AreEqual(ExecuteStatus.Error, result.Status); + Assert.IsInstanceOfType(result.Output, typeof(AzureClientError)); + Assert.AreEqual(expectedError, (AzureClientError)result.Output); + } + + private Task ConnectToWorkspaceAsync(IAzureClient azureClient, string workspaceName = "TEST_WORKSPACE_NAME") + { + return azureClient.ConnectAsync( + new MockChannel(), + "TEST_SUBSCRIPTION_ID", + "TEST_RESOURCE_GROUP_NAME", + workspaceName, + "TEST_CONNECTION_STRING"); + } [TestMethod] - public void TestTargets() + public void TestAzureEnvironment() { - var workspace = "Workspace"; - var services = Startup.CreateServiceProvider(workspace); - var azureClient = services.GetService(); + // Production environment + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, AzureEnvironmentType.Production.ToString()); + var environment = AzureEnvironment.Create("TEST_SUBSCRIPTION_ID"); + Assert.AreEqual(AzureEnvironmentType.Production, environment.Type); - // SetActiveTargetAsync with recognized target ID, but not yet connected - var result = azureClient.SetActiveTargetAsync(new MockChannel(), "ionq.simulator").GetAwaiter().GetResult(); - Assert.IsTrue(result.Status == ExecuteStatus.Error); + // Dogfood environment cannot be created in test because it requires a service call + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, AzureEnvironmentType.Dogfood.ToString()); + Assert.ThrowsException(() => AzureEnvironment.Create("TEST_SUBSCRIPTION_ID")); - // SetActiveTargetAsync with unrecognized target ID - result = azureClient.SetActiveTargetAsync(new MockChannel(), "contoso.qpu").GetAwaiter().GetResult(); - Assert.IsTrue(result.Status == ExecuteStatus.Error); + // Canary environment + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, AzureEnvironmentType.Canary.ToString()); + environment = AzureEnvironment.Create("TEST_SUBSCRIPTION_ID"); + Assert.AreEqual(AzureEnvironmentType.Canary, environment.Type); - // GetActiveTargetAsync, but not yet connected - result = azureClient.GetActiveTargetAsync(new MockChannel()).GetAwaiter().GetResult(); - Assert.IsTrue(result.Status == ExecuteStatus.Error); + // Mock environment + Environment.SetEnvironmentVariable(AzureEnvironment.EnvironmentVariableName, AzureEnvironmentType.Mock.ToString()); + environment = AzureEnvironment.Create("TEST_SUBSCRIPTION_ID"); + Assert.AreEqual(AzureEnvironmentType.Mock, environment.Type); } [TestMethod] @@ -55,21 +92,177 @@ public void TestAzureExecutionTarget() targetId = "ionq.targetId"; executionTarget = AzureExecutionTarget.Create(targetId); - Assert.IsNotNull(executionTarget); - Assert.AreEqual(executionTarget.TargetId, targetId); - Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.IonQ"); + Assert.AreEqual(targetId, executionTarget?.TargetId); + Assert.AreEqual("Microsoft.Quantum.Providers.IonQ", executionTarget?.PackageName); targetId = "HonEYWEll.targetId"; executionTarget = AzureExecutionTarget.Create(targetId); - Assert.IsNotNull(executionTarget); - Assert.AreEqual(executionTarget.TargetId, targetId); - Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.Honeywell"); + Assert.AreEqual(targetId, executionTarget?.TargetId); + Assert.AreEqual("Microsoft.Quantum.Providers.Honeywell", executionTarget?.PackageName); targetId = "qci.target.name.qpu"; executionTarget = AzureExecutionTarget.Create(targetId); - Assert.IsNotNull(executionTarget); - Assert.AreEqual(executionTarget.TargetId, targetId); - Assert.AreEqual(executionTarget.PackageName, "Microsoft.Quantum.Providers.QCI"); + Assert.AreEqual(targetId, executionTarget?.TargetId); + Assert.AreEqual("Microsoft.Quantum.Providers.QCI", executionTarget?.PackageName); + } + + [TestMethod] + public void TestJobStatus() + { + var services = Startup.CreateServiceProvider("Workspace"); + var azureClient = (AzureClient)services.GetService(); + + // not connected + ExpectError(AzureClientError.NotConnected, azureClient.GetJobStatusAsync(new MockChannel(), "JOB_ID_1")); + + // connect + var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient)); + Assert.IsFalse(targets.Any()); + + // set up the mock workspace + var azureWorkspace = azureClient.ActiveWorkspace as MockAzureWorkspace; + Assert.IsNotNull(azureWorkspace); + azureWorkspace?.AddMockJobs("JOB_ID_1", "JOB_ID_2"); + + // valid job ID + var job = ExpectSuccess(azureClient.GetJobStatusAsync(new MockChannel(), "JOB_ID_1")); + Assert.AreEqual("JOB_ID_1", job.Id); + + // invalid job ID + ExpectError(AzureClientError.JobNotFound, azureClient.GetJobStatusAsync(new MockChannel(), "JOB_ID_3")); + + // jobs list + var jobs = ExpectSuccess>(azureClient.GetJobListAsync(new MockChannel())); + Assert.AreEqual(2, jobs.Count()); + } + + [TestMethod] + public void TestManualTargets() + { + var services = Startup.CreateServiceProvider("Workspace"); + var azureClient = (AzureClient)services.GetService(); + + // SetActiveTargetAsync with recognized target ID, but not yet connected + ExpectError(AzureClientError.NotConnected, azureClient.SetActiveTargetAsync(new MockChannel(), "ionq.simulator")); + + // GetActiveTargetAsync, but not yet connected + ExpectError(AzureClientError.NotConnected, azureClient.GetActiveTargetAsync(new MockChannel())); + + // connect + var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient)); + Assert.IsFalse(targets.Any()); + + // set up the mock workspace + var azureWorkspace = azureClient.ActiveWorkspace as MockAzureWorkspace; + Assert.IsNotNull(azureWorkspace); + azureWorkspace?.AddMockTargets("ionq.simulator", "honeywell.qpu", "unrecognized.target"); + + // get connection status to verify list of targets + targets = ExpectSuccess>(azureClient.GetConnectionStatusAsync(new MockChannel())); + Assert.AreEqual(2, targets.Count()); // only 2 valid quantum execution targets + + // GetActiveTargetAsync, but no active target set yet + ExpectError(AzureClientError.NoTarget, azureClient.GetActiveTargetAsync(new MockChannel())); + + // SetActiveTargetAsync with target ID not valid for quantum execution + ExpectError(AzureClientError.InvalidTarget, azureClient.SetActiveTargetAsync(new MockChannel(), "unrecognized.target")); + + // SetActiveTargetAsync with valid target ID + var target = ExpectSuccess(azureClient.SetActiveTargetAsync(new MockChannel(), "ionq.simulator")); + Assert.AreEqual("ionq.simulator", target.Id); + + // GetActiveTargetAsync + target = ExpectSuccess(azureClient.GetActiveTargetAsync(new MockChannel())); + Assert.AreEqual("ionq.simulator", target.Id); + } + + [TestMethod] + public void TestAllTargets() + { + var services = Startup.CreateServiceProvider("Workspace"); + var azureClient = (AzureClient)services.GetService(); + + // connect to mock workspace with all providers + var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient, MockAzureWorkspace.NameWithMockProviders)); + Assert.AreEqual(Enum.GetNames(typeof(AzureProvider)).Length, targets.Count()); + + // set each target, which will load the corresponding package + foreach (var target in targets) + { + var returnedTarget = ExpectSuccess(azureClient.SetActiveTargetAsync(new MockChannel(), target.Id)); + Assert.AreEqual(target.Id, returnedTarget.Id); + } + } + + [TestMethod] + public void TestJobSubmission() + { + var services = Startup.CreateServiceProvider("Workspace"); + var azureClient = (AzureClient)services.GetService(); + var submissionContext = new AzureSubmissionContext(); + + // not yet connected + ExpectError(AzureClientError.NotConnected, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + + // connect + var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient)); + Assert.IsFalse(targets.Any()); + + // no target yet + ExpectError(AzureClientError.NoTarget, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + + // add a target + var azureWorkspace = azureClient.ActiveWorkspace as MockAzureWorkspace; + Assert.IsNotNull(azureWorkspace); + azureWorkspace?.AddMockTargets("ionq.simulator"); + + // set the active target + var target = ExpectSuccess(azureClient.SetActiveTargetAsync(new MockChannel(), "ionq.simulator")); + Assert.AreEqual("ionq.simulator", target.Id); + + // no operation name specified + ExpectError(AzureClientError.NoOperationName, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + + // specify an operation name, but have missing parameters + submissionContext.OperationName = "Tests.qss.HelloAgain"; + ExpectError(AzureClientError.JobSubmissionFailed, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + + // specify input parameters and verify that the job was submitted + submissionContext.InputParameters = new Dictionary() { ["count"] = "3", ["name"] = "testing" }; + var job = ExpectSuccess(azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + var retrievedJob = ExpectSuccess(azureClient.GetJobStatusAsync(new MockChannel(), job.Id)); + Assert.AreEqual(job.Id, retrievedJob.Id); + } + + [TestMethod] + public void TestJobExecution() + { + var services = Startup.CreateServiceProvider("Workspace"); + var azureClient = (AzureClient)services.GetService(); + + // connect + var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient)); + Assert.IsFalse(targets.Any()); + + // add a target + var azureWorkspace = azureClient.ActiveWorkspace as MockAzureWorkspace; + Assert.IsNotNull(azureWorkspace); + azureWorkspace?.AddMockTargets("ionq.simulator"); + + // set the active target + var target = ExpectSuccess(azureClient.SetActiveTargetAsync(new MockChannel(), "ionq.simulator")); + Assert.AreEqual("ionq.simulator", target.Id); + + // execute the job and verify that the results are retrieved successfully + var submissionContext = new AzureSubmissionContext() + { + OperationName = "Tests.qss.HelloAgain", + InputParameters = new Dictionary() { ["count"] = "3", ["name"] = "testing" }, + ExecutionTimeout = 5, + ExecutionPollingInterval = 1, + }; + var histogram = ExpectSuccess(azureClient.ExecuteJobAsync(new MockChannel(), submissionContext)); + Assert.IsNotNull(histogram); } } } From fb9e21834b897d3855e2d4293444782041340157 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Sun, 14 Jun 2020 08:26:23 -0700 Subject: [PATCH 02/22] Raise Python exceptions rather than returning error objects --- src/Python/qsharp/azure.py | 37 ++++++++++++++++----------- src/Python/qsharp/tests/test_azure.py | 21 ++++++++++----- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py index e9c9abe633..f2632e8d32 100644 --- a/src/Python/qsharp/azure.py +++ b/src/Python/qsharp/azure.py @@ -73,7 +73,7 @@ def __eq__(self, other): return NotImplemented return self.__dict__ == other.__dict__ -class AzureError(object): +class AzureError(Exception): """ Contains error information resulting from an attempt to interact with Azure. """ @@ -91,30 +91,37 @@ def __eq__(self, other): ## FUNCTIONS ## -def connect(**params) -> Union[List[AzureTarget], AzureError]: +def connect(**params) -> List[AzureTarget]: result = qsharp.client._execute_magic(f"azure.connect", raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else [AzureTarget(target) for target in result] + if "error_code" in result: raise AzureError(result) + return [AzureTarget(target) for target in result] -def target(name : str = '', **params) -> Union[AzureTarget, AzureError]: +def target(name : str = '', **params) -> AzureTarget: result = qsharp.client._execute_magic(f"azure.target {name}", raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else AzureTarget(result) + if "error_code" in result: raise AzureError(result) + return AzureTarget(result) -def submit(op, **params) -> Union[AzureJob, AzureError]: +def submit(op, **params) -> AzureJob: result = qsharp.client._execute_callable_magic("azure.submit", op, raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else AzureJob(result) + if "error_code" in result: raise AzureError(result) + return AzureJob(result) -def execute(op, **params) -> Union[Dict, AzureError]: +def execute(op, **params) -> Dict: result = qsharp.client._execute_callable_magic("azure.execute", op, raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else result + if "error_code" in result: raise AzureError(result) + return result -def status(jobId : str = '', **params) -> Union[AzureJob, AzureError]: +def status(jobId : str = '', **params) -> AzureJob: result = qsharp.client._execute_magic(f"azure.status {jobId}", raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else AzureJob(result) + if "error_code" in result: raise AzureError(result) + return AzureJob(result) -def output(jobId : str = '', **params) -> Union[Dict, AzureError]: +def output(jobId : str = '', **params) -> Dict: result = qsharp.client._execute_magic(f"azure.output {jobId}", raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else result + if "error_code" in result: raise AzureError(result) + return result -def jobs(**params) -> Union[List[AzureJob], AzureError]: +def jobs(**params) -> List[AzureJob]: result = qsharp.client._execute_magic(f"azure.jobs", raise_on_stderr=False, **params) - return AzureError(result) if "error_code" in result else [AzureJob(job) for job in result] + if "error_code" in result: raise AzureError(result) + return [AzureJob(job) for job in result] diff --git a/src/Python/qsharp/tests/test_azure.py b/src/Python/qsharp/tests/test_azure.py index 4e195e4f88..07bd7e6cf9 100644 --- a/src/Python/qsharp/tests/test_azure.py +++ b/src/Python/qsharp/tests/test_azure.py @@ -32,6 +32,10 @@ def test_empty_workspace(monkeypatch): """ Tests behavior of a mock workspace with no providers. """ + with pytest.raises(AzureError) as exception_info: + qsharp.azure.target() # not yet connected + assert exception_info.value.error_name == "NotConnected" + targets = qsharp.azure.connect( storageAccountConnectionString="test", subscriptionId="test", @@ -40,8 +44,9 @@ def test_empty_workspace(monkeypatch): ) assert targets == [] - result = qsharp.azure.target("invalid.target") - assert isinstance(result, AzureError) + with pytest.raises(AzureError) as exception_info: + qsharp.azure.target("invalid.target") # invalid target + assert exception_info.value.error_name == "InvalidTarget" jobs = qsharp.azure.jobs() assert jobs == [] @@ -50,9 +55,6 @@ def test_workspace_with_providers(): """ Tests behavior of a mock workspace with mock providers. """ - result = qsharp.azure.target() - assert isinstance(result, AzureError) - targets = qsharp.azure.connect( storageAccountConnectionString="test", subscriptionId="test", @@ -62,6 +64,10 @@ def test_workspace_with_providers(): assert isinstance(targets, list) assert len(targets) > 0 + with pytest.raises(AzureError) as exception_info: + qsharp.azure.target() # no target specified yet + assert exception_info.value.error_name == "NoTarget" + for target in targets: active_target = qsharp.azure.target(target.id) assert isinstance(active_target, AzureTarget) @@ -86,8 +92,9 @@ def test_workspace_with_providers(): # Execute a workspace operation with parameters op = qsharp.QSharpCallable("Microsoft.Quantum.SanityTests.HelloAgain", None) - result = qsharp.azure.execute(op) # missing parameters - assert isinstance(result, AzureError) + with pytest.raises(AzureError) as exception_info: + qsharp.azure.execute(op) # missing parameters + assert exception_info.value.error_name == "JobSubmissionFailed" histogram = qsharp.azure.execute(op, count=3, name="test") assert isinstance(histogram, dict) From a6391c7199061b216d58718b2affc88b225925d8 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 15 Jun 2020 07:42:26 -0700 Subject: [PATCH 03/22] Fix nullable warnings --- src/AzureClient/Visualization/CloudJobEncoders.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AzureClient/Visualization/CloudJobEncoders.cs b/src/AzureClient/Visualization/CloudJobEncoders.cs index d4b20ced6f..4d8d9c8a3a 100644 --- a/src/AzureClient/Visualization/CloudJobEncoders.cs +++ b/src/AzureClient/Visualization/CloudJobEncoders.cs @@ -22,8 +22,8 @@ internal static class CloudJobExtensions ? dateTime : null as DateTime?; - internal static Dictionary ToDictionary(this CloudJob cloudJob) => - new Dictionary() + internal static Dictionary ToDictionary(this CloudJob cloudJob) => + new Dictionary() { // TODO: add cloudJob.Uri after https://github.com/microsoft/qsharp-runtime/issues/236 is fixed. ["id"] = cloudJob.Id, @@ -47,11 +47,11 @@ internal static Table ToJupyterTable(this IEnumerable jobsLi ("Job Status", cloudJob => cloudJob.Status), ("Provider", cloudJob => cloudJob.Details.ProviderId), ("Target", cloudJob => cloudJob.Details.Target), - ("Creation Time", cloudJob => cloudJob.Details.CreationTime.ToDateTime()?.ToString()), - ("Begin Execution Time", cloudJob => cloudJob.Details.BeginExecutionTime.ToDateTime()?.ToString()), - ("End Execution Time", cloudJob => cloudJob.Details.EndExecutionTime.ToDateTime()?.ToString()), + ("Creation Time", cloudJob => cloudJob.Details.CreationTime.ToDateTime()?.ToString() ?? string.Empty), + ("Begin Execution Time", cloudJob => cloudJob.Details.BeginExecutionTime.ToDateTime()?.ToString() ?? string.Empty), + ("End Execution Time", cloudJob => cloudJob.Details.EndExecutionTime.ToDateTime()?.ToString() ?? string.Empty), }, - Rows = jobsList.OrderByDescending(job => job.Details.CreationTime).ToList() + Rows = jobsList.OrderByDescending(job => job.Details.CreationTime).ToList(), }; } From 2fed6476b65356ae8096202cda68a40ef2ac6e8d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 15 Jun 2020 14:09:17 -0700 Subject: [PATCH 04/22] Minor cleanup --- src/Python/qsharp/tests/test_azure.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Python/qsharp/tests/test_azure.py b/src/Python/qsharp/tests/test_azure.py index 07bd7e6cf9..f626a759c6 100644 --- a/src/Python/qsharp/tests/test_azure.py +++ b/src/Python/qsharp/tests/test_azure.py @@ -28,12 +28,12 @@ def set_environment_variables(): ## TESTS ## -def test_empty_workspace(monkeypatch): +def test_empty_workspace(): """ Tests behavior of a mock workspace with no providers. """ with pytest.raises(AzureError) as exception_info: - qsharp.azure.target() # not yet connected + qsharp.azure.target() assert exception_info.value.error_name == "NotConnected" targets = qsharp.azure.connect( @@ -45,7 +45,7 @@ def test_empty_workspace(monkeypatch): assert targets == [] with pytest.raises(AzureError) as exception_info: - qsharp.azure.target("invalid.target") # invalid target + qsharp.azure.target("invalid.target") assert exception_info.value.error_name == "InvalidTarget" jobs = qsharp.azure.jobs() @@ -65,7 +65,7 @@ def test_workspace_with_providers(): assert len(targets) > 0 with pytest.raises(AzureError) as exception_info: - qsharp.azure.target() # no target specified yet + qsharp.azure.target() assert exception_info.value.error_name == "NoTarget" for target in targets: @@ -93,7 +93,7 @@ def test_workspace_with_providers(): op = qsharp.QSharpCallable("Microsoft.Quantum.SanityTests.HelloAgain", None) with pytest.raises(AzureError) as exception_info: - qsharp.azure.execute(op) # missing parameters + qsharp.azure.execute(op) assert exception_info.value.error_name == "JobSubmissionFailed" histogram = qsharp.azure.execute(op, count=3, name="test") From 17c24e80d9628cc6a5f581590368097e85b4ec24 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Tue, 16 Jun 2020 16:51:29 -0700 Subject: [PATCH 05/22] Add missing Dedent() in ConfigMagic --- src/Kernel/Magic/ConfigMagic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kernel/Magic/ConfigMagic.cs b/src/Kernel/Magic/ConfigMagic.cs index 6a020af780..586a8a6cbb 100644 --- a/src/Kernel/Magic/ConfigMagic.cs +++ b/src/Kernel/Magic/ConfigMagic.cs @@ -45,7 +45,7 @@ save those options to a JSON file in the current working dump.basisStateLabelingConvention ""BigEndian"" dump.truncateSmallAmplitudes true ``` - ", + ".Dedent(), @" Configure the `DumpMachine` and `DumpRegister` callables From 8e381a45e5b5cdaa9e684d3334b7d17af133fea9 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Thu, 18 Jun 2020 14:59:38 -0700 Subject: [PATCH 06/22] Support kernel interrupt during job execution --- src/AzureClient/AzureClient.cs | 8 +++- .../Events/KernelInterruptRequestedEvent.cs | 40 +++++++++++++++++++ src/Kernel/IQSharpEngine.cs | 15 +++++++ src/Tests/Mocks.cs | 3 +- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/Jupyter/Events/KernelInterruptRequestedEvent.cs diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index c03ad4a61f..94e1aba246 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; +using Microsoft.Quantum.IQSharp.Jupyter; using Microsoft.Quantum.Simulation.Common; namespace Microsoft.Quantum.IQSharp.AzureClient @@ -23,6 +24,7 @@ public class AzureClient : IAzureClient private ILogger Logger { get; } private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } + private IEventService EventService { get; } private string ConnectionString { get; set; } = string.Empty; private AzureExecutionTarget? ActiveTarget { get; set; } private IAzureWorkspace? ActiveWorkspace { get; set; } @@ -45,6 +47,8 @@ public AzureClient( References = references; EntryPointGenerator = entryPointGenerator; Logger = logger; + EventService = eventService; + eventService?.TriggerServiceInitialized(this); if (engine is BaseEngine baseEngine) @@ -177,11 +181,11 @@ private async Task SubmitOrExecuteJobAsync(IChannel channel, Az using (var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) { + EventService.OnKernelInterruptRequested().On += (engine) => cts.Cancel(); + CloudJob? cloudJob = null; do { - // TODO: Once jupyter-core supports interrupt requests (https://github.com/microsoft/jupyter-core/issues/55), - // handle Jupyter kernel interrupt here and break out of this loop await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval)); if (cts.IsCancellationRequested) break; cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); diff --git a/src/Jupyter/Events/KernelInterruptRequestedEvent.cs b/src/Jupyter/Events/KernelInterruptRequestedEvent.cs new file mode 100644 index 0000000000..6c062b3181 --- /dev/null +++ b/src/Jupyter/Events/KernelInterruptRequestedEvent.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Jupyter.Core; + +namespace Microsoft.Quantum.IQSharp.Jupyter +{ + /// + /// A event to trigger when a client requests interruption of the current cell execution. + /// + public class KernelInterruptRequestedEvent : Event + { + } + + /// + /// Extension methods to make it easy to consume and trigger KernelInterruptRequested events. + /// + public static class KernelInterruptRequestedEventExtensions + { + /// + /// Instantiate and trigger the event, invoking all subscriber actions. + /// + /// The event service where the EventSubPub lives. + /// The instance for which interrupt is requested. + public static void TriggerKernelInterruptRequested(this IEventService eventService, IExecutionEngine engine) + { + eventService?.Trigger(engine); + } + + /// + /// Gets the typed EventPubSub for the InterruptRequested event. + /// + /// The event service where the EventSubPub lives. + /// The typed EventPubSub for the KernelInterruptRequested event. + public static EventPubSub OnKernelInterruptRequested(this IEventService eventService) + { + return eventService?.Events(); + } + } +} \ No newline at end of file diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 88122f5271..777dcce1bf 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -50,6 +50,7 @@ IMagicSymbolResolver magicSymbolResolver this.Snippets = services.GetService(); this.SymbolsResolver = services.GetService(); this.MagicResolver = magicSymbolResolver; + this.EventService = eventService; RegisterDisplayEncoder(new IQSharpSymbolToHtmlResultEncoder()); RegisterDisplayEncoder(new IQSharpSymbolToTextResultEncoder()); @@ -88,6 +89,8 @@ IMagicSymbolResolver magicSymbolResolver internal ISymbolResolver MagicResolver { get; } + internal IEventService EventService { get; } + /// /// This is the method used to execute Jupyter "normal" cells. In this case, a normal /// cell is expected to have a Q# snippet, which gets compiled and we return the name of @@ -128,6 +131,18 @@ public override async Task ExecuteMundane(string input, IChanne performanceMonitor.Report(); } } + + /// + /// This method is called when the Jupyter client requests that the current + /// cell execution should be interrupted. + /// + /// The original request from the client. + public override void OnInterruptRequest(Message message) + { + EventService?.Trigger(this); + + base.OnInterruptRequest(message); + } } } diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 84d06abe64..696967dd82 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -36,7 +36,8 @@ public class MockShell : IShellServer { public event Action KernelInfoRequest; public event Action ExecuteRequest; - public event Action ShutdownRequest; + public event Action ShutdownRequest; + public event Action InterruptRequest; internal void Handle(Message message) { From 84874a2f8fb119bb81a7502a91b166e9e04eb78c Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Thu, 18 Jun 2020 16:16:44 -0700 Subject: [PATCH 07/22] Add #nullable enable --- src/Jupyter/Events/KernelInterruptRequestedEvent.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Jupyter/Events/KernelInterruptRequestedEvent.cs b/src/Jupyter/Events/KernelInterruptRequestedEvent.cs index 6c062b3181..967198f3c6 100644 --- a/src/Jupyter/Events/KernelInterruptRequestedEvent.cs +++ b/src/Jupyter/Events/KernelInterruptRequestedEvent.cs @@ -1,6 +1,8 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using Microsoft.Jupyter.Core; namespace Microsoft.Quantum.IQSharp.Jupyter @@ -24,7 +26,7 @@ public static class KernelInterruptRequestedEventExtensions /// The instance for which interrupt is requested. public static void TriggerKernelInterruptRequested(this IEventService eventService, IExecutionEngine engine) { - eventService?.Trigger(engine); + eventService.Trigger(engine); } /// @@ -34,7 +36,7 @@ public static void TriggerKernelInterruptRequested(this IEventService eventServi /// The typed EventPubSub for the KernelInterruptRequested event. public static EventPubSub OnKernelInterruptRequested(this IEventService eventService) { - return eventService?.Events(); + return eventService.Events(); } } } \ No newline at end of file From 5a77e5ad772473f27d16099fcdf473c96e878225 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 19 Jun 2020 08:04:32 -0700 Subject: [PATCH 08/22] Properly dispose CancellationTokenSource --- src/AzureClient/AzureClient.cs | 33 +++++++++++++++++++++++++-------- src/Jupyter/Jupyter.csproj | 2 +- src/Kernel/Kernel.csproj | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 94e1aba246..fd290e6ded 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Quantum; using Microsoft.Azure.Quantum.Client.Models; @@ -25,6 +26,7 @@ public class AzureClient : IAzureClient private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } private IEventService EventService { get; } + private CancellationTokenSource CancellationTokenSource { get; set; } = new CancellationTokenSource(); private string ConnectionString { get; set; } = string.Empty; private AzureExecutionTarget? ActiveTarget { get; set; } private IAzureWorkspace? ActiveWorkspace { get; set; } @@ -49,7 +51,8 @@ public AzureClient( Logger = logger; EventService = eventService; - eventService?.TriggerServiceInitialized(this); + EventService.TriggerServiceInitialized(this); + EventService.OnKernelInterruptRequested().On += (engine) => CancellationTokenSource.Cancel(); if (engine is BaseEngine baseEngine) { @@ -179,19 +182,20 @@ private async Task SubmitOrExecuteJobAsync(IChannel channel, Az channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); - using (var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) + try { - EventService.OnKernelInterruptRequested().On += (engine) => cts.Cancel(); - CloudJob? cloudJob = null; - do + ResetCancellationTokenSource().CancelAfter(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout)); + while ((cloudJob == null || cloudJob.InProgress) && !CancellationTokenSource.IsCancellationRequested) { - await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval)); - if (cts.IsCancellationRequested) break; + await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), CancellationTokenSource.Token); cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); } - while (cloudJob == null || cloudJob.InProgress); + } + catch (TaskCanceledException e) + { + Logger?.LogInformation($"Cancelled waiting for job execution to complete: {e.Message}"); } return await GetJobResultAsync(channel, MostRecentJobId); @@ -356,5 +360,18 @@ public async Task GetJobListAsync(IChannel channel) return jobs.ToExecutionResult(); } + + /// + /// Creates a new object, stores it in + /// the property, and properly disposes of the existing one. + /// + /// The new object. + private CancellationTokenSource ResetCancellationTokenSource() + { + var oldCts = CancellationTokenSource; + CancellationTokenSource = new CancellationTokenSource(); + oldCts.Dispose(); + return CancellationTokenSource; + } } } diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 67a9b01310..e2b3880a39 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Kernel/Kernel.csproj b/src/Kernel/Kernel.csproj index 242398b43b..bdf7a86cb0 100644 --- a/src/Kernel/Kernel.csproj +++ b/src/Kernel/Kernel.csproj @@ -22,7 +22,7 @@ - + From f5acc85b106cb0de1bcf92f324ce4a25491bbd36 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 19 Jun 2020 11:08:24 -0700 Subject: [PATCH 09/22] Consume CancellationToken functionality from jupyter-core --- src/AzureClient/AzureClient.cs | 57 ++++++++----------- src/AzureClient/IAzureClient.cs | 5 +- src/AzureClient/Magic/AzureClientMagicBase.cs | 7 ++- src/AzureClient/Magic/ConnectMagic.cs | 3 +- src/AzureClient/Magic/ExecuteMagic.cs | 5 +- src/AzureClient/Magic/JobsMagic.cs | 3 +- src/AzureClient/Magic/OutputMagic.cs | 3 +- src/AzureClient/Magic/StatusMagic.cs | 3 +- src/AzureClient/Magic/SubmitMagic.cs | 5 +- src/AzureClient/Magic/TargetMagic.cs | 3 +- .../Events/KernelInterruptRequestedEvent.cs | 42 -------------- src/Jupyter/Jupyter.csproj | 2 +- src/Jupyter/Magic/AbstractMagic.cs | 10 ++-- src/Kernel/IQSharpEngine.cs | 15 +---- src/Kernel/Kernel.csproj | 2 +- src/Kernel/Magic/ConfigMagic.cs | 4 +- src/Kernel/Magic/EstimateMagic.cs | 7 ++- src/Kernel/Magic/LsMagicMagic.cs | 4 +- src/Kernel/Magic/PackageMagic.cs | 7 ++- src/Kernel/Magic/PerformanceMagic.cs | 4 +- src/Kernel/Magic/Simulate.cs | 7 ++- src/Kernel/Magic/ToffoliMagic.cs | 7 ++- src/Kernel/Magic/WhoMagic.cs | 4 +- src/Kernel/Magic/WorkspaceMagic.cs | 4 +- src/Tests/AzureClientMagicTests.cs | 7 ++- src/Tests/IQsharpEngineTests.cs | 45 ++++++++------- 26 files changed, 110 insertions(+), 155 deletions(-) delete mode 100644 src/Jupyter/Events/KernelInterruptRequestedEvent.cs diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index fd290e6ded..9008070978 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -26,7 +26,6 @@ public class AzureClient : IAzureClient private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } private IEventService EventService { get; } - private CancellationTokenSource CancellationTokenSource { get; set; } = new CancellationTokenSource(); private string ConnectionString { get; set; } = string.Empty; private AzureExecutionTarget? ActiveTarget { get; set; } private IAzureWorkspace? ActiveWorkspace { get; set; } @@ -51,8 +50,7 @@ public AzureClient( Logger = logger; EventService = eventService; - EventService.TriggerServiceInitialized(this); - EventService.OnKernelInterruptRequested().On += (engine) => CancellationTokenSource.Cancel(); + eventService?.TriggerServiceInitialized(this); if (engine is BaseEngine baseEngine) { @@ -106,7 +104,11 @@ public async Task GetConnectionStatusAsync(IChannel channel) return ValidExecutionTargets.ToExecutionResult(); } - private async Task SubmitOrExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, bool execute) + private async Task SubmitOrExecuteJobAsync( + IChannel channel, + CancellationToken cancellationToken, + AzureSubmissionContext submissionContext, + bool execute) { if (ActiveWorkspace == null) { @@ -182,32 +184,36 @@ private async Task SubmitOrExecuteJobAsync(IChannel channel, Az channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); - try + using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) + using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) { - CloudJob? cloudJob = null; - ResetCancellationTokenSource().CancelAfter(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout)); - while ((cloudJob == null || cloudJob.InProgress) && !CancellationTokenSource.IsCancellationRequested) + try { - await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), CancellationTokenSource.Token); - cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); - channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); + CloudJob? cloudJob = null; + while (cloudJob == null || cloudJob.InProgress) + { + executionCancellationTokenSource.Token.ThrowIfCancellationRequested(); + await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), executionCancellationTokenSource.Token); + cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); + channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); + } + } + catch (Exception e) when (e is TaskCanceledException || e is OperationCanceledException) + { + Logger?.LogInformation($"Operation canceled while waiting for job execution to complete: {e.Message}"); } - } - catch (TaskCanceledException e) - { - Logger?.LogInformation($"Cancelled waiting for job execution to complete: {e.Message}"); } return await GetJobResultAsync(channel, MostRecentJobId); } /// - public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext) => - await SubmitOrExecuteJobAsync(channel, submissionContext, execute: false); + public async Task SubmitJobAsync(IChannel channel, CancellationToken cancellationToken, AzureSubmissionContext submissionContext) => + await SubmitOrExecuteJobAsync(channel, cancellationToken, submissionContext, execute: false); /// - public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext) => - await SubmitOrExecuteJobAsync(channel, submissionContext, execute: true); + public async Task ExecuteJobAsync(IChannel channel, CancellationToken cancellationToken, AzureSubmissionContext submissionContext) => + await SubmitOrExecuteJobAsync(channel, cancellationToken, submissionContext, execute: true); /// public async Task GetActiveTargetAsync(IChannel channel) @@ -360,18 +366,5 @@ public async Task GetJobListAsync(IChannel channel) return jobs.ToExecutionResult(); } - - /// - /// Creates a new object, stores it in - /// the property, and properly disposes of the existing one. - /// - /// The new object. - private CancellationTokenSource ResetCancellationTokenSource() - { - var oldCts = CancellationTokenSource; - CancellationTokenSource = new CancellationTokenSource(); - oldCts.Dispose(); - return CancellationTokenSource; - } } } diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index d6c67678ff..e840051b01 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; @@ -128,7 +129,7 @@ public Task ConnectAsync(IChannel channel, /// /// Details of the submitted job, or an error if submission failed. /// - public Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext); + public Task SubmitJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext); /// /// Executes the specified Q# operation as a job to the currently active target @@ -137,7 +138,7 @@ public Task ConnectAsync(IChannel channel, /// /// The result of the executed job, or an error if execution failed. /// - public Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext); + public Task ExecuteJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext); /// /// Sets the specified target for job submission. diff --git a/src/AzureClient/Magic/AzureClientMagicBase.cs b/src/AzureClient/Magic/AzureClientMagicBase.cs index f7eac3b5ce..a76b330267 100644 --- a/src/AzureClient/Magic/AzureClientMagicBase.cs +++ b/src/AzureClient/Magic/AzureClientMagicBase.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -34,12 +35,12 @@ public AzureClientMagicBase(IAzureClient azureClient, string keyword, Documentat } /// - public override ExecutionResult Run(string input, IChannel channel) => - RunAsync(input, channel).GetAwaiter().GetResult(); + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + RunAsync(input, channel, cancellationToken).GetAwaiter().GetResult(); /// /// Executes the magic command functionality for the given input. /// - public abstract Task RunAsync(string input, IChannel channel); + public abstract Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken); } } diff --git a/src/AzureClient/Magic/ConnectMagic.cs b/src/AzureClient/Magic/ConnectMagic.cs index 559c04e3e7..3ddd408593 100644 --- a/src/AzureClient/Magic/ConnectMagic.cs +++ b/src/AzureClient/Magic/ConnectMagic.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -90,7 +91,7 @@ credentials when connecting to Azure. /// Connects to an Azure workspace given a subscription ID, resource group name, /// workspace name, and connection string as a JSON-encoded object. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input); diff --git a/src/AzureClient/Magic/ExecuteMagic.cs b/src/AzureClient/Magic/ExecuteMagic.cs index 6a3080d274..5643573eca 100644 --- a/src/AzureClient/Magic/ExecuteMagic.cs +++ b/src/AzureClient/Magic/ExecuteMagic.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -56,9 +57,9 @@ The Azure Quantum workspace must previously have been initialized /// name that is present in the current Q# Jupyter workspace, and /// waits for the job to complete before returning. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { - return await AzureClient.ExecuteJobAsync(channel, AzureSubmissionContext.Parse(input)); + return await AzureClient.ExecuteJobAsync(channel, cancellationToken, AzureSubmissionContext.Parse(input)); } } } \ No newline at end of file diff --git a/src/AzureClient/Magic/JobsMagic.cs b/src/AzureClient/Magic/JobsMagic.cs index f23708d9ea..4ab88930d7 100644 --- a/src/AzureClient/Magic/JobsMagic.cs +++ b/src/AzureClient/Magic/JobsMagic.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -52,7 +53,7 @@ The Azure Quantum workspace must previously have been initialized /// /// Lists all jobs in the active workspace. /// - public override async Task RunAsync(string input, IChannel channel) => + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) => await AzureClient.GetJobListAsync(channel); } } \ No newline at end of file diff --git a/src/AzureClient/Magic/OutputMagic.cs b/src/AzureClient/Magic/OutputMagic.cs index 17ca257a46..52cc39a7e9 100644 --- a/src/AzureClient/Magic/OutputMagic.cs +++ b/src/AzureClient/Magic/OutputMagic.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -68,7 +69,7 @@ The Azure Quantum workspace must previously have been initialized /// Displays the output of a given completed job ID, if provided, /// or all jobs submitted in the current session. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameJobId); string jobId = inputParameters.DecodeParameter(ParameterNameJobId); diff --git a/src/AzureClient/Magic/StatusMagic.cs b/src/AzureClient/Magic/StatusMagic.cs index e7eaa56d6c..6ae5a96273 100644 --- a/src/AzureClient/Magic/StatusMagic.cs +++ b/src/AzureClient/Magic/StatusMagic.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -66,7 +67,7 @@ The Azure Quantum workspace must previously have been initialized /// Displays the status corresponding to a given job ID, if provided, /// or the most recently-submitted job in the current session. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameJobId); string jobId = inputParameters.DecodeParameter(ParameterNameJobId); diff --git a/src/AzureClient/Magic/SubmitMagic.cs b/src/AzureClient/Magic/SubmitMagic.cs index 516646382e..d5ad9036c5 100644 --- a/src/AzureClient/Magic/SubmitMagic.cs +++ b/src/AzureClient/Magic/SubmitMagic.cs @@ -4,6 +4,7 @@ #nullable enable using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -52,9 +53,9 @@ The Azure Quantum workspace must previously have been initialized /// Submits a new job to an Azure Quantum workspace given a Q# operation /// name that is present in the current Q# Jupyter workspace. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { - return await AzureClient.SubmitJobAsync(channel, AzureSubmissionContext.Parse(input)); + return await AzureClient.SubmitJobAsync(channel, cancellationToken, AzureSubmissionContext.Parse(input)); } } } \ No newline at end of file diff --git a/src/AzureClient/Magic/TargetMagic.cs b/src/AzureClient/Magic/TargetMagic.cs index 778f88ab74..25d8803a53 100644 --- a/src/AzureClient/Magic/TargetMagic.cs +++ b/src/AzureClient/Magic/TargetMagic.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -63,7 +64,7 @@ available in the workspace. /// /// Sets or views the target for job submission to the current Azure Quantum workspace. /// - public override async Task RunAsync(string input, IChannel channel) + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameTargetId); if (inputParameters.ContainsKey(ParameterNameTargetId)) diff --git a/src/Jupyter/Events/KernelInterruptRequestedEvent.cs b/src/Jupyter/Events/KernelInterruptRequestedEvent.cs deleted file mode 100644 index 967198f3c6..0000000000 --- a/src/Jupyter/Events/KernelInterruptRequestedEvent.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#nullable enable - -using Microsoft.Jupyter.Core; - -namespace Microsoft.Quantum.IQSharp.Jupyter -{ - /// - /// A event to trigger when a client requests interruption of the current cell execution. - /// - public class KernelInterruptRequestedEvent : Event - { - } - - /// - /// Extension methods to make it easy to consume and trigger KernelInterruptRequested events. - /// - public static class KernelInterruptRequestedEventExtensions - { - /// - /// Instantiate and trigger the event, invoking all subscriber actions. - /// - /// The event service where the EventSubPub lives. - /// The instance for which interrupt is requested. - public static void TriggerKernelInterruptRequested(this IEventService eventService, IExecutionEngine engine) - { - eventService.Trigger(engine); - } - - /// - /// Gets the typed EventPubSub for the InterruptRequested event. - /// - /// The event service where the EventSubPub lives. - /// The typed EventPubSub for the KernelInterruptRequested event. - public static EventPubSub OnKernelInterruptRequested(this IEventService eventService) - { - return eventService.Events(); - } - } -} \ No newline at end of file diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index e2b3880a39..42620e37d8 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Jupyter/Magic/AbstractMagic.cs b/src/Jupyter/Magic/AbstractMagic.cs index 64a1326266..4155abfddc 100644 --- a/src/Jupyter/Magic/AbstractMagic.cs +++ b/src/Jupyter/Magic/AbstractMagic.cs @@ -6,11 +6,11 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; using Microsoft.Quantum.QsCompiler.Serialization; -using Newtonsoft.Json.Linq; namespace Microsoft.Quantum.IQSharp.Jupyter { @@ -38,14 +38,14 @@ public AbstractMagic(string keyword, Documentation docs) /// returned execution function displays the given exceptions to its /// display channel. /// - public Func> SafeExecute(Func magic) => - async (input, channel) => + public Func> SafeExecute(Func magic) => + async (input, channel, cancellationToken) => { channel = channel.WithNewLines(); try { - return magic(input, channel); + return magic(input, channel, cancellationToken); } catch (InvalidWorkspaceException ws) { @@ -145,6 +145,6 @@ public static Dictionary ParseInputParameters(string input, stri /// /// A method to be run when the magic command is executed. /// - public abstract ExecutionResult Run(string input, IChannel channel); + public abstract ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken); } } diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 777dcce1bf..9fc03289dd 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -16,6 +16,7 @@ using Newtonsoft.Json.Converters; using Microsoft.Jupyter.Core.Protocol; using Newtonsoft.Json; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.Quantum.IQSharp.Kernel @@ -96,7 +97,7 @@ IMagicSymbolResolver magicSymbolResolver /// cell is expected to have a Q# snippet, which gets compiled and we return the name of /// the operations found. These operations are then available for simulation and estimate. /// - public override async Task ExecuteMundane(string input, IChannel channel) + public override async Task ExecuteMundane(string input, IChannel channel, CancellationToken cancellationToken) { channel = channel.WithNewLines(); @@ -131,18 +132,6 @@ public override async Task ExecuteMundane(string input, IChanne performanceMonitor.Report(); } } - - /// - /// This method is called when the Jupyter client requests that the current - /// cell execution should be interrupted. - /// - /// The original request from the client. - public override void OnInterruptRequest(Message message) - { - EventService?.Trigger(this); - - base.OnInterruptRequest(message); - } } } diff --git a/src/Kernel/Kernel.csproj b/src/Kernel/Kernel.csproj index bdf7a86cb0..89628d71bf 100644 --- a/src/Kernel/Kernel.csproj +++ b/src/Kernel/Kernel.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Kernel/Magic/ConfigMagic.cs b/src/Kernel/Magic/ConfigMagic.cs index 6a020af780..ce115a9934 100644 --- a/src/Kernel/Magic/ConfigMagic.cs +++ b/src/Kernel/Magic/ConfigMagic.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -81,7 +81,7 @@ directory is loaded. /// - public override ExecutionResult Run(string? input, IChannel channel) + public override ExecutionResult Run(string? input, IChannel channel, CancellationToken cancellationToken) { // If we didn't get any input, treat it as a query. if (input == null || input.Trim().Length == 0) diff --git a/src/Kernel/Magic/EstimateMagic.cs b/src/Kernel/Magic/EstimateMagic.cs index 54e58f81d0..de7c709f7d 100644 --- a/src/Kernel/Magic/EstimateMagic.cs +++ b/src/Kernel/Magic/EstimateMagic.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -43,8 +44,8 @@ public EstimateMagic(ISymbolResolver resolver) : base( public ISymbolResolver SymbolResolver { get; } /// - public override ExecutionResult Run(string input, IChannel channel) => - RunAsync(input, channel).Result; + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + RunAsync(input, channel, cancellationToken).Result; /// @@ -52,7 +53,7 @@ public override ExecutionResult Run(string input, IChannel channel) => /// serialization of its inputs, returns a task that can be awaited /// on for resource estimates from running that operation. /// - public async Task RunAsync(string input, IChannel channel) + public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/LsMagicMagic.cs b/src/Kernel/Magic/LsMagicMagic.cs index 05e1037e00..b88d0a6e6b 100644 --- a/src/Kernel/Magic/LsMagicMagic.cs +++ b/src/Kernel/Magic/LsMagicMagic.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Linq; - +using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -44,7 +44,7 @@ public LsMagicMagic(IMagicSymbolResolver resolver, IExecutionEngine engine) : ba } /// - public override ExecutionResult Run(string input, IChannel channel) => + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => resolver .FindAllMagicSymbols() .Select(magic => new MagicSymbolSummary diff --git a/src/Kernel/Magic/PackageMagic.cs b/src/Kernel/Magic/PackageMagic.cs index 8ca8aa2d43..3239bac228 100644 --- a/src/Kernel/Magic/PackageMagic.cs +++ b/src/Kernel/Magic/PackageMagic.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -39,7 +40,7 @@ public PackageMagic(IReferences references) : base( public IReferences References { get; } /// - public override ExecutionResult Run(string input, IChannel channel) + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNamePackageName); var name = inputParameters.DecodeParameter(ParameterNamePackageName); @@ -47,7 +48,7 @@ public override ExecutionResult Run(string input, IChannel channel) var statusUpdater = channel.DisplayUpdatable(status); void Update() => statusUpdater.Update(status); - var task = RunAsync(name, channel, (newStatus) => + var task = RunAsync(name, channel, cancellationToken, (newStatus) => { status.Subtask = newStatus; Update(); @@ -71,7 +72,7 @@ public override ExecutionResult Run(string input, IChannel channel) /// a task that can be awaited on for the completion of the package /// download. /// - public async Task RunAsync(string name, IChannel channel, Action statusCallback) + public async Task RunAsync(string name, IChannel channel, CancellationToken cancellationToken, Action statusCallback) { if (!string.IsNullOrWhiteSpace(name)) { diff --git a/src/Kernel/Magic/PerformanceMagic.cs b/src/Kernel/Magic/PerformanceMagic.cs index a1586e57f3..eaf56cb430 100644 --- a/src/Kernel/Magic/PerformanceMagic.cs +++ b/src/Kernel/Magic/PerformanceMagic.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; - +using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -33,7 +33,7 @@ public PerformanceMagic() : base( } /// - public override ExecutionResult Run(string? input, IChannel channel) + public override ExecutionResult Run(string? input, IChannel channel, CancellationToken cancellationToken) { var currentProcess = Process.GetCurrentProcess(); var performanceResult = new List<(string, string)> diff --git a/src/Kernel/Magic/Simulate.cs b/src/Kernel/Magic/Simulate.cs index 5e552541b8..4d9723aad6 100644 --- a/src/Kernel/Magic/Simulate.cs +++ b/src/Kernel/Magic/Simulate.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -48,14 +49,14 @@ public SimulateMagic(ISymbolResolver resolver, IConfigurationSource configuratio public IConfigurationSource ConfigurationSource { get; } /// - public override ExecutionResult Run(string input, IChannel channel) => - RunAsync(input, channel).Result; + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + RunAsync(input, channel, cancellationToken).Result; /// /// Simulates an operation given a string with its name and a JSON /// encoding of its arguments. /// - public async Task RunAsync(string input, IChannel channel) + public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/ToffoliMagic.cs b/src/Kernel/Magic/ToffoliMagic.cs index 48160c2bef..0dd885074f 100644 --- a/src/Kernel/Magic/ToffoliMagic.cs +++ b/src/Kernel/Magic/ToffoliMagic.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -36,14 +37,14 @@ public ToffoliMagic(ISymbolResolver resolver) : base( public ISymbolResolver SymbolResolver { get; } /// - public override ExecutionResult Run(string input, IChannel channel) => - RunAsync(input, channel).Result; + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + RunAsync(input, channel, cancellationToken).Result; /// /// Simulates a function/operation using the ToffoliSimulator as target machine. /// It expects a single input: the name of the function/operation to simulate. /// - public async Task RunAsync(string input, IChannel channel) + public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/WhoMagic.cs b/src/Kernel/Magic/WhoMagic.cs index 9a8c514f4d..959b39da11 100644 --- a/src/Kernel/Magic/WhoMagic.cs +++ b/src/Kernel/Magic/WhoMagic.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Linq; - +using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -35,7 +35,7 @@ public WhoMagic(ISnippets snippets) : base( public ISnippets Snippets { get; } /// - public override ExecutionResult Run(string input, IChannel channel) => + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => Snippets.Operations .Select(op => op.FullName) .OrderBy(name => name) diff --git a/src/Kernel/Magic/WorkspaceMagic.cs b/src/Kernel/Magic/WorkspaceMagic.cs index 22b089f7b7..5dc915a909 100644 --- a/src/Kernel/Magic/WorkspaceMagic.cs +++ b/src/Kernel/Magic/WorkspaceMagic.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Linq; - +using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; using Microsoft.Quantum.IQSharp.Jupyter; @@ -51,7 +51,7 @@ public void CheckIfReady() } /// - public override ExecutionResult Run(string input, IChannel channel) + public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameCommand); var command = inputParameters.DecodeParameter(ParameterNameCommand); diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs index 51763fdbac..f76a2b6b51 100644 --- a/src/Tests/AzureClientMagicTests.cs +++ b/src/Tests/AzureClientMagicTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; @@ -18,7 +19,7 @@ public static class AzureClientMagicTestExtensions { public static void Test(this MagicSymbol magic, string input, ExecuteStatus expected = ExecuteStatus.Ok) { - var result = magic.Execute(input, new MockChannel()).GetAwaiter().GetResult(); + var result = magic.Execute(input, new MockChannel(), CancellationToken.None).GetAwaiter().GetResult(); Assert.IsTrue(result.Status == expected); } } @@ -223,14 +224,14 @@ public async Task GetActiveTargetAsync(IChannel channel) return ActiveTargetId.ToExecutionResult(); } - public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext) + public async Task SubmitJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext) { LastAction = AzureClientAction.SubmitJob; SubmittedJobs.Add(submissionContext.OperationName); return ExecuteStatus.Ok.ToExecutionResult(); } - public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext) + public async Task ExecuteJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext) { LastAction = AzureClientAction.ExecuteJob; ExecutedJobs.Add(submissionContext.OperationName); diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 2d800b3bb9..1da1e9031f 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; @@ -46,7 +47,7 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) public static async Task AssertCompile(IQSharpEngine engine, string source, params string[] expectedOps) { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(source, channel); + var response = await engine.ExecuteMundane(source, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -60,7 +61,7 @@ public static async Task AssertSimulate(IQSharpEngine engine, string sni var configSource = new ConfigurationSource(skipLoading: true); var simMagic = new SimulateMagic(engine.SymbolsResolver, configSource); var channel = new MockChannel(); - var response = await simMagic.Execute(snippetName, channel); + var response = await simMagic.Execute(snippetName, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); CollectionAssert.AreEqual(messages.Select(ChannelWithNewLines.Format).ToArray(), channel.msgs.ToArray()); @@ -72,7 +73,7 @@ public static async Task AssertEstimate(IQSharpEngine engine, string sni { var channel = new MockChannel(); var estimateMagic = new EstimateMagic(engine.SymbolsResolver); - var response = await estimateMagic.Execute(snippetName, channel); + var response = await estimateMagic.Execute(snippetName, channel, CancellationToken.None); var result = response.Output as DataTable; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -103,7 +104,7 @@ public async Task CompileAndSimulate() var channel = new MockChannel(); // Try running without compiling it, fails: - var response = await simMagic.Execute("_snippet_.HelloQ", channel); + var response = await simMagic.Execute("_snippet_.HelloQ", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -155,7 +156,7 @@ public async Task Toffoli() // Run with toffoli simulator: var toffoliMagic = new ToffoliMagic(engine.SymbolsResolver); - var response = await toffoliMagic.Execute("HelloQ", channel); + var response = await toffoliMagic.Execute("HelloQ", channel, CancellationToken.None); var result = response.Output as Dictionary; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -219,7 +220,7 @@ public async Task ReportWarnings() { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel); + var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(3, channel.msgs.Count); @@ -229,7 +230,7 @@ public async Task ReportWarnings() { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel); + var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(1, channel.msgs.Count); @@ -244,7 +245,7 @@ public async Task ReportErrors() var engine = Init(); var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel); + var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -258,7 +259,7 @@ public async Task TestPackages() var snippets = engine.Snippets as Snippets; var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = await pkgMagic.Execute("", channel); + var response = await pkgMagic.Execute("", channel, CancellationToken.None); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -268,11 +269,11 @@ public async Task TestPackages() // Try compiling TrotterEstimateEnergy, it should fail due to the lack // of chemistry package. - response = await engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel); + response = await engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel, CancellationToken.None); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -293,7 +294,7 @@ public async Task TestInvalidPackages() var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = await pkgMagic.Execute("microsoft.quantum", channel); + var response = await pkgMagic.Execute("microsoft.quantum", channel, CancellationToken.None); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); @@ -313,7 +314,7 @@ public async Task TestWho() var channel = new MockChannel(); // Check the workspace, it should be in error state: - var response = await whoMagic.Execute("", channel); + var response = await whoMagic.Execute("", channel, CancellationToken.None); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -335,34 +336,34 @@ public async Task TestWorkspace() var result = new string[0]; // Check the workspace, it should be in error state: - var response = await wsMagic.Execute("reload", channel); + var response = await wsMagic.Execute("reload", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = await wsMagic.Execute("", channel); + response = await wsMagic.Execute("", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); // Try compiling a snippet that depends on a workspace that depends on the chemistry package: - response = await engine.ExecuteMundane(SNIPPETS.DependsOnChemistryWorkspace, channel); + response = await engine.ExecuteMundane(SNIPPETS.DependsOnChemistryWorkspace, channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); // Add dependencies: - response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = await pkgMagic.Execute("microsoft.quantum.research", channel); + response = await pkgMagic.Execute("microsoft.quantum.research", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); // Reload workspace: - response = await wsMagic.Execute("reload", channel); + response = await wsMagic.Execute("reload", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = await wsMagic.Execute("", channel); + response = await wsMagic.Execute("", channel, CancellationToken.None); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -372,12 +373,12 @@ public async Task TestWorkspace() await AssertCompile(engine, SNIPPETS.DependsOnChemistryWorkspace, "DependsOnChemistryWorkspace"); // Check an invalid command - response = await wsMagic.Execute("foo", channel); + response = await wsMagic.Execute("foo", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); // Check that everything still works: - response = await wsMagic.Execute("", channel); + response = await wsMagic.Execute("", channel, CancellationToken.None); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); } From f328aa5ea8446c757c45484e7e5d2f866eaeaed0 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 19 Jun 2020 13:29:23 -0700 Subject: [PATCH 10/22] Few improvements to output when using Python --- src/AzureClient/AzureClient.cs | 28 +++++++++++++++++----------- src/Python/qsharp/azure.py | 15 ++++++++++++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 1622cb0ccd..ed059d99f9 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -24,6 +24,8 @@ public class AzureClient : IAzureClient private ILogger Logger { get; } private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } + private IMetadataController MetadataController { get; } + private bool IsPythonUserAgent => MetadataController?.UserAgent?.StartsWith("qsharp.py") ?? false; private string ConnectionString { get; set; } = string.Empty; private AzureExecutionTarget? ActiveTarget { get; set; } private string MostRecentJobId { get; set; } = string.Empty; @@ -39,11 +41,13 @@ public AzureClient( IExecutionEngine engine, IReferences references, IEntryPointGenerator entryPointGenerator, + IMetadataController metadataController, ILogger logger, IEventService eventService) { References = references; EntryPointGenerator = entryPointGenerator; + MetadataController = metadataController; Logger = logger; eventService?.TriggerServiceInitialized(this); @@ -110,20 +114,19 @@ private async Task SubmitOrExecuteJobAsync(IChannel channel, Az { if (ActiveWorkspace == null) { - channel.Stderr("Please call %azure.connect before submitting a job."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before submitting a job."); return AzureClientError.NotConnected.ToExecutionResult(); } if (ActiveTarget == null) { - channel.Stderr("Please call %azure.target before submitting a job."); + channel.Stderr($"Please call {GetCommandDisplayName("target")} before submitting a job."); return AzureClientError.NoTarget.ToExecutionResult(); } if (string.IsNullOrEmpty(submissionContext.OperationName)) { - var commandName = execute ? "%azure.execute" : "%azure.submit"; - channel.Stderr($"Please pass a valid Q# operation name to {commandName}."); + channel.Stderr($"Please pass a valid Q# operation name to {GetCommandDisplayName(execute ? "execute" : "submit")}."); return AzureClientError.NoOperationName.ToExecutionResult(); } @@ -213,13 +216,13 @@ public async Task GetActiveTargetAsync(IChannel channel) { if (AvailableProviders == null) { - channel.Stderr("Please call %azure.connect before getting the execution target."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before getting the execution target."); return AzureClientError.NotConnected.ToExecutionResult(); } if (ActiveTarget == null) { - channel.Stderr("No execution target has been specified. To specify one, run:\n%azure.target "); + channel.Stderr($"No execution target has been specified. To specify one, call {GetCommandDisplayName("target")} with the target ID."); channel.Stdout($"Available execution targets: {ValidExecutionTargetsDisplayText}"); return AzureClientError.NoTarget.ToExecutionResult(); } @@ -235,7 +238,7 @@ public async Task SetActiveTargetAsync(IChannel channel, string { if (AvailableProviders == null) { - channel.Stderr("Please call %azure.connect before setting an execution target."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before setting an execution target."); return AzureClientError.NotConnected.ToExecutionResult(); } @@ -272,7 +275,7 @@ public async Task GetJobResultAsync(IChannel channel, string jo { if (ActiveWorkspace == null) { - channel.Stderr("Please call %azure.connect before getting job results."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before getting job results."); return AzureClientError.NotConnected.ToExecutionResult(); } @@ -296,7 +299,7 @@ public async Task GetJobResultAsync(IChannel channel, string jo if (!job.Succeeded || string.IsNullOrEmpty(job.Details.OutputDataUri)) { - channel.Stderr($"Job ID {jobId} has not completed. To check the status, use:\n %azure.status {jobId}"); + channel.Stderr($"Job ID {jobId} has not completed. To check the status, call {GetCommandDisplayName("status")} with the job ID."); return AzureClientError.JobNotCompleted.ToExecutionResult(); } @@ -319,7 +322,7 @@ public async Task GetJobStatusAsync(IChannel channel, string jo { if (ActiveWorkspace == null) { - channel.Stderr("Please call %azure.connect before getting job status."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before getting job status."); return AzureClientError.NotConnected.ToExecutionResult(); } @@ -349,7 +352,7 @@ public async Task GetJobListAsync(IChannel channel) { if (ActiveWorkspace == null) { - channel.Stderr("Please call %azure.connect before listing jobs."); + channel.Stderr($"Please call {GetCommandDisplayName("connect")} before listing jobs."); return AzureClientError.NotConnected.ToExecutionResult(); } @@ -361,5 +364,8 @@ public async Task GetJobListAsync(IChannel channel) return jobs.ToExecutionResult(); } + + private string GetCommandDisplayName(string commandName) => + IsPythonUserAgent ? $"qsharp.azure.{commandName}()" : $"%azure.{commandName}"; } } diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py index f2632e8d32..afbe53c3fb 100644 --- a/src/Python/qsharp/azure.py +++ b/src/Python/qsharp/azure.py @@ -46,7 +46,10 @@ def __init__(self, data: Dict): self.current_availability = data["current_availability"] self.average_queue_time = data["average_queue_time"] - def __eq__(self, other): + def __repr__(self) -> str: + return self.__dict__.__repr__() + + def __eq__(self, other) -> bool: if not isinstance(other, AzureTarget): # don't attempt to compare against unrelated types return NotImplemented @@ -67,7 +70,10 @@ def __init__(self, data: Dict): self.begin_execution_time = data["begin_execution_time"] self.end_execution_time = data["end_execution_time"] - def __eq__(self, other): + def __repr__(self) -> str: + return self.__dict__.__repr__() + + def __eq__(self, other) -> bool: if not isinstance(other, AzureJob): # don't attempt to compare against unrelated types return NotImplemented @@ -83,7 +89,10 @@ def __init__(self, data: Dict): self.error_name = data["error_name"] self.error_description = data["error_description"] - def __eq__(self, other): + def __repr__(self) -> str: + return self.__dict__.__repr__() + + def __eq__(self, other) -> bool: if not isinstance(other, AzureError): # don't attempt to compare against unrelated types return NotImplemented From 58d782e166ccf4f95531fd04c9cc908ed915e476 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 19 Jun 2020 21:24:32 -0700 Subject: [PATCH 11/22] Derive AbstractMagic from CancellableMagicSymbol --- src/AzureClient/AzureClient.cs | 19 +++--- src/AzureClient/IAzureClient.cs | 4 +- src/AzureClient/Magic/AzureClientMagicBase.cs | 6 +- src/AzureClient/Magic/ExecuteMagic.cs | 2 +- src/AzureClient/Magic/SubmitMagic.cs | 2 +- src/Jupyter/Jupyter.csproj | 2 +- src/Jupyter/Magic/AbstractMagic.cs | 67 +++++++++++-------- src/Kernel/IQSharpEngine.cs | 6 +- src/Kernel/Kernel.csproj | 1 - src/Kernel/Magic/ConfigMagic.cs | 3 +- src/Kernel/Magic/EstimateMagic.cs | 7 +- src/Kernel/Magic/LsMagicMagic.cs | 3 +- src/Kernel/Magic/PackageMagic.cs | 7 +- src/Kernel/Magic/PerformanceMagic.cs | 3 +- src/Kernel/Magic/Simulate.cs | 7 +- src/Kernel/Magic/ToffoliMagic.cs | 7 +- src/Kernel/Magic/WhoMagic.cs | 3 +- src/Kernel/Magic/WorkspaceMagic.cs | 3 +- src/Tests/AzureClientMagicTests.cs | 6 +- src/Tests/AzureClientTests.cs | 13 ++-- src/Tests/IQsharpEngineTests.cs | 45 ++++++------- 21 files changed, 108 insertions(+), 108 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 366f900ded..4a3a6993bc 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; -using Microsoft.Quantum.IQSharp.Jupyter; using Microsoft.Quantum.Simulation.Common; namespace Microsoft.Quantum.IQSharp.AzureClient @@ -26,7 +25,6 @@ public class AzureClient : IAzureClient private ILogger Logger { get; } private IReferences References { get; } private IEntryPointGenerator EntryPointGenerator { get; } - private IEventService EventService { get; } private IMetadataController MetadataController { get; } private bool IsPythonUserAgent => MetadataController?.UserAgent?.StartsWith("qsharp.py") ?? false; private string ConnectionString { get; set; } = string.Empty; @@ -52,7 +50,6 @@ public AzureClient( EntryPointGenerator = entryPointGenerator; MetadataController = metadataController; Logger = logger; - EventService = eventService; eventService?.TriggerServiceInitialized(this); @@ -117,9 +114,9 @@ public async Task GetConnectionStatusAsync(IChannel channel) private async Task SubmitOrExecuteJobAsync( IChannel channel, - CancellationToken cancellationToken, AzureSubmissionContext submissionContext, - bool execute) + bool waitForCompletion, + CancellationToken cancellationToken) { if (ActiveWorkspace == null) { @@ -135,7 +132,7 @@ private async Task SubmitOrExecuteJobAsync( if (string.IsNullOrEmpty(submissionContext.OperationName)) { - channel.Stderr($"Please pass a valid Q# operation name to {GetCommandDisplayName(execute ? "execute" : "submit")}."); + channel.Stderr($"Please pass a valid Q# operation name to {GetCommandDisplayName(waitForCompletion ? "execute" : "submit")}."); return AzureClientError.NoOperationName.ToExecutionResult(); } @@ -187,7 +184,7 @@ private async Task SubmitOrExecuteJobAsync( return AzureClientError.JobSubmissionFailed.ToExecutionResult(); } - if (!execute) + if (!waitForCompletion) { return await GetJobStatusAsync(channel, MostRecentJobId); } @@ -218,12 +215,12 @@ private async Task SubmitOrExecuteJobAsync( } /// - public async Task SubmitJobAsync(IChannel channel, CancellationToken cancellationToken, AzureSubmissionContext submissionContext) => - await SubmitOrExecuteJobAsync(channel, cancellationToken, submissionContext, execute: false); + public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => + await SubmitOrExecuteJobAsync(channel, submissionContext, waitForCompletion: false, cancellationToken); /// - public async Task ExecuteJobAsync(IChannel channel, CancellationToken cancellationToken, AzureSubmissionContext submissionContext) => - await SubmitOrExecuteJobAsync(channel, cancellationToken, submissionContext, execute: true); + public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => + await SubmitOrExecuteJobAsync(channel, submissionContext, waitForCompletion: true, cancellationToken); /// public async Task GetActiveTargetAsync(IChannel channel) diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index 1c5747bd9e..640d39b139 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -45,7 +45,7 @@ public Task ConnectAsync(IChannel channel, /// /// Details of the submitted job, or an error if submission failed. /// - public Task SubmitJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext); + public Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token); /// /// Executes the specified Q# operation as a job to the currently active target @@ -54,7 +54,7 @@ public Task ConnectAsync(IChannel channel, /// /// The result of the executed job, or an error if execution failed. /// - public Task ExecuteJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext); + public Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token); /// /// Sets the specified target for job submission. diff --git a/src/AzureClient/Magic/AzureClientMagicBase.cs b/src/AzureClient/Magic/AzureClientMagicBase.cs index 4bc7223c9e..da17815756 100644 --- a/src/AzureClient/Magic/AzureClientMagicBase.cs +++ b/src/AzureClient/Magic/AzureClientMagicBase.cs @@ -35,7 +35,11 @@ public AzureClientMagicBase(IAzureClient azureClient, string keyword, Documentat } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + public override ExecutionResult Run(string input, IChannel channel) => + RunCancellable(input, channel, CancellationToken.None); + + /// + public override ExecutionResult RunCancellable(string input, IChannel channel, CancellationToken cancellationToken) => RunAsync(input, channel, cancellationToken).GetAwaiter().GetResult(); /// diff --git a/src/AzureClient/Magic/ExecuteMagic.cs b/src/AzureClient/Magic/ExecuteMagic.cs index 2e28ba2520..6a675e9f3e 100644 --- a/src/AzureClient/Magic/ExecuteMagic.cs +++ b/src/AzureClient/Magic/ExecuteMagic.cs @@ -59,7 +59,7 @@ The Azure Quantum workspace must previously have been initialized /// public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { - return await AzureClient.ExecuteJobAsync(channel, cancellationToken, AzureSubmissionContext.Parse(input)); + return await AzureClient.ExecuteJobAsync(channel, AzureSubmissionContext.Parse(input), cancellationToken); } } } \ No newline at end of file diff --git a/src/AzureClient/Magic/SubmitMagic.cs b/src/AzureClient/Magic/SubmitMagic.cs index 84c8782715..2a46a9c03b 100644 --- a/src/AzureClient/Magic/SubmitMagic.cs +++ b/src/AzureClient/Magic/SubmitMagic.cs @@ -55,7 +55,7 @@ The Azure Quantum workspace must previously have been initialized /// public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) { - return await AzureClient.SubmitJobAsync(channel, cancellationToken, AzureSubmissionContext.Parse(input)); + return await AzureClient.SubmitJobAsync(channel, AzureSubmissionContext.Parse(input), cancellationToken); } } } \ No newline at end of file diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 42620e37d8..22fb911269 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Jupyter/Magic/AbstractMagic.cs b/src/Jupyter/Magic/AbstractMagic.cs index 4155abfddc..9ddcd9e81c 100644 --- a/src/Jupyter/Magic/AbstractMagic.cs +++ b/src/Jupyter/Magic/AbstractMagic.cs @@ -17,7 +17,7 @@ namespace Microsoft.Quantum.IQSharp.Jupyter /// /// Abstract base class for IQ# magic symbols. /// - public abstract class AbstractMagic : MagicSymbol + public abstract class AbstractMagic : CancellableMagicSymbol { /// /// Constructs a new magic symbol given its name and documentation. @@ -28,7 +28,7 @@ public AbstractMagic(string keyword, Documentation docs) this.Documentation = docs; this.Kind = SymbolKind.Magic; - this.Execute = SafeExecute(this.Run); + this.ExecuteCancellable = this.SafeExecute(this.RunCancellable); } /// @@ -38,31 +38,32 @@ public AbstractMagic(string keyword, Documentation docs) /// returned execution function displays the given exceptions to its /// display channel. /// - public Func> SafeExecute(Func magic) => - async (input, channel, cancellationToken) => - { - channel = channel.WithNewLines(); - - try - { - return magic(input, channel, cancellationToken); - } - catch (InvalidWorkspaceException ws) + public Func> SafeExecute( + Func magic) => + async (input, channel, cancellationToken) => { - foreach (var m in ws.Errors) channel.Stderr(m); - return ExecuteStatus.Error.ToExecutionResult(); - } - catch (AggregateException agg) - { - foreach (var e in agg.InnerExceptions) channel.Stderr(e?.Message); - return ExecuteStatus.Error.ToExecutionResult(); - } - catch (Exception e) - { - channel.Stderr(e.Message); - return ExecuteStatus.Error.ToExecutionResult(); - } - }; + channel = channel.WithNewLines(); + + try + { + return magic(input, channel, cancellationToken); + } + catch (InvalidWorkspaceException ws) + { + foreach (var m in ws.Errors) channel.Stderr(m); + return ExecuteStatus.Error.ToExecutionResult(); + } + catch (AggregateException agg) + { + foreach (var e in agg.InnerExceptions) channel.Stderr(e?.Message); + return ExecuteStatus.Error.ToExecutionResult(); + } + catch (Exception e) + { + channel.Stderr(e.Message); + return ExecuteStatus.Error.ToExecutionResult(); + } + }; /// /// Parses the input to a magic command, interpreting the input as @@ -145,6 +146,18 @@ public static Dictionary ParseInputParameters(string input, stri /// /// A method to be run when the magic command is executed. /// - public abstract ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken); + public abstract ExecutionResult Run(string input, IChannel channel); + + /// + /// A method to be run when the magic command is executed, including a cancellation + /// token to use for requesting cancellation. + /// + /// + /// The default implementation in ignores the cancellation token. + /// Derived classes should override this method and monitor the cancellation token if they + /// wish to support cancellation. + /// + public virtual ExecutionResult RunCancellable(string input, IChannel channel, CancellationToken cancellationToken) => + Run(input, channel); } } diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 9fc03289dd..88122f5271 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -16,7 +16,6 @@ using Newtonsoft.Json.Converters; using Microsoft.Jupyter.Core.Protocol; using Newtonsoft.Json; -using System.Threading; using System.Threading.Tasks; namespace Microsoft.Quantum.IQSharp.Kernel @@ -51,7 +50,6 @@ IMagicSymbolResolver magicSymbolResolver this.Snippets = services.GetService(); this.SymbolsResolver = services.GetService(); this.MagicResolver = magicSymbolResolver; - this.EventService = eventService; RegisterDisplayEncoder(new IQSharpSymbolToHtmlResultEncoder()); RegisterDisplayEncoder(new IQSharpSymbolToTextResultEncoder()); @@ -90,14 +88,12 @@ IMagicSymbolResolver magicSymbolResolver internal ISymbolResolver MagicResolver { get; } - internal IEventService EventService { get; } - /// /// This is the method used to execute Jupyter "normal" cells. In this case, a normal /// cell is expected to have a Q# snippet, which gets compiled and we return the name of /// the operations found. These operations are then available for simulation and estimate. /// - public override async Task ExecuteMundane(string input, IChannel channel, CancellationToken cancellationToken) + public override async Task ExecuteMundane(string input, IChannel channel) { channel = channel.WithNewLines(); diff --git a/src/Kernel/Kernel.csproj b/src/Kernel/Kernel.csproj index 89628d71bf..b42ee4c7be 100644 --- a/src/Kernel/Kernel.csproj +++ b/src/Kernel/Kernel.csproj @@ -22,7 +22,6 @@ - diff --git a/src/Kernel/Magic/ConfigMagic.cs b/src/Kernel/Magic/ConfigMagic.cs index fbb9acb507..6490a3cabb 100644 --- a/src/Kernel/Magic/ConfigMagic.cs +++ b/src/Kernel/Magic/ConfigMagic.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -81,7 +80,7 @@ directory is loaded. /// - public override ExecutionResult Run(string? input, IChannel channel, CancellationToken cancellationToken) + public override ExecutionResult Run(string? input, IChannel channel) { // If we didn't get any input, treat it as a query. if (input == null || input.Trim().Length == 0) diff --git a/src/Kernel/Magic/EstimateMagic.cs b/src/Kernel/Magic/EstimateMagic.cs index de7c709f7d..54e58f81d0 100644 --- a/src/Kernel/Magic/EstimateMagic.cs +++ b/src/Kernel/Magic/EstimateMagic.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -44,8 +43,8 @@ public EstimateMagic(ISymbolResolver resolver) : base( public ISymbolResolver SymbolResolver { get; } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => - RunAsync(input, channel, cancellationToken).Result; + public override ExecutionResult Run(string input, IChannel channel) => + RunAsync(input, channel).Result; /// @@ -53,7 +52,7 @@ public override ExecutionResult Run(string input, IChannel channel, Cancellation /// serialization of its inputs, returns a task that can be awaited /// on for resource estimates from running that operation. /// - public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) + public async Task RunAsync(string input, IChannel channel) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/LsMagicMagic.cs b/src/Kernel/Magic/LsMagicMagic.cs index b88d0a6e6b..ce74406089 100644 --- a/src/Kernel/Magic/LsMagicMagic.cs +++ b/src/Kernel/Magic/LsMagicMagic.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Linq; -using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -44,7 +43,7 @@ public LsMagicMagic(IMagicSymbolResolver resolver, IExecutionEngine engine) : ba } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + public override ExecutionResult Run(string input, IChannel channel) => resolver .FindAllMagicSymbols() .Select(magic => new MagicSymbolSummary diff --git a/src/Kernel/Magic/PackageMagic.cs b/src/Kernel/Magic/PackageMagic.cs index 3239bac228..8ca8aa2d43 100644 --- a/src/Kernel/Magic/PackageMagic.cs +++ b/src/Kernel/Magic/PackageMagic.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Reactive.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Jupyter; @@ -40,7 +39,7 @@ public PackageMagic(IReferences references) : base( public IReferences References { get; } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) + public override ExecutionResult Run(string input, IChannel channel) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNamePackageName); var name = inputParameters.DecodeParameter(ParameterNamePackageName); @@ -48,7 +47,7 @@ public override ExecutionResult Run(string input, IChannel channel, Cancellation var statusUpdater = channel.DisplayUpdatable(status); void Update() => statusUpdater.Update(status); - var task = RunAsync(name, channel, cancellationToken, (newStatus) => + var task = RunAsync(name, channel, (newStatus) => { status.Subtask = newStatus; Update(); @@ -72,7 +71,7 @@ public override ExecutionResult Run(string input, IChannel channel, Cancellation /// a task that can be awaited on for the completion of the package /// download. /// - public async Task RunAsync(string name, IChannel channel, CancellationToken cancellationToken, Action statusCallback) + public async Task RunAsync(string name, IChannel channel, Action statusCallback) { if (!string.IsNullOrWhiteSpace(name)) { diff --git a/src/Kernel/Magic/PerformanceMagic.cs b/src/Kernel/Magic/PerformanceMagic.cs index eaf56cb430..e07919c7ec 100644 --- a/src/Kernel/Magic/PerformanceMagic.cs +++ b/src/Kernel/Magic/PerformanceMagic.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -33,7 +32,7 @@ public PerformanceMagic() : base( } /// - public override ExecutionResult Run(string? input, IChannel channel, CancellationToken cancellationToken) + public override ExecutionResult Run(string? input, IChannel channel) { var currentProcess = Process.GetCurrentProcess(); var performanceResult = new List<(string, string)> diff --git a/src/Kernel/Magic/Simulate.cs b/src/Kernel/Magic/Simulate.cs index 4d9723aad6..5e552541b8 100644 --- a/src/Kernel/Magic/Simulate.cs +++ b/src/Kernel/Magic/Simulate.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -49,14 +48,14 @@ public SimulateMagic(ISymbolResolver resolver, IConfigurationSource configuratio public IConfigurationSource ConfigurationSource { get; } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => - RunAsync(input, channel, cancellationToken).Result; + public override ExecutionResult Run(string input, IChannel channel) => + RunAsync(input, channel).Result; /// /// Simulates an operation given a string with its name and a JSON /// encoding of its arguments. /// - public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) + public async Task RunAsync(string input, IChannel channel) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/ToffoliMagic.cs b/src/Kernel/Magic/ToffoliMagic.cs index 0dd885074f..48160c2bef 100644 --- a/src/Kernel/Magic/ToffoliMagic.cs +++ b/src/Kernel/Magic/ToffoliMagic.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; @@ -37,14 +36,14 @@ public ToffoliMagic(ISymbolResolver resolver) : base( public ISymbolResolver SymbolResolver { get; } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => - RunAsync(input, channel, cancellationToken).Result; + public override ExecutionResult Run(string input, IChannel channel) => + RunAsync(input, channel).Result; /// /// Simulates a function/operation using the ToffoliSimulator as target machine. /// It expects a single input: the name of the function/operation to simulate. /// - public async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) + public async Task RunAsync(string input, IChannel channel) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName); diff --git a/src/Kernel/Magic/WhoMagic.cs b/src/Kernel/Magic/WhoMagic.cs index 959b39da11..f46743bc14 100644 --- a/src/Kernel/Magic/WhoMagic.cs +++ b/src/Kernel/Magic/WhoMagic.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Linq; -using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; @@ -35,7 +34,7 @@ public WhoMagic(ISnippets snippets) : base( public ISnippets Snippets { get; } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) => + public override ExecutionResult Run(string input, IChannel channel) => Snippets.Operations .Select(op => op.FullName) .OrderBy(name => name) diff --git a/src/Kernel/Magic/WorkspaceMagic.cs b/src/Kernel/Magic/WorkspaceMagic.cs index 5dc915a909..f53173d3d3 100644 --- a/src/Kernel/Magic/WorkspaceMagic.cs +++ b/src/Kernel/Magic/WorkspaceMagic.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Linq; -using System.Threading; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; using Microsoft.Quantum.IQSharp.Jupyter; @@ -51,7 +50,7 @@ public void CheckIfReady() } /// - public override ExecutionResult Run(string input, IChannel channel, CancellationToken cancellationToken) + public override ExecutionResult Run(string input, IChannel channel) { var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameCommand); var command = inputParameters.DecodeParameter(ParameterNameCommand); diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs index f76a2b6b51..b61d4a5f84 100644 --- a/src/Tests/AzureClientMagicTests.cs +++ b/src/Tests/AzureClientMagicTests.cs @@ -19,7 +19,7 @@ public static class AzureClientMagicTestExtensions { public static void Test(this MagicSymbol magic, string input, ExecuteStatus expected = ExecuteStatus.Ok) { - var result = magic.Execute(input, new MockChannel(), CancellationToken.None).GetAwaiter().GetResult(); + var result = magic.Execute(input, new MockChannel()).GetAwaiter().GetResult(); Assert.IsTrue(result.Status == expected); } } @@ -224,14 +224,14 @@ public async Task GetActiveTargetAsync(IChannel channel) return ActiveTargetId.ToExecutionResult(); } - public async Task SubmitJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext) + public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token) { LastAction = AzureClientAction.SubmitJob; SubmittedJobs.Add(submissionContext.OperationName); return ExecuteStatus.Ok.ToExecutionResult(); } - public async Task ExecuteJobAsync(IChannel channel, CancellationToken token, AzureSubmissionContext submissionContext) + public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token) { LastAction = AzureClientAction.ExecuteJob; ExecutedJobs.Add(submissionContext.OperationName); diff --git a/src/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs index 099c251268..773d7a6d8a 100644 --- a/src/Tests/AzureClientTests.cs +++ b/src/Tests/AzureClientTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Quantum; using Microsoft.Azure.Quantum.Client.Models; @@ -202,14 +203,14 @@ public void TestJobSubmission() var submissionContext = new AzureSubmissionContext(); // not yet connected - ExpectError(AzureClientError.NotConnected, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + ExpectError(AzureClientError.NotConnected, azureClient.SubmitJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); // connect var targets = ExpectSuccess>(ConnectToWorkspaceAsync(azureClient)); Assert.IsFalse(targets.Any()); // no target yet - ExpectError(AzureClientError.NoTarget, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + ExpectError(AzureClientError.NoTarget, azureClient.SubmitJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); // add a target var azureWorkspace = azureClient.ActiveWorkspace as MockAzureWorkspace; @@ -221,15 +222,15 @@ public void TestJobSubmission() Assert.AreEqual("ionq.simulator", target.Id); // no operation name specified - ExpectError(AzureClientError.NoOperationName, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + ExpectError(AzureClientError.NoOperationName, azureClient.SubmitJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); // specify an operation name, but have missing parameters submissionContext.OperationName = "Tests.qss.HelloAgain"; - ExpectError(AzureClientError.JobSubmissionFailed, azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + ExpectError(AzureClientError.JobSubmissionFailed, azureClient.SubmitJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); // specify input parameters and verify that the job was submitted submissionContext.InputParameters = new Dictionary() { ["count"] = "3", ["name"] = "testing" }; - var job = ExpectSuccess(azureClient.SubmitJobAsync(new MockChannel(), submissionContext)); + var job = ExpectSuccess(azureClient.SubmitJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); var retrievedJob = ExpectSuccess(azureClient.GetJobStatusAsync(new MockChannel(), job.Id)); Assert.AreEqual(job.Id, retrievedJob.Id); } @@ -261,7 +262,7 @@ public void TestJobExecution() ExecutionTimeout = 5, ExecutionPollingInterval = 1, }; - var histogram = ExpectSuccess(azureClient.ExecuteJobAsync(new MockChannel(), submissionContext)); + var histogram = ExpectSuccess(azureClient.ExecuteJobAsync(new MockChannel(), submissionContext, CancellationToken.None)); Assert.IsNotNull(histogram); } } diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 1da1e9031f..2d800b3bb9 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; @@ -47,7 +46,7 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) public static async Task AssertCompile(IQSharpEngine engine, string source, params string[] expectedOps) { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(source, channel, CancellationToken.None); + var response = await engine.ExecuteMundane(source, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -61,7 +60,7 @@ public static async Task AssertSimulate(IQSharpEngine engine, string sni var configSource = new ConfigurationSource(skipLoading: true); var simMagic = new SimulateMagic(engine.SymbolsResolver, configSource); var channel = new MockChannel(); - var response = await simMagic.Execute(snippetName, channel, CancellationToken.None); + var response = await simMagic.Execute(snippetName, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); CollectionAssert.AreEqual(messages.Select(ChannelWithNewLines.Format).ToArray(), channel.msgs.ToArray()); @@ -73,7 +72,7 @@ public static async Task AssertEstimate(IQSharpEngine engine, string sni { var channel = new MockChannel(); var estimateMagic = new EstimateMagic(engine.SymbolsResolver); - var response = await estimateMagic.Execute(snippetName, channel, CancellationToken.None); + var response = await estimateMagic.Execute(snippetName, channel); var result = response.Output as DataTable; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -104,7 +103,7 @@ public async Task CompileAndSimulate() var channel = new MockChannel(); // Try running without compiling it, fails: - var response = await simMagic.Execute("_snippet_.HelloQ", channel, CancellationToken.None); + var response = await simMagic.Execute("_snippet_.HelloQ", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -156,7 +155,7 @@ public async Task Toffoli() // Run with toffoli simulator: var toffoliMagic = new ToffoliMagic(engine.SymbolsResolver); - var response = await toffoliMagic.Execute("HelloQ", channel, CancellationToken.None); + var response = await toffoliMagic.Execute("HelloQ", channel); var result = response.Output as Dictionary; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -220,7 +219,7 @@ public async Task ReportWarnings() { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel, CancellationToken.None); + var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(3, channel.msgs.Count); @@ -230,7 +229,7 @@ public async Task ReportWarnings() { var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel, CancellationToken.None); + var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(1, channel.msgs.Count); @@ -245,7 +244,7 @@ public async Task ReportErrors() var engine = Init(); var channel = new MockChannel(); - var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel, CancellationToken.None); + var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -259,7 +258,7 @@ public async Task TestPackages() var snippets = engine.Snippets as Snippets; var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = await pkgMagic.Execute("", channel, CancellationToken.None); + var response = await pkgMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -269,11 +268,11 @@ public async Task TestPackages() // Try compiling TrotterEstimateEnergy, it should fail due to the lack // of chemistry package. - response = await engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel, CancellationToken.None); + response = await engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel, CancellationToken.None); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -294,7 +293,7 @@ public async Task TestInvalidPackages() var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = await pkgMagic.Execute("microsoft.quantum", channel, CancellationToken.None); + var response = await pkgMagic.Execute("microsoft.quantum", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); @@ -314,7 +313,7 @@ public async Task TestWho() var channel = new MockChannel(); // Check the workspace, it should be in error state: - var response = await whoMagic.Execute("", channel, CancellationToken.None); + var response = await whoMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -336,34 +335,34 @@ public async Task TestWorkspace() var result = new string[0]; // Check the workspace, it should be in error state: - var response = await wsMagic.Execute("reload", channel, CancellationToken.None); + var response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = await wsMagic.Execute("", channel, CancellationToken.None); + response = await wsMagic.Execute("", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); // Try compiling a snippet that depends on a workspace that depends on the chemistry package: - response = await engine.ExecuteMundane(SNIPPETS.DependsOnChemistryWorkspace, channel, CancellationToken.None); + response = await engine.ExecuteMundane(SNIPPETS.DependsOnChemistryWorkspace, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); // Add dependencies: - response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel, CancellationToken.None); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = await pkgMagic.Execute("microsoft.quantum.research", channel, CancellationToken.None); + response = await pkgMagic.Execute("microsoft.quantum.research", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); // Reload workspace: - response = await wsMagic.Execute("reload", channel, CancellationToken.None); + response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = await wsMagic.Execute("", channel, CancellationToken.None); + response = await wsMagic.Execute("", channel); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -373,12 +372,12 @@ public async Task TestWorkspace() await AssertCompile(engine, SNIPPETS.DependsOnChemistryWorkspace, "DependsOnChemistryWorkspace"); // Check an invalid command - response = await wsMagic.Execute("foo", channel, CancellationToken.None); + response = await wsMagic.Execute("foo", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); // Check that everything still works: - response = await wsMagic.Execute("", channel, CancellationToken.None); + response = await wsMagic.Execute("", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); } From 4d227fd32ae5406a43767a2dbc4e5d7407bad127 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Fri, 19 Jun 2020 21:28:38 -0700 Subject: [PATCH 12/22] Whitespace fixes --- src/AzureClient/AzureClient.cs | 1 - src/Kernel/Magic/ConfigMagic.cs | 1 + src/Kernel/Magic/LsMagicMagic.cs | 1 + src/Kernel/Magic/PerformanceMagic.cs | 1 + src/Kernel/Magic/WhoMagic.cs | 1 + src/Kernel/Magic/WorkspaceMagic.cs | 1 + 6 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 4a3a6993bc..e99b37eced 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -50,7 +50,6 @@ public AzureClient( EntryPointGenerator = entryPointGenerator; MetadataController = metadataController; Logger = logger; - eventService?.TriggerServiceInitialized(this); if (engine is BaseEngine baseEngine) diff --git a/src/Kernel/Magic/ConfigMagic.cs b/src/Kernel/Magic/ConfigMagic.cs index 6490a3cabb..586a8a6cbb 100644 --- a/src/Kernel/Magic/ConfigMagic.cs +++ b/src/Kernel/Magic/ConfigMagic.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; diff --git a/src/Kernel/Magic/LsMagicMagic.cs b/src/Kernel/Magic/LsMagicMagic.cs index ce74406089..05e1037e00 100644 --- a/src/Kernel/Magic/LsMagicMagic.cs +++ b/src/Kernel/Magic/LsMagicMagic.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Linq; + using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; diff --git a/src/Kernel/Magic/PerformanceMagic.cs b/src/Kernel/Magic/PerformanceMagic.cs index e07919c7ec..a1586e57f3 100644 --- a/src/Kernel/Magic/PerformanceMagic.cs +++ b/src/Kernel/Magic/PerformanceMagic.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; diff --git a/src/Kernel/Magic/WhoMagic.cs b/src/Kernel/Magic/WhoMagic.cs index f46743bc14..9a8c514f4d 100644 --- a/src/Kernel/Magic/WhoMagic.cs +++ b/src/Kernel/Magic/WhoMagic.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Linq; + using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp; using Microsoft.Quantum.IQSharp.Jupyter; diff --git a/src/Kernel/Magic/WorkspaceMagic.cs b/src/Kernel/Magic/WorkspaceMagic.cs index f53173d3d3..22b089f7b7 100644 --- a/src/Kernel/Magic/WorkspaceMagic.cs +++ b/src/Kernel/Magic/WorkspaceMagic.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Linq; + using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; using Microsoft.Quantum.IQSharp.Jupyter; From 0208ca5a9e5f525307c9f0942a6e094219d8babf Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Sat, 20 Jun 2020 21:14:05 -0700 Subject: [PATCH 13/22] Updates for latest jupyter-core changes --- src/Jupyter/Jupyter.csproj | 2 +- src/Tests/Mocks.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 22fb911269..f09a9508e7 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 696967dd82..1b2cc0bb89 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -37,7 +37,6 @@ public class MockShell : IShellServer public event Action KernelInfoRequest; public event Action ExecuteRequest; public event Action ShutdownRequest; - public event Action InterruptRequest; internal void Handle(Message message) { From 6276830d6b28bd5300990f55142111399dfb90c1 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Sat, 20 Jun 2020 21:16:30 -0700 Subject: [PATCH 14/22] Improve readability --- src/AzureClient/AzureClient.cs | 36 ++++++++++++++++------------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index e99b37eced..9dae1ff5a0 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -183,30 +183,28 @@ private async Task SubmitOrExecuteJobAsync( return AzureClientError.JobSubmissionFailed.ToExecutionResult(); } - if (!waitForCompletion) + if (waitForCompletion) { - return await GetJobStatusAsync(channel, MostRecentJobId); - } - - channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); + channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); - using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) - using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) - { - try + using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) + using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) { - CloudJob? cloudJob = null; - while (cloudJob == null || cloudJob.InProgress) + try { - executionCancellationTokenSource.Token.ThrowIfCancellationRequested(); - await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), executionCancellationTokenSource.Token); - cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); - channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); + CloudJob? cloudJob = null; + while (cloudJob == null || cloudJob.InProgress) + { + executionCancellationTokenSource.Token.ThrowIfCancellationRequested(); + await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), executionCancellationTokenSource.Token); + cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); + channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); + } + } + catch (Exception e) when (e is TaskCanceledException || e is OperationCanceledException) + { + Logger?.LogInformation($"Operation canceled while waiting for job execution to complete: {e.Message}"); } - } - catch (Exception e) when (e is TaskCanceledException || e is OperationCanceledException) - { - Logger?.LogInformation($"Operation canceled while waiting for job execution to complete: {e.Message}"); } } From 0c25ff8e1e77315e57d6524e2e5ffa29fa7f66cb Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Sat, 20 Jun 2020 21:22:00 -0700 Subject: [PATCH 15/22] Improve readability correctly --- src/AzureClient/AzureClient.cs | 46 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 9dae1ff5a0..5d11696ed2 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -114,7 +114,7 @@ public async Task GetConnectionStatusAsync(IChannel channel) private async Task SubmitOrExecuteJobAsync( IChannel channel, AzureSubmissionContext submissionContext, - bool waitForCompletion, + bool execute, CancellationToken cancellationToken) { if (ActiveWorkspace == null) @@ -131,7 +131,7 @@ private async Task SubmitOrExecuteJobAsync( if (string.IsNullOrEmpty(submissionContext.OperationName)) { - channel.Stderr($"Please pass a valid Q# operation name to {GetCommandDisplayName(waitForCompletion ? "execute" : "submit")}."); + channel.Stderr($"Please pass a valid Q# operation name to {GetCommandDisplayName(execute ? "execute" : "submit")}."); return AzureClientError.NoOperationName.ToExecutionResult(); } @@ -183,29 +183,33 @@ private async Task SubmitOrExecuteJobAsync( return AzureClientError.JobSubmissionFailed.ToExecutionResult(); } - if (waitForCompletion) + // If the command was not %azure.execute, simply return the job status. + if (!execute) { - channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); + return await GetJobStatusAsync(channel, MostRecentJobId); + } + + // If the command was %azure.execute, wait for the job to complete and return the job output. + channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); - using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) - using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) + using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) + using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) + { + try { - try + CloudJob? cloudJob = null; + while (cloudJob == null || cloudJob.InProgress) { - CloudJob? cloudJob = null; - while (cloudJob == null || cloudJob.InProgress) - { - executionCancellationTokenSource.Token.ThrowIfCancellationRequested(); - await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), executionCancellationTokenSource.Token); - cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); - channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); - } - } - catch (Exception e) when (e is TaskCanceledException || e is OperationCanceledException) - { - Logger?.LogInformation($"Operation canceled while waiting for job execution to complete: {e.Message}"); + executionCancellationTokenSource.Token.ThrowIfCancellationRequested(); + await Task.Delay(TimeSpan.FromSeconds(submissionContext.ExecutionPollingInterval), executionCancellationTokenSource.Token); + cloudJob = await ActiveWorkspace.GetJobAsync(MostRecentJobId); + channel.Stdout($"[{DateTime.Now.ToLongTimeString()}] Current job status: {cloudJob?.Status ?? "Unknown"}"); } } + catch (Exception e) when (e is TaskCanceledException || e is OperationCanceledException) + { + Logger?.LogInformation($"Operation canceled while waiting for job execution to complete: {e.Message}"); + } } return await GetJobResultAsync(channel, MostRecentJobId); @@ -213,11 +217,11 @@ private async Task SubmitOrExecuteJobAsync( /// public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => - await SubmitOrExecuteJobAsync(channel, submissionContext, waitForCompletion: false, cancellationToken); + await SubmitOrExecuteJobAsync(channel, submissionContext, execute: false, cancellationToken); /// public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => - await SubmitOrExecuteJobAsync(channel, submissionContext, waitForCompletion: true, cancellationToken); + await SubmitOrExecuteJobAsync(channel, submissionContext, execute: true, cancellationToken); /// public async Task GetActiveTargetAsync(IChannel channel) From cb845a94651e8f495010fd0bc003608fba195857 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 07:21:36 -0700 Subject: [PATCH 16/22] Microsoft.Jupyter.Core version --- src/Jupyter/Jupyter.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index f09a9508e7..b22e70c8e0 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From 366e6ba7fd37dfc47809303ba0e384865992c87c Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 07:53:43 -0700 Subject: [PATCH 17/22] Use Jupyter.Core CI package temporarily --- src/Jupyter/Jupyter.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index b22e70c8e0..7dc9b28ad6 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From 3c8bf9b89a646e6a27b15fe499f3327d8f7518be Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 08:07:06 -0700 Subject: [PATCH 18/22] Update to CI package version from master --- src/Jupyter/Jupyter.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 7dc9b28ad6..1ffb1dea10 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From 1ba0e27b4a49b2a20dad62c26a892b3419049920 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 08:28:48 -0700 Subject: [PATCH 19/22] Whitespace --- src/Tests/Mocks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 1b2cc0bb89..84d06abe64 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -36,7 +36,7 @@ public class MockShell : IShellServer { public event Action KernelInfoRequest; public event Action ExecuteRequest; - public event Action ShutdownRequest; + public event Action ShutdownRequest; internal void Handle(Message message) { From 3f684b6a9b56602e2a871dc3a58be89fa4de910d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 10:06:44 -0700 Subject: [PATCH 20/22] Apply suggestions from code review Co-authored-by: Chris Granade --- src/AzureClient/AzureClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 5d11696ed2..318e4c1118 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -192,8 +192,8 @@ private async Task SubmitOrExecuteJobAsync( // If the command was %azure.execute, wait for the job to complete and return the job output. channel.Stdout($"Waiting up to {submissionContext.ExecutionTimeout} seconds for Azure Quantum job to complete..."); - using (var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout))) - using (var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken)) + using var executionTimeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(submissionContext.ExecutionTimeout)); + using var executionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(executionTimeoutTokenSource.Token, cancellationToken); { try { From 858975158aa5fedaa1995a95fc8ad149ec43aad2 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 10:07:25 -0700 Subject: [PATCH 21/22] Default value for CancellationToken in IAzureClient --- src/AzureClient/AzureClient.cs | 8 ++++---- src/AzureClient/IAzureClient.cs | 4 ++-- src/Tests/AzureClientMagicTests.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 318e4c1118..749e674d22 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -216,12 +216,12 @@ private async Task SubmitOrExecuteJobAsync( } /// - public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => - await SubmitOrExecuteJobAsync(channel, submissionContext, execute: false, cancellationToken); + public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? cancellationToken = null) => + await SubmitOrExecuteJobAsync(channel, submissionContext, execute: false, cancellationToken ?? CancellationToken.None); /// - public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken cancellationToken) => - await SubmitOrExecuteJobAsync(channel, submissionContext, execute: true, cancellationToken); + public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? cancellationToken = null) => + await SubmitOrExecuteJobAsync(channel, submissionContext, execute: true, cancellationToken ?? CancellationToken.None); /// public async Task GetActiveTargetAsync(IChannel channel) diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index 640d39b139..c820564c3e 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -45,7 +45,7 @@ public Task ConnectAsync(IChannel channel, /// /// Details of the submitted job, or an error if submission failed. /// - public Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token); + public Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? token); /// /// Executes the specified Q# operation as a job to the currently active target @@ -54,7 +54,7 @@ public Task ConnectAsync(IChannel channel, /// /// The result of the executed job, or an error if execution failed. /// - public Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token); + public Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? token); /// /// Sets the specified target for job submission. diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs index b61d4a5f84..19ebf98982 100644 --- a/src/Tests/AzureClientMagicTests.cs +++ b/src/Tests/AzureClientMagicTests.cs @@ -224,14 +224,14 @@ public async Task GetActiveTargetAsync(IChannel channel) return ActiveTargetId.ToExecutionResult(); } - public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token) + public async Task SubmitJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? token) { LastAction = AzureClientAction.SubmitJob; SubmittedJobs.Add(submissionContext.OperationName); return ExecuteStatus.Ok.ToExecutionResult(); } - public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken token) + public async Task ExecuteJobAsync(IChannel channel, AzureSubmissionContext submissionContext, CancellationToken? token) { LastAction = AzureClientAction.ExecuteJob; ExecutedJobs.Add(submissionContext.OperationName); From 40ee9feb860e66908559f988ea075978a8d64255 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer Date: Mon, 22 Jun 2020 11:15:23 -0700 Subject: [PATCH 22/22] Use released Microsoft.Jupyter.Core version --- src/Jupyter/Jupyter.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 1ffb1dea10..b092621bf2 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - +