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

taosdata / TDengine / #4989

17 Mar 2026 03:40AM UTC coverage: 68.862% (-7.0%) from 75.821%
#4989

push

travis-ci

web-flow
Merge c54c3d8de into e29fadda2

31 of 44 new or added lines in 2 files covered. (70.45%)

527 existing lines in 3 files now uncovered.

174201 of 252971 relevant lines covered (68.86%)

283829025.96 hits per line

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

93.44
/source/os/src/osTime.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 ALLOW_FORBID_FUNC
17
#define _BSD_SOURCE
18

19
#ifdef DARWIN
20
#define _XOPEN_SOURCE
21
#else
22
#define _XOPEN_SOURCE 500
23
#endif
24

25
#define _DEFAULT_SOURCE
26

27
#include "os.h"
28

29
#if defined(WINDOWS) || defined(TD_ASTRA)
30

31
#include <stdlib.h>
32
#include <string.h>
33
#include <time.h>
34
// #define TM_YEAR_BASE 1970 //origin
35
#define TM_YEAR_BASE 1900  // slguan
36

37
// This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
38
// until 00:00:00 January 1, 1970
39
static const uint64_t TIMEEPOCH = ((uint64_t)116444736000000000ULL);
40

41
/*
42
 * We do not implement alternate representations. However, we always
43
 * check whether a given modifier is allowed for a certain conversion.
44
 */
45
#define ALT_E 0x01
46
#define ALT_O 0x02
47
#define LEGAL_ALT(x)                   \
48
  {                                    \
49
    if (alt_format & ~(x)) return (0); \
50
  }
51

52
static int conv_num(const char **buf, int *dest, int llim, int ulim) {
53
  int result = 0;
54

55
  /* The limit also determines the number of valid digits. */
56
  int rulim = ulim;
57

58
  if (**buf < '0' || **buf > '9') return (0);
59

60
  do {
61
    result *= 10;
62
    result += *(*buf)++ - '0';
63
    rulim /= 10;
64
  } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
65

66
  if (result < llim || result > ulim) return (0);
67

68
  *dest = result;
69
  return (1);
70
}
71

