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

taosdata / TDengine / #4917

07 Jan 2026 03:52PM UTC coverage: 65.42% (+0.02%) from 65.402%
#4917

push

travis-ci

web-flow
merge: from main to 3.0 branch #34204

31 of 34 new or added lines in 2 files covered. (91.18%)

819 existing lines in 129 files now uncovered.

202679 of 309814 relevant lines covered (65.42%)

116724351.99 hits per line

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

68.01
/source/util/src/tutil.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
#define _DEFAULT_SOURCE
17
#include "tutil.h"
18
#include "tlog.h"
19
#include "regex.h"
20

21
void *tmemmem(const char *haystack, int32_t hlen, const char *needle, int32_t nlen) {
×
22
  const char *limit;
23

24
  if (nlen == 0 || hlen < nlen) {
×
25
    return NULL;
×
26
  }
27

28
  limit = haystack + hlen - nlen + 1;
×
29
  while ((haystack = (char *)memchr(haystack, needle[0], limit - haystack)) != NULL) {
×
30
    if (memcmp(haystack, needle, nlen) == 0) {
×
31
      return (void *)haystack;
×
32
    }
33
    haystack++;
×
34
  }
35
  return NULL;
×
36
}
37

38
int32_t strdequote(char *z) {
1,685,870,328✔
39
  if (z == NULL) {
1,685,870,328✔
40
    return 0;
×
41
  }
42

43
  int32_t quote = z[0];
1,685,870,328✔
44
  if (quote != '\'' && quote != '"' && quote != '`') {
1,685,874,234✔
45
    return (int32_t)strlen(z);
1,667,409,407✔
46
  }
47

48
  int32_t i = 1, j = 0;
18,464,827✔
49

50
  while (z[i] != 0) {
139,297,694✔
51
    if (z[i] == quote) {
139,288,428✔
52
      if (z[i + 1] == quote) {
18,497,148✔
53
        z[j++] = (char)quote;
34,017✔
54
        i++;
34,017✔
55
      } else {
56
        z[j++] = 0;
18,463,747✔
57
        return (j - 1);
18,464,698✔
58
      }
59
    } else {
60
      z[j++] = z[i];
120,797,681✔
61
    }
62

63
    i++;
120,832,867✔
64
  }
65

66
  return j + 1;  // only one quote, do nothing
×
67
}
68

69
size_t strtrim(char *z) {
2,381,789✔
70
  int32_t i = 0;
2,381,789✔
71
  int32_t j = 0;
2,381,789✔
72

73
  int32_t delta = 0;
2,381,789✔
74
  while (isspace(z[j])) {
2,405,376✔
75
    ++j;
23,587✔
76
  }
77

78
  if (z[j] == 0) {
2,381,789✔
79
    z[0] = 0;
12,951✔
80
    return 0;
12,951✔
81
  }
82

83
  delta = j;
2,368,838✔
84

85
  int32_t stop = 0;
2,368,838✔
86
  while (z[j] != 0) {
26,224,951✔
87
    if (isspace(z[j]) && stop == 0) {
23,856,113✔
88
      stop = j;
277,355✔
89
    } else if (!isspace(z[j]) && stop != 0) {
23,578,758✔
90
      stop = 0;
260,825✔
91
    }
92

93
    z[i++] = z[j++];
23,856,113✔
94
  }
95

96
  if (stop > 0) {
2,368,838✔
97
    z[stop - delta] = 0;
16,530✔
98
    return (stop - delta);
16,530✔
99
  } else if (j != i) {
2,352,308✔
100
    z[i] = 0;
10,260✔
101
  }
102

103
  return i;
2,352,308✔
104
}
105

106
char **strsplit(char *z, const char *delim, int32_t *num) {
×
107
  *num = 0;
×
108
  int32_t size = 4;
×
109

110
  char **split = taosMemoryMalloc(POINTER_BYTES * size);
×
111
  if (split == NULL) {
×
112
    return NULL;
×
113
  }
114

115
  for (char *p = strsep(&z, delim); p != NULL; p = strsep(&z, delim)) {
×
116
    size_t len = strlen(p);
×
117
    if (len == 0) {
×
118
      continue;
×
119
    }
120

121
    split[(*num)++] = p;
×
122
    if ((*num) >= size) {
×
123
      size = (size << 1);
×
124
      split = taosMemoryRealloc(split, POINTER_BYTES * size);
×
125
      if (split == NULL) {
×
126
        return NULL;
×
127
      }
128
    }
129
  }
130

131
  return split;
×
132
}
133

