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

MeltyPlayer / MeltyTool / 20982513833

14 Jan 2026 04:35AM UTC coverage: 41.138% (-0.8%) from 41.907%
20982513833

push

github

MeltyPlayer
Fixed broken shader source tests.

6752 of 18485 branches covered (36.53%)

Branch coverage included in aggregate %.

28650 of 67572 relevant lines covered (42.4%)

64006.02 hits per line

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

53.69
/FinModelUtility/ImaginaryFileSystem/ImaginaryFileSystem.cs
1
using System.Reflection;
2
using System.Runtime.Serialization;
3

4
namespace System.IO.Abstractions.TestingHelpers;
5

6
/// <inheritdoc />
7
#if FEATURE_SERIALIZABLE
8
[Serializable]
9
#endif
10
public class ImaginaryFileSystem : FileSystemBase, IImaginaryFileDataAccessor {
11
  public const char DRIVE_CHAR = '_';
12

13
  private const string DEFAULT_CURRENT_DIRECTORY = @"_:\";
14
  public const string TEMP_DIRECTORY = @"_:\temp\";
15

16
  private readonly IDictionary<string, FileSystemEntry> files;
17
  private readonly IDictionary<string, ImaginaryDriveData> drives;
18
  private readonly ImaginaryPathVerifier imaginaryPathVerifier_;
19
#if FEATURE_SERIALIZABLE
20
    [NonSerialized]
21
#endif
22
  private Func<DateTime> dateTimeProvider = defaultDateTimeProvider;
20✔
23
  private static Func<DateTime> defaultDateTimeProvider = () => DateTime.UtcNow;
69,776✔
24

25
  /// <inheritdoc />
26
  public ImaginaryFileSystem() : this(null) { }
60✔
27

28
  /// <inheritdoc />
29
  public ImaginaryFileSystem(IDictionary<string, ImaginaryFileData> files,
30
                             string currentDirectory = "")
31
      : this(files,
×
32
             new ImaginaryFileSystemOptions {
×
33
                 CurrentDirectory = currentDirectory,
×
34
                 CreateDefaultTempDir = true
×
35
             }) { }
×
36

37
  /// <inheritdoc />
38
  public ImaginaryFileSystem(ImaginaryFileSystemOptions options)
39
      : this(null, options) { }
60✔
40

41
  /// <inheritdoc />
42
  public ImaginaryFileSystem(IDictionary<string, ImaginaryFileData> files,
20✔
43
                             ImaginaryFileSystemOptions options) {
40✔
44
    options ??= new ImaginaryFileSystemOptions();
20✔
45
    var currentDirectory = options.CurrentDirectory;
20✔
46
    if (string.IsNullOrEmpty(currentDirectory)) {
40!
47
      currentDirectory = ImaginaryUnixSupport.Path(DEFAULT_CURRENT_DIRECTORY);
20✔
48
    } else if (!System.IO.Path.IsPathRooted(currentDirectory)) {
20!
49
      throw new ArgumentException("Current directory needs to be rooted.",
×
50
                                  nameof(currentDirectory));
×
51
    }
52

53
    var defaultTempDirectory = ImaginaryUnixSupport.Path(TEMP_DIRECTORY);
20✔
54

55
    StringOperations = new StringOperations(ImaginaryUnixSupport.IsUnixPlatform());
20✔
56
    this.imaginaryPathVerifier_ = new ImaginaryPathVerifier(this);
20✔
57
    this.files
20✔
58
        = new Dictionary<string, FileSystemEntry>(StringOperations.Comparer);
20✔
59
    drives = new Dictionary<string, ImaginaryDriveData>(StringOperations.Comparer);
20✔
60

61
    Path = new ImaginaryPath(this, defaultTempDirectory);
20✔
62
    File = new ImaginaryFile(this);
20✔
63
    Directory = new ImaginaryDirectory(this, currentDirectory);
20✔
64
    FileInfo = new ImaginaryFileInfoFactory(this);
20✔
65
    FileVersionInfo = new ImaginaryFileVersionInfoFactory(this);
20✔
66
    FileStream = new ImaginaryFileStreamFactory(this);
20✔
67
    DirectoryInfo = new ImaginaryDirectoryInfoFactory(this);
20✔
68
    DriveInfo = new ImaginaryDriveInfoFactory(this);
20✔
69
    FileSystemWatcher = new ImaginaryFileSystemWatcherFactory(this);
20✔
70

71
    if (files != null) {
20!
72
      foreach (var entry in files) {
×
73
        AddFile(entry.Key, entry.Value);
×
74
      }
×
75
    }
×
76

77
    if (!FileExists(currentDirectory)) {
40✔
78
      AddDirectory(currentDirectory);
20✔
79
    }
20✔
80

81
    if (options.CreateDefaultTempDir && !FileExists(defaultTempDirectory)) {
40!
82
      AddDirectory(defaultTempDirectory);
20✔
83
    }
20✔
84
  }
20✔
85

86
  /// <inheritdoc />
87
  public StringOperations StringOperations { get; }
1,848,312✔
88

89
  /// <inheritdoc />
90
  public override IFile File { get; }
11,392✔
91

92
  /// <inheritdoc />
93
  public override IDirectory Directory { get; }
155,639✔
94

95
  /// <inheritdoc />
96
  public override IFileInfoFactory FileInfo { get; }
×
97

98
  /// <inheritdoc />
99
  public override IFileVersionInfoFactory FileVersionInfo { get; }
×
100

101
  /// <inheritdoc />
102
  public override IFileStreamFactory FileStream { get; }
×
103

104
  /// <inheritdoc />
105
  public override IPath Path { get; }
668,357✔
106

107
  /// <inheritdoc />
108
  public override IDirectoryInfoFactory DirectoryInfo { get; }
×
109

110
  /// <inheritdoc />
111
  public override IDriveInfoFactory DriveInfo { get; }
×
112

113
  /// <inheritdoc />
114
  public override IFileSystemWatcherFactory FileSystemWatcher { get; }
×
115

116
  /// <inheritdoc />
117
  public IFileSystem FileSystem => this;
145✔
118

119
  /// <inheritdoc />
120
  public ImaginaryPathVerifier ImaginaryPathVerifier => this.imaginaryPathVerifier_;
37,445✔
121

122
  /// <summary>
123
  /// Replaces the time provider with a mocked instance. This allows to influence the used time in tests.
124
  /// <para />
125
  /// If not set, the default implementation returns <see cref="DateTime.Now"/>.
126
  /// </summary>
127
  /// <param name="dateTimeProvider">The function that returns the current <see cref="DateTime"/>.</param>
128
  /// <returns></returns>
129
  public ImaginaryFileSystem MockTime(Func<DateTime> dateTimeProvider) {
×
130
    this.dateTimeProvider = dateTimeProvider ?? defaultDateTimeProvider;
×
131
    return this;
×
132
  }
×
133

134
  private string FixPath(string path, bool checkCaps = false) {
126,039✔
135
    if (path == null) {
126,039!
136
      throw new ArgumentNullException(nameof(path),
×
137
                                      StringResources.Manager.GetString(
×
138
                                          "VALUE_CANNOT_BE_NULL"));
×
139
    }
140

141
    var pathSeparatorFixed
126,039✔
142
        = path.Replace(Path.AltDirectorySeparatorChar,
126,039✔
143
                       Path.DirectorySeparatorChar);
126,039✔
144
    var fullPath = Path.GetFullPath(pathSeparatorFixed);
126,039✔
145

146
    return checkCaps
126,039✔
147
        ? GetPathWithCorrectDirectoryCapitalization(fullPath)
126,039✔
148
        : fullPath;
126,039✔
149
  }
126,039✔
150

151
  //If C:\foo exists, ensures that trying to save a file to "C:\FOO\file.txt" instead saves it to "C:\foo\file.txt".
152
  private string GetPathWithCorrectDirectoryCapitalization(string fullPath) {
11,770✔
153
    string[] splitPath = fullPath.Split(Path.DirectorySeparatorChar);
11,770✔
154
    string leftHalf = fullPath;
11,770✔
155
    string rightHalf = "";
11,770✔
156

157
    for (int i = splitPath.Length - 1; i > 1; i--) {
35,500✔
158
      rightHalf = i == splitPath.Length - 1
11,710✔
159
          ? splitPath[i]
11,710✔
160
          : splitPath[i] + Path.DirectorySeparatorChar + rightHalf;
11,710✔
161
      int lastSeparator = leftHalf.LastIndexOf(Path.DirectorySeparatorChar);
11,710✔
162
      leftHalf = lastSeparator > 0
11,710!
163
          ? leftHalf.Substring(0, lastSeparator)
11,710✔
164
          : leftHalf;
11,710✔
165

166
      if (DirectoryExistsWithoutFixingPath(leftHalf)) {
23,295✔
167
        string baseDirectory;
168
        lock (files) {
23,170✔
169
          baseDirectory = files[leftHalf].Path;
11,585✔
170
        }
11,585✔
171

172
        return baseDirectory + Path.DirectorySeparatorChar + rightHalf;
11,585✔
173
      }
174
    }
125✔
175

176
    return fullPath.TrimSlashes();
185✔
177
  }
11,770✔
178

179
  /// <inheritdoc />
180
  public ImaginaryFileData AdjustTimes(ImaginaryFileData fileData,
181
                                  TimeAdjustments timeAdjustments) {
69,756✔
182
    var now = dateTimeProvider();
69,756✔
183
    if (timeAdjustments.HasFlag(TimeAdjustments.CreationTime)) {
75,421✔
184
      fileData.CreationTime = now;
5,665✔
185
    }
5,665✔
186

187
    if (timeAdjustments.HasFlag(TimeAdjustments.LastAccessTime)) {
128,982✔
188
      fileData.LastAccessTime = now;
59,226✔
189
    }
59,226✔
190

191
    if (timeAdjustments.HasFlag(TimeAdjustments.LastWriteTime)) {
107,638✔
192
      fileData.LastWriteTime = now;
37,882✔
193
    }
37,882✔
194

195
    return fileData;
69,756✔
196
  }
69,756✔
197

198
  /// <inheritdoc />
199
  public ImaginaryFileData GetFile(string path) {
61,564✔
200
    path = FixPath(path).TrimSlashes();
61,564✔
201
    return GetFileWithoutFixingPath(path);
61,564✔
202
  }
61,564✔
203

204
  /// <inheritdoc />
205
  public ImaginaryDriveData GetDrive(string name) {
×
206
    name = this.ImaginaryPathVerifier.NormalizeDriveName(name);
×
207
    lock (drives) {
×
208
      return drives.TryGetValue(name, out var result) ? result : null;
×
209
    }
210
  }
×
211

212
  private void SetEntry(string path, ImaginaryFileData imaginaryFile) {
5,980✔
213
    path = FixPath(path, true).TrimSlashes();
5,980✔
214

215
    lock (files) {
11,960✔
216
      files[path] = new FileSystemEntry { Path = path, Data = imaginaryFile };
5,980✔
217
    }
5,980✔
218

219
    lock (drives) {
11,960✔
220
      if (this.ImaginaryPathVerifier.TryNormalizeDriveName(path, out string driveLetter)) {
11,960✔
221
        if (!drives.ContainsKey(driveLetter)) {
6,000✔
222
          drives[driveLetter] = new ImaginaryDriveData();
20✔
223
        }
20✔
224
      }
5,980✔
225
    }
5,980✔
226
  }
5,980✔
227

228
  /// <inheritdoc />
229
  public void AddFile(string path,
230
                      ImaginaryFileData imaginaryFile,
231
                      bool verifyAccess = true) {
5,665✔
232
    var fixedPath = FixPath(path, true);
5,665✔
233

234
    imaginaryFile ??= new ImaginaryFileData(string.Empty);
5,665!
235
    var file = GetFile(fixedPath);
5,665✔
236

237
    if (file != null) {
5,665!
238
      var isReadOnly = (file.Attributes & FileAttributes.ReadOnly) ==
×
239
                       FileAttributes.ReadOnly;
×
240
      var isHidden = (file.Attributes & FileAttributes.Hidden) ==
×
241
                     FileAttributes.Hidden;
×
242

243
      if (verifyAccess && (isReadOnly || isHidden)) {
×
244
        throw CommonExceptions.AccessDenied(path);
×
245
      }
246

247
      file.CheckFileAccess(fixedPath, FileAccess.Write);
×
248
      imaginaryFile.CreationTime = file.CreationTime;
×
249
    }
×
250

251
    var directoryPath = Path.GetDirectoryName(fixedPath);
5,665✔
252
    if (directoryPath == null) {
5,665!
253
      AddDrive(fixedPath, new ImaginaryDriveData());
×
254
    } else if (!DirectoryExistsWithoutFixingPath(directoryPath)) {
5,665!
255
      AddDirectory(directoryPath);
×
256
    }
×
257

258
    imaginaryFile.FileVersionInfo ??= new ImaginaryFileVersionInfo(fixedPath);
5,665✔
259

260
    SetEntry(fixedPath, imaginaryFile);
5,665✔
261
  }
5,665✔
262

263
  /// <summary>
264
  /// Add a new file that is empty.
265
  /// </summary>
266
  /// <param name="path">A string representing the path of the new file to add.</param>
267
  public void AddEmptyFile(string path) {
×
268
    AddFile(path, new ImaginaryFileData(""));
×
269
  }
×
270

271
  /// <summary>
272
  /// Add a new file that is empty.
273
  /// </summary>
274
  /// <param name="path">An <see cref="IFileInfo"/> representing the path of the new file to add.</param>
275
  public void AddEmptyFile(IFileInfo path) {
×
276
    AddEmptyFile(path.FullName);
×
277
    path.Refresh();
×
278
  }
×
279

280
  /// <summary>
281
  /// Add a new, empty directory.
282
  /// </summary>
283
  /// <param name="path">An <see cref="IDirectoryInfo"/> representing the path of the new directory to add.</param>
284
  public void AddDirectory(IDirectoryInfo path) {
×
285
    AddDirectory(path.FullName);
×
286
    path.Refresh();
×
287
  }
×
288

289
  /// <summary>
290
  /// Add a new file with its contents set to a specified <see cref="ImaginaryFileData"/>.
291
  /// </summary>
292
  /// <param name="path">An <see cref="IFileInfo"/> representing the path of the new file to add.</param>
293
  /// <param name="data">The data to use for the contents of the new file.</param>
294
  /// <param name="verifyAccess">Flag indicating if the access conditions should be verified.</param>
295
  public void AddFile(IFileInfo path,
296
                      ImaginaryFileData data,
297
                      bool verifyAccess = true) {
×
298
    AddFile(path.FullName, data, verifyAccess);
×
299
    path.Refresh();
×
300
  }
×
301

302
  /// <summary>
303
  /// Gets a file.
304
  /// </summary>
305
  /// <param name="path">The path of the file to get.</param>
306
  /// <returns>The file. <see langword="null"/> if the file does not exist.</returns>
307
  public ImaginaryFileData GetFile(IFileInfo path) {
×
308
    return GetFile(path.FullName);
×
309
  }
×
310

311
  /// <inheritdoc />
312
  public void AddDirectory(string path) {
125✔
313
    var fixedPath = FixPath(path, true);
125✔
314
    var separator = Path.DirectorySeparatorChar.ToString();
125✔
315

316
    if (FileExists(fixedPath) && FileIsReadOnly(fixedPath)) {
125!
317
      throw CommonExceptions.AccessDenied(fixedPath);
×
318
    }
319

320
    var lastIndex = 0;
125✔
321
    var isUnc =
125!
322
        StringOperations.StartsWith(fixedPath, @"\\") ||
125✔
323
        StringOperations.StartsWith(fixedPath, @"//");
125✔
324

325
    if (isUnc) {
125!
326
      //First, confirm they aren't trying to create '\\server\'
327
      lastIndex = StringOperations.IndexOf(fixedPath, separator, 2);
×
328

329
      if (lastIndex < 0) {
×
330
        throw CommonExceptions.InvalidUncPath(nameof(path));
×
331
      }
332

333
      /*
334
       * Although CreateDirectory(@"\\server\share\") is not going to work in real code, we allow it here for the purposes of setting up test doubles.
335
       * See PR https://github.com/TestableIO/System.IO.Abstractions/pull/90 for conversation
336
       */
337
    }
×
338

339
    while ((lastIndex
315✔
340
               = StringOperations.IndexOf(fixedPath,
315✔
341
                                          separator,
315✔
342
                                          lastIndex + 1)) >
315✔
343
           -1) {
505✔
344
      var segment = fixedPath.Substring(0, lastIndex + 1);
190✔
345
      if (!DirectoryExistsWithoutFixingPath(segment)) {
380✔
346
        SetEntry(segment, new ImaginaryDirectoryData());
190✔
347
      }
190✔
348
    }
190✔
349

350
    var s = StringOperations.EndsWith(fixedPath, separator)
125!
351
        ? fixedPath
125✔
352
        : fixedPath + separator;
125✔
353
    SetEntry(s, new ImaginaryDirectoryData());
125✔
354
  }
125✔
355

356
  /// <inheritdoc />
357
  public void AddFileFromEmbeddedResource(string path,
358
                                          Assembly resourceAssembly,
359
                                          string embeddedResourcePath) {
×
360
    using (var embeddedResourceStream
×
361
           = resourceAssembly.GetManifestResourceStream(embeddedResourcePath)) {
×
362
      if (embeddedResourceStream == null) {
×
363
        throw new ArgumentException("Resource not found in assembly",
×
364
                                    nameof(embeddedResourcePath));
×
365
      }
366

367
      using (var streamReader = new BinaryReader(embeddedResourceStream)) {
×
368
        var fileData
×
369
            = streamReader.ReadBytes((int) embeddedResourceStream.Length);
×
370
        AddFile(path, new ImaginaryFileData(fileData));
×
371
      }
×
372
    }
×
373
  }
×
374

375
  /// <inheritdoc />
376
  public void AddFilesFromEmbeddedNamespace(string path,
377
                                            Assembly resourceAssembly,
378
                                            string embeddedResourcePath) {
×
379
    var matchingResources = resourceAssembly.GetManifestResourceNames()
×
380
                                            .Where(f => f.StartsWith(
×
381
                                                       embeddedResourcePath));
×
382
    foreach (var resource in matchingResources) {
×
383
      using (var embeddedResourceStream
×
384
             = resourceAssembly.GetManifestResourceStream(resource))
×
385
      using (var streamReader = new BinaryReader(embeddedResourceStream)) {
×
386
        var fileName = resource.Substring(embeddedResourcePath.Length + 1);
×
387
        var fileData
×
388
            = streamReader.ReadBytes((int) embeddedResourceStream.Length);
×
389
        var filePath = Path.Combine(path, fileName);
×
390
        AddFile(filePath, new ImaginaryFileData(fileData));
×
391
      }
×
392
    }
×
393
  }
×
394

395
  /// <inheritdoc />
396
  public void AddDrive(string name, ImaginaryDriveData imaginaryDrive) {
×
397
    name = this.ImaginaryPathVerifier.NormalizeDriveName(name);
×
398
    lock (drives) {
×
399
      drives[name] = imaginaryDrive;
×
400
    }
×
401
  }
×
402

403
  /// <inheritdoc />
404
  public void MoveDirectory(string sourcePath, string destPath) {
×
405
    sourcePath = FixPath(sourcePath);
×
406
    destPath = FixPath(destPath);
×
407

408
    var sourcePathSequence = sourcePath.Split(
×
409
        new[] { Path.DirectorySeparatorChar },
×
410
        StringSplitOptions.RemoveEmptyEntries);
×
411

412
    lock (files) {
×
413
      var affectedPaths = files.Keys
×
414
                               .Where(p => PathStartsWith(
×
415
                                          p,
×
416
                                          sourcePathSequence))
×
417
                               .ToList();
×
418

419
      foreach (var path in affectedPaths) {
×
420
        var newPath = Path.Combine(destPath,
×
421
                                   path.Substring(sourcePath.Length)
×
422
                                       .TrimStart(Path.DirectorySeparatorChar));
×
423
        var entry = files[path];
×
424
        entry.Path = newPath;
×
425
        files[newPath] = entry;
×
426
        files.Remove(path);
×
427
      }
×
428
    }
×
429

430
    bool PathStartsWith(string path, string[] minMatch) {
×
431
      var pathSequence = path.Split(new[] { Path.DirectorySeparatorChar },
×
432
                                    StringSplitOptions.RemoveEmptyEntries);
×
433
      if (pathSequence.Length < minMatch.Length) {
×
434
        return false;
×
435
      }
436

437
      for (var i = 0; i < minMatch.Length; i++) {
×
438
        if (!StringOperations.Equals(minMatch[i], pathSequence[i])) {
×
439
          return false;
×
440
        }
441
      }
×
442

443
      return true;
×
444
    }
×
445
  }
×
446

447
  /// <inheritdoc />
448
  public void RemoveFile(string path, bool verifyAccess = true) {
5,750✔
449
    path = FixPath(path);
5,750✔
450

451
    lock (files) {
11,500✔
452
      if (FileExists(path) &&
5,750!
453
          verifyAccess &&
5,750✔
454
          (FileIsReadOnly(path) ||
5,750✔
455
           Directory.Exists(path) && AnyFileIsReadOnly(path))) {
5,750✔
456
        throw CommonExceptions.AccessDenied(path);
×
457
      }
458

459
      files.Remove(path);
5,750✔
460
    }
5,750✔
461
  }
5,750✔
462

463
  /// <inheritdoc />
464
  public bool FileExists(string path) {
46,955✔
465
    if (string.IsNullOrEmpty(path)) {
46,955!
466
      return false;
×
467
    }
468

469
    path = FixPath(path).TrimSlashes();
46,955✔
470

471
    lock (files) {
93,910✔
472
      return files.ContainsKey(path);
46,955✔
473
    }
474
  }
46,955✔
475

476
  /// <inheritdoc />
477
  public IEnumerable<string> AllPaths {
478
    get {
85✔
479
      lock (files) {
170✔
480
        return files.Keys.ToArray();
85✔
481
      }
482
    }
85✔
483
  }
484

485
  /// <inheritdoc />
486
  public IEnumerable<string> AllNodes {
487
    get {
×
488
      lock (files) {
×
489
        return AllPaths.Where(path => !IsStartOfAnotherPath(path)).ToArray();
×
490
      }
491
    }
×
492
  }
493

494
  /// <inheritdoc />
495
  public IEnumerable<string> AllFiles {
496
    get {
170✔
497
      lock (files) {
340✔
498
        return files.Where(f => !f.Value.Data.IsDirectory)
8,258✔
499
                    .Select(f => f.Key)
7,576✔
500
                    .ToArray();
170✔
501
      }
502
    }
170✔
503
  }
504

505
  /// <inheritdoc />
506
  public IEnumerable<string> AllDirectories {
507
    get {
×
508
      lock (files) {
×
509
        return files.Where(f => f.Value.Data.IsDirectory)
×
510
                    .Select(f => f.Key)
×
511
                    .ToArray();
×
512
      }
513
    }
×
514
  }
515

516
  /// <inheritdoc />
517
  public IEnumerable<string> AllDrives {
518
    get {
×
519
      lock (drives) {
×
520
        return drives.Keys.ToArray();
×
521
      }
522
    }
×
523
  }
524

525
  [OnDeserializing]
526
  private void OnDeserializing(StreamingContext c) {
×
527
    dateTimeProvider = defaultDateTimeProvider;
×
528
  }
×
529

530
  private bool AnyFileIsReadOnly(string path) {
85✔
531
    return Directory.GetFiles(path).Any(file => FileIsReadOnly(file));
1,994✔
532
  }
85✔
533

534
  private bool IsStartOfAnotherPath(string path) {
×
535
    return AllPaths.Any(otherPath
×
536
                            => otherPath.StartsWith(path) && otherPath != path);
×
537
  }
×
538

539
  private ImaginaryFileData GetFileWithoutFixingPath(string path) {
61,564✔
540
    lock (files) {
123,128✔
541
      return files.TryGetValue(path, out var result) ? result.Data : null;
61,564✔
542
    }
543
  }
61,564✔
544

545
  private bool DirectoryExistsWithoutFixingPath(string path) {
17,565✔
546
    lock (files) {
35,130✔
547
      return files.TryGetValue(path, out var result) && result.Data.IsDirectory;
17,565✔
548
    }
549
  }
17,565✔
550

551
  private bool FileIsReadOnly(string path) {
7,659✔
552
    return (GetFile(path).Attributes & FileAttributes.ReadOnly) ==
7,659✔
553
           FileAttributes.ReadOnly;
7,659✔
554
  }
7,659✔
555

556
#if FEATURE_SERIALIZABLE
557
    [Serializable]
558
#endif
559
  private class FileSystemEntry {
560
    public string Path { get; set; }
17,565✔
561
    public ImaginaryFileData Data { get; set; }
87,128✔
562
  }
563
}
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