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

tarantool / luajit / 7262987147

19 Dec 2023 02:10PM UTC coverage: 88.225% (-0.4%) from 88.616%
7262987147

push

github

fckxorg
test: add tests for debugging extensions

This patch adds tests for LuaJIT debugging
extensions for lldb and gdb.

5336 of 5969 branches covered (0.0%)

Branch coverage included in aggregate %.

20475 of 23287 relevant lines covered (87.92%)

1285545.26 hits per line

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

93.33
/src/luajit.c
1
/*
2
** LuaJIT frontend. Runs commands, scripts, read-eval-print (REPL) etc.
3
** Copyright (C) 2005-2017 Mike Pall. See Copyright Notice in luajit.h
4
**
5
** Major portions taken verbatim or adapted from the Lua interpreter.
6
** Copyright (C) 1994-2008 Lua.org, PUC-Rio. See Copyright Notice in lua.h
7
*/
8

9
#include <stdio.h>
10
#include <stdlib.h>
11
#include <string.h>
12

13
#define luajit_c
14

15
#include "lua.h"
16
#include "lauxlib.h"
17
#include "lualib.h"
18
#include "luajit.h"
19

20
#include "lj_arch.h"
21

22
#if LJ_TARGET_POSIX
23
#include <unistd.h>
24
#define lua_stdin_is_tty()        isatty(0)
25
#elif LJ_TARGET_WINDOWS
26
#include <io.h>
27
#ifdef __BORLANDC__
28
#define lua_stdin_is_tty()        isatty(_fileno(stdin))
29
#else
30
#define lua_stdin_is_tty()        _isatty(_fileno(stdin))
31
#endif
32
#else
33
#define lua_stdin_is_tty()        1
34
#endif
35

36
#if !LJ_TARGET_CONSOLE
37
#include <signal.h>
38
#endif
39

40
static lua_State *globalL = NULL;
41
static const char *progname = LUA_PROGNAME;
42

43
#if !LJ_TARGET_CONSOLE
44
static void lstop(lua_State *L, lua_Debug *ar)
×
45
{
46
  (void)ar;  /* unused arg. */
×
47
  lua_sethook(L, NULL, 0, 0);
×
48
  /* Avoid luaL_error -- a C hook doesn't add an extra frame. */
49
  luaL_where(L, 0);
×
50
  lua_pushfstring(L, "%sinterrupted!", lua_tostring(L, -1));
×
51
  lua_error(L);
×
52
}
×
53

54
static void laction(int i)
×
55
{
56
  signal(i, SIG_DFL); /* if another SIGINT happens before lstop,
×
57
                         terminate process (default action) */
58
  lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
×
59
}
×
60
#endif
61

62
static void print_usage(void)
5✔
63
{
64
  fputs("usage: ", stderr);
5✔
65
  fputs(progname, stderr);
5✔
66
  fputs(" [options]... [script [args]...].\n"
5✔
67
  "Available options are:\n"
68
  "  -e chunk  Execute string " LUA_QL("chunk") ".\n"
69
  "  -l name   Require library " LUA_QL("name") ".\n"
70
  "  -b ...    Save or list bytecode.\n"
71
  "  -j cmd    Perform LuaJIT control command.\n"
72
  "  -O[opt]   Control LuaJIT optimizations.\n"
73
  "  -i        Enter interactive mode after executing " LUA_QL("script") ".\n"
74
  "  -v        Show version information.\n"
75
  "  -E        Ignore environment variables.\n"
76
  "  --        Stop handling options.\n"
77
  "  -         Execute stdin and stop handling options.\n", stderr);
78
  fflush(stderr);
5✔
79
}
5✔
80

81
static void l_message(const char *pname, const char *msg)
6✔
82
{
83
  if (pname) { fputs(pname, stderr); fputc(':', stderr); fputc(' ', stderr); }
6✔
84
  fputs(msg, stderr); fputc('\n', stderr);
6✔
85
  fflush(stderr);
6✔
86
}
6✔
87

