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

zorbathut / dec / 16260897978

14 Jul 2025 07:08AM UTC coverage: 90.121% (+0.03%) from 90.087%
16260897978

push

github

zorbathut
Fix: Thread contention error when running Dec on multiple threads simultaneously.

5218 of 5790 relevant lines covered (90.12%)

218918.62 hits per line

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

85.16
/src/WriterValidation.cs
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Text;
5

6
namespace Dec
7
{
8
    internal abstract class WriterValidation
9
    {
10
        private StringBuilder sb = new StringBuilder();
2,982✔
11

12
        internal Dictionary<object, string> referenceLookup = new Dictionary<object, string>();
2,982✔
13
        private WriterUtil.PendingWriteCoordinator pendingWriteCoordinator = new WriterUtil.PendingWriteCoordinator();
2,982✔
14

15
        public abstract bool AllowReflection { get; }
16
        public Recorder.IUserSettings UserSettings { get; }
10,542✔
17

18
        public WriterValidation(Recorder.IUserSettings userSettings)
2,982✔
19
        {
2,982✔
20
            UserSettings = userSettings;
2,982✔
21
        }
2,982✔
22

23
        public void AppendLine(string line)
24
        {
201,750✔
25
            sb.AppendLine(line);
201,750✔
26
        }
201,750✔
27

28
        public void RegisterPendingWrite(Action action)
29
        {
5,004✔
30
            pendingWriteCoordinator.RegisterPendingWrite(action);
5,004✔
31
        }
5,004✔
32

33
        public string Finish()
34
        {
2,982✔
35
            pendingWriteCoordinator.DequeuePendingWrites();
2,982✔
36

37
            return sb.ToString();
2,982✔
38
        }
2,982✔
39
    }
40

41
    internal class WriterValidationCompose : WriterValidation
42
    {
43
        public override bool AllowReflection { get => true; }
22,110✔
44

45
        public WriterValidationCompose(Recorder.IUserSettings userSettings) : base(userSettings)
2,298✔
46
        {
2,298✔
47
        }
2,298✔
48

49
        public WriterNode StartDec(Type type, string decName)
50
        {
8,736✔
51
            return new WriterNodeValidation(this, $"Dec.Database<{type.ComposeCSFormatted()}>.Get(\"{decName}\")", new PathDec(type, decName));
8,736✔
52
        }
8,736✔
53
    }
54

55
    internal class WriterValidationRecord : WriterValidation
56
    {
57
        public override bool AllowReflection { get => false; }
×
58

59
        public WriterValidationRecord(Recorder.IUserSettings userSettings) : base(userSettings)
684✔
60
        {
684✔
61
        }
684✔
62

63
        public WriterNode StartValidation()
64
        {
684✔
65
            return new WriterNodeValidation(this, $"input", new PathRoot("RECORD"));
684✔
66
        }
684✔
67
    }
68

69
    // This is used for things that can be expressed as an easy inline string, which is used as part of the Dictionary-handling code.
70
    internal abstract class WriterNodeCS : WriterNode
71
    {
72
        public WriterNodeCS(Path path) : base(new Recorder.Settings(), path)
180,600✔
73
        {
180,600✔
74
        }
180,600✔
75

76
        public abstract void WriteToken(string token);
77

78
        public override void WritePrimitive(object value)
79
        {
92,322✔
80
            if (value.GetType() == typeof(bool))
92,322✔
81
            {
13,488✔
82
                WriteToken(value.ToString().ToLower());
13,488✔
83
            }
13,488✔
84
            else if (value.GetType() == typeof(float))
78,834✔
85
            {
10,290✔
86
                var val = (float)value;
10,290✔
87
                if (float.IsNaN(val))
10,290✔
88
                {
18✔
89
                    WriteToken("float.NaN");
18✔
90
                }
18✔
91
                else if (float.IsPositiveInfinity(val))
10,272✔
92
                {
18✔
93
                    WriteToken("float.PositiveInfinity");
18✔
94
                }
18✔
95
                else if (float.IsNegativeInfinity(val))
10,254✔
96
                {
18✔
97
                    WriteToken("float.NegativeInfinity");
18✔
98
                }
18✔
99
                else
100
                {
10,236✔
101
                    WriteToken(((float)value).ToString("G17") + 'f');
10,236✔
102
                }
10,236✔
103
            }
10,290✔
104
            else if (value.GetType() == typeof(double))
68,544✔
105
            {
1,086✔
106
                var val = (double)value;
1,086✔
107
                if (double.IsNaN(val))
1,086✔
108
                {
822✔
109
                    WriteToken("double.NaN");
822✔
110
                }
822✔
111
                else if (double.IsPositiveInfinity(val))
264✔
112
                {
18✔
113
                    WriteToken("double.PositiveInfinity");
18✔
114
                }
18✔
115
                else if (double.IsNegativeInfinity(val))
246✔
116
                {
18✔
117
                    WriteToken("double.NegativeInfinity");
18✔
118
                }
18✔
119
                else
120
                {
228✔
121
                    WriteToken(((double)value).ToString("G17") + 'd');
228✔
122
                }
228✔
123
            }
1,086✔
124
            else
125
            {
67,458✔
126
                WriteToken(value.ToString());
67,458✔
127
            }
67,458✔
128
        }
92,322✔
129

130
        public override void WriteEnum(object value)
131
        {
78✔
132
            WriteToken($"{value.GetType().ComposeCSFormatted()}.{value}");
78✔
133
        }
78✔
134

135
        public override void WriteString(string value)
136
        {
10,842✔
137
            WriteToken($"\"{value}\"");
10,842✔
138
        }
10,842✔
139

140
        public override void WriteType(Type value)
141
        {
78✔
142
            WriteToken($"typeof({value.ComposeCSFormatted()})");
78✔
143
        }
78✔
144

145
        public override void WriteDec(Dec value)
146
        {
15,024✔
147
            if (value != null)
15,024✔
148
            {
1,494✔
149
                WriteToken($"Dec.Database<{value.GetType().ComposeCSFormatted()}>.Get(\"{value.DecName}\")");
1,494✔
150
            }
1,494✔
151
            else
152
            {
13,530✔
153
                WriteExplicitNull();
13,530✔
154
            }
13,530✔
155
        }
15,024✔
156
    }
157

158
    internal sealed class WriterNodeValidation : WriterNodeCS
159
    {
160
        private WriterValidation writer;
161
        private string accessor;
162

163
        public override bool AllowReflection { get => writer.AllowReflection; }
22,110✔
164
        public override bool AllowDecPath { get => false; }
135,162✔
165
        public override Recorder.Purpose Intent { get => Recorder.Purpose.Serialization; }  // I mean, sorta
×
166
        public override Recorder.IUserSettings UserSettings { get => writer.UserSettings; }
10,542✔
167

168
        public WriterNodeValidation(WriterValidation writer, string accessor, Path path) : base(path)
170,058✔
169
        {
170,058✔
170
            this.writer = writer;
170,058✔
171
            this.accessor = accessor;
170,058✔
172
        }
170,058✔
173

174
        public override WriterNode CreateRecorderChild(string label, Recorder.Settings settings)
175
        {
9,306✔
176
            return new WriterNodeValidation(writer, $"{accessor}.{label}", new PathMember(Path, label));
9,306✔
177
        }
9,306✔
178

179
        public override WriterNode CreateReflectionChild(System.Reflection.FieldInfo field, Recorder.Settings settings)
180
        {
118,416✔
181
            if (field.IsPublic)
118,416✔
182
            {
118,392✔
183
                return new WriterNodeValidation(writer, $"{accessor}.{field.Name}", new PathMember(Path, field.Name));
118,392✔
184
            }
185
            else
186
            {
24✔
187
                return new WriterNodeValidation(writer, $"(({field.FieldType.ComposeCSFormatted()})typeof({field.DeclaringType.ComposeCSFormatted()}).GetField(\"{field.Name}\", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue({accessor}))", new PathMember(Path, field.Name));
24✔
188
            }
189
        }
118,416✔
190

191
        private void WriteIsEqual(string value)
192
        {
94,272✔
193
            writer.AppendLine($"Assert.AreEqual({value}, {accessor});");
94,272✔
194
        }
94,272✔
195

196
        public override void WriteToken(string token)
197
        {
94,272✔
198
            WriteIsEqual(token);
94,272✔
199
        }
94,272✔
200

201
        public override void TagClass(Type type)
202
        {
276✔
203
            FlagAsClass();
276✔
204

205
            if (type == typeof(Type))
276✔
206
            {
6✔
207
                // Special case: Sometimes we convert System.RuntimeType to System.Type, because System.RuntimeType is a compatible C# implementation detail that we don't want to preserve.
208
                // We handle that conversion here as well.
209
                writer.AppendLine($"Assert.IsTrue({accessor} is System.Type);");
6✔
210
            }
6✔
211
            else if (typeof(Dec).IsAssignableFrom(type))
270✔
212
            {
12✔
213
                // Decs are a little weird; sometimes we specify the root of a dec hierarchy with full expectation that it'll grab a child in that hierarchy.
214
                writer.AppendLine($"Assert.IsTrue(typeof({type.ComposeCSFormatted()}).IsAssignableFrom({accessor}.GetType()));");
12✔
215
            }
12✔
216
            else
217
            {
258✔
218
                writer.AppendLine($"Assert.AreEqual(typeof({type.ComposeCSFormatted()}), {accessor}.GetType());");
258✔
219
            }
258✔
220

221
            accessor = $"(({type.ComposeCSFormatted()}){accessor})";
276✔
222
        }
276✔
223

224
        public override void WriteExplicitNull()
225
        {
33,642✔
226
            FlagAsNull();
33,642✔
227

228
            writer.AppendLine($"Assert.IsNull({accessor});");
33,642✔
229
        }
33,642✔
230

231
        public override bool WriteReference(object value, Path path)
232
        {
30,684✔
233
            // We're just going to ignore whether a reference is "allowed" here; this is not the place for metavalidation.
234

235
            if (writer.referenceLookup.ContainsKey(value))
30,684✔
236
            {
168✔
237
                writer.AppendLine($"Assert.AreSame({writer.referenceLookup[value]}, {accessor});");
168✔
238
                return true;
168✔
239
            }
240
            else
241
            {
30,516✔
242
                writer.referenceLookup[value] = accessor;
30,516✔
243
                return false;
30,516✔
244
            }
245
        }
30,684✔
246

247
        public override void WriteRecord(IRecordable value)
248
        {
5,004✔
249
            // For performance's sake we shouldn't always be doing this.
250
            // But the validation layer is already incredibly slow and none of this is relevant in terms of performance anyway.
251
            // So, whatever.
252
            writer.RegisterPendingWrite(() => value.Record(new RecorderWriter(this)));
10,008✔
253
        }
5,004✔
254

255
        public override void WriteArray(Array value)
256
        {
180✔
257
            Type referencedType = value.GetType().GetElementType();
180✔
258

259
            // Maybe this should just be a giant AreEqual with a dynamically allocated array?
260
            writer.AppendLine($"Assert.AreEqual({accessor}.Length, {value.Length});");
180✔
261
            writer.AppendLine($"if ({accessor}.Length == {value.Length}) {{");
180✔
262

263
            for (int i = 0; i < value.Length; ++i)
1,956✔
264
            {
798✔
265
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}[{i}]", new PathIndex(Path, i)), value.GetValue(i), referencedType);
798✔
266
            }
798✔
267

268
            writer.AppendLine($"}}");
180✔
269
        }
180✔
270

271
        public override void WriteByteArray(byte[] value)
272
        {
120✔
273
            writer.AppendLine($"Assert.AreEqual(new byte[] {{ {string.Join(", ", value)} }}, {accessor});");
120✔
274
        }
120✔
275

276
        public override void WriteList(IList value)
277
        {
9,390✔
278
            Type referencedType = value.GetType().GetGenericArguments()[0];
9,390✔
279

280
            // Maybe this should just be a giant AreEqual with a dynamically allocated list?
281
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {value.Count});");
9,390✔
282
            writer.AppendLine($"if ({accessor}.Count == {value.Count}) {{");
9,390✔
283

284
            for (int i = 0; i < value.Count; ++i)
62,316✔
285
            {
21,768✔
286
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}[{i}]", new PathIndex(Path, i)), value[i], referencedType);
21,768✔
287
            }
