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

zorbathut / dec / 11670896818

04 Nov 2024 07:02PM UTC coverage: 90.556%. Remained the same
11670896818

push

github

zorbathut
Removed redundant namespaces

4564 of 5040 relevant lines covered (90.56%)

191694.1 hits per line

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

95.71
/src/ParserModular.cs
1
using System;
2
using System.Collections.Generic;
3
using System.IO;
4
using System.Linq;
5
using System.Reflection;
6
using System.Runtime.CompilerServices;
7

8
namespace Dec
9
{
10
    /// <summary>
11
    /// Handles all parsing and initialization of dec structures.
12
    ///
13
    /// Intended for moddable games; use Parser for non-moddable or prototype games.
14
    /// </summary>
15
    public class ParserModular
16
    {
17
        public class Module : IParser
18
        {
19
            internal string name;
20
            internal readonly List<ReaderFileDec> readers = new List<ReaderFileDec>();
13,250✔
21
            internal Recorder.IUserSettings userSettings;
22

23
            internal Module(Recorder.IUserSettings userSettings)
13,250✔
24
            {
13,250✔
25
                this.userSettings = userSettings;
13,250✔
26
            }
13,250✔
27

28
            /// <summary>
29
            /// Pass a directory in for recursive processing.
30
            /// </summary>
31
            /// <remarks>
32
            /// This function will ignore dot-prefixed directory names and files, which are common for development tools to create.
33
            /// </remarks>
34
            /// <param name="directory">The directory to look for files in.</param>
35
            public void AddDirectory(string directory)
36
            {
185✔
37
                if (directory.IsNullOrEmpty())
185✔
38
                {
40✔
39
                    Dbg.Err("Attempted to add a null or empty directory to the parser; this is probably wrong");
40✔
40
                    return;
40✔
41
                }
42

43
                foreach (var file in Directory.GetFiles(directory, "*.xml"))
965✔
44
                {
265✔
45
                    if (!System.IO.Path.GetFileName(file).StartsWith("."))
265✔
46
                    {
205✔
47
                        AddFile(Parser.FileType.Xml, file);
205✔
48
                    }
205✔
49
                }
265✔
50

51
                foreach (var subdir in Directory.GetDirectories(directory))
620✔
52
                {
100✔
53
                    if (!System.IO.Path.GetFileName(subdir).StartsWith("."))
100✔
54
                    {
40✔
55
                        AddDirectory(subdir);
40✔
56
                    }
40✔
57
                }
100✔
58
            }
185✔
59

60
            /// <summary>
61
            /// Pass a file in for processing.
62
            /// </summary>
63
            /// <param name="stringName">A human-readable identifier useful for debugging. Generally, the name of the file that the string was read from. Not required; will be derived from filename automatically.</param>
64
            public void AddFile(Parser.FileType fileType, string filename, string identifier = null)
65
            {
285✔
66
                if (filename.IsNullOrEmpty())
285✔
67
                {
40✔
68
                    Dbg.Err("Attempted to add a null or empty filename to the parser; this is probably wrong");
40✔
69
                    return;
40✔
70
                }
71

72
                if (identifier == null)
245✔
73
                {
245✔
74
                    // This is imperfect, but good enough. People can pass their own identifier in if they want something clever.
75
                    identifier = Path.GetFileName(filename);
245✔
76
                }
245✔
77

78
                using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
245✔
79
                {
245✔
80
                    AddStream(fileType, fs, identifier);
245✔
81
                }
245✔
82
            }
285✔
83

84
            /// <summary>
85
            /// Pass a stream in for processing.
86
            /// </summary>
87
            /// <param name="identifier">A human-readable identifier useful for debugging. Generally, the name of the file that the stream was built from. Not required; will be derived from filename automatically</param>
88
            public void AddStream(Parser.FileType fileType, Stream stream, string identifier = "(unnamed)")
89
            {
265✔
90
                if (stream == null)
265✔
91
                {
20✔
92
                    Dbg.Err("Attempted to add a null stream to the parser; this is probably wrong");
20✔
93
                    return;
20✔
94
                }
95

96
                using (var reader = new StreamReader(stream))
245✔
97
                {
245✔
98
                    AddTextReader(fileType, reader, identifier);
245✔
99
                }
245✔
100
            }
265✔
101

102
            /// <summary>
103
            /// Pass a string in for processing.
104
            /// </summary>
105
            /// <param name="identifier">A human-readable identifier useful for debugging. Generally, the name of the file that the string was built from. Not required, but helpful.</param>
106
            public void AddString(Parser.FileType fileType, string contents, string identifier = "(unnamed)")
107
            {
12,385✔
108
                if (contents.IsNullOrEmpty())
12,385✔
109
                {
60✔
110
                    Dbg.Err("Attempted to add a null or empty string to the parser; this is probably wrong");
60✔
111
                    return;
60✔
112
                }
113

114
                // This is a really easy error to make; we might as well handle it.
115
                if (contents.EndsWith(".xml"))
12,325✔
116
                {
20✔
117
                    Dbg.Err($"It looks like you've passed the filename `{contents}` to AddString instead of the actual XML file. Either use AddFile() or pass the file contents in.");
20✔
118
                }
20✔
119

120
                using (var reader = new StringReader(contents))
12,325✔
121
                {
12,325✔
122
                    AddTextReader(fileType, reader, identifier);
12,325✔
123
                }
12,325✔
124
            }
12,385✔
125

126
            private void AddTextReader(Parser.FileType fileType, TextReader textReader, string identifier = "(unnamed)")
127
            {
12,570✔
128
                if (s_Status != Status.Accumulating)
12,570✔
129
                {
5✔
130
                    Dbg.Err($"Adding data while while the world is in {s_Status} state; should be {Status.Accumulating} state");
5✔
131
                }
5✔
132

133
                string bakedIdentifier = (name == "core") ? identifier : $"{name}:{identifier}";
12,570✔
134

135
                ReaderFileDec reader;
136

137
                if (fileType == Parser.FileType.Xml)
12,570✔
138
                {
12,570✔
139
                    reader = ReaderFileDecXml.Create(textReader, bakedIdentifier, userSettings);
12,570✔
140
                }
12,570✔
141
                else
142
                {
×
143
                    Dbg.Err($"{bakedIdentifier}: Only XML files are supported at this time");
×
144
                    return;
×
145
                }
146

147
                if (reader != null)
12,570✔
148
                {
12,510✔
149
                    readers.Add(reader);
12,510✔
150
                }
12,510✔
151

152
                // otherwise, error has already been printed
153
            }
12,570✔
154
        }
155

156
        // Global status
157
        private enum Status
158
        {
159
            Uninitialized,
160
            Accumulating,
161
            Processing,
162
            Distributing,
163
            Finalizing,
164
            Finished,
165
        }
166
        private static Status s_Status = Status.Uninitialized;
167

168
        // Data stored from initialization parameters
169
        private List<Type> staticReferences = new List<Type>();
12,470✔
170
        private Recorder.IUserSettings userSettings;
171

172
        // Modules
173
        internal List<Module> modules = new List<Module>();
12,470✔
174

175
        // Used for static reference validation
176
        private static Action s_StaticReferenceHandler = null;
177

178
        /// <summary>
179
        /// Creates a Parser.
180
        /// </summary>
181
        public ParserModular(Recorder.IUserSettings userSettings = null)
12,470✔
182
        {
12,470✔
183
            this.userSettings = userSettings;
12,470✔
184

185
            if (s_Status != Status.Uninitialized)
12,470✔
186
            {
10✔
187
                Dbg.Err($"Parser created while the world is in {s_Status} state; should be {Status.Uninitialized} state");
10✔
188
            }
10✔
189
            s_Status = Status.Accumulating;
12,470✔
190

191
            bool unitTestMode = Config.TestParameters != null;
12,470✔
192

193
            {
12,470✔
194
                IEnumerable<Type> staticRefs;
195
                if (!unitTestMode)
12,470✔
196
                {
5✔
197
                    staticRefs = UtilReflection.GetAllUserTypes().Where(t => t.GetCustomAttribute<StaticReferencesAttribute>() != null);
6,800✔
198
                }
5✔
199
                else if (Config.TestParameters.explicitStaticRefs != null)
12,465✔
200
                {
535✔
201
                    staticRefs = Config.TestParameters.explicitStaticRefs;
535✔
202
                }
535✔
203
                else
204
                {
11,930✔
205
                    staticRefs = Enumerable.Empty<Type>();
11,930✔
206
                }
11,930✔
207

208
                foreach (var type in staticRefs)
38,170✔
209
                {
380✔
210
                    if (type.GetCustomAttribute<StaticReferencesAttribute>() == null)
380✔
211
                    {
30✔
212
                        Dbg.Err($"{type} is not tagged as StaticReferences");
30✔
213
                    }
30✔
214

215
                    if (!type.IsAbstract || !type.IsSealed)
380✔
216
                    {
30✔
217
                        Dbg.Err($"{type} is not static");
30✔
218
                    }
30✔
219

220
                    staticReferences.Add(type);
380✔
221
                }
380✔
222
            }
12,470✔
223

224
            Serialization.Initialize();
12,470✔
225
        }
12,470✔
226

227
        /// <summary>
228
        /// Creates and registers a new module with a given name.
229
        /// </summary>
230
        public Module CreateModule(string name)
231
        {
13,250✔
232
            var module = new Module(userSettings);
13,250✔
233
            module.name = name;
13,250✔
234

235
            if (modules.Any(mod => mod.name == name))
14,145✔
236
            {
20✔
237
                Dbg.Err($"Created duplicate module {name}");
20✔
238
            }
20✔
239

240
            modules.Add(module);
13,250✔
241
            return module;
13,250✔
242
        }
13,250✔
243

244
        /// <summary>
245
        /// Finish all parsing.
246
        /// </summary>
247
        /// <remarks>
248
        /// The `dependencies` parameter can be used to feed in dependencies for the PostLoad function.
249
        /// This is a placeholder and is probably going to be replaced at some point, though only with something more capable.
250
        /// </remarks>
251
        public void Finish()
252
        {
12,455✔
253
            using (var _ = new CultureInfoScope(Config.CultureInfo))
12,455✔
254
            {
12,455✔
255
                if (s_Status != Status.Accumulating)
12,455✔
256
                {
5✔
257
                    Dbg.Err($"Finishing while the world is in {s_Status} state; should be {Status.Accumulating} state");
5✔
258
                }
5✔
259
                s_Status = Status.Processing;
12,455✔
260

261
                var readerContext = new ReaderContext() { allowReflection = true, allowRefs = false };
12,455✔
262

263
                // Collate reader decs
264
                var registeredDecs = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
265
                foreach (var module in modules)
63,835✔
266
                {
13,235✔
267
                    var seenDecs = new Dictionary<(Type, string), ReaderNode>();
13,235✔
268
                    foreach (var reader in module.readers)
64,715✔
269
                    {
12,505✔
270
                        foreach (var readerDec in reader.ParseDecs())
130,255✔
271
                        {
46,370✔
272
                            var id = (readerDec.type.GetDecRootType(), readerDec.name);
46,370✔
273

274
                            var collidingDec = seenDecs.TryGetValue(id);
46,370✔
275
                            if (collidingDec != null)
46,370✔
276
                            {
100✔
277
                                Dbg.Err($"{collidingDec.GetInputContext()} / {readerDec.node.GetInputContext()}: Dec [{id.Item1}:{id.name}] defined twice");
100✔
278

279
                                // If the already-parsed one is abstract, we throw it away and go with the non-abstract one, because it's arguably more likely to be the one the user wants.
280
                                if (!(registeredDecs[id].Select(dec => dec.abstrct).LastOrDefault(abstrct => abstrct.HasValue) ?? false))
300✔
281
                                {
60✔
282
                                    continue;
60✔
283
                                }
284

285
                                registeredDecs.Remove(id);
40✔
286
                            }
40✔
287

288
                            seenDecs[id] = readerDec.node;
46,310✔
289

290
                            if (!registeredDecs.TryGetValue(id, out var list))
46,310✔
291
                            {
45,190✔
292
                                list = new List<ReaderFileDec.ReaderDec>();
45,190✔
293
                                registeredDecs[id] = list;
45,190✔
294
                            }
45,190✔
295
                            list.Add(readerDec);
46,310✔
296
                        }
46,310✔
297
                    }
12,505✔
298
                }
13,235✔
299

300
                // Compile reader decs into Order assemblies
301
                var registeredDecOrders = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
302
                foreach (var seenDec in registeredDecs)
127,665✔
303
                {
45,150✔
304
                    var orders = Serialization.CompileDecOrders(seenDec.Value);
45,150✔
305

306
                    // If we have no orders, this probably ended up deleted.
307
                    if (orders.Count > 0)
45,150✔
308
                    {
44,810✔
309
                        registeredDecOrders[seenDec.Key] = orders;
44,810✔
310
                    }
44,810✔
311
                }
45,150✔
312

313
                // Instantiate all decs
314
                var toParseDecOrders = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
315
                foreach (var (id, orders) in registeredDecOrders)
126,985✔
316
                {
44,810✔
317
                    // It's currently sort of unclear how we should be deriving the type.
318
                    // I'm choosing, for now, to go with "the most derived type in the list, assuming all types are in the same inheritance sequence".
319
                    // Thankfully this isn't too hard to do.
320
                    var typeDeterminor = orders[0];
44,810✔
321
                    bool abstrct = typeDeterminor.abstrct ?? false;
44,810✔
322

323
                    foreach (var order in orders.Skip(1))
135,360✔
324
                    {
480✔
325
                        // Since we're iterating over this anyway, yank the abstract updates out as go.
326
                        if (order.abstrct.HasValue)
480✔
327
                        {
80✔
328
                            abstrct = order.abstrct.Value;
80✔
329
                        }
80✔
330

331
                        if (order.type == typeDeterminor.type)
480✔
332
                        {
320✔
333
                            // fast case
334
                            continue;
320✔
335
                        }
336

337
                        if (order.type.IsSubclassOf(typeDeterminor.type))
160✔
338
                        {
100✔
339
                            typeDeterminor = order;
100✔
340
                            continue;
100✔
341
                        }
342

343
                        if (typeDeterminor.type.IsSubclassOf(order.type))
60✔
344
                        {
40✔
345
                            continue;
40✔
346
                        }
347

348
                        // oops, they're not subclasses of each other
349
                        Dbg.Err($"{typeDeterminor.inputContext} / {order.inputContext}: Modded dec with tree-identifier [{id.Item1}:{id.Item2}] has conflicting types without a simple subclass relationship ({typeDeterminor.type}/{order.type}); deferring to {order.type}");
20✔
350
                        typeDeterminor = order;
20✔
351
                    }
20✔
352

353
                    // We don't actually want an instance of this.
354
                    if (abstrct)
44,810✔
355
                    {
740✔
356
                        continue;
740✔
357
                    }
358

359
                    // Not an abstract dec instance, so create our instance
360
                    var decInstance = (Dec)typeDeterminor.type.CreateInstanceSafe("dec", typeDeterminor.node);
44,070✔
361

362
                    // Error reporting happens within CreateInstanceSafe; if we get null out, we just need to clean up elegantly
363
                    if (decInstance != null)
44,070✔
364
                    {
44,030✔
365
                        decInstance.DecName = id.Item2;
44,030✔
366

367
                        Database.Register(decInstance);
44,030✔
368

369
                        // create a new map that filters out abstract objects and failed instantiations
370
                        toParseDecOrders.Add(id, orders);
44,030✔
371
                    }
44,030✔
372
                }
44,070✔
373

374
                // It's time to actually pull references out of the database, so let's stop spitting out empty warnings.
375
                Database.SuppressEmptyWarning();
12,455✔
376

377
                foreach (var (id, orders) in toParseDecOrders)
125,425✔
378
                {
44,030✔
379
                    // Accumulate our orders
380
                    var completeOrders = orders;
44,030✔
381

382
                    var currentOrder = orders;
44,030✔
383
                    while (true)
45,050✔
384
                    {
45,050✔
385
                        // See if we have a parent
386
                        var decWithParent = currentOrder.Where(dec => dec.parent != null).LastOrDefault();
90,760✔
387
                        if (decWithParent.parent == null || decWithParent.parent == "")
45,050✔
388
                        {
43,970✔
389
                            break;
43,970✔
390
                        }
391

392
                        var parentId = (id.Item1, decWithParent.parent);
1,080✔
393
                        if (!registeredDecOrders.TryGetValue(parentId, out var parentDec))
1,080✔
394
                        {
60✔
395
                            Dbg.Err($"{decWithParent.node.GetInputContext()}: Dec [{decWithParent.type}:{id.Item2}] is attempting to use parent `[{parentId.Item1}:{parentId.parent}]`, but no such dec exists");
60✔
396
                            // guess we'll just try to build it from nothing
397
                            break;
60✔
398
                        }
399

400
                        completeOrders.InsertRange(0, parentDec);
1,020✔
401

402
                        currentOrder = parentDec;
1,020✔
403
                    }
1,020✔
404

405
                    var targetDec = Database.Get(id.Item1, id.Item2);
44,030✔
406
                    Serialization.ParseElement(completeOrders.Select(order => order.node).ToList(), targetDec.GetType(), targetDec, readerContext, new Recorder.Context(), isRootDec: true, ordersOverride: completeOrders.Select(order => (Serialization.ParseCommand.Patch, order.node)).ToList());
135,450✔
407
                }
44,030✔
408

409
                if (s_Status != Status.Processing)
12,455✔
410
                {
×
411
                    Dbg.Err($"Distributing while the world is in {s_Status} state; should be {Status.Processing} state");
×
412
                }
×
413
                s_Status = Status.Distributing;
12,455✔
414

415
                foreach (var stat in staticReferences)
38,125✔
416
                {
380✔
417
                    if (!StaticReferencesAttribute.StaticReferencesFilled.Contains(stat))
380✔
418
                    {
100✔
419
                        s_StaticReferenceHandler = () =>
100✔
420
                        {
160✔
421
                            s_StaticReferenceHandler = null;
160✔
422
                            StaticReferencesAttribute.StaticReferencesFilled.Add(stat);
160✔
423
                        };
160✔
424
                    }
100✔
425

426
                    bool touched = false;
380✔
427
                    foreach (var field in stat.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
2,600✔
428
                    {
730✔
429
                        var dec = Database.Get(field.FieldType, field.Name);
730✔
430
                        if (dec == null)
730✔
431
                        {
30✔
432
                            Dbg.Err($"Static reference class {stat} has member `{field.FieldType} {field.Name}` that does not correspond to any loaded Dec");
30✔
433
                            field.SetValue(null, null); // this is unnecessary, but it does kick the static constructor just in case we wouldn't do it otherwise
30✔
434
                        }
30✔
435
                        else if (!field.FieldType.IsAssignableFrom(dec.GetType()))
700✔
436
                        {
30✔
437
                            Dbg.Err($"Static reference class {stat} has member `{field.FieldType} {field.Name}` that is not compatible with actual {dec.GetType()} {dec}");
30✔
438
                            field.SetValue(null, null); // this is unnecessary, but it does kick the static constructor just in case we wouldn't do it otherwise
30✔
439
                        }
30✔
440
                        else
441
                        {
670✔
442
                            field.SetValue(null, dec);
670✔
443
                        }
670✔
444

445
                        touched = true;
730✔
446
                    }
730✔
447

448
                    if (s_StaticReferenceHandler != null)
380✔
449
                    {
40✔
450
                        if (touched)
40✔
451
                        {
35✔
452
                            // Otherwise we shouldn't even expect this to have been registered, but at least there's literally no fields in it so it doesn't matter
453
                            Dbg.Err($"Failed to properly register {stat}; you may be missing a call to Dec.StaticReferencesAttribute.Initialized() in its static constructor, or the class may already have been initialized elsewhere (this should have thrown an error)");
35✔
454
                        }
35✔
455

456
                        s_StaticReferenceHandler = null;
40✔
457
                    }
40✔
458
                }
380✔
459

460
                if (s_Status != Status.Distributing)
12,455✔
461
                {
×
462
                    Dbg.Err($"Finalizing while the world is in {s_Status} state; should be {Status.Distributing} state");
×
463
                }
×
464
                s_Status = Status.Finalizing;
12,455✔
465

466
                // figure out our config/postload order; first we just run over all the Decs and collate classes
467
                var decTypes = new Dictionary<Type, List<Dec>>();
12,455✔
468
                foreach (var dec in Database.List)
125,425✔
469
                {
44,030✔
470
                    var list = decTypes.TryGetValue(dec.GetType());
44,030✔
471
                    if (list == null)
44,030✔
472
                    {
12,725✔
473
                        list = new List<Dec>();
12,725✔
474
                        decTypes[dec.GetType()] = list;
12,725✔
475
                    }
12,725✔
476

477
                    list.Add(dec);
44,030✔
478
                }
44,030✔
479

480
                List<Dag<Type>.Dependency> postLoadDependencies = new List<Dag<Type>.Dependency>();
12,455✔
481
                foreach (var type in decTypes)
62,815✔
482
                {
12,725✔
483
                    foreach (var rsaa in type.Key.GetCustomAttributes<SetupDependsOnAttribute>())
38,300✔
484
                    {
70✔
485
                        var dependsOn = rsaa.Type;
70✔
486

487
                        // make sure it inherits from Dec.Dec
488
                        if (!dependsOn.IsSubclassOf(typeof(Dec)))
70✔
489
                        {
×
490
                            Dbg.Err($"{type.Key} has a SetupDependsOnAttribute on {dependsOn}, but {dependsOn} is not a Dec type");
×
491
                            continue;
×
492
                        }
493

494
                        if (!decTypes.ContainsKey(rsaa.Type))
70✔
495
                        {
5✔
496
                            Dbg.Err($"{type.Key} has a SetupDependsOnAttribute on {dependsOn}, but {dependsOn} is not a known Dec type; this might result in weird behavior, either create an instance of {dependsOn} or pester ZorbaTHut on Discord if you need this fixed");
5✔
497
                            continue;
5✔
498
                        }
499

500
                        postLoadDependencies.Add(new Dag<Type>.Dependency { before = rsaa.Type, after = type.Key });
65✔
501
                    }
65✔
502
                }
12,725✔
503

504
                var postprocessOrder = Dag<Type>.CalculateOrder(decTypes.Keys, postLoadDependencies, t => t.Name);
25,180✔
505

506
                foreach (var type in postprocessOrder)
62,815✔
507
                {
12,725✔
508
                    foreach (var dec in decTypes[type])
126,235✔
509
                    {
44,030✔
510
                        try
511
                        {
44,030✔
512
                            dec.ConfigErrors(err => Dbg.Err($"{dec}: {err}"));
44,080✔
513
                        }
43,970✔
514
                        catch (Exception e)
60✔
515
                        {
60✔
516
                            Dbg.Ex(e);
60✔
517
                        }
60✔
518

519
                        try
520
                        {
44,030✔
521
                            dec.PostLoad(err => Dbg.Err($"{dec}: {err}"));
44,080✔
522
                        }
43,970✔
523
                        catch (Exception e)
60✔
524
                        {
60✔
525
                            Dbg.Ex(e);
60✔
526
                        }
60✔
527
                    }
44,030✔
528
                }
12,725✔
529

530
                if (s_Status != Status.Finalizing)
12,455✔
531
                {
×
532
                    Dbg.Err($"Completing while the world is in {s_Status} state; should be {Status.Finalizing} state");
×
533
                }
×
534
                s_Status = Status.Finished;
12,455✔
535
            }
12,455✔
536
        }
12,455✔
537

538
        internal static void Clear()
539
        {
26,325✔
540
            if (s_Status != Status.Finished && s_Status != Status.Uninitialized)
26,325✔
541
            {
15✔
542
                Dbg.Err($"Clearing while the world is in {s_Status} state; should be {Status.Uninitialized} state or {Status.Finished} state");
15✔
543
            }
15✔
544
            s_Status = Status.Uninitialized;
26,325✔
545
        }
26,325✔
546

547
        [MethodImpl(MethodImplOptions.NoInlining)]
548
        internal static void StaticReferencesInitialized()
549
        {
80✔
550
            if (s_StaticReferenceHandler != null)
80✔
551
            {
60✔
552
                s_StaticReferenceHandler();
60✔
553
                return;
60✔
554
            }
555

556
            Dbg.Err($"Initializing static reference class at an inappropriate time. Either you forgot to add [StaticReferences] to the class, or you accessed it before it was ready.");
20✔
557
        }
80✔
558
    }
559
}
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