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

PredatorCZ / PreCore / 460

pending completion
460

push

github-actions-ci

PredatorCZ
try fix coverage

3204 of 6095 relevant lines covered (52.57%)

354.19 hits per line

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

97.18
/src/reflector_io.cpp
1
/*  a source for reflector_io
2

3
    Copyright 2020-2021 Lukas Cone
4

5
    Licensed under the Apache License, Version 2.0 (the "License");
6
    you may not use this file except in compliance with the License.
7
    You may obtain a copy of the License at
8

9
        http://www.apache.org/licenses/LICENSE-2.0
10

11
    Unless required by applicable law or agreed to in writing, software
12
    distributed under the License is distributed on an "AS IS" BASIS,
13
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
    See the License for the specific language governing permissions and
15
    limitations under the License.
16
*/
17

18
#include "spike/reflect/reflector_io.hpp"
19
#include "spike/except.hpp"
20
#include "spike/type/base_128.hpp"
21
#include <sstream>
22

23
struct Fixups {
5✔
24
  struct Fixup {
25
    uint32 ptr, destination;
26
  };
27

28
  std::vector<Fixup> fixups;
29
  uint32 fixupIter = 0;
30
  BinWritterRef ref;
31

32
  Fixups(BinWritterRef _ref) : ref(_ref){};
1✔
33

34
  void AddPointer() { fixups.push_back({static_cast<uint32>(ref.Tell()), 0}); }
157✔
35
  void FixupDestination() {
157✔
36
    fixups[fixupIter++].destination = static_cast<uint32>(ref.Tell());
157✔
37
  }
157✔
38
  void AppendDestination(uint32 dest) {
39
    fixups[fixupIter++].destination = dest;
40
  }
41
  void AddDestination() {
42
    fixups.push_back({0, static_cast<uint32>(ref.Tell())});
43
  }
44
  void FixupPointer() {
45
    fixups[fixupIter++].ptr = static_cast<uint32>(ref.Tell());
46
  }
47
  void WriteFixups() {
5✔
48
    for (auto &f : fixups) {
160✔
49
      ref.Seek(f.ptr);
155✔
50
      ref.Write(f.destination);
155✔
51
    }
52
  }
5✔
53
};
54

55
struct ReflectorIOHeader {
56
  static constexpr uint32 ID = CompileFourCC("RFDB");
57
  static constexpr uint32 VERSION = 3000;
58

59
  uint32 id, version, numClasses, numEnums, enumsOffset, bufferSize;
60

61
  ReflectorIOHeader() : id(ID), version(VERSION) {}
2✔
62
};
63

64
struct reflectorStatic_io {
65
  uint32 classHash;
66
  uint32 nTypes;
67
  uintptr_t types;
68
  uintptr_t typeNames;
69
  uintptr_t className;
70
  uintptr_t typeAliases;
71
  uintptr_t typeAliasHashes;
72
  uintptr_t typeDescs;
73

74
  void Fixup(uintptr_t base) {
8✔
75
    auto fixup = [base](uintptr_t &item) {
28✔
76
      if (item) {
34✔
77
        item += base;
44✔
78
      }
79
    };
80

81
    auto fixupmore = [fixup](auto &...items) { (fixup(items), ...); };
8✔
82

83
    fixupmore(types, className, typeAliasHashes, typeDescs, typeNames,
8✔
84
              typeAliases);
8✔
85

86
    if (typeAliases) {
8✔
87
      auto castedBase = reinterpret_cast<uintptr_t *>(typeAliases);
2✔
88
      auto casted = &*castedBase;
89
      for (uint32 i = 0; i < nTypes; i++) {
10✔
90
        if (casted[i]) {
8✔
91
          casted[i] += base;
6✔
92
        }
93
      }
94
    }
95

96
    if (typeNames) {
8✔
97
      auto castedBase = reinterpret_cast<uintptr_t *>(typeNames);
8✔
98
      auto casted = &*castedBase;
99
      for (uint32 i = 0; i < nTypes; i++) {
80✔
100
        if (casted[i]) {
72✔
101
          casted[i] += base;
71✔
102
        }
103
      }
104
    }
105

106
    if (typeDescs) {
8✔
107
      struct Desc {
108
        uintptr_t data1;
109
        uintptr_t data2;
110
      };
111

112
      auto casted = reinterpret_cast<Desc *>(typeDescs);
2✔
113

114
      for (uint32 t = 0; t < nTypes; t++) {
10✔
115
        auto &item = casted[t];
8✔
116
        fixupmore(item.data1, item.data2);
117
      }
118
    }
119
  }
8✔
120
};
121