21,768✔
288

289
            writer.AppendLine($"}}");
9,390✔
290
        }
9,390✔
291

292
        public override void WriteDictionary(IDictionary value)
293
        {
4,698✔
294
            Type keyType = value.GetType().GetGenericArguments()[0];
4,698✔
295
            Type valueType = value.GetType().GetGenericArguments()[1];
4,698✔
296

297
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {value.Count});");
4,698✔
298

299
            IDictionaryEnumerator iterator = value.GetEnumerator();
4,698✔
300
            while (iterator.MoveNext())
14,352✔
301
            {
9,654✔
302
                var keyNode = new WriterNodeStringize(UserSettings, new PathDictionaryKey(Path));
9,654✔
303
                Serialization.ComposeElement(keyNode, iterator.Key, keyType);
9,654✔
304

305
                writer.AppendLine($"if ({accessor}.ContainsKey({keyNode.SerializedString})) {{");
9,654✔
306
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}[{keyNode.SerializedString}]", new PathDictionaryValueUnpathable(Path)), iterator.Value, valueType);
9,654✔
307
                writer.AppendLine($"}} else {{");
9,654✔
308
                writer.AppendLine($"Assert.IsTrue({accessor}.ContainsKey({keyNode.SerializedString}));");   // this is unnecessary - it could just be .Fail() - but this gives you a *much* better error message
