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

taosdata / TDengine / #4768

01 Oct 2025 04:06AM UTC coverage: 57.85% (-0.8%) from 58.606%
#4768

push

travis-ci

web-flow
Merge pull request #33171 from taosdata/merge/3.3.6tomain

merge: from 3.3.6 to main branch

137167 of 302743 branches covered (45.31%)

Branch coverage included in aggregate %.

15 of 20 new or added lines in 2 files covered. (75.0%)

12125 existing lines in 175 files now uncovered.

208282 of 294403 relevant lines covered (70.75%)

5618137.93 hits per line

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

51.4
/source/util/src/tobjpool.c
1
/*
2
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
3
 *
4
 * This program is free software: you can use, redistribute, and/or modify
5
 * it under the terms of the GNU Affero General Public License, version 3
6
 * or later ("AGPL"), as published by the Free Software Foundation.
7
 *
8
 * This program is distributed in the hope that it will be useful, but WITHOUT
9
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
 * FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * You should have received a copy of the GNU Affero General Public License
13
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14
 */
15

16
#include "tobjpool.h"
17

18
#define TOBJPOOL_MIN_SIZE     4
19
#define BOUNDARY_SIZE         1024 * 1024 * 1024  // 1G
20
#define BOUNDARY_SMALL_FACTOR 1.2
21
#define BOUNDARY_BIG_FACTOR   2
22

23
int32_t taosObjPoolInit(SObjPool *pPool, int64_t cap, size_t objSize) {
1,727✔
24
  if (pPool == NULL || objSize == 0) {
1,727!
25
    return TSDB_CODE_INVALID_PARA;
×
26
  }
27

28
  if (cap < TOBJPOOL_MIN_SIZE) {
1,727!
29
    cap = TOBJPOOL_MIN_SIZE;
×
30
  }
31

32
  pPool->nodeSize = sizeof(SObjPoolNode) + objSize;
1,727✔
33
  pPool->pData = taosMemoryCalloc(cap, pPool->nodeSize);
1,727!
34
  if (pPool->pData == NULL) {
1,727!
35
    return terrno;
×
36
  }
37

38
  // initialize free list
39
  for (int64_t i = 0; i < cap; i++) {
1,767,115✔
40
    SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, i);
1,765,388✔
41
    pNode->prevIdx = i - 1;
1,765,388✔
42
    pNode->nextIdx = i + 1;
1,765,388✔
43
  }
44
  TOBJPOOL_GET_NODE(pPool, 0)->prevIdx = TOBJPOOL_INVALID_IDX;
1,727✔
45
  TOBJPOOL_GET_NODE(pPool, cap - 1)->nextIdx = TOBJPOOL_INVALID_IDX;
1,727✔
46
  pPool->freeHeadIdx = 0;
1,727✔
47
  pPool->freeTailIdx = cap - 1;
1,727✔
48

49
  pPool->size = 0;
1,727✔
50
  pPool->capacity = cap;
1,727✔
51

52
  return TSDB_CODE_SUCCESS;
1,727✔
53
}
54

55
int32_t taosObjPoolEnsureCap(SObjPool *pPool, int64_t newCap) {
94✔
56
  if (newCap < pPool->capacity) {
94!
57
    return TSDB_CODE_SUCCESS;
×
58
  }
59
  double  factor = (newCap * pPool->nodeSize > BOUNDARY_SIZE) ? BOUNDARY_SMALL_FACTOR : BOUNDARY_BIG_FACTOR;
94!
60
  int64_t tsize = (pPool->capacity * factor);
94✔
61
  while (newCap > tsize) {
94!
62
    int64_t newSize = tsize * factor;
×
63
    tsize = (newSize == tsize) ? (tsize + 2) : newSize;
×
64
  }
65

66
  char *p = taosMemoryRealloc(pPool->pData, tsize * pPool->nodeSize);
94!
67
  if (p == NULL) {
94!
68
    return terrno;
×
69
  }
70
  pPool->pData = p;
94✔
71

72
  // append new nodes to free list
73
  for (int64_t i = pPool->capacity; i < tsize; i++) {
390,116✔
74
    SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, i);
390,022✔
75
    pNode->prevIdx = i - 1;
390,022✔
76
    pNode->nextIdx = i + 1;
390,022✔
77
  }
78
  TOBJPOOL_GET_NODE(pPool, pPool->capacity)->prevIdx = pPool->freeTailIdx;
94✔
79
  TOBJPOOL_GET_NODE(pPool, tsize - 1)->nextIdx = TOBJPOOL_INVALID_IDX;
