Files
MBN_STOCK_WEBVIEW/tools/MBN_STOCK_WEBVIEW.DbSmoke/RealSceneDbSmoke.cs

812 lines
33 KiB
C#

#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<string?> 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<string?> 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<StockSelector?> 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<IReadOnlyList<string>> 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<DataRow>()
.Select(row => Convert.ToString(row[column], CultureInfo.InvariantCulture)?.Trim())
.Where(value => !string.IsNullOrEmpty(value))
.Cast<string>()
.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<string> 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<string> _covered = new(StringComparer.Ordinal);
private readonly List<string> _requiredFailures = [];
public async Task RequiredAsync<TData>(
string builderKey,
string caseName,
Func<Task<TData>> loadAsync)
where TData : ILegacySceneData =>
await VerifyAsync(builderKey, caseName, loadAsync, required: true).ConfigureAwait(false);
public async Task DiagnosticAsync<TData>(
string builderKey,
string caseName,
Func<Task<TData>> 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<TData>(
string builderKey,
string caseName,
Func<Task<TData>> 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<DataTable> 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<DataTable> 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)}");
}
}