134
char *strnchr(const char *haystack, char needle, int32_t len, bool skipquote) {
1,563,463,499✔
135
  for (int32_t i = 0; i < len; ++i) {
2,147,483,647✔
136
    if (0 == haystack[i]) {
2,147,483,647✔
137
      break;
×
138
    }
139
    // skip the needle in quote, jump to the end of quoted string
140
    if (skipquote && (haystack[i] == '\'' || haystack[i] == '"')) {
2,147,483,647✔
141
      char quote = haystack[i++];
3,061,808✔
142
      while (i < len && haystack[i++] != quote)
18,368,094✔
143
        ;
144
      if (i >= len) {
3,061,682✔
145
        return NULL;
3,061,682✔
146
      }
147
    }
148

149
    if (haystack[i] == needle) {
2,147,483,647✔
150
      return (char *)&haystack[i];
241,859,416✔
151
    }
152
  }
153

154
  return NULL;
1,318,565,338✔
155
}
156

157
TdUcs4 *wcsnchr(const TdUcs4 *haystack, TdUcs4 needle, size_t len) {
6,485,444✔
158
  for (int32_t i = 0; i < len; ++i) {
17,312,856✔
159
    if (haystack[i] == needle) {
12,954,088✔
160
      return (TdUcs4 *)&haystack[i];
2,126,676✔
161
    }
162
  }
163

164
  return NULL;
4,358,768✔
165
}
166

167
char *strtolower(char *dst, const char *src) {
2,147,483,647✔
168
  int32_t esc = 0;
2,147,483,647✔
169
  char    quote = 0, *p = dst, c;
2,147,483,647✔
170

171
  for (c = *src++; c; c = *src++) {
2,147,483,647✔
172
    if (esc) {
2,147,483,647✔
173
      esc = 0;
×
174
    } else if (quote) {
2,147,483,647✔
175
      if (c == '\\') {
2,147,483,647✔
176
        esc = 1;
×
177
      } else if (c == quote) {
2,147,483,647✔
178
        quote = 0;
2,147,483,647✔
179
      }
180
    } else if (c >= 'A' && c <= 'Z') {
2,147,483,647✔
181
      c -= 'A' - 'a';
2,147,483,647✔
182
    } else if (c == '\'' || c == '"') {
2,147,483,647✔
183
      quote = c;
2,147,483,647✔
184
    }
185
    *p++ = c;
2,147,483,647✔
186
  }
187

188
  *p = 0;
2,147,483,647✔
189
  return dst;
2,147,483,647✔
190
}
191

192
char *strntolower(char *dst, const char *src, int32_t n) {
1,229,013,638✔
193
  int32_t esc = 0;
1,229,013,638✔
194
  char    quote = 0, *p = dst, c;
1,229,013,638✔
195

196
  if (n == 0) {
1,229,013,638✔
197
    *p = 0;
584,378✔
198
    return dst;
584,378✔
199
  }
200
  for (c = *src++; n-- > 0; c = *src++) {
2,147,483,647✔
201
    if (esc) {
2,147,483,647✔
202
      esc = 0;
3,673,695✔
203
    } else if (quote) {
2,147,483,647✔
204
      if (c == '\\') {
2,147,483,647✔
205
        esc = 1;
3,673,695✔
206
      } else if (c == quote) {
2,147,483,647✔
207
        quote = 0;
2,147,483,647✔
208
      }
209
    } else if (c >= 'A' && c <= 'Z') {
2,147,483,647✔
210
      c -= 'A' - 'a';
2,147,483,647✔
211
    } else if (c == '\'' || c == '"' || c == '`') {
2,147,483,647✔
212
      quote = c;
2,147,483,647✔
213
    }
214
    *p++ = c;
2,147,483,647✔
215
  }
216

217
  *p = 0;
1,228,428,844✔
218
  return dst;
1,228,429,612✔
219
}
220