72
static const char *day[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
73
static const char *abday[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
74
static const char *mon[12] = {"January", "February", "March",     "April",   "May",      "June",
75
                              "July",    "August",   "September", "October", "November", "December"};
76
static const char *abmon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
77
static const char *am_pm[2] = {"AM", "PM"};
78

79
#else
80
#include <sys/time.h>
81
#endif
82

83
char *taosStrpTime(const char *buf, const char *fmt, struct tm *tm) {
2,147,483,647✔
84
  if (!buf || !fmt || !tm) return NULL;
2,147,483,647✔
85
#if defined(WINDOWS) || defined(TD_ASTRA) 
86
  char        c;
87
  const char *bp;
88
  size_t      len = 0;
89
  int         alt_format, i, split_year = 0;
90

91
  bp = buf;
92

93
  while ((c = *fmt) != '\0') {
94
    /* Clear `alternate' modifier prior to new conversion. */
95
    alt_format = 0;
96

97
    /* Eat up white-space. */
98
    if (isspace(c)) {
99
      while (isspace(*bp)) bp++;
100

101
      fmt++;
102
      continue;
103
    }
104

105
    if ((c = *fmt++) != '%') goto literal;
106

107
  again:
108
    switch (c = *fmt++) {
109
      case '%': /* "%%" is converted to "%". */
110
      literal:
111
        if (c != *bp++) return (0);
112
        break;
113

114
        /*
115
         * "Alternative" modifiers. Just set the appropriate flag
116
         * and start over again.
117
         */
118
      case 'E': /* "%E?" alternative conversion modifier. */
119
        LEGAL_ALT(0);
120
        alt_format |= ALT_E;
121
        goto again;
122

123
      case 'O': /* "%O?" alternative conversion modifier. */
124
        LEGAL_ALT(0);
125
        alt_format |= ALT_O;
126
        goto again;
127

128
        /*
129
         * "Complex" conversion rules, implemented through recursion.
130
         */
131
      case 'c': /* Date and time, using the locale's format. */
132
        LEGAL_ALT(ALT_E);
133
        if (!(bp = taosStrpTime(bp, "%x %X", tm))) return (0);
134
        break;
135

136
      case 'D': /* The date as "%m/%d/%y". */
137
        LEGAL_ALT(0);
138
        if (!(bp = taosStrpTime(bp, "%m/%d/%y", tm))) return (0);
139
        break;
140

141
      case 'R': /* The time as "%H:%M". */
142
        LEGAL_ALT(0);
143
        if (!(bp = taosStrpTime(bp, "%H:%M", tm))) return (0);
144
        break;
145

146
      case 'r': /* The time in 12-hour clock representation. */
147
        LEGAL_ALT(0);
148
        if (!(bp = taosStrpTime(bp, "%I:%M:%S %p", tm))) return (0);
149
        break;
150

151
      case 'T': /* The time as "%H:%M:%S". */
152
        LEGAL_ALT(0);
153
        if (!(bp = taosStrpTime(bp, "%H:%M:%S", tm))) return (0);
154
        break;
155

156
      case 'X': /* The time, using the locale's format. */
157
        LEGAL_ALT(ALT_E);
158
        if (!(bp = taosStrpTime(bp, "%H:%M:%S", tm))) return (0);
159
        break;
160

161
      case 'x': /* The date, using the locale's format. */
162
        LEGAL_ALT(ALT_E);
163
        if (!(bp = taosStrpTime(bp, "%m/%d/%y", tm))) return (0);
164
        break;
165

166
        /*
167
         * "Elementary" conversion rules.
168
         */
169
      case 'A': /* The day of week, using the locale's form. */
170
      case 'a':
171
        LEGAL_ALT(0);
172
        for (i = 0; i < 7; i++) {
173
          /* Full name. */
174
          len = strlen(day[i]);
175
          if (strncmp(day[i], bp, len) == 0) break;
176

177
          /* Abbreviated name. */
178
          len = strlen(abday[i]);
179
          if (strncmp(abday[i], bp, len) == 0) break;
180
        }
181

182
        /* Nothing matched. */
183
        if (i == 7) return (0);
184

185
        tm->tm_wday = i;
186
        bp += len;
187
        break;
188

189
      case 'B': /* The month, using the locale's form. */
190
      case 'b':
191
      case 'h':
192
        LEGAL_ALT(0);
193
        for (i = 0; i < 12; i++) {
194
          /* Full name. */
195
          len = strlen(mon[i]);
196
          if (strncmp(mon[i], bp, len) == 0) break;
197

198
          /* Abbreviated name. */
199
          len = strlen(abmon[i]);
200
          if (strncmp(abmon[i], bp, len) == 0) break;
201
        }
202

203
        /* Nothing matched. */
204
        if (i == 12) return (0);
205

206
        tm->tm_mon = i;
207
        bp += len;
208
        break;
209

210
      case 'C': /* The century number. */
211
        LEGAL_ALT(ALT_E);
212
        if (!(conv_num(&bp, &i, 0, 99))) return (0);
213

214
        if (split_year) {
215
          tm->tm_year = (tm->tm_year % 100) + (i * 100);
216
        } else {
217
          tm->tm_year = i * 100;
218
          split_year = 1;
219
        }
220
        break;
221

222
      case 'd': /* The day of month. */
223
      case 'e':
224
        LEGAL_ALT(ALT_O);
225
        if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) return (0);
226
        break;
227

228
      case 'k': /* The hour (24-hour clock representation). */
229
        LEGAL_ALT(0);
230
        /* FALLTHROUGH */
231
      case 'H':
232
        LEGAL_ALT(ALT_O);
233
        if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) return (0);
234
        break;
235

236
      case 'l': /* The hour (12-hour clock representation). */
237
        LEGAL_ALT(0);
238
        /* FALLTHROUGH */
239
      case 'I':
240
        LEGAL_ALT(ALT_O);
241
        if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) return (0);
242
        if (tm->tm_hour == 12) tm->tm_hour = 0;
243
        break;
244

245
      case 'j': /* The day of year. */
246
        LEGAL_ALT(0);
247
        if (!(conv_num(&bp, &i, 1, 366))) return (0);
248
        tm->tm_yday = i - 1;
249
        break;
250

251
      case 'M': /* The minute. */
252
        LEGAL_ALT(ALT_O);
253
        if (!(conv_num(&bp, &tm->tm_min, 0, 59))) return (0);
254
        break;
255

256
      case 'm': /* The month. */
257
        LEGAL_ALT(ALT_O);
258
        if (!(conv_num(&bp, &i, 1, 12))) return (0);
259
        tm->tm_mon = i - 1;
260
        break;
261

262
      case 'p': /* The locale's equivalent of AM/PM. */
263
        LEGAL_ALT(0);
264
        /* AM? */
265
        if (strcmp(am_pm[0], bp) == 0) {
266
          if (tm->tm_hour > 11) return (0);
267

268
          bp += strlen(am_pm[0]);
269
          break;
270
        }
271
        /* PM? */
272
        else if (strcmp(am_pm[1], bp) == 0) {
273
          if (tm->tm_hour > 11) return (0);
274

275
          tm->tm_hour += 12;
276
          bp += strlen(am_pm[1]);
277
          break;
278
        }
279

280
        /* Nothing matched. */
281
        return (0);
282

283
      case 'S': /* The seconds. */
284
        LEGAL_ALT(ALT_O);
285
        if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) return (0);
286
        break;
287

288
      case 'U': /* The week of year, beginning on sunday. */
289
      case 'W': /* The week of year, beginning on monday. */
290
        LEGAL_ALT(ALT_O);
291
        /*
292
         * XXX This is bogus, as we can not assume any valid
293
         * information present in the tm structure at this
294
         * point to calculate a real value, so just check the
295
         * range for now.
296
         */
297
        if (!(conv_num(&bp, &i, 0, 53))) return (0);
298
        break;
299

300
      case 'w': /* The day of week, beginning on sunday. */
301
        LEGAL_ALT(ALT_O);
302
        if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) return (0);