88
static int report(lua_State *L, int status)
502✔
89
{
90
  if (status && !lua_isnil(L, -1)) {
502✔
91
    const char *msg = lua_tostring(L, -1);
5✔
92
    if (msg == NULL) msg = "(error object is not a string)";
5✔
93
    l_message(progname, msg);
5✔
94
    lua_pop(L, 1);
5✔
95
  }
96
  return status;
502✔
97
}
98

99
static int traceback(lua_State *L)
3✔
100
{
101
  if (!lua_isstring(L, 1)) { /* Non-string error object? Try metamethod. */
3✔
102
    if (lua_isnoneornil(L, 1) ||
2✔
103
        !luaL_callmeta(L, 1, "__tostring") ||
1✔
104
        !lua_isstring(L, -1))
×
105
      return 1;  /* Return non-string error object. */
1✔
106
    lua_remove(L, 1);  /* Replace object by result of __tostring metamethod. */
×
107
  }
108
  luaL_traceback(L, L, lua_tostring(L, 1), 1);
2✔
109
  return 1;
2✔
110
}
111

112
static int docall(lua_State *L, int narg, int clear)
450✔
113
{
114
  int status;
450✔
115
  int base = lua_gettop(L) - narg;  /* function index */
450✔
116
  lua_pushcfunction(L, traceback);  /* push traceback function */
450✔
117
  lua_insert(L, base);  /* put it under chunk and args */
450✔
118
#if !LJ_TARGET_CONSOLE
119
  signal(SIGINT, laction);
450✔
120
#endif
121
  status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base);
652✔
122
#if !LJ_TARGET_CONSOLE
123
  signal(SIGINT, SIG_DFL);
349✔
124
#endif
125
  lua_remove(L, base);  /* remove traceback function */
349✔
126
  /* force a complete garbage collection in case of errors */
127
  if (status != LUA_OK) lua_gc(L, LUA_GCCOLLECT, 0);
349✔
128
  return status;
349✔
129
}
130

131
static void print_version(void)
8✔
132
{
133
  fputs(LUAJIT_VERSION " -- " LUAJIT_COPYRIGHT ". " LUAJIT_URL "\n", stdout);
8✔
134
}
8✔
135

136
static void print_jit_status(lua_State *L)
4✔
137
{
138
  int n;
4✔
139
  const char *s;
4✔
140
  lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
4✔
141
  lua_getfield(L, -1, "jit");  /* Get jit.* module table. */
4✔
142
  lua_remove(L, -2);
4✔
143
  lua_getfield(L, -1, "status");
4✔
144
  lua_remove(L, -2);
4✔
145
  n = lua_gettop(L);
4✔
146
  lua_call(L, 0, LUA_MULTRET);
4✔
147
  fputs(lua_toboolean(L, n) ? "JIT: ON" : "JIT: OFF", stdout);
4✔
148
  for (n++; (s = lua_tostring(L, n)); n++) {
60✔
149
    putc(' ', stdout);
56✔
150
    fputs(s, stdout);
56✔
151
  }
152
  putc('\n', stdout);
4✔
153
}
4✔
154

155
static void createargtable(lua_State *L, char **argv, int argc, int argf)
227✔
156
{
157
  int i;
227✔
158
  lua_createtable(L, argc - argf, argf);
227✔
159
  for (i = 0; i < argc; i++) {
1,466✔
160
    lua_pushstring(L, argv[i]);
1,012✔
161
    lua_rawseti(L, -2, i - argf);
1,012✔
162
  }
163
  lua_setglobal(L, "arg");
227✔
164
}
227✔
165

166
static int dofile(lua_State *L, const char *name)
1✔
167
{
168
  int status = luaL_loadfile(L, name) || docall(L, 0, 1);
1✔
169
  return report(L, status);
1✔
170
}
171

172
static int dostring(lua_State *L, const char *s, const char *name)
192✔
173
{
174
  int status = luaL_loadbuffer(L, s, strlen(s), name) || docall(L, 0, 1);
192✔
175
  return report(L, status);
189✔
176
}
177

178
static int dolibrary(lua_State *L, const char *name)
57✔
179
{
180
  lua_getglobal(L, "require");
57✔
181
  lua_pushstring(L, name);
57✔
182
  return report(L, docall(L, 1, 1));
57✔
183
}
184

