< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Tasks.TaskJobFactory
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Tasks/TaskJobFactory.cs
Tag: Kestrun/Kestrun@2d87023b37eb91155071c91dd3d6a2eeb3004705
Line coverage
95%
Covered lines: 104
Uncovered lines: 5
Coverable lines: 109
Total lines: 149
Line coverage: 95.4%
Branch coverage
75%
Covered branches: 9
Total branches: 12
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/15/2025 - 01:01:18 Line coverage: 95.3% (102/107) Branch coverage: 75% (9/12) Total lines: 146 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/15/2025 - 21:27:26 Line coverage: 95.4% (104/109) Branch coverage: 75% (9/12) Total lines: 149 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b665594 10/15/2025 - 01:01:18 Line coverage: 95.3% (102/107) Branch coverage: 75% (9/12) Total lines: 146 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/15/2025 - 21:27:26 Line coverage: 95.4% (104/109) Branch coverage: 75% (9/12) Total lines: 149 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b665594

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_TaskId()100%11100%
get_ScriptCode()100%11100%
get_Log()100%11100%
get_Pool()100%11100%
get_Progress()100%11100%
Create(...)75%8890%
PowerShellTask(...)100%1195.23%
CSharpTask(...)75%44100%
VbNetTask(...)100%1193.75%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Tasks/TaskJobFactory.cs