303
        break;
304

305
      case 'Y': /* The year. */
306
        LEGAL_ALT(ALT_E);
307
        if (!(conv_num(&bp, &i, 0, 9999))) return (0);
308

309
        tm->tm_year = i - TM_YEAR_BASE;
310
        break;
311

312
      case 'y': /* The year within 100 years of the epoch. */
313
        LEGAL_ALT(ALT_E | ALT_O);
314
        if (!(conv_num(&bp, &i, 0, 99))) return (0);
315

316
        if (split_year) {
317
          tm->tm_year = ((tm->tm_year / 100) * 100) + i;
318
          break;
319
        }
320
        split_year = 1;
321
        if (i <= 68)
322
          tm->tm_year = i + 2000 - TM_YEAR_BASE;
323
        else
324
          tm->tm_year = i + 1900 - TM_YEAR_BASE;
325
        break;
326

327
        /*
328
         * Miscellaneous conversions.
329
         */
330
      case 'n': /* Any kind of white-space. */
331
      case 't':
332
        LEGAL_ALT(0);
333
        while (isspace(*bp)) bp++;
334
        break;
335

336
      default: /* Unknown/unsupported conversion. */
337
        return (0);
338
    }
339
  }
340

341
  /* LINTED functional specification */
342
  return ((char *)bp);
343
#else
344
  return strptime(buf, fmt, tm);
2,147,483,647✔
345
#endif
346
}
347

