diff --git a/build/ci.yml b/build/ci.yml index 76343709d3..3bb8115793 100644 --- a/build/ci.yml +++ b/build/ci.yml @@ -4,7 +4,7 @@ trigger: variables: Build.Major: 0 - Build.Minor: 10 + Build.Minor: 11 Drops.Dir: $(Build.ArtifactStagingDirectory)/drops jobs: diff --git a/src/Jupyter/CustomShell/ClientInfoHandler.cs b/src/Jupyter/CustomShell/ClientInfoHandler.cs index bc0b066155..211d0380de 100644 --- a/src/Jupyter/CustomShell/ClientInfoHandler.cs +++ b/src/Jupyter/CustomShell/ClientInfoHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Jupyter.Core; using Microsoft.Jupyter.Core.Protocol; @@ -83,9 +84,11 @@ IShellServer shellServer this.shellServer = shellServer; } + /// public string MessageType => "iqsharp_clientinfo_request"; - public void Handle(Message message) + /// + public async Task HandleAsync(Message message) { var content = message.To(); metadata.UserAgent = content.UserAgent ?? metadata.UserAgent; diff --git a/src/Jupyter/CustomShell/EchoHandler.cs b/src/Jupyter/CustomShell/EchoHandler.cs index 352b1e4545..29689fa4cb 100644 --- a/src/Jupyter/CustomShell/EchoHandler.cs +++ b/src/Jupyter/CustomShell/EchoHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Jupyter.Core; using Microsoft.Jupyter.Core.Protocol; @@ -34,7 +35,7 @@ IShellServer shellServer public string MessageType => "iqsharp_echo_request"; - public void Handle(Message message) + public async Task HandleAsync(Message message) { // Find out the thing we need to echo back. var value = (message.Content as UnknownContent).Data["value"] as string; diff --git a/src/Jupyter/Extensions.cs b/src/Jupyter/Extensions.cs index 624b4935f6..a80b941c41 100644 --- a/src/Jupyter/Extensions.cs +++ b/src/Jupyter/Extensions.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; +using System.Text.RegularExpressions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Jupyter.Core; using Microsoft.Jupyter.Core.Protocol; @@ -169,5 +169,32 @@ public static T WithStackTraceDisplay(this T simulator, IChannel channel) }; return simulator; } + + /// + /// Removes common indents from each line in a string, + /// similarly to Python's textwrap.dedent() function. + /// + internal static string Dedent(this string text) + { + // First, start by finding the length of common indents, + // disregarding lines that are only whitespace. + var leadingWhitespaceRegex = new Regex(@"^[ \t]*"); + var minWhitespace = int.MaxValue; + foreach (var line in text.Split("\n")) + { + if (!string.IsNullOrWhiteSpace(line)) + { + var match = leadingWhitespaceRegex.Match(line); + minWhitespace = match.Success + ? System.Math.Min(minWhitespace, match.Value.Length) + : minWhitespace = 0; + } + } + + // We can use that to build a new regex that strips + // out common indenting. + var leftTrimRegex = new Regex(@$"^[ \t]{{{minWhitespace}}}", RegexOptions.Multiline); + return leftTrimRegex.Replace(text, ""); + } } } diff --git a/src/Jupyter/IQSharpEngine.cs b/src/Jupyter/IQSharpEngine.cs index 758a9bb1de..a275044455 100644 --- a/src/Jupyter/IQSharpEngine.cs +++ b/src/Jupyter/IQSharpEngine.cs @@ -15,6 +15,7 @@ using Newtonsoft.Json.Converters; using Microsoft.Jupyter.Core.Protocol; using Newtonsoft.Json; +using System.Threading.Tasks; namespace Microsoft.Quantum.IQSharp.Jupyter { @@ -39,7 +40,7 @@ public IQSharpEngine( PerformanceMonitor performanceMonitor, IShellRouter shellRouter, IEventService eventService - ) : base(shell, context, logger) + ) : base(shell, shellRouter, context, logger, services) { this.performanceMonitor = performanceMonitor; performanceMonitor.Start(); @@ -86,7 +87,7 @@ IEventService eventService /// 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 ExecutionResult ExecuteMundane(string input, IChannel channel) + public override async Task ExecuteMundane(string input, IChannel channel) { channel = channel.WithNewLines(); diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index f5d21b940d..4fd43c8606 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Jupyter/Magic/AbstractMagic.cs b/src/Jupyter/Magic/AbstractMagic.cs index 999d8b4824..f3118d61f3 100644 --- a/src/Jupyter/Magic/AbstractMagic.cs +++ b/src/Jupyter/Magic/AbstractMagic.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using System.Threading.Tasks; using Microsoft.Jupyter.Core; using Microsoft.Quantum.IQSharp.Common; using Microsoft.Quantum.IQSharp.Jupyter; @@ -34,8 +34,8 @@ public AbstractMagic(string keyword, Documentation docs) /// returned execution function displays the given exceptions to its /// display channel. /// - public Func SafeExecute(Func magic) => - (input, channel) => + public Func> SafeExecute(Func magic) => + async (input, channel) => { channel = channel.WithNewLines(); diff --git a/src/Jupyter/Magic/ConfigMagic.cs b/src/Jupyter/Magic/ConfigMagic.cs index 7dd95e7337..7af2e713ec 100644 --- a/src/Jupyter/Magic/ConfigMagic.cs +++ b/src/Jupyter/Magic/ConfigMagic.cs @@ -25,7 +25,48 @@ public class ConfigMagic : AbstractMagic public ConfigMagic(IConfigurationSource configurationSource) : base( "config", new Documentation { - Summary = "Allows setting or querying configuration options." + Summary = "Allows setting or querying configuration options.", + Description = @" + This magic command allows for setting or querying + configuration options used to control the behavior of the + IQ# kernel (e.g.: state visualization options), and to + save those options to a JSON file in the current working + directory. + ".Dedent(), + Examples = new [] + { + @" + Print a list of all currently set configuration options: + ``` + In []: %config + Out[]: Configuration key Value + --------------------------------- ----------- + dump.basisStateLabelingConvention ""BigEndian"" + dump.truncateSmallAmplitudes true + ``` + ", + + @" + Configure the `DumpMachine` and `DumpRegister` callables + to use big-endian convention: + ``` + In []: %config dump.basisStateLabelingConvention = ""BigEndian"" + Out[]: ""BigEndian"" + ``` + ".Dedent(), + + @" + Save current configuration options to `.iqsharp-config.json` + in the current working directory: + ``` + In []: %config --save + Out[]: + ``` + Note that options saved this way will be applied automatically + the next time a notebook in the current working + directory is loaded. + ".Dedent() + } }) { this.ConfigurationSource = configurationSource; diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 84f50ee3cc..10c5e03fd7 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Jupyter.Core; @@ -40,10 +41,10 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) foreach (var m in channel.msgs) Console.WriteLine($" {m}"); } - public static string AssertCompile(IQSharpEngine engine, string source, params string[] expectedOps) + public static async Task AssertCompile(IQSharpEngine engine, string source, params string[] expectedOps) { var channel = new MockChannel(); - var response = engine.ExecuteMundane(source, channel); + var response = await engine.ExecuteMundane(source, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -52,12 +53,12 @@ public static string AssertCompile(IQSharpEngine engine, string source, params s return response.Output?.ToString(); } - public static string AssertSimulate(IQSharpEngine engine, string snippetName, params string[] messages) + public static async Task AssertSimulate(IQSharpEngine engine, string snippetName, params string[] messages) { var configSource = new ConfigurationSource(skipLoading: true); var simMagic = new SimulateMagic(engine.SymbolsResolver, configSource); var channel = new MockChannel(); - var response = simMagic.Execute(snippetName, channel); + 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()); @@ -65,11 +66,11 @@ public static string AssertSimulate(IQSharpEngine engine, string snippetName, pa return response.Output?.ToString(); } - public static string AssertEstimate(IQSharpEngine engine, string snippetName, params string[] messages) + public static async Task AssertEstimate(IQSharpEngine engine, string snippetName, params string[] messages) { var channel = new MockChannel(); var estimateMagic = new EstimateMagic(engine.SymbolsResolver); - var response = estimateMagic.Execute(snippetName, channel); + var response = await estimateMagic.Execute(snippetName, channel); var result = response.Output as DataTable; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -84,15 +85,15 @@ public static string AssertEstimate(IQSharpEngine engine, string snippetName, pa } [TestMethod] - public void CompileOne() + public async Task CompileOne() { var engine = Init(); - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); } [TestMethod] - public void CompileAndSimulate() + public async Task CompileAndSimulate() { var engine = Init(); var configSource = new ConfigurationSource(skipLoading: true); @@ -100,7 +101,7 @@ public void CompileAndSimulate() var channel = new MockChannel(); // Try running without compiling it, fails: - var response = simMagic.Execute("_snippet_.HelloQ", channel); + var response = await simMagic.Execute("_snippet_.HelloQ", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -108,51 +109,51 @@ public void CompileAndSimulate() Assert.AreEqual(ChannelWithNewLines.Format($"Invalid operation name: _snippet_.HelloQ"), channel.errors[0]); // Compile it: - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Try running again: - AssertSimulate(engine, "HelloQ", "Hello from quantum world!"); + await AssertSimulate(engine, "HelloQ", "Hello from quantum world!"); } [TestMethod] - public void SimulateWithArguments() + public async Task SimulateWithArguments() { var engine = Init(); // Compile it: - AssertCompile(engine, SNIPPETS.Reverse, "Reverse"); + await AssertCompile(engine, SNIPPETS.Reverse, "Reverse"); // Try running again: - var results = AssertSimulate(engine, "Reverse { \"array\": [2, 3, 4], \"name\": \"foo\" }", "Hello foo"); + var results = await AssertSimulate(engine, "Reverse { \"array\": [2, 3, 4], \"name\": \"foo\" }", "Hello foo"); Assert.AreEqual("[4,3,2]", results); } [TestMethod] - public void Estimate() + public async Task Estimate() { var engine = Init(); var channel = new MockChannel(); // Compile it: - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Try running again: - AssertEstimate(engine, "HelloQ"); + await AssertEstimate(engine, "HelloQ"); } [TestMethod] - public void Toffoli() + public async Task Toffoli() { var engine = Init(); var channel = new MockChannel(); // Compile it: - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Run with toffoli simulator: var toffoliMagic = new ToffoliMagic(engine.SymbolsResolver); - var response = toffoliMagic.Execute("HelloQ", channel); + var response = await toffoliMagic.Execute("HelloQ", channel); var result = response.Output as Dictionary; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -161,62 +162,62 @@ public void Toffoli() } [TestMethod] - public void DependsOnWorkspace() + public async Task DependsOnWorkspace() { var engine = Init(); // Compile it: - AssertCompile(engine, SNIPPETS.DependsOnWorkspace, "DependsOnWorkspace"); + await AssertCompile(engine, SNIPPETS.DependsOnWorkspace, "DependsOnWorkspace"); // Run: - var results = AssertSimulate(engine, "DependsOnWorkspace", "Hello Foo again!"); + var results = await AssertSimulate(engine, "DependsOnWorkspace", "Hello Foo again!"); Assert.AreEqual("[Zero,One,Zero,Zero,Zero]", results); } [TestMethod] - public void UpdateSnippet() + public async Task UpdateSnippet() { var engine = Init(); // Compile it: - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Run: - AssertSimulate(engine, "HelloQ", "Hello from quantum world!"); + await AssertSimulate(engine, "HelloQ", "Hello from quantum world!"); // Compile it with a new code - AssertCompile(engine, SNIPPETS.HelloQ_2, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ_2, "HelloQ"); // Run again: - AssertSimulate(engine, "HelloQ", "msg0", "msg1"); + await AssertSimulate(engine, "HelloQ", "msg0", "msg1"); } [TestMethod] - public void UpdateDependency() + public async Task UpdateDependency() { var engine = Init(); // Compile HelloQ - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Compile something that depends on it: - AssertCompile(engine, SNIPPETS.DependsOnHelloQ, "DependsOnHelloQ"); + await AssertCompile(engine, SNIPPETS.DependsOnHelloQ, "DependsOnHelloQ"); // Compile new version of HelloQ - AssertCompile(engine, SNIPPETS.HelloQ_2, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ_2, "HelloQ"); // Run dependency, it should reflect changes on HelloQ: - AssertSimulate(engine, "DependsOnHelloQ", "msg0", "msg1"); + await AssertSimulate(engine, "DependsOnHelloQ", "msg0", "msg1"); } [TestMethod] - public void ReportWarnings() + public async Task ReportWarnings() { var engine = Init(); { var channel = new MockChannel(); - var response = engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel); + var response = await engine.ExecuteMundane(SNIPPETS.ThreeWarnings, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(3, channel.msgs.Count); @@ -226,7 +227,7 @@ public void ReportWarnings() { var channel = new MockChannel(); - var response = engine.ExecuteMundane(SNIPPETS.OneWarning, channel); + var response = await engine.ExecuteMundane(SNIPPETS.OneWarning, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(1, channel.msgs.Count); @@ -236,12 +237,12 @@ public void ReportWarnings() } [TestMethod] - public void ReportErrors() + public async Task ReportErrors() { var engine = Init(); var channel = new MockChannel(); - var response = engine.ExecuteMundane(SNIPPETS.TwoErrors, channel); + var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); Assert.AreEqual(0, channel.msgs.Count); @@ -249,13 +250,13 @@ public void ReportErrors() } [TestMethod] - public void TestPackages() + public async Task TestPackages() { var engine = Init(); var snippets = engine.Snippets as Snippets; var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = pkgMagic.Execute("", channel); + var response = await pkgMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -265,11 +266,11 @@ public void TestPackages() // Try compiling TrotterEstimateEnergy, it should fail due to the lack // of chemistry package. - response = engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel); + response = await engine.ExecuteMundane(SNIPPETS.TrotterEstimateEnergy, channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = pkgMagic.Execute("microsoft.quantum.chemistry", channel); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -278,19 +279,19 @@ public void TestPackages() Assert.AreEqual(2, result.Length); // Now it should compile: - AssertCompile(engine, SNIPPETS.TrotterEstimateEnergy, "TrotterEstimateEnergy"); + await AssertCompile(engine, SNIPPETS.TrotterEstimateEnergy, "TrotterEstimateEnergy"); } [TestMethod] - public void TestInvalidPackages() + public async Task TestInvalidPackages() { var engine = Init(); var snippets = engine.Snippets as Snippets; var pkgMagic = new PackageMagic(snippets.GlobalReferences); var channel = new MockChannel(); - var response = pkgMagic.Execute("microsoft.quantum", channel); + var response = await pkgMagic.Execute("microsoft.quantum", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); @@ -301,7 +302,7 @@ public void TestInvalidPackages() [TestMethod] - public void TestWho() + public async Task TestWho() { var snippets = Startup.Create("Workspace"); snippets.Compile(SNIPPETS.HelloQ); @@ -310,7 +311,7 @@ public void TestWho() var channel = new MockChannel(); // Check the workspace, it should be in error state: - var response = whoMagic.Execute("", channel); + var response = await whoMagic.Execute("", channel); var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); @@ -320,7 +321,7 @@ public void TestWho() } [TestMethod] - public void TestWorkspace() + public async Task TestWorkspace() { var engine = Init("Workspace.Chemistry"); var snippets = engine.Snippets as Snippets; @@ -332,49 +333,49 @@ public void TestWorkspace() var result = new string[0]; // Check the workspace, it should be in error state: - var response = wsMagic.Execute("reload", channel); + var response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); - response = wsMagic.Execute("", channel); + 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 = engine.ExecuteMundane(SNIPPETS.DependsOnChemistryWorkspace, channel); + 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 = pkgMagic.Execute("microsoft.quantum.chemistry", channel); + response = await pkgMagic.Execute("microsoft.quantum.chemistry", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = pkgMagic.Execute("microsoft.quantum.research", channel); + response = await pkgMagic.Execute("microsoft.quantum.research", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); // Reload workspace: - response = wsMagic.Execute("reload", channel); + response = await wsMagic.Execute("reload", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - response = wsMagic.Execute("", channel); + response = await wsMagic.Execute("", channel); result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(3, result.Length); // Now compilation must work: - AssertCompile(engine, SNIPPETS.DependsOnChemistryWorkspace, "DependsOnChemistryWorkspace"); + await AssertCompile(engine, SNIPPETS.DependsOnChemistryWorkspace, "DependsOnChemistryWorkspace"); // Check an invalid command - response = wsMagic.Execute("foo", channel); + response = await wsMagic.Execute("foo", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Error, response.Status); // Check that everything still works: - response = wsMagic.Execute("", channel); + response = await wsMagic.Execute("", channel); PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); } diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 6bbbd26ffd..11fcb4f645 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Extensions.Options; using Microsoft.Jupyter.Core; @@ -71,17 +72,17 @@ public MockShellRouter(MockShell shell) this.shell = shell; } - public void Handle(Message message) + public async Task Handle(Message message) { shell.Handle(message); } - public void RegisterFallback(Action fallback) + public void RegisterFallback(Func fallback) { throw new NotImplementedException(); } - public void RegisterHandler(string messageType, Action handler) + public void RegisterHandler(string messageType, Func handler) { }