122
static_assert(sizeof(reflectorStatic_io) == sizeof(reflectorStatic));
123

124
struct ReflectedEnum_io {
125
  uint32 enumHash;
126
  uint32 numMembers;
127
  uintptr_t enumName;
128
  uintptr_t names;
129
  uintptr_t values;
130
  uintptr_t descriptions;
131

132
  void Fixup(uintptr_t base) {
5✔
133
    auto fixup = [base](uintptr_t &item) {
134
      if (item) {
20✔
135
        item += base;
16✔
136
      }
137
    };
138

139
    auto fixupmore = [fixup](auto &...items) { (fixup(items), ...); };
140

141
    fixupmore(enumName, names, values, descriptions);
142

143
    if (names) {
5✔
144
      auto castedBase = reinterpret_cast<uintptr_t *>(names);
5✔
145
      auto casted = &*castedBase;
146
      for (uint32 i = 0; i < numMembers; i++) {
20✔
147
        if (casted[i]) {
15✔
148
          casted[i] += base;
15✔
149
        }
150
      }
151
    }
152

153
    if (descriptions) {
5✔
154
      auto castedBase = reinterpret_cast<uintptr_t *>(descriptions);
1✔
155
      auto casted = &*castedBase;
156
      for (uint32 i = 0; i < numMembers; i++) {
4✔
157
        if (casted[i]) {
3✔
158
          casted[i] += base;
3✔
159
        }
160
      }
161
    }
162
  }
5✔
163
};
164

165
static_assert(sizeof(ReflectedEnum_io) == sizeof(ReflectedEnum));
166

167
int ReflectorIO::Load(BinReaderRef rd) {
1✔
168
  ReflectorIOHeader hdr;
169

170
  rd.Read(hdr);
171

172
  if (hdr.id != hdr.ID) {
1✔
173
    throw es::InvalidHeaderError(hdr.id);
×
174
  }
175

176
  if (hdr.version != hdr.VERSION) {
1✔
177
    throw es::InvalidVersionError(hdr.version);
×
178
  }
179

180
  rd.ReadContainer(data, hdr.bufferSize);
1✔
181

182
  reflectorStatic_io *classesStart =
183
      reinterpret_cast<reflectorStatic_io *>(&data[0]);
184
  const uintptr_t bufferStart = reinterpret_cast<uintptr_t>(classesStart);
1✔
185

186
  for (uint32 c = 0; c < hdr.numClasses; c++) {
9✔
187
    classesStart[c].Fixup(bufferStart);
8✔
188

189
    classes.push_back(
8✔
190
        reinterpret_cast<const reflectorStatic *>(classesStart + c));
8✔
191
  }
192

193
  ReflectedEnum_io *enumsIter =
194
      reinterpret_cast<ReflectedEnum_io *>(&data[0] + hdr.enumsOffset);
1✔
195

196
  for (uint32 c = 0; c < hdr.numEnums; c++) {
6✔
197
    enumsIter[c].Fixup(bufferStart);
5✔
198
    enums.push_back(reinterpret_cast<const ReflectedEnum *>(enumsIter + c));
5✔
199
  }
200

201
  return 0;
1✔
202
}
203

