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

zorbathut / dec / 16188498775

10 Jul 2025 07:08AM UTC coverage: 90.087% (-0.02%) from 90.107%
16188498775

push

github

zorbathut
Added support for RecordAsThis() after a class tag.

5162 of 5730 relevant lines covered (90.09%)

219266.31 hits per line

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

85.57
/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,898✔
11

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

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

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

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

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

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

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

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

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

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

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

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

63
        public WriterNode StartValidation()
64
        {
648✔
65
            return new WriterNodeValidation(this, $"input", new PathRoot("RECORD"));
648✔
66
        }
648✔
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,384✔
73
        {
180,384✔
74
        }
180,384✔
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,062✔
164
        public override bool AllowDecPath { get => false; }
134,958✔
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)
169,842✔
169
        {
169,842✔
170
            this.writer = writer;
169,842✔
171
            this.accessor = accessor;
169,842✔
172
        }
169,842✔
173

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

179
        public override WriterNode CreateReflectionChild(System.Reflection.FieldInfo field, Recorder.Settings settings)
180
        {
118,320✔
181
            if (field.IsPublic)
118,320✔
182
            {
118,296✔
183
                return new WriterNodeValidation(writer, $"{accessor}.{field.Name}", new PathMember(Path, field.Name));
118,296✔
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,320✔
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,630✔
226
            FlagAsNull();
33,630✔
227

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

231
        public override bool WriteReference(object value, Path path)
232
        {
30,528✔
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,528✔
236
            {
150✔
237
                writer.AppendLine($"Assert.AreSame({writer.referenceLookup[value]}, {accessor});");
150✔
238
                return true;
150✔
239
            }
240
            else
241
            {
30,378✔
242
                writer.referenceLookup[value] = accessor;
30,378✔
243
                return false;
30,378✔
244
            }
245
        }
30,528✔
246

247
        public override void WriteRecord(IRecordable value)
248
        {
4,986✔
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)));
9,972✔
253
        }
4,986✔
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 WriteList(IList value)
272
        {
9,390✔
273
            Type referencedType = value.GetType().GetGenericArguments()[0];
9,390✔
274

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

279
            for (int i = 0; i < value.Count; ++i)
62,316✔
280
            {
21,768✔
281
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}[{i}]", new PathIndex(Path, i)), value[i], referencedType);
21,768✔
282
            }
21,768✔
283

284
            writer.AppendLine($"}}");
9,390✔
285
        }
9,390✔
286

287
        public override void WriteDictionary(IDictionary value)
288
        {
4,698✔
289
            Type keyType = value.GetType().GetGenericArguments()[0];
4,698✔
290
            Type valueType = value.GetType().GetGenericArguments()[1];
4,698✔
291

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

294
            IDictionaryEnumerator iterator = value.GetEnumerator();
4,698✔
295
            while (iterator.MoveNext())
14,352✔
296
            {
9,654✔
297
                var keyNode = new WriterNodeStringize(UserSettings, new PathDictionaryKey(Path));
9,654✔
298
                Serialization.ComposeElement(keyNode, iterator.Key, keyType);
9,654✔
299

300
                writer.AppendLine($"if ({accessor}.ContainsKey({keyNode.SerializedString})) {{");
9,654✔
301
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"{accessor}[{keyNode.SerializedString}]", new PathDictionaryValueUnpathable(Path)), iterator.Value, valueType);
9,654✔
302
                writer.AppendLine($"}} else {{");
9,654✔
303
                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✔
304
                writer.AppendLine($"}}");
9,654✔
305
            }
9,654✔
306
        }
4,698✔
307

308
        public override void WriteHashSet(IEnumerable value)
309
        {
240✔
310
            Type keyType = value.GetType().GetGenericArguments()[0];
240✔
311

312
            int count = 0;
240✔
313
            IEnumerator iterator = value.GetEnumerator();
240✔
314
            while (iterator.MoveNext())
1,128✔
315
            {
888✔
316
                var keyNode = new WriterNodeStringize(UserSettings, new PathHashSetElement(Path));
888✔
317
                Serialization.ComposeElement(keyNode, iterator.Current, keyType);
888✔
318

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

322
                ++count;
888✔
323
            }
888✔
324

325
            writer.AppendLine($"Assert.AreEqual({accessor}.Count, {count});");
240✔
326
        }
240✔
327

328
        public override void WriteQueue(IEnumerable value)
329
        {
12✔
330
            Type referencedType = value.GetType().GetGenericArguments()[0];
12✔
331

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

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

338
            // this is just easier
339
            writer.AppendLine($"var tempArray = new {referencedType.ComposeCSFormatted()}[{count}];");
12✔
340
            writer.AppendLine($"{accessor}.CopyTo(tempArray, 0);");
12✔
341

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

345
            for (int i = 0; i < count; ++i)
108✔
346
            {
42✔
347
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"tempArray[{i}]", new PathIndex(Path, i)), array.GetValue(i), referencedType);
42✔
348
            }
42✔
349

350
            writer.AppendLine($"}}");
12✔
351
        }
12✔
352

353
        public override void WriteStack(IEnumerable value)
354
        {
12✔
355
            Type referencedType = value.GetType().GetGenericArguments()[0];
12✔
356

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

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

363
            // this is just easier
364
            writer.AppendLine($"var tempArray = new {referencedType.ComposeCSFormatted()}[{count}];");
12✔
365
            writer.AppendLine($"{accessor}.CopyTo(tempArray, 0);");
12✔
366

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

370
            for (int i = 0; i < count; ++i)
108✔
371
            {
42✔
372
                Serialization.ComposeElement(new WriterNodeValidation(writer, $"tempArray[{i}]", new PathIndex(Path, i)), array.GetValue(i), referencedType);
42✔
373
            }
42✔
374

375
            writer.AppendLine($"}}");
12✔
376
        }
12✔
377

378
        public override void WriteTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
379
        {
78✔
380
            var args = value.GetType().GenericTypeArguments;
78✔
381
            var length = args.Length;
78✔
382

383
            var nameArray = names?.TransformNames ?? UtilMisc.DefaultTupleNames;
78✔
384

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

392
        public override void WriteValueTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
393
        {
138✔
394
            var args = value.GetType().GenericTypeArguments;
138✔
395
            var length = args.Length;
138✔
396

397
            var nameArray = names?.TransformNames ?? UtilMisc.DefaultTupleNames;
138✔
398

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

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

413
        public override void WriteDecPathRef(object value)
414
        {
×
415
            // not really viable honestly
416
            throw new NotImplementedException();
×
417
        }
418
    }
419

420
    // 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.
421
    internal sealed class WriterNodeStringize : WriterNodeCS
422
    {
423
        public override bool AllowReflection { get => false; }
×
424
        public override bool AllowDecPath { get => false; }
10,308✔
425
        public override Recorder.Purpose Intent { get => Recorder.Purpose.Serialization; }
×
426
        public override Recorder.IUserSettings UserSettings { get; }
×
427

428
        public string SerializedString { get; private set; }
50,934✔
429

430
        public WriterNodeStringize(Recorder.IUserSettings userSettings, Path path) : base(path)
10,542✔
431
        {
10,542✔
432
            UserSettings = userSettings;
10,542✔
433
        }
10,542✔
434

435
        public override void WriteToken(string token)
436
        {
10,542✔
437
            if (SerializedString != null)
10,542✔
438
            {
×
439
                Dbg.Err("String is already set!");
×
440
            }
×
441

442
            SerializedString = token;
10,542✔
443
        }
10,542✔
444

445
        public override WriterNode CreateRecorderChild(string label, Recorder.Settings settings)
446
        {
×
447
            throw new NotImplementedException();
×
448
        }
449

450
        public override WriterNode CreateReflectionChild(System.Reflection.FieldInfo field, Recorder.Settings settings)
451
        {
×
452
            throw new NotImplementedException();
×
453
        }
454

455
        public override void TagClass(Type type)
456
        {
×
457
            throw new NotImplementedException();
×
458
        }
459

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

465
        public override bool WriteReference(object value, Path path)
466
        {
×
467
            throw new NotImplementedException();
×
468
        }
469

470
        public override void WriteRecord(IRecordable value)
471
        {
×
472
            throw new NotImplementedException();
×
473
        }
474

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

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

485
        public override void WriteDictionary(IDictionary value)
486
        {
×
487
            throw new NotImplementedException();
×
488
        }
489

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

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

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

505
        public override void WriteTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
506
        {
×
507
            throw new NotImplementedException();
×
508
        }
509

510
        public override void WriteValueTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
511
        {
×
512
            throw new NotImplementedException();
×
513
        }
514

515
        public override void WriteConvertible(Converter converter, object value)
516
        {
×
517
            throw new NotImplementedException();
×
518
        }
519

520
        public override void WriteDecPathRef(object value)
521
        {
×
522
            throw new NotImplementedException();
×
523
        }
524
    }
525
}
526

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