diff --git a/Simulation.sln b/Simulation.sln index 1250f77e65b..2242d066ddd 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -49,7 +49,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QsharpExe", "src\Simulation EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntryPointDriver", "src\Simulation\EntryPointDriver\EntryPointDriver.csproj", "{944FE7EF-9220-4CC6-BB20-CE517195B922}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "EntryPointDriver.Tests", "src\Simulation\EntryPointDriver.Tests\EntryPointDriver.Tests.fsproj", "{E2F30496-19D8-46A8-9BC0-26936FFE70D2}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests.EntryPointDriver", "src\Simulation\EntryPointDriver.Tests\Tests.EntryPointDriver.fsproj", "{E2F30496-19D8-46A8-9BC0-26936FFE70D2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/build/test.ps1 b/build/test.ps1 index 95133997b06..4a879f1fb13 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -34,6 +34,8 @@ function Test-One { Test-One '../src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj' +Test-One '../src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj' + Test-One '../src/Simulation/RoslynWrapper.Tests/Tests.RoslynWrapper.fsproj' Test-One '../src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj' diff --git a/src/Simulation/EntryPointDriver.Tests/EntryPointDriver.Tests.fsproj b/src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj similarity index 92% rename from src/Simulation/EntryPointDriver.Tests/EntryPointDriver.Tests.fsproj rename to src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj index db1f59c022f..24ea7c82cda 100644 --- a/src/Simulation/EntryPointDriver.Tests/EntryPointDriver.Tests.fsproj +++ b/src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj @@ -4,6 +4,7 @@ netcoreapp3.1 false false + Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs index 16e38992919..6df1ce5472d 100644 --- a/src/Simulation/EntryPointDriver.Tests/Tests.fs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs @@ -142,19 +142,28 @@ let private run (assembly : Assembly) (args : string[]) = Console.SetOut previousOut CultureInfo.DefaultThreadCurrentCulture <- previousCulture +/// Replaces every sequence of whitespace characters in the string with a single space. +let private normalize s = Regex.Replace(s, @"\s+", " ").Trim() + /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected -/// output. +/// output. The standard error and out streams of the actual output are concatenated in that order. let private yields expected (assembly, args) = - let normalize text = Regex.Replace(text, @"\s+", " ").Trim() let out, error, exitCode = run assembly args Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s\n\n%s" exitCode out error) - Assert.Equal (normalize expected, normalize out) + Assert.Equal (normalize expected, normalize (error + out)) /// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = let out, error, exitCode = run assembly args Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) +/// Asserts that running the entry point in the assembly with the given arguments fails and the error message starts +/// with the expected message. +let private failsWith expected (assembly, args) = + let out, error, exitCode = run assembly args + Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) + Assert.StartsWith (normalize expected, normalize error) + /// A tuple of the test assembly and arguments using the standard default simulator. The tuple can be passed to yields /// or fails. let private test testNum = @@ -406,8 +415,13 @@ let ``Uses single-dash short names`` () = [] let ``Shadows --simulator`` () = let given = test 27 - given ["--simulator"; "foo"] |> yields "foo" - given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator + given ["--simulator"; "foo"] + |> yields "Warning: Option --simulator is overridden by an entry point parameter name. Using default value QuantumSimulator. + foo" + given ["--simulator"; AssemblyConstants.ResourcesEstimator] + |> yields (sprintf "Warning: Option --simulator is overridden by an entry point parameter name. Using default value QuantumSimulator. + %s" + AssemblyConstants.ResourcesEstimator) given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails given ["-s"; "foo"] |> fails @@ -427,15 +441,16 @@ let ``Shadows --version`` () = // Simulators // The expected output from the resources estimator. -let private resourceSummary = "Metric Sum -CNOT 0 -QubitClifford 1 -R 0 -Measure 1 -T 0 -Depth 0 -Width 1 -BorrowedWidth 0" +let private resourceSummary = + "Metric Sum + CNOT 0 + QubitClifford 1 + R 0 + Measure 1 + T 0 + Depth 0 + Width 1 + BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = @@ -481,7 +496,86 @@ let ``Supports default custom simulator`` () = given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> fails -// TODO: Add tests for the "submit" command. +// Azure Quantum Submission + +/// Standard command-line arguments for the "submit" command without specifying a target. +let private submitWithoutTarget = + [ "submit" + "--storage" + "myStorage" + "--subscription" + "mySubscription" + "--resource-group" + "myResourceGroup" + "--workspace" + "myWorkspace" ] + +/// Standard command-line arguments for the "submit" command using the "nothing" target. +let private submitWithTestTarget = submitWithoutTarget @ ["--target"; "nothing"] + +[] +let ``Submit can submit a job`` () = + let given = test 1 + given submitWithTestTarget + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit can show only the ID`` () = + let given = test 1 + given (submitWithTestTarget @ ["--output"; "id"]) |> yields "00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit uses default values`` () = + let given = test 1 + given (submitWithTestTarget @ ["--verbose"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + Target: nothing + Storage: myStorage + Subscription: mySubscription + Resource Group: myResourceGroup + Workspace: myWorkspace + AAD Token: + Base URI: + Shots: 500 + Output: FriendlyUri + Dry Run: False + Verbose: True + + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit allows overriding default values`` () = + let given = test 1 + given (submitWithTestTarget @ ["--verbose"; "--aad-token"; "myToken"; "--base-uri"; "myBaseUri"; "--shots"; "750"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + Target: nothing + Storage: myStorage + Subscription: mySubscription + Resource Group: myResourceGroup + Workspace: myWorkspace + AAD Token: myToken + Base URI: myBaseUri + Shots: 750 + Output: FriendlyUri + Dry Run: False + Verbose: True + + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit requires a positive number of shots`` () = + let given = test 1 + given (submitWithTestTarget @ ["--shots"; "1"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + 00000000-0000-0000-0000-0000000000000" + given (submitWithTestTarget @ ["--shots"; "0"]) |> fails + given (submitWithTestTarget @ ["--shots"; "-1"]) |> fails + +[] +let ``Submit fails with unknown target`` () = + let given = test 1 + given (submitWithoutTarget @ ["--target"; "foo"]) |> failsWith "The target 'foo' was not recognized." // Help @@ -489,24 +583,54 @@ let ``Supports default custom simulator`` () = [] let ``Uses documentation`` () = let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) - let message = (name, name) ||> sprintf "%s: - This test checks that the entry point documentation appears correctly in the command line help message. + let message = + (name, name) + ||> sprintf "%s: + This test checks that the entry point documentation appears correctly in the command line help message. -Usage: - %s [options] [command] + Usage: + %s [options] [command] -Options: - -n (REQUIRED) A number. - --pauli (REQUIRED) The name of a Pauli matrix. - --my-cool-bool (REQUIRED) A neat bit. - -s, --simulator The name of the simulator to use. - --version Show version information - -?, -h, --help Show help and usage information + Options: + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. + -s, --simulator The name of the simulator to use. + --version Show version information + -?, -h, --help Show help and usage information -Commands: - simulate (default) Run the program using a local simulator." + Commands: + simulate (default) Run the program using a local simulator." let given = test 31 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message + +[] +let ``Shows help text for submit command`` () = + let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) + let message = + name + |> sprintf "Usage: + %s submit [options] + + Options: + --target (REQUIRED) The target device ID. + --storage (REQUIRED) The storage account connection string. + --subscription (REQUIRED) The subscription ID. + --resource-group (REQUIRED) The resource group name. + --workspace (REQUIRED) The workspace name. + --aad-token The Azure Active Directory authentication token. + --base-uri The base URI of the Azure Quantum endpoint. + --output The information to show in the output after the job is submitted. + --shots The number of times the program is executed on the target machine. + --dry-run Validate the program and options, but do not submit to Azure Quantum. + --verbose Show additional information about the submission. + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. + -?, -h, --help Show help and usage information" + + let given = test 31 + given ["submit"; "--help"] |> yields message diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs index e5d2604c21e..6f6f14d0335 100644 --- a/src/Simulation/EntryPointDriver/Azure.cs +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -26,6 +26,12 @@ internal static class Azure internal static async Task Submit( IEntryPoint entryPoint, ParseResult parseResult, AzureSettings settings) { + if (settings.Verbose) + { + Console.WriteLine(settings); + Console.WriteLine(); + } + var machine = CreateMachine(settings); if (machine is null) { @@ -109,13 +115,13 @@ internal enum OutputFormat /// Show a friendly message with a URI that can be used to see the job results. /// FriendlyUri, - + /// /// Show only the job ID. /// Id } - + /// /// Settings for a submission to Azure Quantum. /// @@ -125,37 +131,37 @@ internal sealed class AzureSettings /// The target device ID. /// public string? Target { get; set; } - + /// /// The storage account connection string. /// public string? Storage { get; set; } - + /// /// The subscription ID. /// public string? Subscription { get; set; } - + /// /// The resource group name. /// public string? ResourceGroup { get; set; } - + /// /// The workspace name. /// public string? Workspace { get; set; } - + /// /// The Azure Active Directory authentication token. /// public string? AadToken { get; set; } - + /// /// The base URI of the Azure Quantum endpoint. /// public Uri? BaseUri { get; set; } - + /// /// The number of times the program is executed on the target machine. /// @@ -171,6 +177,11 @@ internal sealed class AzureSettings /// public bool DryRun { get; set; } + /// + /// Show additional information about the submission. + /// + public bool Verbose { get; set; } + /// /// Creates a based on the settings. /// @@ -179,5 +190,19 @@ internal Workspace CreateWorkspace() => AadToken is null ? new Workspace(Subscription, ResourceGroup, Workspace, baseUri: BaseUri) : new Workspace(Subscription, ResourceGroup, Workspace, AadToken, BaseUri); + + public override string ToString() => + string.Join(System.Environment.NewLine, + $"Target: {Target}", + $"Storage: {Storage}", + $"Subscription: {Subscription}", + $"Resource Group: {ResourceGroup}", + $"Workspace: {Workspace}", + $"AAD Token: {AadToken}", + $"Base URI: {BaseUri}", + $"Shots: {Shots}", + $"Output: {Output}", + $"Dry Run: {DryRun}", + $"Verbose: {Verbose}"); } } diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs index 38b68ebe94e..9bca59fe327 100644 --- a/src/Simulation/EntryPointDriver/Driver.cs +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -87,6 +87,7 @@ public async Task Run(string[] args) AddOptionIfAvailable(submit, OutputOption); AddOptionIfAvailable(submit, ShotsOption); AddOptionIfAvailable(submit, DryRunOption); + AddOptionIfAvailable(submit, VerboseOption); var root = new RootCommand(entryPoint.Summary) { simulate, submit }; foreach (var option in entryPoint.Options) @@ -136,7 +137,8 @@ private async Task Submit(ParseResult parseResult, AzureSettings settings) BaseUri = DefaultIfShadowed(BaseUriOption, settings.BaseUri), Shots = DefaultIfShadowed(ShotsOption, settings.Shots), Output = DefaultIfShadowed(OutputOption, settings.Output), - DryRun = DefaultIfShadowed(DryRunOption, settings.DryRun) + DryRun = DefaultIfShadowed(DryRunOption, settings.DryRun), + Verbose = DefaultIfShadowed(VerboseOption, settings.Verbose) }); /// @@ -268,6 +270,12 @@ internal static class Driver false, "Validate the program and options, but do not submit to Azure Quantum."); + /// + /// The verbose option. + /// + internal static readonly OptionInfo VerboseOption = new OptionInfo( + new[] { "--verbose" }, false, "Show additional information about the submission."); + /// /// Displays a message to the console using the given color and text writer. ///