94✔
80
  if (pPool->freeTailIdx != TOBJPOOL_INVALID_IDX) {
94!
81
    TOBJPOOL_GET_NODE(pPool, pPool->freeTailIdx)->nextIdx = pPool->capacity;
×
82
  } else {
83
    pPool->freeHeadIdx = pPool->capacity;
94✔
84
  }
85
  pPool->freeTailIdx = tsize - 1;
94✔
86

87
  pPool->capacity = tsize;
94✔
88
  return TSDB_CODE_SUCCESS;
94✔
89
}
90

91
void taosObjPoolDestroy(SObjPool *pPool) {
1,724✔
92
  if (pPool != NULL) {
1,724!
93
    if (pPool->size != 0) {
1,724!
94
      uWarn("destroying non-empty obj pool, size:%" PRId64 ", capacity:%" PRId64, pPool->size, pPool->capacity);
×
95
    }
96
    taosMemoryFreeClear(pPool->pData);
1,724!
97
    pPool->freeHeadIdx = pPool->freeTailIdx = TOBJPOOL_INVALID_IDX;
1,724✔
98
    pPool->size = 0;
1,724✔
99
    pPool->capacity = 0;
1,724✔
100
  }
101
}
1,724✔
102

103
int32_t taosObjListInit(SObjList *pList, SObjPool *pPool) {
3,616✔
104
  if (pList == NULL || pPool == NULL) {
3,616!
105
    return TSDB_CODE_INVALID_PARA;
×
106
  }
107
  pList->pPool = pPool;
3,616✔
108
  pList->neles = 0;
3,616✔
109
  pList->headIdx = TOBJPOOL_INVALID_IDX;
3,616✔
110
  pList->tailIdx = TOBJPOOL_INVALID_IDX;
3,616✔
111
  return TSDB_CODE_SUCCESS;
3,616✔
112
}
113

114
void taosObjListClear(SObjList *pList) {
15,966✔
115
  if (pList == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
15,966!
116
    return;
11,732✔
117
  }
118

119
  SObjPool     *pPool = pList->pPool;
4,234✔
120
  SObjPoolNode *pTail = TOBJPOOL_GET_NODE(pPool, pList->tailIdx);
4,234✔
121
  pTail->nextIdx = pPool->freeHeadIdx;
4,234✔
122
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
4,234!
123
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = pList->tailIdx;
4,234✔
124
  } else {
125
    pPool->freeTailIdx = pList->tailIdx;
×
126
  }
127
  pPool->freeHeadIdx = pList->headIdx;
4,234✔
128
  pPool->size -= pList->neles;
4,234✔
129

130
  pList->headIdx = pList->tailIdx = TOBJPOOL_INVALID_IDX;
4,234✔
131
  pList->neles = 0;
4,234✔
132
}
133

134
void taosObjListClearEx(SObjList *pList, FDelete fp) {
688✔
135
  if (pList == NULL || pList->pPool == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
688!
136
    return;
685✔
137
  }
138

139
  if (fp != NULL) {
3!
140
    SObjPool *pPool = pList->pPool;
3✔
141
    int64_t   idx = pList->headIdx;
3✔
142
    while (idx != TOBJPOOL_INVALID_IDX) {
6✔
143
      SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, idx);
3✔
144
      fp(TOBJPOOL_NODE_GET_OBJ(pNode));
3✔
145
      idx = pNode->nextIdx;
3✔
146
    }
147
  }
148

149
  taosObjListClear(pList);
3✔
150
}
151

152
int32_t taosObjListPrepend(SObjList *pList, const void *pData) {
×
153
  if (pList == NULL) {
×
154
    return TSDB_CODE_INVALID_PARA;
×
155
  }
156

157
  SObjPool *pPool = pList->pPool;
×
158
  if (pPool->freeHeadIdx == TOBJPOOL_INVALID_IDX) {
×
159
    int32_t code = taosObjPoolEnsureCap(pPool, pPool->capacity + 1);
×
160
    if (code != TSDB_CODE_SUCCESS) {
×
161
      return code;
×
162
    }
163
  }
164

165
  // allocate a new node from pool
166
  int64_t       newIdx = pPool->freeHeadIdx;
×
167
  SObjPoolNode *pNewNode = TOBJPOOL_GET_NODE(pPool, newIdx);
×
168
  pPool->freeHeadIdx = pNewNode->nextIdx;
×
169
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
×
170
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = TOBJPOOL_INVALID_IDX;
×
171
  } else {
172
    pPool->freeTailIdx = TOBJPOOL_INVALID_IDX;
×
173
  }
174
  pPool->size += 1;
×
175