204
int ReflectorIO::Save(BinWritterRef wr) {
1✔
205
  ReflectorIOHeader hdr;
206
  hdr.numClasses = static_cast<uint32>(classes.size());
1✔
207
  hdr.numEnums = static_cast<uint32>(enums.size());
1✔
208
  wr.Write(hdr);
209
  wr.SetRelativeOrigin(wr.Tell(), false);
210

211
  Fixups itemFixups(wr);
212
  Fixups itemStringsFixups(wr);
213
  Fixups classnamesFixups(wr);
214
  Fixups aliasesFixups(wr);
215
  Fixups descsFixups(wr);
216
  Fixups aliasHashesFixups(wr);
217

218
  for (auto i : classes) {
9✔
219
    wr.Write(i->classHash);
8✔
220
    wr.Write(i->nTypes);
8✔
221
    itemFixups.AddPointer();
8✔
222
    wr.Skip<uint64>();
223

224
    if (i->typeNames) {
8✔
225
      itemStringsFixups.AddPointer();
8✔
226
    }
227

228
    wr.Skip<uint64>();
229

230
    if (i->className) {
8✔
231
      classnamesFixups.AddPointer();
8✔
232
    }
233

234
    wr.Skip<uint64>();
235

236
    if (i->typeAliases) {
8✔
237
      aliasesFixups.AddPointer();
2✔
238
    }
239

240
    wr.Skip<uint64>();
241

242
    if (i->typeAliasHashes) {
8✔
243
      aliasHashesFixups.AddPointer();
2✔
244
    }
245

246
    wr.Skip<uint64>();
247

248
    if (i->typeDescs) {
8✔
249
      descsFixups.AddPointer();
2✔
250
    }
251

252
    wr.Skip<uint64>();
253
  }
254

255
  for (auto i : classes) {
9✔
256
    itemFixups.FixupDestination();
8✔
257

258
    for (uint32 r = 0; r < i->nTypes; r++) {
80✔
259
      wr.Write(i->types[r]);
72✔
260
    }
261
  }
262

263
  for (auto i : classes) {
9✔
264
    if (i->typeAliasHashes) {
8✔
265
      aliasHashesFixups.FixupDestination();
2✔
266

267
      for (uint32 r = 0; r < i->nTypes; r++) {
10✔
268
        wr.Write(i->typeAliasHashes[r]);
8✔
269
      }
270
    }
271
  }
272

273
  for (auto i : classes) {
9✔
274
    if (i->typeNames) {
8✔
275
      wr.ApplyPadding(8);
8✔
276
      itemStringsFixups.FixupDestination();
8✔
277

278
      for (uint32 r = 0; r < i->nTypes; r++) {
80✔
279
        if (i->typeNames[r]) {
72✔
280
          itemStringsFixups.AddPointer();
71✔
281
        }
282
        wr.Skip<uint64>();
283
      }
284
    }
285
  }
286

287
  for (auto i : classes) {
9✔
288
    if (i->typeAliases) {
8✔
289
      wr.ApplyPadding(8);
2✔
290
      aliasesFixups.FixupDestination();
2✔
291

292
      for (uint32 r = 0; r < i->nTypes; r++) {
10✔
293
        if (i->typeAliases[r]) {
8✔
294
          aliasesFixups.AddPointer();
6✔
295
        }
296
        wr.Skip<uint64>();
297
      }
298
    }
299
  }
300

301
  for (auto i : classes) {
9✔
302
    if (i->typeDescs) {
8✔
303
      descsFixups.FixupDestination();
2✔
304

305
      for (uint32 r = 0; r < i->nTypes; r++) {
10✔
306
        if (i->typeDescs[r].part1) {
8✔
307
          descsFixups.AddPointer();
8✔
308
        }
309

310
        wr.Skip<uint64>();
311

312
        if (i->typeDescs[r].part2) {
8✔
313
          descsFixups.AddPointer();
8✔
314
        }
315

316
        wr.Skip<uint64>();
317
      }
318
    }
319
  }
320

321
  for (auto i : classes) {
9✔
322
    if (i->typeNames) {
8✔
323
      for (uint32 r = 0; r < i->nTypes; r++) {
80✔
324
        if (i->typeNames[r]) {
72✔
325
          itemStringsFixups.FixupDestination();
71✔
326
          wr.WriteT(i->typeNames[r]);
71✔
327
        }
328
      }
329
    }
330

331
    if (i->typeAliases) {
8✔
332
      for (uint32 r = 0; r < i->nTypes; r++) {
10✔
333
        if (!i->typeAliases[r]) {
8✔
334
          continue;
2✔
335
        }
336

337
        aliasesFixups.FixupDestination();
6✔
338
        wr.WriteT(i->typeAliases[r]);
6✔
339
      }
340
    }
341

342
    if (i->typeDescs) {
8✔
343
      for (uint32 r = 0; r < i->nTypes; r++) {
10✔
344
        if (i->typeDescs[r].part1) {
8✔
345
          descsFixups.FixupDestination();
8✔
346
          wr.WriteT(i->typeDescs[r].part1);
8✔
347
        }
348

349
        if (i->typeDescs[r].part2) {
8✔
350
          descsFixups.FixupDestination();
8✔
351
          wr.WriteT(i->typeDescs[r].part2);
8✔
352
        }
353
      }
354
    }
355

356
    if (i->className) {
8✔
357
      classnamesFixups.FixupDestination();
8✔
358
      wr.WriteT(i->className);
8✔
359
    }
360
  }
361

362
  wr.ApplyPadding(8);
1✔
363
  hdr.enumsOffset = static_cast<uint32>(wr.Tell());
1✔
364

365
  for (auto e : enums) {
6✔
366
    wr.Write(e->enumHash);
5✔
367
    wr.Write(e->numMembers);
5✔
368
    classnamesFixups.AddPointer();
5✔
369
    wr.Skip<uint64>();
370
    itemStringsFixups.AddPointer();
5✔
371
    wr.Skip<uint64>();
372
    itemFixups.AddPointer();
5✔
373
    wr.Skip<uint64>();
374

375
    if (e->descriptions) {
5✔
376
      descsFixups.AddPointer();
1✔
377
    }
378

379
    wr.Skip<uint64>();
380
  }
381

382
  for (auto e : enums) {
6✔
383
    itemFixups.FixupDestination();
5✔
384
    for (size_t v = 0; v < e->numMembers; v++) {
20✔
385
      wr.Write(e->values[v]);
15✔
386
    }
387

388
    itemStringsFixups.FixupDestination();
5✔
389
    for (size_t s = 0; s < e->numMembers; s++) {
20✔
390
      itemStringsFixups.AddPointer();
15✔
391
      wr.Skip<uint64>();
392
    }
393

394
    if (e->descriptions) {
5✔
395
      descsFixups.FixupDestination();
1✔
396
      for (size_t s = 0; s < e->numMembers; s++) {
4✔
397
        descsFixups.AddPointer();
3✔
398
        wr.Skip<uint64>();
399
      }
400
    }
401
  }
402

403
  for (auto e : enums) {
6✔
404
    classnamesFixups.FixupDestination();
5✔
405
    wr.WriteT(e->enumName);
5✔
406

407
    for (size_t v = 0; v < e->numMembers; v++) {
20✔
408
      itemStringsFixups.FixupDestination();
15✔
409
      wr.WriteT(e->names[v]);
15✔
410
    }
411

412
    if (e->descriptions) {
5✔
413
      for (size_t v = 0; v < e->numMembers; v++) {
4✔
414
        descsFixups.FixupDestination();
3✔
415
        wr.WriteT(e->descriptions[v]);
3✔
416
      }
417
    }
418
  }
419

420
  hdr.bufferSize = static_cast<uint32>(wr.Tell());
1✔
421
  wr.Push();
422
  wr.ResetRelativeOrigin();
423
  wr.Write(hdr);
424
  itemFixups.WriteFixups();
1✔
425
  itemStringsFixups.WriteFixups();
1✔
426
  classnamesFixups.WriteFixups();
1✔
427
  aliasesFixups.WriteFixups();
1✔
428
  descsFixups.WriteFixups();
1✔
429
  wr.Pop();
430

431
  return 0;
1✔
432
}
433