348
size_t taosStrfTime(char *s, size_t maxsize, char const *format, struct tm const *t) {
2,147,483,647✔
349
  if (!s || !format || !t) return 0;
2,147,483,647✔
350
  return strftime(s, maxsize, format, t);
2,147,483,647✔
351
}
352

353
int32_t taosGetTimeOfDay(struct timeval *tv) {
2,147,483,647✔
354
  if (tv == NULL) {
2,147,483,647✔
355
    return TSDB_CODE_INVALID_PARA;
970✔
356
  }
357
  int32_t code = 0;
2,147,483,647✔
358
#ifdef WINDOWS
359
  LARGE_INTEGER t;
360
  FILETIME      f;
361

362
  GetSystemTimeAsFileTime(&f);
363
  t.QuadPart = f.dwHighDateTime;
364
  t.QuadPart <<= 32;
365
  t.QuadPart |= f.dwLowDateTime;
366

367
  t.QuadPart -= TIMEEPOCH;
368
  tv->tv_sec = t.QuadPart / 10000000;
369
  tv->tv_usec = (t.QuadPart % 10000000) / 10;
370
  return 0;
371
#else
372
  code = gettimeofday(tv, NULL);
2,147,483,647✔
373
  return (-1 == code) ? (terrno = TAOS_SYSTEM_ERROR(ERRNO)) : 0;
2,147,483,647✔
374
#endif
375
}
376

377
int32_t taosTime(time_t *t) {
178,922,950✔
378
  if (t == NULL) {
178,922,950✔
379
    return TSDB_CODE_INVALID_PARA;
970✔
380
  }
381
  time_t r = time(t);
178,921,980✔
382
  if (r == (time_t)-1) {
178,921,980✔
383
    return TAOS_SYSTEM_ERROR(ERRNO);
×
384
  }
385
  return 0;
178,921,980✔
386
}
387

388
/*
389
 * mktime64 - Converts date to seconds.
390
 * Converts Gregorian date to seconds since 1970-01-01 00:00:00.
391
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
392
 * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
393
 *
394
 * [For the Julian calendar (which was used in Russia before 1917,
395
 * Britain & colonies before 1752, anywhere else before 1582,
396
 * and is still in use by some communities) leave out the
397
 * -year/100+year/400 terms, and add 10.]
398
 *
399
 * This algorithm was first published by Gauss (I think).
400
 *
401
 * A leap second can be indicated by calling this function with sec as
402
 * 60 (allowable under ISO 8601).  The leap second is treated the same
403
 * as the following second since they don't exist in UNIX time.
404
 *
405
 * An encoding of midnight at the end of the day as 24:00:00 - ie. midnight
406
 * tomorrow - (allowable under ISO 8601) is supported.
407
 */
408
int64_t user_mktime64(const uint32_t year, const uint32_t mon, const uint32_t day, const uint32_t hour,
1,940✔
409
                      const uint32_t min, const uint32_t sec, int64_t time_zone) {
410
  uint32_t _mon = mon, _year = year;
1,940✔
411

412
  /* 1..12 -> 11,12,1..10 */
413
  if (0 >= (int32_t)(_mon -= 2)) {
1,940✔
414
    _mon += 12; /* Puts Feb last since it has leap day */
970✔
415
    _year -= 1;
970✔
416
  }
417

418
  // int64_t _res = (((((int64_t) (_year/4 - _year/100 + _year/400 + 367*_mon/12 + day) +
419
  //                _year*365 - 719499)*24 + hour)*60 + min)*60 + sec);
420
  int64_t _res = 367 * ((int64_t)_mon) / 12;
1,940✔
421
  _res += _year / 4 - _year / 100 + _year / 400 + day + ((int64_t)_year) * 365 - 719499;
1,940✔
422
  _res *= 24;
1,940✔
423
  _res = ((_res + hour) * 60 + min) * 60 + sec;
1,940✔
424

425
  return _res + time_zone;
1,940✔
426
}
427

