< Summary

Information
Class: Elsa.Common.Helpers.TopologicalTaskSorter
Assembly: Elsa.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Helpers/TopologicalTaskSorter.cs
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 93
Line coverage: 100%
Branch coverage
100%
Covered branches: 26
Total branches: 26
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Sort(...)100%1010100%
BuildDependencyGraph(...)100%44100%
TopologicalSort(...)100%44100%
Visit(...)100%88100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Common/Helpers/TopologicalTaskSorter.cs

#LineLine coverage
 1using System.Reflection;
 2
 3namespace Elsa.Common.Helpers;
 4
 5/// <summary>
 6/// Sorts tasks based on their dependencies using topological ordering.
 7/// </summary>
 8public static class TopologicalTaskSorter
 9{
 10    /// <summary>
 11    /// Sorts tasks in topological order based on their TaskDependencyAttribute declarations.
 12    /// </summary>
 13    /// <param name="tasks">The tasks to sort.</param>
 14    /// <typeparam name="T">The task type.</typeparam>
 15    /// <returns>A list of tasks sorted in dependency order.</returns>
 16    /// <exception cref="InvalidOperationException">Thrown when a circular dependency is detected.</exception>
 17    public static IReadOnlyList<T> Sort<T>(IEnumerable<T> tasks) where T : ITask
 18    {
 2919        var taskList = tasks.ToList();
 21220        var taskTypes = taskList.Select(t => t.GetType()).ToList();
 2921        var dependencyGraph = BuildDependencyGraph(taskTypes);
 2922        var sortedTypes = TopologicalSort(dependencyGraph);
 23
 24        // Map sorted types back to original task instances, preserving multiples
 51125        var taskGroups = taskList.GroupBy(t => t.GetType()).ToDictionary(g => g.Key, g => g.ToList());
 2726        var result = new List<T>();
 27
 35828        foreach (var type in sortedTypes.Where(taskGroups.ContainsKey))
 29        {
 15230            var group = taskGroups[type];
 15231            result.AddRange(group);
 32        }
 33
 2734        return result;
 35    }
 36
 37    private static Dictionary<Type, List<Type>> BuildDependencyGraph(IEnumerable<Type> taskTypes)
 38    {
 2939        var graph = new Dictionary<Type, List<Type>>();
 40
 42441        foreach (var taskType in taskTypes)
 42        {
 18343            if (!graph.ContainsKey(taskType))
 15544                graph[taskType] = new();
 45
 18346            var dependencies = taskType.GetCustomAttributes<TaskDependencyAttribute>()
 2347                .Select(attr => attr.DependencyTaskType)
 18348                .ToList();
 49
 18350            graph[taskType].AddRange(dependencies);
 51        }
 52
 2953        return graph;
 54    }
 55
 56    private static List<Type> TopologicalSort(Dictionary<Type, List<Type>> graph)
 57    {
 2958        var sorted = new List<Type>();
 2959        var visited = new HashSet<Type>();
 2960        var visiting = new HashSet<Type>();
 61
 36462        foreach (var node in graph.Keys)
 63        {
 15464            if (!visited.Contains(node))
 14765                Visit(node, graph, visited, visiting, sorted);
 66        }
 67
 2768        return sorted;
 69    }
 70
 71    private static void Visit(Type node, Dictionary<Type, List<Type>> graph, HashSet<Type> visited, HashSet<Type> visiti
 72    {
 17073        if (visiting.Contains(node))
 274            throw new InvalidOperationException($"Circular dependency detected involving task type: {node.Name}");
 75
 16876        if (visited.Contains(node))
 1377            return;
 78
 15579        visiting.Add(node);
 80
 15581        if (graph.TryGetValue(node, out var dependencies))
 82        {
 35383            foreach (var dependency in dependencies)
 84            {
 2385                Visit(dependency, graph, visited, visiting, sorted);
 86            }
 87        }
 88
 15289        visiting.Remove(node);
 15290        visited.Add(node);
 15291        sorted.Add(node);
 15292    }
 93}