9,654✔
309
                writer.AppendLine($"}}");
9,654✔
310
            }
9,654✔
311
        }
4,698✔
312

313
        public override void WriteHashSet(IEnumerable value)
314
        {
240✔
315
            Type keyType = value.GetType().GetGenericArguments()[0];
240✔
316

317
            int count = 0;
240✔
318
            IEnumerator iterator = value.GetEnumerator();
240✔
319
            while (iterator.MoveNext())
1,128✔
320
            {
888✔
321
                var keyNode = new WriterNodeStringize(UserSettings, new PathHashSetElement(Path));
888✔
322
                Serialization.ComposeElement(keyNode, iterator.Current, keyType);
888✔
323

324
                // You might think "Assert.Contains" would do what we want, but it doesn't - it requires an ICollection and HashSet isn't an ICollection.
325
                writer.AppendLine($"Assert.IsTrue({accessor}.Contains({keyNode.SerializedString}));");
888✔
326

327
                ++count;
888✔
328
            }
888✔
329

330
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {count});");
240✔
331
        }
240✔
332

333
        public override void WriteQueue(IEnumerable value)
334
        {
12✔
335
            Type referencedType = value.GetType().GetGenericArguments()[0];
12✔
336

337
            var count = (int)value.GetType().GetProperty("Count").GetValue(value);
12✔
338

339
            // Maybe this should just be a giant AreEqual with a dynamically allocated list?
340
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {count});");
12✔
341
            writer.AppendLine($"if ({accessor}.Count == {count}) {{");