428
time_t taosMktime(struct tm *timep, timezone_t tz) {
2,147,483,647✔
429
#ifdef WINDOWS
430
  // Windows: 调用 getWindowsTimezoneOffset 获取时区偏移
431
  int64_t tzw = getWindowsTimezoneOffset();
432

433
  // 使用 user_mktime64 计算时间戳
434
  time_t result = user_mktime64(timep->tm_year + 1900, timep->tm_mon + 1, timep->tm_mday,
435
                                 timep->tm_hour, timep->tm_min, timep->tm_sec, tzw);
436

437
  // 如果结果合理,直接返回
438
  if (result > 0) {
439
    return result;
440
  }
441

442
  // 否则回退到系统 mktime
443
  return mktime(timep);
444
#elif defined(TD_ASTRA)
445
  time_t r =  mktime(timep);
446
  if (r == (time_t)-1) {
447
    terrno = TAOS_SYSTEM_ERROR(ERRNO);
448
  }
449
  return r;
2,147,483,647✔
450
#else
2,147,483,647✔
451
  if (tz == NULL) {
452
    tz = getGlobalDefaultTZ();
2,147,483,647✔
453
  }
2,147,483,647✔
UNCOV
454
  time_t r = (tz != NULL ? mktime_z(tz, timep) : mktime(timep));
×
455
  if (r == (time_t)-1) {
456
    terrno = TAOS_SYSTEM_ERROR(ERRNO);
2,147,483,647✔
457
  }
2,147,483,647✔
458
  timezone = -timep->tm_gmtoff;
459
  return r;
460
#endif
461
}
2,147,483,647✔
462

2,147,483,647✔
463
struct tm *taosGmTimeR(const time_t *timep, struct tm *result) {
2,040✔
464
  if (timep == NULL || result == NULL) {
465
    return NULL;
466
  }
467
#ifdef WINDOWS
468
  errno_t code = gmtime_s(result, timep);
469
  return (code == 0) ? result : NULL;
2,147,483,647✔
470
#else
471
  return gmtime_r(timep, result);
472
#endif
473
}
1,201,437,590✔
474

1,201,437,590✔
475
time_t taosTimeGm(struct tm *tmp) {
1,020✔
476
  if (tmp == NULL) {
477
    return -1;
478
  }
479
#ifdef WINDOWS
480
  return _mkgmtime(tmp);
481
#elif defined(TD_ASTRA)
482
  time_t    local = mktime(tmp);
483
  struct tm local_tm = *localtime(&local);
484
  struct tm utc_tm = *gmtime(&local);
485
  time_t    offset = (local_tm.tm_hour - utc_tm.tm_hour) * 3600 + (local_tm.tm_min - utc_tm.tm_min) * 60 +
486
                  (local_tm.tm_sec - utc_tm.tm_sec);
487
  return local - offset;
1,201,436,570✔
488
#else
489
  return timegm(tmp);
490
#endif
491
}
492

493
#ifdef WINDOWS
494
static int calcDayOfYear(int y, int m, int d) {
495
  static const int days[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
496
  return days[m - 1] + d + ((m > 2 && ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))) ? 1 : 0);
497
}
498
#endif
2,147,483,647✔
499

