From ae9fdd9e4f9e795938916ac20d41a1e197f2bd21 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 13 Jan 2021 18:28:09 -0800 Subject: [PATCH 01/15] Start engine services in Start() instead of constructor. --- src/Kernel/IQSharpEngine.cs | 61 +++++++++---- src/Kernel/Kernel.csproj | 1 + src/Kernel/Magic/Resolution/IMagicResolver.cs | 9 +- src/Kernel/Magic/Resolution/MagicResolver.cs | 90 ++++++++++--------- 4 files changed, 101 insertions(+), 60 deletions(-) diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 85b0300578..3f566171a7 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using System.Collections.Immutable; using Microsoft.Quantum.IQSharp.AzureClient; +using Microsoft.Quantum.QsCompiler.BondSchemas; namespace Microsoft.Quantum.IQSharp.Kernel { @@ -29,6 +30,19 @@ namespace Microsoft.Quantum.IQSharp.Kernel public class IQSharpEngine : BaseEngine { private readonly PerformanceMonitor performanceMonitor; + private readonly IConfigurationSource configurationSource; + private readonly IServiceProvider services; + private readonly ILogger logger; + + // NB: These properties may be null if the engine has not fully started + // up yet. + internal ISnippets? Snippets { get; private set; } = null; + + internal ISymbolResolver? SymbolsResolver { get; private set; } = null; + + internal IMagicSymbolResolver? MagicResolver { get; private set; } = null; + + internal IWorkspace? Workspace { get; private set; } = null; /// /// The main constructor. It expects an `ISnippets` instance that takes care @@ -41,20 +55,38 @@ public IQSharpEngine( IServiceProvider services, IConfigurationSource configurationSource, PerformanceMonitor performanceMonitor, - IShellRouter shellRouter, - IEventService eventService, - IMagicSymbolResolver magicSymbolResolver, - IReferences references + IShellRouter shellRouter ) : base(shell, shellRouter, context, logger, services) { this.performanceMonitor = performanceMonitor; performanceMonitor.Start(); + this.configurationSource = configurationSource; + this.services = services; + this.logger = logger; + } + + /// + public override void Start() + { + base.Start(); + + // Before anything else, make sure to start the right background + // thread on the Q# compilation loader to initialize serializers + // and deserializers. Since that runs in the background, starting + // the engine should not be blocked, and other services can + // continue to initialize while the Q# compilation loader works. + logger.LogDebug("Loading serialization and deserialziation protocols."); + Protocols.Initialize(); + logger.LogDebug("Getting services required to start IQ# engine."); this.Snippets = services.GetService(); this.SymbolsResolver = services.GetService(); - this.MagicResolver = magicSymbolResolver; + this.MagicResolver = services.GetService(); this.Workspace = services.GetService(); + var references = services.GetService(); + var eventService = services.GetService(); + logger.LogDebug("Registering IQ# display and JSON encoders."); RegisterDisplayEncoder(new IQSharpSymbolToHtmlResultEncoder()); RegisterDisplayEncoder(new IQSharpSymbolToTextResultEncoder()); RegisterDisplayEncoder(new TaskStatusToTextEncoder()); @@ -71,13 +103,15 @@ IReferences references .Concat(AzureClient.JsonConverters.AllConverters) .ToArray()); + logger.LogDebug("Registering IQ# symbol resolvers."); RegisterSymbolResolver(this.SymbolsResolver); RegisterSymbolResolver(this.MagicResolver); + logger.LogDebug("Loading known assemblies and registering package loading."); RegisterPackageLoadedEvent(services, logger, references); // Handle new shell messages. - shellRouter.RegisterHandlers(); + ShellRouter.RegisterHandlers(); // Report performance after completing startup. performanceMonitor.Report(); @@ -108,14 +142,17 @@ private void RegisterPackageLoadedEvent(IServiceProvider services, ILogger logge // Register new display encoders when packages load. references.PackageLoaded += (sender, args) => { - logger.LogDebug("Scanning for display encoders after loading {Package}.", args.PackageId); + logger.LogDebug("Scanning for display encoders and magic symbols after loading {Package}.", args.PackageId); foreach (var assembly in references.Assemblies .Select(asm => asm.Assembly) .Where(asm => !knownAssemblies.Contains(asm.GetName())) ) { // Look for display encoders in the new assembly. - logger.LogDebug("Found new assembly {Name}, looking for display encoders.", assembly.FullName); + logger.LogDebug("Found new assembly {Name}, looking for display encoders and magic symbols.", assembly.FullName); + // Use the magic resolver to find magic symbols in the new assembly; + // it will cache the results for the next magic resolution. + this.MagicResolver?.FindMagic(new AssemblyInfo(assembly)); // If types from an assembly cannot be loaded, log a warning and continue. var relevantTypes = Enumerable.Empty(); @@ -167,14 +204,6 @@ private void RegisterPackageLoadedEvent(IServiceProvider services, ILogger logge }; } - internal ISnippets Snippets { get; } - - internal ISymbolResolver SymbolsResolver { get; } - - internal ISymbolResolver MagicResolver { get; } - - internal IWorkspace Workspace { 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 diff --git a/src/Kernel/Kernel.csproj b/src/Kernel/Kernel.csproj index 78f2e8028e..8c5f7f668e 100644 --- a/src/Kernel/Kernel.csproj +++ b/src/Kernel/Kernel.csproj @@ -6,6 +6,7 @@ Microsoft.Quantum.IQSharp.Kernel Microsoft.Quantum.IQSharp.Kernel true + enable diff --git a/src/Kernel/Magic/Resolution/IMagicResolver.cs b/src/Kernel/Magic/Resolution/IMagicResolver.cs index a827328592..57f5d61f36 100644 --- a/src/Kernel/Magic/Resolution/IMagicResolver.cs +++ b/src/Kernel/Magic/Resolution/IMagicResolver.cs @@ -20,7 +20,7 @@ namespace Microsoft.Quantum.IQSharp.Kernel /// public interface IMagicSymbolResolver : ISymbolResolver { - ISymbol ISymbolResolver.Resolve(string symbolName) => + ISymbol? ISymbolResolver.Resolve(string symbolName) => this.Resolve(symbolName); /// @@ -29,11 +29,16 @@ ISymbol ISymbolResolver.Resolve(string symbolName) => /// /// The magic symbol name to resolve. /// The resolved object, or null if none was found. - public new MagicSymbol Resolve(string symbolName); + public new MagicSymbol? Resolve(string symbolName); /// /// Returns the list of all objects defined in loaded assemblies. /// public IEnumerable FindAllMagicSymbols(); + + /// + /// Finds the MagicSymbols inside an assembly, and returns an instance of each. + /// + public IEnumerable FindMagic(AssemblyInfo assm); } } diff --git a/src/Kernel/Magic/Resolution/MagicResolver.cs b/src/Kernel/Magic/Resolution/MagicResolver.cs index dae5b7c112..6b705f8586 100644 --- a/src/Kernel/Magic/Resolution/MagicResolver.cs +++ b/src/Kernel/Magic/Resolution/MagicResolver.cs @@ -84,7 +84,7 @@ private IEnumerable RelevantAssemblies() /// Symbol names without a dot are resolved to the first symbol /// whose base name matches the given name. /// - public MagicSymbol Resolve(string symbolName) + public MagicSymbol? Resolve(string symbolName) { if (symbolName == null || !symbolName.TrimStart().StartsWith("%")) return null; @@ -102,59 +102,65 @@ public MagicSymbol Resolve(string symbolName) return null; } - /// - /// Finds the MagicSymbols inside an assembly, and returns an instance of each. - /// + /// public IEnumerable FindMagic(AssemblyInfo assm) { var result = new MagicSymbol[0]; - if (cache.TryGetValue(assm, out result)) + lock (cache) { - return result; - } - - this.logger.LogInformation($"Looking for MagicSymbols in {assm.Assembly.FullName}"); + if (cache.TryGetValue(assm, out result)) + { + return result; + } - // If types from an assembly cannot be loaded, log a warning and continue. - var allMagic = new List(); - try - { - var magicTypes = assm.Assembly - .GetTypes() - .Where(t => - { - if (!t.IsClass && t.IsAbstract) { return false; } - var matched = t.IsSubclassOf(typeof(MagicSymbol)); - this.logger.LogDebug("Class {Class} subclass of MagicSymbol? {Matched}", t.FullName, matched); - return matched; - }); + this.logger.LogInformation($"Looking for MagicSymbols in {assm.Assembly.FullName}"); - foreach (var t in magicTypes) + // If types from an assembly cannot be loaded, log a warning and continue. + var allMagic = new List(); + try { - try + var magicTypes = assm.Assembly + .GetTypes() + .Where(t => + { + if (!t.IsClass && t.IsAbstract) { return false; } + var matched = t.IsSubclassOf(typeof(MagicSymbol)); + + // This logging statement is expensive, so we only run it when we need to for debugging. + #if DEBUG + this.logger.LogDebug("Class {Class} subclass of MagicSymbol? {Matched}", t.FullName, matched); + #endif + + return matched; + }); + + foreach (var t in magicTypes) { - var m = ActivatorUtilities.CreateInstance(services, t) as MagicSymbol; - allMagic.Add(m); - this.logger.LogInformation($"Found MagicSymbols {m.Name} ({t.FullName})"); - } - catch (Exception e) - { - this.logger.LogWarning($"Unable to create instance of MagicSymbol {t.FullName}. Magic will not be enabled.\nMessage:{e.Message}"); + try + { + var m = ActivatorUtilities.CreateInstance(services, t) as MagicSymbol; + allMagic.Add(m); + this.logger.LogInformation($"Found MagicSymbols {m.Name} ({t.FullName})"); + } + catch (Exception e) + { + this.logger.LogWarning($"Unable to create instance of MagicSymbol {t.FullName}. Magic will not be enabled.\nMessage:{e.Message}"); + } } } - } - catch (Exception ex) - { - this.logger.LogWarning( - ex, - "Encountered exception loading types from {AssemblyName}.", - assm.Assembly.FullName - ); - } + catch (Exception ex) + { + this.logger.LogWarning( + ex, + "Encountered exception loading types from {AssemblyName}.", + assm.Assembly.FullName + ); + } - result = allMagic.ToArray(); - cache[assm] = result; + result = allMagic.ToArray(); + cache[assm] = result; + } return result; } From 21f6118fa56925e6983185905e4dcfb84daaa51b Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 13 Jan 2021 18:44:16 -0800 Subject: [PATCH 02/15] Guard mundane execution on engine start. --- src/Kernel/IQSharpEngine.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 3f566171a7..27d777d4ea 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -44,6 +44,9 @@ public class IQSharpEngine : BaseEngine internal IWorkspace? Workspace { get; private set; } = null; + private TaskCompletionSource initializedSource = new TaskCompletionSource(); + private Task initialized => initializedSource.Task; + /// /// The main constructor. It expects an `ISnippets` instance that takes care /// of compiling and keeping track of the code Snippets provided by users. @@ -120,8 +123,12 @@ public override void Start() Process.GetCurrentProcess().Id ); - eventService?.TriggerServiceInitialized(this); + + var initializedSuccessfully = initializedSource.TrySetResult(true); + #if DEBUG + Debug.Assert(initializedSuccessfully, "Was unable to complete initialization task."); + #endif } /// @@ -217,9 +224,15 @@ public override async Task ExecuteMundane(string input, IChanne { try { - await Workspace.Initialization; + await this.initialized; + // Once the engine is initialized, we know that Workspace + // and Snippets are both not-null. + var workspace = this.Workspace!; + var snippets = this.Snippets!; + + await workspace.Initialization; - var code = Snippets.Compile(input); + var code = snippets.Compile(input); foreach (var m in code.warnings) { channel.Stdout(m); } From ef6dfd809cf5f4c94bca9b34eab121deedb4cc92 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 09:13:10 -0800 Subject: [PATCH 03/15] Wait for service initialization in tests. --- src/Kernel/IQSharpEngine.cs | 4 ++-- src/Tests/IQsharpEngineTests.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 27d777d4ea..92bd7b72d3 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -45,7 +45,7 @@ public class IQSharpEngine : BaseEngine internal IWorkspace? Workspace { get; private set; } = null; private TaskCompletionSource initializedSource = new TaskCompletionSource(); - private Task initialized => initializedSource.Task; + internal Task Initialized => initializedSource.Task; /// /// The main constructor. It expects an `ISnippets` instance that takes care @@ -224,7 +224,7 @@ public override async Task ExecuteMundane(string input, IChanne { try { - await this.initialized; + await this.Initialized; // Once the engine is initialized, we know that Workspace // and Snippets are both not-null. var workspace = this.Workspace!; diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 358b155cda..301d81f6d4 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -30,6 +30,8 @@ public class IQSharpEngineTests public IQSharpEngine Init(string workspace = "Workspace") { var engine = Startup.Create(workspace); + engine.Start(); + engine.Initialized.Wait(); engine.Workspace.Initialization.Wait(); return engine; } From 7f79ddf26683efa5db472120e573d6714d526748 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 10:15:31 -0800 Subject: [PATCH 04/15] Improve nullable reference type handling to avoid future test failures. --- src/Core/Loggers/NugetLogger.cs | 28 ++--- src/Core/References/NugetPackages.cs | 2 +- src/Kernel/CustomShell/ClientInfoHandler.cs | 20 ++-- src/Kernel/CustomShell/EchoHandler.cs | 2 +- src/Kernel/KernelApp/IQSharpKernelApp.cs | 4 +- src/Kernel/Magic/LsMagicMagic.cs | 12 +- src/Kernel/Magic/Resolution/MagicResolver.cs | 17 ++- src/Tests/IQsharpEngineTests.cs | 114 ++++++++++++------- src/Tests/NugetPackagesTests.cs | 2 +- src/Tests/SerializationTests.cs | 20 ++-- src/Tests/SnippetsControllerTests.cs | 2 +- src/Tests/Tests.IQsharp.csproj | 1 + src/Tests/WorkspaceTests.cs | 4 +- 13 files changed, 135 insertions(+), 93 deletions(-) diff --git a/src/Core/Loggers/NugetLogger.cs b/src/Core/Loggers/NugetLogger.cs index e8b4e3e0b9..20fb810ce6 100644 --- a/src/Core/Loggers/NugetLogger.cs +++ b/src/Core/Loggers/NugetLogger.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +#nullable enable using System; using System.Collections.Generic; @@ -14,31 +15,24 @@ namespace Microsoft.Quantum.IQSharp.Common /// public class NuGetLogger : NuGet.Common.LoggerBase { - private ILogger _logger { get; set; } + private ILogger? _logger { get; set; } public List Logs { get; private set; } - public NuGetLogger(ILogger logger) + public NuGetLogger(ILogger? logger) { _logger = logger; this.Logs = new List(); } - public static LogLevel MapLevel(NuGet.Common.LogLevel original) - { - switch (original) + public static LogLevel MapLevel(NuGet.Common.LogLevel original) => + original switch { - case NuGet.Common.LogLevel.Error: - return LogLevel.Error; - case NuGet.Common.LogLevel.Warning: - return LogLevel.Warning; - case NuGet.Common.LogLevel.Information: - return LogLevel.Information; - case NuGet.Common.LogLevel.Debug: - return LogLevel.Debug; - default: - return LogLevel.Trace; - } - } + NuGet.Common.LogLevel.Error => LogLevel.Error, + NuGet.Common.LogLevel.Warning => LogLevel.Warning, + NuGet.Common.LogLevel.Information => LogLevel.Information, + NuGet.Common.LogLevel.Debug => LogLevel.Debug, + _ => LogLevel.Trace + }; public override void Log(NuGet.Common.ILogMessage m) { diff --git a/src/Core/References/NugetPackages.cs b/src/Core/References/NugetPackages.cs index e766df6a1e..5621991c32 100644 --- a/src/Core/References/NugetPackages.cs +++ b/src/Core/References/NugetPackages.cs @@ -111,7 +111,7 @@ public IEnumerable Repositories public NugetPackages( IOptions config, - ILogger logger + ILogger? logger ) { this.Logger = new NuGetLogger(logger); diff --git a/src/Kernel/CustomShell/ClientInfoHandler.cs b/src/Kernel/CustomShell/ClientInfoHandler.cs index 7b5b4355e7..06b2842dbc 100644 --- a/src/Kernel/CustomShell/ClientInfoHandler.cs +++ b/src/Kernel/CustomShell/ClientInfoHandler.cs @@ -17,34 +17,34 @@ namespace Microsoft.Quantum.IQSharp.Kernel internal class ClientInfoContent : MessageContent { [JsonProperty("hosting_environment")] - public string HostingEnvironment { get; set; } + public string? HostingEnvironment { get; set; } [JsonProperty("iqsharp_version")] - public string IQSharpVersion { get; set; } + public string? IQSharpVersion { get; set; } [JsonProperty("user_agent")] - public string UserAgent { get; set; } + public string? UserAgent { get; set; } [JsonProperty("client_id")] - public string ClientId { get; set; } + public string? ClientId { get; set; } [JsonProperty("client_isnew")] public bool? ClientIsNew { get; set; } [JsonProperty("client_host")] - public string ClientHost { get; set; } + public string? ClientHost { get; set; } [JsonProperty("client_origin")] - public string ClientOrigin { get; set; } + public string? ClientOrigin { get; set; } [JsonProperty("client_first_origin")] - public string ClientFirstOrigin { get; set; } + public string? ClientFirstOrigin { get; set; } [JsonProperty("client_language")] - public string ClientLanguage { get; set; } + public string? ClientLanguage { get; set; } [JsonProperty("client_country")] - public string ClientCountry { get; set; } + public string? ClientCountry { get; set; } [JsonProperty("telemetry_opt_out")] public bool? TelemetryOptOut { get; set; } @@ -69,7 +69,7 @@ internal static ClientInfoContent AsClientInfoContent(this IMetadataController m /// internal class ClientInfoHandler : IShellHandler { - public string UserAgent { get; private set; } + public string? UserAgent { get; private set; } private readonly ILogger logger; private readonly IMetadataController metadata; private readonly IShellServer shellServer; diff --git a/src/Kernel/CustomShell/EchoHandler.cs b/src/Kernel/CustomShell/EchoHandler.cs index dc469b4138..06222aa138 100644 --- a/src/Kernel/CustomShell/EchoHandler.cs +++ b/src/Kernel/CustomShell/EchoHandler.cs @@ -44,7 +44,7 @@ IShellServer shellServer 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; + var value = (message.Content as UnknownContent)?.Data?["value"] as string ?? ""; // Send the echo both as an output and as a reply so that clients // can test both kinds of callbacks. await Task.Run(() => diff --git a/src/Kernel/KernelApp/IQSharpKernelApp.cs b/src/Kernel/KernelApp/IQSharpKernelApp.cs index b601366848..3df1806039 100644 --- a/src/Kernel/KernelApp/IQSharpKernelApp.cs +++ b/src/Kernel/KernelApp/IQSharpKernelApp.cs @@ -63,7 +63,7 @@ public static class KernelEventsExtensions /// /// The event service where the EventSubPub lives /// The typed EventPubSub for the KernelStarted event - public static EventPubSub OnKernelStarted(this IEventService eventService) + public static EventPubSub? OnKernelStarted(this IEventService eventService) { return eventService?.Events(); } @@ -73,7 +73,7 @@ public static EventPubSub OnKernelStarted( /// /// The event service where the EventSubPub lives /// The typed EventPubSub for the KernelStopped event - public static EventPubSub OnKernelStopped(this IEventService eventService) + public static EventPubSub? OnKernelStopped(this IEventService eventService) { return eventService?.Events(); } diff --git a/src/Kernel/Magic/LsMagicMagic.cs b/src/Kernel/Magic/LsMagicMagic.cs index 24004e80ed..04412f93cf 100644 --- a/src/Kernel/Magic/LsMagicMagic.cs +++ b/src/Kernel/Magic/LsMagicMagic.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Linq; using Microsoft.Jupyter.Core; @@ -54,8 +55,15 @@ as well as those defined in any packages that have been loaded in the current }) { this.resolver = resolver; - (engine as IQSharpEngine).RegisterDisplayEncoder(new MagicSymbolSummariesToHtmlEncoder()); - (engine as IQSharpEngine).RegisterDisplayEncoder(new MagicSymbolSummariesToTextEncoder()); + if (engine is IQSharpEngine iQSharpEngine) + { + iQSharpEngine.RegisterDisplayEncoder(new MagicSymbolSummariesToHtmlEncoder()); + iQSharpEngine.RegisterDisplayEncoder(new MagicSymbolSummariesToTextEncoder()); + } + else + { + throw new Exception($"Expected execution engine to be an IQ# engine, but was {engine.GetType()}."); + } } /// diff --git a/src/Kernel/Magic/Resolution/MagicResolver.cs b/src/Kernel/Magic/Resolution/MagicResolver.cs index 6b705f8586..a09eda8fa7 100644 --- a/src/Kernel/Magic/Resolution/MagicResolver.cs +++ b/src/Kernel/Magic/Resolution/MagicResolver.cs @@ -139,9 +139,20 @@ public IEnumerable FindMagic(AssemblyInfo assm) { try { - var m = ActivatorUtilities.CreateInstance(services, t) as MagicSymbol; - allMagic.Add(m); - this.logger.LogInformation($"Found MagicSymbols {m.Name} ({t.FullName})"); + var symbol = ActivatorUtilities.CreateInstance(services, t); + if (symbol is MagicSymbol magic) + { + allMagic.Add(magic); + this.logger.LogInformation($"Found MagicSymbols {magic.Name} ({t.FullName})"); + } + else if (symbol is {}) + { + throw new Exception($"Unable to create magic symbol of type {t}; service activator returned an object of type {symbol.GetType()}, which is not a subtype of MagicSymbol."); + } + else if (symbol == null) + { + throw new Exception($"Unable to create magic symbol of type {t}; service activator returned null."); + } } catch (Exception e) { diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 301d81f6d4..4d3566bced 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -32,7 +32,8 @@ public IQSharpEngine Init(string workspace = "Workspace") var engine = Startup.Create(workspace); engine.Start(); engine.Initialized.Wait(); - engine.Workspace.Initialization.Wait(); + Assert.IsNotNull(engine.Workspace); + engine.Workspace!.Initialization.Wait(); return engine; } @@ -48,7 +49,7 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) foreach (var m in channel.msgs) Console.WriteLine($" {m}"); } - public static async Task 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 = await engine.ExecuteMundane(source, channel); @@ -59,10 +60,12 @@ public static async Task AssertCompile(IQSharpEngine engine, string sour return response.Output?.ToString(); } - public static async Task AssertSimulate(IQSharpEngine engine, string snippetName, params string[] messages) + public static async Task AssertSimulate(IQSharpEngine engine, string snippetName, params string[] messages) { + await engine.Initialized; var configSource = new ConfigurationSource(skipLoading: true); - var simMagic = new SimulateMagic(engine.SymbolsResolver, configSource); + + var simMagic = new SimulateMagic(engine.SymbolsResolver!, configSource); var channel = new MockChannel(); var response = await simMagic.Execute(snippetName, channel); PrintResult(response, channel); @@ -72,17 +75,20 @@ public static async Task AssertSimulate(IQSharpEngine engine, string sni return response.Output?.ToString(); } - public static async Task AssertEstimate(IQSharpEngine engine, string snippetName, params string[] messages) + public static async Task AssertEstimate(IQSharpEngine engine, string snippetName, params string[] messages) { + await engine.Initialized; + Assert.IsNotNull(engine.SymbolsResolver); + var channel = new MockChannel(); - var estimateMagic = new EstimateMagic(engine.SymbolsResolver); + var estimateMagic = new EstimateMagic(engine.SymbolsResolver!); var response = await estimateMagic.Execute(snippetName, channel); var result = response.Output as DataTable; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.IsNotNull(result); - Assert.AreEqual(9, result.Rows.Count); - var keys = result.Rows.Cast().Select(row => row.ItemArray[0]).ToList(); + Assert.AreEqual(9, result?.Rows.Count); + var keys = result?.Rows.Cast().Select(row => row.ItemArray[0]).ToList(); CollectionAssert.Contains(keys, "T"); CollectionAssert.Contains(keys, "CNOT"); CollectionAssert.AreEqual(messages.Select(ChannelWithNewLines.Format).ToArray(), channel.msgs.ToArray()); @@ -94,11 +100,13 @@ private async Task AssertTrace(string name, ExecutionPath expectedPath, int expe { var engine = Init("Workspace.ExecutionPathTracer"); var snippets = engine.Snippets as Snippets; + Assert.IsNotNull(snippets); + Assert.IsNotNull(engine.SymbolsResolver); var configSource = new ConfigurationSource(skipLoading: true); - var wsMagic = new WorkspaceMagic(snippets.Workspace); + var wsMagic = new WorkspaceMagic(snippets!.Workspace); var pkgMagic = new PackageMagic(snippets.GlobalReferences); - var traceMagic = new TraceMagic(engine.SymbolsResolver, configSource); + var traceMagic = new TraceMagic(engine.SymbolsResolver!, configSource); var channel = new MockChannel(); @@ -123,11 +131,11 @@ private async Task AssertTrace(string name, ExecutionPath expectedPath, int expe var content = message.Content as ExecutionPathVisualizerContent; Assert.IsNotNull(content); - Assert.AreEqual(expectedDepth, content.RenderDepth); + Assert.AreEqual(expectedDepth, content?.RenderDepth); - var path = content.ExecutionPath.ToObject(); + var path = content?.ExecutionPath.ToObject(); Assert.IsNotNull(path); - Assert.AreEqual(expectedPath.ToJson(), path.ToJson()); + Assert.AreEqual(expectedPath.ToJson(), path!.ToJson()); } [TestMethod] @@ -141,8 +149,9 @@ public async Task CompileOne() public async Task CompileAndSimulate() { var engine = Init(); + Assert.IsNotNull(engine.SymbolsResolver); var configSource = new ConfigurationSource(skipLoading: true); - var simMagic = new SimulateMagic(engine.SymbolsResolver, configSource); + var simMagic = new SimulateMagic(engine.SymbolsResolver!, configSource); var channel = new MockChannel(); // Try running without compiling it, fails: @@ -230,12 +239,13 @@ public async Task Toffoli() { var engine = Init(); var channel = new MockChannel(); + Assert.IsNotNull(engine.SymbolsResolver); // Compile it: await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); // Run with toffoli simulator: - var toffoliMagic = new ToffoliMagic(engine.SymbolsResolver); + var toffoliMagic = new ToffoliMagic(engine.SymbolsResolver!); var response = await toffoliMagic.Execute("HelloQ", channel); var result = response.Output as Dictionary; PrintResult(response, channel); @@ -333,7 +343,9 @@ public async Task ReportWarnings() Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(3, channel.msgs.Count); Assert.AreEqual(0, channel.errors.Count); - Assert.AreEqual("ThreeWarnings", new ListToTextResultEncoder().Encode(response.Output).Value.Data); + Assert.AreEqual("ThreeWarnings", + new ListToTextResultEncoder().Encode(response.Output)?.Data + ); } { @@ -343,7 +355,9 @@ public async Task ReportWarnings() Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(1, channel.msgs.Count); Assert.AreEqual(0, channel.errors.Count); - Assert.AreEqual("OneWarning", new ListToTextResultEncoder().Encode(response.Output).Value.Data); + Assert.AreEqual("OneWarning", + new ListToTextResultEncoder().Encode(response.Output)?.Data + ); } } @@ -365,8 +379,9 @@ public async Task TestPackages() { var engine = Init(); var snippets = engine.Snippets as Snippets; + Assert.IsNotNull(snippets); - var pkgMagic = new PackageMagic(snippets.GlobalReferences); + var pkgMagic = new PackageMagic(snippets!.GlobalReferences); var references = ((References)pkgMagic.References); var packageCount = references.AutoLoadPackages.Count; var channel = new MockChannel(); @@ -375,9 +390,9 @@ public async Task TestPackages() PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(0, channel.msgs.Count); - Assert.AreEqual(packageCount, result.Length); - Assert.AreEqual("Microsoft.Quantum.Standard::0.0.0", result[0]); - Assert.AreEqual("Microsoft.Quantum.Standard.Visualization::0.0.0", result[1]); + Assert.AreEqual(packageCount, result?.Length); + Assert.AreEqual("Microsoft.Quantum.Standard::0.0.0", result?[0]); + Assert.AreEqual("Microsoft.Quantum.Standard.Visualization::0.0.0", result?[1]); // Try compiling TrotterEstimateEnergy, it should fail due to the lack // of chemistry package. @@ -391,7 +406,7 @@ public async Task TestPackages() Assert.AreEqual(ExecuteStatus.Ok, response.Status); Assert.AreEqual(0, channel.msgs.Count); Assert.IsNotNull(result); - Assert.AreEqual(packageCount + 1, result.Length); + Assert.AreEqual(packageCount + 1, result?.Length); // Now it should compile: await AssertCompile(engine, SNIPPETS.UseJordanWignerEncodingData, "UseJordanWignerEncodingData"); @@ -402,7 +417,8 @@ public async Task TestInvalidPackages() { var engine = Init(); var snippets = engine.Snippets as Snippets; - var pkgMagic = new PackageMagic(snippets.GlobalReferences); + Assert.IsNotNull(snippets); + var pkgMagic = new PackageMagic(snippets!.GlobalReferences); var channel = new MockChannel(); var response = await pkgMagic.Execute("microsoft.invalid.quantum", channel); @@ -419,13 +435,14 @@ public async Task TestProjectMagic() { var engine = Init(); var snippets = engine.Snippets as Snippets; - var projectMagic = new ProjectMagic(snippets.Workspace); + Assert.IsNotNull(snippets); + var projectMagic = new ProjectMagic(snippets!.Workspace); var channel = new MockChannel(); var response = await projectMagic.Execute("../Workspace.ProjectReferences/Workspace.ProjectReferences.csproj", channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); var loadedProjectFiles = response.Output as string[]; - Assert.AreEqual(3, loadedProjectFiles.Length); + Assert.AreEqual(3, loadedProjectFiles?.Length); } [TestMethod] @@ -443,9 +460,9 @@ public async Task TestWho() var result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - Assert.AreEqual(5, result.Length); - Assert.AreEqual("HelloQ", result[0]); - Assert.AreEqual("Tests.qss.NoOp", result[4]); + Assert.AreEqual(5, result?.Length); + Assert.AreEqual("HelloQ", result?[0]); + Assert.AreEqual("Tests.qss.NoOp", result?[4]); } [TestMethod] @@ -453,9 +470,10 @@ public async Task TestWorkspace() { var engine = Init("Workspace.Chemistry"); var snippets = engine.Snippets as Snippets; + Assert.IsNotNull(snippets); - var wsMagic = new WorkspaceMagic(snippets.Workspace); - var pkgMagic = new PackageMagic(snippets.GlobalReferences); + var wsMagic = new WorkspaceMagic(snippets!.Workspace); + var pkgMagic = new PackageMagic(snippets!.GlobalReferences); var channel = new MockChannel(); var result = new string[0]; @@ -492,7 +510,7 @@ public async Task TestWorkspace() result = response.Output as string[]; PrintResult(response, channel); Assert.AreEqual(ExecuteStatus.Ok, response.Status); - Assert.AreEqual(2, result.Length); + Assert.AreEqual(2, result?.Length); // Compilation must work: await AssertCompile(engine, SNIPPETS.DependsOnChemistryWorkspace, "DependsOnChemistryWorkspace"); @@ -520,26 +538,26 @@ public async Task TestResolver() // Intrinsics: var symbol = resolver.Resolve("X"); Assert.IsNotNull(symbol); - Assert.AreEqual("Microsoft.Quantum.Intrinsic.X", symbol.Name); + Assert.AreEqual("Microsoft.Quantum.Intrinsic.X", symbol!.Name); // FQN Intrinsics: symbol = resolver.Resolve("Microsoft.Quantum.Intrinsic.X"); Assert.IsNotNull(symbol); - Assert.AreEqual("Microsoft.Quantum.Intrinsic.X", symbol.Name); + Assert.AreEqual("Microsoft.Quantum.Intrinsic.X", symbol!.Name); // From namespace: symbol = resolver.Resolve("Tests.qss.CCNOTDriver"); Assert.IsNotNull(symbol); - Assert.AreEqual("Tests.qss.CCNOTDriver", symbol.Name); + Assert.AreEqual("Tests.qss.CCNOTDriver", symbol!.Name); symbol = resolver.Resolve("CCNOTDriver"); Assert.IsNotNull(symbol); - Assert.AreEqual("Tests.qss.CCNOTDriver", symbol.Name); + Assert.AreEqual("Tests.qss.CCNOTDriver", symbol!.Name); // Snippets: symbol = resolver.Resolve("HelloQ"); Assert.IsNotNull(symbol); - Assert.AreEqual("HelloQ", symbol.Name); + Assert.AreEqual("HelloQ", symbol!.Name); // resolver is case sensitive: symbol = resolver.Resolve("helloq"); @@ -555,13 +573,17 @@ public void TestResolveMagic() { var resolver = Startup.Create("Workspace.Broken"); + // We use the null-forgiving operator on symbol below, as the C# 8 + // nullable reference feature does not incorporate the result of + // Assert.IsNotNull. + var symbol = resolver.Resolve("%workspace"); Assert.IsNotNull(symbol); - Assert.AreEqual("%workspace", symbol.Name); + Assert.AreEqual("%workspace", symbol!.Name); symbol = resolver.Resolve("%package") as MagicSymbol; Assert.IsNotNull(symbol); - Assert.AreEqual("%package", symbol.Name); + Assert.AreEqual("%package", symbol!.Name); Assert.IsNotNull(resolver.Resolve("%who")); Assert.IsNotNull(resolver.Resolve("%estimate")); @@ -584,10 +606,11 @@ public void TestResolveMagic() public async Task TestDebugMagic() { var engine = Init(); + Assert.IsNotNull(engine.SymbolsResolver); await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); var configSource = new ConfigurationSource(skipLoading: true); - var debugMagic = new DebugMagic(engine.SymbolsResolver, configSource, engine.ShellRouter, engine.ShellServer, null); + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); // Start a debug session var channel = new MockChannel(); @@ -601,7 +624,7 @@ public async Task TestDebugMagic() var content = message.Content as DebugSessionContent; Assert.IsNotNull(content); - var debugSessionId = content.DebugSession; + var debugSessionId = content!.DebugSession; // Send several iqsharp_debug_advance messages var debugAdvanceMessage = new Message @@ -641,18 +664,23 @@ public async Task TestDebugMagic() // Verify debug status content var debugStatusContent = channel.iopubMessages[1].Content as DebugStatusContent; - Assert.IsNotNull(debugStatusContent.State); - Assert.AreEqual(debugSessionId, debugStatusContent.DebugSession); + Assert.IsNotNull(debugStatusContent?.State); + Assert.AreEqual(debugSessionId, debugStatusContent?.DebugSession); } [TestMethod] public async Task TestDebugMagicCancel() { var engine = Init(); + // Since Init guarantees that engine services have started, we + // assert non-nullness here. + Assert.IsNotNull(engine.SymbolsResolver); await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); var configSource = new ConfigurationSource(skipLoading: true); - var debugMagic = new DebugMagic(engine.SymbolsResolver, configSource, engine.ShellRouter, engine.ShellServer, null); + // We asserted that SymbolsResolver is not null above, and can use + // the null-forgiving operator here as a result. + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); // Start a debug session var channel = new MockChannel(); diff --git a/src/Tests/NugetPackagesTests.cs b/src/Tests/NugetPackagesTests.cs index e4adae231b..d1f708c864 100644 --- a/src/Tests/NugetPackagesTests.cs +++ b/src/Tests/NugetPackagesTests.cs @@ -36,7 +36,7 @@ public async Task GetLatestVersion() { var mgr = Init(); - async Task TestOne(string pkg, string version) + async Task TestOne(string pkg, string? version) { var actual = await mgr.GetLatestVersion(pkg); diff --git a/src/Tests/SerializationTests.cs b/src/Tests/SerializationTests.cs index 0a714710a9..03a37e36a0 100644 --- a/src/Tests/SerializationTests.cs +++ b/src/Tests/SerializationTests.cs @@ -36,9 +36,9 @@ public async Task SerializeFlatUdtInstance() { var complex = new Complex((12.0, 1.4)); var token = JToken.Parse(JsonConvert.SerializeObject(complex, JsonConverters.TupleConverters)); - Assert.AreEqual(typeof(Complex).FullName, token["@type"].Value()); - Assert.AreEqual(12, token["Item1"].Value()); - Assert.AreEqual(1.4, token["Item2"].Value()); + Assert.AreEqual(typeof(Complex).FullName, token?["@type"]?.Value()); + Assert.AreEqual(12, token?["Item1"]?.Value()); + Assert.AreEqual(1.4, token?["Item2"]?.Value()); } [TestMethod] @@ -62,13 +62,13 @@ public async Task SerializeNestedUdtInstance() var jsonData = JsonConvert.SerializeObject(testValue, JsonConverters.TupleConverters); System.Console.WriteLine(jsonData); var token = JToken.Parse(jsonData); - Assert.AreEqual(typeof(QubitState).FullName, token["@type"].Value()); - Assert.AreEqual(typeof(Complex).FullName, token["Item1"]["@type"].Value()); - Assert.AreEqual(0.1, token["Item1"]["Item1"].Value()); - Assert.AreEqual(0.2, token["Item1"]["Item2"].Value()); - Assert.AreEqual(typeof(Complex).FullName, token["Item2"]["@type"].Value()); - Assert.AreEqual(0.3, token["Item2"]["Item1"].Value()); - Assert.AreEqual(0.4, token["Item2"]["Item2"].Value()); + Assert.AreEqual(typeof(QubitState).FullName, token?["@type"]?.Value()); + Assert.AreEqual(typeof(Complex).FullName, token?["Item1"]?["@type"]?.Value()); + Assert.AreEqual(0.1, token?["Item1"]?["Item1"]?.Value()); + Assert.AreEqual(0.2, token?["Item1"]?["Item2"]?.Value()); + Assert.AreEqual(typeof(Complex).FullName, token?["Item2"]?["@type"]?.Value()); + Assert.AreEqual(0.3, token?["Item2"]?["Item1"]?.Value()); + Assert.AreEqual(0.4, token?["Item2"]?["Item2"]?.Value()); } [TestMethod] diff --git a/src/Tests/SnippetsControllerTests.cs b/src/Tests/SnippetsControllerTests.cs index cf4c88bc29..2126ff3f31 100644 --- a/src/Tests/SnippetsControllerTests.cs +++ b/src/Tests/SnippetsControllerTests.cs @@ -174,7 +174,7 @@ public async Task DependsOnWorkspace() // Run: var results = await AssertSimulate(controller, "DependsOnWorkspace", "Hello Foo again!") as QArray; Assert.IsNotNull(results); - Assert.AreEqual(5, results.Length); + Assert.AreEqual(5, results!.Length); } [TestMethod] diff --git a/src/Tests/Tests.IQsharp.csproj b/src/Tests/Tests.IQsharp.csproj index 4dbee92f77..507ec365cd 100644 --- a/src/Tests/Tests.IQsharp.csproj +++ b/src/Tests/Tests.IQsharp.csproj @@ -5,6 +5,7 @@ x64 false 1701 + enable diff --git a/src/Tests/WorkspaceTests.cs b/src/Tests/WorkspaceTests.cs index 9b4fe32996..43e956ff0c 100644 --- a/src/Tests/WorkspaceTests.cs +++ b/src/Tests/WorkspaceTests.cs @@ -59,10 +59,10 @@ public async Task ReloadWorkspace() var fileName = Path.Combine(Path.GetFullPath("Workspace"), "BasicOps.qs"); File.SetLastWriteTimeUtc(fileName, DateTime.UtcNow); ws.Reload(); - op = ws.AssemblyInfo.Operations.FirstOrDefault(o => o.FullName == "Tests.qss.NoOp"); + op = ws.AssemblyInfo?.Operations.FirstOrDefault(o => o.FullName == "Tests.qss.NoOp"); + Assert.IsNotNull(op); Assert.IsFalse(ws.HasErrors); Assert.AreNotSame(originalAssembly, ws.AssemblyInfo); - Assert.IsNotNull(op); } [TestMethod] From 83efd2698da381b3be030c9c933d5944ff832a1e Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 10:24:26 -0800 Subject: [PATCH 05/15] Last couple nullability improvements. --- src/Kernel/CustomShell/EchoHandler.cs | 2 +- src/Kernel/CustomShell/MessageExtensions.cs | 26 ++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Kernel/CustomShell/EchoHandler.cs b/src/Kernel/CustomShell/EchoHandler.cs index 06222aa138..3a109eccbb 100644 --- a/src/Kernel/CustomShell/EchoHandler.cs +++ b/src/Kernel/CustomShell/EchoHandler.cs @@ -13,7 +13,7 @@ namespace Microsoft.Quantum.IQSharp.Kernel internal class EchoReplyContent : MessageContent { [JsonProperty("value")] - public string Value { get; set; } + public string Value { get; set; } = ""; } /// diff --git a/src/Kernel/CustomShell/MessageExtensions.cs b/src/Kernel/CustomShell/MessageExtensions.cs index ce349ad0e7..22341f3c67 100644 --- a/src/Kernel/CustomShell/MessageExtensions.cs +++ b/src/Kernel/CustomShell/MessageExtensions.cs @@ -22,18 +22,28 @@ public static class MessageExtensions public static T To(this Message message) where T : MessageContent { - var content = (message.Content as UnknownContent); - var result = Activator.CreateInstance(); - foreach (var property in typeof(T).GetProperties().Where(p => p.CanWrite)) + if (message.Content is UnknownContent content) { - var jsonPropertyAttribute = property.GetCustomAttributes(true).OfType().FirstOrDefault(); - var propertyName = jsonPropertyAttribute?.PropertyName ?? property.Name; - if (content.Data.TryGetValue(propertyName, out var value)) + var result = Activator.CreateInstance(); + foreach (var property in typeof(T).GetProperties().Where(p => p.CanWrite)) { - property.SetValue(result, content.Data[propertyName]); + var jsonPropertyAttribute = property.GetCustomAttributes(true).OfType().FirstOrDefault(); + var propertyName = jsonPropertyAttribute?.PropertyName ?? property.Name; + if (content.Data.TryGetValue(propertyName, out var value)) + { + property.SetValue(result, content.Data[propertyName]); + } } + return result; + } + else if (message.Content is T decoded) + { + return decoded; + } + else + { + throw new Exception($"Attempted to convert a message with content type {message.Content.GetType()} to content type {typeof(T)}; can only convert from unknown content or subclasses of the desired content type."); } - return result; } } } From 15af94802c4c398a9a5e61c8617305718f710fae Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 11:23:08 -0800 Subject: [PATCH 06/15] Addressing feedback. --- src/Kernel/IQSharpEngine.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index 92bd7b72d3..a3f8ddb062 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -78,6 +78,10 @@ public override void Start() // and deserializers. Since that runs in the background, starting // the engine should not be blocked, and other services can // continue to initialize while the Q# compilation loader works. + // + // For more details, see: + // https://github.com/microsoft/qsharp-compiler/pull/727 + // https://github.com/microsoft/qsharp-compiler/pull/810 logger.LogDebug("Loading serialization and deserialziation protocols."); Protocols.Initialize(); From a7f4f800bbf9b934efd2bfdc24cbf99a36556681 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 15:17:37 -0800 Subject: [PATCH 07/15] Update to use released jupyter-core. --- 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 8069c89c43..28df313d76 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From 50e0a5660a7ee685cb4c5ae4e376326074eb06b3 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 17:38:43 -0800 Subject: [PATCH 08/15] Add more diagnostics to understand CI failure. --- build/test.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/test.ps1 b/build/test.ps1 index 89b3061ae4..d3144a25ca 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -43,6 +43,10 @@ function Test-One { --no-build ` --logger trx ` --filter $_ ` + <# Enable additional output from the test logger, but + without also turning on lots of additional noise from msbuild. + #> ` + --logger:"console;verbosity=detailed" ` /property:DefineConstants=$Env:ASSEMBLY_CONSTANTS ` /property:InformationalVersion=$Env:SEMVER_VERSION ` /property:Version=$Env:ASSEMBLY_VERSION From 38efcddec4e73268703640b6d3c776747de1f5a1 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 18:41:23 -0800 Subject: [PATCH 09/15] Fixed JupyterActions test. --- src/Tests/TelemetryTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Tests/TelemetryTests.cs b/src/Tests/TelemetryTests.cs index 770364fb3f..2c7c438823 100644 --- a/src/Tests/TelemetryTests.cs +++ b/src/Tests/TelemetryTests.cs @@ -39,6 +39,16 @@ public class TelemetryTests { public static readonly Type TelemetryServiceType = typeof(MockTelemetryService); + public IQSharpEngine Init(IServiceProvider services) + { + var engine = services.GetService() as IQSharpEngine; + engine.Start(); + engine.Initialized.Wait(); + Assert.IsNotNull(engine.Workspace); + engine.Workspace!.Initialization.Wait(); + return engine; + } + [TestMethod] public void MockTelemetryService() { @@ -249,7 +259,7 @@ public void JupyterActions() var services = Startup.CreateServiceProvider(workspace); var logger = GetAppLogger(services); - var engine = services.GetService() as IQSharpEngine; + var engine = Init(services); var channel = new MockChannel(); logger.Events.Clear(); From a87ba82790abcb800aea1a536b8f8a6612d1437b Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 19:42:49 -0800 Subject: [PATCH 10/15] Lock %debug magic in tests. --- src/Tests/IQsharpEngineTests.cs | 158 +++++++++++++++++--------------- 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 4d3566bced..9b0e654619 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -27,6 +27,10 @@ namespace Tests.IQSharp [TestClass] public class IQSharpEngineTests { + // We want to make sure that tests that exercise the %debug magic + // do not run at the same time, since they each check the list of IOPub + // messages going back and forth to the rest of the kernel. + private object debugTestLock = new object(); public IQSharpEngine Init(string workspace = "Workspace") { var engine = Startup.Create(workspace); @@ -605,99 +609,105 @@ public void TestResolveMagic() [TestMethod] public async Task TestDebugMagic() { - var engine = Init(); - Assert.IsNotNull(engine.SymbolsResolver); - await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); + lock (debugTestLock) + { + var engine = Init(); + Assert.IsNotNull(engine.SymbolsResolver); + await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); - var configSource = new ConfigurationSource(skipLoading: true); - var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); + var configSource = new ConfigurationSource(skipLoading: true); + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); - // Start a debug session - var channel = new MockChannel(); - var cts = new CancellationTokenSource(); - var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); + // Start a debug session + var channel = new MockChannel(); + var cts = new CancellationTokenSource(); + var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); - // Retrieve the debug session ID - var message = channel.iopubMessages[0]; - Assert.IsNotNull(message); - Assert.AreEqual("iqsharp_debug_sessionstart", message.Header.MessageType); + // Retrieve the debug session ID + var message = channel.iopubMessages[0]; + Assert.IsNotNull(message); + Assert.AreEqual("iqsharp_debug_sessionstart", message.Header.MessageType); - var content = message.Content as DebugSessionContent; - Assert.IsNotNull(content); - var debugSessionId = content!.DebugSession; + var content = message.Content as DebugSessionContent; + Assert.IsNotNull(content); + var debugSessionId = content!.DebugSession; - // Send several iqsharp_debug_advance messages - var debugAdvanceMessage = new Message - { - Header = new MessageHeader - { - MessageType = "iqsharp_debug_advance" - }, - Content = new UnknownContent + // Send several iqsharp_debug_advance messages + var debugAdvanceMessage = new Message { - Data = new Dictionary + Header = new MessageHeader { - ["debug_session"] = debugSessionId + MessageType = "iqsharp_debug_advance" + }, + Content = new UnknownContent + { + Data = new Dictionary + { + ["debug_session"] = debugSessionId + } } - } - }; + }; - foreach (int _ in Enumerable.Range(0, 1000)) - { - Thread.Sleep(millisecondsTimeout: 10); - if (debugTask.IsCompleted) - break; + foreach (int _ in Enumerable.Range(0, 1000)) + { + Thread.Sleep(millisecondsTimeout: 10); + if (debugTask.IsCompleted) + break; - await debugMagic.HandleAdvanceMessage(debugAdvanceMessage); - } + await debugMagic.HandleAdvanceMessage(debugAdvanceMessage); + } - // Verify that the command completes successfully - Assert.IsTrue(debugTask.IsCompleted); - Assert.AreEqual(System.Threading.Tasks.TaskStatus.RanToCompletion, debugTask.Status); - - // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); - Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); - Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); - - // Verify debug status content - var debugStatusContent = channel.iopubMessages[1].Content as DebugStatusContent; - Assert.IsNotNull(debugStatusContent?.State); - Assert.AreEqual(debugSessionId, debugStatusContent?.DebugSession); + // Verify that the command completes successfully + Assert.IsTrue(debugTask.IsCompleted); + Assert.AreEqual(System.Threading.Tasks.TaskStatus.RanToCompletion, debugTask.Status); + + // Ensure that expected messages were sent + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); + Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); + Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); + + // Verify debug status content + var debugStatusContent = channel.iopubMessages[1].Content as DebugStatusContent; + Assert.IsNotNull(debugStatusContent?.State); + Assert.AreEqual(debugSessionId, debugStatusContent?.DebugSession); + } } [TestMethod] public async Task TestDebugMagicCancel() { - var engine = Init(); - // Since Init guarantees that engine services have started, we - // assert non-nullness here. - Assert.IsNotNull(engine.SymbolsResolver); - await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); - - var configSource = new ConfigurationSource(skipLoading: true); - // We asserted that SymbolsResolver is not null above, and can use - // the null-forgiving operator here as a result. - var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); - - // Start a debug session - var channel = new MockChannel(); - var cts = new CancellationTokenSource(); - var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); + lock (debugTestLock) + { + var engine = Init(); + // Since Init guarantees that engine services have started, we + // assert non-nullness here. + Assert.IsNotNull(engine.SymbolsResolver); + await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); + + var configSource = new ConfigurationSource(skipLoading: true); + // We asserted that SymbolsResolver is not null above, and can use + // the null-forgiving operator here as a result. + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); + + // Start a debug session + var channel = new MockChannel(); + var cts = new CancellationTokenSource(); + var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); - // Cancel the session - cts.Cancel(); + // Cancel the session + cts.Cancel(); - // Ensure that the task throws an exception - Assert.ThrowsException(() => debugTask.Wait()); + // Ensure that the task throws an exception + Assert.ThrowsException(() => debugTask.Wait()); - // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); - Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); - Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); + // Ensure that expected messages were sent + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); + Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); + Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); + } } [TestMethod] From 046d516dffa5798167934ed32d2b812ee1ec2bd3 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Thu, 14 Jan 2021 20:26:11 -0800 Subject: [PATCH 11/15] Revert "Lock %debug magic in tests." This reverts commit a87ba82790abcb800aea1a536b8f8a6612d1437b. --- src/Tests/IQsharpEngineTests.cs | 158 +++++++++++++++----------------- 1 file changed, 74 insertions(+), 84 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 9b0e654619..4d3566bced 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -27,10 +27,6 @@ namespace Tests.IQSharp [TestClass] public class IQSharpEngineTests { - // We want to make sure that tests that exercise the %debug magic - // do not run at the same time, since they each check the list of IOPub - // messages going back and forth to the rest of the kernel. - private object debugTestLock = new object(); public IQSharpEngine Init(string workspace = "Workspace") { var engine = Startup.Create(workspace); @@ -609,105 +605,99 @@ public void TestResolveMagic() [TestMethod] public async Task TestDebugMagic() { - lock (debugTestLock) - { - var engine = Init(); - Assert.IsNotNull(engine.SymbolsResolver); - await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); + var engine = Init(); + Assert.IsNotNull(engine.SymbolsResolver); + await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); - var configSource = new ConfigurationSource(skipLoading: true); - var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); + var configSource = new ConfigurationSource(skipLoading: true); + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); - // Start a debug session - var channel = new MockChannel(); - var cts = new CancellationTokenSource(); - var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); + // Start a debug session + var channel = new MockChannel(); + var cts = new CancellationTokenSource(); + var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); - // Retrieve the debug session ID - var message = channel.iopubMessages[0]; - Assert.IsNotNull(message); - Assert.AreEqual("iqsharp_debug_sessionstart", message.Header.MessageType); + // Retrieve the debug session ID + var message = channel.iopubMessages[0]; + Assert.IsNotNull(message); + Assert.AreEqual("iqsharp_debug_sessionstart", message.Header.MessageType); - var content = message.Content as DebugSessionContent; - Assert.IsNotNull(content); - var debugSessionId = content!.DebugSession; + var content = message.Content as DebugSessionContent; + Assert.IsNotNull(content); + var debugSessionId = content!.DebugSession; - // Send several iqsharp_debug_advance messages - var debugAdvanceMessage = new Message + // Send several iqsharp_debug_advance messages + var debugAdvanceMessage = new Message + { + Header = new MessageHeader { - Header = new MessageHeader - { - MessageType = "iqsharp_debug_advance" - }, - Content = new UnknownContent + MessageType = "iqsharp_debug_advance" + }, + Content = new UnknownContent + { + Data = new Dictionary { - Data = new Dictionary - { - ["debug_session"] = debugSessionId - } + ["debug_session"] = debugSessionId } - }; - - foreach (int _ in Enumerable.Range(0, 1000)) - { - Thread.Sleep(millisecondsTimeout: 10); - if (debugTask.IsCompleted) - break; - - await debugMagic.HandleAdvanceMessage(debugAdvanceMessage); } + }; + + foreach (int _ in Enumerable.Range(0, 1000)) + { + Thread.Sleep(millisecondsTimeout: 10); + if (debugTask.IsCompleted) + break; - // Verify that the command completes successfully - Assert.IsTrue(debugTask.IsCompleted); - Assert.AreEqual(System.Threading.Tasks.TaskStatus.RanToCompletion, debugTask.Status); - - // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); - Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); - Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); - - // Verify debug status content - var debugStatusContent = channel.iopubMessages[1].Content as DebugStatusContent; - Assert.IsNotNull(debugStatusContent?.State); - Assert.AreEqual(debugSessionId, debugStatusContent?.DebugSession); + await debugMagic.HandleAdvanceMessage(debugAdvanceMessage); } + + // Verify that the command completes successfully + Assert.IsTrue(debugTask.IsCompleted); + Assert.AreEqual(System.Threading.Tasks.TaskStatus.RanToCompletion, debugTask.Status); + + // Ensure that expected messages were sent + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); + Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); + Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); + + // Verify debug status content + var debugStatusContent = channel.iopubMessages[1].Content as DebugStatusContent; + Assert.IsNotNull(debugStatusContent?.State); + Assert.AreEqual(debugSessionId, debugStatusContent?.DebugSession); } [TestMethod] public async Task TestDebugMagicCancel() { - lock (debugTestLock) - { - var engine = Init(); - // Since Init guarantees that engine services have started, we - // assert non-nullness here. - Assert.IsNotNull(engine.SymbolsResolver); - await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); - - var configSource = new ConfigurationSource(skipLoading: true); - // We asserted that SymbolsResolver is not null above, and can use - // the null-forgiving operator here as a result. - var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); - - // Start a debug session - var channel = new MockChannel(); - var cts = new CancellationTokenSource(); - var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); + var engine = Init(); + // Since Init guarantees that engine services have started, we + // assert non-nullness here. + Assert.IsNotNull(engine.SymbolsResolver); + await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); + + var configSource = new ConfigurationSource(skipLoading: true); + // We asserted that SymbolsResolver is not null above, and can use + // the null-forgiving operator here as a result. + var debugMagic = new DebugMagic(engine.SymbolsResolver!, configSource, engine.ShellRouter, engine.ShellServer, null); - // Cancel the session - cts.Cancel(); + // Start a debug session + var channel = new MockChannel(); + var cts = new CancellationTokenSource(); + var debugTask = debugMagic.RunAsync("SimpleDebugOperation", channel, cts.Token); - // Ensure that the task throws an exception - Assert.ThrowsException(() => debugTask.Wait()); + // Cancel the session + cts.Cancel(); - // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); - Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); - Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); - } + // Ensure that the task throws an exception + Assert.ThrowsException(() => debugTask.Wait()); + + // Ensure that expected messages were sent + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); + Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); + Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); } [TestMethod] From 566b5b93512de9f60f6db01404b30fcd9a108d21 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 15 Jan 2021 09:56:55 -0800 Subject: [PATCH 12/15] Update to CI version of Jupyter Core. --- src/Jupyter/Jupyter.csproj | 2 +- src/Kernel/IQSharpEngine.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Jupyter/Jupyter.csproj b/src/Jupyter/Jupyter.csproj index 28df313d76..2b702dc1ee 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Kernel/IQSharpEngine.cs b/src/Kernel/IQSharpEngine.cs index a3f8ddb062..d229ea53aa 100644 --- a/src/Kernel/IQSharpEngine.cs +++ b/src/Kernel/IQSharpEngine.cs @@ -45,7 +45,9 @@ public class IQSharpEngine : BaseEngine internal IWorkspace? Workspace { get; private set; } = null; private TaskCompletionSource initializedSource = new TaskCompletionSource(); - internal Task Initialized => initializedSource.Task; + + /// + public override Task Initialized => initializedSource.Task; /// /// The main constructor. It expects an `ISnippets` instance that takes care From 57194f302111528715c4ece2b50a9d0dc0b0dfa0 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 15 Jan 2021 11:14:34 -0800 Subject: [PATCH 13/15] Bump jupyter-core. --- 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 2b702dc1ee..bd14bc6a3f 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From 5e82b1a61a5d573eaf29f0f224ac1f57f79b7a54 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 15 Jan 2021 14:12:19 -0800 Subject: [PATCH 14/15] Bump to newest jupyter-core. --- 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 bd14bc6a3f..fe4234dc56 100644 --- a/src/Jupyter/Jupyter.csproj +++ b/src/Jupyter/Jupyter.csproj @@ -35,7 +35,7 @@ - + From d62d82d3780ad853002cdd8d5ff837e851d06f86 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Fri, 15 Jan 2021 15:51:41 -0800 Subject: [PATCH 15/15] More diagnostics in %debug. --- src/Tests/IQsharpEngineTests.cs | 90 ++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs index 4d3566bced..b7b58f4b1c 100644 --- a/src/Tests/IQsharpEngineTests.cs +++ b/src/Tests/IQsharpEngineTests.cs @@ -27,13 +27,13 @@ namespace Tests.IQSharp [TestClass] public class IQSharpEngineTests { - public IQSharpEngine Init(string workspace = "Workspace") + public async Task Init(string workspace = "Workspace") { var engine = Startup.Create(workspace); engine.Start(); - engine.Initialized.Wait(); + await engine.Initialized; Assert.IsNotNull(engine.Workspace); - engine.Workspace!.Initialization.Wait(); + await engine.Workspace!.Initialization; return engine; } @@ -49,6 +49,16 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) foreach (var m in channel.msgs) Console.WriteLine($" {m}"); } + public static string SessionAsString(IEnumerable session) => + string.Join("\n", + session.Select(message => + $"\tHeader: {JsonConvert.SerializeObject(message.Header)}\n" + + $"\tParent header: {JsonConvert.SerializeObject(message.ParentHeader)}\n" + + $"\tMetadata: {JsonConvert.SerializeObject(message.Metadata)}\n" + + $"\tContent: {JsonConvert.SerializeObject(message.Content)}\n\n" + ) + ); + public static async Task AssertCompile(IQSharpEngine engine, string source, params string[] expectedOps) { var channel = new MockChannel(); @@ -98,7 +108,7 @@ public static void PrintResult(ExecutionResult result, MockChannel channel) private async Task AssertTrace(string name, ExecutionPath expectedPath, int expectedDepth) { - var engine = Init("Workspace.ExecutionPathTracer"); + var engine = await Init("Workspace.ExecutionPathTracer"); var snippets = engine.Snippets as Snippets; Assert.IsNotNull(snippets); Assert.IsNotNull(engine.SymbolsResolver); @@ -141,14 +151,14 @@ private async Task AssertTrace(string name, ExecutionPath expectedPath, int expe [TestMethod] public async Task CompileOne() { - var engine = Init(); + var engine = await Init(); await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); } [TestMethod] public async Task CompileAndSimulate() { - var engine = Init(); + var engine = await Init(); Assert.IsNotNull(engine.SymbolsResolver); var configSource = new ConfigurationSource(skipLoading: true); var simMagic = new SimulateMagic(engine.SymbolsResolver!, configSource); @@ -172,7 +182,7 @@ public async Task CompileAndSimulate() [TestMethod] public async Task SimulateWithArguments() { - var engine = Init(); + var engine = await Init(); // Compile it: await AssertCompile(engine, SNIPPETS.Reverse, "Reverse"); @@ -185,7 +195,7 @@ public async Task SimulateWithArguments() [TestMethod] public async Task OpenNamespaces() { - var engine = Init(); + var engine = await Init(); // Compile: await AssertCompile(engine, SNIPPETS.OpenNamespaces1); @@ -199,7 +209,7 @@ public async Task OpenNamespaces() [TestMethod] public async Task OpenAliasedNamespaces() { - var engine = Init(); + var engine = await Init(); // Compile: await AssertCompile(engine, SNIPPETS.OpenAliasedNamespace); @@ -212,7 +222,7 @@ public async Task OpenAliasedNamespaces() [TestMethod] public async Task CompileApplyWithin() { - var engine = Init(); + var engine = await Init(); // Compile: await AssertCompile(engine, SNIPPETS.ApplyWithinBlock, "ApplyWithinBlock"); @@ -224,7 +234,7 @@ public async Task CompileApplyWithin() [TestMethod] public async Task Estimate() { - var engine = Init(); + var engine = await Init(); var channel = new MockChannel(); // Compile it: @@ -237,7 +247,7 @@ public async Task Estimate() [TestMethod] public async Task Toffoli() { - var engine = Init(); + var engine = await Init(); var channel = new MockChannel(); Assert.IsNotNull(engine.SymbolsResolver); @@ -257,7 +267,7 @@ public async Task Toffoli() [TestMethod] public async Task DependsOnWorkspace() { - var engine = Init(); + var engine = await Init(); // Compile it: await AssertCompile(engine, SNIPPETS.DependsOnWorkspace, "DependsOnWorkspace"); @@ -270,7 +280,7 @@ public async Task DependsOnWorkspace() [TestMethod] public async Task UpdateSnippet() { - var engine = Init(); + var engine = await Init(); // Compile it: await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); @@ -288,7 +298,7 @@ public async Task UpdateSnippet() [TestMethod] public async Task DumpToFile() { - var engine = Init(); + var engine = await Init(); // Compile DumpMachine snippet. await AssertCompile(engine, SNIPPETS.DumpToFile, "DumpToFile"); @@ -316,7 +326,7 @@ public async Task DumpToFile() [TestMethod] public async Task UpdateDependency() { - var engine = Init(); + var engine = await Init(); // Compile HelloQ await AssertCompile(engine, SNIPPETS.HelloQ, "HelloQ"); @@ -334,7 +344,7 @@ public async Task UpdateDependency() [TestMethod] public async Task ReportWarnings() { - var engine = Init(); + var engine = await Init(); { var channel = new MockChannel(); @@ -364,7 +374,7 @@ public async Task ReportWarnings() [TestMethod] public async Task ReportErrors() { - var engine = Init(); + var engine = await Init(); var channel = new MockChannel(); var response = await engine.ExecuteMundane(SNIPPETS.TwoErrors, channel); @@ -377,7 +387,7 @@ public async Task ReportErrors() [TestMethod] public async Task TestPackages() { - var engine = Init(); + var engine = await Init(); var snippets = engine.Snippets as Snippets; Assert.IsNotNull(snippets); @@ -415,7 +425,7 @@ public async Task TestPackages() [TestMethod] public async Task TestInvalidPackages() { - var engine = Init(); + var engine = await Init(); var snippets = engine.Snippets as Snippets; Assert.IsNotNull(snippets); var pkgMagic = new PackageMagic(snippets!.GlobalReferences); @@ -433,7 +443,7 @@ public async Task TestInvalidPackages() [TestMethod] public async Task TestProjectMagic() { - var engine = Init(); + var engine = await Init(); var snippets = engine.Snippets as Snippets; Assert.IsNotNull(snippets); var projectMagic = new ProjectMagic(snippets!.Workspace); @@ -468,7 +478,7 @@ public async Task TestWho() [TestMethod] public async Task TestWorkspace() { - var engine = Init("Workspace.Chemistry"); + var engine = await Init("Workspace.Chemistry"); var snippets = engine.Snippets as Snippets; Assert.IsNotNull(snippets); @@ -605,7 +615,7 @@ public void TestResolveMagic() [TestMethod] public async Task TestDebugMagic() { - var engine = Init(); + var engine = await Init(); Assert.IsNotNull(engine.SymbolsResolver); await AssertCompile(engine, SNIPPETS.SimpleDebugOperation, "SimpleDebugOperation"); @@ -656,9 +666,20 @@ public async Task TestDebugMagic() Assert.AreEqual(System.Threading.Tasks.TaskStatus.RanToCompletion, debugTask.Status); // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); + try + { + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_opstart", channel.iopubMessages[1].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages.Last().Header.MessageType); + } + catch (AssertFailedException ex) + { + await Console.Error.WriteLineAsync( + "IOPub messages sent by %debug were incorrect.\nReceived messages:\n" + + SessionAsString(channel.iopubMessages) + ); + throw ex; + } Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); @@ -671,7 +692,7 @@ public async Task TestDebugMagic() [TestMethod] public async Task TestDebugMagicCancel() { - var engine = Init(); + var engine = await Init(); // Since Init guarantees that engine services have started, we // assert non-nullness here. Assert.IsNotNull(engine.SymbolsResolver); @@ -694,8 +715,19 @@ public async Task TestDebugMagicCancel() Assert.ThrowsException(() => debugTask.Wait()); // Ensure that expected messages were sent - Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); - Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); + try + { + Assert.AreEqual("iqsharp_debug_sessionstart", channel.iopubMessages[0].Header.MessageType); + Assert.AreEqual("iqsharp_debug_sessionend", channel.iopubMessages[1].Header.MessageType); + } + catch (AssertFailedException ex) + { + await Console.Error.WriteLineAsync( + "IOPub messages sent by %debug were incorrect.\nReceived messages:\n" + + SessionAsString(channel.iopubMessages) + ); + throw ex; + } Assert.IsTrue(channel.msgs[0].Contains("Starting debug session")); Assert.IsTrue(channel.msgs[1].Contains("Finished debug session")); }