185
static void write_prompt(lua_State *L, int firstline)
131✔
186
{
187
  const char *p;
131✔
188
  lua_getfield(L, LUA_GLOBALSINDEX, firstline ? "_PROMPT" : "_PROMPT2");
240✔
189
  p = lua_tostring(L, -1);
131✔
190
  if (p == NULL) p = firstline ? LUA_PROMPT : LUA_PROMPT2;
131✔
191
  fputs(p, stdout);
131✔
192
  fflush(stdout);
131✔
193
  lua_pop(L, 1);  /* remove global */
131✔
194
}
131✔
195

196
static int incomplete(lua_State *L, int status)
127✔
197
{
198
  if (status == LUA_ERRSYNTAX) {
127✔
199
    size_t lmsg;
109✔
200
    const char *msg = lua_tolstring(L, -1, &lmsg);
109✔
201
    const char *tp = msg + lmsg - (sizeof(LUA_QL("<eof>")) - 1);
109✔
202
    if (strstr(msg, LUA_QL("<eof>")) == tp) {
109✔
203
      lua_pop(L, 1);
109✔
204
      return 1;
109✔
205
    }
206
  }
207
  return 0;  /* else... */
208
}
209

210
static int pushline(lua_State *L, int firstline)
131✔
211
{
212
  char buf[LUA_MAXINPUT];
131✔
213
  write_prompt(L, firstline);
131✔
214
  if (fgets(buf, LUA_MAXINPUT, stdin)) {
131✔
215
    size_t len = strlen(buf);
127✔
216
    if (len > 0 && buf[len-1] == '\n')
127✔
217
      buf[len-1] = '\0';
125✔
218
    if (firstline && buf[0] == '=')
127✔
219
      lua_pushfstring(L, "return %s", buf+1);
5✔
220
    else
221
      lua_pushstring(L, buf);
122✔
222
    return 1;
127✔
223
  }
224
  return 0;
225
}
226

227
static int loadline(lua_State *L)
22✔
228
{
229
  int status;
22✔
230
  lua_settop(L, 0);
22✔
231
  if (!pushline(L, 1))
22✔
232
    return -1;  /* no input */
233
  for (;;) {  /* repeat until gets a complete line */
236✔
234
    status = luaL_loadbuffer(L, lua_tostring(L, 1), lua_strlen(L, 1), "=stdin");
127✔
235
    if (!incomplete(L, status)) break;  /* cannot try to add lines? */
127✔
236
    if (!pushline(L, 0))  /* no more input? */
109✔
237
      return -1;
238
    lua_pushliteral(L, "\n");  /* add a new line... */
109✔
239
    lua_insert(L, -2);  /* ...between the two lines */
109✔
240
    lua_concat(L, 3);  /* join them */
109✔
241
  }
242
  lua_remove(L, 1);  /* remove line */
18✔
243
  return status;
18✔
244
}
245

246
static void dotty(lua_State *L)
4✔
247
{
248
  int status;
4✔
249
  const char *oldprogname = progname;
4✔
250
  progname = NULL;
4✔
251
  while ((status = loadline(L)) != -1) {
26✔
252
    if (status == LUA_OK) status = docall(L, 0, 0);
18✔
253
    report(L, status);
18✔
254
    if (status == LUA_OK && lua_gettop(L) > 0) {  /* any result to print? */
18✔
255
      lua_getglobal(L, "print");
5✔
256
      lua_insert(L, 1);
5✔
257
      if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != 0)
5✔
258
        l_message(progname,
×
259
          lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)",
260
                              lua_tostring(L, -1)));
261
    }
262
  }
263
  lua_settop(L, 0);  /* clear stack */
4✔
264
  fputs("\n", stdout);
4✔
265
  fflush(stdout);
4✔
266
  progname = oldprogname;
4✔
267
}
4✔
268