434
struct ReflectedInstanceFriend : ReflectedInstance {
435
  void *Instance() { return instance; }
16✔
436
  const void *Instance() const { return constInstance; }
437
  const reflectorStatic *Refl() const { return rfStatic; }
16✔
438
};
439

440
class ReflectorFriend : public Reflector {
441
public:
442
  using Reflector::GetReflectedInstance;
443
  using Reflector::GetReflectedType;
444
};
445

446
static int SaveClass(const Reflector &ri, ReflectedInstanceFriend inst,
447
                     BinWritterRef wr);
448

449
static int WriteDataItem(const Reflector &ri, BinWritterRef wr,
177✔
450
                         const char *objAddr, ReflType type, size_t index,
451
                         size_t element = 0) {
452
  switch (type.type) {
177✔
453
  case REFType::Integer:
31✔
454
  case REFType::Enum:
455
    if (type.size > 1) {
31✔
456
      bint128 tvar;
457
      memcpy(&tvar, objAddr, type.size);
23✔
458
      const size_t shValue = 64 - (type.size * 8);
23✔
459
      tvar.value = (tvar.value << shValue) >> shValue;
23✔
460
      wr.Write(tvar);
461
      return 0;
462
    }
463

464
    [[fallthrough]];
465
  case REFType::UnsignedInteger:
466
  case REFType::EnumFlags:
467
  case REFType::BitFieldClass:
468
    if (type.size > 1) {
40✔
469
      buint128 tvar;
470
      memcpy(&tvar, objAddr, type.size);
24✔
471
      wr.Write(tvar);
472
      return 0;
473
    }
474

475
    [[fallthrough]];
476
  case REFType::Bool:
477
  case REFType::FloatingPoint:
478
    wr.WriteBuffer(objAddr, type.size);
479
    return 0;
86✔
480

481
  case REFType::Array:
482
  case REFType::ArrayClass:
483
  case REFType::Vector: {
484
    for (uint32 i = 0; i < type.asArray.numItems; i++) {
161✔
485
      if (WriteDataItem(ri, wr, objAddr + (type.asArray.stride * i),
121✔
486
                        type.asArray, type.index, i))
121✔
487
        return 2;
488
    }
489
    return 0;
490
  }
491

492
  case REFType::String: {
1✔
493
    const std::string *rStr = reinterpret_cast<const std::string *>(objAddr);
494
    wr.WriteContainerWCount(*rStr);
1✔
495
    return 0;
1✔
496
  }
497

498
  case REFType::Class: {
3✔
499
    auto sri = static_cast<ReflectedInstanceFriend &&>(
500
        ri.GetReflectedSubClass(index, element));
3✔
501
    return SaveClass(ri, sri, wr);
3✔
502
  }
503
  default:
504
    return 1;
505
  }
506
}
507