12✔
342

343
            // this is just easier
344
            writer.AppendLine($"var tempArray = new {referencedType.ComposeCSFormatted()}[{count}];");
12✔
345
            writer.AppendLine($"{accessor}.CopyTo(tempArray, 0);");
12✔
346

347
            // and meanwhile . . .
348
            var array = value.GetType().GetMethod("ToArray").Invoke(value, new object[] { }) as Array;
12✔
349

350
            for (int i = 0; i < count; ++i)
108✔
351
            {
42✔
352
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"tempArray[{i}]", new PathIndex(Path, i)), array.GetValue(i), referencedType);
42✔
353
            }
42✔
354

355
            writer.AppendLine($"}}");
12✔
356
        }
12✔
357

358
        public override void WriteStack(IEnumerable value)
359
        {
12✔
360
            Type referencedType = value.GetType().GetGenericArguments()[0];
12✔
361

362
            var count = (int)value.GetType().GetProperty("Count").GetValue(value);
12✔
363

364
            // Maybe this should just be a giant AreEqual with a dynamically allocated list?
365
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {count});");
12✔
366
            writer.AppendLine($"if ({accessor}.Count == {count}) {{");
12✔
367

368
            // this is just easier
369
            writer.AppendLine($"var tempArray = new {referencedType.ComposeCSFormatted()}[{count}];");
12✔
370
            writer.AppendLine($"{accessor}.CopyTo(tempArray, 0);");
12✔
371

372
            // and meanwhile . . .
373
            var array = value.GetType().GetMethod("ToArray").Invoke(value, new object[] { }) as Array;
12✔
374

375
            for (int i = 0; i < count; ++i)
108✔
376
            {
42✔
377
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"tempArray[{i}]", new PathIndex(Path, i)), array.GetValue(i), referencedType);
42✔
378
            }
42✔
379

380
            writer.AppendLine($"}}");
12✔
381
        }
12✔
382

383
        public override void WriteTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
384
        {
78✔
385
            var args = value.GetType().GenericTypeArguments;
78✔
386
            var length = args.Length;
78✔
387

388
            var nameArray = names?.TransformNames ?? UtilMisc.DefaultTupleNames;
78✔
389

390
            for (int i = 0; i < length; ++i)
648✔
391
            {
246✔
392
                var propertyName = nameArray[i];
246✔
393
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}.{propertyName}", new PathIndex(Path, i)), value.GetType().GetProperty(UtilMisc.DefaultTupleNames[i]).GetValue(value), args[i]);
246✔
394
            }
246✔
395
        }
