#nullable enable using System.Data; using System.Globalization; using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MBN_STOCK_WEBVIEW.Infrastructure; using MMoneyCoderSharp.Data; internal static class RealSceneDbSmoke { public static async Task RunAsync( IDataQueryExecutor executor, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(executor); cancellationToken.ThrowIfCancellationRequested(); var traced = new QueryTracingDataQueryExecutor(executor); var run = new SceneSmokeRun(); Console.WriteLine("DB_SMOKE_STEP: bounded scene selectors"); var pieName = await SelectOneAsync( traced, "SELECTOR_INPUT_PIE", "SELECT STOCK_NAME FROM INPUT_PIE WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1", "STOCK_NAME", cancellationToken).ConfigureAwait(false); var profitName = await SelectOneAsync( traced, "SELECTOR_INPUT_PROFIT", "SELECT STOCK_NAME FROM INPUT_PROFIT WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1", "STOCK_NAME", cancellationToken).ConfigureAwait(false); var growthName = await SelectOneAsync( traced, "SELECTOR_INPUT_GROW", "SELECT STOCK_NAME FROM INPUT_GROW WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1", "STOCK_NAME", cancellationToken).ConfigureAwait(false); var salesName = await SelectOneAsync( traced, "SELECTOR_INPUT_SELL", "SELECT STOCK_NAME FROM INPUT_SELL WHERE STOCK_NAME IS NOT NULL AND ROWNUM = 1", "STOCK_NAME", cancellationToken).ConfigureAwait(false); var nxtStockCode = await SelectOneAsync( traced, DataSourceKind.MariaDb, "SELECTOR_NXT_STOCK", """ SELECT a.f_stock_code STOCK_CODE FROM n_online a JOIN n_stock b ON a.f_stock_code = b.f_stock_code JOIN n_batch_his c ON a.f_stock_code = c.f_stock_code WHERE a.f_curr_price <> 0 AND c.f_data_day = ( SELECT MAX(h.f_data_day) FROM n_batch_his h WHERE h.f_stock_code = a.f_stock_code) LIMIT 1 """, "STOCK_CODE", cancellationToken).ConfigureAwait(false); var domesticStock = await SelectStockAsync( traced, "SELECTOR_DOMESTIC_STOCK", """ SELECT STOCK_CODE, STOCK_NAME FROM ( SELECT a.f_stock_code STOCK_CODE, b.f_stock_wanname STOCK_NAME FROM t_online1 a JOIN t_stock b ON a.f_stock_code = b.f_stock_code WHERE a.f_curr_price <> 0 AND b.f_mkt_halt = 'N' AND b.f_stock_wanname IS NOT NULL ORDER BY a.f_stock_code ) WHERE ROWNUM = 1 """, cancellationToken).ConfigureAwait(false); var comparisonNames = await SelectManyAsync( traced, "SELECTOR_COMPARISON_STOCKS", """ SELECT STOCK_NAME FROM ( SELECT b.f_stock_wanname STOCK_NAME FROM t_online1 a JOIN t_stock b ON a.f_stock_code = b.f_stock_code WHERE a.f_curr_price <> 0 AND b.f_mkt_halt = 'N' AND b.f_stock_wanname IS NOT NULL AND EXISTS ( SELECT 1 FROM t_candle_history h WHERE h.f_stock_code = a.f_stock_code) ORDER BY a.f_stock_code ) WHERE ROWNUM <= 2 """, "STOCK_NAME", maximumRows: 2, cancellationToken).ConfigureAwait(false); var dealerStockName = await SelectOneAsync( traced, "SELECTOR_DEALER_STOCK", """ SELECT STOCK_NAME FROM ( SELECT b.f_stock_wanname STOCK_NAME FROM t_online1 a JOIN t_stock b ON a.f_stock_code = b.f_stock_code JOIN t_dealer d ON a.f_stock_code = d.f_stock_code WHERE a.f_curr_price <> 0 AND b.f_mkt_halt = 'N' AND d.f_buy_dealer_no_1 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_buy_dealer_no_2 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_buy_dealer_no_3 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_buy_dealer_no_4 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_buy_dealer_no_5 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_sell_dealer_no_1 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_sell_dealer_no_2 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_sell_dealer_no_3 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_sell_dealer_no_4 IN (SELECT f_sale_no FROM t_sale_member) AND d.f_sell_dealer_no_5 IN (SELECT f_sale_no FROM t_sale_member) ORDER BY a.f_stock_code ) WHERE ROWNUM = 1 """, "STOCK_NAME", cancellationToken).ConfigureAwait(false); var orderBookStockCode = await SelectOneAsync( traced, "SELECTOR_ORDER_BOOK_STOCK", """ SELECT STOCK_CODE FROM ( SELECT a.f_stock_code STOCK_CODE FROM t_online1 a JOIN t_stock b ON a.f_stock_code = b.f_stock_code JOIN t_online1_call c ON a.f_stock_code = c.f_stock_code WHERE a.f_curr_price > 0 AND b.f_mkt_halt = 'N' AND c.f_call_sell_1 IS NOT NULL AND c.f_call_sell_2 IS NOT NULL AND c.f_call_sell_3 IS NOT NULL AND c.f_call_sell_jan_1 IS NOT NULL AND c.f_call_sell_jan_2 IS NOT NULL AND c.f_call_sell_jan_3 IS NOT NULL AND c.f_call_buy_1 IS NOT NULL AND c.f_call_buy_2 IS NOT NULL AND c.f_call_buy_3 IS NOT NULL AND c.f_call_buy_jan_1 IS NOT NULL AND c.f_call_buy_jan_2 IS NOT NULL AND c.f_call_buy_jan_3 IS NOT NULL AND c.f_t_call_sell_jan IS NOT NULL AND c.f_t_call_buy_jan IS NOT NULL ORDER BY a.f_stock_code ) WHERE ROWNUM = 1 """, "STOCK_CODE", cancellationToken).ConfigureAwait(false); var domesticCode = domesticStock?.Code; var pageRequest = new DomesticPagedQuoteLoadRequest( PagedQuoteMarket.Kospi, DomesticPagedQuoteList.MarketCapitalization, DateOnly.FromDateTime(DateTime.Today)); await run.RequiredAsync( "s5001", "Oracle/BalticDryIndex", () => new S5001SceneDataLoader(traced).LoadAsync( new S5001BalticDryIndexLoadRequest(), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5006", "Oracle/domestic", () => new S5006DomesticSceneDataLoader(traced).LoadAsync( new S5006DomesticSceneDataRequest( LegacyDomesticEquityMarket.Kospi, RequireSelector(domesticCode, "domestic stock code")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5006", "MariaDb/NXT", () => new S5006NxtSceneDataLoader(traced).LoadAsync( LegacyDomesticEquityMarket.NxtKospi, RequireSelector(nxtStockCode, "NXT stock code"), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5011", "Oracle/face-value", () => new S5011SceneDataLoader(traced).LoadAsync( new S5011SceneDataRequest( LegacyDomesticEquityMarket.Kospi, S5011DetailBranch.FaceValue, RequireSelector(domesticCode, "domestic stock code")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5016", "Oracle/US-indices", () => new S5016SceneDataLoader(traced).LoadAsync( S5016PanelTarget.UnitedStatesIndices, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s50160", "Oracle/cotton", () => new S50160SceneDataLoader(traced).LoadAsync( S50160PanelTarget.Cotton, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5023", "Oracle/Kospi", () => new S5023SceneDataLoader(traced).LoadAsync( new S5023SceneLoadRequest(LegacyDomesticMarket.Kospi), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5024", "Oracle/Kospi", () => new S5024SceneDataLoader(traced).LoadAsync( new S5024SceneLoadRequest(LegacyDomesticMarket.Kospi), cancellationToken)).ConfigureAwait(false); var manualDirectory = Environment.GetEnvironmentVariable( S5025TrustedManualFileDataSource.DirectoryEnvironmentVariable); var manualConfigured = !string.IsNullOrWhiteSpace(manualDirectory); if (manualConfigured) { await run.RequiredAsync( "s5025", "trusted-external/manual-individual", () => new S5025SceneDataLoader( S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync( new S5025ManualSceneLoadRequest(S5025ManualAudience.Individual), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5025", "trusted-external/manual-foreign", () => new S5025SceneDataLoader( S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync( new S5025ManualSceneLoadRequest(S5025ManualAudience.Foreign), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5025", "trusted-external/manual-institution", () => new S5025SceneDataLoader( S5025TrustedManualFileDataSource.CreateFromEnvironment()).LoadAsync( new S5025ManualSceneLoadRequest(S5025ManualAudience.Institution), cancellationToken)).ConfigureAwait(false); } else { Console.WriteLine( "SCENE_SMOKE:s5025:PREREQUISITE - trusted external manual-data directory is not configured"); } await run.RequiredAsync( "s5026", "Oracle/five-day-candle-pair", () => new S5026SceneDataLoader(traced).LoadAsync( new ComparisonPairSceneLoadRequest( ComparisonGraphPeriod.FiveDays, new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 0)), new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 1))), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5029", "Oracle/five-day-yield-pair", () => new S5029SceneDataLoader(traced).LoadAsync( new ComparisonPairSceneLoadRequest( ComparisonGraphPeriod.FiveDays, new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 0)), new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 1))), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5032", "Oracle/index-and-futures", () => new S5032SceneDataLoader(traced).LoadAsync( new S5032IndexAndFuturesLoadRequest(LegacyMarketQuoteTarget.Kospi), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5037", "Oracle/dealer-quote", () => new S5037SceneDataLoader(traced).LoadAsync( new S5037SceneLoadRequest( LegacyDomesticMarket.Kospi, RequireSelector(dealerStockName, "dealer stock name")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5074", "Oracle/paged-5", () => new S5074SceneDataLoader(traced).LoadAsync( pageRequest, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5076", "Oracle/manual-pie", () => new S5076SceneDataLoader(traced).LoadAsync( RequireSelector(pieName, "INPUT_PIE stock name"), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5077", "Oracle/paged-6", () => new S5077SceneDataLoader(traced).LoadAsync( pageRequest, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5078", "Oracle/Kospi-sectors", () => new S5078SceneDataLoader(traced).LoadAsync( new S5078SceneDataRequest(SectorIndexMarket.Kospi), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5079", "Oracle/manual-growth", () => new S5079SceneDataLoader(traced).LoadAsync( new S5079SceneDataRequest(RequireSelector(growthName, "INPUT_GROW stock name")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5080", "Oracle/manual-sales", () => new S5080SceneDataLoader(traced).LoadAsync( new S5080SceneDataRequest(RequireSelector(salesName, "INPUT_SELL stock name")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5081", "Oracle/manual-profit", () => new S5081SceneDataLoader(traced).LoadAsync( RequireSelector(profitName, "INPUT_PROFIT stock name"), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5082", "Oracle/grid", () => new S5082SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5083", "Oracle/investor-flow", () => new S5083SceneDataLoader(traced).LoadAsync( new S5083SceneDataRequest(InvestorFlowParticipant.Individual), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5084", "Oracle/index-flow", () => new S5084SceneDataLoader(traced).LoadAsync( new S5084SceneDataRequest(InvestorFlowIndex.Kospi), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5085", "Oracle/program-trading", () => new S5085SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5086", "Oracle/five-day-FX-yield", () => new S5086SceneDataLoader(traced).LoadAsync( new YieldSceneLoadRequest( YieldGraphPeriod.FiveDays, new YieldGraphInstrument(YieldGraphInstrumentKind.WonDollar)), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s50860", "Oracle/five-day-quoted-FX-yield", () => new S50860SceneDataLoader(traced).LoadAsync( new YieldSceneLoadRequest( YieldGraphPeriod.FiveDays, new YieldGraphInstrument(YieldGraphInstrumentKind.WonDollar)), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5087", "Oracle/five-day-comparison", () => new S5087SceneDataLoader(traced).LoadAsync( new ComparisonPairSceneLoadRequest( ComparisonGraphPeriod.FiveDays, new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 0)), new ComparisonStockSelection( ComparisonEquityMarket.Kospi, RequireComparisonSelector(comparisonNames, 1))), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s5088", "Oracle/paged-12", () => new S5088SceneDataLoader(traced).LoadAsync( pageRequest, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s6001", "Oracle/DowJones", () => new S6001SceneDataLoader(traced).LoadAsync( S6001QuoteTarget.DowJones, cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s6067", "Oracle/institution-grid", () => new S6067SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s8001", "Oracle/Kospi-sector-map", () => new S8001SceneDataLoader(traced).LoadAsync( new S8001SceneLoadRequest(S8001Market.Kospi), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s8003", "Oracle/order-book", () => new S8003SceneDataLoader(traced).LoadAsync( new S8003SceneLoadRequest( LegacyDomesticMarket.Kospi, RequireSelector(orderBookStockCode, "order-book stock code")), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s8010", "Oracle/Kospi-index-candle", () => new S8010SceneDataLoader(traced).LoadAsync( new S8010SceneDataRequest( CandleCutAlias.Cut8061, CandleMarketTarget.KospiIndex, CandleChartMode.Price, InstrumentName: null, ShowMovingAverage5: false, ShowMovingAverage20: false), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s8018", "Oracle/current-market-pair", () => new S8018SceneDataLoader(traced).LoadAsync( new S8018MarketPairLoadRequest( S8018MarketDataMode.Current, LegacyMarketQuoteTarget.Kospi, LegacyMarketQuoteTarget.Kosdaq), cancellationToken)).ConfigureAwait(false); await run.RequiredAsync( "s8067", "Oracle/world-map", () => new S8067SceneDataLoader(traced).LoadAsync(cancellationToken)).ConfigureAwait(false); await run.DiagnosticAsync( "s8086", "Oracle/unreachable-no-cut-alias", () => new S8086DiagnosticSceneDataLoader(traced).LoadAsync( S8086DiagnosticTarget.Gold, cancellationToken)).ConfigureAwait(false); run.Complete(manualConfigured); } private static async Task SelectOneAsync( IDataQueryExecutor executor, string tableName, string sql, string column, CancellationToken cancellationToken) => await SelectOneAsync( executor, DataSourceKind.Oracle, tableName, sql, column, cancellationToken).ConfigureAwait(false); private static async Task SelectOneAsync( IDataQueryExecutor executor, DataSourceKind source, string tableName, string sql, string column, CancellationToken cancellationToken) { try { var table = await executor.ExecuteAsync( source, tableName, new DataQuerySpec(sql), cancellationToken).ConfigureAwait(false); var value = table.Rows.Count == 1 && table.Columns.Contains(column) ? Convert.ToString(table.Rows[0][column], CultureInfo.InvariantCulture)?.Trim() : null; Console.WriteLine( string.IsNullOrEmpty(value) ? $"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW" : $"DB_SMOKE_SELECTOR:{tableName}:PASS"); return string.IsNullOrEmpty(value) ? null : value; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception exception) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}"); return null; } } private static async Task SelectStockAsync( IDataQueryExecutor executor, string tableName, string sql, CancellationToken cancellationToken) { try { var table = await executor.ExecuteAsync( DataSourceKind.Oracle, tableName, new DataQuerySpec(sql), cancellationToken).ConfigureAwait(false); if (table.Rows.Count != 1 || !table.Columns.Contains("STOCK_CODE") || !table.Columns.Contains("STOCK_NAME")) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW"); return null; } var code = Convert.ToString(table.Rows[0]["STOCK_CODE"], CultureInfo.InvariantCulture)?.Trim(); var name = Convert.ToString(table.Rows[0]["STOCK_NAME"], CultureInfo.InvariantCulture)?.Trim(); if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(name)) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW"); return null; } Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:PASS"); return new StockSelector(code, name); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception exception) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}"); return null; } } private static async Task> SelectManyAsync( IDataQueryExecutor executor, string tableName, string sql, string column, int maximumRows, CancellationToken cancellationToken) { try { var table = await executor.ExecuteAsync( DataSourceKind.Oracle, tableName, new DataQuerySpec(sql), cancellationToken).ConfigureAwait(false); if (!table.Columns.Contains(column) || table.Rows.Count > maximumRows) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:INVALID_RESULT"); return []; } var values = table.Rows.Cast() .Select(row => Convert.ToString(row[column], CultureInfo.InvariantCulture)?.Trim()) .Where(value => !string.IsNullOrEmpty(value)) .Cast() .Distinct(StringComparer.Ordinal) .ToArray(); Console.WriteLine( values.Length == 0 ? $"DB_SMOKE_SELECTOR:{tableName}:NO_ELIGIBLE_ROW" : $"DB_SMOKE_SELECTOR:{tableName}:PASS:{values.Length.ToString(CultureInfo.InvariantCulture)}"); return values; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception exception) { Console.WriteLine($"DB_SMOKE_SELECTOR:{tableName}:FAIL:{SafeFailure(exception)}"); return []; } } private static string RequireSelector(string? value, string selector) { if (string.IsNullOrWhiteSpace(value)) { throw new LegacySceneDataException( $"The {selector} selector returned no eligible database row."); } return value; } private static string RequireComparisonSelector( IReadOnlyList values, int index) { if (values.Count != 2 || index is < 0 or > 1) { return RequireSelector(null, "comparison stock pair"); } return values[index]; } private static string SafeFailure(Exception exception) => exception switch { LegacySceneDataException => "DATA_UNAVAILABLE_OR_INVALID", DatabaseOperationException operation when operation.ProviderErrorCode.HasValue => "DATABASE_QUERY_FAILED_CODE_" + operation.ProviderErrorCode.Value.ToString(CultureInfo.InvariantCulture), DatabaseInfrastructureException => "DATABASE_QUERY_FAILED", S5025TrustedManualDataException => "TRUSTED_MANUAL_DATA_UNAVAILABLE", _ => exception.GetType().Name.ToUpperInvariant() }; private sealed record StockSelector(string Code, string Name); private sealed class SceneSmokeRun { private readonly HashSet _covered = new(StringComparer.Ordinal); private readonly List _requiredFailures = []; public async Task RequiredAsync( string builderKey, string caseName, Func> loadAsync) where TData : ILegacySceneData => await VerifyAsync(builderKey, caseName, loadAsync, required: true).ConfigureAwait(false); public async Task DiagnosticAsync( string builderKey, string caseName, Func> loadAsync) where TData : ILegacySceneData => await VerifyAsync(builderKey, caseName, loadAsync, required: false).ConfigureAwait(false); public void Complete(bool manualConfigured) { var expected = LegacySceneMutationBuilderRegistry.Default.Registrations .Select(registration => registration.BuilderKey) .Where(key => !string.Equals(key, "s8086", StringComparison.Ordinal)) .Where(key => manualConfigured || !string.Equals(key, "s5025", StringComparison.Ordinal)) .ToHashSet(StringComparer.Ordinal); var missing = expected.Except(_covered, StringComparer.Ordinal) .OrderBy(key => key, StringComparer.Ordinal) .ToArray(); var requiredCovered = expected.Count(_covered.Contains); var diagnosticStatus = _covered.Contains("s8086") ? "pass-unreachable-no-cut-alias" : "failed-unreachable-no-cut-alias"; Console.WriteLine( $"SCENE_SMOKE_SUMMARY: requiredCovered={requiredCovered.ToString(CultureInfo.InvariantCulture)} " + $"required={expected.Count.ToString(CultureInfo.InvariantCulture)} " + $"requiredFailures={_requiredFailures.Count.ToString(CultureInfo.InvariantCulture)} " + $"diagnostic={diagnosticStatus} " + $"manualS5025={(manualConfigured ? "configured" : "prerequisite")}"); if (missing.Length > 0) { Console.WriteLine("SCENE_SMOKE_MISSING: " + string.Join(",", missing)); } if (_requiredFailures.Count > 0 || missing.Length > 0) { throw new DatabaseConfigurationException( "One or more required real-data scene loader cases did not produce validated mutations."); } } private async Task VerifyAsync( string builderKey, string caseName, Func> loadAsync, bool required) where TData : ILegacySceneData { Console.WriteLine($"SCENE_SMOKE:{builderKey}:{caseName}:START"); try { var data = await loadAsync().ConfigureAwait(false); var mutations = LegacySceneMutationBuilderRegistry.Default.Build(builderKey, data); if (mutations.Count == 0) { throw new LegacySceneDataException( "The scene mutation builder produced no mutations."); } _covered.Add(builderKey); Console.WriteLine( $"SCENE_SMOKE:{builderKey}:{caseName}:PASS:" + mutations.Count.ToString(CultureInfo.InvariantCulture)); } catch (OperationCanceledException) { throw; } catch (Exception exception) { var failure = SafeFailure(exception); Console.WriteLine($"SCENE_SMOKE:{builderKey}:{caseName}:FAIL:{failure}"); if (required) { _requiredFailures.Add(builderKey + "/" + caseName); } } } } private sealed class QueryTracingDataQueryExecutor : IDataQueryExecutor { private readonly IDataQueryExecutor _inner; private int _sequence; public QueryTracingDataQueryExecutor(IDataQueryExecutor inner) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public DataTable Execute(DataSourceKind source, string tableName, string query) { var sequence = Start(source, tableName); try { var result = _inner.Execute(source, tableName, query); End(sequence, result); return result; } catch (Exception exception) { Fail(sequence, exception); throw; } } public async Task ExecuteAsync( DataSourceKind source, string tableName, string query, CancellationToken cancellationToken = default) { var sequence = Start(source, tableName); try { var result = await _inner.ExecuteAsync( source, tableName, query, cancellationToken).ConfigureAwait(false); End(sequence, result); return result; } catch (Exception exception) { Fail(sequence, exception); throw; } } public async Task ExecuteAsync( DataSourceKind source, string tableName, DataQuerySpec query, CancellationToken cancellationToken = default) { var sequence = Start(source, tableName); try { var result = await _inner.ExecuteAsync( source, tableName, query, cancellationToken).ConfigureAwait(false); End(sequence, result); return result; } catch (Exception exception) { Fail(sequence, exception); throw; } } private int Start(DataSourceKind source, string tableName) { var sequence = Interlocked.Increment(ref _sequence); Console.WriteLine( $"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" + $"START:{source}:{tableName}"); return sequence; } private static void End(int sequence, DataTable result) => Console.WriteLine( $"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" + $"PASS:rows={result.Rows.Count.ToString(CultureInfo.InvariantCulture)}"); private static void Fail(int sequence, Exception exception) => Console.WriteLine( $"DB_SMOKE_QUERY:{sequence.ToString(CultureInfo.InvariantCulture)}:" + $"FAIL:{SafeFailure(exception)}"); } }