docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -18,6 +18,7 @@ public sealed class CompiledScriptCacheTests
|
||||
public int Count;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the first call to GetOrCompile compiles the script and caches the evaluator.</summary>
|
||||
[Fact]
|
||||
public void First_call_compiles_and_caches()
|
||||
{
|
||||
@@ -30,6 +31,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Contains("""return 42;""").ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that identical source code returns the same cached evaluator instance.</summary>
|
||||
[Fact]
|
||||
public void Identical_source_returns_the_same_compiled_evaluator()
|
||||
{
|
||||
@@ -40,6 +42,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that different source code produces different evaluator instances.</summary>
|
||||
[Fact]
|
||||
public void Different_source_produces_different_evaluator()
|
||||
{
|
||||
@@ -50,6 +53,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that whitespace differences in source code cause cache misses.</summary>
|
||||
[Fact]
|
||||
public void Whitespace_difference_misses_cache()
|
||||
{
|
||||
@@ -61,6 +65,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cached evaluator produces correct results when executed.</summary>
|
||||
[Fact]
|
||||
public async Task Cached_evaluator_still_runs_correctly()
|
||||
{
|
||||
@@ -76,6 +81,7 @@ public sealed class CompiledScriptCacheTests
|
||||
second.ShouldBe(21.0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that failed compiles are evicted so that retried source with corrections can succeed.</summary>
|
||||
[Fact]
|
||||
public void Failed_compile_is_evicted_so_retry_with_corrected_source_works()
|
||||
{
|
||||
@@ -90,6 +96,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear removes all cached entries.</summary>
|
||||
[Fact]
|
||||
public void Clear_drops_every_entry()
|
||||
{
|
||||
@@ -103,6 +110,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Contains("""return 1;""").ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concurrent compiles of the same source deduplicate to a single compilation.</summary>
|
||||
[Fact]
|
||||
public void Concurrent_compiles_of_the_same_source_deduplicate()
|
||||
{
|
||||
@@ -124,6 +132,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that different TContext and TResult type parameter pairs use separate cache instances.</summary>
|
||||
[Fact]
|
||||
public void Different_TContext_TResult_pairs_use_separate_cache_instances()
|
||||
{
|
||||
@@ -142,6 +151,7 @@ public sealed class CompiledScriptCacheTests
|
||||
boolCache.Contains("""return 1;""").ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that null source throws ArgumentNullException.</summary>
|
||||
[Fact]
|
||||
public void Null_source_throws_ArgumentNullException()
|
||||
{
|
||||
@@ -149,6 +159,7 @@ public sealed class CompiledScriptCacheTests
|
||||
Should.Throw<ArgumentNullException>(() => cache.GetOrCompile(null!));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that failed compile eviction does not remove a concurrent retry entry (Core.Scripting-006).</summary>
|
||||
[Fact]
|
||||
public void Failed_compile_eviction_does_not_remove_a_concurrent_retry_entry()
|
||||
{
|
||||
@@ -211,6 +222,7 @@ public sealed class CompiledScriptCacheTests
|
||||
"the entry under the key must still be the fresh Lazy — an unconditional TryRemove(key) would have evicted it");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the failed compile path still evicts its own faulted entry.</summary>
|
||||
[Fact]
|
||||
public void Failed_compile_path_still_evicts_its_own_faulted_entry()
|
||||
{
|
||||
@@ -222,6 +234,7 @@ public sealed class CompiledScriptCacheTests
|
||||
cache.Count.ShouldBe(0, "faulted Lazy must still be evicted after compile failure");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear uses value-scoped TryRemove so a race-inserted entry survives (Core.Scripting-014).</summary>
|
||||
[Fact]
|
||||
public void Clear_uses_value_scoped_TryRemove_so_a_race_inserted_entry_survives()
|
||||
{
|
||||
@@ -290,6 +303,7 @@ public sealed class CompiledScriptCacheTests
|
||||
|
||||
// --- Core.Scripting-008: collectible AssemblyLoadContext unload ---
|
||||
|
||||
/// <summary>Verifies that Dispose unloads the compiled script assembly load context (Core.Scripting-008).</summary>
|
||||
[Fact]
|
||||
public void Dispose_unloads_compiled_script_assembly_load_context()
|
||||
{
|
||||
@@ -342,6 +356,7 @@ public sealed class CompiledScriptCacheTests
|
||||
return del.Method.Module.Assembly;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Clear disposes every materialised evaluator (Core.Scripting-008).</summary>
|
||||
[Fact]
|
||||
public void Clear_disposes_every_materialised_evaluator()
|
||||
{
|
||||
@@ -382,6 +397,7 @@ public sealed class CompiledScriptCacheTests
|
||||
return weaks;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GetOrCompile after Dispose throws ObjectDisposedException.</summary>
|
||||
[Fact]
|
||||
public void GetOrCompile_after_Dispose_throws_ObjectDisposedException()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DependencyExtractorTests
|
||||
{
|
||||
/// <summary>Verifies that a single literal tag read is extracted.</summary>
|
||||
[Fact]
|
||||
public void Extracts_single_literal_read()
|
||||
{
|
||||
@@ -24,6 +25,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multiple distinct tag reads are extracted.</summary>
|
||||
[Fact]
|
||||
public void Extracts_multiple_distinct_reads()
|
||||
{
|
||||
@@ -39,6 +41,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Reads.ShouldContain("Line1/B");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that identical reads are deduplicated.</summary>
|
||||
[Fact]
|
||||
public void Deduplicates_identical_reads_across_the_script()
|
||||
{
|
||||
@@ -53,6 +56,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Reads.ShouldContain("X");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that virtual tag writes are tracked separately from reads.</summary>
|
||||
[Fact]
|
||||
public void Tracks_virtual_tag_writes_separately_from_reads()
|
||||
{
|
||||
@@ -69,6 +73,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Writes.ShouldNotContain("InTag");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that variable paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_variable_path()
|
||||
{
|
||||
@@ -82,6 +87,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Message.ShouldContain("string literal");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concatenated string paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_concatenated_path()
|
||||
{
|
||||
@@ -91,6 +97,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Message.ShouldContain("string literal");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that interpolated string paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_interpolated_path()
|
||||
{
|
||||
@@ -103,6 +110,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Message.ShouldContain("string literal");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that method-returned paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_method_returned_path()
|
||||
{
|
||||
@@ -115,6 +123,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Message.ShouldContain("string literal");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that empty literal paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_empty_literal_path()
|
||||
{
|
||||
@@ -124,6 +133,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Message.ShouldContain("empty");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that whitespace-only paths are rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_whitespace_only_path()
|
||||
{
|
||||
@@ -132,6 +142,7 @@ public sealed class DependencyExtractorTests
|
||||
result.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that free-function GetTag calls are ignored.</summary>
|
||||
[Fact]
|
||||
public void Ignores_non_ctx_method_named_GetTag_free_function()
|
||||
{
|
||||
@@ -147,6 +158,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Reads.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that member-access GetTag on non-ctx receivers is ignored.</summary>
|
||||
[Fact]
|
||||
public void Ignores_member_access_GetTag_on_non_ctx_receiver()
|
||||
{
|
||||
@@ -165,6 +177,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Reads.ShouldNotContain("X");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that empty source is handled without error.</summary>
|
||||
[Fact]
|
||||
public void Empty_source_is_a_no_op()
|
||||
{
|
||||
@@ -173,6 +186,7 @@ public sealed class DependencyExtractorTests
|
||||
DependencyExtractor.Extract(null!).IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that rejections include source span for UI pointing.</summary>
|
||||
[Fact]
|
||||
public void Rejection_carries_source_span_for_UI_pointing()
|
||||
{
|
||||
@@ -185,6 +199,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections[0].Span.Length.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that all bad paths are reported in a single pass.</summary>
|
||||
[Fact]
|
||||
public void Multiple_bad_paths_all_reported_in_one_pass()
|
||||
{
|
||||
@@ -197,6 +212,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that nested literal GetTag calls inside expressions are extracted.</summary>
|
||||
[Fact]
|
||||
public void Nested_literal_GetTag_inside_expression_is_extracted()
|
||||
{
|
||||
@@ -210,6 +226,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Reads.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that single-line raw string literal paths are accepted.</summary>
|
||||
[Fact]
|
||||
public void Accepts_single_line_raw_string_literal_path()
|
||||
{
|
||||
@@ -224,6 +241,7 @@ public sealed class DependencyExtractorTests
|
||||
result.Rejections.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multi-line raw string literal paths are accepted.</summary>
|
||||
[Fact]
|
||||
public void Accepts_multi_line_raw_string_literal_path()
|
||||
{
|
||||
|
||||
@@ -13,12 +13,19 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
/// </summary>
|
||||
public sealed class FakeScriptContext : ScriptContext
|
||||
{
|
||||
/// <summary>Gets the dictionary of tags available in this context.</summary>
|
||||
public Dictionary<string, DataValueSnapshot> Tags { get; } = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>Gets the log of virtual tag write operations.</summary>
|
||||
public List<(string Path, object? Value)> Writes { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DateTime Now { get; } = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ILogger Logger { get; } = new LoggerConfiguration().CreateLogger();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DataValueSnapshot GetTag(string path)
|
||||
{
|
||||
return Tags.TryGetValue(path, out var v)
|
||||
@@ -26,11 +33,18 @@ public sealed class FakeScriptContext : ScriptContext
|
||||
: new DataValueSnapshot(null, 0x80340000u, null, Now); // BadNodeIdUnknown
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetVirtualTag(string path, object? value)
|
||||
{
|
||||
Writes.Add((path, value));
|
||||
}
|
||||
|
||||
/// <summary>Seeds the context with a tag value for testing.</summary>
|
||||
/// <param name="path">The tag path.</param>
|
||||
/// <param name="value">The tag value.</param>
|
||||
/// <param name="statusCode">The OPC UA status code (default: 0).</param>
|
||||
/// <param name="sourceTs">The source timestamp (default: <see cref="Now"/>).</param>
|
||||
/// <returns>This instance for method chaining.</returns>
|
||||
public FakeScriptContext Seed(string path, object? value,
|
||||
uint statusCode = 0u, DateTime? sourceTs = null)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptContextTests
|
||||
{
|
||||
/// <summary>Verifies that Deadband returns false when the difference exactly equals the tolerance.</summary>
|
||||
[Fact]
|
||||
public void Deadband_returns_false_when_difference_equals_tolerance()
|
||||
{
|
||||
@@ -21,6 +22,7 @@ public sealed class ScriptContextTests
|
||||
ScriptContext.Deadband(current: 10.5, previous: 10.0, tolerance: 0.5).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband returns true when the difference exceeds the tolerance.</summary>
|
||||
[Fact]
|
||||
public void Deadband_returns_true_when_difference_just_exceeds_tolerance()
|
||||
{
|
||||
@@ -28,12 +30,14 @@ public sealed class ScriptContextTests
|
||||
ScriptContext.Deadband(current: 10.6, previous: 10.0, tolerance: 0.5).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband returns false when the difference is below the tolerance.</summary>
|
||||
[Fact]
|
||||
public void Deadband_returns_false_when_difference_just_below_tolerance()
|
||||
{
|
||||
ScriptContext.Deadband(current: 10.4, previous: 10.0, tolerance: 0.5).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband returns the same result regardless of the direction of change.</summary>
|
||||
[Fact]
|
||||
public void Deadband_is_symmetric_in_direction_of_change()
|
||||
{
|
||||
@@ -42,12 +46,14 @@ public sealed class ScriptContextTests
|
||||
ScriptContext.Deadband(current: 11.0, previous: 10.0, tolerance: 0.5).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband returns false when current and previous values are equal.</summary>
|
||||
[Fact]
|
||||
public void Deadband_returns_false_when_values_are_equal()
|
||||
{
|
||||
ScriptContext.Deadband(current: 10.0, previous: 10.0, tolerance: 0.001).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband with zero tolerance trips on any non-zero difference.</summary>
|
||||
[Fact]
|
||||
public void Deadband_with_zero_tolerance_returns_true_for_any_difference()
|
||||
{
|
||||
@@ -57,6 +63,7 @@ public sealed class ScriptContextTests
|
||||
ScriptContext.Deadband(current: 10.0, previous: 10.000001, tolerance: 0).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Deadband with negative tolerance trips on any non-zero difference.</summary>
|
||||
[Fact]
|
||||
public void Deadband_with_negative_tolerance_always_trips_for_unequal_values()
|
||||
{
|
||||
|
||||
@@ -18,7 +18,10 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
{
|
||||
private sealed class CapturingSink : ILogEventSink
|
||||
{
|
||||
/// <summary>Gets the list of captured log events.</summary>
|
||||
public List<LogEvent> Events { get; } = [];
|
||||
/// <summary>Emits a log event by adding it to the events list.</summary>
|
||||
/// <param name="logEvent">The log event to emit.</param>
|
||||
public void Emit(LogEvent logEvent) => Events.Add(logEvent);
|
||||
}
|
||||
|
||||
@@ -40,6 +43,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
return (scriptLogger, scriptSink, mainSink);
|
||||
}
|
||||
|
||||
/// <summary>Tests that Info event lands in scripts sink but not in main.</summary>
|
||||
[Fact]
|
||||
public void Info_event_lands_in_scripts_sink_but_not_in_main()
|
||||
{
|
||||
@@ -50,6 +54,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Tests that Warning event lands in scripts sink but not in main.</summary>
|
||||
[Fact]
|
||||
public void Warning_event_lands_in_scripts_sink_but_not_in_main()
|
||||
{
|
||||
@@ -60,6 +65,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>Tests that Error event is mirrored to main at Warning level.</summary>
|
||||
[Fact]
|
||||
public void Error_event_mirrored_to_main_at_Warning_level()
|
||||
{
|
||||
@@ -72,6 +78,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events[0].Level.ShouldBe(LogEventLevel.Warning, "Error+ is downgraded to Warning in the main log");
|
||||
}
|
||||
|
||||
/// <summary>Tests that mirrored event includes ScriptName and original level.</summary>
|
||||
[Fact]
|
||||
public void Mirrored_event_includes_ScriptName_and_original_level()
|
||||
{
|
||||
@@ -86,6 +93,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
((ScalarValue)forwarded.Properties["OriginalLevel"]).Value.ShouldBe(LogEventLevel.Error);
|
||||
}
|
||||
|
||||
/// <summary>Tests that mirrored event preserves exception for main log stack trace.</summary>
|
||||
[Fact]
|
||||
public void Mirrored_event_preserves_exception_for_main_log_stack_trace()
|
||||
{
|
||||
@@ -97,6 +105,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events[0].Exception.ShouldBeSameAs(ex);
|
||||
}
|
||||
|
||||
/// <summary>Tests that Fatal event is mirrored just like Error.</summary>
|
||||
[Fact]
|
||||
public void Fatal_event_mirrored_just_like_Error()
|
||||
{
|
||||
@@ -106,6 +115,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events[0].Level.ShouldBe(LogEventLevel.Warning);
|
||||
}
|
||||
|
||||
/// <summary>Tests that missing ScriptName property falls back to unknown.</summary>
|
||||
[Fact]
|
||||
public void Missing_ScriptName_property_falls_back_to_unknown()
|
||||
{
|
||||
@@ -126,12 +136,14 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
Should.NotThrow(() => companion.Emit(ev));
|
||||
}
|
||||
|
||||
/// <summary>Tests that null main logger is rejected.</summary>
|
||||
[Fact]
|
||||
public void Null_main_logger_rejected()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() => new ScriptLogCompanionSink(null!));
|
||||
}
|
||||
|
||||
/// <summary>Tests that custom mirror threshold is applied.</summary>
|
||||
[Fact]
|
||||
public void Custom_mirror_threshold_applied()
|
||||
{
|
||||
@@ -153,6 +165,7 @@ public sealed class ScriptLogCompanionSinkTests
|
||||
mainSink.Events.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Tests that factory plus companion sink integration surfaces script error in both logs.</summary>
|
||||
[Fact]
|
||||
public void Factory_plus_companion_sink_integration_surfaces_script_error_in_both_logs()
|
||||
{
|
||||
|
||||
@@ -18,10 +18,15 @@ public sealed class ScriptLoggerFactoryTests
|
||||
/// <summary>Capturing sink that collects every emitted LogEvent for assertion.</summary>
|
||||
private sealed class CapturingSink : ILogEventSink
|
||||
{
|
||||
/// <summary>Gets the list of captured log events.</summary>
|
||||
public List<LogEvent> Events { get; } = [];
|
||||
|
||||
/// <summary>Adds a log event to the captured list.</summary>
|
||||
/// <param name="logEvent">The log event to capture.</param>
|
||||
public void Emit(LogEvent logEvent) => Events.Add(logEvent);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Create sets the ScriptName structured property.</summary>
|
||||
[Fact]
|
||||
public void Create_sets_ScriptName_structured_property()
|
||||
{
|
||||
@@ -38,6 +43,7 @@ public sealed class ScriptLoggerFactoryTests
|
||||
((ScalarValue)ev.Properties[ScriptLoggerFactory.ScriptNameProperty]).Value.ShouldBe("LineRate");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that each script gets its own property value.</summary>
|
||||
[Fact]
|
||||
public void Each_script_gets_its_own_property_value()
|
||||
{
|
||||
@@ -55,6 +61,7 @@ public sealed class ScriptLoggerFactoryTests
|
||||
((ScalarValue)sink.Events[2].Properties[ScriptLoggerFactory.ScriptNameProperty]).Value.ShouldBe("Alarm_A");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that error-level events preserve level and exception.</summary>
|
||||
[Fact]
|
||||
public void Error_level_event_preserves_level_and_exception()
|
||||
{
|
||||
@@ -68,12 +75,14 @@ public sealed class ScriptLoggerFactoryTests
|
||||
sink.Events[0].Exception.ShouldBeOfType<InvalidOperationException>();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that null root logger is rejected.</summary>
|
||||
[Fact]
|
||||
public void Null_root_rejected()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() => new ScriptLoggerFactory(null!));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that empty script names are rejected.</summary>
|
||||
[Fact]
|
||||
public void Empty_script_name_rejected()
|
||||
{
|
||||
@@ -84,6 +93,7 @@ public sealed class ScriptLoggerFactoryTests
|
||||
Should.Throw<ArgumentException>(() => factory.Create(null!));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the ScriptNameProperty constant is stable.</summary>
|
||||
[Fact]
|
||||
public void ScriptNameProperty_constant_is_stable()
|
||||
{
|
||||
|
||||
@@ -14,12 +14,14 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptSandboxBuildTests
|
||||
{
|
||||
/// <summary>Verifies that a null context type throws ArgumentNullException.</summary>
|
||||
[Fact]
|
||||
public void Null_context_type_throws_ArgumentNullException()
|
||||
{
|
||||
Should.Throw<ArgumentNullException>(() => ScriptSandbox.Build(null!));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a non-ScriptContext type throws ArgumentException.</summary>
|
||||
[Fact]
|
||||
public void Non_ScriptContext_type_throws_ArgumentException()
|
||||
{
|
||||
@@ -30,6 +32,7 @@ public sealed class ScriptSandboxBuildTests
|
||||
.ParamName.ShouldBe("contextType");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the abstract ScriptContext base type is accepted by the sandbox builder.</summary>
|
||||
[Fact]
|
||||
public void Abstract_ScriptContext_base_type_is_accepted()
|
||||
{
|
||||
@@ -40,6 +43,7 @@ public sealed class ScriptSandboxBuildTests
|
||||
options.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a concrete subclass is accepted and its assembly is referenced in the sandbox.</summary>
|
||||
[Fact]
|
||||
public void Concrete_subclass_is_accepted_and_its_assembly_referenced()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptSandboxTests
|
||||
{
|
||||
/// <summary>Verifies that a baseline script compiles successfully.</summary>
|
||||
[Fact]
|
||||
public void Happy_path_script_compiles_and_returns()
|
||||
{
|
||||
@@ -25,6 +26,7 @@ public sealed class ScriptSandboxTests
|
||||
evaluator.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a script can compile, run, and read a seeded tag.</summary>
|
||||
[Fact]
|
||||
public async Task Happy_path_script_runs_and_reads_seeded_tag()
|
||||
{
|
||||
@@ -36,6 +38,7 @@ public sealed class ScriptSandboxTests
|
||||
result.ShouldBe(42.0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SetVirtualTag records write operations.</summary>
|
||||
[Fact]
|
||||
public async Task SetVirtualTag_records_the_write()
|
||||
{
|
||||
@@ -51,6 +54,7 @@ public sealed class ScriptSandboxTests
|
||||
ctx.Writes[0].Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that file I/O is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_File_IO_at_compile()
|
||||
{
|
||||
@@ -59,6 +63,7 @@ public sealed class ScriptSandboxTests
|
||||
"""return System.IO.File.ReadAllText("c:/secrets.txt");"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that HttpClient is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_HttpClient_at_compile()
|
||||
{
|
||||
@@ -70,6 +75,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Process.Start is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Process_Start_at_compile()
|
||||
{
|
||||
@@ -81,6 +87,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Assembly.Load is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Reflection_Assembly_Load_at_compile()
|
||||
{
|
||||
@@ -92,6 +99,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Environment.Exit is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Environment_Exit_at_compile()
|
||||
{
|
||||
@@ -107,6 +115,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Environment.FailFast is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Environment_FailFast_at_compile()
|
||||
{
|
||||
@@ -120,6 +129,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AppDomain is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_AppDomain_at_compile()
|
||||
{
|
||||
@@ -133,6 +143,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GC.Collect is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_GC_Collect_at_compile()
|
||||
{
|
||||
@@ -146,6 +157,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Activator.CreateInstance is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Activator_CreateInstance_at_compile()
|
||||
{
|
||||
@@ -159,6 +171,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Environment.GetEnvironmentVariable is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Environment_GetEnvironmentVariable_at_compile()
|
||||
{
|
||||
@@ -178,6 +191,7 @@ public sealed class ScriptSandboxTests
|
||||
// name a forbidden type without producing any of those nodes. The broadened walker
|
||||
// resolves GetTypeInfo on every TypeSyntax / ExpressionSyntax so they are all caught.
|
||||
|
||||
/// <summary>Verifies that typeof a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_typeof_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -188,6 +202,7 @@ public sealed class ScriptSandboxTests
|
||||
"""return typeof(System.IO.File).Name;"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a forbidden type in a generic argument is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_generic_type_argument_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -201,6 +216,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cast to a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_cast_to_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -214,6 +230,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that default of a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_default_of_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -227,6 +244,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an is-pattern with a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_is_pattern_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -240,6 +258,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an as-expression with a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_as_expression_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -253,6 +272,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that array creation with a forbidden element type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_array_creation_forbidden_element_type_at_compile()
|
||||
{
|
||||
@@ -266,6 +286,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a local variable with a forbidden type is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_local_declared_variable_forbidden_type_at_compile()
|
||||
{
|
||||
@@ -279,6 +300,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that typeof a forbidden type inside Activator is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_typeof_forbidden_type_inside_Activator_at_compile()
|
||||
{
|
||||
@@ -293,6 +315,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an allowed generic type argument still compiles.</summary>
|
||||
[Fact]
|
||||
public async Task Allowed_generic_type_argument_still_compiles()
|
||||
{
|
||||
@@ -307,6 +330,7 @@ public sealed class ScriptSandboxTests
|
||||
result.ShouldBe(3);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that typeof an allowed type still compiles.</summary>
|
||||
[Fact]
|
||||
public async Task Allowed_typeof_still_compiles()
|
||||
{
|
||||
@@ -317,6 +341,7 @@ public sealed class ScriptSandboxTests
|
||||
result.ShouldBe("Int32");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that script exceptions propagate unwrapped.</summary>
|
||||
[Fact]
|
||||
public async Task Script_exception_propagates_unwrapped()
|
||||
{
|
||||
@@ -326,6 +351,7 @@ public sealed class ScriptSandboxTests
|
||||
await evaluator.RunAsync(new FakeScriptContext(), TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ctx.Now is available without reaching the wall clock.</summary>
|
||||
[Fact]
|
||||
public void Ctx_Now_is_available_without_DateTime_UtcNow_reaching_wall_clock()
|
||||
{
|
||||
@@ -334,6 +360,7 @@ public sealed class ScriptSandboxTests
|
||||
evaluator.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the Deadband helper is reachable from scripts.</summary>
|
||||
[Fact]
|
||||
public void Deadband_helper_is_reachable_from_scripts()
|
||||
{
|
||||
@@ -342,6 +369,7 @@ public sealed class ScriptSandboxTests
|
||||
evaluator.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that LINQ Enumerable is available from scripts.</summary>
|
||||
[Fact]
|
||||
public async Task Linq_Enumerable_is_available_from_scripts()
|
||||
{
|
||||
@@ -356,6 +384,7 @@ public sealed class ScriptSandboxTests
|
||||
result.ShouldBe(12);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DataValueSnapshot is usable in scripts.</summary>
|
||||
[Fact]
|
||||
public async Task DataValueSnapshot_is_usable_in_scripts()
|
||||
{
|
||||
@@ -370,6 +399,7 @@ public sealed class ScriptSandboxTests
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that compile errors include location information in diagnostics.</summary>
|
||||
[Fact]
|
||||
public void Compile_error_gives_location_in_diagnostics()
|
||||
{
|
||||
@@ -392,6 +422,7 @@ public sealed class ScriptSandboxTests
|
||||
// rejection. Adding them here closes the coverage gap that allowed Core.Scripting-001 and
|
||||
// -002 to go undetected.
|
||||
|
||||
/// <summary>Verifies that Thread instantiation is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Thread_new_at_compile()
|
||||
{
|
||||
@@ -407,6 +438,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Task.Run is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Tasks_TaskRun_at_compile()
|
||||
{
|
||||
@@ -421,6 +453,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that InteropServices is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_InteropServices_at_compile()
|
||||
{
|
||||
@@ -434,6 +467,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Win32 Registry access is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Win32_Registry_at_compile()
|
||||
{
|
||||
@@ -451,6 +485,7 @@ public sealed class ScriptSandboxTests
|
||||
// the original deny-list missed. Each is denied type-granularly in
|
||||
// ForbiddenTypeAnalyzer.ForbiddenFullTypeNames; these tests pin the rejection.
|
||||
|
||||
/// <summary>Verifies that ThreadPool.QueueUserWorkItem is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_ThreadPool_QueueUserWorkItem_at_compile()
|
||||
{
|
||||
@@ -466,6 +501,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Timer instantiation is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_Timer_new_at_compile()
|
||||
{
|
||||
@@ -480,6 +516,7 @@ public sealed class ScriptSandboxTests
|
||||
"""));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AssemblyLoadContext is rejected at compile time.</summary>
|
||||
[Fact]
|
||||
public void Rejects_AssemblyLoadContext_at_compile()
|
||||
{
|
||||
@@ -497,6 +534,7 @@ public sealed class ScriptSandboxTests
|
||||
|
||||
// --- Core.Scripting-013: wrapper-source injection ---
|
||||
|
||||
/// <summary>Verifies that sibling method injection via balanced braces is rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_sibling_method_injection_via_balanced_braces()
|
||||
{
|
||||
@@ -513,6 +551,7 @@ public sealed class ScriptSandboxTests
|
||||
ex.Message.ShouldContain("Core.Scripting-013");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that sibling class injection via balanced braces is rejected.</summary>
|
||||
[Fact]
|
||||
public void Rejects_sibling_class_injection_via_balanced_braces()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class TimedScriptEvaluatorTests
|
||||
{
|
||||
/// <summary>Verifies that fast scripts complete under timeout and return value.</summary>
|
||||
[Fact]
|
||||
public async Task Fast_script_completes_under_timeout_and_returns_value()
|
||||
{
|
||||
@@ -26,6 +27,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
result.ShouldBe(42.0);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that scripts longer than timeout throw ScriptTimeoutException.</summary>
|
||||
[Fact]
|
||||
public async Task Script_longer_than_timeout_throws_ScriptTimeoutException()
|
||||
{
|
||||
@@ -47,6 +49,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
ex.Message.ShouldContain("50.0");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that caller cancellation takes precedence over timeout.</summary>
|
||||
[Fact]
|
||||
public async Task Caller_cancellation_takes_precedence_over_timeout()
|
||||
{
|
||||
@@ -67,6 +70,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
await timed.RunAsync(new FakeScriptContext(), cts.Token));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that default timeout is 250ms per plan.</summary>
|
||||
[Fact]
|
||||
public void Default_timeout_is_250ms_per_plan()
|
||||
{
|
||||
@@ -74,6 +78,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
.ShouldBe(TimeSpan.FromMilliseconds(250));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that zero or negative timeout is rejected at construction.</summary>
|
||||
[Fact]
|
||||
public void Zero_or_negative_timeout_is_rejected_at_construction()
|
||||
{
|
||||
@@ -84,6 +89,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
new TimedScriptEvaluator<FakeScriptContext, int>(inner, TimeSpan.FromMilliseconds(-1)));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that null inner evaluator is rejected.</summary>
|
||||
[Fact]
|
||||
public void Null_inner_is_rejected()
|
||||
{
|
||||
@@ -91,6 +97,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
new TimedScriptEvaluator<FakeScriptContext, int>(null!));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that null context is rejected.</summary>
|
||||
[Fact]
|
||||
public void Null_context_is_rejected()
|
||||
{
|
||||
@@ -100,6 +107,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
await timed.RunAsync(null!, TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that script exceptions propagate unwrapped.</summary>
|
||||
[Fact]
|
||||
public async Task Script_exception_propagates_unwrapped()
|
||||
{
|
||||
@@ -115,6 +123,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
ex.Message.ShouldBe("script boom");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ScriptTimeoutException message points at diagnostic path.</summary>
|
||||
[Fact]
|
||||
public async Task ScriptTimeoutException_message_points_at_diagnostic_path()
|
||||
{
|
||||
@@ -133,6 +142,7 @@ public sealed class TimedScriptEvaluatorTests
|
||||
ex.Message.ShouldContain("widening the timeout");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that caller cancellation wins even when timeout fires first.</summary>
|
||||
[Fact]
|
||||
public async Task Caller_cancellation_wins_even_when_timeout_fires_first()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user