176
  TAOS_MEMCPY(TOBJPOOL_NODE_GET_OBJ(pNewNode), pData, pPool->nodeSize - sizeof(SObjPoolNode));
×
177

178
  // insert the new node to the head of the list
179
  pNewNode->prevIdx = TOBJPOOL_INVALID_IDX;
×
180
  pNewNode->nextIdx = pList->headIdx;
×
181
  if (pList->headIdx != TOBJPOOL_INVALID_IDX) {
×
182
    TOBJPOOL_GET_NODE(pPool, pList->headIdx)->prevIdx = newIdx;
×
183
  } else {
184
    pList->tailIdx = newIdx;
×
185
  }
186
  pList->headIdx = newIdx;
×
187
  pList->neles += 1;
×
188

189
  return TSDB_CODE_SUCCESS;
×
190
}
191

192
int32_t taosObjListAppend(SObjList *pList, const void *pData) {
3,177,032✔
193
  if (pList == NULL) {
3,177,032!
194
    return TSDB_CODE_INVALID_PARA;
×
195
  }
196

197
  SObjPool *pPool = pList->pPool;
3,177,032✔
198
  if (pPool->freeHeadIdx == TOBJPOOL_INVALID_IDX) {
3,177,032✔
199
    int32_t code = taosObjPoolEnsureCap(pPool, pPool->capacity + 1);
94✔
200
    if (code != TSDB_CODE_SUCCESS) {
94!
201
      return code;
×
202
    }
203
  }
204

205
  // allocate a new node from pool
206
  int64_t       newIdx = pPool->freeHeadIdx;
3,177,032✔
207
  SObjPoolNode *pNewNode = TOBJPOOL_GET_NODE(pPool, newIdx);
3,177,032✔
208
  pPool->freeHeadIdx = pNewNode->nextIdx;
3,177,032✔
209
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
3,177,032!
210
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = TOBJPOOL_INVALID_IDX;
3,178,022✔
211
  } else {
212
    pPool->freeTailIdx = TOBJPOOL_INVALID_IDX;
×
213
  }
214
  pPool->size += 1;
3,177,032✔
215

216
  TAOS_MEMCPY(TOBJPOOL_NODE_GET_OBJ(pNewNode), pData, pPool->nodeSize - sizeof(SObjPoolNode));
3,177,032✔
217

218
  // insert the new node to the tail of the list
219
  pNewNode->nextIdx = TOBJPOOL_INVALID_IDX;
3,177,032✔
220
  pNewNode->prevIdx = pList->tailIdx;
3,177,032✔
221
  if (pList->tailIdx != TOBJPOOL_INVALID_IDX) {
3,177,032✔
222
    TOBJPOOL_GET_NODE(pPool, pList->tailIdx)->nextIdx = newIdx;
3,173,485✔
223
  } else {
224
    pList->headIdx = newIdx;
3,547✔
225
  }
226
  pList->tailIdx = newIdx;
3,177,032✔
227
  pList->neles += 1;
3,177,032✔
228

229
  return TSDB_CODE_SUCCESS;
3,177,032✔
230
}
231

232
void taosObjListPopHeadTo(SObjList *pList, void *pObj, int32_t nele) {
972✔
233
  if (pList == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
972!
234
    return;
×
235
  }
236

237
  if (pObj == NULL) {
972!
238
    taosObjListClear(pList);
972✔
239
    return;
972✔
240
  }
241

UNCOV
242
  SObjPool     *pPool = pList->pPool;
×
UNCOV
243
  SObjPoolNode *pNode = TOBJPOOL_OBJ_GET_NODE(pObj);
×
UNCOV
244
  int64_t       idx = TOBJPOOL_GET_IDX(pPool, pNode);
×
UNCOV
245
  if (idx < 0 || idx >= pPool->capacity || pNode->prevIdx == TOBJPOOL_INVALID_IDX) {
×
UNCOV
246
    return;
×
247
  }
UNCOV
248
  SObjPoolNode *pPrevNode = TOBJPOOL_GET_NODE(pPool, pNode->prevIdx);
×
249

250
  // add all nodes before pNode back to pool
UNCOV
251
  pPrevNode->nextIdx = pPool->freeHeadIdx;
×
UNCOV
252
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
×
UNCOV
253
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = pPrevNode->prevIdx;
×
254
  } else {
UNCOV
255
    pPool->freeTailIdx = pPrevNode->prevIdx;
×
256
  }
UNCOV
257
  pPool->freeHeadIdx = pList->headIdx;
×
UNCOV
258
  pPool->size -= nele;
×
259

260
  // remove the nodes from the list
UNCOV
261
  pNode->prevIdx = TOBJPOOL_INVALID_IDX;