#LineLine coverage
 1using System.Management.Automation;
 2using Kestrun.Scripting;
 3using Kestrun.SharedState;
 4using Kestrun.Utilities;
 5using Kestrun.Languages;
 6using Kestrun.Hosting.Options;
 7
 8namespace Kestrun.Tasks;
 9
 10/// <summary>
 11/// Factory to create task job delegates for different scripting languages.
 12/// </summary>
 13internal static class TaskJobFactory
 14{
 15    /// <summary>
 16    /// Configuration for creating a task job delegate.
 17    /// </summary>
 18    /// <param name="TaskId">Unique identifier of the task.</param>
 19    /// <param name="ScriptCode">The language options containing the script code and settings.</param>
 20    /// <param name="Log">Logger instance for logging.</param>
 21    /// <param name="Pool">Optional PowerShell runspace pool.</param>
 22    /// <param name="Progress">Progress state object to expose to scripts.</param>
 1523    internal record TaskJobConfig(
 424        string TaskId,
 7825        LanguageOptions ScriptCode,
 1526        Serilog.ILogger Log,
 1627        KestrunRunspacePoolManager? Pool,
 1528        ProgressiveKestrunTaskState Progress
 1529    );
 30
 31    internal static Func<CancellationToken, Task<object?>> Create(TaskJobConfig config)
 1532        => config.ScriptCode.Language switch
 1533        {
 1534            ScriptLanguage.PowerShell =>
 435                config.Pool is null
 436                    ? throw new InvalidOperationException("PowerShell runspace pool must be provided for PowerShell task
 437                    : PowerShellTask(config),
 1038            ScriptLanguage.CSharp => CSharpTask(config),
 139            ScriptLanguage.VBNet => VbNetTask(config),
 040            _ => throw new NotSupportedException($"Language {config.ScriptCode.Language} not supported."),
 1541        };
 42
 43    private static Func<CancellationToken, Task<object?>> PowerShellTask(TaskJobConfig config)
 44    {
 445        return async ct =>
 446        {
 447            if (config.Pool is null)
 448            {
 049                throw new InvalidOperationException("PowerShell runspace pool must be provided for PowerShell tasks.");
 450            }
 451            var runspace = config.Pool.Acquire();
 452            try
 453            {
 454                using var ps = PowerShell.Create();
 455                ps.Runspace = runspace;
 456                _ = ps.AddScript(config.ScriptCode.Code);
 457
 458                // Merge arguments and inject Progress variable for cooperative updates
 459                var vars = config.ScriptCode.Arguments is { Count: > 0 }
 460                    ? new Dictionary<string, object?>(config.ScriptCode.Arguments)
 461                    : [];
 462                vars["TaskProgress"] = config.Progress;
 463                vars["TaskId"] = config.TaskId;
 464                PowerShellExecutionHelpers.SetVariables(ps, vars, config.Log);
 665                using var reg = ct.Register(() => ps.Stop());
 466                var results = await ps.InvokeAsync().WaitAsync(ct).ConfigureAwait(false);
 467
 468                // Collect pipeline output (base objects) as an object[]
 169                var output = results.Count == 0
 170                    ? null
 271                    : results.Select(r => r.BaseObject).ToArray();
 472
 173                if (ps.HadErrors || ps.Streams.Error.Count != 0 ||
 174                    ps.Streams.Warning.Count > 0 || ps.Streams.Verbose.Count > 0 ||
 175                    ps.Streams.Debug.Count > 0 || ps.Streams.Information.Count > 0)
 476                {
 077                    config.Log.Verbose(BuildError.Text(ps));
 478                }
 479
 180                return output;
 481            }
 482            finally
 483            {
 484                config.Pool.Release(runspace);
 485            }
 586        };
 87    }
 88
 89    private static Func<CancellationToken, Task<object?>> CSharpTask(TaskJobConfig config)
 90    {
 91        // Prepare locals upfront and include TaskProgress so compilation preamble declares it
 1092        var compileLocals = config.ScriptCode.Arguments is { Count: > 0 }
 1093            ? new Dictionary<string, object?>(config.ScriptCode.Arguments)
 1094            : [];
 1095        compileLocals["TaskProgress"] = config.Progress;
 96
 97        // Compile and get a runner that returns object? (last expression)
 1098        var script = CSharpDelegateBuilder.Compile(
 1099            config.ScriptCode.Code,
 10100            config.Log,
 10101            config.ScriptCode.ExtraImports,
 10102            config.ScriptCode.ExtraRefs,
 10103            compileLocals,
 10104            config.ScriptCode.LanguageVersion);
 10105        var runner = script.CreateDelegate(); // ScriptRunner<object?>
 10106        return async ct =>
 10107        {
 10108            // Use the same locals at execution time
 7109            var globals = new CsGlobals(SharedStateStore.Snapshot(), compileLocals);
 7110            return await runner(globals, ct).ConfigureAwait(false);
 17111        };
 112    }
 113
 114    private static Func<CancellationToken, Task<object?>> VbNetTask(TaskJobConfig config)
 115    {
 1116        var code = config.ScriptCode.Code;
 1117        var log = config.Log;
 1118        var extraImports = config.ScriptCode.ExtraImports;
 1119        var extraRefs = config.ScriptCode.ExtraRefs;
 1120        var arguments = config.ScriptCode.Arguments;
 1121        var vbLangVersion = Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16_9;
 1122        var runner = VBNetDelegateBuilder.Compile<object>(
 1123            code,
 1124            log,
 1125            extraImports,
 1126            extraRefs,
 1127            arguments,
 1128            vbLangVersion
 1129        );
 1130        return async ct =>
 1131        {
 1132            // For VB, expose Progress via locals as well
 1133            var locals = config.ScriptCode.Arguments is { Count: > 0 }
 1134                ? new Dictionary<string, object?>(config.ScriptCode.Arguments)
 1135                : [];
 1136            locals["TaskProgress"] = config.Progress;
 1137            var globals = new CsGlobals(SharedStateStore.Snapshot(), locals);
 1138            // VB compiled delegate does not accept CancellationToken; allow cooperative cancel of awaiting.
 1139            var task = runner(globals);
 1140            var completed = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, ct)).ConfigureAwait(false);
 1141            if (completed == task)
 1142            {
 1143                return await task.ConfigureAwait(false);
 1144            }
 0145            ct.ThrowIfCancellationRequested();
 0146            return null; // unreachable
 2147        };
 148    }
 149}