269
static int handle_script(lua_State *L, char **argx)
185✔
270
{
271
  int status;
185✔
272
  const char *fname = argx[0];
185✔
273
  if (strcmp(fname, "-") == 0 && strcmp(argx[-1], "--") != 0)
185✔
274
    fname = NULL;  /* stdin */
4✔
275
  status = luaL_loadfile(L, fname);
185✔
276
  if (status == LUA_OK) {
185✔
277
    /* Fetch args from arg table. LUA_INIT or -e might have changed them. */
278
    int narg = 0;
184✔
279
    lua_getglobal(L, "arg");
184✔
280
    if (lua_istable(L, -1)) {
184✔
281
      do {
243✔
282
        narg++;
243✔
283
        lua_rawgeti(L, -narg, narg);
243✔
284
      } while (!lua_isnil(L, -1));
243✔
285
      lua_pop(L, 1);
184✔
286
      lua_remove(L, -narg);
184✔
287
      narg--;
184✔
288
    } else {
289
      lua_pop(L, 1);
×
290
    }
291
    status = docall(L, narg, 0);
184✔
292
  }
293
  return report(L, status);
87✔
294
}
295

296
/* Load add-on module. */
297
static int loadjitmodule(lua_State *L)
10✔
298
{
299
  lua_getglobal(L, "require");
10✔
300
  lua_pushliteral(L, "jit.");
10✔
301
  lua_pushvalue(L, -3);
10✔
302
  lua_concat(L, 2);
10✔
303
  if (lua_pcall(L, 1, 1, 0)) {
10✔
304
    const char *msg = lua_tostring(L, -1);
1✔
305
    if (msg && !strncmp(msg, "module ", 7))
1✔
306
      goto nomodule;
1✔
307
    return report(L, 1);
×
308
  }
309
  lua_getfield(L, -1, "start");
9✔
310
  if (lua_isnil(L, -1)) {
9✔
311
  nomodule:
×
312
    l_message(progname,
1✔
313
              "unknown luaJIT command or jit.* modules not installed");
314
    return 1;
1✔
315
  }
316
  lua_remove(L, -2);  /* Drop module table. */
9✔
317
  return 0;
9✔
318
}
319

320
/* Run command with options. */
321
static int runcmdopt(lua_State *L, const char *opt)
10✔
322
{
323
  int narg = 0;
10✔
324
  if (opt && *opt) {
10✔
325
    for (;;) {  /* Split arguments. */
10✔
326
      const char *p = strchr(opt, ',');
8✔
327
      narg++;
8✔
328
      if (!p) break;
8✔
329
      if (p == opt)
2✔
330
        lua_pushnil(L);
×
331
      else
332
        lua_pushlstring(L, opt, (size_t)(p - opt));
2✔
333
      opt = p + 1;
2✔
334
    }
335
    if (*opt)
6✔
336
      lua_pushstring(L, opt);
6✔
337
    else
338
      lua_pushnil(L);
×
339
  }
340
  return report(L, lua_pcall(L, narg, 0, 0));
10✔
341
}
342

343
/* JIT engine control command: try jit library first or load add-on module. */
344
static int dojitcmd(lua_State *L, const char *cmd)
4✔
345
{
346
  const char *opt = strchr(cmd, '=');
4✔
347
  lua_pushlstring(L, cmd, opt ? (size_t)(opt - cmd) : strlen(cmd));
4✔
348
  lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
4✔
349
  lua_getfield(L, -1, "jit");  /* Get jit.* module table. */
4✔
350
  lua_remove(L, -2);
4✔
351
  lua_pushvalue(L, -2);
4✔
352
  lua_gettable(L, -2);  /* Lookup library function. */
4✔
353
  if (!lua_isfunction(L, -1)) {
4✔
354
    lua_pop(L, 2);  /* Drop non-function and jit.* table, keep module name. */
1✔
355
    if (loadjitmodule(L))
1✔
356
      return 1;
357
  } else {
358
    lua_remove(L, -2);  /* Drop jit.* table. */
3✔
359
  }
360
  lua_remove(L, -2);  /* Drop module name. */
3✔
361
  return runcmdopt(L, opt ? opt+1 : opt);
3✔
362
}
363

364
/* Optimization flags. */
365
static int dojitopt(lua_State *L, const char *opt)
7✔
366
{
367
  lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
7✔
368
  lua_getfield(L, -1, "jit.opt");  /* Get jit.opt.* module table. */
7✔
369
  lua_remove(L, -2);
7✔
370
  lua_getfield(L, -1, "start");
7✔
371
  lua_remove(L, -2);
7✔
372
  return runcmdopt(L, opt);
7✔
373
}
374