×
262
  pList->headIdx = idx;
×
263
  pList->neles -= nele;
×
264
}
265

266
void taosObjListPopHead(SObjList *pList) {
1,473,802✔
267
  if (pList == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
1,473,802!
268
    return;
×
269
  }
270

271
  SObjPool     *pPool = pList->pPool;
1,473,802✔
272
  int64_t       idx = pList->headIdx;
1,473,802✔
273
  SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, idx);
1,473,802✔
274

275
  // remove the node from the list
276
  pList->headIdx = pNode->nextIdx;
1,473,802✔
277
  if (pList->headIdx != TOBJPOOL_INVALID_IDX) {
1,473,802✔
278
    TOBJPOOL_GET_NODE(pPool, pList->headIdx)->prevIdx = TOBJPOOL_INVALID_IDX;
1,471,706✔
279
  } else {
280
    pList->tailIdx = TOBJPOOL_INVALID_IDX;
2,096✔
281
  }
282
  pList->neles -= 1;
1,473,802✔
283

284
  // add the node back to pool
285
  pNode->prevIdx = TOBJPOOL_INVALID_IDX;
1,473,802✔
286
  pNode->nextIdx = pPool->freeHeadIdx;
1,473,802✔
287
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
1,473,802!
288
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = idx;
1,473,802✔
289
  } else {
UNCOV
290
    pPool->freeTailIdx = idx;
×
291
  }
292
  pPool->freeHeadIdx = idx;
1,473,802✔
293
  pPool->size -= 1;
1,473,802✔
294
}
295

296
void taosObjListPopHeadEx(SObjList *pList, FDelete fp) {
×
297
  if (pList == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
×
298
    return;
×
299
  }
300

301
  if (fp != NULL) {
×
UNCOV
302
    SObjPool     *pPool = pList->pPool;
×
303
    int64_t       idx = pList->headIdx;
×
304
    SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, idx);
×
UNCOV
305
    fp(TOBJPOOL_NODE_GET_OBJ(pNode));
×
306
  }
307

308
  taosObjListPopHead(pList);
×
309
}
310

UNCOV
311
void taosObjListPopTail(SObjList *pList) {
×
312
  if (pList == NULL || pList->tailIdx == TOBJPOOL_INVALID_IDX) {
×
313
    return;
×
314
  }
315

316
  SObjPool     *pPool = pList->pPool;
×
UNCOV
317
  int64_t       idx = pList->tailIdx;
×
UNCOV
318
  SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, idx);
×
319

320
  // remove the node from the list
UNCOV
321
  pList->tailIdx = pNode->prevIdx;
×
UNCOV
322
  if (pList->tailIdx != TOBJPOOL_INVALID_IDX) {
×
UNCOV
323
    TOBJPOOL_GET_NODE(pPool, pList->tailIdx)->nextIdx = TOBJPOOL_INVALID_IDX;
×
324
  } else {
UNCOV
325
    pList->headIdx = TOBJPOOL_INVALID_IDX;
×
326
  }
UNCOV
327
  pList->neles -= 1;
×
328

329
  // add the node back to pool
UNCOV
330
  pNode->prevIdx = TOBJPOOL_INVALID_IDX;
×
331
  pNode->nextIdx = pPool->freeHeadIdx;
×
UNCOV
332
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
×
UNCOV
333
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = idx;
×
334
  } else {
UNCOV
335
    pPool->freeTailIdx = idx;
×
336
  }
UNCOV
337
  pPool->freeHeadIdx = idx;
×
UNCOV
338
  pPool->size -= 1;
×
339
}
340

UNCOV
341
void taosObjListPopTailEx(SObjList *pList, FDelete fp) {
×
UNCOV
342
  if (pList == NULL || pList->tailIdx == TOBJPOOL_INVALID_IDX) {
×
UNCOV
343
    return;
×
344
  }
345

UNCOV
346
  if (fp != NULL) {
×
UNCOV
347
    SObjPool     *pPool = pList->pPool;
×
UNCOV
348
    int64_t       idx = pList->tailIdx;
×
UNCOV
349
    SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pPool, idx);
×
UNCOV
350
    fp(TOBJPOOL_NODE_GET_OBJ(pNode));
×
351
  }
352

353
  taosObjListPopTail(pList);
×
354
}
355

