• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 15889942503

26 Jun 2025 12:20AM UTC coverage: 47.631% (+5.4%) from 42.239%
15889942503

push

github

HebaruSan
Merge #4400 Make hash caches thread-safe, Netkan warning for uncompiled plugins

3880 of 8730 branches covered (44.44%)

Branch coverage included in aggregate %.

33 of 75 new or added lines in 9 files covered. (44.0%)

6 existing lines in 3 files now uncovered.

8334 of 16913 relevant lines covered (49.28%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.25
/Core/Extensions/EnumerableExtensions.cs
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Collections.Concurrent;
5
using System.Linq;
6
using System.Threading;
7
using System.Threading.Tasks;
8
using System.Text.RegularExpressions;
9

10
namespace CKAN.Extensions
11
{
12
    public static class EnumerableExtensions
13
    {
14
        public static ConcurrentDictionary<K, V> ToConcurrentDictionary<K, V>(this IEnumerable<KeyValuePair<K, V>> pairs) where K: class
15
            => new ConcurrentDictionary<K, V>(pairs);
2✔
16

17
        public static IEnumerable<T> AsParallelIf<T>(this IEnumerable<T> source,
18
                                                     bool                parallel)
19
            => parallel ? source.AsParallel()
2✔
20
                        : source;
21

22
        // https://stackoverflow.com/a/55591477/2422988
23
        public static ParallelQuery<T> WithProgress<T>(this ParallelQuery<T> source,
24
                                                       long                  totalCount,
25
                                                       IProgress<int>?       progress)
26
        {
2✔
27
            long count       = 0;
2✔
28
            int  prevPercent = -1;
2✔
29
            return progress == null
2!
30
                ? source
31
                : source.Select(item =>
32
                {
×
33
                    var percent = (int)(100 * Interlocked.Increment(ref count) / totalCount);
×
34
                    if (percent > prevPercent)
×
35
                    {
×
36
                        progress.Report(percent);
×
37
                        prevPercent = percent;
×
38
                    }
×
39
                    return item;
×
40
                });
×
41
        }
2✔
42

43
        public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source)
44
            => source is Memoized<T>
2!
45
                   // Already memoized, don't wrap another layer
46
                   ? source
47
                   : new Memoized<T>(source);
48

49
        public static void RemoveWhere<K, V>(this Dictionary<K, V> source,
50
                                             Func<KeyValuePair<K, V>, bool> predicate) where K: class
51
        {
2✔
52
            var pairs = source.ToList();
2✔
53
            foreach (var kvp in pairs)
5✔
54
            {
2✔
55
                if (predicate(kvp))
2✔
56
                {
2✔
57
                    source.Remove(kvp.Key);
2✔
58
                }
2✔
59
            }
2✔
60
        }
2✔
61

62
        /// <summary>
63
        /// Sum a sequence of TimeSpans.
64
        /// Mysteriously not defined standardly.
65
        /// </summary>
66
        /// <param name="source">Sequence of TimeSpans to sum</param>
67
        /// <returns>
68
        /// Sum of the TimeSpans
69
        /// </returns>
70
        public static TimeSpan Sum(this IEnumerable<TimeSpan> source)
71
            => source.Aggregate(TimeSpan.Zero,
×
72
                                (a, b) => a + b);
×
73

74
        /// <summary>
75
        /// Select : SelectMany :: Zip : ZipMany
76
        /// </summary>
77
        /// <param name="seq1">Sequence from which to get first values</param>
78
        /// <param name="seq2">Sequence from which to get second values</param>
79
        /// <param name="func">Function to transform a value from each input sequence into a sequence of multiple outputs</param>
80
        /// <returns>Flattened sequence of values from func applies to seq1 and seq2</returns>
81
        public static IEnumerable<V> ZipMany<T, U, V>(this IEnumerable<T> seq1, IEnumerable<U> seq2, Func<T, U, IEnumerable<V>> func)
82
            => seq1.Zip(seq2, func).SelectMany(seqs => seqs);
×
83

84
        /// <summary>
85
        /// Zip a sequence with a sequence generated from the first sequence using the given function
86
        /// </summary>
87
        /// <typeparam name="T">Source sequence type</typeparam>
88
        /// <typeparam name="V">Type of elements returned by func</typeparam>
89
        /// <param name="source">Source sequence</param>
90
        /// <param name="func">Function to generate values of second sequence given source</param>
91
        /// <returns>Sequence of tuples containing pairs from each sequence</returns>
92
        public static IEnumerable<(T First, V Second)> ZipBy<T, V>(this IEnumerable<T> source, Func<IEnumerable<T>, IEnumerable<V>> func)
93
            => source.ToArray() is T[] array
2!
94
                   ? array.Zip(func(array))
95
                   : Enumerable.Empty<(T First, V Second)>();
96

97
        /// <summary>
98
        /// Insert new elements between consecutive pairs of existing elements,
99
        /// preserving the original elements in order, and using null to
100
        /// represent the elements before the beginning and after the end.
101
        /// </summary>
102
        /// <param name="source">Sequence into which to inject</param>
103
        /// <param name="inBetween">Function to generate the new elements</param>
104
        /// <returns>Sequence with new elements in it</returns>
105
        public static IEnumerable<T> Inject<T>(this IEnumerable<T> source,
106
                                               Func<T?, T?, T>     inBetween)
107
            where T : class
108
        {
×
109
            using (var e = source.GetEnumerator())
×
110
            {
×
111
                if (e.MoveNext())
×
112
                {
×
113
                    yield return inBetween(null, e.Current);
×
114
                    yield return e.Current;
×
115
                    var prev = e.Current;
×
116
                    while (e.MoveNext())
×
117
                    {
×
118
                        yield return inBetween(prev, e.Current);
×
119
                        yield return e.Current;
×
120
                        prev = e.Current;
×
121
                    }
×
122
                    yield return inBetween(prev, null);
×
123
                }
×
124
                else
125
                {
×
126
                    yield return inBetween(null, null);
×
127
                }
×
128
            }
×
129
        }
×
130

131
        /// <summary>
132
        /// Poor man's PLINQ, a trivially parallelized SelectMany that
133
        /// runs one process per item in the source sequence.
134
        /// For short sequences and long-running functions,
135
        /// when you don't feel like fighting with Partitioner.Create
136
        /// over how many items should be in each partition.
137
        /// </summary>
138
        /// <param name="source">The sequence to process</param>
139
        /// <param name="func">The function to apply to each item in the sequence</param>
140
        /// <returns>Sequence of all values from the function</returns>
141
        public static IEnumerable<V> SelectManyTasks<T, V>(this ICollection<T>     source,
142
                                                           Func<T, IEnumerable<V>> func)
143
        {
×
144
            if (source.Count <= 1)
×
145
            {
×
146
                return source.SelectMany(func);
×
147
            }
148
            else
149
            {
×
150
                var tasks = source.Select(item => Task.Run(() => func(item).ToArray()))
×
151
                                  // Force non-lazy creation of tasks
152
                                  .ToArray();
153
                // Without this, later tasks don't finish if an earlier one throws
154
                Task.WaitAll(tasks);
×
155
                try
156
                {
×
157
                    return tasks.SelectMany(task => task.Result);
×
158
                }
NEW
159
                catch (AggregateException agExc)
×
160
                {
×
NEW
161
                    agExc.RethrowInner();
×
162
                    throw;
×
163
                }
164
            }
165
        }
×
166

167
        /// <summary>
168
        /// Generate a sequence from a linked list
169
        /// </summary>
170
        /// <param name="start">The first node</param>
171
        /// <param name="getNext">Function to go from one node to the next</param>
172
        /// <returns>All the nodes in the list as a sequence</returns>
173
        public static IEnumerable<T> TraverseNodes<T>(this T start, Func<T, T?> getNext)
174
            where T : class
175
        {
2✔
176
            for (T? t = start; t != null; t = Utilities.DefaultIfThrows(() => getNext(t)))
4✔
177
            {
2✔
178
                yield return t;
2✔
179
            }
2✔
180
        }
2✔
181

182
        /// <summary>
183
        /// Try matching a regex against a series of strings and return the Match objects
184
        /// </summary>
185
        /// <param name="source">Sequence of strings to scan</param>
186
        /// <param name="pattern">Pattern to match</param>
187
        /// <returns>Sequence of Match objects</returns>
188
        public static IEnumerable<Match> WithMatches(this IEnumerable<string> source, Regex pattern)
189
            => source.Select(val => pattern.TryMatch(val, out Match? match) ? match : null)
×
190
                     .OfType<Match>();
191

192
        /// <summary>
193
        /// Apply a function to a sequence and handle any exceptions that are thrown
194
        /// </summary>
195
        /// <typeparam name="TSrc">Type of source sequence</typeparam>
196
        /// <typeparam name="TDest">Type of destination sequence</typeparam>
197
        /// <param name="source">Source sequence</param>
198
        /// <param name="func">Function to apply to each item</param>
199
        /// <param name="onThrow">Function to call if there's an exception</param>
200
        /// <returns>Sequence of return values of given function</returns>
201
        public static IEnumerable<TDest?> SelectWithCatch<TSrc, TDest>(this IEnumerable<TSrc>       source,
202
                                                                      Func<TSrc, TDest>             func,
203
                                                                      Func<TSrc, Exception, TDest?> onThrow)
204
                where TDest : class
205
            => source.Select(item => Utilities.DefaultIfThrows(()  => func(item),
2✔
206
                                                               exc => onThrow(item, exc)));
2✔
207

208
        /// <summary>
209
        /// Get a hash code for a sequence with a variable number of elements
210
        /// </summary>
211
        /// <typeparam name="T">Type of the elements in the sequence</typeparam>
212
        /// <param name="source">The sequence</param>
213
        /// <returns></returns>
214
        public static int ToSequenceHashCode<T>(this IEnumerable<T> source)
215
            => source.Aggregate(new HashCode(),
×
216
                                (hc, item) =>
217
                                {
×
218
                                    hc.Add(item);
×
219
                                    return hc;
×
220
                                },
×
221
                                hc => hc.ToHashCode());
×
222

223
        /// <summary>
224
        /// Accumulate a sequence of values based on a seed value and a function,
225
        /// similar to Aggregate but including intermediate values
226
        /// </summary>
227
        /// <typeparam name="TSource"></typeparam>
228
        /// <typeparam name="TResult"></typeparam>
229
        /// <param name="source">Input sequence</param>
230
        /// <param name="seed">First intermediate value, not included in return sequence</param>
231
        /// <param name="func">Function to transform a previous result and a next input sequence element into the next result</param>
232
        /// <returns></returns>
233
        public static IEnumerable<TResult> Accumulate<TSource, TResult>(this IEnumerable<TSource>       source,
234
                                                                        TResult                         seed,
235
                                                                        Func<TResult, TSource, TResult> func)
236
        {
×
237
            var result = seed;
×
238
            foreach (var item in source)
×
239
            {
×
240
                result = func(result, item);
×
241
                yield return result;
×
242
            }
×
243
        }
×
244
    }
245

246
    /// <summary>
247
    /// Memoized lazy evaluation in C#!
248
    /// From https://stackoverflow.com/a/12428250/2422988
249
    /// </summary>
250
    public class Memoized<T> : IEnumerable<T>
251
    {
252
        public Memoized(IEnumerable<T> source)
2✔
253
        {
2✔
254
            this.source = source;
2✔
255
        }
2✔
256

257
        IEnumerator IEnumerable.GetEnumerator()
258
            => GetEnumerator();
×
259

260
        public IEnumerator<T> GetEnumerator()
261
        {
2✔
262
            lock (gate)
2✔
263
            {
2✔
264
                if (isCacheComplete)
2✔
265
                {
2✔
266
                    return cache.GetEnumerator();
2✔
267
                }
268
                else
269
                {
2✔
270
                    enumerator ??= source.GetEnumerator();
2!
271
                }
2✔
272
            }
2✔
273
            return GetMemoizingEnumerator();
2✔
274
        }
2✔
275

276
        private IEnumerator<T> GetMemoizingEnumerator()
277
        {
2✔
278
            for (int index = 0; TryGetItem(index, out T? item); ++index)
4✔
279
            {
2✔
280
                yield return item;
2✔
281
            }
2✔
282
        }
2✔
283

284
        private bool TryGetItem(int index,
285
                                out T item)
286
        {
2✔
287
            lock (gate)
2✔
288
            {
2✔
289
                if (enumerator is not null && !IsItemInCache(index))
2!
290
                {
2✔
291
                    // The iteration may have completed while waiting for the lock
292
                    #nullable disable
293
                    if (isCacheComplete)
2!
294
                    {
×
295
                        item = default;
×
296
                        return false;
×
297
                    }
298
                    if (!enumerator.MoveNext())
2✔
299
                    {
2✔
300
                        item = default;
2✔
301
                        isCacheComplete = true;
2✔
302
                        enumerator.Dispose();
2✔
303
                        return false;
2✔
304
                    }
305
                    #nullable enable
306
                    cache.Add(enumerator.Current);
2✔
307
                }
2✔
308
                item = cache[index];
2✔
309
                return true;
2✔
310
            }
311
        }
2✔
312

313
        private bool IsItemInCache(int index)
314
            => index < cache.Count;
2✔
315

316
        private readonly IEnumerable<T>  source;
317
        private          IEnumerator<T>? enumerator;
318
        private readonly List<T>         cache = new List<T>();
2✔
319
        private          bool            isCacheComplete;
320
        private readonly object          gate = new object();
2✔
321
    }
322
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc