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

zorbathut / dec / 11691562073

05 Nov 2024 07:56PM UTC coverage: 90.637% (+0.2%) from 90.487%
11691562073

push

github

zorbathut
Rename StartData to more descriptive names.

4656 of 5137 relevant lines covered (90.64%)

191886.43 hits per line

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

93.94
/src/WriterXml.cs
1
using System;
2
using System.Collections;
3
using System.Linq;
4
using System.Xml.Linq;
5

6
namespace Dec
7
{
8
    internal abstract class WriterXml
9
    {
10
        // A list of writes that still have to happen. This is used so we don't have to do deep recursive dives and potentially blow our stack.
11
        // I think this is only used for WriterXmlRecord, but right now this all goes through WriterNodeXml which is meant to work with both of these.
12
        // The inheritance tree is kind of messed up right now and should be fixed.
13
        private WriterUtil.PendingWriteCoordinator pendingWriteCoordinator = new WriterUtil.PendingWriteCoordinator();
6,850✔
14

15
        public abstract bool AllowReflection { get; }
16
        public abstract Recorder.IUserSettings UserSettings { get; }
17

18
        public abstract bool RegisterReference(object referenced, XElement element, Recorder.Settings recSettings);
19

20
        public void RegisterPendingWrite(Action action)
21
        {
2,990✔
22
            pendingWriteCoordinator.RegisterPendingWrite(action);
2,990✔
23
        }
2,990✔
24

25
        public void DequeuePendingWrites()
26
        {
6,850✔
27
            pendingWriteCoordinator.DequeuePendingWrites();
6,850✔
28
        }
6,850✔
29
    }
30

31
    internal sealed class WriterNodeXml : WriterNode
32
    {
33
        private WriterXml writer;
34
        private XElement node;
35

36
        // Represents only the *active* depth in the program stack.
37
        // This is kind of painfully hacky, because when it's created, we don't know if it's going to represent a new stack start.
38
        // So we just kinda adjust it as we go.
39
        private int depth;
40
        private const int MaxRecursionDepth = 100;
41

42
        public override bool AllowReflection { get => writer.AllowReflection; }
37,136✔
43
        public override Recorder.IUserSettings UserSettings { get => writer.UserSettings; }
40✔
44

45
        private WriterNodeXml(WriterXml writer, XContainer parent, string label, int depth, Recorder.Settings settings, Path path) : base(settings, path)
1,083,999✔
46
        {
1,083,999✔
47
            this.writer = writer;
1,083,999✔
48
            this.depth = depth;
1,083,999✔
49

50
            node = new XElement(label);
1,083,999✔
51
            parent.Add(node);
1,083,999✔
52
        }
1,083,999✔
53

54
        public static WriterNodeXml StartDec(WriterXmlCompose writer, XContainer decRoot, string type, string decName)
55
        {
14,770✔
56
            var node = new WriterNodeXml(writer, decRoot, type, 0, new Recorder.Settings(), new PathDec(type, decName));
14,770✔
57
            node.GetXElement().Add(new XAttribute("decName", decName));
14,770✔
58
            return node;
14,770✔
59
        }
14,770✔
60

61
        public static WriterNodeXml StartRecord(WriterXml writer, XContainer decRoot, string name, Type type, string pathId)
62
        {
2,815✔
63
            return new WriterNodeXml(writer, decRoot, name, 0, new Recorder.Settings() { shared = Recorder.Settings.Shared.Flexible }, new PathRoot(pathId));
2,815✔
64
        }
2,815✔
65

66
        internal WriterNodeXml CreateNamedChild(string label, Recorder.Settings settings, Path newPath)
67
        {
96,935✔
68
            return new WriterNodeXml(writer, node, label, depth + 1, settings, newPath);
96,935✔
69
        }
96,935✔
70

71
        // this should be WriterNodeXml but this C# doesn't support that
72
        public override WriterNode CreateRecorderChild(string label, Recorder.Settings settings)
73
        {
771,375✔
74
            return new WriterNodeXml(writer, node, label, depth + 1, settings, new PathMember(Path, label));
771,375✔
75
        }
771,375✔
76

77
        // this should be WriterNodeXml but this C# doesn't support that
78
        public override WriterNode CreateReflectionChild(System.Reflection.FieldInfo field, Recorder.Settings settings)
79
        {
198,104✔
80
            return new WriterNodeXml(writer, node, field.Name, depth + 1, settings, new PathMember(Path, field.Name));
198,104✔
81
        }
198,104✔
82

83
        public override void WritePrimitive(object value)
84
        {
168,936✔
85
            if (value.GetType() == typeof(double))
168,936✔
86
            {
1,900✔
87
                double val = (double)value;
1,900✔
88
                if (double.IsNaN(val) && BitConverter.DoubleToInt64Bits(val) != BitConverter.DoubleToInt64Bits(double.NaN))
1,900✔
89
                {
75✔
90
                    // oops, all nan boxing!
91
                    node.Add(new XText("NaNbox" + BitConverter.DoubleToInt64Bits(val).ToString("X16")));
75✔
92
                }
75✔
93
                else if (Compat.FloatRoundtripBroken)
1,825✔
94
                {
365✔
95
                    node.Add(new XText(val.ToString("G17")));
365✔
96
                }
365✔
97
                else
98
                {
1,460✔
99
                    node.Add(new XText(val.ToString()));
1,460✔
100
                }
1,460✔
101
            }
1,900✔
102
            else if (value.GetType() == typeof(float))
167,036✔
103
            {
17,325✔
104
                float val = (float)value;
17,325✔
105
                if (float.IsNaN(val) && BitConverter.SingleToInt32Bits(val) != BitConverter.SingleToInt32Bits(float.NaN))
17,325✔
106
                {
85✔
107
                    // oops, all nan boxing!
108
                    node.Add(new XText("NaNbox" + BitConverter.SingleToInt32Bits(val).ToString("X8")));
85✔
109
                }
85✔
110
                else if (Compat.FloatRoundtripBroken)
17,240✔
111
                {
3,448✔
112
                    node.Add(new XText(val.ToString("G9")));
3,448✔
113
                }
3,448✔
114
                else
115
                {
13,792✔
116
                    node.Add(new XText(val.ToString()));
13,792✔
117
                }
13,792✔
118
            }
17,325✔
119
            else
120
            {
149,711✔
121
                node.Add(new XText(value.ToString()));
149,711✔
122
            }
149,711✔
123
        }
168,936✔
124

125
        public override void WriteEnum(object value)
126
        {
220✔
127
            node.Add(new XText(value.ToString()));
220✔
128
        }
220✔
129

130
        public override void WriteString(string value)
131
        {
18,775✔
132
            node.Add(new XText(value));
18,775✔
133
        }
18,775✔
134

135
        public override void WriteType(Type value)
136
        {
175✔
137
            node.Add(new XText(value.ComposeDecFormatted()));
175✔
138
        }
175✔
139

140
        public override void WriteDec(Dec value)
141
        {
25,255✔
142
            // Get the dec name and be done with it.
143
            if (value == null)
25,255✔
144
            {
22,655✔
145
                // "No data" is defined as null for decs, so we just do that
146
            }
22,655✔
147
            else if (value.DecName == "" || value.DecName == null)
2,600✔
148
            {
×
149
                Dbg.Err($"Attempted to write a Dec that was dynamically created but never registered; this will be left as a null reference. In most cases you shouldn't be dynamically creating Decs anyway, this is likely a malfunctioning deep copy such as a misbehaving ICloneable");
×
150
            }
×
151
            else if (value != Database.Get(value.GetType(), value.DecName))
2,600✔
152
            {
60✔
153
                Dbg.Err($"Referenced dec `{value}` does not exist in the database; serializing an error value instead");
60✔
154
                node.Add(new XText($"{value.DecName}_DELETED"));
60✔
155

156
                // if you actually have a dec named SomePreviouslyExistingDec_DELETED then you need to sort out what you're doing with your life
157
            }
60✔
158
            else
159
            {
2,540✔
160
                node.Add(new XText(value.DecName));
2,540✔
161
            }
2,540✔
162
        }
25,255✔
163

164
        public override void TagClass(Type type)
165
        {
871✔
166
            // I guess we just keep going? what's likely to be less damaging here? this may at least be manually reconstructible I suppose?
167
            FlagAsClass();
871✔
168

169
            node.Add(new XAttribute("class", type.ComposeDecFormatted()));
871✔
170
        }
871✔
171

172
        public override void WriteExplicitNull()
173
        {
249,677✔
174
            node.SetAttributeValue("null", "true");
249,677✔
175
        }
249,677✔
176

177
        public override bool WriteReference(object value)
178
        {
585,186✔
179
            return writer.RegisterReference(value, node, RecorderSettings);
585,186✔
180
        }
585,186✔
181

182
        private void WriteArrayRank(WriterNodeXml node, Array value, Type referencedType, int rank, int[] indices)
183
        {
930✔
184
            if (rank == value.Rank)
930✔
185
            {
550✔
186
                Serialization.ComposeElement(node, value.GetValue(indices), referencedType);
550✔
187
            }
550✔
188
            else
189
            {
380✔
190
                for (int i = 0; i < value.GetLength(rank); ++i)
2,500✔
191
                {
870✔
192
                    indices[rank] = i;
870✔
193
                    var child = node.CreateNamedChild("li", RecorderSettings.CreateChild(), new PathIndexMultidim(Path, indices.ToArray()));
870✔
194

195
                    WriteArrayRank(child, value, referencedType, rank + 1, indices);
870✔
196
                }
870✔
197
            }
380✔
198
        }
930✔
199

200
        public override void WriteArray(Array value)
201
        {
2,110✔
202
            Type referencedType = value.GetType().GetElementType();
2,110✔
203

204
            if (value.Rank == 1)
2,110✔
205
            {
2,050✔
206
                // fast path
207
                for (int i = 0; i < value.Length; ++i)
14,850✔
208
                {
5,375✔
209
                    Serialization.ComposeElement(CreateNamedChild("li", RecorderSettings.CreateChild(), new PathIndex(Path, i)), value.GetValue(i), referencedType);
5,375✔
210
                }
5,375✔
211

212
                return;
2,050✔
213
            }
214
            else
215
            {
60✔
216
                // slow path
217
                int[] indices = new int[value.Rank];
60✔
218
                WriteArrayRank(this, value, referencedType, 0, indices);
60✔
219
            }
60✔
220
        }
2,110✔
221

222
        public override void WriteList(IList value)
223
        {
17,905✔
224
            Type referencedType = value.GetType().GetGenericArguments()[0];
17,905✔
225

226
            for (int i = 0; i < value.Count; ++i)
114,770✔
227
            {
39,480✔
228
                Serialization.ComposeElement(CreateNamedChild("li", RecorderSettings.CreateChild(), new PathIndex(Path, i)), value[i], referencedType);
39,480✔
229
            }
39,480✔
230
        }
17,905✔
231

232
        public override void WriteDictionary(IDictionary value)
233
        {
7,885✔
234
            Type keyType = value.GetType().GetGenericArguments()[0];
7,885✔
235
            Type valueType = value.GetType().GetGenericArguments()[1];
7,885✔
236

237
            // I really want some way to canonicalize this ordering
238
            IDictionaryEnumerator iterator = value.GetEnumerator();
7,885✔
239
            while (iterator.MoveNext())
24,085✔
240
            {
16,200✔
241
                // In theory, some dicts support inline format, not li format. Inline format is cleaner and smaller and we should be using it when possible.
242
                // In practice, it's hard and I'm lazy and this always works, and we're not providing any guarantees about cleanliness of serialized output.
243
                // Revisit this later when someone (possibly myself) really wants it improved.
244
                var li = CreateNamedChild("li", RecorderSettings, Path);
16,200✔
245

246
                Serialization.ComposeElement(li.CreateNamedChild("key", RecorderSettings.CreateChild(), new PathDictionaryKey(Path)), iterator.Key, keyType);
16,200✔
247
                Serialization.ComposeElement(li.CreateNamedChild("value", RecorderSettings.CreateChild(), new PathDictionaryValue(Path)), iterator.Value, valueType);
16,200✔
248
            }
16,200✔
249
        }
7,885✔
250

251
        public override void WriteHashSet(IEnumerable value)
252
        {
395✔
253
            Type keyType = value.GetType().GetGenericArguments()[0];
395✔
254

255
            // I really want some way to canonicalize this ordering
256
            IEnumerator iterator = value.GetEnumerator();
395✔
257
            while (iterator.MoveNext())
1,885✔
258
            {
1,490✔
259
                // In theory, some sets support inline format, not li format. Inline format is cleaner and smaller and we should be using it when possible.
260
                // In practice, it's hard and I'm lazy and this always works, and we're not providing any guarantees about cleanliness of serialized output.
261
                // Revisit this later when someone (possibly myself) really wants it improved.
262
                Serialization.ComposeElement(CreateNamedChild("li", RecorderSettings.CreateChild(), new PathHashSetElement(Path)), iterator.Current, keyType);
1,490✔
263
            }
1,490✔
264
        }
395✔
265

266
        public override void WriteQueue(IEnumerable value)
267
        {
35✔
268
            // We actually just treat this like an array right now; it's the same behavior and it's easier
269
            Type keyType = value.GetType().GetGenericArguments()[0];
35✔
270
            var array = value.GetType().GetMethod("ToArray").Invoke(value, new object[] { }) as Array;
35✔
271

272
            WriteArray(array);
35✔
273
        }
35✔
274

275
        public override void WriteStack(IEnumerable value)
276
        {
35✔
277
            // We actually just treat this like an array right now; it's the same behavior and it's easier
278
            Type keyType = value.GetType().GetGenericArguments()[0];
35✔
279
            var array = value.GetType().GetMethod("ToArray").Invoke(value, new object[] { }) as Array;
35✔
280

281
            // For some reason this writes it out to an array in the reverse order than I'd expect
282
            // (and also the reverse order it inputs in!)
283
            // so, uh, time to munge
284
            Array.Reverse(array);
35✔
285

286
            WriteArray(array);
35✔
287
        }
35✔
288

289
        public override void WriteTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
290
        {
145✔
291
            var args = value.GetType().GenericTypeArguments;
145✔
292
            var length = args.Length;
145✔
293

294
            var nameArray = names?.TransformNames;
145✔
295

296
            for (int i = 0; i < length; ++i)
1,170✔
297
            {
440✔
298
                Serialization.ComposeElement(CreateNamedChild(nameArray != null ? nameArray[i] : "li", RecorderSettings.CreateChild(), new PathIndex(Path, i)), value.GetType().GetProperty(UtilMisc.DefaultTupleNames[i]).GetValue(value), args[i]);
440✔
299
            }
440✔
300
        }
145✔
301

302
        public override void WriteValueTuple(object value, System.Runtime.CompilerServices.TupleElementNamesAttribute names)
303
        {
265✔
304
            var args = value.GetType().GenericTypeArguments;
265✔
305
            var length = args.Length;
265✔
306

307
            var nameArray = names?.TransformNames;
265✔
308

309
            for (int i = 0; i < length; ++i)
1,890✔
310
            {
680✔
311
                Serialization.ComposeElement(CreateNamedChild(nameArray != null ? nameArray[i] : "li", RecorderSettings.CreateChild(), new PathIndex(Path, i)), value.GetType().GetField(UtilMisc.DefaultTupleNames[i]).GetValue(value), args[i]);
680✔
312
            }
680✔
313
        }
265✔
314

315
        public override void WriteRecord(IRecordable value)
316
        {
390,905✔
317
            if (depth < MaxRecursionDepth)
390,905✔
318
            {
387,915✔
319
                // This is somewhat faster than a full pending write (5-10% faster in one test case, though with a lot of noise), so we do it whenever we can.
320
                value.Record(new RecorderWriter(this));
387,915✔
321
            }
387,915✔
322
            else
323
            {
2,990✔
324
                // Reset depth because this will be run only when the pending writes are ready.
325
                depth = 0;
2,990✔
326
                writer.RegisterPendingWrite(() => WriteRecord(value));
5,980✔
327
            }
2,990✔
328
        }
390,905✔
329

330
        public override void WriteConvertible(Converter converter, object value)
331
        {
520✔
332
            // Convertibles are kind of a wildcard, so right now we're just changing this to Flexible mode
333
            MakeRecorderContextChild();
520✔
334

335
            if (depth < MaxRecursionDepth)
520✔
336
            {
520✔
337
                try
338
                {
520✔
339
                    if (converter is ConverterString converterString)
520✔
340
                    {
180✔
341
                        WriteString(converterString.WriteObj(value));
180✔
342
                    }
180✔
343
                    else if (converter is ConverterRecord converterRecord)
340✔
344
                    {
180✔
345
                        converterRecord.RecordObj(value, new RecorderWriter(this));
180✔
346
                    }
180✔
347
                    else if (converter is ConverterFactory converterFactory)
160✔
348
                    {
160✔
349
                        converterFactory.WriteObj(value, new RecorderWriter(this));
160✔
350
                    }
160✔
351
                    else
352
                    {
×
353
                        Dbg.Err($"Somehow ended up with an unsupported converter {converter.GetType()}");
×
354
                    }
×
355
                }
520✔
356
                catch (Exception e)
×
357
                {
×
358
                    Dbg.Ex(e);
×
359
                }
×
360
            }
520✔
361
            else
362
            {
×
363
                // Reset depth because this will be run only when the pending writes are ready.
364
                depth = 0;
×
365
                writer.RegisterPendingWrite(() => WriteConvertible(converter, value));
×
366
            }
×
367
        }
520✔
368

369
        internal XElement GetXElement()
370
        {
17,075✔
371
            return node;
17,075✔
372
        }
17,075✔
373
    }
374
}
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