508
static int SaveClass(const Reflector &ri, ReflectedInstanceFriend inst,
4✔
509
                     BinWritterRef wr) {
510
  auto refData = inst.Refl();
511
  const buint128 numItems = refData->nTypes;
4✔
512
  const char *thisAddr = static_cast<const char *>(inst.Instance());
513
  std::stringstream tmpClassBuffer;
4✔
514

515
  BinWritterRef wrTmpClass(tmpClassBuffer);
516
  wrTmpClass.Write(numItems);
517

518
  for (size_t i = 0; i < numItems; i++) {
60✔
519
    wrTmpClass.Write(refData->types[i].valueNameHash);
56✔
520

521
    std::stringstream tmpValueBuffer;
56✔
522
    BinWritterRef wrTmp(tmpValueBuffer);
523

524
    int rVal = WriteDataItem(ri, wrTmp, thisAddr + refData->types[i].offset,
56✔
525
                             refData->types[i], refData->types[i].index);
56✔
526

527
    if (rVal)
56✔
528
      return rVal;
529

530
    const auto sVarBuffer = tmpValueBuffer.str();
531
    wrTmpClass.WriteContainerWCount<buint128>(sVarBuffer);
56✔
532
  }
56✔
533

534
  const auto sClassBuffer = tmpClassBuffer.str();
535
  wr.WriteContainerWCount<buint128>(sClassBuffer);
4✔
536

537
  return 0;
538
}
4✔
539

540
int ReflectorBinUtil::Save(const Reflector &ri, BinWritterRef wr) {
1✔
541
  auto &&rif = static_cast<const ReflectorFriend &>(ri);
542
  auto inst =
543
      static_cast<ReflectedInstanceFriend &&>(rif.GetReflectedInstance());
1✔
544
  auto refData = inst.Refl();
545

546
  wr.Write(refData->classHash);
1✔
547

548
  return SaveClass(ri, inst, wr);
1✔
549
}
550

551
static int LoadClass(ReflectedInstanceFriend inst, BinReaderRef rd);
552

