From fd91fd4a90649608ce3d212fbc29b139200de73a Mon Sep 17 00:00:00 2001 From: Giacomo Castellani Date: Fri, 7 Oct 2022 16:23:32 +0000 Subject: [PATCH 1/4] switched cmdline management to spectre.console --- src/qest/Commands/GenerateCommand.cs | 80 +++++++++--------- src/qest/Commands/Options.cs | 71 ---------------- src/qest/Commands/RunCommand.cs | 118 +++++++++++++++++---------- src/qest/Commands/Validators.cs | 31 +++++++ src/qest/Program.cs | 70 +++++----------- src/qest/Utils/Utils.cs | 57 +++++++++++++ src/qest/qest.csproj | 6 +- 7 files changed, 226 insertions(+), 207 deletions(-) delete mode 100644 src/qest/Commands/Options.cs create mode 100644 src/qest/Commands/Validators.cs diff --git a/src/qest/Commands/GenerateCommand.cs b/src/qest/Commands/GenerateCommand.cs index ed2bf5d..1a6d57e 100644 --- a/src/qest/Commands/GenerateCommand.cs +++ b/src/qest/Commands/GenerateCommand.cs @@ -1,16 +1,42 @@ using System; +using System.ComponentModel; using System.Data.SqlClient; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; using qest.Models; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; +using Spectre.Console; +using Spectre.Console.Cli; namespace qest.Commands { - internal static class Generate + + internal sealed class GenerateCommand : AsyncCommand { + public sealed class Settings : CommandSettings + { + [Description("Folder containing test files")] + [CommandOption("-d|--folder ")] + [DefaultValue("templates")] + public string Folder { get; init; } + + [Description("Target connection string")] + [CommandOption("-c|--tcs ")] + public string ConnectionString { get; init; } + + public override ValidationResult Validate() + { + DirectoryInfo folderToLoad = new DirectoryInfo(Folder); + if (!folderToLoad.Exists) + folderToLoad.Create(); + + var result = Validators.ValidateConnectionString(ConnectionString); + + return result; + } + } + internal const string parametersQuery = @" SELECT SCHEMA_NAME(SCHEMA_ID) AS [Schema], @@ -30,12 +56,10 @@ ORDER BY p.parameter_id "; - internal const string yamlSchema = - @"# yaml-language-server: $schema=https://raw.githubusercontent.com/Geims83/qest/0.9.2/docs/yamlSchema.json"; - - internal static async Task Execute(DirectoryInfo? folder, string tcs) + public override async Task ExecuteAsync([NotNull] CommandContext context, [NotNull] Settings settings) { - var sqlConnection = new SqlConnection(tcs); + var sqlConnection = new SqlConnection(settings.ConnectionString); + DirectoryInfo folder = new DirectoryInfo(settings.Folder); try { @@ -58,7 +82,7 @@ internal static async Task Execute(DirectoryInfo? folder, string tcs) if (currentSchema != rowSchema || currentSp != rowSp) { - await SafeWriteYamlAsync(folder!, currentTest); + await Utils.SafeWriteYamlAsync(folder!, currentTest); currentSchema = rowSchema; currentSp = rowSp; @@ -88,20 +112,20 @@ internal static async Task Execute(DirectoryInfo? folder, string tcs) } } - await SafeWriteYamlAsync(folder!, currentTest); + await Utils.SafeWriteYamlAsync(folder!, currentTest); } catch (Exception ex) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(ex.ToString()); - Environment.Exit(1); - Console.ResetColor(); + AnsiConsole.WriteException(ex); + return 1; } + + return 0; } private static Test GenerateNewTest(string schemaName, string spName) - { + { Test currentTest = new(); currentTest.Steps = new(); TestStep currentStep = new(); @@ -118,31 +142,5 @@ private static Test GenerateNewTest(string schemaName, string spName) return currentTest; } - - static async Task SafeWriteYamlAsync(DirectoryInfo folder, Test testTemplate) - { - FileInfo output = new(Path.Combine(folder.Name, $"{testTemplate.Name}.yml")); - - var serializer = new SerializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - try - { - await using var stream = new StreamWriter(output.FullName, false); - - string yaml = serializer.Serialize(new Test[] {testTemplate}); - await stream.WriteLineAsync(yamlSchema); - await stream.WriteAsync(yaml); - - Console.WriteLine($"Created template {output.Name}"); - } - catch (Exception ex) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Error creating template {output.Name}: {ex.Message}"); - Console.ResetColor(); - } - } } } diff --git a/src/qest/Commands/Options.cs b/src/qest/Commands/Options.cs deleted file mode 100644 index da21e48..0000000 --- a/src/qest/Commands/Options.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.CommandLine; -using System.IO; -using System.Linq; - -namespace qest.Commands -{ - internal static class Options - { - internal static Option FolderOption() - { - Option folderOption = new( - name: "--folder", - description: "The destination folder for the templates.", - parseArgument: result => - { - if (result.Tokens.Any()) - { - DirectoryInfo dir = new(result.Tokens.First().Value); - if (dir.Exists) - { - return dir; - } - } - - result.ErrorMessage = "Please specify a valid folder."; - return null; - }); - folderOption.Arity = ArgumentArity.ZeroOrOne; - folderOption.AddAlias("-d"); - - return folderOption; - } - - - internal static Option FileOption() - { - Option fileOption = new( - name: "--file", - description: "A YML test file.", - parseArgument: result => - { - if (result.Tokens.Any()) - { - FileInfo file = new(result.Tokens.First().Value); - if (file.Exists) - return file; - } - - result.ErrorMessage = "Please specify a YML file."; - return null; - }); - fileOption.Arity = ArgumentArity.ZeroOrOne; - fileOption.AddAlias("-f"); - - return fileOption; - } - - internal static Option ConnectionStringOption() - { - Option tcsOption = new( - name: "--tcs", - description: "MSSQL Server target connectionstring." - ); - tcsOption.IsRequired = true; - tcsOption.Arity = ArgumentArity.ExactlyOne; - tcsOption.AddAlias("-c"); - - return tcsOption; - } - } -} diff --git a/src/qest/Commands/RunCommand.cs b/src/qest/Commands/RunCommand.cs index e2be350..fd38fbd 100644 --- a/src/qest/Commands/RunCommand.cs +++ b/src/qest/Commands/RunCommand.cs @@ -1,73 +1,103 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data.SqlClient; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using qest.Models; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - +using Spectre.Console; +using Spectre.Console.Cli; namespace qest.Commands { - internal static class Run - { - internal static void Execute(FileInfo? file, DirectoryInfo? folder, string tcs) + internal sealed class RunCommand : Command + { + public sealed class Settings : CommandSettings { - List TestCollection = new List(); + [Description("Test file")] + [CommandOption("-f|--file ")] + public string? File { get; init; } - if (file is not null) - { - TestCollection.AddRange(SafeReadYaml(file)); - } - else if (folder is not null) + [Description("Folder containing test files")] + [CommandOption("-d|--folder ")] + public string? Folder { get; init; } + + [Description("Target connection string")] + [CommandOption("-c|--tcs ")] + public string ConnectionString { get; init; } + + public override ValidationResult Validate() { - foreach (var item in folder.EnumerateFiles().Where(f => f.Extension == ".yml" || f.Extension == ".yaml")) - TestCollection.AddRange(SafeReadYaml(item)); + if (Folder is null && File is null) + { + return ValidationResult.Error("One parameter between FILE or FOLDER must be supplied."); + } + + List testsToRun = new(); + + if (File is not null) + { + FileInfo fileToLoad = new FileInfo(File); + if (!fileToLoad.Exists) + return ValidationResult.Error("File specified does not exist."); + else + testsToRun.AddRange(Utils.SafeReadYaml(fileToLoad)); + } + + if (Folder is not null) + { + DirectoryInfo folderToLoad = new DirectoryInfo(Folder); + if (!folderToLoad.Exists) + return ValidationResult.Error("Folder specified does not exist."); + else + foreach (var fileToLoad in folderToLoad.EnumerateFiles().Where(f => f.Extension == ".yml" || f.Extension == ".yaml")) + testsToRun.AddRange(Utils.SafeReadYaml(fileToLoad)); + } + + if (!testsToRun.Any()) + return ValidationResult.Error("No tests found in file or folder."); + + var result = Validators.ValidateConnectionString(ConnectionString); + + return result; } - else + + } + + private List? TestCollection; + + public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) + { + + TestCollection = new(); + + if (settings.File is not null) { - Console.WriteLine("One parameter between --file or --folder is mandatory."); - Environment.Exit(1); - } + FileInfo fileToLoad = new FileInfo(settings.File); - if (TestCollection.Count == 0) + TestCollection.AddRange(Utils.SafeReadYaml(fileToLoad)); + } + else if (settings.Folder is not null) { - Console.WriteLine("No test loaded"); - Environment.Exit(1); + DirectoryInfo folderToLoad = new DirectoryInfo(settings.Folder); + foreach (var item in folderToLoad.EnumerateFiles().Where(f => f.Extension == ".yml" || f.Extension == ".yaml")) + TestCollection.AddRange(Utils.SafeReadYaml(item)); } - var sqlConnection = new SqlConnection(tcs); + AnsiConsole.MarkupLine($"[green]{TestCollection.Count} tests loaded.[/]"); + + var sqlConnection = new SqlConnection(settings.ConnectionString); foreach (var test in TestCollection) { test.Connection = sqlConnection; bool pass = test.Run(); if (!pass) - Environment.Exit(1); + return 1; } - Environment.Exit(0); - } - static List SafeReadYaml(FileInfo file) - { - List list = new List(); - - var deserializer = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - try - { - using var stream = new StreamReader(file.FullName); - string yaml = stream.ReadToEnd(); - list.AddRange(deserializer.Deserialize>(yaml)); - } - catch (Exception ex) - { - Console.WriteLine($"Error deserializing {file.FullName}: {ex.Message}"); - } - return list; + return 0; } } } diff --git a/src/qest/Commands/Validators.cs b/src/qest/Commands/Validators.cs new file mode 100644 index 0000000..1fe6ff0 --- /dev/null +++ b/src/qest/Commands/Validators.cs @@ -0,0 +1,31 @@ +using System; +using System.Data.SqlClient; +using Spectre.Console; + +namespace qest.Commands +{ + internal static class Validators + { + internal static ValidationResult ValidateConnectionString(string ConnectionString) + { + if (ConnectionString is null || ConnectionString.Length < Utils.ConnectionStringBarebone.Length) + return ValidationResult.Error("Connection string not supplied or too short (are you missing any keyword?)"); + else + { + var sqlConnection = new SqlConnection(ConnectionString); + try + { + sqlConnection.Open(); + sqlConnection.Close(); + } + catch (Exception ex) + { + AnsiConsole.WriteException(ex); + return ValidationResult.Error("Connection to the targed database failed."); + } + } + + return ValidationResult.Success(); + } + } +} diff --git a/src/qest/Program.cs b/src/qest/Program.cs index c957284..df920b9 100644 --- a/src/qest/Program.cs +++ b/src/qest/Program.cs @@ -1,62 +1,32 @@ using qest.Commands; -using System.CommandLine; +using Spectre.Console.Cli; using System.Threading.Tasks; namespace qest { internal class Program { + const string ExampleCs = "\"Server=WHOPR;Initial Catalog=WHOOPR;User id=StevenFalken;Password=JOSHUA\""; static async Task Main(string[] args) { - RootCommand rootCommand = new("Simple, cross platform, command line tool to test MSSQL procedures"); - - rootCommand.AddCommand(CreateRunCommand()); - rootCommand.AddCommand(CreateGenerateCommand()); - - await rootCommand.InvokeAsync(args); + var app = new CommandApp(); + + app.Configure(config => + { + config.SetApplicationName("qest"); + + config.AddCommand("run") + .WithDescription("Run tests defined in YML files.") + .WithExample(new string[] {"run", "--file", "test.yml", "--tcs", ExampleCs }) + .WithExample(new string[] {"run", "--folder", "./tests", "--tcs", ExampleCs }); + + config.AddCommand("generate") + .WithDescription("Generates YML templates from Stored Procedures.") + .WithExample(new string[] {"generate", "--folder", "./templates", "--tcs", ExampleCs }); + } + ); + + await app.RunAsync(args); } - - internal static Command CreateRunCommand() - { - Command runCommand = new("run", "Run tests defined in YML files."); - - var tcs = Options.ConnectionStringOption(); - var folder = Options.FolderOption(); - var file = Options.FileOption(); - - runCommand.AddOption(tcs); - runCommand.AddOption(folder); - runCommand.AddOption(file); - - runCommand.SetHandler((file, folder, tcs) => - { - Run.Execute(file, folder, tcs); - }, - file, folder, tcs); - - return runCommand; - } - - - internal static Command CreateGenerateCommand() - { - var tcs = Options.ConnectionStringOption(); - var folder = Options.FolderOption(); - folder.SetDefaultValue("templates"); - - Command generateCommand = new("generate", "Generates YML templates from Stored Procedures."); - - generateCommand.AddOption(tcs); - generateCommand.AddOption(folder); - - generateCommand.SetHandler(async(folder, tcs) => - { - await Generate.Execute(folder, tcs); - }, - folder, tcs); - - return generateCommand; - } - } } \ No newline at end of file diff --git a/src/qest/Utils/Utils.cs b/src/qest/Utils/Utils.cs index 71d51d5..5d7547b 100644 --- a/src/qest/Utils/Utils.cs +++ b/src/qest/Utils/Utils.cs @@ -1,7 +1,12 @@ using System; using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; +using System.Threading.Tasks; +using qest.Models; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace qest { @@ -72,5 +77,57 @@ internal static string ReplaceVars(this string value, Dictionary return value; } } + + internal static List SafeReadYaml(FileInfo file) + { + List list = new List(); + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + try + { + using var stream = new StreamReader(file.FullName); + string yaml = stream.ReadToEnd(); + list.AddRange(deserializer.Deserialize>(yaml)); + } + catch (Exception ex) + { + Console.WriteLine($"Error deserializing {file.FullName}: {ex.Message}"); + } + return list; + } + + internal const string ConnectionStringBarebone = "Server=;Initial Catalog=;User Id=;Password=;"; + + internal const string yamlSchema = + @"# yaml-language-server: $schema=https://raw.githubusercontent.com/Geims83/qest/0.9.2/docs/yamlSchema.json"; + + internal static async Task SafeWriteYamlAsync(DirectoryInfo folder, Test testTemplate) + { + FileInfo output = new(Path.Combine(folder.Name, $"{testTemplate.Name}.yml")); + + var serializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + try + { + await using var stream = new StreamWriter(output.FullName, false); + + string yaml = serializer.Serialize(new Test[] {testTemplate}); + await stream.WriteLineAsync(yamlSchema); + await stream.WriteAsync(yaml); + + Console.WriteLine($"Created template {output.Name}"); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Error creating template {output.Name}: {ex.Message}"); + Console.ResetColor(); + } + } } } diff --git a/src/qest/qest.csproj b/src/qest/qest.csproj index 51500c2..fa11d68 100644 --- a/src/qest/qest.csproj +++ b/src/qest/qest.csproj @@ -9,8 +9,12 @@ embedded + + 0.9.4 + + - + From cd957f59e95e6e40eaa7f9e50cd63ca1d4098d26 Mon Sep 17 00:00:00 2001 From: Giacomo Castellani Date: Sun, 9 Oct 2022 19:19:36 +0200 Subject: [PATCH 2/4] playing around with spectre widgets --- src/qest/Commands/RunCommand.cs | 20 +++++++++++++++----- src/qest/Models/Test.cs | 25 ++++++++++++++++++++----- src/qest/Program.cs | 2 +- src/qest/Utils/Utils.cs | 2 +- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/qest/Commands/RunCommand.cs b/src/qest/Commands/RunCommand.cs index fd38fbd..9ccc0d4 100644 --- a/src/qest/Commands/RunCommand.cs +++ b/src/qest/Commands/RunCommand.cs @@ -67,10 +67,17 @@ public override ValidationResult Validate() private List? TestCollection; + private List<(string Message, bool? IsError)>? Report; + + private Tree? TestTree; + public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) { TestCollection = new(); + Report = new(); + TestTree = new Tree("quest"); + if (settings.File is not null) { @@ -85,16 +92,19 @@ public override int Execute([NotNull] CommandContext context, [NotNull] Settings TestCollection.AddRange(Utils.SafeReadYaml(item)); } - AnsiConsole.MarkupLine($"[green]{TestCollection.Count} tests loaded.[/]"); + AnsiConsole.MarkupLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} [green]{TestCollection.Count} tests loaded.[/]"); var sqlConnection = new SqlConnection(settings.ConnectionString); + foreach (var test in TestCollection) { - test.Connection = sqlConnection; - bool pass = test.Run(); - if (!pass) - return 1; + AnsiConsole.Live(TestTree) + .Start(ctx => + { + test.Connection = sqlConnection; + bool pass = test.Run(TestTree); + }); } return 0; diff --git a/src/qest/Models/Test.cs b/src/qest/Models/Test.cs index 35d8f0b..1af4c0a 100644 --- a/src/qest/Models/Test.cs +++ b/src/qest/Models/Test.cs @@ -3,6 +3,7 @@ using System.Data; using System.Data.SqlClient; using System.Linq; +using Spectre.Console; using YamlDotNet.Serialization; namespace qest.Models @@ -17,11 +18,15 @@ public class Test public Scripts? After { get; set; } public Dictionary? Variables { get; set; } private List<(string Message, bool? IsError)>? Report { get; set; } + [YamlIgnore] + private TreeNode testNode; - public bool Run() + public bool Run(Tree tree) { Report = new(); + testNode = tree.AddNode(this.Name); + ReportAdd($"-----------------------------------------------------------------------------------"); ReportAdd($"Running Test: {this.Name}"); @@ -60,6 +65,9 @@ public bool Run() var isPass = !Report.Where(m => m.IsError.HasValue && m.IsError.Value).Any(); + if (isPass) + testNode.Collapse(); + ReportAdd($"Test {Name}: {(isPass ? "OK" : "KO")}", !isPass); return isPass; @@ -235,14 +243,21 @@ private void RunTestStep(Test parentTest, TestStep step) internal void ReportAdd(string message, bool? isError = null) { - message = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + " " + message; + string pfx = $"[grey]{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; Report.Add((message, isError)); + message = message.EscapeMarkup(); + if (isError.HasValue) - Console.ForegroundColor = isError.Value ? ConsoleColor.Red : ConsoleColor.Green; + { + if (isError.Value) + testNode.AddNode($"{pfx} [red]{message}[/]"); + else + testNode.AddNode($"{pfx} [green]{message}[/]"); + } + else + testNode.AddNode($"{pfx} {message}"); - Console.WriteLine(message); - Console.ForegroundColor = ConsoleColor.White; } public static SqlDbType MapType(string type) => Utils.MapSqlType(type); diff --git a/src/qest/Program.cs b/src/qest/Program.cs index df920b9..f1fd19a 100644 --- a/src/qest/Program.cs +++ b/src/qest/Program.cs @@ -6,7 +6,7 @@ namespace qest { internal class Program { - const string ExampleCs = "\"Server=WHOPR;Initial Catalog=WHOOPR;User id=StevenFalken;Password=JOSHUA\""; + const string ExampleCs = "\"Server=WOPR;Initial Catalog=qest;User=StephenFalken;Password=JOSHUA\""; static async Task Main(string[] args) { var app = new CommandApp(); diff --git a/src/qest/Utils/Utils.cs b/src/qest/Utils/Utils.cs index 5d7547b..c22d77f 100644 --- a/src/qest/Utils/Utils.cs +++ b/src/qest/Utils/Utils.cs @@ -99,7 +99,7 @@ internal static List SafeReadYaml(FileInfo file) return list; } - internal const string ConnectionStringBarebone = "Server=;Initial Catalog=;User Id=;Password=;"; + internal const string ConnectionStringBarebone = "Server=;Initial Catalog=;User=;Password=;"; internal const string yamlSchema = @"# yaml-language-server: $schema=https://raw.githubusercontent.com/Geims83/qest/0.9.2/docs/yamlSchema.json"; From 115427d45bfc74cd49c90854b0f8eba87b12dc0f Mon Sep 17 00:00:00 2001 From: Giacomo Castellani Date: Mon, 10 Oct 2022 19:01:39 +0000 Subject: [PATCH 3/4] logging and colors refactor --- src/qest/Commands/GenerateCommand.cs | 2 +- src/qest/Commands/RunCommand.cs | 42 +++++++------ src/qest/Models/Scripts.cs | 17 +++--- src/qest/Models/Test.cs | 88 +++++++++++++--------------- 4 files changed, 72 insertions(+), 77 deletions(-) diff --git a/src/qest/Commands/GenerateCommand.cs b/src/qest/Commands/GenerateCommand.cs index 1a6d57e..442bf0d 100644 --- a/src/qest/Commands/GenerateCommand.cs +++ b/src/qest/Commands/GenerateCommand.cs @@ -102,7 +102,7 @@ public override async Task ExecuteAsync([NotNull] CommandContext context, [ var outputPar = new OutputParameter(); outputPar.Name = parameterName[1..]; outputPar.Value = "?"; - outputPar.Type = Test.MapType(parameterType); + outputPar.Type = Utils.MapSqlType(parameterType); currentStep.Results.OutputParameters.Add(outputPar); } diff --git a/src/qest/Commands/RunCommand.cs b/src/qest/Commands/RunCommand.cs index 9ccc0d4..f2d9365 100644 --- a/src/qest/Commands/RunCommand.cs +++ b/src/qest/Commands/RunCommand.cs @@ -5,13 +5,14 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Threading.Tasks; using qest.Models; using Spectre.Console; using Spectre.Console.Cli; namespace qest.Commands { - internal sealed class RunCommand : Command + internal sealed class RunCommand : AsyncCommand { public sealed class Settings : CommandSettings { @@ -65,23 +66,16 @@ public override ValidationResult Validate() } - private List? TestCollection; + private SqlConnection? TargetSqlConnection; - private List<(string Message, bool? IsError)>? Report; - - private Tree? TestTree; - - public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) + public override async Task ExecuteAsync([NotNull] CommandContext context, [NotNull] Settings settings) { - TestCollection = new(); - Report = new(); - TestTree = new Tree("quest"); + List TestCollection = new(); - - if (settings.File is not null) + if (settings.File is not null) { - FileInfo fileToLoad = new FileInfo(settings.File); + FileInfo fileToLoad = new FileInfo(settings.File); TestCollection.AddRange(Utils.SafeReadYaml(fileToLoad)); } @@ -92,22 +86,26 @@ public override int Execute([NotNull] CommandContext context, [NotNull] Settings TestCollection.AddRange(Utils.SafeReadYaml(item)); } - AnsiConsole.MarkupLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} [green]{TestCollection.Count} tests loaded.[/]"); - - var sqlConnection = new SqlConnection(settings.ConnectionString); + AnsiConsole.MarkupLine($"[grey]{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}[/] {TestCollection.Count} tests loaded."); + + TargetSqlConnection = new SqlConnection(settings.ConnectionString); + int exitCode = 0; foreach (var test in TestCollection) { - AnsiConsole.Live(TestTree) - .Start(ctx => + + test.Connection = TargetSqlConnection; + bool pass = await test.RunAsync(); + + if (!pass) { - test.Connection = sqlConnection; - bool pass = test.Run(TestTree); - }); + exitCode = 1; + break; + } } - return 0; + return exitCode; } } } diff --git a/src/qest/Models/Scripts.cs b/src/qest/Models/Scripts.cs index ca25db8..1a3ed14 100644 --- a/src/qest/Models/Scripts.cs +++ b/src/qest/Models/Scripts.cs @@ -1,13 +1,15 @@ using System.Collections.Generic; using System.Data.SqlClient; +using System.Threading.Tasks; namespace qest.Models { public class Scripts : List