< 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@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
95%
Covered lines: 101
Uncovered lines: 5
Coverable lines: 106
Total lines: 146
Line coverage: 95.2%
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@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559411/14/2025 - 12:29:34 Line coverage: 95.2% (101/106) Branch coverage: 75% (9/12) Total lines: 146 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a626 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@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559411/14/2025 - 12:29:34 Line coverage: 95.2% (101/106) Branch coverage: 75% (9/12) Total lines: 146 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a626

Metrics

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

File(s)

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

#LineLine coverage
 1using System.Management.Automation;
 2using Kestrun.Scripting;
 3using Kestrun.Utilities;
 4using Kestrun.Languages;
 5using Kestrun.Hosting.Options;
 6using Kestrun.Hosting;
 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="Host">The Kestrun host instance.</param>
 19    /// <param name="TaskId">Unique identifier of the task.</param>
 20    /// <param name="ScriptCode">The language options containing the script code and settings.</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(
 2424        KestrunHost Host,
 325        string TaskId,
 8126        LanguageOptions ScriptCode,
 1227        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 =>
 335                config.Pool is null
 336                    ? throw new InvalidOperationException("PowerShell runspace pool must be provided for PowerShell task
 337                    : PowerShellTask(config),
 1138            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    {
 345        return async ct =>
 346        {
 347            if (config.Pool is null)
 348            {
 049                throw new InvalidOperationException("PowerShell runspace pool must be provided for PowerShell tasks.");
 350            }
 351            var log = config.Host.Logger;
 352            var runspace = config.Pool.Acquire();
 353            try
 354            {
 355                using var ps = PowerShell.Create();
 356                ps.Runspace = runspace;
 357                _ = ps.AddScript(config.ScriptCode.Code);
 358
 359                // Merge arguments and inject Progress variable for cooperative updates
 360                var vars = config.ScriptCode.Arguments is { Count: > 0 }
 361                    ? new Dictionary<string, object?>(config.ScriptCode.Arguments)
 362                    : [];
 363                vars["TaskProgress"] = config.Progress;
 364                vars["TaskId"] = config.TaskId;
 365                PowerShellExecutionHelpers.SetVariables(ps, vars, log);
 566                using var reg = ct.Register(() => ps.Stop());
 367                //  var results = await ps.InvokeAsync().WaitAsync(ct).ConfigureAwait(false);
 368                var results = await ps.InvokeAsync(log, ct).ConfigureAwait(false);
 369                // Collect pipeline output (base objects) as an object[]
 170                var output = results.Count == 0
 171                    ? null
 272                    : results.Select(r => r.BaseObject).ToArray();
 373
 174                if (ps.HadErrors || ps.Streams.Error.Count != 0 ||
 175                    ps.Streams.Warning.Count > 0 || ps.Streams.Verbose.Count > 0 ||
 176                    ps.Streams.Debug.Count > 0 || ps.Streams.Information.Count > 0)
 377                {
 078                    log.Verbose(BuildError.Text(ps));
 379                }
 380
 181                return output;
 382            }
 383            finally
 384            {
 385                config.Pool.Release(runspace);
 386            }
 487        };
 88    }
 89
 90    private static Func<CancellationToken, Task<object?>> CSharpTask(TaskJobConfig config)
 91    {
 92        // Prepare locals upfront and include TaskProgress so compilation preamble declares it
 1193        var compileLocals = config.ScriptCode.Arguments is { Count: > 0 }
 1194            ? new Dictionary<string, object?>(config.ScriptCode.Arguments)
 1195            : [];
 1196        compileLocals["TaskProgress"] = config.Progress;
 97
 98        // Compile and get a runner that returns object? (last expression)
 1199        var script = CSharpDelegateBuilder.Compile(
 11100            host: config.Host,
 11101            code: config.ScriptCode.Code,
 11102            extraImports: config.ScriptCode.ExtraImports,
 11103            extraRefs: config.ScriptCode.ExtraRefs,
 11104            locals: compileLocals,
 11105            languageVersion: config.ScriptCode.LanguageVersion);
 11106        var runner = script.CreateDelegate(); // ScriptRunner<object?>
 11107        return async ct =>
 11108        {
 11109            // Use the same locals at execution time
 8110            var globals = new CsGlobals(config.Host.SharedState.Snapshot(), compileLocals);
 8111            return await runner(globals, ct).ConfigureAwait(false);
 19112        };
 113    }
 114
 115    private static Func<CancellationToken, Task<object?>> VbNetTask(TaskJobConfig config)
 116    {
 1117        var code = config.ScriptCode.Code;
 1118        var arguments = config.ScriptCode.Arguments;
 1119        var runner = VBNetDelegateBuilder.Compile<object>(
 1120            host: config.Host,
 1121            code: config.ScriptCode.Code,
 1122            extraImports: config.ScriptCode.ExtraImports,
 1123            extraRefs: config.ScriptCode.ExtraRefs,
 1124            locals: arguments,
 1125            languageVersion: Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16_9
 1126        );
 1127        return async ct =>
 1128        {
 1129            // For VB, expose Progress via locals as well
 1130            var locals = arguments is { Count: > 0 }
 1131                ? new Dictionary<string, object?>(arguments)
 1132                : [];
 1133            locals["TaskProgress"] = config.Progress;
 1134            var globals = new CsGlobals(config.Host.SharedState.Snapshot(), locals);
 1135            // VB compiled delegate does not accept CancellationToken; allow cooperative cancel of awaiting.
 1136            var task = runner(globals);
 1137            var completed = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, ct)).ConfigureAwait(false);
 1138            if (completed == task)
 1139            {
 1140                return await task.ConfigureAwait(false);
 1141            }
 0142            ct.ThrowIfCancellationRequested();
 0143            return null; // unreachable
 2144        };
 145    }
 146}