src/SSC/ParallelCompareApi.cs

実行済み 490 · 未実行 96 · 対象外 520
Coverage source: 043e27b9e5febd713c747d2558802e450fad8edd

実行済みCoberturaのhit数が1以上の行です。
未実行カバレッジ対象ですがhit数が0の行です。
対象外Coberturaに実行対象として記録されていない行です。
行状態HitsSource
1 対象外 using System.Collections;
2 対象外 using System.Globalization;
3 対象外 using System.Reflection;
4 対象外 using SSC.Internal;
5 対象外
6 対象外 namespace SSC;
7 対象外
8 対象外 public static class ParallelCompareApi
9 対象外 {
10 対象外 private const string NullKeyText = "<null>";
11 対象外
12 実行済み 2 private static readonly MethodInfo BuildNodeMethod = typeof(ParallelCompareApi)
13 実行済み 2 .GetMethod(nameof(BuildNodeGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
14 対象外
15 対象外 public static CompareResult<T> Compare<T>(IReadOnlyList<T> models, CompareConfiguration? configuration = null)
16 対象外 {
17 実行済み 112 var config = configuration ?? new CompareConfiguration();
18 実行済み 112 var context = new CompareContext(config);
19 実行済み 112 if (context.IsTraceEnabled)
20 対象外 {
21 実行済み 5 context.Trace("start", typeof(T).Name, ("rootType", typeof(T)), ("modelCount", models?.Count), ("strictMode", config.StrictMode));
22 対象外 }
23 対象外
24 実行済み 112 if (models is null || models.Count == 0)
25 対象外 {
26 実行済み 4 context.RecordInputError(
27 実行済み 4 CompareIssueCode.InputModelListEmpty,
28 実行済み 4 typeof(T).Name,
29 実行済み 4 null,
30 実行済み 4 null,
31 実行済み 4 "models must contain at least one element.");
32 対象外
33 実行済み 3 return BuildResult<T>(root: null, context.Issues, config);
34 対象外 }
35 対象外
36 実行済み 648 for (var modelIndex = 0; modelIndex < models.Count; modelIndex++)
37 対象外 {
38 実行済み 216 if (models[modelIndex] is not null)
39 対象外 {
40 対象外 continue;
41 対象外 }
42 対象外
43 未実行 0 context.RecordInputError(
44 未実行 0 CompareIssueCode.InputModelNullElement,
45 未実行 0 typeof(T).Name,
46 未実行 0 modelIndex,
47 未実行 0 null,
48 未実行 0 "models contains null element.");
49 対象外 }
50 対象外
51 実行済み 108 if (context.HasErrors)
52 対象外 {
53 未実行 0 return BuildResult<T>(root: null, context.Issues, config);
54 対象外 }
55 対象外
56 実行済み 108 var slots = new NodeSlot[models.Count];
57 実行済み 648 for (var index = 0; index < models.Count; index++)
58 対象外 {
59 実行済み 216 slots[index] = new NodeSlot(models[index], NodePresenceState.PresentValue);
60 対象外 }
61 対象外
62 実行済み 108 var root = (ParallelNode<T>)BuildNode(typeof(T), slots, typeof(T).Name, context, keyText: null, keyValue: null, keyComparer: null);
63 実行済み 106 return BuildResult(root, context.Issues, config);
64 対象外 }
65 対象外
66 対象外 private static CompareResult<T> BuildResult<T>(Parallel<T>? root, IReadOnlyList<CompareIssue> issues, CompareConfiguration configuration)
67 対象外 {
68 実行済み 109 return new CompareResult<T>
69 実行済み 109 {
70 実行済み 109 Root = root,
71 実行済み 109 Issues = issues,
72 実行済み 9 HasError = issues.Any(issue => issue.Level == CompareIssueLevel.Error),
73 実行済み 109 Configuration = configuration,
74 実行済み 109 };
75 対象外 }
76 対象外
77 対象外 private static IParallelNode BuildNode(
78 対象外 Type nodeType,
79 対象外 NodeSlot[] slots,
80 対象外 string path,
81 対象外 CompareContext context,
82 対象外 string? keyText,
83 対象外 object? keyValue,
84 対象外 IEqualityComparer<object>? keyComparer,
85 対象外 Type? comparisonType = null)
86 対象外 {
87 実行済み 899 var generic = BuildNodeMethod.MakeGenericMethod(nodeType);
88 対象外 try
89 対象外 {
90 実行済み 899 return (IParallelNode)generic.Invoke(
91 実行済み 899 null,
92 実行済み 899 [
93 実行済み 899 slots,
94 実行済み 899 path,
95 実行済み 899 context,
96 実行済み 899 keyText,
97 実行済み 899 keyValue,
98 実行済み 899 keyComparer,
99 実行済み 899 comparisonType ?? nodeType,
100 実行済み 899 comparisonType is not null,
101 実行済み 899 ])!;
102 対象外 }
103 実行済み 2 catch (TargetInvocationException ex) when (ex.InnerException is CompareInputException)
104 対象外 {
105 未実行 0 throw ex.InnerException;
106 対象外 }
107 実行済み 2 catch (TargetInvocationException ex) when (ex.InnerException is CompareExecutionException)
108 対象外 {
109 実行済み 2 throw ex.InnerException;
110 対象外 }
111 実行済み 897 }
112 対象外
113 対象外 private static IParallelNode BuildNodeGeneric<TNode>(
114 対象外 NodeSlot[] slots,
115 対象外 string path,
116 対象外 CompareContext context,
117 対象外 string? keyText,
118 対象外 object? keyValue,
119 対象外 IEqualityComparer<object>? keyComparer,
120 対象外 Type comparisonType,
121 対象外 bool detectRuntimeTypeMismatch)
122 対象外 {
123 実行済み 899 var typedValues = new TNode?[slots.Length];
124 実行済み 899 var states = new NodePresenceState[slots.Length];
125 対象外
126 実行済み 5398 for (var index = 0; index < slots.Length; index++)
127 対象外 {
128 実行済み 1800 states[index] = slots[index].State;
129 実行済み 1800 typedValues[index] = slots[index].State == NodePresenceState.PresentValue
130 実行済み 1800 ? (TNode?)slots[index].Value
131 実行済み 1800 : default;
132 対象外 }
133 対象外
134 実行済み 899 var isScalarNode = IsScalarType(comparisonType);
135 実行済み 899 var node = new ParallelNode<TNode>(
136 実行済み 899 typedValues,
137 実行済み 899 states,
138 実行済み 899 keyText,
139 実行済み 899 keyValue,
140 実行済み 899 keyComparer,
141 実行済み 899 isScalarNode,
142 実行済み 899 detectRuntimeTypeMismatch);
143 実行済み 899 if (node.HasRuntimeTypeMismatch)
144 対象外 {
145 実行済み 1 if (context.IsTraceEnabled)
146 対象外 {
147 未実行 0 context.Trace(
148 未実行 0 "node",
149 未実行 0 path,
150 未実行 0 ("nodeType", typeof(TNode)),
151 未実行 0 ("comparisonType", comparisonType),
152 未実行 0 ("nodeKind", "RuntimeTypeMismatch"),
153 未実行 0 ("runtimeTypes", FormatRuntimeTypes(slots)),
154 未実行 0 ("keyText", keyText));
155 対象外 }
156 対象外
157 実行済み 1 return node;
158 対象外 }
159 対象外
160 実行済み 898 if (isScalarNode)
161 対象外 {
162 実行済み 529 if (context.IsTraceEnabled)
163 対象外 {
164 実行済み 13 context.Trace(
165 実行済み 13 "node",
166 実行済み 13 path,
167 実行済み 13 ("nodeType", typeof(TNode)),
168 実行済み 13 ("comparisonType", comparisonType),
169 実行済み 13 ("nodeKind", "Scalar"),
170 実行済み 13 ("keyText", keyText));
171 対象外 }
172 対象外
173 実行済み 529 return node;
174 対象外 }
175 対象外
176 実行済み 369 if (context.IsTraceEnabled)
177 対象外 {
178 実行済み 11 context.Trace(
179 実行済み 11 "node",
180 実行済み 11 path,
181 実行済み 11 ("nodeType", typeof(TNode)),
182 実行済み 11 ("comparisonType", comparisonType),
183 実行済み 11 ("nodeKind", "Object"),
184 実行済み 11 ("keyText", keyText));
185 対象外 }
186 対象外
187 実行済み 2242 foreach (var property in TypeMetadataResolver.GetComparableMembers(comparisonType))
188 対象外 {
189 実行済み 753 var propertyPath = $"{path}.{property.Name}";
190 実行済み 753 if (context.IsTraceEnabled)
191 対象外 {
192 実行済み 17 context.Trace(
193 実行済み 17 "metadata",
194 実行済み 17 propertyPath,
195 実行済み 17 ("declaredType", property.MemberType),
196 実行済み 17 ("container", GetContainerCategory(property.MemberType)));
197 対象外 }
198 対象外
199 実行済み 753 var memberSlots = BuildMemberSlots(slots, property, propertyPath, context);
200 対象外
201 実行済み 753 if (TryGetDictionaryTypes(property.MemberType, out var keyType, out var elementType))
202 対象外 {
203 実行済み 27 if (context.IsTraceEnabled)
204 対象外 {
205 実行済み 1 context.Trace("metadata", propertyPath, ("keyType", keyType), ("valueType", elementType));
206 対象外 }
207 実行済み 27 var children = BuildDictionaryChildren(memberSlots, keyType, elementType, propertyPath, context);
208 実行済み 78 node.SetChildren(property.Name, children, memberSlots.Select(slot => slot.State).ToArray());
209 実行済み 26 continue;
210 対象外 }
211 対象外
212 実行済み 726 if (TryGetSequenceElementType(property.MemberType, out elementType))
213 対象外 {
214 実行済み 158 if (context.IsTraceEnabled)
215 対象外 {
216 実行済み 5 context.Trace("metadata", propertyPath, ("elementType", elementType));
217 対象外 }
218 実行済み 158 var children = BuildSequenceChildren(memberSlots, property.MemberType, elementType, propertyPath, context);
219 実行済み 468 node.SetChildren(property.Name, children, memberSlots.Select(slot => slot.State).ToArray());
220 実行済み 157 continue;
221 対象外 }
222 対象外
223 実行済み 568 var memberNode = BuildNode(property.MemberType, memberSlots, propertyPath, context, keyText: null, keyValue: null, keyComparer: null);
224 実行済み 568 node.SetMemberNode(property.Name, memberNode);
225 対象外 }
226 対象外
227 実行済み 367 return node;
228 対象外 }
229 対象外
230 対象外 private static NodeSlot[] BuildMemberSlots(
231 対象外 NodeSlot[] parentSlots,
232 対象外 ComparableMember property,
233 対象外 string path,
234 対象外 CompareContext context)
235 対象外 {
236 実行済み 753 var slots = new NodeSlot[parentSlots.Length];
237 実行済み 4510 for (var modelIndex = 0; modelIndex < parentSlots.Length; modelIndex++)
238 対象外 {
239 実行済み 1502 if (parentSlots[modelIndex].State == NodePresenceState.Missing)
240 対象外 {
241 実行済み 168 slots[modelIndex] = NodeSlot.Missing;
242 実行済み 168 continue;
243 対象外 }
244 対象外
245 実行済み 1334 if (parentSlots[modelIndex].State == NodePresenceState.PresentNull || parentSlots[modelIndex].Value is null)
246 対象外 {
247 実行済み 2 slots[modelIndex] = NodeSlot.PresentNull;
248 実行済み 2 continue;
249 対象外 }
250 対象外
251 対象外 object? value;
252 対象外 try
253 対象外 {
254 実行済み 1332 value = property.GetValue(parentSlots[modelIndex].Value!);
255 実行済み 1330 }
256 実行済み 2 catch (Exception ex)
257 対象外 {
258 実行済み 2 context.RecordExecutionError(
259 実行済み 2 CompareIssueCode.ReflectionMetadataBuildFailed,
260 実行済み 2 path,
261 実行済み 2 modelIndex,
262 実行済み 2 null,
263 実行済み 2 $"failed to get member '{property.Name}': {ex.Message}");
264 実行済み 2 slots[modelIndex] = NodeSlot.Missing;
265 実行済み 2 continue;
266 対象外 }
267 対象外
268 実行済み 1330 slots[modelIndex] = value is null ? NodeSlot.PresentNull : NodeSlot.PresentValue(value);
269 対象外 }
270 対象外
271 実行済み 753 return slots;
272 対象外 }
273 対象外
274 対象外 private static IReadOnlyList<IParallelNode> BuildDictionaryChildren(
275 対象外 NodeSlot[] containerSlots,
276 対象外 Type keyType,
277 対象外 Type elementType,
278 対象外 string path,
279 対象外 CompareContext context)
280 対象外 {
281 実行済み 27 var comparer = new KeyComparer(keyType, context.Configuration.StringKeyComparison);
282 実行済み 27 var maps = new Dictionary<object, object?>[containerSlots.Length];
283 実行済み 27 var union = new HashSet<object>(comparer);
284 実行済み 27 var keyTexts = new Dictionary<object, string>(comparer);
285 対象外
286 実行済み 158 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
287 対象外 {
288 実行済み 53 maps[modelIndex] = new Dictionary<object, object?>(comparer);
289 実行済み 53 if (containerSlots[modelIndex].State != NodePresenceState.PresentValue || containerSlots[modelIndex].Value is null)
290 対象外 {
291 対象外 continue;
292 対象外 }
293 対象外
294 実行済み 51 var rawContainer = containerSlots[modelIndex].Value;
295 対象外
296 実行済み 51 if (rawContainer is null)
297 対象外 {
298 未実行 0 if (context.IsTraceEnabled)
299 対象外 {
300 未実行 0 context.Trace("container", path, ("modelIndex", modelIndex), ("container", "Dictionary"), ("runtimeType", "<null>"), ("materializedCount", 0));
301 対象外 }
302 未実行 0 continue;
303 対象外 }
304 対象外
305 実行済み 51 if (context.IsTraceEnabled)
306 対象外 {
307 実行済み 2 context.Trace(
308 実行済み 2 "container",
309 実行済み 2 path,
310 実行済み 2 ("modelIndex", modelIndex),
311 実行済み 2 ("container", "Dictionary"),
312 実行済み 2 ("runtimeType", rawContainer.GetType()),
313 実行済み 2 ("keyType", keyType),
314 実行済み 2 ("valueType", elementType));
315 対象外 }
316 対象外
317 実行済み 215 foreach (var (entryKey, entryValue) in EnumerateDictionary(rawContainer))
318 対象外 {
319 実行済み 57 if (entryKey is null)
320 対象外 {
321 未実行 0 context.RecordExecutionError(
322 未実行 0 CompareIssueCode.CompareKeyValueIsNull,
323 未実行 0 path,
324 未実行 0 modelIndex,
325 未実行 0 NullKeyText,
326 未実行 0 "dictionary key is null.");
327 未実行 0 continue;
328 対象外 }
329 対象外
330 実行済み 57 var normalizedKey = NormalizeKey(entryKey);
331 実行済み 57 UpdateCanonicalKeyText(
332 実行済み 57 keyTexts,
333 実行済み 57 normalizedKey,
334 実行済み 57 KeyToText(entryKey),
335 実行済み 57 keyType,
336 実行済み 57 context.Configuration.StringKeyComparison);
337 実行済み 57 if (maps[modelIndex].ContainsKey(normalizedKey))
338 対象外 {
339 実行済み 2 context.RecordExecutionError(
340 実行済み 2 CompareIssueCode.DuplicateCompareKeyDetected,
341 実行済み 2 path,
342 実行済み 2 modelIndex,
343 実行済み 2 keyTexts[normalizedKey],
344 実行済み 2 $"duplicate compare key '{normalizedKey}'.");
345 実行済み 1 continue;
346 対象外 }
347 対象外
348 実行済み 55 maps[modelIndex][normalizedKey] = entryValue;
349 実行済み 55 union.Add(normalizedKey);
350 対象外 }
351 対象外 }
352 対象外
353 実行済み 59 var orderedKeys = union.OrderBy(key => key, comparer).ToArray();
354 実行済み 26 var children = new List<IParallelNode>(orderedKeys.Length);
355 実行済み 118 foreach (var key in orderedKeys)
356 対象外 {
357 実行済み 33 var slots = new NodeSlot[containerSlots.Length];
358 実行済み 206 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
359 対象外 {
360 実行済み 70 if (!maps[modelIndex].TryGetValue(key, out var value))
361 対象外 {
362 実行済み 16 slots[modelIndex] = NodeSlot.Missing;
363 実行済み 16 continue;
364 対象外 }
365 対象外
366 実行済み 54 slots[modelIndex] = value is null ? NodeSlot.PresentNull : NodeSlot.PresentValue(value);
367 対象外 }
368 対象外
369 実行済み 33 var childNode = BuildNode(
370 実行済み 33 elementType,
371 実行済み 33 slots,
372 実行済み 33 path,
373 実行済み 33 context,
374 実行済み 33 keyText: keyTexts.TryGetValue(key, out var keyText) ? keyText : KeyToText(key),
375 実行済み 33 keyValue: key,
376 実行済み 33 keyComparer: comparer);
377 実行済み 33 children.Add(childNode);
378 対象外 }
379 対象外
380 実行済み 26 return children;
381 対象外 }
382 対象外
383 対象外 private static IReadOnlyList<IParallelNode> BuildSequenceChildren(
384 対象外 NodeSlot[] containerSlots,
385 対象外 Type containerType,
386 対象外 Type elementType,
387 対象外 string path,
388 対象外 CompareContext context)
389 対象外 {
390 実行済み 162 var compareKeyMember = TypeMetadataResolver.GetCompareKeyMember(elementType);
391 実行済み 162 if (compareKeyMember is null)
392 対象外 {
393 実行済み 65 if (context.Configuration.MissingCompareKeyListPolicy == MissingCompareKeyListPolicy.AlignByIndex)
394 対象外 {
395 実行済み 64 return BuildIndexAlignedSequenceChildren(containerSlots, containerType, elementType, path, context);
396 対象外 }
397 対象外
398 実行済み 1 context.RecordExecutionError(
399 実行済み 1 CompareIssueCode.CompareKeyNotFoundOnSequenceElement,
400 実行済み 1 path,
401 実行済み 1 null,
402 実行済み 1 null,
403 実行済み 1 $"sequence element type '{elementType.Name}' requires [CompareKey].");
404 実行済み 1 return Array.Empty<IParallelNode>();
405 対象外 }
406 対象外
407 実行済み 97 var containerCategory = GetContainerCategory(containerType);
408 実行済み 97 if (context.IsTraceEnabled)
409 対象外 {
410 実行済み 3 context.Trace(
411 実行済み 3 "container",
412 実行済み 3 path,
413 実行済み 3 ("container", containerCategory),
414 実行済み 3 ("elementType", elementType),
415 実行済み 3 ("compareKey", compareKeyMember.Name));
416 対象外 }
417 対象外
418 実行済み 97 var keyType = compareKeyMember.MemberType;
419 実行済み 97 var comparer = new KeyComparer(keyType, context.Configuration.StringKeyComparison);
420 実行済み 97 var maps = new Dictionary<object, object?>[containerSlots.Length];
421 実行済み 97 var union = new HashSet<object>(comparer);
422 実行済み 97 var keyTexts = new Dictionary<object, string>(comparer);
423 対象外
424 実行済み 572 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
425 対象外 {
426 実行済み 190 maps[modelIndex] = new Dictionary<object, object?>(comparer);
427 実行済み 190 if (containerSlots[modelIndex].State != NodePresenceState.PresentValue || containerSlots[modelIndex].Value is null)
428 対象外 {
429 対象外 continue;
430 対象外 }
431 対象外
432 実行済み 164 var rawContainer = containerSlots[modelIndex].Value;
433 対象外
434 実行済み 164 if (rawContainer is null)
435 対象外 {
436 未実行 0 if (context.IsTraceEnabled)
437 対象外 {
438 未実行 0 context.Trace(
439 未実行 0 "container",
440 未実行 0 path,
441 未実行 0 ("modelIndex", modelIndex),
442 未実行 0 ("container", containerCategory),
443 未実行 0 ("runtimeType", "<null>"),
444 未実行 0 ("materializedCount", 0));
445 対象外 }
446 未実行 0 continue;
447 対象外 }
448 対象外
449 実行済み 164 if (rawContainer is not IEnumerable enumerable)
450 対象外 {
451 未実行 0 context.RecordExecutionError(
452 未実行 0 CompareIssueCode.UnsupportedContainerType,
453 未実行 0 path,
454 未実行 0 modelIndex,
455 未実行 0 null,
456 未実行 0 $"container type '{rawContainer.GetType().Name}' is not supported.");
457 未実行 0 continue;
458 対象外 }
459 対象外
460 実行済み 164 var materialized = enumerable.Cast<object?>().ToList();
461 実行済み 164 if (context.IsTraceEnabled)
462 対象外 {
463 実行済み 6 context.Trace(
464 実行済み 6 "container",
465 実行済み 6 path,
466 実行済み 6 ("modelIndex", modelIndex),
467 実行済み 6 ("container", containerCategory),
468 実行済み 6 ("runtimeType", rawContainer.GetType()),
469 実行済み 6 ("materializedCount", materialized.Count));
470 対象外 }
471 実行済み 755 foreach (var element in materialized)
472 対象外 {
473 実行済み 214 if (element is null)
474 対象外 {
475 実行済み 1 context.RecordExecutionError(
476 実行済み 1 CompareIssueCode.CompareKeyValueIsNull,
477 実行済み 1 path,
478 実行済み 1 modelIndex,
479 実行済み 1 NullKeyText,
480 実行済み 1 "sequence element is null.");
481 実行済み 1 continue;
482 対象外 }
483 対象外
484 対象外 object? key;
485 対象外 try
486 対象外 {
487 実行済み 213 key = compareKeyMember.GetValue(element);
488 実行済み 213 }
489 未実行 0 catch (Exception ex)
490 対象外 {
491 未実行 0 context.RecordExecutionError(
492 未実行 0 CompareIssueCode.ReflectionMetadataBuildFailed,
493 未実行 0 path,
494 未実行 0 modelIndex,
495 未実行 0 null,
496 未実行 0 $"failed to read compare key '{compareKeyMember.Name}': {ex.Message}");
497 未実行 0 continue;
498 対象外 }
499 対象外
500 実行済み 213 if (key is null)
501 対象外 {
502 実行済み 1 context.RecordExecutionError(
503 実行済み 1 CompareIssueCode.CompareKeyValueIsNull,
504 実行済み 1 path,
505 実行済み 1 modelIndex,
506 実行済み 1 NullKeyText,
507 実行済み 1 $"compare key '{compareKeyMember.Name}' is null.");
508 実行済み 1 continue;
509 対象外 }
510 対象外
511 実行済み 212 var normalizedKey = NormalizeKey(key);
512 実行済み 212 UpdateCanonicalKeyText(
513 実行済み 212 keyTexts,
514 実行済み 212 normalizedKey,
515 実行済み 212 KeyToText(key),
516 実行済み 212 keyType,
517 実行済み 212 context.Configuration.StringKeyComparison);
518 実行済み 212 if (maps[modelIndex].ContainsKey(normalizedKey))
519 対象外 {
520 実行済み 2 context.RecordExecutionError(
521 実行済み 2 CompareIssueCode.DuplicateCompareKeyDetected,
522 実行済み 2 path,
523 実行済み 2 modelIndex,
524 実行済み 2 keyTexts[normalizedKey],
525 実行済み 2 $"duplicate compare key '{normalizedKey}'.");
526 実行済み 1 continue;
527 対象外 }
528 対象外
529 実行済み 210 maps[modelIndex][normalizedKey] = element;
530 実行済み 210 union.Add(normalizedKey);
531 対象外 }
532 対象外 }
533 対象外
534 実行済み 238 var orderedKeys = union.OrderBy(key => key, comparer).ToArray();
535 実行済み 96 var children = new List<IParallelNode>(orderedKeys.Length);
536 実行済み 476 foreach (var key in orderedKeys)
537 対象外 {
538 実行済み 142 var slots = new NodeSlot[containerSlots.Length];
539 実行済み 850 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
540 対象外 {
541 実行済み 283 if (!maps[modelIndex].TryGetValue(key, out var value))
542 対象外 {
543 実行済み 74 slots[modelIndex] = NodeSlot.Missing;
544 実行済み 74 continue;
545 対象外 }
546 対象外
547 実行済み 209 slots[modelIndex] = value is null ? NodeSlot.PresentNull : NodeSlot.PresentValue(value);
548 対象外 }
549 対象外
550 実行済み 142 var childNode = BuildNode(
551 実行済み 142 elementType,
552 実行済み 142 slots,
553 実行済み 142 path,
554 実行済み 142 context,
555 実行済み 142 keyText: keyTexts.TryGetValue(key, out var keyText) ? keyText : KeyToText(key),
556 実行済み 142 keyValue: key,
557 実行済み 142 keyComparer: comparer,
558 実行済み 142 comparisonType: ResolveRuntimeComparisonType(elementType, slots));
559 実行済み 142 children.Add(childNode);
560 対象外 }
561 対象外
562 実行済み 96 return children;
563 対象外 }
564 対象外
565 対象外 private static IReadOnlyList<IParallelNode> BuildIndexAlignedSequenceChildren(
566 対象外 NodeSlot[] containerSlots,
567 対象外 Type containerType,
568 対象外 Type elementType,
569 対象外 string path,
570 対象外 CompareContext context)
571 対象外 {
572 実行済み 64 var containerCategory = GetContainerCategory(containerType);
573 実行済み 64 if (context.IsTraceEnabled)
574 対象外 {
575 実行済み 2 context.Trace(
576 実行済み 2 "container",
577 実行済み 2 path,
578 実行済み 2 ("container", containerCategory),
579 実行済み 2 ("elementType", elementType),
580 実行済み 2 ("compareKey", "<index>"));
581 対象外 }
582 対象外
583 実行済み 64 var lists = new List<object?>[containerSlots.Length];
584 実行済み 64 var maxCount = 0;
585 実行済み 384 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
586 対象外 {
587 実行済み 128 lists[modelIndex] = [];
588 実行済み 128 if (containerSlots[modelIndex].State != NodePresenceState.PresentValue || containerSlots[modelIndex].Value is null)
589 対象外 {
590 対象外 continue;
591 対象外 }
592 対象外
593 実行済み 117 var rawContainer = containerSlots[modelIndex].Value!;
594 実行済み 117 if (rawContainer is not IEnumerable enumerable)
595 対象外 {
596 未実行 0 context.RecordExecutionError(
597 未実行 0 CompareIssueCode.UnsupportedContainerType,
598 未実行 0 path,
599 未実行 0 modelIndex,
600 未実行 0 null,
601 未実行 0 $"container type '{rawContainer.GetType().Name}' is not supported.");
602 未実行 0 continue;
603 対象外 }
604 対象外
605 実行済み 117 lists[modelIndex] = enumerable.Cast<object?>().ToList();
606 実行済み 117 maxCount = Math.Max(maxCount, lists[modelIndex].Count);
607 実行済み 117 if (context.IsTraceEnabled)
608 対象外 {
609 実行済み 4 context.Trace(
610 実行済み 4 "container",
611 実行済み 4 path,
612 実行済み 4 ("modelIndex", modelIndex),
613 実行済み 4 ("container", containerCategory),
614 実行済み 4 ("runtimeType", rawContainer.GetType()),
615 実行済み 4 ("materializedCount", lists[modelIndex].Count));
616 対象外 }
617 対象外 }
618 対象外
619 実行済み 64 var children = new List<IParallelNode>(maxCount);
620 実行済み 224 for (var itemIndex = 0; itemIndex < maxCount; itemIndex++)
621 対象外 {
622 実行済み 48 var slots = new NodeSlot[containerSlots.Length];
623 実行済み 288 for (var modelIndex = 0; modelIndex < containerSlots.Length; modelIndex++)
624 対象外 {
625 実行済み 96 if (itemIndex >= lists[modelIndex].Count)
626 対象外 {
627 実行済み 4 slots[modelIndex] = NodeSlot.Missing;
628 実行済み 4 continue;
629 対象外 }
630 対象外
631 実行済み 92 var value = lists[modelIndex][itemIndex];
632 実行済み 92 slots[modelIndex] = value is null ? NodeSlot.PresentNull : NodeSlot.PresentValue(value);
633 対象外 }
634 対象外
635 実行済み 48 children.Add(BuildNode(
636 実行済み 48 elementType,
637 実行済み 48 slots,
638 実行済み 48 path,
639 実行済み 48 context,
640 実行済み 48 keyText: itemIndex.ToString(CultureInfo.InvariantCulture),
641 実行済み 48 keyValue: null,
642 実行済み 48 keyComparer: null,
643 実行済み 48 comparisonType: ResolveRuntimeComparisonType(elementType, slots)));
644 対象外 }
645 対象外
646 実行済み 64 return children;
647 対象外 }
648 対象外
649 対象外 private static Type ResolveRuntimeComparisonType(Type declaredType, IReadOnlyList<NodeSlot> slots)
650 対象外 {
651 実行済み 190 Type? runtimeType = null;
652 実行済み 1136 for (var index = 0; index < slots.Count; index++)
653 対象外 {
654 実行済み 379 var slot = slots[index];
655 実行済み 379 if (slot.State != NodePresenceState.PresentValue || slot.Value is null)
656 対象外 {
657 対象外 continue;
658 対象外 }
659 対象外
660 実行済み 300 var currentType = slot.Value.GetType();
661 実行済み 300 if (!declaredType.IsAssignableFrom(currentType))
662 対象外 {
663 未実行 0 return declaredType;
664 対象外 }
665 対象外
666 実行済み 300 if (runtimeType is null)
667 対象外 {
668 実行済み 190 runtimeType = currentType;
669 実行済み 190 continue;
670 対象外 }
671 対象外
672 実行済み 110 if (runtimeType != currentType)
673 対象外 {
674 実行済み 1 return declaredType;
675 対象外 }
676 対象外 }
677 対象外
678 実行済み 189 return runtimeType ?? declaredType;
679 対象外 }
680 対象外
681 対象外 private static string FormatRuntimeTypes(IReadOnlyList<NodeSlot> slots)
682 対象外 {
683 未実行 0 var runtimeTypes = slots
684 未実行 0 .Where(slot => slot.State == NodePresenceState.PresentValue && slot.Value is not null)
685 未実行 0 .Select(slot => slot.Value!.GetType().ToString())
686 未実行 0 .Distinct(StringComparer.Ordinal)
687 未実行 0 .OrderBy(typeName => typeName, StringComparer.Ordinal)
688 未実行 0 .ToArray();
689 対象外
690 未実行 0 return runtimeTypes.Length == 0 ? "<none>" : string.Join(",", runtimeTypes);
691 対象外 }
692 対象外
693 対象外 private static IEnumerable<(object? Key, object? Value)> EnumerateDictionary(object rawContainer)
694 対象外 {
695 実行済み 51 if (rawContainer is IDictionary dictionary)
696 対象外 {
697 実行済み 215 foreach (DictionaryEntry entry in dictionary)
698 対象外 {
699 実行済み 57 yield return (entry.Key, entry.Value);
700 対象外 }
701 対象外
702 実行済み 50 yield break;
703 対象外 }
704 対象外
705 未実行 0 if (rawContainer is not IEnumerable enumerable)
706 対象外 {
707 未実行 0 yield break;
708 対象外 }
709 対象外
710 未実行 0 foreach (var entry in enumerable)
711 対象外 {
712 未実行 0 if (entry is null)
713 対象外 {
714 対象外 continue;
715 対象外 }
716 対象外
717 未実行 0 var type = entry.GetType();
718 未実行 0 var key = type.GetProperty("Key")?.GetValue(entry);
719 未実行 0 var value = type.GetProperty("Value")?.GetValue(entry);
720 未実行 0 yield return (key, value);
721 対象外 }
722 未実行 0 }
723 対象外
724 対象外 private static bool TryGetDictionaryTypes(Type type, out Type keyType, out Type valueType)
725 対象外 {
726 実行済み 1542 if (TryGetGenericInterface(type, typeof(IDictionary<,>), out var dictionaryType)
727 実行済み 1542 || TryGetGenericInterface(type, typeof(IReadOnlyDictionary<,>), out dictionaryType))
728 対象外 {
729 実行済み 28 var arguments = dictionaryType.GetGenericArguments();
730 実行済み 28 keyType = arguments[0];
731 実行済み 28 valueType = arguments[1];
732 実行済み 28 return true;
733 対象外 }
734 対象外
735 実行済み 1514 keyType = null!;
736 実行済み 1514 valueType = null!;
737 実行済み 1514 return false;
738 対象外 }
739 対象外
740 対象外 private static bool TryGetSequenceElementType(Type type, out Type elementType)
741 対象外 {
742 実行済み 740 if (type == typeof(string))
743 対象外 {
744 実行済み 143 elementType = null!;
745 実行済み 143 return false;
746 対象外 }
747 対象外
748 実行済み 597 if (TryGetDictionaryTypes(type, out _, out _))
749 対象外 {
750 未実行 0 elementType = null!;
751 未実行 0 return false;
752 対象外 }
753 対象外
754 実行済み 597 if (type.IsArray)
755 対象外 {
756 実行済み 1 elementType = type.GetElementType()!;
757 実行済み 1 return true;
758 対象外 }
759 対象外
760 実行済み 596 if (TryGetGenericInterface(type, typeof(IEnumerable<>), out var enumerableType))
761 対象外 {
762 実行済み 169 elementType = enumerableType.GetGenericArguments()[0];
763 実行済み 169 return true;
764 対象外 }
765 対象外
766 実行済み 427 elementType = null!;
767 実行済み 427 return false;
768 対象外 }
769 対象外
770 対象外 private static bool TryGetGenericInterface(Type concreteType, Type openGeneric, out Type match)
771 対象外 {
772 実行済み 3895 if (concreteType.IsGenericType && concreteType.GetGenericTypeDefinition() == openGeneric)
773 対象外 {
774 実行済み 48 match = concreteType;
775 実行済み 48 return true;
776 対象外 }
777 対象外
778 実行済み 3847 match = concreteType.GetInterfaces()
779 実行済み 76936 .FirstOrDefault(type => type.IsGenericType && type.GetGenericTypeDefinition() == openGeneric)!;
780 対象外
781 実行済み 3847 return match is not null;
782 対象外 }
783 対象外
784 対象外 private static string GetContainerCategory(Type type)
785 対象外 {
786 実行済み 178 if (TryGetDictionaryTypes(type, out _, out _))
787 対象外 {
788 実行済み 1 return "Dictionary";
789 対象外 }
790 対象外
791 実行済み 177 if (type.IsArray)
792 対象外 {
793 実行済み 2 return "Array";
794 対象外 }
795 対象外
796 実行済み 175 if (type != typeof(string)
797 実行済み 175 && (TryGetGenericInterface(type, typeof(IList<>), out _)
798 実行済み 175 || TryGetGenericInterface(type, typeof(IReadOnlyList<>), out _)))
799 対象外 {
800 実行済み 142 return "List";
801 対象外 }
802 対象外
803 実行済み 33 if (type != typeof(string) && TryGetGenericInterface(type, typeof(IEnumerable<>), out _))
804 対象外 {
805 実行済み 22 return "IEnumerable";
806 対象外 }
807 対象外
808 実行済み 11 return "ScalarOrObject";
809 対象外 }
810 対象外
811 対象外 private static bool IsScalarType(Type type)
812 対象外 {
813 実行済み 899 var nullableUnderlying = Nullable.GetUnderlyingType(type);
814 実行済み 899 var actual = nullableUnderlying ?? type;
815 対象外
816 実行済み 899 return actual.IsPrimitive
817 実行済み 899 || actual.IsEnum
818 実行済み 899 || actual == typeof(string)
819 実行済み 899 || actual == typeof(decimal)
820 実行済み 899 || actual == typeof(DateTime)
821 実行済み 899 || actual == typeof(DateTimeOffset)
822 実行済み 899 || actual == typeof(Guid)
823 実行済み 899 || actual == typeof(TimeSpan);
824 対象外 }
825 対象外
826 対象外 internal static bool IsContainerType(Type type)
827 対象外 {
828 実行済み 10 return TryGetDictionaryTypes(type, out _, out _)
829 実行済み 10 || TryGetSequenceElementType(type, out _);
830 対象外 }
831 対象外
832 対象外 internal static bool TryBuildDynamicContainerChildren(
833 対象外 Type containerType,
834 対象外 object?[] values,
835 対象外 NodePresenceState[] states,
836 対象外 string path,
837 対象外 CompareConfiguration configuration,
838 対象外 out IReadOnlyList<IParallelNode> children)
839 対象外 {
840 実行済み 4 var slots = new NodeSlot[values.Length];
841 実行済み 24 for (var index = 0; index < values.Length; index++)
842 対象外 {
843 実行済み 8 slots[index] = states[index] switch
844 実行済み 8 {
845 未実行 0 NodePresenceState.Missing => NodeSlot.Missing,
846 未実行 0 NodePresenceState.PresentNull => NodeSlot.PresentNull,
847 実行済み 8 _ => values[index] is object value ? NodeSlot.PresentValue(value) : NodeSlot.PresentNull,
848 実行済み 8 };
849 対象外 }
850 対象外
851 実行済み 4 var context = new CompareContext(configuration);
852 実行済み 4 if (TryGetDictionaryTypes(containerType, out var keyType, out var valueType))
853 対象外 {
854 未実行 0 children = BuildDictionaryChildren(slots, keyType, valueType, path, context);
855 未実行 0 if (context.HasErrors)
856 対象外 {
857 未実行 0 ThrowDynamicContainerAccessError(context.Issues);
858 対象外 }
859 対象外
860 未実行 0 return true;
861 対象外 }
862 対象外
863 実行済み 4 if (TryGetSequenceElementType(containerType, out var elementType))
864 対象外 {
865 実行済み 4 children = BuildSequenceChildren(slots, containerType, elementType, path, context);
866 実行済み 4 if (context.HasErrors)
867 対象外 {
868 未実行 0 ThrowDynamicContainerAccessError(context.Issues);
869 対象外 }
870 対象外
871 実行済み 4 return true;
872 対象外 }
873 対象外
874 未実行 0 children = Array.Empty<IParallelNode>();
875 未実行 0 return false;
876 対象外 }
877 対象外
878 対象外 private static void ThrowDynamicContainerAccessError(IReadOnlyList<CompareIssue> issues)
879 対象外 {
880 未実行 0 var issue = issues.FirstOrDefault(issue => issue.Level == CompareIssueLevel.Error) ?? issues.First();
881 未実行 0 throw new CompareExecutionException(issue.Code, issue.Message);
882 対象外 }
883 対象外
884 対象外 private static object NormalizeKey(object key)
885 対象外 {
886 実行済み 269 return key is DateTime dateTime ? dateTime.ToUniversalTime() : key;
887 対象外 }
888 対象外
889 対象外 private static string KeyToText(object key)
890 対象外 {
891 実行済み 269 return key.ToString() ?? string.Empty;
892 対象外 }
893 対象外
894 対象外 private static void UpdateCanonicalKeyText(
895 対象外 IDictionary<object, string> keyTexts,
896 対象外 object normalizedKey,
897 対象外 string candidate,
898 対象外 Type keyType,
899 対象外 StringComparison stringKeyComparison)
900 対象外 {
901 実行済み 269 if (!keyTexts.TryGetValue(normalizedKey, out var existing))
902 対象外 {
903 実行済み 177 keyTexts[normalizedKey] = candidate;
904 実行済み 177 return;
905 対象外 }
906 対象外
907 実行済み 92 keyTexts[normalizedKey] = SelectCanonicalKeyText(existing, candidate, keyType, stringKeyComparison);
908 実行済み 92 }
909 対象外
910 対象外 private static string SelectCanonicalKeyText(
911 対象外 string existing,
912 対象外 string candidate,
913 対象外 Type keyType,
914 対象外 StringComparison stringKeyComparison)
915 対象外 {
916 実行済み 92 if (keyType == typeof(string) && stringKeyComparison == StringComparison.OrdinalIgnoreCase)
917 対象外 {
918 実行済み 4 return StringComparer.Ordinal.Compare(candidate, existing) < 0 ? candidate : existing;
919 対象外 }
920 対象外
921 実行済み 88 return existing;
922 対象外 }
923 対象外
924 実行済み 13009 private readonly record struct NodeSlot(object? Value, NodePresenceState State)
925 対象外 {
926 実行済み 264 public static NodeSlot Missing => new(null, NodePresenceState.Missing);
927 対象外
928 実行済み 43 public static NodeSlot PresentNull => new(null, NodePresenceState.PresentNull);
929 対象外
930 実行済み 1652 public static NodeSlot PresentValue(object value) => new(value, NodePresenceState.PresentValue);
931 対象外 }
932 対象外
933 対象外 private sealed class CompareContext
934 対象外 {
935 実行済み 116 private readonly List<CompareIssue> _issues = [];
936 対象外
937 実行済み 116 public CompareContext(CompareConfiguration configuration)
938 対象外 {
939 実行済み 116 Configuration = configuration;
940 実行済み 116 }
941 対象外
942 実行済み 3064 public CompareConfiguration Configuration { get; }
943 対象外
944 実行済み 109 public IReadOnlyList<CompareIssue> Issues => _issues;
945 対象外
946 実行済み 112 public bool HasErrors => _issues.Any(issue => issue.Level == CompareIssueLevel.Error);
947 対象外
948 実行済み 2455 public bool IsTraceEnabled => Configuration.TraceLog is not null;
949 対象外
950 対象外 public void RecordInputError(CompareIssueCode code, string path, int? modelIndex, string? keyText, string message)
951 対象外 {
952 実行済み 4 var issue = CreateIssue(code, path, modelIndex, keyText, message);
953 実行済み 4 _issues.Add(issue);
954 実行済み 4 if (IsTraceEnabled)
955 対象外 {
956 未実行 0 Trace("issue", path, ("level", CompareIssueLevel.Error), ("code", code), ("modelIndex", modelIndex), ("keyText", keyText), ("message", message));
957 対象外 }
958 対象外
959 実行済み 4 if (Configuration.StrictMode)
960 対象外 {
961 実行済み 1 throw new CompareInputException(code, message);
962 対象外 }
963 実行済み 3 }
964 対象外
965 対象外 public void RecordExecutionError(CompareIssueCode code, string path, int? modelIndex, string? keyText, string message)
966 対象外 {
967 実行済み 9 var issue = CreateIssue(code, path, modelIndex, keyText, message);
968 実行済み 9 _issues.Add(issue);
969 実行済み 9 if (IsTraceEnabled)
970 対象外 {
971 未実行 0 Trace("issue", path, ("level", CompareIssueLevel.Error), ("code", code), ("modelIndex", modelIndex), ("keyText", keyText), ("message", message));
972 対象外 }
973 対象外
974 実行済み 9 if (Configuration.StrictMode)
975 対象外 {
976 実行済み 2 throw new CompareExecutionException(code, message);
977 対象外 }
978 実行済み 7 }
979 対象外
980 対象外 private static CompareIssue CreateIssue(CompareIssueCode code, string path, int? modelIndex, string? keyText, string message)
981 対象外 {
982 実行済み 13 return new CompareIssue
983 実行済み 13 {
984 実行済み 13 Level = CompareIssueLevel.Error,
985 実行済み 13 Code = code,
986 実行済み 13 Path = path,
987 実行済み 13 ModelIndex = modelIndex,
988 実行済み 13 KeyText = keyText,
989 実行済み 13 Message = message,
990 実行済み 13 };
991 対象外 }
992 対象外
993 対象外 public void Trace(string phase, string path, params (string Key, object? Value)[] fields)
994 対象外 {
995 実行済み 69 if (Configuration.TraceLog is null)
996 対象外 {
997 未実行 0 return;
998 対象外 }
999 対象外
1000 実行済み 69 var parts = new List<string>
1001 実行済み 69 {
1002 実行済み 69 $"phase={FormatTraceValue(phase)}",
1003 実行済み 69 $"path={FormatTraceValue(path)}",
1004 実行済み 69 };
1005 対象外
1006 実行済み 572 foreach (var (key, value) in fields)
1007 対象外 {
1008 実行済み 217 parts.Add($"{key}={FormatTraceValue(value)}");
1009 対象外 }
1010 対象外
1011 実行済み 69 Configuration.TraceLog(string.Join(" ", parts));
1012 実行済み 69 }
1013 対象外
1014 対象外 private static string FormatTraceValue(object? value)
1015 対象外 {
1016 実行済み 355 if (value is null)
1017 対象外 {
1018 実行済み 16 return "<null>";
1019 対象外 }
1020 対象外
1021 実行済み 339 var text = value switch
1022 実行済み 339 {
1023 実行済み 98 Type type => type.ToString(),
1024 実行済み 5 bool flag => flag ? "true" : "false",
1025 未実行 0 Enum enumValue => enumValue.ToString(),
1026 実行済み 236 _ => value.ToString() ?? string.Empty,
1027 実行済み 339 };
1028 対象外
1029 実行済み 339 return text.Any(char.IsWhiteSpace) ? $"\"{text}\"" : text;
1030 対象外 }
1031 対象外 }
1032 対象外
1033 実行済み 124 private sealed class KeyComparer(Type keyType, StringComparison stringKeyComparison) : IEqualityComparer<object>, IComparer<object>
1034 対象外 {
1035 実行済み 124 private readonly Type _keyType = keyType;
1036 実行済み 124 private readonly StringComparison _stringKeyComparison = stringKeyComparison;
1037 対象外
1038 対象外 bool IEqualityComparer<object>.Equals(object? x, object? y)
1039 対象外 {
1040 実行済み 749 if (ReferenceEquals(x, y))
1041 対象外 {
1042 実行済み 441 return true;
1043 対象外 }
1044 対象外
1045 実行済み 308 if (x is null || y is null)
1046 対象外 {
1047 未実行 0 return false;
1048 対象外 }
1049 対象外
1050 実行済み 308 if (_keyType == typeof(string) && x is string sx && y is string sy)
1051 対象外 {
1052 実行済み 53 return string.Equals(sx, sy, _stringKeyComparison);
1053 対象外 }
1054 対象外
1055 実行済み 255 return x.Equals(y);
1056 対象外 }
1057 対象外
1058 対象外 public int GetHashCode(object obj)
1059 対象外 {
1060 実行済み 1653 if (_keyType == typeof(string) && obj is string text)
1061 対象外 {
1062 実行済み 467 return _stringKeyComparison switch
1063 実行済み 467 {
1064 実行済み 52 StringComparison.OrdinalIgnoreCase => StringComparer.OrdinalIgnoreCase.GetHashCode(text),
1065 実行済み 415 _ => StringComparer.Ordinal.GetHashCode(text),
1066 実行済み 467 };
1067 対象外 }
1068 対象外
1069 実行済み 1186 return obj.GetHashCode();
1070 対象外 }
1071 対象外
1072 対象外 public int Compare(object? x, object? y)
1073 対象外 {
1074 実行済み 102 if (ReferenceEquals(x, y))
1075 対象外 {
1076 未実行 0 return 0;
1077 対象外 }
1078 対象外
1079 実行済み 102 if (x is null)
1080 対象外 {
1081 未実行 0 return -1;
1082 対象外 }
1083 対象外
1084 実行済み 102 if (y is null)
1085 対象外 {
1086 未実行 0 return 1;
1087 対象外 }
1088 対象外
1089 実行済み 102 if (_keyType == typeof(string) && x is string sx && y is string sy)
1090 対象外 {
1091 実行済み 22 return _stringKeyComparison switch
1092 実行済み 22 {
1093 実行済み 1 StringComparison.OrdinalIgnoreCase => StringComparer.OrdinalIgnoreCase.Compare(sx, sy),
1094 実行済み 21 _ => StringComparer.Ordinal.Compare(sx, sy),
1095 実行済み 22 };
1096 対象外 }
1097 対象外
1098 実行済み 80 if (x is IComparable comparable && x.GetType() == y.GetType())
1099 対象外 {
1100 実行済み 80 return comparable.CompareTo(y);
1101 対象外 }
1102 対象外
1103 未実行 0 return StringComparer.Ordinal.Compare(x.ToString(), y.ToString());
1104 対象外 }
1105 対象外 }
1106 対象外 }