356
void taosObjListPopObj(SObjList *pList, void *pObj) {
8,092✔
357
  if (pList == NULL || pObj == NULL) {
8,092!
UNCOV
358
    return;
×
359
  }
360

361
  SObjPool     *pPool = pList->pPool;
8,092✔
362
  SObjPoolNode *pNode = TOBJPOOL_OBJ_GET_NODE(pObj);
8,092✔
363
  int64_t       idx = TOBJPOOL_GET_IDX(pPool, pNode);
8,092✔
364
  if (idx < 0 || idx >= pPool->capacity) {
8,092!
365
    return;
×
366
  }
367

368
  // remove the node from the list
369
  if (pNode->prevIdx != TOBJPOOL_INVALID_IDX) {
8,092✔
370
    TOBJPOOL_GET_NODE(pPool, pNode->prevIdx)->nextIdx = pNode->nextIdx;
131✔
371
  } else {
372
    pList->headIdx = pNode->nextIdx;
7,961✔
373
  }
374
  if (pNode->nextIdx != TOBJPOOL_INVALID_IDX) {
8,092✔
375
    TOBJPOOL_GET_NODE(pPool, pNode->nextIdx)->prevIdx = pNode->prevIdx;
6,643✔
376
  } else {
377
    pList->tailIdx = pNode->prevIdx;
1,449✔
378
  }
379
  pList->neles -= 1;
8,092✔
380

381
  // add the node back to pool
382
  pNode->prevIdx = TOBJPOOL_INVALID_IDX;
8,092✔
383
  pNode->nextIdx = pPool->freeHeadIdx;
8,092✔
384
  if (pPool->freeHeadIdx != TOBJPOOL_INVALID_IDX) {
8,092!
385
    TOBJPOOL_GET_NODE(pPool, pPool->freeHeadIdx)->prevIdx = idx;
8,092✔
386
  } else {
UNCOV
387
    pPool->freeTailIdx = idx;
×
388
  }
389
  pPool->freeHeadIdx = idx;
8,092✔
390
  pPool->size -= 1;
8,092✔
391
}
392

UNCOV
393
void taosObjListPopObjEx(SObjList *pList, void *pObj, FDelete fp) {
×
UNCOV
394
  if (pList == NULL || pObj == NULL) {
×
UNCOV
395
    return;
×
396
  }
397

UNCOV
398
  if (fp != NULL) {
×
UNCOV
399
    fp(pObj);
×
400
  }
401

UNCOV
402
  taosObjListPopObj(pList, pObj);
×
403
}
404

405
void *taosObjListGetHead(SObjList *pList) {
8,374✔
406
  if (pList == NULL || pList->headIdx == TOBJPOOL_INVALID_IDX) {
8,374!
UNCOV
407
    return NULL;
×
408
  }
409

410
  SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pList->pPool, pList->headIdx);
8,374✔
411
  return TOBJPOOL_NODE_GET_OBJ(pNode);
8,374✔
412
}
413

UNCOV
414
void *taosObjListGetTail(SObjList *pList) {
×
UNCOV
415
  if (pList == NULL || pList->tailIdx == TOBJPOOL_INVALID_IDX) {
×
UNCOV
416
    return NULL;
×
417
  }
418

UNCOV
419
  SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pList->pPool, pList->tailIdx);
×
UNCOV
420
  return TOBJPOOL_NODE_GET_OBJ(pNode);
×
421
}
422

423
void taosObjListInitIter(SObjList *pList, SObjListIter *pIter, EObjListIterDirection direction) {
76,698✔
424
  if (pIter == NULL) {
76,698!
UNCOV
425
    return;
×
426
  }
427

428
  pIter->direction = direction;
76,698✔
429
  if (pList == NULL) {
76,698!
UNCOV
430
    pIter->pPool = NULL;
×
UNCOV
431
    pIter->nextIdx = TOBJPOOL_INVALID_IDX;
×
432
  } else {
433
    pIter->pPool = pList->pPool;
76,698✔
434
    pIter->nextIdx = (direction == TOBJLIST_ITER_FORWARD) ? pList->headIdx : pList->tailIdx;
76,698!
435
  }
436
}
437

438
void *taosObjListIterNext(SObjListIter *pIter) {
151,056,875✔
439
  if (pIter == NULL || pIter->nextIdx == TOBJPOOL_INVALID_IDX) {
151,056,875!
440
    return NULL;
15,369✔
441
  }
442

443
  SObjPoolNode *pNode = TOBJPOOL_GET_NODE(pIter->pPool, pIter->nextIdx);
151,041,506✔
444
  void         *pObj = TOBJPOOL_NODE_GET_OBJ(pNode);
151,041,506✔
445
  pIter->nextIdx = (pIter->direction == TOBJLIST_ITER_FORWARD) ? pNode->nextIdx : pNode->prevIdx;
151,041,506!
446

447
  return pObj;
151,041,506✔
448
}
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