375
/* Save or list bytecode. */
376
static int dobytecode(lua_State *L, char **argv)
9✔
377
{
378
  int narg = 0;
9✔
379
  lua_pushliteral(L, "bcsave");
9✔
380
  if (loadjitmodule(L))
9✔
381
    return 1;
382
  if (argv[0][2]) {
9✔
383
    narg++;
4✔
384
    argv[0][1] = '-';
4✔
385
    lua_pushstring(L, argv[0]+1);
4✔
386
  }
387
  for (argv++; *argv != NULL; narg++, argv++)
29✔
388
    lua_pushstring(L, *argv);
20✔
389
  report(L, lua_pcall(L, narg, 0, 0));
9✔
390
  return -1;
9✔
391
}
392

393
/* check that argument has no extra characters at the end */
394
#define notail(x)        {if ((x)[2] != '\0') return -1;}
395

396
#define FLAGS_INTERACTIVE        1
397
#define FLAGS_VERSION                2
398
#define FLAGS_EXEC                4
399
#define FLAGS_OPTION                8
400
#define FLAGS_NOENV                16
401

402
static int collectargs(char **argv, int *flags)
232✔
403
{
404
  int i;
232✔
405
  for (i = 1; argv[i] != NULL; i++) {
501✔
406
    if (argv[i][0] != '-')  /* Not an option? */
471✔
407
      return i;
182✔
408
    switch (argv[i][1]) {  /* Check option. */
289✔
409
    case '-':
3✔
410
      notail(argv[i]);
3✔
411
      return i+1;
2✔
412
    case '\0':
413
      return i;
414
    case 'i':
4✔
415
      notail(argv[i]);
4✔
416
      *flags |= FLAGS_INTERACTIVE;
4✔
417
      /* fallthrough */
418
    case 'v':
8✔
419
      notail(argv[i]);
8✔
420
      *flags |= FLAGS_VERSION;
8✔
421
      break;
8✔
422
    case 'e':
194✔
423
      *flags |= FLAGS_EXEC;
194✔
424
      /* fallthrough */
425
    case 'j':  /* LuaJIT extension */
255✔
426
    case 'l':
427
      *flags |= FLAGS_OPTION;
255✔
428
      if (argv[i][2] == '\0') {
255✔
429
        i++;
241✔
430
        if (argv[i] == NULL) return -1;
241✔
431
      }
432
      break;
433
    case 'O': break;  /* LuaJIT extension */
434
    case 'b':  /* LuaJIT extension */
9✔
435
      if (*flags) return -1;
9✔
436
      *flags |= FLAGS_EXEC;
9✔
437
      return i+1;
9✔
438
    case 'E':
1✔
439
      *flags |= FLAGS_NOENV;
1✔
440
      break;
1✔
441
    default: return -1;  /* invalid option */
442
    }
443
  }
444
  return i;
445
}
446

447
static int runargs(lua_State *L, char **argv, int argn)
227✔
448
{
449
  int i;
227✔
450
  for (i = 1; i < argn; i++) {
490✔
451
    if (argv[i] == NULL) continue;
280✔
452
    lua_assert(argv[i][0] == '-');
280✔
453
    switch (argv[i][1]) {
280✔
454
    case 'e': {
192✔
455
      const char *chunk = argv[i] + 2;
192✔
456
      if (*chunk == '\0') chunk = argv[++i];
192✔
457
      lua_assert(chunk != NULL);
192✔
458
      if (dostring(L, chunk, "=(command line)") != 0)
192✔
459
        return 1;
460
      break;
461
      }
462
    case 'l': {
57✔
463
      const char *filename = argv[i] + 2;
57✔
464
      if (*filename == '\0') filename = argv[++i];
57✔
465
      lua_assert(filename != NULL);
57✔
466
      if (dolibrary(L, filename))
57✔
467
        return 1;
468
      break;
469
      }
470
    case 'j': {  /* LuaJIT extension. */
4✔
471
      const char *cmd = argv[i] + 2;
4✔
472
      if (*cmd == '\0') cmd = argv[++i];
4✔
473
      lua_assert(cmd != NULL);
4✔
474
      if (dojitcmd(L, cmd))
4✔
475
        return 1;
476
      break;
477
      }
478
    case 'O':  /* LuaJIT extension. */
7✔
479
      if (dojitopt(L, argv[i] + 2))
7✔
480
        return 1;
481
      break;
482
    case 'b':  /* LuaJIT extension. */
9✔
483
      return dobytecode(L, argv+i);
9✔
484
    default: break;
485
    }
486
  }
487
  return LUA_OK;
488
}
489

