From a81db6d8347fa60045ae9028572e54aff61a35b3 Mon Sep 17 00:00:00 2001 From: Giacomo Castellani Date: Thu, 20 Oct 2022 18:43:32 +0000 Subject: [PATCH 1/3] splitted utils --- src/qest/Utils/ExtensionMethods.cs | 45 +++++++++ src/qest/Utils/IO.cs | 81 +++++++++++++++ src/qest/Utils/Maps.cs | 50 ++++++++++ src/qest/Utils/Strings.cs | 10 ++ src/qest/Utils/Utils.cs | 154 ----------------------------- 5 files changed, 186 insertions(+), 154 deletions(-) create mode 100644 src/qest/Utils/ExtensionMethods.cs create mode 100644 src/qest/Utils/IO.cs create mode 100644 src/qest/Utils/Maps.cs create mode 100644 src/qest/Utils/Strings.cs delete mode 100644 src/qest/Utils/Utils.cs diff --git a/src/qest/Utils/ExtensionMethods.cs b/src/qest/Utils/ExtensionMethods.cs new file mode 100644 index 0000000..be10904 --- /dev/null +++ b/src/qest/Utils/ExtensionMethods.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using Spectre.Console; + +namespace qest +{ + internal static partial class Utils + { + internal static object? ReplaceVarsInParameter(this object value, Dictionary? variables) + { + if (variables != null && value is string stringValue) + { + var result = stringValue.ReplaceVars(variables); + return result != "NULL" ? result : null; + } + else + { + return value; + } + } + + internal static string ReplaceVars(this string value, Dictionary? variables) + { + if (variables != null) + { + return variables.Aggregate(value, (acc, var) => acc.Replace($"{{{var.Key}}}", var.Value?.ToString() ?? "NULL")); + } + else + { + return value; + } + } + + internal static string EscapeAndAddStyles(this string message, string? customStyle) + { + message = message.EscapeMarkup(); + + if (customStyle is not null) + foreach (string style in customStyle.Split(',')) + message = $"[{style}]{message}[/]"; + + return message; + } + } +} diff --git a/src/qest/Utils/IO.cs b/src/qest/Utils/IO.cs new file mode 100644 index 0000000..f3a2457 --- /dev/null +++ b/src/qest/Utils/IO.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using qest.Models; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace qest +{ + internal static partial class Utils + { + 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 static async Task> SafeReadYamlAsync(FileInfo file) + { + List list = new List(); + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + try + { + using var stream = new StreamReader(file.FullName); + string yaml = await stream.ReadToEndAsync(); + list.AddRange(deserializer.Deserialize>(yaml)); + } + catch (Exception ex) + { + Console.WriteLine($"Error deserializing {file.FullName}: {ex.Message}"); + } + return list; + } + + 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/Utils/Maps.cs b/src/qest/Utils/Maps.cs new file mode 100644 index 0000000..a3e5ce6 --- /dev/null +++ b/src/qest/Utils/Maps.cs @@ -0,0 +1,50 @@ +using System; +using System.Data; + +namespace qest +{ + internal static partial class Utils + { + internal static Type MapType(SqlDbType type) => type switch + { + SqlDbType.Bit => typeof(bool), + SqlDbType.TinyInt => typeof(byte), + SqlDbType.SmallInt => typeof(short), + SqlDbType.BigInt => typeof(long), + SqlDbType.Float => typeof(double), + SqlDbType.Int => typeof(int), + SqlDbType.NVarChar => typeof(string), + SqlDbType.DateTime => typeof(DateTime), + SqlDbType.DateTime2 => typeof(DateTime), + SqlDbType.Date => typeof(DateTime), + SqlDbType.DateTimeOffset => typeof(DateTimeOffset), + SqlDbType.Time => typeof(TimeSpan), + SqlDbType.Real => typeof(float), + SqlDbType.Decimal => typeof(decimal), + SqlDbType.Money => typeof(decimal), + + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Type not expected: {type}"), + }; + + internal static SqlDbType MapSqlType(string type) => type switch + { + "bit" => SqlDbType.Bit, + "tinyint" => SqlDbType.TinyInt, + "smallint" => SqlDbType.SmallInt, + "bigint" => SqlDbType.BigInt, + "float" => SqlDbType.Float, + "int" => SqlDbType.Int, + "nvarchar" => SqlDbType.NVarChar, + "datetime" => SqlDbType.DateTime, + "datetime2" => SqlDbType.DateTime2, + "date" => SqlDbType.Date, + "datetimetoffset" => SqlDbType.DateTimeOffset, + "time" => SqlDbType.Time, + "real" => SqlDbType.Real, + "decimal" => SqlDbType.Decimal, + "money" => SqlDbType.Decimal, + + _ => throw new ArgumentOutOfRangeException(nameof(type), $"Type not expected: {type}"), + }; + } +} diff --git a/src/qest/Utils/Strings.cs b/src/qest/Utils/Strings.cs new file mode 100644 index 0000000..feef30e --- /dev/null +++ b/src/qest/Utils/Strings.cs @@ -0,0 +1,10 @@ +namespace qest +{ + internal static partial class Utils + { + 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"; + } +} diff --git a/src/qest/Utils/Utils.cs b/src/qest/Utils/Utils.cs deleted file mode 100644 index 26d1d58..0000000 --- a/src/qest/Utils/Utils.cs +++ /dev/null @@ -1,154 +0,0 @@ -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 -{ - internal static class Utils - { - internal static Type MapType(SqlDbType type) => type switch - { - SqlDbType.Bit => typeof(bool), - SqlDbType.TinyInt => typeof(byte), - SqlDbType.SmallInt => typeof(short), - SqlDbType.BigInt => typeof(long), - SqlDbType.Float => typeof(double), - SqlDbType.Int => typeof(int), - SqlDbType.NVarChar => typeof(string), - SqlDbType.DateTime => typeof(DateTime), - SqlDbType.DateTime2 => typeof(DateTime), - SqlDbType.Date => typeof(DateTime), - SqlDbType.DateTimeOffset => typeof(DateTimeOffset), - SqlDbType.Time => typeof(TimeSpan), - SqlDbType.Real => typeof(float), - SqlDbType.Decimal => typeof(decimal), - SqlDbType.Money => typeof(decimal), - - _ => throw new ArgumentOutOfRangeException(nameof(type), $"Type not expected: {type}"), - }; - - internal static SqlDbType MapSqlType(string type) => type switch - { - "bit" => SqlDbType.Bit, - "tinyint" => SqlDbType.TinyInt, - "smallint" => SqlDbType.SmallInt, - "bigint" => SqlDbType.BigInt, - "float" => SqlDbType.Float, - "int" => SqlDbType.Int, - "nvarchar" => SqlDbType.NVarChar, - "datetime" => SqlDbType.DateTime, - "datetime2" => SqlDbType.DateTime2, - "date" => SqlDbType.Date, - "datetimetoffset" => SqlDbType.DateTimeOffset, - "time" => SqlDbType.Time, - "real" => SqlDbType.Real, - "decimal" => SqlDbType.Decimal, - "money" => SqlDbType.Decimal, - - _ => throw new ArgumentOutOfRangeException(nameof(type), $"Type not expected: {type}"), - }; - internal static object? ReplaceVarsInParameter(this object value, Dictionary? variables) - { - if (variables != null && value is string stringValue) - { - var result = stringValue.ReplaceVars(variables); - return result != "NULL" ? result : null; - } - else - { - return value; - } - } - - internal static string ReplaceVars(this string value, Dictionary? variables) - { - if (variables != null) - { - return variables.Aggregate(value, (acc, var) => acc.Replace($"{{{var.Key}}}", var.Value?.ToString() ?? "NULL")); - } - else - { - 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 static async Task> SafeReadYamlAsync(FileInfo file) - { - List list = new List(); - - var deserializer = new DeserializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .Build(); - - try - { - using var stream = new StreamReader(file.FullName); - string yaml = await stream.ReadToEndAsync(); - 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=;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(); - } - } - } -} From a271265e5a7f3017e7dd0baeb5003f08c6bbc388 Mon Sep 17 00:00:00 2001 From: Giacomo Castellani Date: Thu, 20 Oct 2022 18:45:03 +0000 Subject: [PATCH 2/3] moved logic to runners --- src/qest/Commands/RunCommand.cs | 35 ++-- src/qest/Models/Script.cs | 5 + src/qest/Models/Scripts.cs | 35 ---- src/qest/Models/Test.cs | 250 +----------------------- src/qest/Runners/ConsoleRunner.cs | 299 +++++++++++++++++++++++++++++ src/qest/Runners/TreeRunner.cs | 306 ++++++++++++++++++++++++++++++ 6 files changed, 633 insertions(+), 297 deletions(-) delete mode 100644 src/qest/Models/Scripts.cs create mode 100644 src/qest/Runners/ConsoleRunner.cs create mode 100644 src/qest/Runners/TreeRunner.cs diff --git a/src/qest/Commands/RunCommand.cs b/src/qest/Commands/RunCommand.cs index d669ea6..afe6a92 100644 --- a/src/qest/Commands/RunCommand.cs +++ b/src/qest/Commands/RunCommand.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.Data.SqlClient; using System.Diagnostics.CodeAnalysis; @@ -7,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using qest.Models; +using qest.Runners; using Spectre.Console; using Spectre.Console.Cli; namespace qest.Commands @@ -86,24 +86,33 @@ public override async Task ExecuteAsync([NotNull] CommandContext context, [ TestCollection.AddRange(await Utils.SafeReadYamlAsync(item)); } - AnsiConsole.MarkupLine($"[grey]{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}[/] {TestCollection.Count} tests loaded."); + var root = new Tree($"{TestCollection.Count} tests loaded"); TargetSqlConnection = new SqlConnection(settings.ConnectionString); int exitCode = 0; - foreach (var test in TestCollection) - { + await AnsiConsole.Live(root) + .StartAsync(async ctx => + { + foreach (var test in TestCollection) + { + var testNode = root.AddNode($"{test.Name}".EscapeAndAddStyles("bold,blue")); - test.Connection = TargetSqlConnection; - bool pass = await test.RunAsync(); + var runner = new TreeRunner(test, TargetSqlConnection, testNode); + ctx.Refresh(); + + bool pass = await runner.RunAsync(); + ctx.Refresh(); + + if (!pass) + { + exitCode = 1; + break; + } + } + }); - if (!pass) - { - exitCode = 1; - break; - } - } return exitCode; } diff --git a/src/qest/Models/Script.cs b/src/qest/Models/Script.cs index 011855e..4792169 100644 --- a/src/qest/Models/Script.cs +++ b/src/qest/Models/Script.cs @@ -44,4 +44,9 @@ public enum ScriptType Inline, File } + + public class Scripts : List