2,147,483,647✔
500
struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize, timezone_t tz) {
2,147,483,647✔
UNCOV
501
  struct tm *res = NULL;
×
502
  if (timep == NULL || result == NULL) {
503
    return NULL;
504
  }
505
#ifdef WINDOWS
506
  // Windows: 直接调用函数获取时区偏移,避免跨 DLL 的指针问题
507
  time_t adjusted_time = *timep;
508
  int64_t tz_offset = getWindowsTimezoneOffset();
509

510
  if (tz_offset != 0) {
511
    adjusted_time = *timep + (-tz_offset);
512
  }
513

514
  if (adjusted_time < -2208988800LL) {
515
    if (buf != NULL) {
516
      snprintf(buf, bufSize, "NaN");
517
    }
518
    return NULL;
519
  } else if (adjusted_time < 0) {
520
    SYSTEMTIME ss, s;
521
    FILETIME   ff, f;
522

523
    LARGE_INTEGER offset;
524
    struct tm     tm1;
525
    time_t        tt = 0;
526
    if (gmtime_s(&tm1, &tt) != 0) {
527
      if (buf != NULL) {
528
        snprintf(buf, bufSize, "NaN");
529
      }
530
      return NULL;
531
    }
532
    ss.wYear = tm1.tm_year + 1900;
533
    ss.wMonth = tm1.tm_mon + 1;
534
    ss.wDay = tm1.tm_mday;
535
    ss.wHour = tm1.tm_hour;
536
    ss.wMinute = tm1.tm_min;
537
    ss.wSecond = tm1.tm_sec;
538
    ss.wMilliseconds = 0;
539
    SystemTimeToFileTime(&ss, &ff);
540
    offset.QuadPart = ff.dwHighDateTime;
541
    offset.QuadPart <<= 32;
542
    offset.QuadPart |= ff.dwLowDateTime;
543
    offset.QuadPart += adjusted_time * 10000000;
544
    f.dwLowDateTime = offset.QuadPart & 0xffffffff;
545
    f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff;
546
    FileTimeToSystemTime(&f, &s);
547
    result->tm_sec = s.wSecond;
548
    result->tm_min = s.wMinute;
549
    result->tm_hour = s.wHour;
550
    result->tm_mday = s.wDay;
551
    result->tm_mon = s.wMonth - 1;
552
    result->tm_year = s.wYear - 1900;
553
    result->tm_wday = s.wDayOfWeek;
554
    result->tm_yday = calcDayOfYear(s.wYear, s.wMonth, s.wDay);
555
    result->tm_isdst = 0;
556
  } else {
557
    if (gmtime_s(result, &adjusted_time) != 0) {
558
      if (buf != NULL) {
559
        snprintf(buf, bufSize, "NaN");
560
      }
561
      return NULL;
562
    }
2,147,483,647✔
563
  }
2,147,483,647✔
564
  return result;
565
#elif defined(TD_ASTRA)
2,147,483,647✔
566
  res = localtime_r(timep, result);
2,147,483,647✔
UNCOV
567
  if (res == NULL && buf != NULL) {
×
568
    (void)sprintf(buf, "NaN");
569
  }
2,147,483,647✔
570
  return res;
2,147,483,647✔
571
#else
572
  if (tz == NULL) {
573
    tz = getGlobalDefaultTZ();
574
  }
2,147,483,647✔
575
  res = (tz != NULL ? localtime_rz(tz, timep, result) : localtime_r(timep, result));
576
  if (res == NULL && buf != NULL) {
2,147,483,647✔
577
    (void)snprintf(buf, bufSize, "NaN");
2,147,483,647✔
578
  }
579
  timezone = -result->tm_gmtoff;
580
  return res;
581
#endif
582
}
583

584
int32_t taosGetTimestampSec() { return (int32_t)time(NULL); }
585

586
int32_t taosClockGetTime(int clock_id, struct timespec *pTS) {
587
  int32_t code = 0;
588
#ifdef WINDOWS
589
  LARGE_INTEGER t;
590
  FILETIME      f;
591

592
  GetSystemTimeAsFileTime(&f);
2,147,483,647✔
593
  t.QuadPart = f.dwHighDateTime;
2,147,483,647✔
594
  t.QuadPart <<= 32;
595
  t.QuadPart |= f.dwLowDateTime;
596

597
  t.QuadPart -= TIMEEPOCH;
598
  pTS->tv_sec = t.QuadPart / 10000000;
599
  pTS->tv_nsec = (t.QuadPart % 10000000) * 100;
600
  return (0);
601
#else
602
  code = clock_gettime(clock_id, pTS);
603
  return (-1 == code) ? (terrno = TAOS_SYSTEM_ERROR(ERRNO)) : 0;
604
#endif
605
}
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