490
static int handle_luainit(lua_State *L)
226✔
491
{
492
#if LJ_TARGET_CONSOLE
493
  const char *init = NULL;
494
#else
495
  const char *init = getenv(LUA_INIT);
226✔
496
#endif
497
  if (init == NULL)
226✔
498
    return LUA_OK;
499
  else if (init[0] == '@')
×
500
    return dofile(L, init+1);
×
501
  else
502
    return dostring(L, init, "=" LUA_INIT);
×
503
}
504

505
static struct Smain {
506
  char **argv;
507
  int argc;
508
  int status;
509
} smain;
510

511
static int pmain(lua_State *L)
232✔
512
{
513
  struct Smain *s = &smain;
232✔
514
  char **argv = s->argv;
232✔
515
  int argn;
232✔
516
  int flags = 0;
232✔
517
  globalL = L;
232✔
518
  if (argv[0] && argv[0][0]) progname = argv[0];
232✔
519

520
  LUAJIT_VERSION_SYM();  /* Linker-enforced version check. */
232✔
521

522
  argn = collectargs(argv, &flags);
232✔
523
  if (argn < 0) {  /* Invalid args? */
232✔
524
    print_usage();
5✔
525
    s->status = 1;
5✔
526
    return 0;
5✔
527
  }
528

529
  if ((flags & FLAGS_NOENV)) {
227✔
530
    lua_pushboolean(L, 1);
1✔
531
    lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
1✔
532
  }
533

534
  /* Stop collector during library initialization. */
535
  lua_gc(L, LUA_GCSTOP, 0);
227✔
536
  luaL_openlibs(L);
227✔
537
  lua_gc(L, LUA_GCRESTART, -1);
227✔
538

539
  createargtable(L, argv, s->argc, argn);
227✔
540

541
  if (!(flags & FLAGS_NOENV)) {
227✔
542
    s->status = handle_luainit(L);
226✔
543
    if (s->status != LUA_OK) return 0;
226✔
544
  }
545

546
  if ((flags & FLAGS_VERSION)) print_version();
227✔
547

548
  s->status = runargs(L, argv, argn);
227✔
549
  if (s->status != LUA_OK) return 0;
224✔
550

551
  if (s->argc > argn) {
210✔
552
    s->status = handle_script(L, argv + argn);
185✔
553
    if (s->status != LUA_OK) return 0;
87✔
554
  }
555

556
  if ((flags & FLAGS_INTERACTIVE)) {
111✔
557
    print_jit_status(L);
4✔
558
    dotty(L);
4✔
559
  } else if (s->argc == argn && !(flags & (FLAGS_EXEC|FLAGS_VERSION))) {
107✔
560
    if (lua_stdin_is_tty()) {
1✔
561
      print_version();
×
562
      print_jit_status(L);
×
563
      dotty(L);
×
564
    } else {
565
      dofile(L, NULL);  /* Executes stdin as a file. */
1✔
566
    }
567
  }
568
  return 0;
569
}
570

571
int main(int argc, char **argv)
232✔
572
{
573
  int status;
232✔
574
  lua_State *L = lua_open();
232✔
575
  if (L == NULL) {
232✔
576
    l_message(argv[0], "cannot create state: not enough memory");
×
577
    return EXIT_FAILURE;
×
578
  }
579
  smain.argc = argc;
232✔
580
  smain.argv = argv;
232✔
581
  status = lua_cpcall(L, pmain, NULL);
232✔
582
  report(L, status);
131✔
583
  lua_close(L);
131✔
584
  return (status || smain.status > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
131✔
585
}
586

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

© 2025 Coveralls, Inc