221
char *strntolower_s(char *dst, const char *src, int32_t n) {
×
222
  char *p = dst, c;
×
223
  if (n == 0) {
×
224
    return NULL;
×
225
  }
226

227
  while (n-- > 0) {
×
228
    c = *src;
×
229
    if (c >= 'A' && c <= 'Z') {
×
230
      c -= 'A' - 'a';
×
231
    }
232
    *p++ = c;
×
233
    src++;
×
234
  }
235

236
  return dst;
×
237
}
238

239
char *paGetToken(char *string, char **token, int32_t *tokenLen) {
378,792,265✔
240
  char quote = 0;
378,792,265✔
241

242
  while (*string != 0) {
378,792,265✔
243
    if (*string == ' ' || *string == '\t') {
168,038,260✔
244
      ++string;
×
245
    } else {
246
      break;
247
    }
248
  }
249

250
  if (*string == '@') {
378,792,265✔
251
    quote = 1;
×
252
    string++;
×
253
  }
254

255
  *token = string;
378,792,265✔
256

257
  while (*string != 0) {
1,718,934,472✔
258
    if (*string == '@' && quote) {
1,424,787,928✔
259
      //*string = 0;
260
      ++string;
×
261
      break;
×
262
    }
263

264
    if (*string == '#' || *string == '\n' || *string == '\r') {
1,424,787,928✔
265
      *string = 0;
×
266
      break;
×
267
    }
268

269
    if ((*string == ' ' || *string == '\t') && !quote) {
1,424,787,928✔
270
      break;
271
    } else {
272
      ++string;
1,340,142,207✔
273
    }
274
  }
275

276
  *tokenLen = (int32_t)(string - *token);
378,792,265✔
277
  if (quote) {
378,792,265✔
278
    *tokenLen = *tokenLen - 1;
×
279
  }
280

281
  return string;
378,792,265✔
282
}
283

284
int64_t strnatoi(const char *num, int32_t len) {
890,319,184✔
285
  int64_t ret = 0, i, dig, base = 1;
890,319,184✔
286

287
  if (len > (int32_t)strlen(num)) {
890,319,184✔
288
    len = (int32_t)strlen(num);
×
289
  }
290

291
  if ((len > 2) && (num[0] == '0') && ((num[1] == 'x') || (num[1] == 'X'))) {
890,319,184✔
292
    for (i = len - 1; i >= 2; --i, base *= 16) {
×
293
      if (num[i] >= '0' && num[i] <= '9') {
×
294
        dig = (num[i] - '0');
×
295
      } else if (num[i] >= 'a' && num[i] <= 'f') {
×
296
        dig = num[i] - 'a' + 10;
×
297
      } else if (num[i] >= 'A' && num[i] <= 'F') {
×
298
        dig = num[i] - 'A' + 10;
×
299
      } else {
300
        return 0;
×
301
      }
302
      ret += dig * base;
×
303
    }
304
  } else {
305
    for (i = len - 1; i >= 0; --i, base *= 10) {
2,147,483,647✔
306
      if (num[i] >= '0' && num[i] <= '9') {
2,147,483,647✔
307
        dig = (num[i] - '0');
2,147,483,647✔
308
      } else {
309
        return 0;
×
310
      }
311
      ret += dig * base;
2,147,483,647✔
312
    }
313
  }
314

315
  return ret;
890,320,251✔
316
}
317

318
char *strbetween(char *string, char *begin, char *end) {
×
319
  char *result = NULL;
×
320
  char *_begin = strstr(string, begin);
×
321
  if (_begin != NULL) {
×
322
    char   *_end = strstr(_begin + strlen(begin), end);
×
323
    int32_t size = (int32_t)(_end - _begin);
×
324
    if (_end != NULL && size > 0) {
×
325
      result = (char *)taosMemoryCalloc(1, size);
×
326
      if (!result) {
×
327
        return NULL;
×
328
      }
329
      memcpy(result, _begin + strlen(begin), size - +strlen(begin));
×
330
    }
331
  }
332
  return result;
×
333
}
334

