< 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    {
 1819        var taskList = tasks.ToList();
 10720        var taskTypes = taskList.Select(t => t.GetType()).ToList();
 1821        var dependencyGraph = BuildDependencyGraph(taskTypes);
 1822        var sortedTypes = TopologicalSort(dependencyGraph);
 23
 24        // Map sorted types back to original task instances, preserving multiples
 23825        var taskGroups = taskList.GroupBy(t => t.GetType()).ToDictionary(g => g.Key, g => g.ToList());
 1626        var result = new List<T>();
 27
 16828        foreach (var type in sortedTypes.Where(taskGroups.ContainsKey))
 29        {
 6830            var group = taskGroups[type];
 6831            result.AddRange(group);
 32        }
 33
 1634        return result;
 35    }
 36
 37    private static Dictionary<Type, List<Type>> BuildDependencyGraph(IEnumerable<Type> taskTypes)
 38    {
 1839        var graph = new Dictionary<Type, List<Type>>();
 40
 21441        foreach (var taskType in taskTypes)
 42        {
 8943            if (!graph.ContainsKey(taskType))
 7144                graph[taskType] = new();
 45
 8946            var dependencies = taskType.GetCustomAttributes<TaskDependencyAttribute>()
 1147                .Select(attr => attr.DependencyTaskType)
 8948                .ToList();
 49
 8950            graph[taskType].AddRange(dependencies);
 51        }
 52
 1853        return graph;
 54    }
 55
 56    private static List<Type> TopologicalSort(Dictionary<Type, List<Type>> graph)
 57    {
 1858        var sorted = new List<Type>();
 1859        var visited = new HashSet<Type>();
 1860        var visiting = new HashSet<Type>();
 61
 17462        foreach (var node in graph.Keys)
 63        {
 7064            if (!visited.Contains(node))
 6365                Visit(node, graph, visited, visiting, sorted);
 66        }
 67
 1668        return sorted;
 69    }
 70
 71    private static void Visit(Type node, Dictionary<Type, List<Type>> graph, HashSet<Type> visited, HashSet<Type> visiti
 72    {
 7473        if (visiting.Contains(node))
 274            throw new InvalidOperationException($"Circular dependency detected involving task type: {node.Name}");
 75
 7276        if (visited.Contains(node))
 177            return;
 78
 7179        visiting.Add(node);
 80
 7181        if (graph.TryGetValue(node, out var dependencies))
 82        {
 16183            foreach (var dependency in dependencies)
 84            {
 1185                Visit(dependency, graph, visited, visiting, sorted);
 86            }
 87        }
 88
 6889        visiting.Remove(node);
 6890        visited.Add(node);
 6891        sorted.Add(node);
 6892    }
 93}