From 83d8f048d8d81fbfd18f07b878bd2c471c6a06e4 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 15:53:54 -0700 Subject: [PATCH 01/10] Added string extension for stripping indents. --- src/Jupyter/Extensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Jupyter/Extensions.cs b/src/Jupyter/Extensions.cs index 624b4935f6..23d59b51a5 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,9 @@ public static T WithStackTraceDisplay(this T simulator, IChannel channel) }; return simulator; } + + internal static string TrimLeadingWhitespace(this string s) => + new Regex(@"^\s+", RegexOptions.Multiline) + .Replace(s, string.Empty); } } From cad6c9a54a00e1befed971011c7c573c9afaf6e7 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:20:11 -0700 Subject: [PATCH 02/10] Update MS.Jupyter.Core to 1.3. --- src/Jupyter/CustomShell/ClientInfoHandler.cs | 5 ++++- src/Jupyter/CustomShell/EchoHandler.cs | 3 ++- src/Jupyter/IQSharpEngine.cs | 5 +++-- src/Jupyter/Jupyter.csproj | 2 +- src/Jupyter/Magic/AbstractMagic.cs | 6 +++--- 5 files changed, 13 insertions(+), 8 deletions(-) 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/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(); From 3c50d0520382299f14c01ed169bcadace9823fd0 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:20:46 -0700 Subject: [PATCH 03/10] Add extended documentation to %config. --- src/Jupyter/Magic/ConfigMagic.cs | 43 +++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Jupyter/Magic/ConfigMagic.cs b/src/Jupyter/Magic/ConfigMagic.cs index 7dd95e7337..e147e86c09 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. + ".TrimLeadingWhitespace(), + 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"" + ``` + ".TrimLeadingWhitespace(), + + @" + 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. + ".TrimLeadingWhitespace() + } }) { this.ConfigurationSource = configurationSource; From 95103c87b58c9d612d1728d205d225e26fcdcc87 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:22:23 -0700 Subject: [PATCH 04/10] Bump minor version. --- build/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 94c1c16a451d96b43c47f015a9f2389367016676 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:28:46 -0700 Subject: [PATCH 05/10] Updated mocks. --- src/Tests/Mocks.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 6bbbd26ffd..1c35290c4f 100644 --- a/src/Tests/Mocks.cs +++ b/src/Tests/Mocks.cs @@ -71,17 +71,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) { } From 6fc84c2ebbcbc4c4cecb35f7f257511f2863d032 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:32:59 -0700 Subject: [PATCH 06/10] Forgot a using. --- src/Tests/Mocks.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs index 1c35290c4f..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; From 1b08d589406d5882bb286c24b05eb32e71f0d605 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:49:11 -0700 Subject: [PATCH 07/10] Adapted tests project as well. --- src/Tests/IQsharpEngineTests.cs | 87 +++++++++++++++++---------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 84f50ee3cc..d5ffc1a585 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); @@ -92,7 +93,7 @@ public void CompileOne() [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); @@ -115,15 +116,15 @@ public void CompileAndSimulate() } [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); } @@ -142,17 +143,17 @@ public void Estimate() } [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); @@ -192,31 +193,31 @@ public void UpdateSnippet() } [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,34 +333,34 @@ 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); @@ -369,12 +370,12 @@ public void TestWorkspace() 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); } From da68ba6a52a722f3f0a5fb993e1acb9c201656b4 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 16:58:05 -0700 Subject: [PATCH 08/10] Fixed failing test. --- src/Tests/IQsharpEngineTests.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index d5ffc1a585..0ecc8d9fce 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -88,7 +88,7 @@ public static async Task AssertEstimate(IQSharpEngine engine, string sni public void CompileOne() { var engine = Init(); - AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); + await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); } @@ -109,10 +109,10 @@ public async Task 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] @@ -136,10 +136,10 @@ public void Estimate() 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] @@ -167,10 +167,10 @@ public void 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); } @@ -180,16 +180,16 @@ public void 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] @@ -367,7 +367,7 @@ public async Task TestWorkspace() 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 = await wsMagic.Execute("foo", channel); From abd4337530988765b472885ccd27c8ddf227b082 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Tue, 7 Apr 2020 17:07:12 -0700 Subject: [PATCH 09/10] Adapted remaining tests. --- src/Tests/IQsharpEngineTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 0ecc8d9fce..10c5e03fd7 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -85,7 +85,7 @@ public static async Task AssertEstimate(IQSharpEngine engine, string sni } [TestMethod] - public void CompileOne() + public async Task CompileOne() { var engine = Init(); await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); @@ -130,7 +130,7 @@ public async Task SimulateWithArguments() [TestMethod] - public void Estimate() + public async Task Estimate() { var engine = Init(); var channel = new MockChannel(); @@ -162,7 +162,7 @@ public async Task Toffoli() } [TestMethod] - public void DependsOnWorkspace() + public async Task DependsOnWorkspace() { var engine = Init(); @@ -175,7 +175,7 @@ public void DependsOnWorkspace() } [TestMethod] - public void UpdateSnippet() + public async Task UpdateSnippet() { var engine = Init(); From 53bfcd80e6988893fba21912376edee5fd8ad6aa Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 8 Apr 2020 09:53:40 -0700 Subject: [PATCH 10/10] Change to use Dedent extension instead. --- src/Jupyter/Extensions.cs | 29 ++++++++++++++++++++++++++--- src/Jupyter/Magic/ConfigMagic.cs | 6 +++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Jupyter/Extensions.cs b/src/Jupyter/Extensions.cs index 23d59b51a5..a80b941c41 100644 --- a/src/Jupyter/Extensions.cs +++ b/src/Jupyter/Extensions.cs @@ -170,8 +170,31 @@ public static T WithStackTraceDisplay(this T simulator, IChannel channel) return simulator; } - internal static string TrimLeadingWhitespace(this string s) => - new Regex(@"^\s+", RegexOptions.Multiline) - .Replace(s, string.Empty); + /// + /// 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/Magic/ConfigMagic.cs b/src/Jupyter/Magic/ConfigMagic.cs index e147e86c09..7af2e713ec 100644 --- a/src/Jupyter/Magic/ConfigMagic.cs +++ b/src/Jupyter/Magic/ConfigMagic.cs @@ -32,7 +32,7 @@ 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. - ".TrimLeadingWhitespace(), + ".Dedent(), Examples = new [] { @" @@ -53,7 +53,7 @@ Configure the `DumpMachine` and `DumpRegister` callables In []: %config dump.basisStateLabelingConvention = ""BigEndian"" Out[]: ""BigEndian"" ``` - ".TrimLeadingWhitespace(), + ".Dedent(), @" Save current configuration options to `.iqsharp-config.json` @@ -65,7 +65,7 @@ Save current configuration options to `.iqsharp-config.json` Note that options saved this way will be applied automatically the next time a notebook in the current working directory is loaded. - ".TrimLeadingWhitespace() + ".Dedent() } }) {