335
static const char hexstr[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
336

337
int32_t tintToHex(uint64_t val, char hex[]) {
×
338
  int32_t j = 0, k = 0;
×
339
  if (val == 0) {
×
340
    hex[j++] = hexstr[0];
×
341
    return j;
×
342
  }
343

344
  // ignore the initial 0
345
  while ((val & (((uint64_t)0xfL) << ((15 - k) * 4))) == 0) {
×
346
    k += 1;
×
347
  }
348

349
  for (j = 0; k < 16; ++k, ++j) {
×
350
    hex[j] = hexstr[(val & (((uint64_t)0xfL) << ((15 - k) * 4))) >> (15 - k) * 4];
×
351
  }
352

353
  return j;
×
354
}
355

356
int32_t titoa(uint64_t val, size_t radix, char str[]) {
2,147,483,647✔
357
  if (radix < 2 || radix > 16) {
2,147,483,647✔
358
    return 0;
×
359
  }
360

361
  char        buf[65] = {0};
2,147,483,647✔
362

363
  int32_t  i = 0;
2,147,483,647✔
364
  uint64_t v = val;
2,147,483,647✔
365
  do {
366
    buf[i++] = hexstr[v % radix];
2,147,483,647✔
367
    v /= radix;
2,147,483,647✔
368
  } while (v > 0);
2,147,483,647✔
369

370
  // reverse order
371
  for (int32_t j = 0; j < i; ++j) {
2,147,483,647✔
372
    str[j] = buf[i - j - 1];
2,147,483,647✔
373
  }
374

375
  return i;
2,147,483,647✔
376
}
377

378
int32_t taosByteArrayToHexStr(char bytes[], int32_t len, char str[]) {
698,177✔
379
  int32_t i;
380

381
  for (i = 0; i < len; i++) {
11,866,806✔
382
    str[i * 2] = hexstr[((bytes[i] >> 4u) & 0xF)];
11,168,670✔
383
    str[(i * 2) + 1] = hexstr[(bytes[i]) & 0x0F];
11,167,891✔
384
  }
385

386
  return 0;
698,136✔
387
}
388

389
int32_t taosHexStrToByteArray(char hexstr[], char bytes[]) {
×
390
  int32_t len, i;
391
  char    ch;
392
  // char *by;
393

394
  len = (int32_t)strlen((char *)hexstr) / 2;
×
395

396
  for (i = 0; i < len; i++) {
×
397
    ch = hexstr[i * 2];
×
398
    if (ch >= '0' && ch <= '9')
×
399
      bytes[i] = (char)(ch - '0');
×
400
    else if (ch >= 'A' && ch <= 'F')
×
401
      bytes[i] = (char)(ch - 'A' + 10);
×
402
    else if (ch >= 'a' && ch <= 'f')
×
403
      bytes[i] = (char)(ch - 'a' + 10);
×
404
    else
405
      return -1;
×
406

407
    ch = hexstr[i * 2 + 1];
×
408
    if (ch >= '0' && ch <= '9')
×
409
      bytes[i] = (char)((bytes[i] << 4) + (ch - '0'));
×
410
    else if (ch >= 'A' && ch <= 'F')
×
411
      bytes[i] = (char)((bytes[i] << 4) + (ch - 'A' + 10));
×
412
    else if (ch >= 'a' && ch <= 'f')
×
413
      bytes[i] = (char)((bytes[i] << 4) + (ch - 'a' + 10));
×
414
    else
415
      return -1;
×
416
  }
417

418
  return 0;
×
419
}
420

421
size_t tstrncspn(const char *str, size_t size, const char *reject, size_t rsize) {
2,147,483,647✔
422
  if (rsize == 0 || rsize == 1) {
2,147,483,647✔
UNCOV
423
    char *p = strnchr(str, reject[0], size, false);
×
424
    return (p == NULL) ? size : (p - str);
×
425
  }
426

427
  /* Use multiple small memsets to enable inlining on most targets.  */
428
  unsigned char  table[256];
2,147,483,647✔
429
  unsigned char *p = memset(table, 0, 64);
2,147,483,647✔
430
  memset(p + 64, 0, 64);
2,147,483,647✔
431
  memset(p + 128, 0, 64);
2,147,483,647✔
432
  memset(p + 192, 0, 64);
2,147,483,647✔
433

434
  unsigned char *s = (unsigned char *)reject;
2,147,483,647✔
435
  int32_t        index = 0;
2,147,483,647✔
436
  do {
437
    p[s[index++]] = 1;
2,147,483,647✔
438
  } while (index < rsize);
2,147,483,647✔
439

440
  s = (unsigned char *)str;
2,147,483,647✔
441
  int32_t times = size >> 2;
2,147,483,647✔
442
  if (times == 0) {
2,147,483,647✔
443
    for (int32_t i = 0; i < size; ++i) {
22,677,024✔
444
      if (p[s[i]]) {
20,192,305✔
445
        return i;
14,790,431✔
446
      }
447
    }
448

449
    return size;
2,484,719✔
450
  }
451

452
  index = 0;
2,147,483,647✔
453
  uint32_t c0, c1, c2, c3;
454
  for (int32_t i = 0; i < times; ++i, index += 4) {
2,147,483,647✔
455
    int32_t j = index;
2,147,483,647✔
456
    c0 = p[s[j]];
2,147,483,647✔
457
    c1 = p[s[j + 1]];
2,147,483,647✔
458
    c2 = p[s[j + 2]];
2,147,483,647✔
459
    c3 = p[s[j + 3]];
2,147,483,647✔
460

461
    if ((c0 | c1 | c2 | c3) != 0) {
2,147,483,647✔
462
      size_t count = i * 4;
2,147,483,647✔
463
      return (c0 | c1) != 0 ? count - c0 + 1 : count - c2 + 3;
2,147,483,647✔
464
    }
465
  }
466

467
  int32_t offset = times * 4;
8,919,025✔
468
  for (int32_t i = offset; i < size; ++i) {
22,399,533✔
469
    if (p[s[i]]) {
14,176,274✔
470
      return i;
697,823✔
471
    }
472
  }
473

474
  return size;
8,223,259✔
475
}
476

477
size_t twcsncspn(const TdUcs4 *wcs, size_t size, const TdUcs4 *reject, size_t rsize) {
2,174,958✔
478
  if (rsize == 0 || rsize == 1) {
2,174,958✔
479
    TdUcs4 *p = wcsnchr(wcs, reject[0], size);
×
480
    return (p == NULL) ? size : (p - wcs);
×
481
  }
482

483
  size_t index = 0;
2,174,958✔
484
  while ((index < size) && (wcsnchr(reject, wcs[index], rsize) == NULL)) {
6,533,726✔
485
    ++index;
4,358,768✔
486
  }
487

488
  return index;
2,174,958✔
489
}
490

491
int32_t parseCfgReal(const char *str, float *out) {
2,840✔
492
  float val;
493
  char  *endPtr;
2,788✔
494
  SET_ERRNO(0);
2,840✔
495
  val = taosStr2Float(str, &endPtr);
2,840✔
496
  if (str == endPtr || ERRNO == ERANGE || isnan(val)) {
2,840✔
497
    return terrno = TSDB_CODE_INVALID_CFG_VALUE;
320✔
498
  }
499
  while (isspace((unsigned char)*endPtr)) endPtr++;
2,840✔
500
  if (*endPtr != '\0') {
2,520✔
501
    return terrno = TSDB_CODE_INVALID_CFG_VALUE;
480✔
502
  }
503
  *out = val;
2,040✔
504
  return TSDB_CODE_SUCCESS;
2,040✔
505
}
506

507
bool tIsValidFileName(const char *fileName, const char *pattern) {
251✔
508
  const char *fileNamePattern = "^[a-zA-Z0-9_.-]+$";
251✔
509

510
  regex_t fileNameReg;
86✔
511

512
  if (pattern) fileNamePattern = pattern;
251✔
513

514
  if (regcomp(&fileNameReg, fileNamePattern, REG_EXTENDED) != 0) {
251✔
515
    fprintf(stderr, "failed to compile file name pattern:%s\n", fileNamePattern);
×
516
    return false;
×
517
  }
518

519
  int32_t code = regexec(&fileNameReg, fileName, 0, NULL, 0);
251✔
520
  regfree(&fileNameReg);
251✔
521
  if (code != 0) {
251✔
522
    return false;
15✔
523
  }
524
  return true;
236✔
525
}
526

527
bool tIsValidFilePath(const char *filePath, const char *pattern) {
191✔
528
  const char *filePathPattern = "^[a-zA-Z0-9:/\\_.-]+$";
191✔
529
  return tIsValidFileName(filePath, pattern ? pattern : filePathPattern);
191✔
530
}
531

532
bool taosIsBigChar(char c) {
1,093,402✔
533
  if (c >= 'A' && c <= 'Z') {
1,093,402✔
534
    return true;
33,589✔
535
  } else {
536
    return false;
1,059,813✔
537
  }
538
}
539

540
bool taosIsSmallChar(char c) {
1,059,813✔
541
  if (c >= 'a' && c <= 'z') {
1,059,813✔
542
    return true;
511,563✔
543
  } else {
544
    return false;
548,250✔
545
  }
546
}
547

548
bool taosIsNumberChar(char c) {
548,250✔
549
  if (c >= '0' && c <= '9') {
548,250✔
550
    return true;
261,653✔
551
  } else {
552
    return false;
286,597✔
553
  }
554
}
555

556
bool taosIsSpecialChar(char c) {
286,597✔
557
  switch (c) {
286,597✔
558
    case '!':
285,981✔
559
    case '@':
560
    case '#':
561
    case '$':
562
    case '%':
563
    case '^':
564
    case '&':
565
    case '*':
566
    case '(':
567
    case ')':
568
    case '-':
569
    case '_':
570
    case '+':
571
    case '=':
572
    case '[':
573
    case ']':
574
    case '{':
575
    case '}':
576
    case ':':
577
    case ';':
578
    case '>':
579
    case '<':
580
    case '?':
581
    case '|':
582
    case '~':
583
    case ',':
584
    case '.':
585
    return true;
285,981✔
586
    default:
616✔
587
    return false;
616✔
588
  }
589
}
590

591
// check if the string is a complex string, a complex string contains
592
// at least 3 types of characters: upper, lower, digit, special
593
bool taosIsComplexString(const char* str) {
100,603✔
594
  int hasUpper = 0, hasLower = 0, hasDigit = 0, hasSpecial = 0;
100,603✔
595

596
  for (char c = *str; c != 0; c = *(++str)) {
1,193,389✔
597
    if (taosIsBigChar(c)) {
1,093,402✔
598
      hasUpper = 1;
33,589✔
599
    } else if (taosIsSmallChar(c)) {
1,059,813✔
600
      hasLower = 1;
511,563✔
601
    } else if (taosIsNumberChar(c)) {
548,250✔
602
      hasDigit = 1;
261,653✔
603
    } else if (taosIsSpecialChar(c)) {
286,597✔
604
      hasSpecial = 1;
285,981✔
605
    } else {
606
      return false;
616✔
607
    }
608
  }
609

610
  return (hasUpper + hasLower + hasDigit + hasSpecial) >= 3;
99,987✔
611
}
612

613

614
void tTrimMountPrefix(char *fullName) {
36,985✔
615
  if (fullName == NULL) {
36,985✔
616
    return;
×
617
  }
618
  char *dot_pos = fullName;
36,985✔
619
  while (*dot_pos && *dot_pos != '.') ++dot_pos;
73,970✔
620
  if (!*dot_pos) return;
36,985✔
621

622
  char *underscore_pos = dot_pos + 1;
36,985✔
623
  while (*underscore_pos && *underscore_pos != '_') ++underscore_pos;
184,925✔
624
  if (!*underscore_pos) return;
36,985✔
625

626
  char *src = underscore_pos + 1;
36,985✔
627
  char *dst = dot_pos + 1;
36,985✔
628

629
  while (*src) *dst++ = *src++;
310,151✔
630

631
  *dst = '\0';
36,985✔
632
}
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