553
static int LoadDataItem(Reflector &ri, BinReaderRef rd, char *objAddr,
531✔
554
                        ReflType type, size_t index, size_t element = 0) {
555
  switch (type.type) {
531✔
556
  case REFType::Integer:
93✔
557
  case REFType::Enum:
558
    if (type.size > 1) {
93✔
559
      bint128 tvar;
560
      rd.Read(tvar);
561
      memcpy(objAddr, &tvar, type.size);
69✔
562
      return 0;
563
    }
564

565
    [[fallthrough]];
566
  case REFType::UnsignedInteger:
567
  case REFType::EnumFlags:
568
  case REFType::BitFieldClass:
569
    if (type.size > 1) {
120✔
570
      buint128 tvar;
571
      rd.Read(tvar);
572
      memcpy(objAddr, &tvar, type.size);
72✔
573
      return 0;
574
    }
575

576
    [[fallthrough]];
577
  case REFType::Bool:
578
  case REFType::FloatingPoint:
579
    rd.ReadBuffer(objAddr, type.size);
580
    return 0;
258✔
581

582
  case REFType::Array:
583
  case REFType::ArrayClass:
584
  case REFType::Vector: {
585
    for (uint32 i = 0; i < type.asArray.numItems; i++) {
483✔
586
      if (LoadDataItem(ri, rd, objAddr + (type.asArray.stride * i),
363✔
587
                       type.asArray, type.index, i))
363✔
588
        return 2;
589
    }
590
    return 0;
591
  }
592

593
  case REFType::String: {
3✔
594
    std::string *rStr = reinterpret_cast<std::string *>(objAddr);
595
    rd.ReadContainer(*rStr);
3✔
596
    return 0;
3✔
597
  }
598

599
  case REFType::Class: {
9✔
600
    auto sri = static_cast<ReflectedInstanceFriend &&>(
601
        ri.GetReflectedSubClass(index, element));
9✔
602
    return LoadClass(sri, rd);
9✔
603
  }
604
  default:
605
    return 1;
606
  }
607
}
608

609
static int LoadClass(ReflectedInstanceFriend inst, BinReaderRef rd) {
12✔
610
  auto refData = inst.Refl();
611
  const uint32 numItems = refData->nTypes;
12✔
612
  char *thisAddr = static_cast<char *>(inst.Instance());
613
  buint128 chunkSize;
614
  rd.Read(chunkSize);
615
  rd.Push();
616

617
  buint128 numIOItems;
618
  rd.Read(numIOItems);
619

620
  if (numIOItems != numItems) {
12✔
621
    rd.Pop();
622
    rd.Skip(chunkSize);
×
623
    return 1;
×
624
  }
625

626
  int errType = 0;
627

628
  for (uint32 i = 0; i < numItems; i++) {
180✔
629
    BinReaderRef rf(rd);
168✔
630
    JenHash valueNameHash;
168✔
631
    buint128 valueChunkSize;
632
    rd.Read(valueNameHash);
633
    rd.Read(valueChunkSize);
634
    rd.Push();
635

636
    ReflectorPureWrap ri(inst);
637
    auto &&rif = static_cast<ReflectorFriend &>(static_cast<Reflector &>(ri));
638
    const ReflType *cType = rif.GetReflectedType(valueNameHash);
168✔
639

640
    if (!cType) {
168✔
641
      rd.Pop();
642
      rd.Skip(valueChunkSize);
×
643
      continue;
644
    }
645

646
    errType =
647
        LoadDataItem(ri, rf, thisAddr + cType->offset, *cType, cType->index);
168✔
648

649
    if (errType) {
168✔
650
      rd.Pop();
651
      rd.Skip(valueChunkSize);
×
652
    }
653
  }
654

655
  return errType;
656
}
657

658
int ReflectorBinUtil::Load(Reflector &ri, BinReaderRef rd) {
3✔
659
  auto &&rif = static_cast<ReflectorFriend &>(ri);
660
  auto inst =
661
      static_cast<ReflectedInstanceFriend &&>(rif.GetReflectedInstance());
3✔
662
  auto refData = inst.Refl();
663
  JenHash clsHash;
3✔
664

665
  rd.Push();
666
  rd.Read(clsHash);
667

668
  if (clsHash != refData->classHash) {
3✔
669
    rd.Pop();
670
    return 1;
×
671
  }
672

673
  return LoadClass(inst, rd);
3✔
674
}
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