78✔
396

397
        public override void WriteValueTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
398
        {
138✔
399
            var args = value.GetType().GenericTypeArguments;
138✔
400
            var length = args.Length;
138✔
401

402
            var nameArray = names?.TransformNames ?? UtilMisc.DefaultTupleNames;
138✔
403

404
            for (int i = 0; i < length; ++i)
1,008✔
405
            {
366✔
406
                var propertyName = nameArray[i];
366✔
407
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}.{propertyName}", new PathIndex(Path, i)), value.GetType().GetField(UtilMisc.DefaultTupleNames[i]).GetValue(value), args[i]);
366✔
408
            }
366✔
409
        }
138✔
410

411
        public override void WriteConvertible(Converter converter, object value)
412
        {
×
413
            // this isn't really a thing I can implement because the entire point of this is to compare the output to known values
414
            // and if we're going through Converter, we don't know what the underlying known values will be
415
            throw new NotImplementedException();
×
416
        }
417

418
        public override void WriteDecPathRef(object value)
419
        {
×
420
            // not really viable honestly
421
            throw new NotImplementedException();
×
422
        }
423
    }
424

425
    // This is used solely for dict keys, because we want to get a reasonably-stringized version of this without having to jump through hideous hoops.
426
    internal sealed class WriterNodeStringize : WriterNodeCS
427
    {
428
        public override bool AllowReflection { get => false; }
×
429
        public override bool AllowDecPath { get => false; }
10,308✔
430
        public override Recorder.Purpose Intent { get => Recorder.Purpose.Serialization; }
×
431
        public override Recorder.IUserSettings UserSettings { get; }
×
432

433
        public string SerializedString { get; private set; }
50,934✔
434

435
        public WriterNodeStringize(Recorder.IUserSettings userSettings, Path path) : base(path)
10,542✔
436
        {
10,542✔
437
            UserSettings = userSettings;
10,542✔
438
        }
10,542✔
439

440
        public override void WriteToken(string token)
441
        {
10,542✔
442
            if (SerializedString != null)
10,542✔
443
            {
×
444
                Dbg.Err("String is already set!");
×
445
            }
×
446

447
            SerializedString = token;
10,542✔
448
        }
10,542✔
449

450
        public override WriterNode CreateRecorderChild(string label, Recorder.Settings settings)
451
        {
×
452
            throw new NotImplementedException();
×
453
        }
454

455
        public override WriterNode CreateReflectionChild(System.Reflection.FieldInfo field, Recorder.Settings settings)
456
        {
×
457
            throw new NotImplementedException();
×
458
        }
459

460
        public override void TagClass(Type type)
461
        {
×
462
            throw new NotImplementedException();
×
463
        }
464

465
        public override void WriteExplicitNull()
466
        {
×
467
            throw new NotImplementedException();
×
468
        }
469

470
        public override bool WriteReference(object value, Path path)
471
        {
×
472
            throw new NotImplementedException();
×
473
        }
474

475
        public override void WriteRecord(IRecordable value)
476
        {
×
477
            throw new NotImplementedException();
×
478
        }
479

480
        public override void WriteArray(Array value)
481
        {
×
482
            throw new NotImplementedException();
×
483
        }
484

485
        public override void WriteByteArray(byte[] value)
486
        {
×
487
            throw new NotImplementedException();
×
488
        }
489

490
        public override void WriteList(IList value)
491
        {
×
492
            throw new NotImplementedException();
×
493
        }
494

495
        public override void WriteDictionary(IDictionary value)
496
        {
×
497
            throw new NotImplementedException();
×
498
        }
499

500
        public override void WriteHashSet(IEnumerable value)
501
        {
×
502
            throw new NotImplementedException();
×
503
        }
504

505
        public override void WriteStack(IEnumerable value)
506
        {
×
507
            throw new NotImplementedException();
×
508
        }
509

510
        public override void WriteQueue(IEnumerable value)
511
        {
×
512
            throw new NotImplementedException();
×
513
        }
514

515
        public override void WriteTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
516
        {
×
517
            throw new NotImplementedException();
×
518
        }
519

520
        public override void WriteValueTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
521
        {
×
522
            throw new NotImplementedException();
×
523
        }
524

525
        public override void WriteConvertible(Converter converter, object value)
526
        {
×
527
            throw new NotImplementedException();
×
528
        }
529

530
        public override void WriteDecPathRef(object value)
531
        {
×
532
            throw new NotImplementedException();
×
533
        }
534
    }
535
}
536

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